From 5fee5ec362f7a243f459e6378fd49dfc89dc9fb5 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Tue, 18 Jun 2019 20:42:10 +0200 Subject: d: Import dmd b8384668f, druntime e6caaab9, phobos 5ab9ad256 (v2.098.0-beta.1) The D front-end is now itself written in D, in order to build GDC, you will need a working GDC compiler (GCC version 9.1 or later). GCC changes: - Add support for bootstrapping the D front-end. These add the required components in order to have a D front-end written in D itself. Because the compiler front-end only depends on the core runtime modules, only libdruntime is built for the bootstrap stages. D front-end changes: - Import dmd v2.098.0-beta.1. Druntime changes: - Import druntime v2.098.0-beta.1. Phobos changes: - Import phobos v2.098.0-beta.1. The jump from v2.076.1 to v2.098.0 covers nearly 4 years worth of development on the D programming language and run-time libraries. ChangeLog: * Makefile.def: Add bootstrap to libbacktrace, libphobos, zlib, and libatomic. * Makefile.in: Regenerate. * Makefile.tpl (POSTSTAGE1_HOST_EXPORTS): Fix command for GDC. (STAGE1_CONFIGURE_FLAGS): Add --with-libphobos-druntime-only if target-libphobos-bootstrap. (STAGE2_CONFIGURE_FLAGS): Likewise. * configure: Regenerate. * configure.ac: Add support for bootstrapping D front-end. config/ChangeLog: * acx.m4 (ACX_PROG_GDC): New m4 function. gcc/ChangeLog: * Makefile.in (GDC): New variable. (GDCFLAGS): New variable. * configure: Regenerate. * configure.ac: Add call to ACX_PROG_GDC. Substitute GDCFLAGS. gcc/d/ChangeLog: * dmd/MERGE: Merge upstream dmd b8384668f. * Make-lang.in (d-warn): Use strict warnings. (DMD_WARN_CXXFLAGS): Remove. (DMD_COMPILE): Remove. (CHECKING_DFLAGS): Define. (WARN_DFLAGS): Define. (ALL_DFLAGS): Define. (DCOMPILE.base): Define. (DCOMPILE): Define. (DPOSTCOMPILE): Define. (DLINKER): Define. (DLLINKER): Define. (D_FRONTEND_OBJS): Add new dmd front-end objects. (D_GENERATED_SRCS): Remove. (D_GENERATED_OBJS): Remove. (D_ALL_OBJS): Remove D_GENERATED_OBJS. (d21$(exeext)): Build using DLLINKER and -static-libphobos. (d.tags): Remove dmd/*.c and dmd/root/*.c. (d.mostlyclean): Remove D_GENERATED_SRCS, d/idgen$(build_exeext), d/impcnvgen$(build_exeext). (D_INCLUDES): Include $(srcdir)/d/dmd/res. (CFLAGS-d/id.o): Remove. (CFLAGS-d/impcnvtab.o): Remove. (d/%.o): Build using DCOMPILE and DPOSTCOMPILE. Update dependencies from d/dmd/%.c to d/dmd/%.d. (d/idgen$(build_exeext)): Remove. (d/impcnvgen$(build_exeext)): Remove. (d/id.c): Remove. (d/id.h): Remove. (d/impcnvtab.c): Remove. (d/%.dmdgen.o): Remove. (D_SYSTEM_H): Remove. (d/idgen.dmdgen.o): Remove. (d/impcnvgen.dmdgen.o): Remove. * config-lang.in (boot_language): New variable. * d-attribs.cc: Include dmd/expression.h. * d-builtins.cc: Include d-frontend.h. (build_frontend_type): Update for new front-end interface. (d_eval_constant_expression): Likewise. (d_build_builtins_module): Likewise. (maybe_set_builtin_1): Likewise. (d_build_d_type_nodes): Likewise. * d-codegen.cc (d_decl_context): Likewise. (declaration_reference_p): Likewise. (declaration_type): Likewise. (parameter_reference_p): Likewise. (parameter_type): Likewise. (get_array_length): Likewise. (build_delegate_cst): Likewise. (build_typeof_null_value): Likewise. (identity_compare_p): Likewise. (lower_struct_comparison): Likewise. (build_filename_from_loc): Likewise. (build_assert_call): Remove LIBCALL_SWITCH_ERROR. (build_bounds_index_condition): Call LIBCALL_ARRAYBOUNDS_INDEXP on bounds error. (build_bounds_slice_condition): Call LIBCALL_ARRAYBOUNDS_SLICEP on bounds error. (array_bounds_check): Update for new front-end interface. (checkaction_trap_p): Handle CHECKACTION_context. (get_function_type): Update for new front-end interface. (d_build_call): Likewise. * d-compiler.cc: Remove include of dmd/scope.h. (Compiler::genCmain): Remove. (Compiler::paintAsType): Update for new front-end interface. (Compiler::onParseModule): Likewise. * d-convert.cc (convert_expr): Remove call to LIBCALL_ARRAYCAST. (convert_for_rvalue): Update for new front-end interface. (convert_for_assignment): Likewise. (convert_for_condition): Likewise. (d_array_convert): Likewise. * d-diagnostic.cc (error): Remove. (errorSupplemental): Remove. (warning): Remove. (warningSupplemental): Remove. (deprecation): Remove. (deprecationSupplemental): Remove. (message): Remove. (vtip): New. * d-frontend.cc (global): Remove. (Global::_init): Remove. (Global::startGagging): Remove. (Global::endGagging): Remove. (Global::increaseErrorCount): Remove. (Loc::Loc): Remove. (Loc::toChars): Remove. (Loc::equals): Remove. (isBuiltin): Update for new front-end interface. (eval_builtin): Likewise. (getTypeInfoType): Likewise. (inlineCopy): Remove. * d-incpath.cc: Include d-frontend.h. (add_globalpaths): Call d_gc_malloc to allocate Strings. (add_filepaths): Likewise. * d-lang.cc: Include dmd/id.h, dmd/root/file.h, d-frontend.h. Remove include of dmd/mars.h, id.h. (entrypoint_module): Remove. (entrypoint_root_module): Remove. (deps_write_string): Update for new front-end interface. (deps_write): Likewise. (d_init_options): Call rt_init. Remove setting global params that are default initialized by the front-end. (d_handle_option): Handle OPT_fcheckaction_, OPT_fdump_c___spec_, OPT_fdump_c___spec_verbose, OPT_fextern_std_, OPT_fpreview, OPT_revert, OPT_fsave_mixins_, and OPT_ftransition. (d_post_options): Propagate dip1021 and dip1000 preview flags to dip25, and flag_diagnostics_show_caret to printErrorContext. (d_add_entrypoint_module): Remove. (d_parse_file): Update for new front-end interface. (d_type_promotes_to): Likewise. (d_types_compatible_p): Likewise. * d-longdouble.cc (CTFloat::zero): Remove. (CTFloat::one): Remove. (CTFloat::minusone): Remove. (CTFloat::half): Remove. * d-system.h (POSIX): Remove. (realpath): Remove. (isalpha): Remove. (isalnum): Remove. (isdigit): Remove. (islower): Remove. (isprint): Remove. (isspace): Remove. (isupper): Remove. (isxdigit): Remove. (tolower): Remove. (_mkdir): Remove. (INT32_MAX): Remove. (INT32_MIN): Remove. (INT64_MIN): Remove. (UINT32_MAX): Remove. (UINT64_MAX): Remove. * d-target.cc: Include calls.h. (target): Remove. (define_float_constants): Remove initialization of snan. (Target::_init): Update for new front-end interface. (Target::isVectorTypeSupported): Likewise. (Target::isVectorOpSupported): Remove cases for unordered operators. (TargetCPP::typeMangle): Update for new front-end interface. (TargetCPP::parameterType): Likewise. (Target::systemLinkage): Likewise. (Target::isReturnOnStack): Likewise. (Target::isCalleeDestroyingArgs): Define. (Target::preferPassByRef): Define. * d-tree.h (d_add_entrypoint_module): Remove. * decl.cc (gcc_attribute_p): Update for new front-end interface. (apply_pragma_crt): Define. (DeclVisitor::visit(PragmaDeclaration *)): Handle pragmas crt_constructor and crt_destructor. (DeclVisitor::visit(TemplateDeclaration *)): Update for new front-end interface. (DeclVisitor::visit): Likewise. (DeclVisitor::finish_vtable): Likewise. (get_symbol_decl): Error if template has more than one nesting context. Update for new front-end interface. (make_thunk): Update for new front-end interface. (get_vtable_decl): Likewise. * expr.cc (ExprVisitor::visit): Likewise. (build_return_dtor): Likewise. * imports.cc (ImportVisitor::visit): Likewise. * intrinsics.cc: Include dmd/expression.h. Remove include of dmd/mangle.h. (maybe_set_intrinsic): Update for new front-end interface. * intrinsics.def (INTRINSIC_ROL): Update intrinsic signature. (INTRINSIC_ROR): Likewise. (INTRINSIC_ROR_TIARG): Likewise. (INTRINSIC_TOPREC): Likewise. (INTRINSIC_TOPRECL): Likewise. (INTRINSIC_TAN): Update intrinsic module and signature. (INTRINSIC_ISNAN): Likewise. (INTRINSIC_ISFINITE): Likewise. (INTRINSIC_COPYSIGN): Define intrinsic. (INTRINSIC_COPYSIGNI): Define intrinsic. (INTRINSIC_EXP): Update intrinsic module. (INTRINSIC_EXPM1): Likewise. (INTRINSIC_EXP2): Likewise. (INTRINSIC_LOG): Likewise. (INTRINSIC_LOG2): Likewise. (INTRINSIC_LOG10): Likewise. (INTRINSIC_POW): Likewise. (INTRINSIC_ROUND): Likewise. (INTRINSIC_FLOORF): Likewise. (INTRINSIC_FLOOR): Likewise. (INTRINSIC_FLOORL): Likewise. (INTRINSIC_CEILF): Likewise. (INTRINSIC_CEIL): Likewise. (INTRINSIC_CEILL): Likewise. (INTRINSIC_TRUNC): Likewise. (INTRINSIC_FMIN): Likewise. (INTRINSIC_FMAX): Likewise. (INTRINSIC_FMA): Likewise. (INTRINSIC_VA_ARG): Update intrinsic signature. (INTRINSIC_VASTART): Likewise. * lang.opt (fcheck=): Add alternate aliases for contract switches. (fcheckaction=): New option. (check_action): New Enum and EnumValue entries. (fdump-c++-spec-verbose): New option. (fdump-c++-spec=): New option. (fextern-std=): New option. (extern_stdcpp): New Enum and EnumValue entries (fpreview=): New options. (frevert=): New options. (fsave-mixins): New option. (ftransition=): Update options. * modules.cc (get_internal_fn): Replace Prot with Visibility. (build_internal_fn): Likewise. (build_dso_cdtor_fn): Likewise. (build_module_tree): Remove check for __entrypoint module. * runtime.def (P5): Define. (ARRAYBOUNDS_SLICEP): Define. (ARRAYBOUNDS_INDEXP): Define. (NEWTHROW): Define. (ADCMP2): Remove. (ARRAYCAST): Remove. (SWITCH_STRING): Remove. (SWITCH_USTRING): Remove. (SWITCH_DSTRING): Remove. (SWITCH_ERROR): Remove. * toir.cc (IRVisitor::visit): Update for new front-end interface. (IRVisitor::check_previous_goto): Remove checks for case and default statements. (IRVisitor::visit(SwitchStatement *)): Remove handling of string switch conditions. * typeinfo.cc: Include d-frontend.h. (get_typeinfo_kind): Update for new front-end interface. (make_frontend_typeinfo): Likewise. (TypeInfoVisitor::visit): Likewise. (builtin_typeinfo_p): Likewise. (get_typeinfo_decl): Likewise. (build_typeinfo): Likewise. * types.cc (valist_array_p): Likewise. (make_array_type): Likewise. (merge_aggregate_types): Likewise. (TypeVisitor::visit(TypeBasic *)): Likewise. (TypeVisitor::visit(TypeFunction *)): Likewise. (TypeVisitor::visit(TypeStruct *)): Update comment. * verstr.h: Removed. * d-frontend.h: New file. gcc/po/ChangeLog: * EXCLUDES: Remove d/dmd sources from list. gcc/testsuite/ChangeLog: * gdc.dg/Wcastresult2.d: Update test. * gdc.dg/asm1.d: Likewise. * gdc.dg/asm2.d: Likewise. * gdc.dg/asm3.d: Likewise. * gdc.dg/gdc282.d: Likewise. * gdc.dg/imports/gdc170.d: Likewise. * gdc.dg/intrinsics.d: Likewise. * gdc.dg/pr101672.d: Likewise. * gdc.dg/pr90650a.d: Likewise. * gdc.dg/pr90650b.d: Likewise. * gdc.dg/pr94777a.d: Likewise. * gdc.dg/pr95250.d: Likewise. * gdc.dg/pr96869.d: Likewise. * gdc.dg/pr98277.d: Likewise. * gdc.dg/pr98457.d: Likewise. * gdc.dg/simd1.d: Likewise. * gdc.dg/simd2a.d: Likewise. * gdc.dg/simd2b.d: Likewise. * gdc.dg/simd2c.d: Likewise. * gdc.dg/simd2d.d: Likewise. * gdc.dg/simd2e.d: Likewise. * gdc.dg/simd2f.d: Likewise. * gdc.dg/simd2g.d: Likewise. * gdc.dg/simd2h.d: Likewise. * gdc.dg/simd2i.d: Likewise. * gdc.dg/simd2j.d: Likewise. * gdc.dg/simd7951.d: Likewise. * gdc.dg/torture/gdc309.d: Likewise. * gdc.dg/torture/pr94424.d: Likewise. * gdc.dg/torture/pr94777b.d: Likewise. * lib/gdc-utils.exp (gdc-convert-args): Handle new compiler options. (gdc-convert-test): Handle CXXFLAGS, EXTRA_OBJC_SOURCES, and ARG_SETS test directives. (gdc-do-test): Only import modules in the test run directory. * gdc.dg/pr94777c.d: New test. * gdc.dg/pr96156b.d: New test. * gdc.dg/pr96157c.d: New test. * gdc.dg/simd_ctfe.d: New test. * gdc.dg/torture/simd17344.d: New test. * gdc.dg/torture/simd20052.d: New test. * gdc.dg/torture/simd6.d: New test. * gdc.dg/torture/simd7.d: New test. libphobos/ChangeLog: * libdruntime/MERGE: Merge upstream druntime e6caaab9. * libdruntime/Makefile.am (D_EXTRA_FLAGS): Build libdruntime with -fpreview=dip1000, -fpreview=fieldwise, and -fpreview=dtorfields. (ALL_DRUNTIME_SOURCES): Add DRUNTIME_DSOURCES_STDCXX. (DRUNTIME_DSOURCES): Update list of C binding modules. (DRUNTIME_DSOURCES_STDCXX): Likewise. (DRUNTIME_DSOURCES_LINUX): Likewise. (DRUNTIME_DSOURCES_OPENBSD): Likewise. (DRUNTIME_DISOURCES): Remove __entrypoint.di. * libdruntime/Makefile.in: Regenerated. * libdruntime/__entrypoint.di: Removed. * libdruntime/gcc/deh.d (_d_isbaseof): Update signature. (_d_createTrace): Likewise. (__gdc_begin_catch): Remove reference to the exception. (_d_throw): Increment reference count of thrown object before unwind. (__gdc_personality): Chain exceptions with Throwable.chainTogether. * libdruntime/gcc/emutls.d: Update imports. * libdruntime/gcc/sections/elf.d: Update imports. (DSO.moduleGroup): Update signature. * libdruntime/gcc/sections/macho.d: Update imports. (DSO.moduleGroup): Update signature. * libdruntime/gcc/sections/pecoff.d: Update imports. (DSO.moduleGroup): Update signature. * src/MERGE: Merge upstream phobos 5ab9ad256. * src/Makefile.am (D_EXTRA_DFLAGS): Add -fpreview=dip1000 and -fpreview=dtorfields flags. (PHOBOS_DSOURCES): Update list of std modules. * src/Makefile.in: Regenerate. * testsuite/lib/libphobos.exp (libphobos-dg-test): Handle assembly compile types. (dg-test): Override. (additional_prunes): Define. (libphobos-dg-prune): Filter any additional_prunes set by tests. * testsuite/libphobos.aa/test_aa.d: Update test. * testsuite/libphobos.druntime/druntime.exp (version_flags): Add -fversion=CoreUnittest. * testsuite/libphobos.druntime_shared/druntime_shared.exp (version_flags): Add -fversion=CoreUnittest -fversion=Shared. * testsuite/libphobos.exceptions/unknown_gc.d: Update test. * testsuite/libphobos.hash/test_hash.d: Update test. * testsuite/libphobos.phobos/phobos.exp (version_flags): Add -fversion=StdUnittest * testsuite/libphobos.phobos_shared/phobos_shared.exp (version_flags): Likewise. * testsuite/libphobos.shared/host.c: Update test. * testsuite/libphobos.shared/load.d: Update test. * testsuite/libphobos.shared/load_13414.d: Update test. * testsuite/libphobos.thread/fiber_guard_page.d: Update test. * testsuite/libphobos.thread/tlsgc_sections.d: Update test. * testsuite/testsuite_flags.in: Add -fpreview=dip1000 to --gdcflags. * testsuite/libphobos.shared/link_mod_collision.d: Removed. * testsuite/libphobos.shared/load_mod_collision.d: Removed. * testsuite/libphobos.betterc/betterc.exp: New test. * testsuite/libphobos.config/config.exp: New test. * testsuite/libphobos.gc/gc.exp: New test. * testsuite/libphobos.imports/imports.exp: New test. * testsuite/libphobos.lifetime/lifetime.exp: New test. * testsuite/libphobos.unittest/unittest.exp: New test. --- libphobos/libdruntime/LICENSE | 26 - libphobos/libdruntime/LICENSE.txt | 23 + libphobos/libdruntime/MERGE | 2 +- libphobos/libdruntime/Makefile.am | 96 +- libphobos/libdruntime/Makefile.in | 508 +- libphobos/libdruntime/__entrypoint.di | 56 - libphobos/libdruntime/core/atomic.d | 2448 ++--- libphobos/libdruntime/core/attribute.d | 188 +- libphobos/libdruntime/core/bitop.d | 19 + libphobos/libdruntime/core/builtins.d | 19 + libphobos/libdruntime/core/checkedint.d | 114 +- libphobos/libdruntime/core/demangle.d | 184 +- libphobos/libdruntime/core/exception.d | 347 +- libphobos/libdruntime/core/gc/config.d | 129 + libphobos/libdruntime/core/gc/gcinterface.d | 198 + libphobos/libdruntime/core/gc/registry.d | 87 + libphobos/libdruntime/core/internal/abort.d | 20 +- .../libdruntime/core/internal/array/appending.d | 222 + .../libdruntime/core/internal/array/capacity.d | 85 + .../libdruntime/core/internal/array/casting.d | 115 + .../libdruntime/core/internal/array/comparison.d | 242 + .../core/internal/array/concatenation.d | 75 + .../libdruntime/core/internal/array/construction.d | 307 + .../libdruntime/core/internal/array/equality.d | 237 + .../libdruntime/core/internal/array/operations.d | 670 ++ libphobos/libdruntime/core/internal/array/utils.d | 121 + libphobos/libdruntime/core/internal/arrayop.d | 451 - libphobos/libdruntime/core/internal/atomic.d | 1141 ++ .../libdruntime/core/internal/container/array.d | 232 + .../libdruntime/core/internal/container/common.d | 63 + .../libdruntime/core/internal/container/hashtab.d | 330 + .../libdruntime/core/internal/container/treap.d | 368 + libphobos/libdruntime/core/internal/convert.d | 56 +- libphobos/libdruntime/core/internal/dassert.d | 590 + libphobos/libdruntime/core/internal/destruction.d | 47 + libphobos/libdruntime/core/internal/entrypoint.d | 41 + libphobos/libdruntime/core/internal/gc/bits.d | 493 + .../core/internal/gc/impl/conservative/gc.d | 4836 +++++++++ .../libdruntime/core/internal/gc/impl/manual/gc.d | 269 + .../libdruntime/core/internal/gc/impl/proto/gc.d | 248 + libphobos/libdruntime/core/internal/gc/os.d | 308 + libphobos/libdruntime/core/internal/gc/pooltable.d | 295 + libphobos/libdruntime/core/internal/gc/proxy.d | 296 + libphobos/libdruntime/core/internal/hash.d | 453 +- libphobos/libdruntime/core/internal/lifetime.d | 213 + libphobos/libdruntime/core/internal/moving.d | 147 + libphobos/libdruntime/core/internal/parseoptions.d | 422 + libphobos/libdruntime/core/internal/postblit.d | 274 + libphobos/libdruntime/core/internal/qsort.d | 196 + libphobos/libdruntime/core/internal/spinlock.d | 23 +- libphobos/libdruntime/core/internal/string.d | 166 +- libphobos/libdruntime/core/internal/switch_.d | 190 + libphobos/libdruntime/core/internal/traits.d | 609 +- libphobos/libdruntime/core/internal/utf.d | 938 ++ libphobos/libdruntime/core/internal/util/array.d | 72 + libphobos/libdruntime/core/internal/util/math.d | 53 + libphobos/libdruntime/core/lifetime.d | 2201 ++++ libphobos/libdruntime/core/memory.d | 926 +- libphobos/libdruntime/core/runtime.d | 848 +- libphobos/libdruntime/core/stdc/math.d | 395 +- libphobos/libdruntime/core/stdc/stdint.d | 91 +- libphobos/libdruntime/core/stdcpp/allocator.d | 373 + libphobos/libdruntime/core/stdcpp/array.d | 133 + libphobos/libdruntime/core/stdcpp/exception.d | 161 +- libphobos/libdruntime/core/stdcpp/memory.d | 163 + libphobos/libdruntime/core/stdcpp/new_.d | 186 + libphobos/libdruntime/core/stdcpp/string.d | 2593 +++++ libphobos/libdruntime/core/stdcpp/string_view.d | 130 + libphobos/libdruntime/core/stdcpp/type_traits.d | 50 + libphobos/libdruntime/core/stdcpp/typeinfo.d | 87 +- libphobos/libdruntime/core/stdcpp/utility.d | 50 + libphobos/libdruntime/core/stdcpp/vector.d | 850 ++ libphobos/libdruntime/core/stdcpp/xutility.d | 427 + libphobos/libdruntime/core/sync/barrier.d | 61 +- libphobos/libdruntime/core/sync/condition.d | 450 +- libphobos/libdruntime/core/sync/config.d | 19 +- libphobos/libdruntime/core/sync/event.d | 345 + libphobos/libdruntime/core/sync/mutex.d | 16 +- libphobos/libdruntime/core/sync/rwmutex.d | 173 +- libphobos/libdruntime/core/sync/semaphore.d | 42 +- libphobos/libdruntime/core/sys/darwin/dlfcn.d | 5 + .../libdruntime/core/sys/dragonflybsd/sys/elf32.d | 2 +- .../libdruntime/core/sys/dragonflybsd/sys/elf64.d | 2 +- libphobos/libdruntime/core/sys/freebsd/sys/elf32.d | 2 +- libphobos/libdruntime/core/sys/freebsd/sys/elf64.d | 2 +- libphobos/libdruntime/core/sys/linux/fs.d | 265 + libphobos/libdruntime/core/sys/linux/io_uring.d | 414 + libphobos/libdruntime/core/sys/linux/perf_event.d | 2515 +++++ libphobos/libdruntime/core/sys/linux/sys/procfs.d | 15 + libphobos/libdruntime/core/sys/netbsd/sys/elf32.d | 2 +- libphobos/libdruntime/core/sys/netbsd/sys/elf64.d | 2 +- libphobos/libdruntime/core/sys/openbsd/execinfo.d | 147 + libphobos/libdruntime/core/sys/openbsd/sys/elf32.d | 2 +- libphobos/libdruntime/core/sys/openbsd/sys/elf64.d | 2 +- libphobos/libdruntime/core/sys/posix/arpa/inet.d | 116 +- libphobos/libdruntime/core/sys/posix/fcntl.d | 16 +- libphobos/libdruntime/core/sys/posix/net/if_.d | 2 +- libphobos/libdruntime/core/sys/posix/semaphore.d | 2 +- libphobos/libdruntime/core/sys/posix/setjmp.d | 4 + libphobos/libdruntime/core/sys/posix/stdio.d | 10 + libphobos/libdruntime/core/sys/posix/string.d | 8 +- libphobos/libdruntime/core/sys/windows/basetsd.d | 2 +- libphobos/libdruntime/core/sys/windows/dll.d | 1 - libphobos/libdruntime/core/sys/windows/sqlext.d | 2 +- libphobos/libdruntime/core/thread/fiber.d | 2 +- libphobos/libdruntime/core/thread/osthread.d | 34 +- libphobos/libdruntime/core/thread/threadbase.d | 12 +- libphobos/libdruntime/core/time.d | 1201 +-- libphobos/libdruntime/gc/bits.d | 129 - libphobos/libdruntime/gc/config.d | 291 - libphobos/libdruntime/gc/gcinterface.d | 190 - libphobos/libdruntime/gc/impl/conservative/gc.d | 3413 ------ libphobos/libdruntime/gc/impl/manual/gc.d | 274 - libphobos/libdruntime/gc/os.d | 214 - libphobos/libdruntime/gc/pooltable.d | 285 - libphobos/libdruntime/gc/proxy.d | 239 - libphobos/libdruntime/gcc/deh.d | 22 +- libphobos/libdruntime/gcc/emutls.d | 3 +- libphobos/libdruntime/gcc/sections/elf.d | 6 +- libphobos/libdruntime/gcc/sections/macho.d | 6 +- libphobos/libdruntime/gcc/sections/pecoff.d | 6 +- libphobos/libdruntime/object.d | 3567 ++++--- libphobos/libdruntime/rt/aApply.d | 6 +- libphobos/libdruntime/rt/aApplyR.d | 5 +- libphobos/libdruntime/rt/aaA.d | 272 +- libphobos/libdruntime/rt/adi.d | 306 +- libphobos/libdruntime/rt/arrayassign.d | 4 +- libphobos/libdruntime/rt/arraycast.d | 52 - libphobos/libdruntime/rt/arraycat.d | 4 +- libphobos/libdruntime/rt/cast_.d | 51 +- libphobos/libdruntime/rt/config.d | 85 +- libphobos/libdruntime/rt/critical_.d | 3 +- libphobos/libdruntime/rt/deh.d | 36 +- libphobos/libdruntime/rt/dmain2.d | 333 +- libphobos/libdruntime/rt/dylib_fixes.c | 2 +- libphobos/libdruntime/rt/ehalloc.d | 125 + libphobos/libdruntime/rt/invariant.d | 3 +- libphobos/libdruntime/rt/lifetime.d | 896 +- libphobos/libdruntime/rt/memory.d | 2 +- libphobos/libdruntime/rt/minfo.d | 10 +- libphobos/libdruntime/rt/monitor_.d | 10 +- libphobos/libdruntime/rt/obj.d | 35 - libphobos/libdruntime/rt/profilegc.d | 170 + libphobos/libdruntime/rt/qsort.d | 166 - libphobos/libdruntime/rt/sections.d | 17 +- libphobos/libdruntime/rt/switch_.d | 424 - libphobos/libdruntime/rt/tlsgc.d | 3 +- libphobos/libdruntime/rt/util/array.d | 72 - libphobos/libdruntime/rt/util/container/array.d | 232 - libphobos/libdruntime/rt/util/container/common.d | 66 - libphobos/libdruntime/rt/util/container/hashtab.d | 329 - libphobos/libdruntime/rt/util/container/treap.d | 338 - libphobos/libdruntime/rt/util/random.d | 51 - libphobos/libdruntime/rt/util/typeinfo.d | 304 +- libphobos/libdruntime/rt/util/utf.d | 920 -- libphobos/libdruntime/rt/util/utility.d | 44 + libphobos/src/MERGE | 2 +- libphobos/src/Makefile.am | 47 +- libphobos/src/Makefile.in | 145 +- libphobos/src/etc/c/curl.d | 34 +- libphobos/src/etc/c/sqlite3.d | 2126 ---- libphobos/src/etc/c/zlib.d | 9 +- libphobos/src/index.d | 22 +- libphobos/src/std/algorithm/comparison.d | 950 +- libphobos/src/std/algorithm/internal.d | 22 +- libphobos/src/std/algorithm/iteration.d | 4539 ++++++-- libphobos/src/std/algorithm/mutation.d | 1416 ++- libphobos/src/std/algorithm/package.d | 13 +- libphobos/src/std/algorithm/searching.d | 1894 ++-- libphobos/src/std/algorithm/setops.d | 198 +- libphobos/src/std/algorithm/sorting.d | 1273 ++- libphobos/src/std/array.d | 2036 +++- libphobos/src/std/ascii.d | 188 +- libphobos/src/std/base64.d | 374 +- libphobos/src/std/bigint.d | 1045 +- libphobos/src/std/bitmanip.d | 2805 +++-- libphobos/src/std/compiler.d | 6 +- libphobos/src/std/complex.d | 1235 ++- libphobos/src/std/concurrency.d | 695 +- libphobos/src/std/container/array.d | 467 +- libphobos/src/std/container/binaryheap.d | 98 +- libphobos/src/std/container/dlist.d | 177 +- libphobos/src/std/container/package.d | 519 +- libphobos/src/std/container/rbtree.d | 271 +- libphobos/src/std/container/slist.d | 268 +- libphobos/src/std/container/util.d | 8 +- libphobos/src/std/conv.d | 3098 +++--- libphobos/src/std/csv.d | 556 +- libphobos/src/std/datetime/date.d | 1202 ++- libphobos/src/std/datetime/interval.d | 835 +- libphobos/src/std/datetime/package.d | 784 +- libphobos/src/std/datetime/stopwatch.d | 190 +- libphobos/src/std/datetime/systime.d | 2126 ++-- libphobos/src/std/datetime/timezone.d | 469 +- libphobos/src/std/demangle.d | 106 +- libphobos/src/std/digest/crc.d | 165 +- libphobos/src/std/digest/digest.d | 22 +- libphobos/src/std/digest/hmac.d | 60 +- libphobos/src/std/digest/md.d | 74 +- libphobos/src/std/digest/murmurhash.d | 2 +- libphobos/src/std/digest/package.d | 233 +- libphobos/src/std/digest/ripemd.d | 108 +- libphobos/src/std/digest/sha.d | 329 +- libphobos/src/std/encoding.d | 592 +- libphobos/src/std/exception.d | 920 +- .../allocator/building_blocks/affix_allocator.d | 195 +- .../allocator/building_blocks/aligned_block_list.d | 699 ++ .../allocator/building_blocks/allocator_list.d | 424 +- .../building_blocks/ascending_page_allocator.d | 1007 ++ .../allocator/building_blocks/bitmapped_block.d | 2534 ++++- .../allocator/building_blocks/bucketizer.d | 163 +- .../allocator/building_blocks/fallback_allocator.d | 237 +- .../allocator/building_blocks/free_list.d | 385 +- .../allocator/building_blocks/free_tree.d | 82 +- .../allocator/building_blocks/kernighan_ritchie.d | 193 +- .../allocator/building_blocks/null_allocator.d | 64 +- .../allocator/building_blocks/package.d | 145 +- .../allocator/building_blocks/quantizer.d | 184 +- .../allocator/building_blocks/region.d | 891 +- .../allocator/building_blocks/scoped_allocator.d | 134 +- .../allocator/building_blocks/segregator.d | 241 +- .../allocator/building_blocks/stats_collector.d | 339 +- libphobos/src/std/experimental/allocator/common.d | 192 +- .../src/std/experimental/allocator/gc_allocator.d | 88 +- .../src/std/experimental/allocator/mallocator.d | 175 +- .../std/experimental/allocator/mmap_allocator.d | 92 +- libphobos/src/std/experimental/allocator/package.d | 1405 ++- .../src/std/experimental/allocator/showcase.d | 7 +- libphobos/src/std/experimental/allocator/typed.d | 14 +- libphobos/src/std/experimental/checkedint.d | 486 +- libphobos/src/std/experimental/logger/core.d | 444 +- libphobos/src/std/experimental/logger/filelogger.d | 71 +- .../src/std/experimental/logger/multilogger.d | 55 +- libphobos/src/std/experimental/logger/nulllogger.d | 16 +- libphobos/src/std/experimental/logger/package.d | 153 +- libphobos/src/std/experimental/typecons.d | 43 +- libphobos/src/std/file.d | 2090 +++- libphobos/src/std/format.d | 6028 ----------- libphobos/src/std/format/internal/floats.d | 2930 +++++ libphobos/src/std/format/internal/read.d | 410 + libphobos/src/std/format/internal/write.d | 3980 +++++++ libphobos/src/std/format/package.d | 1787 ++++ libphobos/src/std/format/read.d | 721 ++ libphobos/src/std/format/spec.d | 949 ++ libphobos/src/std/format/write.d | 1289 +++ libphobos/src/std/functional.d | 688 +- libphobos/src/std/getopt.d | 371 +- libphobos/src/std/internal/attributes.d | 11 + libphobos/src/std/internal/cstring.d | 318 +- libphobos/src/std/internal/math/biguintcore.d | 822 +- libphobos/src/std/internal/math/biguintnoasm.d | 18 +- libphobos/src/std/internal/math/errorfunction.d | 139 +- libphobos/src/std/internal/math/gammafunction.d | 303 +- libphobos/src/std/internal/memory.d | 58 + libphobos/src/std/internal/scopebuffer.d | 29 +- libphobos/src/std/internal/test/dummyrange.d | 11 +- libphobos/src/std/internal/windows/advapi32.d | 4 +- libphobos/src/std/json.d | 1031 +- libphobos/src/std/math.d | 8586 --------------- libphobos/src/std/math/algebraic.d | 1072 ++ libphobos/src/std/math/constants.d | 38 + libphobos/src/std/math/exponential.d | 3439 ++++++ libphobos/src/std/math/hardware.d | 1212 +++ libphobos/src/std/math/operations.d | 1998 ++++ libphobos/src/std/math/package.d | 494 + libphobos/src/std/math/remainder.d | 155 + libphobos/src/std/math/rounding.d | 1004 ++ libphobos/src/std/math/traits.d | 853 ++ libphobos/src/std/math/trigonometry.d | 1425 +++ libphobos/src/std/mathspecial.d | 35 +- libphobos/src/std/meta.d | 929 +- libphobos/src/std/mmfile.d | 146 +- libphobos/src/std/net/curl.d | 1101 +- libphobos/src/std/net/isemail.d | 135 +- libphobos/src/std/numeric.d | 1272 ++- libphobos/src/std/outbuffer.d | 79 +- libphobos/src/std/package.d | 82 + libphobos/src/std/parallelism.d | 741 +- libphobos/src/std/path.d | 997 +- libphobos/src/std/process.d | 6646 ++++++------ libphobos/src/std/random.d | 1905 +++- libphobos/src/std/range/interfaces.d | 69 +- libphobos/src/std/range/package.d | 4715 +++++--- libphobos/src/std/range/primitives.d | 767 +- libphobos/src/std/regex/internal/backtracking.d | 1388 +-- libphobos/src/std/regex/internal/generator.d | 2 +- libphobos/src/std/regex/internal/ir.d | 565 +- libphobos/src/std/regex/internal/kickstart.d | 14 +- libphobos/src/std/regex/internal/parser.d | 792 +- libphobos/src/std/regex/internal/tests.d | 36 +- libphobos/src/std/regex/internal/tests2.d | 159 +- libphobos/src/std/regex/internal/thompson.d | 158 +- libphobos/src/std/regex/package.d | 558 +- libphobos/src/std/signals.d | 88 +- libphobos/src/std/socket.d | 695 +- libphobos/src/std/stdint.d | 6 +- libphobos/src/std/stdio.d | 1875 +++- libphobos/src/std/string.d | 2082 ++-- libphobos/src/std/sumtype.d | 2500 +++++ libphobos/src/std/system.d | 20 +- libphobos/src/std/traits.d | 3200 ++++-- libphobos/src/std/typecons.d | 3441 ++++-- libphobos/src/std/typetuple.d | 5 +- libphobos/src/std/uni.d | 9768 ----------------- libphobos/src/std/uni/package.d | 10637 +++++++++++++++++++ libphobos/src/std/uri.d | 189 +- libphobos/src/std/utf.d | 1207 ++- libphobos/src/std/uuid.d | 129 +- libphobos/src/std/variant.d | 973 +- libphobos/src/std/windows/charset.d | 12 +- libphobos/src/std/windows/registry.d | 122 +- libphobos/src/std/windows/syserror.d | 24 +- libphobos/src/std/xml.d | 312 +- libphobos/src/std/zip.d | 1303 ++- libphobos/src/std/zlib.d | 270 +- libphobos/testsuite/lib/libphobos.exp | 60 + libphobos/testsuite/libphobos.aa/test_aa.d | 79 +- .../libphobos.allocations/alloc_from_assert.d | 25 + libphobos/testsuite/libphobos.betterc/betterc.exp | 27 + libphobos/testsuite/libphobos.betterc/test18828.d | 10 + libphobos/testsuite/libphobos.betterc/test19416.d | 14 + libphobos/testsuite/libphobos.betterc/test19421.d | 13 + libphobos/testsuite/libphobos.betterc/test19561.d | 16 + libphobos/testsuite/libphobos.betterc/test19924.d | 15 + libphobos/testsuite/libphobos.betterc/test20088.d | 14 + libphobos/testsuite/libphobos.betterc/test20613.d | 18 + libphobos/testsuite/libphobos.config/config.exp | 46 + libphobos/testsuite/libphobos.config/test19433.d | 7 + libphobos/testsuite/libphobos.config/test20459.d | 5 + .../testsuite/libphobos.druntime/druntime.exp | 2 +- .../libphobos.druntime_shared/druntime_shared.exp | 2 +- .../testsuite/libphobos.exceptions/assert_fail.d | 564 + .../libphobos.exceptions/catch_in_finally.d | 191 + .../libphobos.exceptions/future_message.d | 71 + .../libphobos.exceptions/long_backtrace_trunc.d | 37 + .../testsuite/libphobos.exceptions/refcounted.d | 96 + .../libphobos.exceptions/rt_trap_exceptions.d | 15 + .../libphobos.exceptions/rt_trap_exceptions_drt.d | 11 + .../testsuite/libphobos.exceptions/unknown_gc.d | 4 + libphobos/testsuite/libphobos.gc/attributes.d | 30 + libphobos/testsuite/libphobos.gc/forkgc.d | 36 + libphobos/testsuite/libphobos.gc/forkgc2.d | 22 + libphobos/testsuite/libphobos.gc/gc.exp | 27 + libphobos/testsuite/libphobos.gc/nocollect.d | 15 + libphobos/testsuite/libphobos.gc/precisegc.d | 126 + libphobos/testsuite/libphobos.gc/recoverfree.d | 13 + libphobos/testsuite/libphobos.gc/sigmaskgc.d | 42 + libphobos/testsuite/libphobos.gc/startbackgc.d | 22 + libphobos/testsuite/libphobos.hash/test_hash.d | 140 +- libphobos/testsuite/libphobos.imports/bug18193.d | 4 + libphobos/testsuite/libphobos.imports/imports.exp | 29 + .../testsuite/libphobos.init_fini/custom_gc.d | 203 + .../testsuite/libphobos.init_fini/test18996.d | 13 + .../large_aggregate_destroy_21097.d | 78 + .../testsuite/libphobos.lifetime/lifetime.exp | 27 + libphobos/testsuite/libphobos.phobos/phobos.exp | 2 +- .../libphobos.phobos_shared/phobos_shared.exp | 2 +- libphobos/testsuite/libphobos.shared/host.c | 8 + .../libphobos.shared/link_mod_collision.d | 5 - libphobos/testsuite/libphobos.shared/load.d | 1 - libphobos/testsuite/libphobos.shared/load_13414.d | 13 +- .../libphobos.shared/load_mod_collision.d | 14 - .../testsuite/libphobos.thread/external_threads.d | 50 + .../testsuite/libphobos.thread/fiber_guard_page.d | 4 + libphobos/testsuite/libphobos.thread/join_detach.d | 20 + libphobos/testsuite/libphobos.thread/test_import.d | 7 + .../testsuite/libphobos.thread/tlsgc_sections.d | 61 +- libphobos/testsuite/libphobos.thread/tlsstack.d | 38 + libphobos/testsuite/libphobos.typeinfo/enum_.d | 21 + libphobos/testsuite/libphobos.typeinfo/isbaseof.d | 46 + .../testsuite/libphobos.unittest/customhandler.d | 21 + .../testsuite/libphobos.unittest/unittest.exp | 53 + libphobos/testsuite/testsuite_flags.in | 2 +- 373 files changed, 133355 insertions(+), 71443 deletions(-) delete mode 100644 libphobos/libdruntime/LICENSE create mode 100644 libphobos/libdruntime/LICENSE.txt delete mode 100644 libphobos/libdruntime/__entrypoint.di create mode 100644 libphobos/libdruntime/core/builtins.d create mode 100644 libphobos/libdruntime/core/gc/config.d create mode 100644 libphobos/libdruntime/core/gc/gcinterface.d create mode 100644 libphobos/libdruntime/core/gc/registry.d create mode 100644 libphobos/libdruntime/core/internal/array/appending.d create mode 100644 libphobos/libdruntime/core/internal/array/capacity.d create mode 100644 libphobos/libdruntime/core/internal/array/casting.d create mode 100644 libphobos/libdruntime/core/internal/array/comparison.d create mode 100644 libphobos/libdruntime/core/internal/array/concatenation.d create mode 100644 libphobos/libdruntime/core/internal/array/construction.d create mode 100644 libphobos/libdruntime/core/internal/array/equality.d create mode 100644 libphobos/libdruntime/core/internal/array/operations.d create mode 100644 libphobos/libdruntime/core/internal/array/utils.d delete mode 100644 libphobos/libdruntime/core/internal/arrayop.d create mode 100644 libphobos/libdruntime/core/internal/atomic.d create mode 100644 libphobos/libdruntime/core/internal/container/array.d create mode 100644 libphobos/libdruntime/core/internal/container/common.d create mode 100644 libphobos/libdruntime/core/internal/container/hashtab.d create mode 100644 libphobos/libdruntime/core/internal/container/treap.d create mode 100644 libphobos/libdruntime/core/internal/dassert.d create mode 100644 libphobos/libdruntime/core/internal/destruction.d create mode 100644 libphobos/libdruntime/core/internal/entrypoint.d create mode 100644 libphobos/libdruntime/core/internal/gc/bits.d create mode 100644 libphobos/libdruntime/core/internal/gc/impl/conservative/gc.d create mode 100644 libphobos/libdruntime/core/internal/gc/impl/manual/gc.d create mode 100644 libphobos/libdruntime/core/internal/gc/impl/proto/gc.d create mode 100644 libphobos/libdruntime/core/internal/gc/os.d create mode 100644 libphobos/libdruntime/core/internal/gc/pooltable.d create mode 100644 libphobos/libdruntime/core/internal/gc/proxy.d create mode 100644 libphobos/libdruntime/core/internal/lifetime.d create mode 100644 libphobos/libdruntime/core/internal/moving.d create mode 100644 libphobos/libdruntime/core/internal/parseoptions.d create mode 100644 libphobos/libdruntime/core/internal/postblit.d create mode 100644 libphobos/libdruntime/core/internal/qsort.d create mode 100644 libphobos/libdruntime/core/internal/switch_.d create mode 100644 libphobos/libdruntime/core/internal/utf.d create mode 100644 libphobos/libdruntime/core/internal/util/array.d create mode 100644 libphobos/libdruntime/core/internal/util/math.d create mode 100644 libphobos/libdruntime/core/lifetime.d create mode 100644 libphobos/libdruntime/core/stdcpp/allocator.d create mode 100644 libphobos/libdruntime/core/stdcpp/array.d create mode 100644 libphobos/libdruntime/core/stdcpp/memory.d create mode 100644 libphobos/libdruntime/core/stdcpp/new_.d create mode 100644 libphobos/libdruntime/core/stdcpp/string.d create mode 100644 libphobos/libdruntime/core/stdcpp/string_view.d create mode 100644 libphobos/libdruntime/core/stdcpp/type_traits.d create mode 100644 libphobos/libdruntime/core/stdcpp/utility.d create mode 100644 libphobos/libdruntime/core/stdcpp/vector.d create mode 100644 libphobos/libdruntime/core/stdcpp/xutility.d create mode 100644 libphobos/libdruntime/core/sync/event.d create mode 100644 libphobos/libdruntime/core/sys/linux/fs.d create mode 100644 libphobos/libdruntime/core/sys/linux/io_uring.d create mode 100644 libphobos/libdruntime/core/sys/linux/perf_event.d create mode 100644 libphobos/libdruntime/core/sys/linux/sys/procfs.d create mode 100644 libphobos/libdruntime/core/sys/openbsd/execinfo.d delete mode 100644 libphobos/libdruntime/gc/bits.d delete mode 100644 libphobos/libdruntime/gc/config.d delete mode 100644 libphobos/libdruntime/gc/gcinterface.d delete mode 100644 libphobos/libdruntime/gc/impl/conservative/gc.d delete mode 100644 libphobos/libdruntime/gc/impl/manual/gc.d delete mode 100644 libphobos/libdruntime/gc/os.d delete mode 100644 libphobos/libdruntime/gc/pooltable.d delete mode 100644 libphobos/libdruntime/gc/proxy.d delete mode 100644 libphobos/libdruntime/rt/arraycast.d create mode 100644 libphobos/libdruntime/rt/ehalloc.d delete mode 100644 libphobos/libdruntime/rt/obj.d create mode 100644 libphobos/libdruntime/rt/profilegc.d delete mode 100644 libphobos/libdruntime/rt/qsort.d delete mode 100644 libphobos/libdruntime/rt/switch_.d delete mode 100644 libphobos/libdruntime/rt/util/array.d delete mode 100644 libphobos/libdruntime/rt/util/container/array.d delete mode 100644 libphobos/libdruntime/rt/util/container/common.d delete mode 100644 libphobos/libdruntime/rt/util/container/hashtab.d delete mode 100644 libphobos/libdruntime/rt/util/container/treap.d delete mode 100644 libphobos/libdruntime/rt/util/random.d delete mode 100644 libphobos/libdruntime/rt/util/utf.d create mode 100644 libphobos/libdruntime/rt/util/utility.d delete mode 100644 libphobos/src/etc/c/sqlite3.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/aligned_block_list.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/ascending_page_allocator.d delete mode 100644 libphobos/src/std/format.d create mode 100644 libphobos/src/std/format/internal/floats.d create mode 100644 libphobos/src/std/format/internal/read.d create mode 100644 libphobos/src/std/format/internal/write.d create mode 100644 libphobos/src/std/format/package.d create mode 100644 libphobos/src/std/format/read.d create mode 100644 libphobos/src/std/format/spec.d create mode 100644 libphobos/src/std/format/write.d create mode 100644 libphobos/src/std/internal/attributes.d create mode 100644 libphobos/src/std/internal/memory.d delete mode 100644 libphobos/src/std/math.d create mode 100644 libphobos/src/std/math/algebraic.d create mode 100644 libphobos/src/std/math/constants.d create mode 100644 libphobos/src/std/math/exponential.d create mode 100644 libphobos/src/std/math/hardware.d create mode 100644 libphobos/src/std/math/operations.d create mode 100644 libphobos/src/std/math/package.d create mode 100644 libphobos/src/std/math/remainder.d create mode 100644 libphobos/src/std/math/rounding.d create mode 100644 libphobos/src/std/math/traits.d create mode 100644 libphobos/src/std/math/trigonometry.d create mode 100644 libphobos/src/std/package.d create mode 100644 libphobos/src/std/sumtype.d delete mode 100644 libphobos/src/std/uni.d create mode 100644 libphobos/src/std/uni/package.d create mode 100644 libphobos/testsuite/libphobos.allocations/alloc_from_assert.d create mode 100644 libphobos/testsuite/libphobos.betterc/betterc.exp create mode 100644 libphobos/testsuite/libphobos.betterc/test18828.d create mode 100644 libphobos/testsuite/libphobos.betterc/test19416.d create mode 100644 libphobos/testsuite/libphobos.betterc/test19421.d create mode 100644 libphobos/testsuite/libphobos.betterc/test19561.d create mode 100644 libphobos/testsuite/libphobos.betterc/test19924.d create mode 100644 libphobos/testsuite/libphobos.betterc/test20088.d create mode 100644 libphobos/testsuite/libphobos.betterc/test20613.d create mode 100644 libphobos/testsuite/libphobos.config/config.exp create mode 100644 libphobos/testsuite/libphobos.config/test19433.d create mode 100644 libphobos/testsuite/libphobos.config/test20459.d create mode 100644 libphobos/testsuite/libphobos.exceptions/assert_fail.d create mode 100644 libphobos/testsuite/libphobos.exceptions/catch_in_finally.d create mode 100644 libphobos/testsuite/libphobos.exceptions/future_message.d create mode 100644 libphobos/testsuite/libphobos.exceptions/long_backtrace_trunc.d create mode 100644 libphobos/testsuite/libphobos.exceptions/refcounted.d create mode 100644 libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions.d create mode 100644 libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions_drt.d create mode 100644 libphobos/testsuite/libphobos.gc/attributes.d create mode 100644 libphobos/testsuite/libphobos.gc/forkgc.d create mode 100644 libphobos/testsuite/libphobos.gc/forkgc2.d create mode 100644 libphobos/testsuite/libphobos.gc/gc.exp create mode 100644 libphobos/testsuite/libphobos.gc/nocollect.d create mode 100644 libphobos/testsuite/libphobos.gc/precisegc.d create mode 100644 libphobos/testsuite/libphobos.gc/recoverfree.d create mode 100644 libphobos/testsuite/libphobos.gc/sigmaskgc.d create mode 100644 libphobos/testsuite/libphobos.gc/startbackgc.d create mode 100644 libphobos/testsuite/libphobos.imports/bug18193.d create mode 100644 libphobos/testsuite/libphobos.imports/imports.exp create mode 100644 libphobos/testsuite/libphobos.init_fini/custom_gc.d create mode 100644 libphobos/testsuite/libphobos.init_fini/test18996.d create mode 100644 libphobos/testsuite/libphobos.lifetime/large_aggregate_destroy_21097.d create mode 100644 libphobos/testsuite/libphobos.lifetime/lifetime.exp delete mode 100644 libphobos/testsuite/libphobos.shared/link_mod_collision.d delete mode 100644 libphobos/testsuite/libphobos.shared/load_mod_collision.d create mode 100644 libphobos/testsuite/libphobos.thread/external_threads.d create mode 100644 libphobos/testsuite/libphobos.thread/join_detach.d create mode 100644 libphobos/testsuite/libphobos.thread/test_import.d create mode 100644 libphobos/testsuite/libphobos.thread/tlsstack.d create mode 100644 libphobos/testsuite/libphobos.typeinfo/enum_.d create mode 100644 libphobos/testsuite/libphobos.typeinfo/isbaseof.d create mode 100644 libphobos/testsuite/libphobos.unittest/customhandler.d create mode 100644 libphobos/testsuite/libphobos.unittest/unittest.exp (limited to 'libphobos') diff --git a/libphobos/libdruntime/LICENSE b/libphobos/libdruntime/LICENSE deleted file mode 100644 index c83ac89..0000000 --- a/libphobos/libdruntime/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -DRuntime: Runtime Library for the D Programming Language -======================================================== - -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/libdruntime/LICENSE.txt b/libphobos/libdruntime/LICENSE.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/libphobos/libdruntime/LICENSE.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/libdruntime/MERGE b/libphobos/libdruntime/MERGE index 0d554e0..11bef0f 100644 --- a/libphobos/libdruntime/MERGE +++ b/libphobos/libdruntime/MERGE @@ -1,4 +1,4 @@ -98c6ff0cf1241a0cfac196bf8a0523b1d4ecd3ac +e6caaab9d359198b760c698dcb6d253afb3f81f6 The first line of this file holds the git revision number of the last merge done from the dlang/druntime repository. diff --git a/libphobos/libdruntime/Makefile.am b/libphobos/libdruntime/Makefile.am index a2e2bff..80c7567 100644 --- a/libphobos/libdruntime/Makefile.am +++ b/libphobos/libdruntime/Makefile.am @@ -19,7 +19,8 @@ include $(top_srcdir)/d_rules.am # Make sure GDC can find libdruntime include files -D_EXTRA_DFLAGS=-nostdinc -I $(srcdir) -I . +D_EXTRA_DFLAGS=-fpreview=dip1000 -fpreview=fieldwise -fpreview=dtorfields \ + -nostdinc -I $(srcdir) -I . # D flags for compilation AM_DFLAGS= \ @@ -119,6 +120,7 @@ endif DRUNTIME_DSOURCES_GENERATED = gcc/config.d gcc/libbacktrace.d ALL_DRUNTIME_SOURCES = $(DRUNTIME_DSOURCES) $(DRUNTIME_CSOURCES) \ + $(DRUNTIME_DSOURCES_STDCXX) \ $(DRUNTIME_SOURCES_CONFIGURED) $(DRUNTIME_DSOURCES_GENERATED) # Need this library to both be part of libgphobos.a, and installed separately. @@ -166,12 +168,30 @@ install-data-local: DRUNTIME_CSOURCES = core/stdc/errno_.c DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \ - core/checkedint.d core/cpuid.d core/demangle.d core/exception.d \ - core/internal/abort.d core/internal/arrayop.d \ - core/internal/attributes.d core/internal/convert.d \ - core/internal/hash.d core/internal/spinlock.d core/internal/string.d \ - core/internal/traits.d core/math.d core/memory.d core/runtime.d \ - core/simd.d core/stdc/assert_.d core/stdc/complex.d core/stdc/config.d \ + core/builtins.d core/checkedint.d core/cpuid.d core/demangle.d \ + core/exception.d core/gc/config.d core/gc/gcinterface.d \ + core/gc/registry.d core/internal/abort.d \ + core/internal/array/appending.d core/internal/array/capacity.d \ + core/internal/array/casting.d core/internal/array/comparison.d \ + core/internal/array/concatenation.d core/internal/array/construction.d \ + core/internal/array/equality.d core/internal/array/operations.d \ + core/internal/array/utils.d core/internal/atomic.d \ + core/internal/attributes.d core/internal/container/array.d \ + core/internal/container/common.d core/internal/container/hashtab.d \ + core/internal/container/treap.d core/internal/convert.d \ + core/internal/dassert.d core/internal/destruction.d \ + core/internal/entrypoint.d core/internal/gc/bits.d \ + core/internal/gc/impl/conservative/gc.d \ + core/internal/gc/impl/manual/gc.d core/internal/gc/impl/proto/gc.d \ + core/internal/gc/os.d core/internal/gc/pooltable.d \ + core/internal/gc/proxy.d core/internal/hash.d core/internal/lifetime.d \ + core/internal/moving.d core/internal/parseoptions.d \ + core/internal/postblit.d core/internal/qsort.d \ + core/internal/spinlock.d core/internal/string.d \ + core/internal/switch_.d core/internal/traits.d core/internal/utf.d \ + core/internal/util/array.d core/internal/util/math.d core/lifetime.d \ + core/math.d core/memory.d core/runtime.d core/simd.d \ + core/stdc/assert_.d core/stdc/complex.d core/stdc/config.d \ core/stdc/ctype.d core/stdc/errno.d core/stdc/fenv.d \ core/stdc/float_.d core/stdc/inttypes.d core/stdc/limits.d \ core/stdc/locale.d core/stdc/math.d core/stdc/signal.d \ @@ -179,28 +199,28 @@ DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \ core/stdc/stdio.d core/stdc/stdlib.d core/stdc/string.d \ core/stdc/tgmath.d core/stdc/time.d core/stdc/wchar_.d \ core/stdc/wctype.d core/sync/barrier.d core/sync/condition.d \ - core/sync/config.d core/sync/exception.d core/sync/mutex.d \ - core/sync/rwmutex.d core/sync/semaphore.d core/thread/context.d \ - core/thread/fiber.d core/thread/osthread.d core/thread/package.d \ - core/thread/threadbase.d core/thread/threadgroup.d core/thread/types.d \ - core/time.d core/vararg.d core/volatile.d gc/bits.d gc/config.d \ - gc/gcinterface.d gc/impl/conservative/gc.d gc/impl/manual/gc.d gc/os.d \ - gc/pooltable.d gc/proxy.d gcc/attribute.d gcc/attributes.d \ + core/sync/config.d core/sync/event.d core/sync/exception.d \ + core/sync/mutex.d core/sync/rwmutex.d core/sync/semaphore.d \ + core/thread/context.d core/thread/fiber.d core/thread/osthread.d \ + core/thread/package.d core/thread/threadbase.d \ + core/thread/threadgroup.d core/thread/types.d core/time.d \ + core/vararg.d core/volatile.d gcc/attribute.d gcc/attributes.d \ gcc/backtrace.d gcc/builtins.d gcc/deh.d gcc/emutls.d gcc/gthread.d \ gcc/sections/common.d gcc/sections/elf.d gcc/sections/macho.d \ gcc/sections/package.d gcc/sections/pecoff.d gcc/unwind/arm.d \ gcc/unwind/arm_common.d gcc/unwind/c6x.d gcc/unwind/generic.d \ gcc/unwind/package.d gcc/unwind/pe.d object.d rt/aApply.d rt/aApplyR.d \ - rt/aaA.d rt/adi.d rt/arrayassign.d rt/arraycast.d rt/arraycat.d \ - rt/cast_.d rt/config.d rt/critical_.d rt/deh.d rt/dmain2.d \ + rt/aaA.d rt/adi.d rt/arrayassign.d rt/arraycat.d rt/cast_.d \ + rt/config.d rt/critical_.d rt/deh.d rt/dmain2.d rt/ehalloc.d \ rt/invariant.d rt/lifetime.d rt/memory.d rt/minfo.d rt/monitor_.d \ - rt/obj.d rt/qsort.d rt/sections.d rt/switch_.d rt/tlsgc.d \ - rt/util/array.d rt/util/container/array.d rt/util/container/common.d \ - rt/util/container/hashtab.d rt/util/container/treap.d rt/util/random.d \ - rt/util/typeinfo.d rt/util/utf.d + rt/profilegc.d rt/sections.d rt/tlsgc.d rt/util/typeinfo.d \ + rt/util/utility.d -DRUNTIME_DSOURCES_STDCXX = core/stdcpp/exception.d \ - core/stdcpp/typeinfo.d +DRUNTIME_DSOURCES_STDCXX = core/stdcpp/allocator.d core/stdcpp/array.d \ + core/stdcpp/exception.d core/stdcpp/memory.d core/stdcpp/new_.d \ + core/stdcpp/string.d core/stdcpp/string_view.d \ + core/stdcpp/type_traits.d core/stdcpp/typeinfo.d core/stdcpp/utility.d \ + core/stdcpp/vector.d core/stdcpp/xutility.d DRUNTIME_DSOURCES_BIONIC = core/sys/bionic/err.d \ core/sys/bionic/fcntl.d core/sys/bionic/stdlib.d \ @@ -249,17 +269,19 @@ DRUNTIME_DSOURCES_FREEBSD = core/sys/freebsd/config.d \ DRUNTIME_DSOURCES_LINUX = core/sys/linux/config.d \ core/sys/linux/dlfcn.d core/sys/linux/elf.d core/sys/linux/epoll.d \ core/sys/linux/err.d core/sys/linux/errno.d core/sys/linux/execinfo.d \ - core/sys/linux/fcntl.d core/sys/linux/ifaddrs.d core/sys/linux/link.d \ + core/sys/linux/fcntl.d core/sys/linux/fs.d core/sys/linux/ifaddrs.d \ + core/sys/linux/io_uring.d core/sys/linux/link.d \ core/sys/linux/netinet/in_.d core/sys/linux/netinet/tcp.d \ - core/sys/linux/sched.d core/sys/linux/stdio.d core/sys/linux/string.d \ + core/sys/linux/perf_event.d core/sys/linux/sched.d \ + core/sys/linux/stdio.d core/sys/linux/string.d \ core/sys/linux/sys/auxv.d core/sys/linux/sys/eventfd.d \ core/sys/linux/sys/file.d core/sys/linux/sys/inotify.d \ core/sys/linux/sys/mman.d core/sys/linux/sys/prctl.d \ - core/sys/linux/sys/signalfd.d core/sys/linux/sys/socket.d \ - core/sys/linux/sys/sysinfo.d core/sys/linux/sys/time.d \ - core/sys/linux/sys/xattr.d core/sys/linux/termios.d \ - core/sys/linux/time.d core/sys/linux/timerfd.d core/sys/linux/tipc.d \ - core/sys/linux/unistd.d + core/sys/linux/sys/procfs.d core/sys/linux/sys/signalfd.d \ + core/sys/linux/sys/socket.d core/sys/linux/sys/sysinfo.d \ + core/sys/linux/sys/time.d core/sys/linux/sys/xattr.d \ + core/sys/linux/termios.d core/sys/linux/time.d \ + core/sys/linux/timerfd.d core/sys/linux/tipc.d core/sys/linux/unistd.d DRUNTIME_DSOURCES_NETBSD = core/sys/netbsd/dlfcn.d \ core/sys/netbsd/err.d core/sys/netbsd/execinfo.d \ @@ -271,13 +293,13 @@ DRUNTIME_DSOURCES_NETBSD = core/sys/netbsd/dlfcn.d \ core/sys/netbsd/sys/sysctl.d core/sys/netbsd/time.d DRUNTIME_DSOURCES_OPENBSD = core/sys/openbsd/dlfcn.d \ - core/sys/openbsd/err.d core/sys/openbsd/stdlib.d \ - core/sys/openbsd/string.d core/sys/openbsd/sys/cdefs.d \ - core/sys/openbsd/sys/elf.d core/sys/openbsd/sys/elf32.d \ - core/sys/openbsd/sys/elf64.d core/sys/openbsd/sys/elf_common.d \ - core/sys/openbsd/sys/link_elf.d core/sys/openbsd/sys/mman.d \ - core/sys/openbsd/sys/sysctl.d core/sys/openbsd/time.d \ - core/sys/openbsd/unistd.d + core/sys/openbsd/err.d core/sys/openbsd/execinfo.d \ + core/sys/openbsd/stdlib.d core/sys/openbsd/string.d \ + core/sys/openbsd/sys/cdefs.d core/sys/openbsd/sys/elf.d \ + core/sys/openbsd/sys/elf32.d core/sys/openbsd/sys/elf64.d \ + core/sys/openbsd/sys/elf_common.d core/sys/openbsd/sys/link_elf.d \ + core/sys/openbsd/sys/mman.d core/sys/openbsd/sys/sysctl.d \ + core/sys/openbsd/time.d core/sys/openbsd/unistd.d DRUNTIME_DSOURCES_POSIX = core/sys/posix/aio.d \ core/sys/posix/arpa/inet.d core/sys/posix/config.d \ @@ -402,4 +424,4 @@ DRUNTIME_DSOURCES_WINDOWS = core/sys/windows/accctrl.d \ core/sys/windows/winuser.d core/sys/windows/winver.d \ core/sys/windows/wtsapi32.d core/sys/windows/wtypes.d -DRUNTIME_DISOURCES = __entrypoint.di __main.di +DRUNTIME_DISOURCES = __main.di diff --git a/libphobos/libdruntime/Makefile.in b/libphobos/libdruntime/Makefile.in index cb2e372..b5f29da 100644 --- a/libphobos/libdruntime/Makefile.in +++ b/libphobos/libdruntime/Makefile.in @@ -188,47 +188,70 @@ LTLIBRARIES = $(noinst_LTLIBRARIES) $(toolexeclib_LTLIBRARIES) am__DEPENDENCIES_1 = am__dirstamp = $(am__leading_dot)dirstamp am__objects_1 = core/atomic.lo core/attribute.lo core/bitop.lo \ - core/checkedint.lo core/cpuid.lo core/demangle.lo \ - core/exception.lo core/internal/abort.lo \ - core/internal/arrayop.lo core/internal/attributes.lo \ - core/internal/convert.lo core/internal/hash.lo \ + core/builtins.lo core/checkedint.lo core/cpuid.lo \ + core/demangle.lo core/exception.lo core/gc/config.lo \ + core/gc/gcinterface.lo core/gc/registry.lo \ + core/internal/abort.lo core/internal/array/appending.lo \ + core/internal/array/capacity.lo core/internal/array/casting.lo \ + core/internal/array/comparison.lo \ + core/internal/array/concatenation.lo \ + core/internal/array/construction.lo \ + core/internal/array/equality.lo \ + core/internal/array/operations.lo core/internal/array/utils.lo \ + core/internal/atomic.lo core/internal/attributes.lo \ + core/internal/container/array.lo \ + core/internal/container/common.lo \ + core/internal/container/hashtab.lo \ + core/internal/container/treap.lo core/internal/convert.lo \ + core/internal/dassert.lo core/internal/destruction.lo \ + core/internal/entrypoint.lo core/internal/gc/bits.lo \ + core/internal/gc/impl/conservative/gc.lo \ + core/internal/gc/impl/manual/gc.lo \ + core/internal/gc/impl/proto/gc.lo core/internal/gc/os.lo \ + core/internal/gc/pooltable.lo core/internal/gc/proxy.lo \ + core/internal/hash.lo core/internal/lifetime.lo \ + core/internal/moving.lo core/internal/parseoptions.lo \ + core/internal/postblit.lo core/internal/qsort.lo \ core/internal/spinlock.lo core/internal/string.lo \ - core/internal/traits.lo core/math.lo core/memory.lo \ - core/runtime.lo core/simd.lo core/stdc/assert_.lo \ - core/stdc/complex.lo core/stdc/config.lo core/stdc/ctype.lo \ - core/stdc/errno.lo core/stdc/fenv.lo core/stdc/float_.lo \ - core/stdc/inttypes.lo core/stdc/limits.lo core/stdc/locale.lo \ - core/stdc/math.lo core/stdc/signal.lo core/stdc/stdarg.lo \ - core/stdc/stddef.lo core/stdc/stdint.lo core/stdc/stdio.lo \ - core/stdc/stdlib.lo core/stdc/string.lo core/stdc/tgmath.lo \ - core/stdc/time.lo core/stdc/wchar_.lo core/stdc/wctype.lo \ - core/sync/barrier.lo core/sync/condition.lo \ - core/sync/config.lo core/sync/exception.lo core/sync/mutex.lo \ - core/sync/rwmutex.lo core/sync/semaphore.lo \ - core/thread/context.lo core/thread/fiber.lo \ - core/thread/osthread.lo core/thread/package.lo \ - core/thread/threadbase.lo core/thread/threadgroup.lo \ - core/thread/types.lo core/time.lo core/vararg.lo \ - core/volatile.lo gc/bits.lo gc/config.lo gc/gcinterface.lo \ - gc/impl/conservative/gc.lo gc/impl/manual/gc.lo gc/os.lo \ - gc/pooltable.lo gc/proxy.lo gcc/attribute.lo gcc/attributes.lo \ - gcc/backtrace.lo gcc/builtins.lo gcc/deh.lo gcc/emutls.lo \ - gcc/gthread.lo gcc/sections/common.lo gcc/sections/elf.lo \ - gcc/sections/macho.lo gcc/sections/package.lo \ - gcc/sections/pecoff.lo gcc/unwind/arm.lo \ - gcc/unwind/arm_common.lo gcc/unwind/c6x.lo \ + core/internal/switch_.lo core/internal/traits.lo \ + core/internal/utf.lo core/internal/util/array.lo \ + core/internal/util/math.lo core/lifetime.lo core/math.lo \ + core/memory.lo core/runtime.lo core/simd.lo \ + core/stdc/assert_.lo core/stdc/complex.lo core/stdc/config.lo \ + core/stdc/ctype.lo core/stdc/errno.lo core/stdc/fenv.lo \ + core/stdc/float_.lo core/stdc/inttypes.lo core/stdc/limits.lo \ + core/stdc/locale.lo core/stdc/math.lo core/stdc/signal.lo \ + core/stdc/stdarg.lo core/stdc/stddef.lo core/stdc/stdint.lo \ + core/stdc/stdio.lo core/stdc/stdlib.lo core/stdc/string.lo \ + core/stdc/tgmath.lo core/stdc/time.lo core/stdc/wchar_.lo \ + core/stdc/wctype.lo core/sync/barrier.lo \ + core/sync/condition.lo core/sync/config.lo core/sync/event.lo \ + core/sync/exception.lo core/sync/mutex.lo core/sync/rwmutex.lo \ + core/sync/semaphore.lo core/thread/context.lo \ + core/thread/fiber.lo core/thread/osthread.lo \ + core/thread/package.lo core/thread/threadbase.lo \ + core/thread/threadgroup.lo core/thread/types.lo core/time.lo \ + core/vararg.lo core/volatile.lo gcc/attribute.lo \ + gcc/attributes.lo gcc/backtrace.lo gcc/builtins.lo gcc/deh.lo \ + gcc/emutls.lo gcc/gthread.lo gcc/sections/common.lo \ + gcc/sections/elf.lo gcc/sections/macho.lo \ + gcc/sections/package.lo gcc/sections/pecoff.lo \ + gcc/unwind/arm.lo gcc/unwind/arm_common.lo gcc/unwind/c6x.lo \ gcc/unwind/generic.lo gcc/unwind/package.lo gcc/unwind/pe.lo \ object.lo rt/aApply.lo rt/aApplyR.lo rt/aaA.lo rt/adi.lo \ - rt/arrayassign.lo rt/arraycast.lo rt/arraycat.lo rt/cast_.lo \ - rt/config.lo rt/critical_.lo rt/deh.lo rt/dmain2.lo \ + rt/arrayassign.lo rt/arraycat.lo rt/cast_.lo rt/config.lo \ + rt/critical_.lo rt/deh.lo rt/dmain2.lo rt/ehalloc.lo \ rt/invariant.lo rt/lifetime.lo rt/memory.lo rt/minfo.lo \ - rt/monitor_.lo rt/obj.lo rt/qsort.lo rt/sections.lo \ - rt/switch_.lo rt/tlsgc.lo rt/util/array.lo \ - rt/util/container/array.lo rt/util/container/common.lo \ - rt/util/container/hashtab.lo rt/util/container/treap.lo \ - rt/util/random.lo rt/util/typeinfo.lo rt/util/utf.lo + rt/monitor_.lo rt/profilegc.lo rt/sections.lo rt/tlsgc.lo \ + rt/util/typeinfo.lo rt/util/utility.lo am__objects_2 = core/stdc/libgdruntime_la-errno_.lo -am__objects_3 = core/sys/posix/aio.lo core/sys/posix/arpa/inet.lo \ +am__objects_3 = core/stdcpp/allocator.lo core/stdcpp/array.lo \ + core/stdcpp/exception.lo core/stdcpp/memory.lo \ + core/stdcpp/new_.lo core/stdcpp/string.lo \ + core/stdcpp/string_view.lo core/stdcpp/type_traits.lo \ + core/stdcpp/typeinfo.lo core/stdcpp/utility.lo \ + core/stdcpp/vector.lo core/stdcpp/xutility.lo +am__objects_4 = core/sys/posix/aio.lo core/sys/posix/arpa/inet.lo \ core/sys/posix/config.lo core/sys/posix/dirent.lo \ core/sys/posix/dlfcn.lo core/sys/posix/fcntl.lo \ core/sys/posix/grp.lo core/sys/posix/iconv.lo \ @@ -255,8 +278,8 @@ am__objects_3 = core/sys/posix/aio.lo core/sys/posix/arpa/inet.lo \ core/sys/posix/syslog.lo core/sys/posix/termios.lo \ core/sys/posix/time.lo core/sys/posix/ucontext.lo \ core/sys/posix/unistd.lo core/sys/posix/utime.lo -@DRUNTIME_OS_POSIX_TRUE@am__objects_4 = $(am__objects_3) -am__objects_5 = core/sys/darwin/config.lo \ +@DRUNTIME_OS_POSIX_TRUE@am__objects_5 = $(am__objects_4) +am__objects_6 = core/sys/darwin/config.lo \ core/sys/darwin/crt_externs.lo core/sys/darwin/dlfcn.lo \ core/sys/darwin/err.lo core/sys/darwin/execinfo.lo \ core/sys/darwin/fcntl.lo core/sys/darwin/ifaddrs.lo \ @@ -271,8 +294,8 @@ am__objects_5 = core/sys/darwin/config.lo \ core/sys/darwin/sys/attr.lo core/sys/darwin/sys/cdefs.lo \ core/sys/darwin/sys/event.lo core/sys/darwin/sys/mman.lo \ core/sys/darwin/sys/sysctl.lo -@DRUNTIME_OS_DARWIN_TRUE@am__objects_6 = $(am__objects_5) -am__objects_7 = core/sys/dragonflybsd/dlfcn.lo \ +@DRUNTIME_OS_DARWIN_TRUE@am__objects_7 = $(am__objects_6) +am__objects_8 = core/sys/dragonflybsd/dlfcn.lo \ core/sys/dragonflybsd/err.lo core/sys/dragonflybsd/execinfo.lo \ core/sys/dragonflybsd/netinet/in_.lo \ core/sys/dragonflybsd/pthread_np.lo \ @@ -291,12 +314,12 @@ am__objects_7 = core/sys/dragonflybsd/dlfcn.lo \ core/sys/dragonflybsd/sys/socket.lo \ core/sys/dragonflybsd/sys/sysctl.lo \ core/sys/dragonflybsd/time.lo -@DRUNTIME_OS_DRAGONFLYBSD_TRUE@am__objects_8 = $(am__objects_7) -am__objects_9 = core/sys/bionic/err.lo core/sys/bionic/fcntl.lo \ +@DRUNTIME_OS_DRAGONFLYBSD_TRUE@am__objects_9 = $(am__objects_8) +am__objects_10 = core/sys/bionic/err.lo core/sys/bionic/fcntl.lo \ core/sys/bionic/stdlib.lo core/sys/bionic/string.lo \ core/sys/bionic/unistd.lo -@DRUNTIME_OS_ANDROID_TRUE@am__objects_10 = $(am__objects_9) -am__objects_11 = core/sys/freebsd/config.lo core/sys/freebsd/dlfcn.lo \ +@DRUNTIME_OS_ANDROID_TRUE@am__objects_11 = $(am__objects_10) +am__objects_12 = core/sys/freebsd/config.lo core/sys/freebsd/dlfcn.lo \ core/sys/freebsd/err.lo core/sys/freebsd/execinfo.lo \ core/sys/freebsd/netinet/in_.lo core/sys/freebsd/pthread_np.lo \ core/sys/freebsd/stdlib.lo core/sys/freebsd/string.lo \ @@ -309,8 +332,8 @@ am__objects_11 = core/sys/freebsd/config.lo core/sys/freebsd/dlfcn.lo \ core/sys/freebsd/sys/mman.lo core/sys/freebsd/sys/mount.lo \ core/sys/freebsd/sys/sysctl.lo core/sys/freebsd/time.lo \ core/sys/freebsd/unistd.lo -@DRUNTIME_OS_FREEBSD_TRUE@am__objects_12 = $(am__objects_11) -am__objects_13 = core/sys/netbsd/dlfcn.lo core/sys/netbsd/err.lo \ +@DRUNTIME_OS_FREEBSD_TRUE@am__objects_13 = $(am__objects_12) +am__objects_14 = core/sys/netbsd/dlfcn.lo core/sys/netbsd/err.lo \ core/sys/netbsd/execinfo.lo core/sys/netbsd/stdlib.lo \ core/sys/netbsd/string.lo core/sys/netbsd/sys/elf.lo \ core/sys/netbsd/sys/elf32.lo core/sys/netbsd/sys/elf64.lo \ @@ -318,34 +341,37 @@ am__objects_13 = core/sys/netbsd/dlfcn.lo core/sys/netbsd/err.lo \ core/sys/netbsd/sys/featuretest.lo \ core/sys/netbsd/sys/link_elf.lo core/sys/netbsd/sys/mman.lo \ core/sys/netbsd/sys/sysctl.lo core/sys/netbsd/time.lo -@DRUNTIME_OS_NETBSD_TRUE@am__objects_14 = $(am__objects_13) -am__objects_15 = core/sys/openbsd/dlfcn.lo core/sys/openbsd/err.lo \ - core/sys/openbsd/stdlib.lo core/sys/openbsd/string.lo \ - core/sys/openbsd/sys/cdefs.lo core/sys/openbsd/sys/elf.lo \ - core/sys/openbsd/sys/elf32.lo core/sys/openbsd/sys/elf64.lo \ +@DRUNTIME_OS_NETBSD_TRUE@am__objects_15 = $(am__objects_14) +am__objects_16 = core/sys/openbsd/dlfcn.lo core/sys/openbsd/err.lo \ + core/sys/openbsd/execinfo.lo core/sys/openbsd/stdlib.lo \ + core/sys/openbsd/string.lo core/sys/openbsd/sys/cdefs.lo \ + core/sys/openbsd/sys/elf.lo core/sys/openbsd/sys/elf32.lo \ + core/sys/openbsd/sys/elf64.lo \ core/sys/openbsd/sys/elf_common.lo \ core/sys/openbsd/sys/link_elf.lo core/sys/openbsd/sys/mman.lo \ core/sys/openbsd/sys/sysctl.lo core/sys/openbsd/time.lo \ core/sys/openbsd/unistd.lo -@DRUNTIME_OS_OPENBSD_TRUE@am__objects_16 = $(am__objects_15) -am__objects_17 = core/sys/linux/config.lo core/sys/linux/dlfcn.lo \ +@DRUNTIME_OS_OPENBSD_TRUE@am__objects_17 = $(am__objects_16) +am__objects_18 = core/sys/linux/config.lo core/sys/linux/dlfcn.lo \ core/sys/linux/elf.lo core/sys/linux/epoll.lo \ core/sys/linux/err.lo core/sys/linux/errno.lo \ core/sys/linux/execinfo.lo core/sys/linux/fcntl.lo \ - core/sys/linux/ifaddrs.lo core/sys/linux/link.lo \ + core/sys/linux/fs.lo core/sys/linux/ifaddrs.lo \ + core/sys/linux/io_uring.lo core/sys/linux/link.lo \ core/sys/linux/netinet/in_.lo core/sys/linux/netinet/tcp.lo \ - core/sys/linux/sched.lo core/sys/linux/stdio.lo \ - core/sys/linux/string.lo core/sys/linux/sys/auxv.lo \ - core/sys/linux/sys/eventfd.lo core/sys/linux/sys/file.lo \ - core/sys/linux/sys/inotify.lo core/sys/linux/sys/mman.lo \ - core/sys/linux/sys/prctl.lo core/sys/linux/sys/signalfd.lo \ + core/sys/linux/perf_event.lo core/sys/linux/sched.lo \ + core/sys/linux/stdio.lo core/sys/linux/string.lo \ + core/sys/linux/sys/auxv.lo core/sys/linux/sys/eventfd.lo \ + core/sys/linux/sys/file.lo core/sys/linux/sys/inotify.lo \ + core/sys/linux/sys/mman.lo core/sys/linux/sys/prctl.lo \ + core/sys/linux/sys/procfs.lo core/sys/linux/sys/signalfd.lo \ core/sys/linux/sys/socket.lo core/sys/linux/sys/sysinfo.lo \ core/sys/linux/sys/time.lo core/sys/linux/sys/xattr.lo \ core/sys/linux/termios.lo core/sys/linux/time.lo \ core/sys/linux/timerfd.lo core/sys/linux/tipc.lo \ core/sys/linux/unistd.lo -@DRUNTIME_OS_LINUX_TRUE@am__objects_18 = $(am__objects_17) -am__objects_19 = core/sys/windows/accctrl.lo \ +@DRUNTIME_OS_LINUX_TRUE@am__objects_19 = $(am__objects_18) +am__objects_20 = core/sys/windows/accctrl.lo \ core/sys/windows/aclapi.lo core/sys/windows/aclui.lo \ core/sys/windows/basetsd.lo core/sys/windows/basetyps.lo \ core/sys/windows/cderr.lo core/sys/windows/cguid.lo \ @@ -430,9 +456,9 @@ am__objects_19 = core/sys/windows/accctrl.lo \ core/sys/windows/winsvc.lo core/sys/windows/winuser.lo \ core/sys/windows/winver.lo core/sys/windows/wtsapi32.lo \ core/sys/windows/wtypes.lo -@DRUNTIME_OS_MINGW_TRUE@am__objects_20 = $(am__objects_19) \ +@DRUNTIME_OS_MINGW_TRUE@am__objects_21 = $(am__objects_20) \ @DRUNTIME_OS_MINGW_TRUE@ config/mingw/libgdruntime_la-msvc.lo -am__objects_21 = core/sys/solaris/dlfcn.lo core/sys/solaris/elf.lo \ +am__objects_22 = core/sys/solaris/dlfcn.lo core/sys/solaris/elf.lo \ core/sys/solaris/err.lo core/sys/solaris/execinfo.lo \ core/sys/solaris/libelf.lo core/sys/solaris/link.lo \ core/sys/solaris/stdlib.lo core/sys/solaris/sys/elf.lo \ @@ -444,48 +470,48 @@ am__objects_21 = core/sys/solaris/dlfcn.lo core/sys/solaris/elf.lo \ core/sys/solaris/sys/priocntl.lo \ core/sys/solaris/sys/procset.lo core/sys/solaris/sys/types.lo \ core/sys/solaris/time.lo -@DRUNTIME_OS_SOLARIS_TRUE@am__objects_22 = $(am__objects_21) -@DRUNTIME_CPU_AARCH64_TRUE@am__objects_23 = config/aarch64/libgdruntime_la-switchcontext.lo -@DRUNTIME_CPU_ARM_TRUE@am__objects_24 = config/arm/libgdruntime_la-switchcontext.lo -@DRUNTIME_CPU_MIPS_TRUE@am__objects_25 = config/mips/libgdruntime_la-switchcontext.lo -@DRUNTIME_CPU_POWERPC_TRUE@am__objects_26 = config/powerpc/libgdruntime_la-switchcontext.lo -@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_TRUE@am__objects_27 = config/mingw/libgdruntime_la-switchcontext.lo -@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_FALSE@am__objects_28 = config/x86/libgdruntime_la-switchcontext.lo -@DRUNTIME_CPU_SYSTEMZ_TRUE@am__objects_29 = config/systemz/libgdruntime_la-get_tls_offset.lo -@DRUNTIME_CPU_S390_TRUE@am__objects_30 = config/s390/libgdruntime_la-get_tls_offset.lo -am__objects_31 = $(am__objects_4) $(am__objects_6) $(am__objects_8) \ - $(am__objects_10) $(am__objects_12) $(am__objects_14) \ - $(am__objects_16) $(am__objects_18) $(am__objects_20) \ - $(am__objects_22) $(am__objects_23) $(am__objects_24) \ - $(am__objects_25) $(am__objects_26) $(am__objects_27) \ - $(am__objects_28) $(am__objects_29) $(am__objects_30) -am__objects_32 = gcc/config.lo gcc/libbacktrace.lo -am__objects_33 = $(am__objects_1) $(am__objects_2) $(am__objects_31) \ - $(am__objects_32) -am_libgdruntime_la_OBJECTS = $(am__objects_33) +@DRUNTIME_OS_SOLARIS_TRUE@am__objects_23 = $(am__objects_22) +@DRUNTIME_CPU_AARCH64_TRUE@am__objects_24 = config/aarch64/libgdruntime_la-switchcontext.lo +@DRUNTIME_CPU_ARM_TRUE@am__objects_25 = config/arm/libgdruntime_la-switchcontext.lo +@DRUNTIME_CPU_MIPS_TRUE@am__objects_26 = config/mips/libgdruntime_la-switchcontext.lo +@DRUNTIME_CPU_POWERPC_TRUE@am__objects_27 = config/powerpc/libgdruntime_la-switchcontext.lo +@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_TRUE@am__objects_28 = config/mingw/libgdruntime_la-switchcontext.lo +@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_FALSE@am__objects_29 = config/x86/libgdruntime_la-switchcontext.lo +@DRUNTIME_CPU_SYSTEMZ_TRUE@am__objects_30 = config/systemz/libgdruntime_la-get_tls_offset.lo +@DRUNTIME_CPU_S390_TRUE@am__objects_31 = config/s390/libgdruntime_la-get_tls_offset.lo +am__objects_32 = $(am__objects_5) $(am__objects_7) $(am__objects_9) \ + $(am__objects_11) $(am__objects_13) $(am__objects_15) \ + $(am__objects_17) $(am__objects_19) $(am__objects_21) \ + $(am__objects_23) $(am__objects_24) $(am__objects_25) \ + $(am__objects_26) $(am__objects_27) $(am__objects_28) \ + $(am__objects_29) $(am__objects_30) $(am__objects_31) +am__objects_33 = gcc/config.lo gcc/libbacktrace.lo +am__objects_34 = $(am__objects_1) $(am__objects_2) $(am__objects_3) \ + $(am__objects_32) $(am__objects_33) +am_libgdruntime_la_OBJECTS = $(am__objects_34) libgdruntime_la_OBJECTS = $(am_libgdruntime_la_OBJECTS) am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) -am__objects_34 = core/stdc/libgdruntime_convenience_la-errno_.lo -@DRUNTIME_OS_MINGW_TRUE@am__objects_35 = $(am__objects_19) \ +am__objects_35 = core/stdc/libgdruntime_convenience_la-errno_.lo +@DRUNTIME_OS_MINGW_TRUE@am__objects_36 = $(am__objects_20) \ @DRUNTIME_OS_MINGW_TRUE@ config/mingw/libgdruntime_convenience_la-msvc.lo -@DRUNTIME_CPU_AARCH64_TRUE@am__objects_36 = config/aarch64/libgdruntime_convenience_la-switchcontext.lo -@DRUNTIME_CPU_ARM_TRUE@am__objects_37 = config/arm/libgdruntime_convenience_la-switchcontext.lo -@DRUNTIME_CPU_MIPS_TRUE@am__objects_38 = config/mips/libgdruntime_convenience_la-switchcontext.lo -@DRUNTIME_CPU_POWERPC_TRUE@am__objects_39 = config/powerpc/libgdruntime_convenience_la-switchcontext.lo -@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_TRUE@am__objects_40 = config/mingw/libgdruntime_convenience_la-switchcontext.lo -@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_FALSE@am__objects_41 = config/x86/libgdruntime_convenience_la-switchcontext.lo -@DRUNTIME_CPU_SYSTEMZ_TRUE@am__objects_42 = config/systemz/libgdruntime_convenience_la-get_tls_offset.lo -@DRUNTIME_CPU_S390_TRUE@am__objects_43 = config/s390/libgdruntime_convenience_la-get_tls_offset.lo -am__objects_44 = $(am__objects_4) $(am__objects_6) $(am__objects_8) \ - $(am__objects_10) $(am__objects_12) $(am__objects_14) \ - $(am__objects_16) $(am__objects_18) $(am__objects_35) \ - $(am__objects_22) $(am__objects_36) $(am__objects_37) \ - $(am__objects_38) $(am__objects_39) $(am__objects_40) \ - $(am__objects_41) $(am__objects_42) $(am__objects_43) -am__objects_45 = $(am__objects_1) $(am__objects_34) $(am__objects_44) \ - $(am__objects_32) -am__objects_46 = $(am__objects_45) -am_libgdruntime_convenience_la_OBJECTS = $(am__objects_46) +@DRUNTIME_CPU_AARCH64_TRUE@am__objects_37 = config/aarch64/libgdruntime_convenience_la-switchcontext.lo +@DRUNTIME_CPU_ARM_TRUE@am__objects_38 = config/arm/libgdruntime_convenience_la-switchcontext.lo +@DRUNTIME_CPU_MIPS_TRUE@am__objects_39 = config/mips/libgdruntime_convenience_la-switchcontext.lo +@DRUNTIME_CPU_POWERPC_TRUE@am__objects_40 = config/powerpc/libgdruntime_convenience_la-switchcontext.lo +@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_TRUE@am__objects_41 = config/mingw/libgdruntime_convenience_la-switchcontext.lo +@DRUNTIME_CPU_X86_TRUE@@DRUNTIME_OS_MINGW_FALSE@am__objects_42 = config/x86/libgdruntime_convenience_la-switchcontext.lo +@DRUNTIME_CPU_SYSTEMZ_TRUE@am__objects_43 = config/systemz/libgdruntime_convenience_la-get_tls_offset.lo +@DRUNTIME_CPU_S390_TRUE@am__objects_44 = config/s390/libgdruntime_convenience_la-get_tls_offset.lo +am__objects_45 = $(am__objects_5) $(am__objects_7) $(am__objects_9) \ + $(am__objects_11) $(am__objects_13) $(am__objects_15) \ + $(am__objects_17) $(am__objects_19) $(am__objects_36) \ + $(am__objects_23) $(am__objects_37) $(am__objects_38) \ + $(am__objects_39) $(am__objects_40) $(am__objects_41) \ + $(am__objects_42) $(am__objects_43) $(am__objects_44) +am__objects_46 = $(am__objects_1) $(am__objects_35) $(am__objects_3) \ + $(am__objects_45) $(am__objects_33) +am__objects_47 = $(am__objects_46) +am_libgdruntime_convenience_la_OBJECTS = $(am__objects_47) libgdruntime_convenience_la_OBJECTS = \ $(am_libgdruntime_convenience_la_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) @@ -728,7 +754,9 @@ LTDCOMPILE = $(LIBTOOL) --tag=D $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ # Include D build rules # Make sure GDC can find libdruntime include files -D_EXTRA_DFLAGS = -nostdinc -I $(srcdir) -I . +D_EXTRA_DFLAGS = -fpreview=dip1000 -fpreview=fieldwise -fpreview=dtorfields \ + -nostdinc -I $(srcdir) -I . + # D flags for compilation AM_DFLAGS = \ @@ -767,6 +795,7 @@ DRUNTIME_SOURCES_CONFIGURED = $(am__append_1) $(am__append_2) \ # Generated by configure DRUNTIME_DSOURCES_GENERATED = gcc/config.d gcc/libbacktrace.d ALL_DRUNTIME_SOURCES = $(DRUNTIME_DSOURCES) $(DRUNTIME_CSOURCES) \ + $(DRUNTIME_DSOURCES_STDCXX) \ $(DRUNTIME_SOURCES_CONFIGURED) $(DRUNTIME_DSOURCES_GENERATED) @@ -803,12 +832,30 @@ libgdruntime_convenience_la_LINK = $(libgdruntime_la_LINK) # https://www.gnu.org/software/automake/manual/html_node/Wildcards.html DRUNTIME_CSOURCES = core/stdc/errno_.c DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \ - core/checkedint.d core/cpuid.d core/demangle.d core/exception.d \ - core/internal/abort.d core/internal/arrayop.d \ - core/internal/attributes.d core/internal/convert.d \ - core/internal/hash.d core/internal/spinlock.d core/internal/string.d \ - core/internal/traits.d core/math.d core/memory.d core/runtime.d \ - core/simd.d core/stdc/assert_.d core/stdc/complex.d core/stdc/config.d \ + core/builtins.d core/checkedint.d core/cpuid.d core/demangle.d \ + core/exception.d core/gc/config.d core/gc/gcinterface.d \ + core/gc/registry.d core/internal/abort.d \ + core/internal/array/appending.d core/internal/array/capacity.d \ + core/internal/array/casting.d core/internal/array/comparison.d \ + core/internal/array/concatenation.d core/internal/array/construction.d \ + core/internal/array/equality.d core/internal/array/operations.d \ + core/internal/array/utils.d core/internal/atomic.d \ + core/internal/attributes.d core/internal/container/array.d \ + core/internal/container/common.d core/internal/container/hashtab.d \ + core/internal/container/treap.d core/internal/convert.d \ + core/internal/dassert.d core/internal/destruction.d \ + core/internal/entrypoint.d core/internal/gc/bits.d \ + core/internal/gc/impl/conservative/gc.d \ + core/internal/gc/impl/manual/gc.d core/internal/gc/impl/proto/gc.d \ + core/internal/gc/os.d core/internal/gc/pooltable.d \ + core/internal/gc/proxy.d core/internal/hash.d core/internal/lifetime.d \ + core/internal/moving.d core/internal/parseoptions.d \ + core/internal/postblit.d core/internal/qsort.d \ + core/internal/spinlock.d core/internal/string.d \ + core/internal/switch_.d core/internal/traits.d core/internal/utf.d \ + core/internal/util/array.d core/internal/util/math.d core/lifetime.d \ + core/math.d core/memory.d core/runtime.d core/simd.d \ + core/stdc/assert_.d core/stdc/complex.d core/stdc/config.d \ core/stdc/ctype.d core/stdc/errno.d core/stdc/fenv.d \ core/stdc/float_.d core/stdc/inttypes.d core/stdc/limits.d \ core/stdc/locale.d core/stdc/math.d core/stdc/signal.d \ @@ -816,28 +863,28 @@ DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \ core/stdc/stdio.d core/stdc/stdlib.d core/stdc/string.d \ core/stdc/tgmath.d core/stdc/time.d core/stdc/wchar_.d \ core/stdc/wctype.d core/sync/barrier.d core/sync/condition.d \ - core/sync/config.d core/sync/exception.d core/sync/mutex.d \ - core/sync/rwmutex.d core/sync/semaphore.d core/thread/context.d \ - core/thread/fiber.d core/thread/osthread.d core/thread/package.d \ - core/thread/threadbase.d core/thread/threadgroup.d core/thread/types.d \ - core/time.d core/vararg.d core/volatile.d gc/bits.d gc/config.d \ - gc/gcinterface.d gc/impl/conservative/gc.d gc/impl/manual/gc.d gc/os.d \ - gc/pooltable.d gc/proxy.d gcc/attribute.d gcc/attributes.d \ + core/sync/config.d core/sync/event.d core/sync/exception.d \ + core/sync/mutex.d core/sync/rwmutex.d core/sync/semaphore.d \ + core/thread/context.d core/thread/fiber.d core/thread/osthread.d \ + core/thread/package.d core/thread/threadbase.d \ + core/thread/threadgroup.d core/thread/types.d core/time.d \ + core/vararg.d core/volatile.d gcc/attribute.d gcc/attributes.d \ gcc/backtrace.d gcc/builtins.d gcc/deh.d gcc/emutls.d gcc/gthread.d \ gcc/sections/common.d gcc/sections/elf.d gcc/sections/macho.d \ gcc/sections/package.d gcc/sections/pecoff.d gcc/unwind/arm.d \ gcc/unwind/arm_common.d gcc/unwind/c6x.d gcc/unwind/generic.d \ gcc/unwind/package.d gcc/unwind/pe.d object.d rt/aApply.d rt/aApplyR.d \ - rt/aaA.d rt/adi.d rt/arrayassign.d rt/arraycast.d rt/arraycat.d \ - rt/cast_.d rt/config.d rt/critical_.d rt/deh.d rt/dmain2.d \ + rt/aaA.d rt/adi.d rt/arrayassign.d rt/arraycat.d rt/cast_.d \ + rt/config.d rt/critical_.d rt/deh.d rt/dmain2.d rt/ehalloc.d \ rt/invariant.d rt/lifetime.d rt/memory.d rt/minfo.d rt/monitor_.d \ - rt/obj.d rt/qsort.d rt/sections.d rt/switch_.d rt/tlsgc.d \ - rt/util/array.d rt/util/container/array.d rt/util/container/common.d \ - rt/util/container/hashtab.d rt/util/container/treap.d rt/util/random.d \ - rt/util/typeinfo.d rt/util/utf.d + rt/profilegc.d rt/sections.d rt/tlsgc.d rt/util/typeinfo.d \ + rt/util/utility.d -DRUNTIME_DSOURCES_STDCXX = core/stdcpp/exception.d \ - core/stdcpp/typeinfo.d +DRUNTIME_DSOURCES_STDCXX = core/stdcpp/allocator.d core/stdcpp/array.d \ + core/stdcpp/exception.d core/stdcpp/memory.d core/stdcpp/new_.d \ + core/stdcpp/string.d core/stdcpp/string_view.d \ + core/stdcpp/type_traits.d core/stdcpp/typeinfo.d core/stdcpp/utility.d \ + core/stdcpp/vector.d core/stdcpp/xutility.d DRUNTIME_DSOURCES_BIONIC = core/sys/bionic/err.d \ core/sys/bionic/fcntl.d core/sys/bionic/stdlib.d \ @@ -886,17 +933,19 @@ DRUNTIME_DSOURCES_FREEBSD = core/sys/freebsd/config.d \ DRUNTIME_DSOURCES_LINUX = core/sys/linux/config.d \ core/sys/linux/dlfcn.d core/sys/linux/elf.d core/sys/linux/epoll.d \ core/sys/linux/err.d core/sys/linux/errno.d core/sys/linux/execinfo.d \ - core/sys/linux/fcntl.d core/sys/linux/ifaddrs.d core/sys/linux/link.d \ + core/sys/linux/fcntl.d core/sys/linux/fs.d core/sys/linux/ifaddrs.d \ + core/sys/linux/io_uring.d core/sys/linux/link.d \ core/sys/linux/netinet/in_.d core/sys/linux/netinet/tcp.d \ - core/sys/linux/sched.d core/sys/linux/stdio.d core/sys/linux/string.d \ + core/sys/linux/perf_event.d core/sys/linux/sched.d \ + core/sys/linux/stdio.d core/sys/linux/string.d \ core/sys/linux/sys/auxv.d core/sys/linux/sys/eventfd.d \ core/sys/linux/sys/file.d core/sys/linux/sys/inotify.d \ core/sys/linux/sys/mman.d core/sys/linux/sys/prctl.d \ - core/sys/linux/sys/signalfd.d core/sys/linux/sys/socket.d \ - core/sys/linux/sys/sysinfo.d core/sys/linux/sys/time.d \ - core/sys/linux/sys/xattr.d core/sys/linux/termios.d \ - core/sys/linux/time.d core/sys/linux/timerfd.d core/sys/linux/tipc.d \ - core/sys/linux/unistd.d + core/sys/linux/sys/procfs.d core/sys/linux/sys/signalfd.d \ + core/sys/linux/sys/socket.d core/sys/linux/sys/sysinfo.d \ + core/sys/linux/sys/time.d core/sys/linux/sys/xattr.d \ + core/sys/linux/termios.d core/sys/linux/time.d \ + core/sys/linux/timerfd.d core/sys/linux/tipc.d core/sys/linux/unistd.d DRUNTIME_DSOURCES_NETBSD = core/sys/netbsd/dlfcn.d \ core/sys/netbsd/err.d core/sys/netbsd/execinfo.d \ @@ -908,13 +957,13 @@ DRUNTIME_DSOURCES_NETBSD = core/sys/netbsd/dlfcn.d \ core/sys/netbsd/sys/sysctl.d core/sys/netbsd/time.d DRUNTIME_DSOURCES_OPENBSD = core/sys/openbsd/dlfcn.d \ - core/sys/openbsd/err.d core/sys/openbsd/stdlib.d \ - core/sys/openbsd/string.d core/sys/openbsd/sys/cdefs.d \ - core/sys/openbsd/sys/elf.d core/sys/openbsd/sys/elf32.d \ - core/sys/openbsd/sys/elf64.d core/sys/openbsd/sys/elf_common.d \ - core/sys/openbsd/sys/link_elf.d core/sys/openbsd/sys/mman.d \ - core/sys/openbsd/sys/sysctl.d core/sys/openbsd/time.d \ - core/sys/openbsd/unistd.d + core/sys/openbsd/err.d core/sys/openbsd/execinfo.d \ + core/sys/openbsd/stdlib.d core/sys/openbsd/string.d \ + core/sys/openbsd/sys/cdefs.d core/sys/openbsd/sys/elf.d \ + core/sys/openbsd/sys/elf32.d core/sys/openbsd/sys/elf64.d \ + core/sys/openbsd/sys/elf_common.d core/sys/openbsd/sys/link_elf.d \ + core/sys/openbsd/sys/mman.d core/sys/openbsd/sys/sysctl.d \ + core/sys/openbsd/time.d core/sys/openbsd/unistd.d DRUNTIME_DSOURCES_POSIX = core/sys/posix/aio.d \ core/sys/posix/arpa/inet.d core/sys/posix/config.d \ @@ -1039,7 +1088,7 @@ DRUNTIME_DSOURCES_WINDOWS = core/sys/windows/accctrl.d \ core/sys/windows/winuser.d core/sys/windows/winver.d \ core/sys/windows/wtsapi32.d core/sys/windows/wtypes.d -DRUNTIME_DISOURCES = __entrypoint.di __main.di +DRUNTIME_DISOURCES = __main.di all: all-am .SUFFIXES: @@ -1126,21 +1175,93 @@ core/$(am__dirstamp): core/atomic.lo: core/$(am__dirstamp) core/attribute.lo: core/$(am__dirstamp) core/bitop.lo: core/$(am__dirstamp) +core/builtins.lo: core/$(am__dirstamp) core/checkedint.lo: core/$(am__dirstamp) core/cpuid.lo: core/$(am__dirstamp) core/demangle.lo: core/$(am__dirstamp) core/exception.lo: core/$(am__dirstamp) +core/gc/$(am__dirstamp): + @$(MKDIR_P) core/gc + @: > core/gc/$(am__dirstamp) +core/gc/config.lo: core/gc/$(am__dirstamp) +core/gc/gcinterface.lo: core/gc/$(am__dirstamp) +core/gc/registry.lo: core/gc/$(am__dirstamp) core/internal/$(am__dirstamp): @$(MKDIR_P) core/internal @: > core/internal/$(am__dirstamp) core/internal/abort.lo: core/internal/$(am__dirstamp) -core/internal/arrayop.lo: core/internal/$(am__dirstamp) +core/internal/array/$(am__dirstamp): + @$(MKDIR_P) core/internal/array + @: > core/internal/array/$(am__dirstamp) +core/internal/array/appending.lo: core/internal/array/$(am__dirstamp) +core/internal/array/capacity.lo: core/internal/array/$(am__dirstamp) +core/internal/array/casting.lo: core/internal/array/$(am__dirstamp) +core/internal/array/comparison.lo: \ + core/internal/array/$(am__dirstamp) +core/internal/array/concatenation.lo: \ + core/internal/array/$(am__dirstamp) +core/internal/array/construction.lo: \ + core/internal/array/$(am__dirstamp) +core/internal/array/equality.lo: core/internal/array/$(am__dirstamp) +core/internal/array/operations.lo: \ + core/internal/array/$(am__dirstamp) +core/internal/array/utils.lo: core/internal/array/$(am__dirstamp) +core/internal/atomic.lo: core/internal/$(am__dirstamp) core/internal/attributes.lo: core/internal/$(am__dirstamp) +core/internal/container/$(am__dirstamp): + @$(MKDIR_P) core/internal/container + @: > core/internal/container/$(am__dirstamp) +core/internal/container/array.lo: \ + core/internal/container/$(am__dirstamp) +core/internal/container/common.lo: \ + core/internal/container/$(am__dirstamp) +core/internal/container/hashtab.lo: \ + core/internal/container/$(am__dirstamp) +core/internal/container/treap.lo: \ + core/internal/container/$(am__dirstamp) core/internal/convert.lo: core/internal/$(am__dirstamp) +core/internal/dassert.lo: core/internal/$(am__dirstamp) +core/internal/destruction.lo: core/internal/$(am__dirstamp) +core/internal/entrypoint.lo: core/internal/$(am__dirstamp) +core/internal/gc/$(am__dirstamp): + @$(MKDIR_P) core/internal/gc + @: > core/internal/gc/$(am__dirstamp) +core/internal/gc/bits.lo: core/internal/gc/$(am__dirstamp) +core/internal/gc/impl/conservative/$(am__dirstamp): + @$(MKDIR_P) core/internal/gc/impl/conservative + @: > core/internal/gc/impl/conservative/$(am__dirstamp) +core/internal/gc/impl/conservative/gc.lo: \ + core/internal/gc/impl/conservative/$(am__dirstamp) +core/internal/gc/impl/manual/$(am__dirstamp): + @$(MKDIR_P) core/internal/gc/impl/manual + @: > core/internal/gc/impl/manual/$(am__dirstamp) +core/internal/gc/impl/manual/gc.lo: \ + core/internal/gc/impl/manual/$(am__dirstamp) +core/internal/gc/impl/proto/$(am__dirstamp): + @$(MKDIR_P) core/internal/gc/impl/proto + @: > core/internal/gc/impl/proto/$(am__dirstamp) +core/internal/gc/impl/proto/gc.lo: \ + core/internal/gc/impl/proto/$(am__dirstamp) +core/internal/gc/os.lo: core/internal/gc/$(am__dirstamp) +core/internal/gc/pooltable.lo: core/internal/gc/$(am__dirstamp) +core/internal/gc/proxy.lo: core/internal/gc/$(am__dirstamp) core/internal/hash.lo: core/internal/$(am__dirstamp) +core/internal/lifetime.lo: core/internal/$(am__dirstamp) +core/internal/moving.lo: core/internal/$(am__dirstamp) +core/internal/parseoptions.lo: core/internal/$(am__dirstamp) +core/internal/postblit.lo: core/internal/$(am__dirstamp) +core/internal/qsort.lo: core/internal/$(am__dirstamp) core/internal/spinlock.lo: core/internal/$(am__dirstamp) core/internal/string.lo: core/internal/$(am__dirstamp) +core/internal/switch_.lo: core/internal/$(am__dirstamp) core/internal/traits.lo: core/internal/$(am__dirstamp) +core/internal/utf.lo: core/internal/$(am__dirstamp) +core/internal/util/$(am__dirstamp): + @$(MKDIR_P) core/internal/util + @: > core/internal/util/$(am__dirstamp) +core/internal/util/array.lo: core/internal/util/$(am__dirstamp) +core/internal/util/math.lo: core/internal/util/$(am__dirstamp) +core/lifetime.lo: core/$(am__dirstamp) core/math.lo: core/$(am__dirstamp) core/memory.lo: core/$(am__dirstamp) core/runtime.lo: core/$(am__dirstamp) @@ -1176,6 +1297,7 @@ core/sync/$(am__dirstamp): core/sync/barrier.lo: core/sync/$(am__dirstamp) core/sync/condition.lo: core/sync/$(am__dirstamp) core/sync/config.lo: core/sync/$(am__dirstamp) +core/sync/event.lo: core/sync/$(am__dirstamp) core/sync/exception.lo: core/sync/$(am__dirstamp) core/sync/mutex.lo: core/sync/$(am__dirstamp) core/sync/rwmutex.lo: core/sync/$(am__dirstamp) @@ -1193,23 +1315,6 @@ core/thread/types.lo: core/thread/$(am__dirstamp) core/time.lo: core/$(am__dirstamp) core/vararg.lo: core/$(am__dirstamp) core/volatile.lo: core/$(am__dirstamp) -gc/$(am__dirstamp): - @$(MKDIR_P) gc - @: > gc/$(am__dirstamp) -gc/bits.lo: gc/$(am__dirstamp) -gc/config.lo: gc/$(am__dirstamp) -gc/gcinterface.lo: gc/$(am__dirstamp) -gc/impl/conservative/$(am__dirstamp): - @$(MKDIR_P) gc/impl/conservative - @: > gc/impl/conservative/$(am__dirstamp) -gc/impl/conservative/gc.lo: gc/impl/conservative/$(am__dirstamp) -gc/impl/manual/$(am__dirstamp): - @$(MKDIR_P) gc/impl/manual - @: > gc/impl/manual/$(am__dirstamp) -gc/impl/manual/gc.lo: gc/impl/manual/$(am__dirstamp) -gc/os.lo: gc/$(am__dirstamp) -gc/pooltable.lo: gc/$(am__dirstamp) -gc/proxy.lo: gc/$(am__dirstamp) gcc/$(am__dirstamp): @$(MKDIR_P) gcc @: > gcc/$(am__dirstamp) @@ -1245,38 +1350,42 @@ rt/aApplyR.lo: rt/$(am__dirstamp) rt/aaA.lo: rt/$(am__dirstamp) rt/adi.lo: rt/$(am__dirstamp) rt/arrayassign.lo: rt/$(am__dirstamp) -rt/arraycast.lo: rt/$(am__dirstamp) rt/arraycat.lo: rt/$(am__dirstamp) rt/cast_.lo: rt/$(am__dirstamp) rt/config.lo: rt/$(am__dirstamp) rt/critical_.lo: rt/$(am__dirstamp) rt/deh.lo: rt/$(am__dirstamp) rt/dmain2.lo: rt/$(am__dirstamp) +rt/ehalloc.lo: rt/$(am__dirstamp) rt/invariant.lo: rt/$(am__dirstamp) rt/lifetime.lo: rt/$(am__dirstamp) rt/memory.lo: rt/$(am__dirstamp) rt/minfo.lo: rt/$(am__dirstamp) rt/monitor_.lo: rt/$(am__dirstamp) -rt/obj.lo: rt/$(am__dirstamp) -rt/qsort.lo: rt/$(am__dirstamp) +rt/profilegc.lo: rt/$(am__dirstamp) rt/sections.lo: rt/$(am__dirstamp) -rt/switch_.lo: rt/$(am__dirstamp) rt/tlsgc.lo: rt/$(am__dirstamp) rt/util/$(am__dirstamp): @$(MKDIR_P) rt/util @: > rt/util/$(am__dirstamp) -rt/util/array.lo: rt/util/$(am__dirstamp) -rt/util/container/$(am__dirstamp): - @$(MKDIR_P) rt/util/container - @: > rt/util/container/$(am__dirstamp) -rt/util/container/array.lo: rt/util/container/$(am__dirstamp) -rt/util/container/common.lo: rt/util/container/$(am__dirstamp) -rt/util/container/hashtab.lo: rt/util/container/$(am__dirstamp) -rt/util/container/treap.lo: rt/util/container/$(am__dirstamp) -rt/util/random.lo: rt/util/$(am__dirstamp) rt/util/typeinfo.lo: rt/util/$(am__dirstamp) -rt/util/utf.lo: rt/util/$(am__dirstamp) +rt/util/utility.lo: rt/util/$(am__dirstamp) core/stdc/libgdruntime_la-errno_.lo: core/stdc/$(am__dirstamp) +core/stdcpp/$(am__dirstamp): + @$(MKDIR_P) core/stdcpp + @: > core/stdcpp/$(am__dirstamp) +core/stdcpp/allocator.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/array.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/exception.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/memory.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/new_.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/string.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/string_view.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/type_traits.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/typeinfo.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/utility.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/vector.lo: core/stdcpp/$(am__dirstamp) +core/stdcpp/xutility.lo: core/stdcpp/$(am__dirstamp) core/sys/posix/$(am__dirstamp): @$(MKDIR_P) core/sys/posix @: > core/sys/posix/$(am__dirstamp) @@ -1506,6 +1615,7 @@ core/sys/openbsd/$(am__dirstamp): @: > core/sys/openbsd/$(am__dirstamp) core/sys/openbsd/dlfcn.lo: core/sys/openbsd/$(am__dirstamp) core/sys/openbsd/err.lo: core/sys/openbsd/$(am__dirstamp) +core/sys/openbsd/execinfo.lo: core/sys/openbsd/$(am__dirstamp) core/sys/openbsd/stdlib.lo: core/sys/openbsd/$(am__dirstamp) core/sys/openbsd/string.lo: core/sys/openbsd/$(am__dirstamp) core/sys/openbsd/sys/$(am__dirstamp): @@ -1534,13 +1644,16 @@ core/sys/linux/err.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/errno.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/execinfo.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/fcntl.lo: core/sys/linux/$(am__dirstamp) +core/sys/linux/fs.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/ifaddrs.lo: core/sys/linux/$(am__dirstamp) +core/sys/linux/io_uring.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/link.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/netinet/$(am__dirstamp): @$(MKDIR_P) core/sys/linux/netinet @: > core/sys/linux/netinet/$(am__dirstamp) core/sys/linux/netinet/in_.lo: core/sys/linux/netinet/$(am__dirstamp) core/sys/linux/netinet/tcp.lo: core/sys/linux/netinet/$(am__dirstamp) +core/sys/linux/perf_event.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/sched.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/stdio.lo: core/sys/linux/$(am__dirstamp) core/sys/linux/string.lo: core/sys/linux/$(am__dirstamp) @@ -1553,6 +1666,7 @@ core/sys/linux/sys/file.lo: core/sys/linux/sys/$(am__dirstamp) core/sys/linux/sys/inotify.lo: core/sys/linux/sys/$(am__dirstamp) core/sys/linux/sys/mman.lo: core/sys/linux/sys/$(am__dirstamp) core/sys/linux/sys/prctl.lo: core/sys/linux/sys/$(am__dirstamp) +core/sys/linux/sys/procfs.lo: core/sys/linux/sys/$(am__dirstamp) core/sys/linux/sys/signalfd.lo: core/sys/linux/sys/$(am__dirstamp) core/sys/linux/sys/socket.lo: core/sys/linux/sys/$(am__dirstamp) core/sys/linux/sys/sysinfo.lo: core/sys/linux/sys/$(am__dirstamp) @@ -1857,10 +1971,28 @@ mostlyclean-compile: -rm -f config/x86/*.lo -rm -f core/*.$(OBJEXT) -rm -f core/*.lo + -rm -f core/gc/*.$(OBJEXT) + -rm -f core/gc/*.lo -rm -f core/internal/*.$(OBJEXT) -rm -f core/internal/*.lo + -rm -f core/internal/array/*.$(OBJEXT) + -rm -f core/internal/array/*.lo + -rm -f core/internal/container/*.$(OBJEXT) + -rm -f core/internal/container/*.lo + -rm -f core/internal/gc/*.$(OBJEXT) + -rm -f core/internal/gc/*.lo + -rm -f core/internal/gc/impl/conservative/*.$(OBJEXT) + -rm -f core/internal/gc/impl/conservative/*.lo + -rm -f core/internal/gc/impl/manual/*.$(OBJEXT) + -rm -f core/internal/gc/impl/manual/*.lo + -rm -f core/internal/gc/impl/proto/*.$(OBJEXT) + -rm -f core/internal/gc/impl/proto/*.lo + -rm -f core/internal/util/*.$(OBJEXT) + -rm -f core/internal/util/*.lo -rm -f core/stdc/*.$(OBJEXT) -rm -f core/stdc/*.lo + -rm -f core/stdcpp/*.$(OBJEXT) + -rm -f core/stdcpp/*.lo -rm -f core/sync/*.$(OBJEXT) -rm -f core/sync/*.lo -rm -f core/sys/bionic/*.$(OBJEXT) @@ -1921,12 +2053,6 @@ mostlyclean-compile: -rm -f core/sys/windows/stdc/*.lo -rm -f core/thread/*.$(OBJEXT) -rm -f core/thread/*.lo - -rm -f gc/*.$(OBJEXT) - -rm -f gc/*.lo - -rm -f gc/impl/conservative/*.$(OBJEXT) - -rm -f gc/impl/conservative/*.lo - -rm -f gc/impl/manual/*.$(OBJEXT) - -rm -f gc/impl/manual/*.lo -rm -f gcc/*.$(OBJEXT) -rm -f gcc/*.lo -rm -f gcc/sections/*.$(OBJEXT) @@ -1937,8 +2063,6 @@ mostlyclean-compile: -rm -f rt/*.lo -rm -f rt/util/*.$(OBJEXT) -rm -f rt/util/*.lo - -rm -f rt/util/container/*.$(OBJEXT) - -rm -f rt/util/container/*.lo distclean-compile: -rm -f *.tab.c @@ -2035,8 +2159,17 @@ clean-libtool: -rm -rf config/systemz/.libs config/systemz/_libs -rm -rf config/x86/.libs config/x86/_libs -rm -rf core/.libs core/_libs + -rm -rf core/gc/.libs core/gc/_libs -rm -rf core/internal/.libs core/internal/_libs + -rm -rf core/internal/array/.libs core/internal/array/_libs + -rm -rf core/internal/container/.libs core/internal/container/_libs + -rm -rf core/internal/gc/.libs core/internal/gc/_libs + -rm -rf core/internal/gc/impl/conservative/.libs core/internal/gc/impl/conservative/_libs + -rm -rf core/internal/gc/impl/manual/.libs core/internal/gc/impl/manual/_libs + -rm -rf core/internal/gc/impl/proto/.libs core/internal/gc/impl/proto/_libs + -rm -rf core/internal/util/.libs core/internal/util/_libs -rm -rf core/stdc/.libs core/stdc/_libs + -rm -rf core/stdcpp/.libs core/stdcpp/_libs -rm -rf core/sync/.libs core/sync/_libs -rm -rf core/sys/bionic/.libs core/sys/bionic/_libs -rm -rf core/sys/darwin/.libs core/sys/darwin/_libs @@ -2067,15 +2200,11 @@ clean-libtool: -rm -rf core/sys/windows/.libs core/sys/windows/_libs -rm -rf core/sys/windows/stdc/.libs core/sys/windows/stdc/_libs -rm -rf core/thread/.libs core/thread/_libs - -rm -rf gc/.libs gc/_libs - -rm -rf gc/impl/conservative/.libs gc/impl/conservative/_libs - -rm -rf gc/impl/manual/.libs gc/impl/manual/_libs -rm -rf gcc/.libs gcc/_libs -rm -rf gcc/sections/.libs gcc/sections/_libs -rm -rf gcc/unwind/.libs gcc/unwind/_libs -rm -rf rt/.libs rt/_libs -rm -rf rt/util/.libs rt/util/_libs - -rm -rf rt/util/container/.libs rt/util/container/_libs install-toolexeclibDATA: $(toolexeclib_DATA) @$(NORMAL_INSTALL) @list='$(toolexeclib_DATA)'; test -n "$(toolexeclibdir)" || list=; \ @@ -2191,8 +2320,17 @@ distclean-generic: -rm -f config/systemz/$(am__dirstamp) -rm -f config/x86/$(am__dirstamp) -rm -f core/$(am__dirstamp) + -rm -f core/gc/$(am__dirstamp) -rm -f core/internal/$(am__dirstamp) + -rm -f core/internal/array/$(am__dirstamp) + -rm -f core/internal/container/$(am__dirstamp) + -rm -f core/internal/gc/$(am__dirstamp) + -rm -f core/internal/gc/impl/conservative/$(am__dirstamp) + -rm -f core/internal/gc/impl/manual/$(am__dirstamp) + -rm -f core/internal/gc/impl/proto/$(am__dirstamp) + -rm -f core/internal/util/$(am__dirstamp) -rm -f core/stdc/$(am__dirstamp) + -rm -f core/stdcpp/$(am__dirstamp) -rm -f core/sync/$(am__dirstamp) -rm -f core/sys/bionic/$(am__dirstamp) -rm -f core/sys/darwin/$(am__dirstamp) @@ -2223,15 +2361,11 @@ distclean-generic: -rm -f core/sys/windows/$(am__dirstamp) -rm -f core/sys/windows/stdc/$(am__dirstamp) -rm -f core/thread/$(am__dirstamp) - -rm -f gc/$(am__dirstamp) - -rm -f gc/impl/conservative/$(am__dirstamp) - -rm -f gc/impl/manual/$(am__dirstamp) -rm -f gcc/$(am__dirstamp) -rm -f gcc/sections/$(am__dirstamp) -rm -f gcc/unwind/$(am__dirstamp) -rm -f rt/$(am__dirstamp) -rm -f rt/util/$(am__dirstamp) - -rm -f rt/util/container/$(am__dirstamp) maintainer-clean-generic: @echo "This command is intended for maintainers to use" diff --git a/libphobos/libdruntime/__entrypoint.di b/libphobos/libdruntime/__entrypoint.di deleted file mode 100644 index fba2ae28..0000000 --- a/libphobos/libdruntime/__entrypoint.di +++ /dev/null @@ -1,56 +0,0 @@ -/* GDC -- D front-end for GCC - Copyright (C) 2013-2021 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 - . -*/ - -/* This module provides the C main() function supplied by the user's program. */ - -module __entrypoint; - -extern(C): - -/* The D main() function supplied by the user's program - - It always has `_Dmain` symbol name and uses C calling convention. - But D frontend returns its type as `extern(D)` because of Issue 9028. - As we need to deal with actual calling convention we have to mark it - as `extern(C)` and use its symbol name. -*/ - -int _Dmain(char[][] args); -int _d_run_main(int argc, char **argv, void* mainFunc); - -/* Substitutes for the C main() function. Just calls into d_run_main with - the default main function. Applications are free to implement their own - main function and call the _d_run_main function themselves with any main - function. -*/ - -int main(int argc, char **argv) -{ - return _d_run_main(argc, argv, &_Dmain); -} - -/* This is apparently needed on Solaris because the C tool chain seems to - expect the main function to be called _main. It needs both not just one! -*/ - -version (Solaris) -int _main(int argc, char** argv) -{ - return main(argc, argv); -} - diff --git a/libphobos/libdruntime/core/atomic.d b/libphobos/libdruntime/core/atomic.d index 1d0a2ea..e6a82e5 100644 --- a/libphobos/libdruntime/core/atomic.d +++ b/libphobos/libdruntime/core/atomic.d @@ -4,1691 +4,915 @@ * * Copyright: Copyright Sean Kelly 2005 - 2016. * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) - * Authors: Sean Kelly, Alex Rønne Petersen + * Authors: Sean Kelly, Alex Rønne Petersen, Manu Evans * Source: $(DRUNTIMESRC core/_atomic.d) */ - -/* NOTE: This file has been patched from the original DMD distribution to - * work with the GDC compiler. - */ module core.atomic; -version (D_InlineAsm_X86) -{ - version = AsmX86; - version = AsmX86_32; - enum has64BitCAS = true; - enum has128BitCAS = false; -} -else version (D_InlineAsm_X86_64) -{ - version = AsmX86; - version = AsmX86_64; - enum has64BitCAS = true; - enum has128BitCAS = true; -} -else version (GNU) -{ - import gcc.config; - enum has64BitCAS = GNU_Have_64Bit_Atomics; - enum has128BitCAS = GNU_Have_LibAtomic; -} -else -{ - enum has64BitCAS = false; - enum has128BitCAS = false; -} - -private -{ - template HeadUnshared(T) - { - static if ( is( T U : shared(U*) ) ) - alias shared(U)* HeadUnshared; - else - alias T HeadUnshared; - } -} - - -version (AsmX86) -{ - // NOTE: Strictly speaking, the x86 supports atomic operations on - // unaligned values. However, this is far slower than the - // common case, so such behavior should be prohibited. - private bool atomicValueIsProperlyAligned(T)( ref T val ) pure nothrow @nogc @trusted - { - return atomicPtrIsProperlyAligned(&val); - } - - private bool atomicPtrIsProperlyAligned(T)( T* ptr ) pure nothrow @nogc @safe - { - // NOTE: 32 bit x86 systems support 8 byte CAS, which only requires - // 4 byte alignment, so use size_t as the align type here. - static if ( T.sizeof > size_t.sizeof ) - return cast(size_t)ptr % size_t.sizeof == 0; - else - return cast(size_t)ptr % T.sizeof == 0; - } -} +import core.internal.atomic; +import core.internal.attributes : betterC; +import core.internal.traits : hasUnsharedIndirections; - -version (CoreDdoc) +/** + * Specifies the memory ordering semantics of an atomic operation. + * + * See_Also: + * $(HTTP en.cppreference.com/w/cpp/atomic/memory_order) + */ +enum MemoryOrder { /** - * Performs the binary operation 'op' on val using 'mod' as the modifier. - * - * Params: - * val = The target variable. - * mod = The modifier to apply. - * - * Returns: - * The result of the operation. - */ - HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc @safe - if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) - { - return HeadUnshared!(T).init; - } - - - /** - * Stores 'writeThis' to the memory referenced by 'here' if the value - * referenced by 'here' is equal to 'ifThis'. This operation is both - * lock-free and atomic. - * - * Params: - * here = The address of the destination variable. - * writeThis = The value to store. - * ifThis = The comparison value. - * - * Returns: - * true if the store occurred, false if not. + * Not sequenced. + * Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#monotonic, LLVM AtomicOrdering.Monotonic) + * and C++11/C11 `memory_order_relaxed`. */ - bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe - if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ); - - /// Ditto - bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe - if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ); - - /// Ditto - bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe - if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ); - + raw = 0, /** - * Loads 'val' from memory and returns it. The memory barrier specified - * by 'ms' is applied to the operation, which is fully sequenced by - * default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, - * and MemoryOrder.seq. - * - * Params: - * val = The target variable. - * - * Returns: - * The value of 'val'. + * Hoist-load + hoist-store barrier. + * Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#acquire, LLVM AtomicOrdering.Acquire) + * and C++11/C11 `memory_order_acquire`. */ - HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq,T)( ref const shared T val ) pure nothrow @nogc @safe - { - return HeadUnshared!(T).init; - } - - + acq = 2, /** - * Writes 'newval' into 'val'. The memory barrier specified by 'ms' is - * applied to the operation, which is fully sequenced by default. - * Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and - * MemoryOrder.seq. - * - * Params: - * val = The target variable. - * newval = The value to store. + * Sink-load + sink-store barrier. + * Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#release, LLVM AtomicOrdering.Release) + * and C++11/C11 `memory_order_release`. */ - void atomicStore(MemoryOrder ms = MemoryOrder.seq,T,V1)( ref shared T val, V1 newval ) pure nothrow @nogc @safe - if ( __traits( compiles, { val = newval; } ) ) - { - - } - - + rel = 3, /** - * Specifies the memory ordering semantics of an atomic operation. + * Acquire + release barrier. + * Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#acquirerelease, LLVM AtomicOrdering.AcquireRelease) + * and C++11/C11 `memory_order_acq_rel`. */ - enum MemoryOrder - { - raw, /// Not sequenced. - acq, /// Hoist-load + hoist-store barrier. - rel, /// Sink-load + sink-store barrier. - seq, /// Fully sequenced (acquire + release). - } - - deprecated("Please use MemoryOrder instead.") - alias MemoryOrder msync; - + acq_rel = 4, /** - * Inserts a full load/store memory fence (on platforms that need it). This ensures - * that all loads and stores before a call to this function are executed before any - * loads and stores after the call. + * Fully sequenced (acquire + release). Corresponds to + * $(LINK2 https://llvm.org/docs/Atomics.html#sequentiallyconsistent, LLVM AtomicOrdering.SequentiallyConsistent) + * and C++11/C11 `memory_order_seq_cst`. */ - void atomicFence() nothrow @nogc; + seq = 5, } -else version (AsmX86_32) + +/** + * Loads 'val' from memory and returns it. The memory barrier specified + * by 'ms' is applied to the operation, which is fully sequenced by + * default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, + * and MemoryOrder.seq. + * + * Params: + * val = The target variable. + * + * Returns: + * The value of 'val'. + */ +T atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref return scope const T val) pure nothrow @nogc @trusted + if (!is(T == shared U, U) && !is(T == shared inout U, U) && !is(T == shared const U, U)) { - // Uses specialized asm for fast fetch and add operations - private HeadUnshared!(T) atomicFetchAdd(T)( ref shared T val, size_t mod ) pure nothrow @nogc @safe - if ( T.sizeof <= 4 ) + static if (__traits(isFloating, T)) { - size_t tmp = mod; - asm pure nothrow @nogc @trusted - { - mov EAX, tmp; - mov EDX, val; - } - static if (T.sizeof == 1) asm pure nothrow @nogc @trusted { lock; xadd[EDX], AL; } - else static if (T.sizeof == 2) asm pure nothrow @nogc @trusted { lock; xadd[EDX], AX; } - else static if (T.sizeof == 4) asm pure nothrow @nogc @trusted { lock; xadd[EDX], EAX; } - - asm pure nothrow @nogc @trusted - { - mov tmp, EAX; - } - - return cast(T)tmp; + alias IntTy = IntForFloat!T; + IntTy r = core.internal.atomic.atomicLoad!ms(cast(IntTy*)&val); + return *cast(T*)&r; } + else + return core.internal.atomic.atomicLoad!ms(cast(T*)&val); +} - private HeadUnshared!(T) atomicFetchSub(T)( ref shared T val, size_t mod ) pure nothrow @nogc @safe - if ( T.sizeof <= 4) - { - return atomicFetchAdd(val, -mod); - } +/// Ditto +T atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref return scope shared const T val) pure nothrow @nogc @trusted + if (!hasUnsharedIndirections!T) +{ + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!T, "Copying `" ~ shared(const(T)).stringof ~ "` would violate shared."); - HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc - if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) - in - { - assert(atomicValueIsProperlyAligned(val)); - } - body - { - // binary operators - // - // + - * / % ^^ & - // | ^ << >> >>> ~ in - // == != < <= > >= - static if ( op == "+" || op == "-" || op == "*" || op == "/" || - op == "%" || op == "^^" || op == "&" || op == "|" || - op == "^" || op == "<<" || op == ">>" || op == ">>>" || - op == "~" || // skip "in" - op == "==" || op == "!=" || op == "<" || op == "<=" || - op == ">" || op == ">=" ) - { - HeadUnshared!(T) get = atomicLoad!(MemoryOrder.raw)( val ); - mixin( "return get " ~ op ~ " mod;" ); - } - else - // assignment operators - // - // += -= *= /= %= ^^= &= - // |= ^= <<= >>= >>>= ~= - static if ( op == "+=" && __traits(isIntegral, T) && T.sizeof <= 4 && V1.sizeof <= 4) - { - return cast(T)(atomicFetchAdd!(T)(val, mod) + mod); - } - else static if ( op == "-=" && __traits(isIntegral, T) && T.sizeof <= 4 && V1.sizeof <= 4) - { - return cast(T)(atomicFetchSub!(T)(val, mod) - mod); - } - else static if ( op == "+=" || op == "-=" || op == "*=" || op == "/=" || - op == "%=" || op == "^^=" || op == "&=" || op == "|=" || - op == "^=" || op == "<<=" || op == ">>=" || op == ">>>=" ) // skip "~=" - { - HeadUnshared!(T) get, set; + return atomicLoad!ms(*cast(T*)&val); +} - do - { - get = set = atomicLoad!(MemoryOrder.raw)( val ); - mixin( "set " ~ op ~ " mod;" ); - } while ( !casByRef( val, get, set ) ); - return set; - } - else - { - static assert( false, "Operation not supported." ); - } - } +/// Ditto +TailShared!T atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref shared const T val) pure nothrow @nogc @trusted + if (hasUnsharedIndirections!T) +{ + // HACK: DEPRECATE THIS FUNCTION, IT IS INVALID TO DO ATOMIC LOAD OF SHARED CLASS + // this is here because code exists in the wild that does this... - bool casByRef(T,V1,V2)( ref T value, V1 ifThis, V2 writeThis ) pure nothrow @nogc @trusted - { - return cas(&value, ifThis, writeThis); - } + return core.internal.atomic.atomicLoad!ms(cast(TailShared!T*)&val); +} - bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe - if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) - { - return casImpl(here, ifThis, writeThis); - } +/** + * Writes 'newval' into 'val'. The memory barrier specified by 'ms' is + * applied to the operation, which is fully sequenced by default. + * Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and + * MemoryOrder.seq. + * + * Params: + * val = The target variable. + * newval = The value to store. + */ +void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) pure nothrow @nogc @trusted + if (!is(T == shared) && !is(V == shared)) +{ + import core.internal.traits : hasElaborateCopyConstructor; + static assert (!hasElaborateCopyConstructor!T, "`T` may not have an elaborate copy: atomic operations override regular copying semantics."); - bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe - if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ) - { - return casImpl(here, ifThis, writeThis); - } + // resolve implicit conversions + T arg = newval; - bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe - if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) + static if (__traits(isFloating, T)) { - return casImpl(here, ifThis, writeThis); + alias IntTy = IntForFloat!T; + core.internal.atomic.atomicStore!ms(cast(IntTy*)&val, *cast(IntTy*)&arg); } + else + core.internal.atomic.atomicStore!ms(&val, arg); +} - private bool casImpl(T,V1,V2)( shared(T)* here, V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe - in +/// Ditto +void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure nothrow @nogc @trusted + if (!is(T == class)) +{ + static if (is (V == shared U, U)) + alias Thunk = U; + else { - assert( atomicPtrIsProperlyAligned( here ) ); + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V, "Copying argument `" ~ V.stringof ~ " newval` to `" ~ shared(T).stringof ~ " here` would violate shared."); + alias Thunk = V; } - body - { - static if ( T.sizeof == byte.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 1 Byte CAS - ////////////////////////////////////////////////////////////////// - - asm pure nothrow @nogc @trusted - { - mov DL, writeThis; - mov AL, ifThis; - mov ECX, here; - lock; // lock always needed to make this op atomic - cmpxchg [ECX], DL; - setz AL; - } - } - else static if ( T.sizeof == short.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 2 Byte CAS - ////////////////////////////////////////////////////////////////// + atomicStore!ms(*cast(T*)&val, *cast(Thunk*)&newval); +} - asm pure nothrow @nogc @trusted - { - mov DX, writeThis; - mov AX, ifThis; - mov ECX, here; - lock; // lock always needed to make this op atomic - cmpxchg [ECX], DX; - setz AL; - } - } - else static if ( T.sizeof == int.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 4 Byte CAS - ////////////////////////////////////////////////////////////////// +/// Ditto +void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, shared V newval) pure nothrow @nogc @trusted + if (is(T == class)) +{ + static assert (is (V : T), "Can't assign `newval` of type `shared " ~ V.stringof ~ "` to `shared " ~ T.stringof ~ "`."); - asm pure nothrow @nogc @trusted - { - mov EDX, writeThis; - mov EAX, ifThis; - mov ECX, here; - lock; // lock always needed to make this op atomic - cmpxchg [ECX], EDX; - setz AL; - } - } - else static if ( T.sizeof == long.sizeof && has64BitCAS ) - { + core.internal.atomic.atomicStore!ms(cast(T*)&val, cast(V)newval); +} - ////////////////////////////////////////////////////////////////// - // 8 Byte CAS on a 32-Bit Processor - ////////////////////////////////////////////////////////////////// +/** + * Atomically adds `mod` to the value referenced by `val` and returns the value `val` held previously. + * This operation is both lock-free and atomic. + * + * Params: + * val = Reference to the value to modify. + * mod = The value to add. + * + * Returns: + * The value held previously by `val`. + */ +T atomicFetchAdd(MemoryOrder ms = MemoryOrder.seq, T)(ref return scope T val, size_t mod) pure nothrow @nogc @trusted + if ((__traits(isIntegral, T) || is(T == U*, U)) && !is(T == shared)) +in (atomicValueIsProperlyAligned(val)) +{ + static if (is(T == U*, U)) + return cast(T)core.internal.atomic.atomicFetchAdd!ms(cast(size_t*)&val, mod * U.sizeof); + else + return core.internal.atomic.atomicFetchAdd!ms(&val, cast(T)mod); +} - asm pure nothrow @nogc @trusted - { - push EDI; - push EBX; - lea EDI, writeThis; - mov EBX, [EDI]; - mov ECX, 4[EDI]; - lea EDI, ifThis; - mov EAX, [EDI]; - mov EDX, 4[EDI]; - mov EDI, here; - lock; // lock always needed to make this op atomic - cmpxchg8b [EDI]; - setz AL; - pop EBX; - pop EDI; +/// Ditto +T atomicFetchAdd(MemoryOrder ms = MemoryOrder.seq, T)(ref return scope shared T val, size_t mod) pure nothrow @nogc @trusted + if (__traits(isIntegral, T) || is(T == U*, U)) +in (atomicValueIsProperlyAligned(val)) +{ + return atomicFetchAdd!ms(*cast(T*)&val, mod); +} - } +/** + * Atomically subtracts `mod` from the value referenced by `val` and returns the value `val` held previously. + * This operation is both lock-free and atomic. + * + * Params: + * val = Reference to the value to modify. + * mod = The value to subtract. + * + * Returns: + * The value held previously by `val`. + */ +T atomicFetchSub(MemoryOrder ms = MemoryOrder.seq, T)(ref return scope T val, size_t mod) pure nothrow @nogc @trusted + if ((__traits(isIntegral, T) || is(T == U*, U)) && !is(T == shared)) +in (atomicValueIsProperlyAligned(val)) +{ + static if (is(T == U*, U)) + return cast(T)core.internal.atomic.atomicFetchSub!ms(cast(size_t*)&val, mod * U.sizeof); + else + return core.internal.atomic.atomicFetchSub!ms(&val, cast(T)mod); +} - } - else - { - static assert( false, "Invalid template type specified." ); - } - } +/// Ditto +T atomicFetchSub(MemoryOrder ms = MemoryOrder.seq, T)(ref return scope shared T val, size_t mod) pure nothrow @nogc @trusted + if (__traits(isIntegral, T) || is(T == U*, U)) +in (atomicValueIsProperlyAligned(val)) +{ + return atomicFetchSub!ms(*cast(T*)&val, mod); +} +/** + * Exchange `exchangeWith` with the memory referenced by `here`. + * This operation is both lock-free and atomic. + * + * Params: + * here = The address of the destination variable. + * exchangeWith = The value to exchange. + * + * Returns: + * The value held previously by `here`. + */ +T atomicExchange(MemoryOrder ms = MemoryOrder.seq,T,V)(T* here, V exchangeWith) pure nothrow @nogc @trusted + if (!is(T == shared) && !is(V == shared)) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + // resolve implicit conversions + T arg = exchangeWith; - enum MemoryOrder + static if (__traits(isFloating, T)) { - raw, - acq, - rel, - seq, + alias IntTy = IntForFloat!T; + IntTy r = core.internal.atomic.atomicExchange!ms(cast(IntTy*)here, *cast(IntTy*)&arg); + return *cast(shared(T)*)&r; } + else + return core.internal.atomic.atomicExchange!ms(here, arg); +} - deprecated("Please use MemoryOrder instead.") - alias MemoryOrder msync; - - - private +/// Ditto +TailShared!T atomicExchange(MemoryOrder ms = MemoryOrder.seq,T,V)(shared(T)* here, V exchangeWith) pure nothrow @nogc @trusted + if (!is(T == class) && !is(T == interface)) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + static if (is (V == shared U, U)) + alias Thunk = U; + else { - // NOTE: x86 loads implicitly have acquire semantics so a memory - // barrier is only necessary on releases. - template needsLoadBarrier( MemoryOrder ms ) - { - enum bool needsLoadBarrier = ms == MemoryOrder.seq; - } - - - // NOTE: x86 stores implicitly have release semantics so a memory - // barrier is only necessary on acquires. - template needsStoreBarrier( MemoryOrder ms ) - { - enum bool needsStoreBarrier = ms == MemoryOrder.seq; - } + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V, "Copying `exchangeWith` of type `" ~ V.stringof ~ "` to `" ~ shared(T).stringof ~ "` would violate shared."); + alias Thunk = V; } + return atomicExchange!ms(cast(T*)here, *cast(Thunk*)&exchangeWith); +} +/// Ditto +shared(T) atomicExchange(MemoryOrder ms = MemoryOrder.seq,T,V)(shared(T)* here, shared(V) exchangeWith) pure nothrow @nogc @trusted + if (is(T == class) || is(T == interface)) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + static assert (is (V : T), "Can't assign `exchangeWith` of type `" ~ shared(V).stringof ~ "` to `" ~ shared(T).stringof ~ "`."); - HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @safe - if (!__traits(isFloating, T)) - { - static assert( ms != MemoryOrder.rel, "invalid MemoryOrder for atomicLoad()" ); - static assert( __traits(isPOD, T), "argument to atomicLoad() must be POD" ); - - static if ( T.sizeof == byte.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 1 Byte Load - ////////////////////////////////////////////////////////////////// - - static if ( needsLoadBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov DL, 0; - mov AL, 0; - mov ECX, val; - lock; // lock always needed to make this op atomic - cmpxchg [ECX], DL; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov AL, [EAX]; - } - } - } - else static if ( T.sizeof == short.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 2 Byte Load - ////////////////////////////////////////////////////////////////// + return cast(shared)core.internal.atomic.atomicExchange!ms(cast(T*)here, cast(V)exchangeWith); +} - static if ( needsLoadBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov DX, 0; - mov AX, 0; - mov ECX, val; - lock; // lock always needed to make this op atomic - cmpxchg [ECX], DX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov AX, [EAX]; - } - } - } - else static if ( T.sizeof == int.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 4 Byte Load - ////////////////////////////////////////////////////////////////// +/** + * Performs either compare-and-set or compare-and-swap (or exchange). + * + * There are two categories of overloads in this template: + * The first category does a simple compare-and-set. + * The comparison value (`ifThis`) is treated as an rvalue. + * + * The second category does a compare-and-swap (a.k.a. compare-and-exchange), + * and expects `ifThis` to be a pointer type, where the previous value + * of `here` will be written. + * + * This operation is both lock-free and atomic. + * + * Params: + * here = The address of the destination variable. + * writeThis = The value to store. + * ifThis = The comparison value. + * + * Returns: + * true if the store occurred, false if not. + */ +template cas(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq) +{ + /// Compare-and-set for non-shared values + bool cas(T, V1, V2)(T* here, V1 ifThis, V2 writeThis) pure nothrow @nogc @trusted + if (!is(T == shared) && is(T : V1)) + in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") + { + // resolve implicit conversions + T arg1 = ifThis; + T arg2 = writeThis; - static if ( needsLoadBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov EDX, 0; - mov EAX, 0; - mov ECX, val; - lock; // lock always needed to make this op atomic - cmpxchg [ECX], EDX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov EAX, [EAX]; - } - } - } - else static if ( T.sizeof == long.sizeof && has64BitCAS ) + static if (__traits(isFloating, T)) { - ////////////////////////////////////////////////////////////////// - // 8 Byte Load on a 32-Bit Processor - ////////////////////////////////////////////////////////////////// - - asm pure nothrow @nogc @trusted - { - push EDI; - push EBX; - mov EBX, 0; - mov ECX, 0; - mov EAX, 0; - mov EDX, 0; - mov EDI, val; - lock; // lock always needed to make this op atomic - cmpxchg8b [EDI]; - pop EBX; - pop EDI; - } + alias IntTy = IntForFloat!T; + return atomicCompareExchangeStrongNoResult!(succ, fail)( + cast(IntTy*)here, *cast(IntTy*)&arg1, *cast(IntTy*)&arg2); } else - { - static assert( false, "Invalid template type specified." ); - } + return atomicCompareExchangeStrongNoResult!(succ, fail)(here, arg1, arg2); } - void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V1)( ref shared T val, V1 newval ) pure nothrow @nogc @safe - if ( __traits( compiles, { val = newval; } ) ) + /// Compare-and-set for shared value type + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure nothrow @nogc @trusted + if (!is(T == class) && (is(T : V1) || is(shared T : V1))) + in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") { - static assert( ms != MemoryOrder.acq, "invalid MemoryOrder for atomicStore()" ); - static assert( __traits(isPOD, T), "argument to atomicStore() must be POD" ); - - static if ( T.sizeof == byte.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 1 Byte Store - ////////////////////////////////////////////////////////////////// - - static if ( needsStoreBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov DL, newval; - lock; - xchg [EAX], DL; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov DL, newval; - mov [EAX], DL; - } - } - } - else static if ( T.sizeof == short.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 2 Byte Store - ////////////////////////////////////////////////////////////////// - - static if ( needsStoreBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov DX, newval; - lock; - xchg [EAX], DX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov DX, newval; - mov [EAX], DX; - } - } - } - else static if ( T.sizeof == int.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 4 Byte Store - ////////////////////////////////////////////////////////////////// - - static if ( needsStoreBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov EDX, newval; - lock; - xchg [EAX], EDX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov EAX, val; - mov EDX, newval; - mov [EAX], EDX; - } - } - } - else static if ( T.sizeof == long.sizeof && has64BitCAS ) - { - ////////////////////////////////////////////////////////////////// - // 8 Byte Store on a 32-Bit Processor - ////////////////////////////////////////////////////////////////// - - asm pure nothrow @nogc @trusted - { - push EDI; - push EBX; - lea EDI, newval; - mov EBX, [EDI]; - mov ECX, 4[EDI]; - mov EDI, val; - mov EAX, [EDI]; - mov EDX, 4[EDI]; - L1: lock; // lock always needed to make this op atomic - cmpxchg8b [EDI]; - jne L1; - pop EBX; - pop EDI; - } - } + static if (is (V1 == shared U1, U1)) + alias Thunk1 = U1; + else + alias Thunk1 = V1; + static if (is (V2 == shared U2, U2)) + alias Thunk2 = U2; else { - static assert( false, "Invalid template type specified." ); + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V2, + "Copying `" ~ V2.stringof ~ "* writeThis` to `" ~ + shared(T).stringof ~ "* here` would violate shared."); + alias Thunk2 = V2; } + return cas(cast(T*)here, *cast(Thunk1*)&ifThis, *cast(Thunk2*)&writeThis); } - - void atomicFence() nothrow @nogc @safe - { - import core.cpuid; - - asm pure nothrow @nogc @trusted - { - naked; - - call sse2; - test AL, AL; - jne Lcpuid; - - // Fast path: We have SSE2, so just use mfence. - mfence; - jmp Lend; - - Lcpuid: - - // Slow path: We use cpuid to serialize. This is - // significantly slower than mfence, but is the - // only serialization facility we have available - // on older non-SSE2 chips. - push EBX; - - mov EAX, 0; - cpuid; - - pop EBX; - - Lend: - - ret; - } - } -} -else version (AsmX86_64) -{ - // Uses specialized asm for fast fetch and add operations - private HeadUnshared!(T) atomicFetchAdd(T)( ref shared T val, size_t mod ) pure nothrow @nogc @trusted - if ( __traits(isIntegral, T) ) - in + /// Compare-and-set for `shared` reference type (`class`) + bool cas(T, V1, V2)(shared(T)* here, shared(V1) ifThis, shared(V2) writeThis) + pure nothrow @nogc @trusted + if (is(T == class)) + in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") { - assert( atomicValueIsProperlyAligned(val)); + return atomicCompareExchangeStrongNoResult!(succ, fail)( + cast(T*)here, cast(V1)ifThis, cast(V2)writeThis); } - body + + /// Compare-and-exchange for non-`shared` types + bool cas(T, V)(T* here, T* ifThis, V writeThis) pure nothrow @nogc @trusted + if (!is(T == shared) && !is(V == shared)) + in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") { - size_t tmp = mod; - asm pure nothrow @nogc @trusted - { - mov RAX, tmp; - mov RDX, val; - } - static if (T.sizeof == 1) asm pure nothrow @nogc @trusted { lock; xadd[RDX], AL; } - else static if (T.sizeof == 2) asm pure nothrow @nogc @trusted { lock; xadd[RDX], AX; } - else static if (T.sizeof == 4) asm pure nothrow @nogc @trusted { lock; xadd[RDX], EAX; } - else static if (T.sizeof == 8) asm pure nothrow @nogc @trusted { lock; xadd[RDX], RAX; } + // resolve implicit conversions + T arg1 = writeThis; - asm pure nothrow @nogc @trusted + static if (__traits(isFloating, T)) { - mov tmp, RAX; + alias IntTy = IntForFloat!T; + return atomicCompareExchangeStrong!(succ, fail)( + cast(IntTy*)here, cast(IntTy*)ifThis, *cast(IntTy*)&writeThis); } - - return cast(T)tmp; - } - - private HeadUnshared!(T) atomicFetchSub(T)( ref shared T val, size_t mod ) pure nothrow @nogc @safe - if ( __traits(isIntegral, T) ) - { - return atomicFetchAdd(val, -mod); + else + return atomicCompareExchangeStrong!(succ, fail)(here, ifThis, writeThis); } - HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc - if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) - in - { - assert( atomicValueIsProperlyAligned(val)); - } - body + /// Compare and exchange for mixed-`shared`ness types + bool cas(T, V1, V2)(shared(T)* here, V1* ifThis, V2 writeThis) pure nothrow @nogc @trusted + if (!is(T == class) && (is(T : V1) || is(shared T : V1))) + in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") { - // binary operators - // - // + - * / % ^^ & - // | ^ << >> >>> ~ in - // == != < <= > >= - static if ( op == "+" || op == "-" || op == "*" || op == "/" || - op == "%" || op == "^^" || op == "&" || op == "|" || - op == "^" || op == "<<" || op == ">>" || op == ">>>" || - op == "~" || // skip "in" - op == "==" || op == "!=" || op == "<" || op == "<=" || - op == ">" || op == ">=" ) - { - HeadUnshared!(T) get = atomicLoad!(MemoryOrder.raw)( val ); - mixin( "return get " ~ op ~ " mod;" ); - } + static if (is (V1 == shared U1, U1)) + alias Thunk1 = U1; else - // assignment operators - // - // += -= *= /= %= ^^= &= - // |= ^= <<= >>= >>>= ~= - static if ( op == "+=" && __traits(isIntegral, T) && __traits(isIntegral, V1)) - { - return cast(T)(atomicFetchAdd!(T)(val, mod) + mod); - } - else static if ( op == "-=" && __traits(isIntegral, T) && __traits(isIntegral, V1)) { - return cast(T)(atomicFetchSub!(T)(val, mod) - mod); - } - else static if ( op == "+=" || op == "-=" || op == "*=" || op == "/=" || - op == "%=" || op == "^^=" || op == "&=" || op == "|=" || - op == "^=" || op == "<<=" || op == ">>=" || op == ">>>=" ) // skip "~=" - { - HeadUnshared!(T) get, set; - - do - { - get = set = atomicLoad!(MemoryOrder.raw)( val ); - mixin( "set " ~ op ~ " mod;" ); - } while ( !casByRef( val, get, set ) ); - return set; + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V1, + "Copying `" ~ shared(T).stringof ~ "* here` to `" ~ + V1.stringof ~ "* ifThis` would violate shared."); + alias Thunk1 = V1; } + static if (is (V2 == shared U2, U2)) + alias Thunk2 = U2; else { - static assert( false, "Operation not supported." ); + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V2, + "Copying `" ~ V2.stringof ~ "* writeThis` to `" ~ + shared(T).stringof ~ "* here` would violate shared."); + alias Thunk2 = V2; } + static assert (is(T : Thunk1), + "Mismatching types for `here` and `ifThis`: `" ~ + shared(T).stringof ~ "` and `" ~ V1.stringof ~ "`."); + return cas(cast(T*)here, cast(Thunk1*)ifThis, *cast(Thunk2*)&writeThis); } - - bool casByRef(T,V1,V2)( ref T value, V1 ifThis, V2 writeThis ) pure nothrow @nogc @trusted - { - return cas(&value, ifThis, writeThis); - } - - bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe - if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) + /// Compare-and-exchange for `class` + bool cas(T, V)(shared(T)* here, shared(T)* ifThis, shared(V) writeThis) + pure nothrow @nogc @trusted + if (is(T == class)) + in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") { - return casImpl(here, ifThis, writeThis); - } - - bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe - if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ) - { - return casImpl(here, ifThis, writeThis); + return atomicCompareExchangeStrong!(succ, fail)( + cast(T*)here, cast(T*)ifThis, cast(V)writeThis); } +} - bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe - if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) - { - return casImpl(here, ifThis, writeThis); - } +/** +* Stores 'writeThis' to the memory referenced by 'here' if the value +* referenced by 'here' is equal to 'ifThis'. +* The 'weak' version of cas may spuriously fail. It is recommended to +* use `casWeak` only when `cas` would be used in a loop. +* This operation is both +* lock-free and atomic. +* +* Params: +* here = The address of the destination variable. +* writeThis = The value to store. +* ifThis = The comparison value. +* +* Returns: +* true if the store occurred, false if not. +*/ +bool casWeak(MemoryOrder succ = MemoryOrder.seq,MemoryOrder fail = MemoryOrder.seq,T,V1,V2)(T* here, V1 ifThis, V2 writeThis) pure nothrow @nogc @trusted + if (!is(T == shared) && is(T : V1)) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + // resolve implicit conversions + T arg1 = ifThis; + T arg2 = writeThis; - private bool casImpl(T,V1,V2)( shared(T)* here, V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe - in - { - assert( atomicPtrIsProperlyAligned( here ) ); - } - body + static if (__traits(isFloating, T)) { - static if ( T.sizeof == byte.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 1 Byte CAS - ////////////////////////////////////////////////////////////////// - - asm pure nothrow @nogc @trusted - { - mov DL, writeThis; - mov AL, ifThis; - mov RCX, here; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], DL; - setz AL; - } - } - else static if ( T.sizeof == short.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 2 Byte CAS - ////////////////////////////////////////////////////////////////// - - asm pure nothrow @nogc @trusted - { - mov DX, writeThis; - mov AX, ifThis; - mov RCX, here; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], DX; - setz AL; - } - } - else static if ( T.sizeof == int.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 4 Byte CAS - ////////////////////////////////////////////////////////////////// - - asm pure nothrow @nogc @trusted - { - mov EDX, writeThis; - mov EAX, ifThis; - mov RCX, here; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], EDX; - setz AL; - } - } - else static if ( T.sizeof == long.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 8 Byte CAS on a 64-Bit Processor - ////////////////////////////////////////////////////////////////// - - asm pure nothrow @nogc @trusted - { - mov RDX, writeThis; - mov RAX, ifThis; - mov RCX, here; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], RDX; - setz AL; - } - } - else static if ( T.sizeof == long.sizeof*2 && has128BitCAS) - { - ////////////////////////////////////////////////////////////////// - // 16 Byte CAS on a 64-Bit Processor - ////////////////////////////////////////////////////////////////// - version (Win64){ - //Windows 64 calling convention uses different registers. - //DMD appears to reverse the register order. - asm pure nothrow @nogc @trusted - { - push RDI; - push RBX; - mov R9, writeThis; - mov R10, ifThis; - mov R11, here; - - mov RDI, R9; - mov RBX, [RDI]; - mov RCX, 8[RDI]; - - mov RDI, R10; - mov RAX, [RDI]; - mov RDX, 8[RDI]; - - mov RDI, R11; - lock; - cmpxchg16b [RDI]; - setz AL; - pop RBX; - pop RDI; - } - - }else{ - - asm pure nothrow @nogc @trusted - { - push RDI; - push RBX; - lea RDI, writeThis; - mov RBX, [RDI]; - mov RCX, 8[RDI]; - lea RDI, ifThis; - mov RAX, [RDI]; - mov RDX, 8[RDI]; - mov RDI, here; - lock; // lock always needed to make this op atomic - cmpxchg16b [RDI]; - setz AL; - pop RBX; - pop RDI; - } - } - } - else - { - static assert( false, "Invalid template type specified." ); - } + alias IntTy = IntForFloat!T; + return atomicCompareExchangeWeakNoResult!(succ, fail)(cast(IntTy*)here, *cast(IntTy*)&arg1, *cast(IntTy*)&arg2); } + else + return atomicCompareExchangeWeakNoResult!(succ, fail)(here, arg1, arg2); +} - - enum MemoryOrder +/// Ditto +bool casWeak(MemoryOrder succ = MemoryOrder.seq,MemoryOrder fail = MemoryOrder.seq,T,V1,V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure nothrow @nogc @trusted + if (!is(T == class) && (is(T : V1) || is(shared T : V1))) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + static if (is (V1 == shared U1, U1)) + alias Thunk1 = U1; + else + alias Thunk1 = V1; + static if (is (V2 == shared U2, U2)) + alias Thunk2 = U2; + else { - raw, - acq, - rel, - seq, + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V2, "Copying `" ~ V2.stringof ~ "* writeThis` to `" ~ shared(T).stringof ~ "* here` would violate shared."); + alias Thunk2 = V2; } + return casWeak!(succ, fail)(cast(T*)here, *cast(Thunk1*)&ifThis, *cast(Thunk2*)&writeThis); +} - deprecated("Please use MemoryOrder instead.") - alias MemoryOrder msync; - - - private - { - // NOTE: x86 loads implicitly have acquire semantics so a memory - // barrier is only necessary on releases. - template needsLoadBarrier( MemoryOrder ms ) - { - enum bool needsLoadBarrier = ms == MemoryOrder.seq; - } - - - // NOTE: x86 stores implicitly have release semantics so a memory - // barrier is only necessary on acquires. - template needsStoreBarrier( MemoryOrder ms ) - { - enum bool needsStoreBarrier = ms == MemoryOrder.seq; - } - } +/// Ditto +bool casWeak(MemoryOrder succ = MemoryOrder.seq,MemoryOrder fail = MemoryOrder.seq,T,V1,V2)(shared(T)* here, shared(V1) ifThis, shared(V2) writeThis) pure nothrow @nogc @trusted + if (is(T == class)) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + return atomicCompareExchangeWeakNoResult!(succ, fail)(cast(T*)here, cast(V1)ifThis, cast(V2)writeThis); +} +/** +* Stores 'writeThis' to the memory referenced by 'here' if the value +* referenced by 'here' is equal to the value referenced by 'ifThis'. +* The prior value referenced by 'here' is written to `ifThis` and +* returned to the user. +* The 'weak' version of cas may spuriously fail. It is recommended to +* use `casWeak` only when `cas` would be used in a loop. +* This operation is both lock-free and atomic. +* +* Params: +* here = The address of the destination variable. +* writeThis = The value to store. +* ifThis = The address of the value to compare, and receives the prior value of `here` as output. +* +* Returns: +* true if the store occurred, false if not. +*/ +bool casWeak(MemoryOrder succ = MemoryOrder.seq,MemoryOrder fail = MemoryOrder.seq,T,V)(T* here, T* ifThis, V writeThis) pure nothrow @nogc @trusted + if (!is(T == shared S, S) && !is(V == shared U, U)) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + // resolve implicit conversions + T arg1 = writeThis; - HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @safe - if (!__traits(isFloating, T)) + static if (__traits(isFloating, T)) { - static assert( ms != MemoryOrder.rel, "invalid MemoryOrder for atomicLoad()" ); - static assert( __traits(isPOD, T), "argument to atomicLoad() must be POD" ); - - static if ( T.sizeof == byte.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 1 Byte Load - ////////////////////////////////////////////////////////////////// - - static if ( needsLoadBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov DL, 0; - mov AL, 0; - mov RCX, val; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], DL; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov AL, [RAX]; - } - } - } - else static if ( T.sizeof == short.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 2 Byte Load - ////////////////////////////////////////////////////////////////// - - static if ( needsLoadBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov DX, 0; - mov AX, 0; - mov RCX, val; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], DX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov AX, [RAX]; - } - } - } - else static if ( T.sizeof == int.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 4 Byte Load - ////////////////////////////////////////////////////////////////// - - static if ( needsLoadBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov EDX, 0; - mov EAX, 0; - mov RCX, val; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], EDX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov EAX, [RAX]; - } - } - } - else static if ( T.sizeof == long.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 8 Byte Load - ////////////////////////////////////////////////////////////////// - - static if ( needsLoadBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov RDX, 0; - mov RAX, 0; - mov RCX, val; - lock; // lock always needed to make this op atomic - cmpxchg [RCX], RDX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov RAX, [RAX]; - } - } - } - else static if ( T.sizeof == long.sizeof*2 && has128BitCAS ) - { - ////////////////////////////////////////////////////////////////// - // 16 Byte Load on a 64-Bit Processor - ////////////////////////////////////////////////////////////////// - version (Win64){ - size_t[2] retVal; - asm pure nothrow @nogc @trusted - { - push RDI; - push RBX; - mov RDI, val; - mov RBX, 0; - mov RCX, 0; - mov RAX, 0; - mov RDX, 0; - lock; // lock always needed to make this op atomic - cmpxchg16b [RDI]; - lea RDI, retVal; - mov [RDI], RAX; - mov 8[RDI], RDX; - pop RBX; - pop RDI; - } - - static if (is(T:U[], U)) - { - pragma(inline, true) - static typeof(return) toTrusted(size_t[2] retVal) @trusted - { - return *(cast(typeof(return)*) retVal.ptr); - } - - return toTrusted(retVal); - } - else - { - return cast(typeof(return)) retVal; - } - }else{ - asm pure nothrow @nogc @trusted - { - push RDI; - push RBX; - mov RBX, 0; - mov RCX, 0; - mov RAX, 0; - mov RDX, 0; - mov RDI, val; - lock; // lock always needed to make this op atomic - cmpxchg16b [RDI]; - pop RBX; - pop RDI; - } - } - } - else - { - static assert( false, "Invalid template type specified." ); - } + alias IntTy = IntForFloat!T; + return atomicCompareExchangeWeak!(succ, fail)(cast(IntTy*)here, cast(IntTy*)ifThis, *cast(IntTy*)&writeThis); } + else + return atomicCompareExchangeWeak!(succ, fail)(here, ifThis, writeThis); +} - - void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V1)( ref shared T val, V1 newval ) pure nothrow @nogc @safe - if ( __traits( compiles, { val = newval; } ) ) - { - static assert( ms != MemoryOrder.acq, "invalid MemoryOrder for atomicStore()" ); - static assert( __traits(isPOD, T), "argument to atomicStore() must be POD" ); - - static if ( T.sizeof == byte.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 1 Byte Store - ////////////////////////////////////////////////////////////////// - - static if ( needsStoreBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov DL, newval; - lock; - xchg [RAX], DL; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov DL, newval; - mov [RAX], DL; - } - } - } - else static if ( T.sizeof == short.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 2 Byte Store - ////////////////////////////////////////////////////////////////// - - static if ( needsStoreBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov DX, newval; - lock; - xchg [RAX], DX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov DX, newval; - mov [RAX], DX; - } - } - } - else static if ( T.sizeof == int.sizeof ) - { - ////////////////////////////////////////////////////////////////// - // 4 Byte Store - ////////////////////////////////////////////////////////////////// - - static if ( needsStoreBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov EDX, newval; - lock; - xchg [RAX], EDX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov EDX, newval; - mov [RAX], EDX; - } - } - } - else static if ( T.sizeof == long.sizeof && has64BitCAS ) - { - ////////////////////////////////////////////////////////////////// - // 8 Byte Store on a 64-Bit Processor - ////////////////////////////////////////////////////////////////// - - static if ( needsStoreBarrier!(ms) ) - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov RDX, newval; - lock; - xchg [RAX], RDX; - } - } - else - { - asm pure nothrow @nogc @trusted - { - mov RAX, val; - mov RDX, newval; - mov [RAX], RDX; - } - } - } - else static if ( T.sizeof == long.sizeof*2 && has128BitCAS ) - { - ////////////////////////////////////////////////////////////////// - // 16 Byte Store on a 64-Bit Processor - ////////////////////////////////////////////////////////////////// - version (Win64){ - asm pure nothrow @nogc @trusted - { - push RDI; - push RBX; - mov R9, val; - mov R10, newval; - - mov RDI, R10; - mov RBX, [RDI]; - mov RCX, 8[RDI]; - - mov RDI, R9; - mov RAX, [RDI]; - mov RDX, 8[RDI]; - - L1: lock; // lock always needed to make this op atomic - cmpxchg16b [RDI]; - jne L1; - pop RBX; - pop RDI; - } - }else{ - asm pure nothrow @nogc @trusted - { - push RDI; - push RBX; - lea RDI, newval; - mov RBX, [RDI]; - mov RCX, 8[RDI]; - mov RDI, val; - mov RAX, [RDI]; - mov RDX, 8[RDI]; - L1: lock; // lock always needed to make this op atomic - cmpxchg16b [RDI]; - jne L1; - pop RBX; - pop RDI; - } - } - } - else - { - static assert( false, "Invalid template type specified." ); - } +/// Ditto +bool casWeak(MemoryOrder succ = MemoryOrder.seq,MemoryOrder fail = MemoryOrder.seq,T,V1,V2)(shared(T)* here, V1* ifThis, V2 writeThis) pure nothrow @nogc @trusted + if (!is(T == class) && (is(T : V1) || is(shared T : V1))) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + static if (is (V1 == shared U1, U1)) + alias Thunk1 = U1; + else + { + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V1, "Copying `" ~ shared(T).stringof ~ "* here` to `" ~ V1.stringof ~ "* ifThis` would violate shared."); + alias Thunk1 = V1; } - - - void atomicFence() nothrow @nogc @safe + static if (is (V2 == shared U2, U2)) + alias Thunk2 = U2; + else { - // SSE2 is always present in 64-bit x86 chips. - asm nothrow @nogc @trusted - { - naked; - - mfence; - ret; - } + import core.internal.traits : hasUnsharedIndirections; + static assert(!hasUnsharedIndirections!V2, "Copying `" ~ V2.stringof ~ "* writeThis` to `" ~ shared(T).stringof ~ "* here` would violate shared."); + alias Thunk2 = V2; } + static assert (is(T : Thunk1), "Mismatching types for `here` and `ifThis`: `" ~ shared(T).stringof ~ "` and `" ~ V1.stringof ~ "`."); + return casWeak!(succ, fail)(cast(T*)here, cast(Thunk1*)ifThis, *cast(Thunk2*)&writeThis); } -else version (GNU) -{ - import gcc.builtins; - HeadUnshared!(T) atomicOp(string op, T, V1)( ref shared T val, V1 mod ) pure nothrow @nogc @trusted - if ( __traits( compiles, mixin( "*cast(T*)&val" ~ op ~ "mod" ) ) ) - { - // binary operators - // - // + - * / % ^^ & - // | ^ << >> >>> ~ in - // == != < <= > >= - static if ( op == "+" || op == "-" || op == "*" || op == "/" || - op == "%" || op == "^^" || op == "&" || op == "|" || - op == "^" || op == "<<" || op == ">>" || op == ">>>" || - op == "~" || // skip "in" - op == "==" || op == "!=" || op == "<" || op == "<=" || - op == ">" || op == ">=" ) - { - HeadUnshared!(T) get = atomicLoad!(MemoryOrder.raw)( val ); - mixin( "return get " ~ op ~ " mod;" ); - } - else - // assignment operators - // - // += -= *= /= %= ^^= &= - // |= ^= <<= >>= >>>= ~= - static if ( op == "+=" || op == "-=" || op == "*=" || op == "/=" || - op == "%=" || op == "^^=" || op == "&=" || op == "|=" || - op == "^=" || op == "<<=" || op == ">>=" || op == ">>>=" ) // skip "~=" - { - HeadUnshared!(T) get, set; +/// Ditto +bool casWeak(MemoryOrder succ = MemoryOrder.seq,MemoryOrder fail = MemoryOrder.seq,T,V)(shared(T)* here, shared(T)* ifThis, shared(V) writeThis) pure nothrow @nogc @trusted + if (is(T == class)) +in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") +{ + return atomicCompareExchangeWeak!(succ, fail)(cast(T*)here, cast(T*)ifThis, cast(V)writeThis); +} - do - { - get = set = atomicLoad!(MemoryOrder.raw)( val ); - mixin( "set " ~ op ~ " mod;" ); - } while ( !cas( &val, get, set ) ); - return set; - } - else - { - static assert( false, "Operation not supported." ); - } - } +/** + * Inserts a full load/store memory fence (on platforms that need it). This ensures + * that all loads and stores before a call to this function are executed before any + * loads and stores after the call. + */ +void atomicFence(MemoryOrder order = MemoryOrder.seq)() pure nothrow @nogc @safe +{ + core.internal.atomic.atomicFence!order(); +} +/** + * Gives a hint to the processor that the calling thread is in a 'spin-wait' loop, + * allowing to more efficiently allocate resources. + */ +void pause() pure nothrow @nogc @safe +{ + core.internal.atomic.pause(); +} - bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, V2 writeThis ) pure nothrow @nogc @safe - if ( !is(T == class) && !is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) +/** + * Performs the binary operation 'op' on val using 'mod' as the modifier. + * + * Params: + * val = The target variable. + * mod = The modifier to apply. + * + * Returns: + * The result of the operation. + */ +TailShared!T atomicOp(string op, T, V1)(ref shared T val, V1 mod) pure nothrow @nogc @safe + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) +in (atomicValueIsProperlyAligned(val)) +{ + // binary operators + // + // + - * / % ^^ & + // | ^ << >> >>> ~ in + // == != < <= > >= + static if (op == "+" || op == "-" || op == "*" || op == "/" || + op == "%" || op == "^^" || op == "&" || op == "|" || + op == "^" || op == "<<" || op == ">>" || op == ">>>" || + op == "~" || // skip "in" + op == "==" || op == "!=" || op == "<" || op == "<=" || + op == ">" || op == ">=") { - return casImpl(here, ifThis, writeThis); + T get = atomicLoad!(MemoryOrder.raw, T)(val); + mixin("return get " ~ op ~ " mod;"); } - - bool cas(T,V1,V2)( shared(T)* here, const shared(V1) ifThis, shared(V2) writeThis ) pure nothrow @nogc @safe - if ( is(T == class) && __traits( compiles, { *here = writeThis; } ) ) + else + // assignment operators + // + // += -= *= /= %= ^^= &= + // |= ^= <<= >>= >>>= ~= + static if (op == "+=" && __traits(isIntegral, T) && __traits(isIntegral, V1) && T.sizeof <= size_t.sizeof && V1.sizeof <= size_t.sizeof) { - return casImpl(here, ifThis, writeThis); + return cast(T)(atomicFetchAdd(val, mod) + mod); } - - bool cas(T,V1,V2)( shared(T)* here, const shared(V1)* ifThis, shared(V2)* writeThis ) pure nothrow @nogc @safe - if ( is(T U : U*) && __traits( compiles, { *here = writeThis; } ) ) + else static if (op == "-=" && __traits(isIntegral, T) && __traits(isIntegral, V1) && T.sizeof <= size_t.sizeof && V1.sizeof <= size_t.sizeof) { - return casImpl(here, ifThis, writeThis); + return cast(T)(atomicFetchSub(val, mod) - mod); } - - private bool casImpl(T,V1,V2)( shared(T)* here, V1 ifThis, V2 writeThis ) pure nothrow @nogc @trusted + else static if (op == "+=" || op == "-=" || op == "*=" || op == "/=" || + op == "%=" || op == "^^=" || op == "&=" || op == "|=" || + op == "^=" || op == "<<=" || op == ">>=" || op == ">>>=") // skip "~=" { - bool res = void; - - static if (GNU_Have_Atomics || GNU_Have_LibAtomic) - { - static if (T.sizeof == byte.sizeof) - { - res = __atomic_compare_exchange_1(here, cast(void*) &ifThis, *cast(ubyte*) &writeThis, - false, MemoryOrder.seq, MemoryOrder.seq); - } - else static if (T.sizeof == short.sizeof) - { - res = __atomic_compare_exchange_2(here, cast(void*) &ifThis, *cast(ushort*) &writeThis, - false, MemoryOrder.seq, MemoryOrder.seq); - } - else static if (T.sizeof == int.sizeof) - { - res = __atomic_compare_exchange_4(here, cast(void*) &ifThis, *cast(uint*) &writeThis, - false, MemoryOrder.seq, MemoryOrder.seq); - } - else static if (T.sizeof == long.sizeof && GNU_Have_64Bit_Atomics) - { - res = __atomic_compare_exchange_8(here, cast(void*) &ifThis, *cast(ulong*) &writeThis, - false, MemoryOrder.seq, MemoryOrder.seq); - } - else static if (GNU_Have_LibAtomic) - { - res = __atomic_compare_exchange(T.sizeof, here, cast(void*) &ifThis, cast(void*) &writeThis, - MemoryOrder.seq, MemoryOrder.seq); - } - else - static assert(0, "Invalid template type specified."); - } - else + T set, get = atomicLoad!(MemoryOrder.raw, T)(val); + do { - static if (T.sizeof == byte.sizeof) - alias U = byte; - else static if (T.sizeof == short.sizeof) - alias U = short; - else static if (T.sizeof == int.sizeof) - alias U = int; - else static if (T.sizeof == long.sizeof) - alias U = long; - else - static assert(0, "Invalid template type specified."); - - getAtomicMutex.lock(); - scope(exit) getAtomicMutex.unlock(); - - if (*cast(U*)here == *cast(U*)&ifThis) - { - *here = writeThis; - res = true; - } - else - res = false; - } - - return res; + set = get; + mixin("set " ~ op ~ " mod;"); + } while (!casWeakByRef(val, get, set)); + return set; } - - - // Memory model types for the __atomic* builtins. - enum MemoryOrder + else { - raw = 0, - acq = 2, - rel = 3, - seq = 5, + static assert(false, "Operation not supported."); } +} - deprecated("Please use MemoryOrder instead.") - alias MemoryOrder msync; +version (D_InlineAsm_X86) +{ + enum has64BitXCHG = false; + enum has64BitCAS = true; + enum has128BitCAS = false; +} +else version (D_InlineAsm_X86_64) +{ + enum has64BitXCHG = true; + enum has64BitCAS = true; + enum has128BitCAS = true; +} +else version (GNU) +{ + import gcc.config; + enum has64BitCAS = GNU_Have_64Bit_Atomics; + enum has64BitXCHG = GNU_Have_64Bit_Atomics; + enum has128BitCAS = GNU_Have_LibAtomic; +} +else +{ + enum has64BitXCHG = false; + enum has64BitCAS = false; + enum has128BitCAS = false; +} - HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @trusted - if (!__traits(isFloating, T)) +private +{ + bool atomicValueIsProperlyAligned(T)(ref T val) pure nothrow @nogc @trusted { - static assert(ms != MemoryOrder.rel, "Invalid MemoryOrder for atomicLoad"); - static assert(__traits(isPOD, T), "argument to atomicLoad() must be POD"); + return atomicPtrIsProperlyAligned(&val); + } - static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + bool atomicPtrIsProperlyAligned(T)(T* ptr) pure nothrow @nogc @safe + { + // NOTE: Strictly speaking, the x86 supports atomic operations on + // unaligned values. However, this is far slower than the + // common case, so such behavior should be prohibited. + static if (T.sizeof > size_t.sizeof) { - static if (T.sizeof == ubyte.sizeof) - { - ubyte value = __atomic_load_1(&val, ms); - return *cast(HeadUnshared!T*) &value; - } - else static if (T.sizeof == ushort.sizeof) - { - ushort value = __atomic_load_2(&val, ms); - return *cast(HeadUnshared!T*) &value; - } - else static if (T.sizeof == uint.sizeof) + version (X86) { - uint value = __atomic_load_4(&val, ms); - return *cast(HeadUnshared!T*) &value; + // cmpxchg8b only requires 4-bytes alignment + return cast(size_t)ptr % size_t.sizeof == 0; } - else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) - { - ulong value = __atomic_load_8(&val, ms); - return *cast(HeadUnshared!T*) &value; - } - else static if (GNU_Have_LibAtomic) + else { - T value; - __atomic_load(T.sizeof, &val, cast(void*)&value, ms); - return *cast(HeadUnshared!T*) &value; + // e.g., x86_64 cmpxchg16b requires 16-bytes alignment + return cast(size_t)ptr % T.sizeof == 0; } - else - static assert(0, "Invalid template type specified."); } else { - getAtomicMutex.lock(); - scope(exit) getAtomicMutex.unlock(); - return *cast(HeadUnshared!T*)&val; + return cast(size_t)ptr % T.sizeof == 0; } } - - void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V1)( ref shared T val, V1 newval ) pure nothrow @nogc @trusted - if ( __traits( compiles, { val = newval; } ) ) + template IntForFloat(F) + if (__traits(isFloating, F)) { - static assert(ms != MemoryOrder.acq, "Invalid MemoryOrder for atomicStore"); - static assert(__traits(isPOD, T), "argument to atomicLoad() must be POD"); - - static if (GNU_Have_Atomics || GNU_Have_LibAtomic) - { - static if (T.sizeof == ubyte.sizeof) - { - __atomic_store_1(&val, *cast(ubyte*) &newval, ms); - } - else static if (T.sizeof == ushort.sizeof) - { - __atomic_store_2(&val, *cast(ushort*) &newval, ms); - } - else static if (T.sizeof == uint.sizeof) - { - __atomic_store_4(&val, *cast(uint*) &newval, ms); - } - else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) - { - __atomic_store_8(&val, *cast(ulong*) &newval, ms); - } - else static if (GNU_Have_LibAtomic) - { - __atomic_store(T.sizeof, &val, cast(void*)&newval, ms); - } - else - static assert(0, "Invalid template type specified."); - } + static if (F.sizeof == 4) + alias IntForFloat = uint; + else static if (F.sizeof == 8) + alias IntForFloat = ulong; else - { - getAtomicMutex.lock(); - val = newval; - getAtomicMutex.unlock(); - } + static assert (false, "Invalid floating point type: " ~ F.stringof ~ ", only support `float` and `double`."); } - - void atomicFence() nothrow @nogc + template IntForStruct(S) + if (is(S == struct)) { - static if (GNU_Have_Atomics || GNU_Have_LibAtomic) - __atomic_thread_fence(MemoryOrder.seq); + static if (S.sizeof == 1) + alias IntForFloat = ubyte; + else static if (F.sizeof == 2) + alias IntForFloat = ushort; + else static if (F.sizeof == 4) + alias IntForFloat = uint; + else static if (F.sizeof == 8) + alias IntForFloat = ulong; + else static if (F.sizeof == 16) + alias IntForFloat = ulong[2]; // TODO: what's the best type here? slice/delegates pass in registers... else - { - getAtomicMutex.lock(); - getAtomicMutex.unlock(); - } + static assert (ValidateStruct!S); } - static if (!GNU_Have_Atomics && !GNU_Have_LibAtomic) + template ValidateStruct(S) + if (is(S == struct)) { - // Use system mutex for atomics, faking the purity of the functions so - // that they can be used in pure/nothrow/@safe code. - extern (C) private pure @trusted @nogc nothrow - { - static if (GNU_Thread_Model == ThreadModel.Posix) - { - import core.sys.posix.pthread; - alias atomicMutexHandle = pthread_mutex_t; + import core.internal.traits : hasElaborateAssign; - pragma(mangle, "pthread_mutex_init") int fakePureMutexInit(pthread_mutex_t*, pthread_mutexattr_t*); - pragma(mangle, "pthread_mutex_lock") int fakePureMutexLock(pthread_mutex_t*); - pragma(mangle, "pthread_mutex_unlock") int fakePureMutexUnlock(pthread_mutex_t*); - } - else static if (GNU_Thread_Model == ThreadModel.Win32) - { - import core.sys.windows.winbase; - alias atomicMutexHandle = CRITICAL_SECTION; + // `(x & (x-1)) == 0` checks that x is a power of 2. + static assert (S.sizeof <= size_t.sizeof * 2 + && (S.sizeof & (S.sizeof - 1)) == 0, + S.stringof ~ " has invalid size for atomic operations."); + static assert (!hasElaborateAssign!S, S.stringof ~ " may not have an elaborate assignment when used with atomic operations."); - pragma(mangle, "InitializeCriticalSection") int fakePureMutexInit(CRITICAL_SECTION*); - pragma(mangle, "EnterCriticalSection") void fakePureMutexLock(CRITICAL_SECTION*); - pragma(mangle, "LeaveCriticalSection") int fakePureMutexUnlock(CRITICAL_SECTION*); - } - else - { - alias atomicMutexHandle = int; - } - } + enum ValidateStruct = true; + } - // Implements lock/unlock operations. - private struct AtomicMutex - { - int lock() pure @trusted @nogc nothrow - { - static if (GNU_Thread_Model == ThreadModel.Posix) + // TODO: it'd be nice if we had @trusted scopes; we could remove this... + bool casWeakByRef(T,V1,V2)(ref T value, ref V1 ifThis, V2 writeThis) pure nothrow @nogc @trusted + { + return casWeak(&value, &ifThis, writeThis); + } + + /* Construct a type with a shared tail, and if possible with an unshared + head. */ + template TailShared(U) if (!is(U == shared)) + { + alias TailShared = .TailShared!(shared U); + } + template TailShared(S) if (is(S == shared)) + { + // Get the unshared variant of S. + static if (is(S U == shared U)) {} + else static assert(false, "Should never be triggered. The `static " ~ + "if` declares `U` as the unshared version of the shared type " ~ + "`S`. `S` is explicitly declared as shared, so getting `U` " ~ + "should always work."); + + static if (is(S : U)) + alias TailShared = U; + else static if (is(S == struct)) + { + enum implName = () { + /* Start with "_impl". If S has a field with that name, append + underscores until the clash is resolved. */ + string name = "_impl"; + string[] fieldNames; + static foreach (alias field; S.tupleof) { - if (!_inited) - { - fakePureMutexInit(&_handle, null); - _inited = true; - } - return fakePureMutexLock(&_handle); + fieldNames ~= __traits(identifier, field); } - else + static bool canFind(string[] haystack, string needle) { - static if (GNU_Thread_Model == ThreadModel.Win32) + foreach (candidate; haystack) { - if (!_inited) - { - fakePureMutexInit(&_handle); - _inited = true; - } - fakePureMutexLock(&_handle); + if (candidate == needle) return true; } - return 0; + return false; } - } - - int unlock() pure @trusted @nogc nothrow + while (canFind(fieldNames, name)) name ~= "_"; + return name; + } (); + struct TailShared { - static if (GNU_Thread_Model == ThreadModel.Posix) - return fakePureMutexUnlock(&_handle); - else + static foreach (i, alias field; S.tupleof) { - static if (GNU_Thread_Model == ThreadModel.Win32) - fakePureMutexUnlock(&_handle); - return 0; + /* On @trusted: This is casting the field from shared(Foo) + to TailShared!Foo. The cast is safe because the field has + been loaded and is not shared anymore. */ + mixin(" + @trusted @property + ref " ~ __traits(identifier, field) ~ "() + { + alias R = TailShared!(typeof(field)); + return * cast(R*) &" ~ implName ~ ".tupleof[i]; + } + "); } + mixin(" + S " ~ implName ~ "; + alias " ~ implName ~ " this; + "); } - - private: - atomicMutexHandle _handle; - bool _inited; } + else + alias TailShared = S; + } + @safe unittest + { + // No tail (no indirections) -> fully unshared. - // Internal static mutex reference. - private AtomicMutex* _getAtomicMutex() @trusted @nogc nothrow - { - __gshared static AtomicMutex mutex; - return &mutex; - } + static assert(is(TailShared!int == int)); + static assert(is(TailShared!(shared int) == int)); + + static struct NoIndir { int i; } + static assert(is(TailShared!NoIndir == NoIndir)); + static assert(is(TailShared!(shared NoIndir) == NoIndir)); + + // Tail can be independently shared or is already -> tail-shared. + + static assert(is(TailShared!(int*) == shared(int)*)); + static assert(is(TailShared!(shared int*) == shared(int)*)); + static assert(is(TailShared!(shared(int)*) == shared(int)*)); + + static assert(is(TailShared!(int[]) == shared(int)[])); + static assert(is(TailShared!(shared int[]) == shared(int)[])); + static assert(is(TailShared!(shared(int)[]) == shared(int)[])); + + static struct S1 { shared int* p; } + static assert(is(TailShared!S1 == S1)); + static assert(is(TailShared!(shared S1) == S1)); + + static struct S2 { shared(int)* p; } + static assert(is(TailShared!S2 == S2)); + static assert(is(TailShared!(shared S2) == S2)); + + // Tail follows shared-ness of head -> fully shared. - // Pure alias for _getAtomicMutex. - pragma(mangle, _getAtomicMutex.mangleof) - private AtomicMutex* getAtomicMutex() pure @trusted @nogc nothrow @property; + static class C { int i; } + static assert(is(TailShared!C == shared C)); + static assert(is(TailShared!(shared C) == shared C)); + + /* However, structs get a wrapper that has getters which cast to + TailShared. */ + + static struct S3 { int* p; int _impl; int _impl_; int _impl__; } + static assert(!is(TailShared!S3 : S3)); + static assert(is(TailShared!S3 : shared S3)); + static assert(is(TailShared!(shared S3) == TailShared!S3)); + + static struct S4 { shared(int)** p; } + static assert(!is(TailShared!S4 : S4)); + static assert(is(TailShared!S4 : shared S4)); + static assert(is(TailShared!(shared S4) == TailShared!S4)); } } -// This is an ABI adapter that works on all architectures. It type puns -// floats and doubles to ints and longs, atomically loads them, then puns -// them back. This is necessary so that they get returned in floating -// point instead of integer registers. -HeadUnshared!(T) atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)( ref const shared T val ) pure nothrow @nogc @trusted -if (__traits(isFloating, T)) + +//////////////////////////////////////////////////////////////////////////////// +// Unit Tests +//////////////////////////////////////////////////////////////////////////////// + + +version (CoreUnittest) { - static if (T.sizeof == int.sizeof) + version (D_LP64) { - static assert(is(T : float)); - auto ptr = cast(const shared int*) &val; - auto asInt = atomicLoad!(ms)(*ptr); - return *(cast(typeof(return)*) &asInt); + enum hasDWCAS = has128BitCAS; } - else static if (T.sizeof == long.sizeof) + else { - static assert(is(T : double)); - auto ptr = cast(const shared long*) &val; - auto asLong = atomicLoad!(ms)(*ptr); - return *(cast(typeof(return)*) &asLong); + enum hasDWCAS = has64BitCAS; } - else + + void testXCHG(T)(T val) pure nothrow @nogc @trusted + in { - static assert(0, "Cannot atomically load 80-bit reals."); + assert(val !is T.init); } -} + do + { + T base = cast(T)null; + shared(T) atom = cast(shared(T))null; -//////////////////////////////////////////////////////////////////////////////// -// Unit Tests -//////////////////////////////////////////////////////////////////////////////// + assert(base !is val, T.stringof); + assert(atom is base, T.stringof); + assert(atomicExchange(&atom, val) is base, T.stringof); + assert(atom is val, T.stringof); + } -version (unittest) -{ - void testCAS(T)( T val ) pure nothrow @nogc @trusted + void testCAS(T)(T val) pure nothrow @nogc @trusted in { assert(val !is T.init); } - body + do { T base = cast(T)null; shared(T) atom = cast(shared(T))null; - assert( base !is val, T.stringof ); - assert( atom is base, T.stringof ); + assert(base !is val, T.stringof); + assert(atom is base, T.stringof); + + assert(cas(&atom, base, val), T.stringof); + assert(atom is val, T.stringof); + assert(!cas(&atom, base, base), T.stringof); + assert(atom is val, T.stringof); - assert( cas( &atom, base, val ), T.stringof ); - assert( atom is val, T.stringof ); - assert( !cas( &atom, base, base ), T.stringof ); - assert( atom is val, T.stringof ); + atom = cast(shared(T))null; + + shared(T) arg = base; + assert(cas(&atom, &arg, val), T.stringof); + assert(arg is base, T.stringof); + assert(atom is val, T.stringof); + + arg = base; + assert(!cas(&atom, &arg, base), T.stringof); + assert(arg is val, T.stringof); + assert(atom is val, T.stringof); } - void testLoadStore(MemoryOrder ms = MemoryOrder.seq, T)( T val = T.init + 1 ) pure nothrow @nogc @trusted + void testLoadStore(MemoryOrder ms = MemoryOrder.seq, T)(T val = T.init + 1) pure nothrow @nogc @trusted { T base = cast(T) 0; shared(T) atom = cast(T) 0; - assert( base !is val ); - assert( atom is base ); - atomicStore!(ms)( atom, val ); - base = atomicLoad!(ms)( atom ); + assert(base !is val); + assert(atom is base); + atomicStore!(ms)(atom, val); + base = atomicLoad!(ms)(atom); - assert( base is val, T.stringof ); - assert( atom is val ); + assert(base is val, T.stringof); + assert(atom is val); } - void testType(T)( T val = T.init + 1 ) pure nothrow @nogc @safe + void testType(T)(T val = T.init + 1) pure nothrow @nogc @safe { - testCAS!(T)( val ); - testLoadStore!(MemoryOrder.seq, T)( val ); - testLoadStore!(MemoryOrder.raw, T)( val ); + static if (T.sizeof < 8 || has64BitXCHG) + testXCHG!(T)(val); + testCAS!(T)(val); + testLoadStore!(MemoryOrder.seq, T)(val); + testLoadStore!(MemoryOrder.raw, T)(val); } - @safe pure nothrow unittest + @betterC @safe pure nothrow unittest { testType!(bool)(); @@ -1700,42 +924,81 @@ version (unittest) testType!(int)(); testType!(uint)(); + } + + @safe pure nothrow unittest + { testType!(shared int*)(); + static interface Inter {} + static class KlassImpl : Inter {} + testXCHG!(shared Inter)(new shared(KlassImpl)); + testCAS!(shared Inter)(new shared(KlassImpl)); + static class Klass {} - testCAS!(shared Klass)( new shared(Klass) ); + testXCHG!(shared Klass)(new shared(Klass)); + testCAS!(shared Klass)(new shared(Klass)); - testType!(float)(1.0f); + testXCHG!(shared int)(42); - static if ( has64BitCAS ) + testType!(float)(0.1f); + + static if (has64BitCAS) { - testType!(double)(1.0); + testType!(double)(0.1); testType!(long)(); testType!(ulong)(); } + static if (has128BitCAS) + { + () @trusted + { + align(16) struct Big { long a, b; } + + shared(Big) atom; + shared(Big) base; + shared(Big) arg; + shared(Big) val = Big(1, 2); + + assert(cas(&atom, arg, val), Big.stringof); + assert(atom is val, Big.stringof); + assert(!cas(&atom, arg, val), Big.stringof); + assert(atom is val, Big.stringof); + + atom = Big(); + assert(cas(&atom, &arg, val), Big.stringof); + assert(arg is base, Big.stringof); + assert(atom is val, Big.stringof); + + arg = Big(); + assert(!cas(&atom, &arg, base), Big.stringof); + assert(arg is val, Big.stringof); + assert(atom is val, Big.stringof); + }(); + } shared(size_t) i; - atomicOp!"+="( i, cast(size_t) 1 ); - assert( i == 1 ); + atomicOp!"+="(i, cast(size_t) 1); + assert(i == 1); - atomicOp!"-="( i, cast(size_t) 1 ); - assert( i == 0 ); + atomicOp!"-="(i, cast(size_t) 1); + assert(i == 0); - shared float f = 0; - atomicOp!"+="( f, 1 ); - assert( f == 1 ); + shared float f = 0.1f; + atomicOp!"+="(f, 0.1f); + assert(f > 0.1999f && f < 0.2001f); - static if ( has64BitCAS ) + static if (has64BitCAS) { - shared double d = 0; - atomicOp!"+="( d, 1 ); - assert( d == 1 ); + shared double d = 0.1; + atomicOp!"+="(d, 0.1); + assert(d > 0.1999 && d < 0.2001); } } - pure nothrow unittest + @betterC pure nothrow unittest { static if (has128BitCAS) { @@ -1756,15 +1019,6 @@ version (unittest) assert(b.value1 == 3 && b.value2 ==4); } - version (D_LP64) - { - enum hasDWCAS = has128BitCAS; - } - else - { - enum hasDWCAS = has64BitCAS; - } - static if (hasDWCAS) { static struct List { size_t gen; List* next; } @@ -1773,9 +1027,48 @@ version (unittest) assert(head.gen == 1); assert(cast(size_t)head.next == 1); } + + // https://issues.dlang.org/show_bug.cgi?id=20629 + static struct Struct + { + uint a, b; + } + shared Struct s1 = Struct(1, 2); + atomicStore(s1, Struct(3, 4)); + assert(cast(uint) s1.a == 3); + assert(cast(uint) s1.b == 4); + } + + // https://issues.dlang.org/show_bug.cgi?id=20844 + static if (hasDWCAS) + { + debug: // tests CAS in-contract + + pure nothrow unittest + { + import core.exception : AssertError; + + align(16) shared ubyte[2 * size_t.sizeof + 1] data; + auto misalignedPointer = cast(size_t[2]*) &data[1]; + size_t[2] x; + + try + cas(misalignedPointer, x, x); + catch (AssertError) + return; + + assert(0, "should have failed"); + } } - pure nothrow unittest + @betterC pure nothrow @nogc @safe unittest + { + int a; + if (casWeak!(MemoryOrder.acq_rel, MemoryOrder.raw)(&a, 0, 4)) + assert(a == 4); + } + + @betterC pure nothrow unittest { static struct S { int val; } auto s = shared(S)(1); @@ -1794,11 +1087,6 @@ version (unittest) shared(S*) writeThis2 = null; assert(cas(&ptr, ifThis2, writeThis2)); assert(ptr is null); - - // head unshared target doesn't want atomic CAS - shared(S)* ptr2; - static assert(!__traits(compiles, cas(&ptr2, ifThis, writeThis))); - static assert(!__traits(compiles, cas(&ptr2, ifThis2, writeThis2))); } unittest @@ -1838,7 +1126,7 @@ version (unittest) } // === atomicFetchAdd and atomicFetchSub operations ==== - pure nothrow @nogc @safe unittest + @betterC pure nothrow @nogc @safe unittest { shared ubyte u8 = 1; shared ushort u16 = 2; @@ -1853,7 +1141,7 @@ version (unittest) assert(atomicOp!"+="(i8, 8) == 13); assert(atomicOp!"+="(i16, 8) == 14); assert(atomicOp!"+="(i32, 8) == 15); - version (AsmX86_64) + version (D_LP64) { shared ulong u64 = 4; shared long i64 = 8; @@ -1862,7 +1150,30 @@ version (unittest) } } - pure nothrow @nogc @safe unittest + @betterC pure nothrow @nogc unittest + { + byte[10] byteArray = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; + ulong[10] ulongArray = [2, 4, 6, 8, 10, 12, 14, 16, 19, 20]; + + { + auto array = byteArray; + byte* ptr = &array[0]; + byte* prevPtr = atomicFetchAdd(ptr, 3); + assert(prevPtr == &array[0]); + assert(*prevPtr == 1); + assert(*ptr == 7); + } + { + auto array = ulongArray; + ulong* ptr = &array[0]; + ulong* prevPtr = atomicFetchAdd(ptr, 3); + assert(prevPtr == &array[0]); + assert(*prevPtr == 2); + assert(*ptr == 8); + } + } + + @betterC pure nothrow @nogc @safe unittest { shared ubyte u8 = 1; shared ushort u16 = 2; @@ -1877,7 +1188,7 @@ version (unittest) assert(atomicOp!"-="(i8, 1) == 4); assert(atomicOp!"-="(i16, 1) == 5); assert(atomicOp!"-="(i32, 1) == 6); - version (AsmX86_64) + version (D_LP64) { shared ulong u64 = 4; shared long i64 = 8; @@ -1886,16 +1197,83 @@ version (unittest) } } - pure nothrow @nogc @safe unittest // issue 16651 + @betterC pure nothrow @nogc unittest + { + byte[10] byteArray = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; + ulong[10] ulongArray = [2, 4, 6, 8, 10, 12, 14, 16, 19, 20]; + + { + auto array = byteArray; + byte* ptr = &array[5]; + byte* prevPtr = atomicFetchSub(ptr, 4); + assert(prevPtr == &array[5]); + assert(*prevPtr == 11); + assert(*ptr == 3); // https://issues.dlang.org/show_bug.cgi?id=21578 + } + { + auto array = ulongArray; + ulong* ptr = &array[5]; + ulong* prevPtr = atomicFetchSub(ptr, 4); + assert(prevPtr == &array[5]); + assert(*prevPtr == 12); + assert(*ptr == 4); // https://issues.dlang.org/show_bug.cgi?id=21578 + } + } + + @betterC pure nothrow @nogc @safe unittest // issue 16651 { shared ulong a = 2; uint b = 1; - atomicOp!"-="( a, b ); + atomicOp!"-="(a, b); assert(a == 1); shared uint c = 2; ubyte d = 1; - atomicOp!"-="( c, d ); + atomicOp!"-="(c, d); assert(c == 1); } + + pure nothrow @safe unittest // issue 16230 + { + shared int i; + static assert(is(typeof(atomicLoad(i)) == int)); + + shared int* p; + static assert(is(typeof(atomicLoad(p)) == shared(int)*)); + + shared int[] a; + static if (__traits(compiles, atomicLoad(a))) + { + static assert(is(typeof(atomicLoad(a)) == shared(int)[])); + } + + static struct S { int* _impl; } + shared S s; + static assert(is(typeof(atomicLoad(s)) : shared S)); + static assert(is(typeof(atomicLoad(s)._impl) == shared(int)*)); + auto u = atomicLoad(s); + assert(u._impl is null); + u._impl = new shared int(42); + assert(atomicLoad(*u._impl) == 42); + + static struct S2 { S s; } + shared S2 s2; + static assert(is(typeof(atomicLoad(s2).s) == TailShared!S)); + + static struct S3 { size_t head; int* tail; } + shared S3 s3; + static if (__traits(compiles, atomicLoad(s3))) + { + static assert(is(typeof(atomicLoad(s3).head) == size_t)); + static assert(is(typeof(atomicLoad(s3).tail) == shared(int)*)); + } + + static class C { int i; } + shared C c; + static assert(is(typeof(atomicLoad(c)) == shared C)); + + static struct NoIndirections { int i; } + shared NoIndirections n; + static assert(is(typeof(atomicLoad(n)) == NoIndirections)); + } } diff --git a/libphobos/libdruntime/core/attribute.d b/libphobos/libdruntime/core/attribute.d index 9d350d8..b0b973f 100644 --- a/libphobos/libdruntime/core/attribute.d +++ b/libphobos/libdruntime/core/attribute.d @@ -15,6 +15,56 @@ */ module core.attribute; +version (GNU) + public import gcc.attributes; + +version (LDC) + public import ldc.attributes; + +version (D_ObjectiveC) +{ + version = UdaOptional; + version = UdaSelector; +} + +version (Posix) + version = UdaGNUAbiTag; + +version (CoreDdoc) +{ + version = UdaGNUAbiTag; + version = UdaOptional; + version = UdaSelector; +} + +/** + * Use this attribute to specify that a global symbol should be emitted with + * weak linkage. This is primarily useful in defining library functions that + * can be overridden by user code, though it can also be used with shared and + * static variables too. + * + * The overriding symbol must have the same type as the weak symbol. In + * addition, if it designates a variable it must also have the same size and + * alignment as the weak symbol. + * + * Quote from the LLVM manual: "Note that weak linkage does not actually allow + * the optimizer to inline the body of this function into callers because it + * doesn’t know if this definition of the function is the definitive definition + * within the program or whether it will be overridden by a stronger + * definition." + * + * This attribute is only meaningful to the GNU and LLVM D compilers. The + * Digital Mars D compiler emits all symbols with weak linkage by default. + */ +version (DigitalMars) +{ + enum weak; +} +else +{ + // GDC and LDC declare this attribute in their own modules. +} + /** * Use this attribute to attach an Objective-C selector to a method. * @@ -51,7 +101,143 @@ module core.attribute; * } * --- */ -version (D_ObjectiveC) struct selector +version (UdaSelector) struct selector { string selector; } + +/** + * Use this attribute to make an Objective-C interface method optional. + * + * An optional method is a method that does **not** have to be implemented in + * the class that implements the interface. To safely call an optional method, + * a runtime check should be performed to make sure the receiver implements the + * method. + * + * This is a special compiler recognized attribute, it has several + * requirements, which all will be enforced by the compiler: + * + * * The attribute can only be attached to methods which have Objective-C + * linkage. That is, a method inside an interface declared as `extern (Objective-C)` + * + * * It can only be used for methods that are declared inside an interface + * * It can only be used once in a method declaration + * * It cannot be attached to a method that is a template + * + * Examples: + * --- + * import core.attribute : optional, selector; + * + * extern (Objective-C): + * + * struct objc_selector; + * alias SEL = objc_selector*; + * + * SEL sel_registerName(in char* str); + * + * extern class NSObject + * { + * bool respondsToSelector(SEL sel) @selector("respondsToSelector:"); + * } + * + * interface Foo + * { + * @optional void foo() @selector("foo"); + * @optional void bar() @selector("bar"); + * } + * + * class Bar : NSObject + * { + * static Bar alloc() @selector("alloc"); + * Bar init() @selector("init"); + * + * void bar() @selector("bar") + * { + * } + * } + * + * extern (D) void main() + * { + * auto bar = Bar.alloc.init; + * + * if (bar.respondsToSelector(sel_registerName("bar"))) + * bar.bar(); + * } + * --- + */ +version (UdaOptional) + enum optional; + +/** + * Use this attribute to declare an ABI tag on a C++ symbol. + * + * ABI tag is an attribute introduced by the GNU C++ compiler. + * It modifies the mangled name of the symbol to incorporate the tag name, + * in order to distinguish from an earlier version with a different ABI. + * + * This is a special compiler recognized attribute, it has a few + * requirements, which all will be enforced by the compiler: + * + * $(UL + * $(LI + * There can only be one such attribute per symbol. + * ), + * $(LI + * The attribute can only be attached to an `extern(C++)` symbol + * (`struct`, `class`, `enum`, function, and their templated counterparts). + * ), + * $(LI + * The attribute cannot be applied to C++ namespaces. + * This is to prevent confusion with the C++ semantic, which allows it to + * be applied to namespaces. + * ), + * $(LI + * The string arguments must only contain valid characters + * for C++ name mangling which currently include alphanumerics + * and the underscore character. + * ), + * ) + * + * This UDA is not transitive, and inner scope do not inherit outer scopes' + * ABI tag. See examples below for how to translate a C++ declaration to D. + * Also note that entries in this UDA will be automatically sorted + * alphabetically, hence `gnuAbiTag("c", "b", "a")` will appear as + * `@gnuAbiTag("a", "b", "c")`. + * + * See_Also: + * $(LINK2 https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.abi-tag, Itanium ABI spec) + * $(LINK2 https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html, GCC attributes documentation). + * + * Examples: + * --- + * // ---- foo.cpp + * struct [[gnu::abi_tag ("tag1", "tag2")]] Tagged1_2 + * { + * struct [[gnu::abi_tag ("tag3")]] Tagged3 + * { + * [[gnu::abi_tag ("tag4")]] + * int Tagged4 () { return 42; } + * } + * } + * Tagged1_2 inst1; + * // ---- foo.d + * @gnuAbiTag("tag1", "tag2") struct Tagged1_2 + * { + * // Notice the repetition + * @gnuAbiTag("tag1", "tag2", "tag3") struct Tagged3 + * { + * @gnuAbiTag("tag1", "tag2", "tag3", "tag4") int Tagged4 (); + * } + * } + * extern __gshared Tagged1_2 inst1; + * --- + */ +version (UdaGNUAbiTag) struct gnuAbiTag +{ + string[] tags; + + this(string[] tags...) + { + this.tags = tags; + } +} diff --git a/libphobos/libdruntime/core/bitop.d b/libphobos/libdruntime/core/bitop.d index 25b5cd5..40f2242 100644 --- a/libphobos/libdruntime/core/bitop.d +++ b/libphobos/libdruntime/core/bitop.d @@ -758,11 +758,13 @@ version (DigitalMars) version (AnyX86) } +// @@@DEPRECATED_2.099@@@ deprecated("volatileLoad has been moved to core.volatile. Use core.volatile.volatileLoad instead.") { public import core.volatile : volatileLoad; } +// @@@DEPRECATED_2.099@@@ deprecated("volatileStore has been moved to core.volatile. Use core.volatile.volatileStore instead.") { public import core.volatile : volatileStore; @@ -951,6 +953,9 @@ pure T rol(T)(const T value, const uint count) if (__traits(isIntegral, T) && __traits(isUnsigned, T)) { assert(count < 8 * T.sizeof); + if (count == 0) + return cast(T) value; + return cast(T) ((value << count) | (value >> (T.sizeof * 8 - count))); } /// ditto @@ -958,6 +963,9 @@ pure T ror(T)(const T value, const uint count) if (__traits(isIntegral, T) && __traits(isUnsigned, T)) { assert(count < 8 * T.sizeof); + if (count == 0) + return cast(T) value; + return cast(T) ((value >> count) | (value << (T.sizeof * 8 - count))); } /// ditto @@ -965,6 +973,9 @@ pure T rol(uint count, T)(const T value) if (__traits(isIntegral, T) && __traits(isUnsigned, T)) { static assert(count < 8 * T.sizeof); + static if (count == 0) + return cast(T) value; + return cast(T) ((value << count) | (value >> (T.sizeof * 8 - count))); } /// ditto @@ -972,6 +983,9 @@ pure T ror(uint count, T)(const T value) if (__traits(isIntegral, T) && __traits(isUnsigned, T)) { static assert(count < 8 * T.sizeof); + static if (count == 0) + return cast(T) value; + return cast(T) ((value >> count) | (value << (T.sizeof * 8 - count))); } @@ -994,4 +1008,9 @@ unittest assert(rol!3(a) == 0b10000111); assert(ror!3(a) == 0b00011110); + + enum c = rol(uint(1), 0); + enum d = ror(uint(1), 0); + assert(c == uint(1)); + assert(d == uint(1)); } diff --git a/libphobos/libdruntime/core/builtins.d b/libphobos/libdruntime/core/builtins.d new file mode 100644 index 0000000..f2ca503 --- /dev/null +++ b/libphobos/libdruntime/core/builtins.d @@ -0,0 +1,19 @@ +/********************************************** + * This module implements common builtins for the D frontend. + * + * Copyright: Copyright © 2019, The D Language Foundation + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Authors: Walter Bright + * Source: $(DRUNTIMESRC core/builtins.d) + */ + +module core.builtins; + +version (GNU) + public import gcc.builtins; + +version (LDC) + public import ldc.intrinsics; + +/// Writes `s` to `stderr` during CTFE (does nothing at runtime). +void __ctfeWrite(scope const(char)[] s) @nogc @safe pure nothrow {} diff --git a/libphobos/libdruntime/core/checkedint.d b/libphobos/libdruntime/core/checkedint.d index 57209ad..49a5c11 100644 --- a/libphobos/libdruntime/core/checkedint.d +++ b/libphobos/libdruntime/core/checkedint.d @@ -19,6 +19,9 @@ * relative to the cost of the operation itself, compiler implementations are free * to recognize them and generate equivalent and faster code. * + * The functions here are templates so they can be used with -betterC, + * as betterC does not link with this library. + * * References: $(LINK2 http://blog.regehr.org/archives/1139, Fast Integer Overflow Checks) * Copyright: Copyright (c) Walter Bright 2014. * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) @@ -28,6 +31,8 @@ module core.checkedint; +import core.internal.attributes : betterC; + nothrow: @safe: @nogc: @@ -55,20 +60,27 @@ int adds()(int x, int y, ref bool overflow) return cast(int)r; } +/// +@betterC unittest { bool overflow; assert(adds(2, 3, overflow) == 5); assert(!overflow); + assert(adds(1, int.max - 1, overflow) == int.max); assert(!overflow); + assert(adds(int.min + 1, -1, overflow) == int.min); assert(!overflow); + assert(adds(int.max, 1, overflow) == int.min); assert(overflow); + overflow = false; assert(adds(int.min, -1, overflow) == int.max); assert(overflow); + assert(adds(0, 0, overflow) == 0); assert(overflow); // sticky } @@ -84,20 +96,27 @@ long adds()(long x, long y, ref bool overflow) return r; } +/// +@betterC unittest { bool overflow; assert(adds(2L, 3L, overflow) == 5); assert(!overflow); + assert(adds(1L, long.max - 1, overflow) == long.max); assert(!overflow); + assert(adds(long.min + 1, -1, overflow) == long.min); assert(!overflow); + assert(adds(long.max, 1, overflow) == long.min); assert(overflow); + overflow = false; assert(adds(long.min, -1, overflow) == long.max); assert(overflow); + assert(adds(0L, 0L, overflow) == 0); assert(overflow); // sticky } @@ -152,25 +171,42 @@ pragma(inline, true) uint addu()(uint x, uint y, ref bool overflow) { immutable uint r = x + y; - if (r < x || r < y) + immutable bool o = r < x; + assert(o == (r < y)); + if (o) overflow = true; return r; } +/// +@betterC unittest { + for (uint i = 0; i < 10; ++i) + { + bool overflow; + immutable uint r = addu (uint.max - i, uint.max - i, overflow); + assert (r == 2 * (uint.max - i)); + assert (overflow); + } + bool overflow; assert(addu(2, 3, overflow) == 5); assert(!overflow); + assert(addu(1, uint.max - 1, overflow) == uint.max); assert(!overflow); + assert(addu(uint.min, -1, overflow) == uint.max); assert(!overflow); + assert(addu(uint.max, 1, overflow) == uint.min); assert(overflow); + overflow = false; assert(addu(uint.min + 1, -1, overflow) == uint.min); assert(overflow); + assert(addu(0, 0, overflow) == 0); assert(overflow); // sticky } @@ -180,25 +216,34 @@ pragma(inline, true) ulong addu()(ulong x, ulong y, ref bool overflow) { immutable ulong r = x + y; - if (r < x || r < y) + immutable bool o = r < x; + assert(o == (r < y)); + if (o) overflow = true; return r; } +/// +@betterC unittest { bool overflow; assert(addu(2L, 3L, overflow) == 5); assert(!overflow); + assert(addu(1, ulong.max - 1, overflow) == ulong.max); assert(!overflow); + assert(addu(ulong.min, -1L, overflow) == ulong.max); assert(!overflow); + assert(addu(ulong.max, 1, overflow) == ulong.min); assert(overflow); + overflow = false; assert(addu(ulong.min + 1, -1L, overflow) == ulong.min); assert(overflow); + assert(addu(0L, 0L, overflow) == 0); assert(overflow); // sticky } @@ -210,7 +255,9 @@ pragma(inline, true) ucent addu()(ucent x, ucent y, ref bool overflow) { immutable ucent r = x + y; - if (r < x || r < y) + immutable bool o = r < x; + assert(o == (r < y)); + if (o) overflow = true; return r; } @@ -257,20 +304,27 @@ int subs()(int x, int y, ref bool overflow) return cast(int)r; } +/// +@betterC unittest { bool overflow; assert(subs(2, -3, overflow) == 5); assert(!overflow); + assert(subs(1, -int.max + 1, overflow) == int.max); assert(!overflow); + assert(subs(int.min + 1, 1, overflow) == int.min); assert(!overflow); + assert(subs(int.max, -1, overflow) == int.min); assert(overflow); + overflow = false; assert(subs(int.min, 1, overflow) == int.max); assert(overflow); + assert(subs(0, 0, overflow) == 0); assert(overflow); // sticky } @@ -286,22 +340,30 @@ long subs()(long x, long y, ref bool overflow) return r; } +/// +@betterC unittest { bool overflow; assert(subs(2L, -3L, overflow) == 5); assert(!overflow); + assert(subs(1L, -long.max + 1, overflow) == long.max); assert(!overflow); + assert(subs(long.min + 1, 1, overflow) == long.min); assert(!overflow); + assert(subs(-1L, long.min, overflow) == long.max); assert(!overflow); + assert(subs(long.max, -1, overflow) == long.min); assert(overflow); + overflow = false; assert(subs(long.min, 1, overflow) == long.max); assert(overflow); + assert(subs(0L, 0L, overflow) == 0); assert(overflow); // sticky } @@ -362,20 +424,27 @@ uint subu()(uint x, uint y, ref bool overflow) return x - y; } +/// +@betterC unittest { bool overflow; assert(subu(3, 2, overflow) == 1); assert(!overflow); + assert(subu(uint.max, 1, overflow) == uint.max - 1); assert(!overflow); + assert(subu(1, 1, overflow) == uint.min); assert(!overflow); + assert(subu(0, 1, overflow) == uint.max); assert(overflow); + overflow = false; assert(subu(uint.max - 1, uint.max, overflow) == uint.max); assert(overflow); + assert(subu(0, 0, overflow) == 0); assert(overflow); // sticky } @@ -390,20 +459,27 @@ ulong subu()(ulong x, ulong y, ref bool overflow) return x - y; } +/// +@betterC unittest { bool overflow; assert(subu(3UL, 2UL, overflow) == 1); assert(!overflow); + assert(subu(ulong.max, 1, overflow) == ulong.max - 1); assert(!overflow); + assert(subu(1UL, 1UL, overflow) == ulong.min); assert(!overflow); + assert(subu(0UL, 1UL, overflow) == ulong.max); assert(overflow); + overflow = false; assert(subu(ulong.max - 1, ulong.max, overflow) == ulong.max); assert(overflow); + assert(subu(0UL, 0UL, overflow) == 0); assert(overflow); // sticky } @@ -457,17 +533,23 @@ int negs()(int x, ref bool overflow) return -x; } +/// +@betterC unittest { bool overflow; assert(negs(0, overflow) == -0); assert(!overflow); + assert(negs(1234, overflow) == -1234); assert(!overflow); + assert(negs(-5678, overflow) == 5678); assert(!overflow); + assert(negs(int.min, overflow) == -int.min); assert(overflow); + assert(negs(0, overflow) == -0); assert(overflow); // sticky } @@ -481,17 +563,23 @@ long negs()(long x, ref bool overflow) return -x; } +/// +@betterC unittest { bool overflow; assert(negs(0L, overflow) == -0); assert(!overflow); + assert(negs(1234L, overflow) == -1234); assert(!overflow); + assert(negs(-5678L, overflow) == 5678); assert(!overflow); + assert(negs(long.min, overflow) == -long.min); assert(overflow); + assert(negs(0L, overflow) == -0); assert(overflow); // sticky } @@ -546,22 +634,30 @@ int muls()(int x, int y, ref bool overflow) return cast(int)r; } +/// +@betterC unittest { bool overflow; assert(muls(2, 3, overflow) == 6); assert(!overflow); + assert(muls(-200, 300, overflow) == -60_000); assert(!overflow); + assert(muls(1, int.max, overflow) == int.max); assert(!overflow); + assert(muls(int.min, 1, overflow) == int.min); assert(!overflow); + assert(muls(int.max, 2, overflow) == (int.max * 2)); assert(overflow); + overflow = false; assert(muls(int.min, -1, overflow) == int.min); assert(overflow); + assert(muls(0, 0, overflow) == 0); assert(overflow); // sticky } @@ -579,25 +675,34 @@ long muls()(long x, long y, ref bool overflow) return r; } +/// +@betterC unittest { bool overflow; assert(muls(2L, 3L, overflow) == 6); assert(!overflow); + assert(muls(-200L, 300L, overflow) == -60_000); assert(!overflow); + assert(muls(1, long.max, overflow) == long.max); assert(!overflow); + assert(muls(long.min, 1L, overflow) == long.min); assert(!overflow); + assert(muls(long.max, 2L, overflow) == (long.max * 2)); assert(overflow); overflow = false; + assert(muls(-1L, long.min, overflow) == long.min); assert(overflow); + overflow = false; assert(muls(long.min, -1L, overflow) == long.min); assert(overflow); + assert(muls(0L, 0L, overflow) == 0); assert(overflow); // sticky } @@ -652,7 +757,6 @@ unittest * Returns: * the product */ - pragma(inline, true) uint mulu()(uint x, uint y, ref bool overflow) { @@ -662,6 +766,7 @@ uint mulu()(uint x, uint y, ref bool overflow) return cast(uint) r; } +@betterC unittest { void test(uint x, uint y, uint r, bool overflow) @nogc nothrow @@ -705,6 +810,7 @@ ulong mulu()(ulong x, ulong y, ref bool overflow) return r; } +@betterC unittest { void test(T, U)(T x, U y, ulong r, bool overflow) @nogc nothrow diff --git a/libphobos/libdruntime/core/demangle.d b/libphobos/libdruntime/core/demangle.d index 4458b70..ad9b44a 100644 --- a/libphobos/libdruntime/core/demangle.d +++ b/libphobos/libdruntime/core/demangle.d @@ -54,13 +54,13 @@ pure @safe: enum AddType { no, yes } - this( const(char)[] buf_, char[] dst_ = null ) + this( return const(char)[] buf_, return char[] dst_ = null ) { this( buf_, AddType.yes, dst_ ); } - this( const(char)[] buf_, AddType addType_, char[] dst_ = null ) + this( return const(char)[] buf_, AddType addType_, return char[] dst_ = null ) { buf = buf_; addType = addType_; @@ -208,15 +208,15 @@ pure @safe: { assert( contains( dst[0 .. len], val ) ); debug(info) printf( "removing (%.*s)\n", cast(int) val.length, val.ptr ); - size_t v = &val[0] - &dst[0]; + assert( len >= val.length && len <= dst.length ); + len -= val.length; for (size_t p = v; p < len; p++) dst[p] = dst[p + val.length]; - len -= val.length; } } - char[] append( const(char)[] val ) + char[] append( const(char)[] val ) return scope { pragma(inline, false); // tame dmd inliner @@ -227,8 +227,7 @@ pure @safe: assert( !contains( dst[0 .. len], val ) ); debug(info) printf( "appending (%.*s)\n", cast(int) val.length, val.ptr ); - if ( &dst[len] == &val[0] && - dst.length - len >= val.length ) + if ( dst.length - len >= val.length && &dst[len] == &val[0] ) { // data is already in place auto t = dst[len .. len + val.length]; @@ -254,13 +253,13 @@ pure @safe: put(", "); } - char[] put(char c) + char[] put(char c) return scope { char[1] val = c; return put(val[]); } - char[] put( const(char)[] val ) + char[] put( scope const(char)[] val ) return scope { pragma(inline, false); // tame dmd inliner @@ -278,9 +277,9 @@ pure @safe: { import core.internal.string; - UnsignedStringBuf buf; + UnsignedStringBuf buf = void; - auto s = unsignedToTempString(val, buf, 16); + auto s = unsignedToTempString!16(val, buf); int slen = cast(int)s.length; if (slen < width) { @@ -301,7 +300,7 @@ pure @safe: } - void silent( lazy void dg ) + void silent( void delegate() pure @safe dg ) { debug(trace) printf( "silent+\n" ); debug(trace) scope(success) printf( "silent-\n" ); @@ -431,7 +430,7 @@ pure @safe: Digit Digit Number */ - const(char)[] sliceNumber() + const(char)[] sliceNumber() return scope { debug(trace) printf( "sliceNumber+\n" ); debug(trace) scope(success) printf( "sliceNumber-\n" ); @@ -449,7 +448,7 @@ pure @safe: } - size_t decodeNumber() + size_t decodeNumber() scope { debug(trace) printf( "decodeNumber+\n" ); debug(trace) scope(success) printf( "decodeNumber-\n" ); @@ -458,7 +457,7 @@ pure @safe: } - size_t decodeNumber( const(char)[] num ) + size_t decodeNumber( scope const(char)[] num ) scope { debug(trace) printf( "decodeNumber+\n" ); debug(trace) scope(success) printf( "decodeNumber-\n" ); @@ -479,7 +478,7 @@ pure @safe: } - void parseReal() + void parseReal() scope { debug(trace) printf( "parseReal+\n" ); debug(trace) scope(success) printf( "parseReal-\n" ); @@ -570,7 +569,7 @@ pure @safe: Namechar Namechar Namechars */ - void parseLName() + void parseLName() scope { debug(trace) printf( "parseLName+\n" ); debug(trace) scope(success) printf( "parseLName-\n" ); @@ -788,7 +787,7 @@ pure @safe: TypeTuple: B Number Arguments */ - char[] parseType( char[] name = null ) + char[] parseType( char[] name = null ) return scope { static immutable string[23] primitives = [ "char", // a @@ -924,7 +923,6 @@ pure @safe: return dst[beg .. len]; case 'F': case 'U': case 'W': case 'V': case 'R': // TypeFunction return parseTypeFunction( name ); - case 'I': // TypeIdent (I LName) case 'C': // TypeClass (C LName) case 'S': // TypeStruct (S LName) case 'E': // TypeEnum (E LName) @@ -1186,13 +1184,17 @@ pure @safe: popFront(); put( "scope " ); continue; + case 'm': // FuncAttrLive + popFront(); + put( "@live " ); + continue; default: error(); } } } - void parseFuncArguments() + void parseFuncArguments() scope { // Arguments for ( size_t n = 0; true; n++ ) @@ -1233,9 +1235,11 @@ pure @safe: } switch ( front ) { - case 'J': // out (J Type) + case 'I': // in (I Type) popFront(); - put( "out " ); + put("in "); + if (front == 'K') + goto case; parseType(); continue; case 'K': // ref (K Type) @@ -1243,6 +1247,11 @@ pure @safe: put( "ref " ); parseType(); continue; + case 'J': // out (J Type) + popFront(); + put( "out " ); + parseType(); + continue; case 'L': // lazy (L Type) popFront(); put( "lazy " ); @@ -1260,7 +1269,7 @@ pure @safe: TypeFunction: CallConvention FuncAttrs Arguments ArgClose Type */ - char[] parseTypeFunction( char[] name = null, IsDelegate isdg = IsDelegate.no ) + char[] parseTypeFunction( char[] name = null, IsDelegate isdg = IsDelegate.no ) return { debug(trace) printf( "parseTypeFunction+\n" ); debug(trace) scope(success) printf( "parseTypeFunction-\n" ); @@ -1349,7 +1358,7 @@ pure @safe: E F */ - void parseValue( char[] name = null, char type = '\0' ) + void parseValue(scope char[] name = null, char type = '\0' ) scope { debug(trace) printf( "parseValue+\n" ); debug(trace) scope(success) printf( "parseValue-\n" ); @@ -1464,13 +1473,19 @@ pure @safe: } put( ')' ); return; + case 'f': + // f MangledName + // A function literal symbol + popFront(); + parseMangledName(false, 1); + return; default: error(); } } - void parseIntegerValue( char[] name = null, char type = '\0' ) + void parseIntegerValue( scope char[] name = null, char type = '\0' ) scope { debug(trace) printf( "parseIntegerValue+\n" ); debug(trace) scope(success) printf( "parseIntegerValue-\n" ); @@ -1580,7 +1595,7 @@ pure @safe: S Number_opt QualifiedName X ExternallyMangledName */ - void parseTemplateArgs() + void parseTemplateArgs() scope { debug(trace) printf( "parseTemplateArgs+\n" ); debug(trace) scope(success) printf( "parseTemplateArgs-\n" ); @@ -1608,7 +1623,7 @@ pure @safe: char t = front; // peek at type for parseValue if ( t == 'Q' ) t = peekBackref(); - char[] name; silent( name = parseType() ); + char[] name; silent( delegate void() { name = parseType(); } ); parseValue( name, t ); continue; case 'S': @@ -1714,7 +1729,7 @@ pure @safe: TemplateInstanceName: Number __T LName TemplateArgs Z */ - void parseTemplateInstanceName(bool hasNumber) + void parseTemplateInstanceName(bool hasNumber) scope { debug(trace) printf( "parseTemplateInstanceName+\n" ); debug(trace) scope(success) printf( "parseTemplateInstanceName-\n" ); @@ -1739,7 +1754,7 @@ pure @safe: } - bool mayBeTemplateInstanceName() + bool mayBeTemplateInstanceName() scope { debug(trace) printf( "mayBeTemplateInstanceName+\n" ); debug(trace) scope(success) printf( "mayBeTemplateInstanceName-\n" ); @@ -1759,7 +1774,7 @@ pure @safe: LName TemplateInstanceName */ - void parseSymbolName() + void parseSymbolName() scope { debug(trace) printf( "parseSymbolName+\n" ); debug(trace) scope(success) printf( "parseSymbolName-\n" ); @@ -1801,7 +1816,7 @@ pure @safe: // parse optional function arguments as part of a symbol name, i.e without return type // if keepAttr, the calling convention and function attributes are not discarded, but returned - char[] parseFunctionTypeNoReturn( bool keepAttr = false ) + char[] parseFunctionTypeNoReturn( bool keepAttr = false ) return scope { // try to demangle a function, in case we are pointing to some function local auto prevpos = pos; @@ -1852,7 +1867,7 @@ pure @safe: SymbolName SymbolName QualifiedName */ - char[] parseQualifiedName() + char[] parseQualifiedName() return scope { debug(trace) printf( "parseQualifiedName+\n" ); debug(trace) scope(success) printf( "parseQualifiedName-\n" ); @@ -1876,7 +1891,7 @@ pure @safe: _D QualifiedName Type _D QualifiedName M Type */ - void parseMangledName( bool displayType, size_t n = 0 ) + void parseMangledName( bool displayType, size_t n = 0 ) scope { debug(trace) printf( "parseMangledName+\n" ); debug(trace) scope(success) printf( "parseMangledName-\n" ); @@ -1951,7 +1966,16 @@ pure @safe: parseMangledName( AddType.yes == addType ); } - char[] doDemangle(alias FUNC)() + char[] copyInput() return scope + { + if (dst.length < buf.length) + dst.length = buf.length; + char[] r = dst[0 .. buf.length]; + r[] = buf[]; + return r; + } + + char[] doDemangle(alias FUNC)() return scope { while ( true ) { @@ -1979,10 +2003,7 @@ pure @safe: auto msg = e.toString(); printf( "error: %.*s\n", cast(int) msg.length, msg.ptr ); } - if ( dst.length < buf.length ) - dst.length = buf.length; - dst[0 .. buf.length] = buf[]; - return dst[0 .. buf.length]; + return copyInput(); } catch ( Exception e ) { @@ -2015,10 +2036,13 @@ pure @safe: * The demangled name or the original string if the name is not a mangled D * name. */ -char[] demangle( const(char)[] buf, char[] dst = null ) nothrow pure @safe +char[] demangle(return scope const(char)[] buf, return scope char[] dst = null ) nothrow pure @safe { - //return Demangle(buf, dst)(); auto d = Demangle!()(buf, dst); + // fast path (avoiding throwing & catching exception) for obvious + // non-D mangled names + if (buf.length < 2 || !(buf[0] == 'D' || buf[0..2] == "_D")) + return d.copyInput(); return d.demangleName(); } @@ -2051,7 +2075,7 @@ char[] demangleType( const(char)[] buf, char[] dst = null ) nothrow pure @safe * Returns: * The mangled name with deduplicated identifiers */ -char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe +char[] reencodeMangled(return scope const(char)[] mangled) nothrow pure @safe { static struct PrependHooks { @@ -2067,7 +2091,7 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe Replacement [] replacements; pure @safe: - size_t positionInResult(size_t pos) + size_t positionInResult(size_t pos) scope { foreach_reverse (r; replacements) if (pos >= r.pos) @@ -2077,7 +2101,7 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe alias Remangle = Demangle!(PrependHooks); - void flushPosition(ref Remangle d) + void flushPosition(ref Remangle d) scope { if (lastpos < d.pos) { @@ -2096,7 +2120,7 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe } } - bool parseLName(ref Remangle d) + bool parseLName(scope ref Remangle d) scope { flushPosition(d); @@ -2127,7 +2151,8 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe npos = positionInResult(*pid); } encodeBackref(reslen - npos); - replacements ~= Replacement(d.pos, result.length); + const pos = d.pos; // work around issues.dlang.org/show_bug.cgi?id=20675 + replacements ~= Replacement(pos, result.length); } else { @@ -2141,7 +2166,8 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe size_t npos = positionInResult(*pid); result.length = reslen; encodeBackref(reslen - npos); - replacements ~= Replacement(d.pos, result.length); + const pos = d.pos; // work around issues.dlang.org/show_bug.cgi?id=20675 + replacements ~= Replacement(pos, result.length); } else { @@ -2153,7 +2179,7 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe return true; } - char[] parseType( ref Remangle d, char[] name = null ) + char[] parseType( ref Remangle d, char[] name = null ) return scope { if (d.front != 'Q') return null; @@ -2174,7 +2200,7 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe return result[reslen .. $]; // anything but null } - void encodeBackref(size_t relpos) + void encodeBackref(size_t relpos) scope { result ~= 'Q'; enum base = 26; @@ -2221,7 +2247,7 @@ char[] reencodeMangled(const(char)[] mangled) nothrow pure @safe * The mangled name for a symbols of type T and the given fully * qualified name. */ -char[] mangle(T)(const(char)[] fqn, char[] dst = null) @safe pure nothrow +char[] mangle(T)(return scope const(char)[] fqn, return scope char[] dst = null) @safe pure nothrow { import core.internal.string : numDigits, unsignedToTempString; @@ -2232,19 +2258,19 @@ char[] mangle(T)(const(char)[] fqn, char[] dst = null) @safe pure nothrow @property bool empty() const { return !s.length; } - @property const(char)[] front() const + @property const(char)[] front() const return { immutable i = indexOfDot(); return i == -1 ? s[0 .. $] : s[0 .. i]; } - void popFront() + void popFront() scope { immutable i = indexOfDot(); s = i == -1 ? s[$ .. $] : s[i+1 .. $]; } - private ptrdiff_t indexOfDot() const + private ptrdiff_t indexOfDot() const scope { foreach (i, c; s) if (c == '.') return i; return -1; @@ -2311,7 +2337,7 @@ char[] mangle(T)(const(char)[] fqn, char[] dst = null) @safe pure nothrow * The mangled name for a function with function pointer type T and * the given fully qualified name. */ -char[] mangleFunc(T:FT*, FT)(const(char)[] fqn, char[] dst = null) @safe pure nothrow if (is(FT == function)) +char[] mangleFunc(T:FT*, FT)(return scope const(char)[] fqn, return scope char[] dst = null) @safe pure nothrow if (is(FT == function)) { static if (isExternD!FT) { @@ -2335,7 +2361,6 @@ char[] mangleFunc(T:FT*, FT)(const(char)[] fqn, char[] dst = null) @safe pure no private enum hasTypeBackRef = (int function(void**,void**)).mangleof[$-4 .. $] == "QdZi"; -/// @safe pure nothrow unittest { assert(mangleFunc!(int function(int))("a.b") == "_D1a1bFiZi"); @@ -2412,13 +2437,15 @@ else version (Darwin) else enum string cPrefix = ""; -version (unittest) +@safe pure nothrow unittest { immutable string[2][] table = [ ["printf", "printf"], ["_foo", "_foo"], ["_D88", "_D88"], + ["_D3fooQeFIAyaZv", "void foo.foo(in immutable(char)[])" ], + ["_D3barQeFIKAyaZv", "void bar.bar(in ref immutable(char)[])" ], ["_D4test3fooAa", "char[] test.foo"], ["_D8demangle8demangleFAaZAa", "char[] demangle.demangle(char[])"], ["_D6object6Object8opEqualsFC6ObjectZi", "int object.Object.opEquals(Object)"], @@ -2470,7 +2497,7 @@ version (unittest) ["_D3foo7__arrayZ", "foo.__array"], ["_D8link657428__T3fooVE8link65746Methodi0Z3fooFZi", "int link6574.foo!(0).foo()"], ["_D8link657429__T3fooHVE8link65746Methodi0Z3fooFZi", "int link6574.foo!(0).foo()"], - ["_D4test22__T4funcVAyaa3_610a62Z4funcFNaNbNiNfZAya", `pure nothrow @nogc @safe immutable(char)[] test.func!("a\x0ab").func()`], + ["_D4test22__T4funcVAyaa3_610a62Z4funcFNaNbNiNmNfZAya", `pure nothrow @nogc @live @safe immutable(char)[] test.func!("a\x0ab").func()`], ["_D3foo3barFzkZzi", "cent foo.bar(ucent)"], ["_D5bug145Class3fooMFNlZPv", "scope void* bug14.Class.foo()"], ["_D5bug145Class3barMFNjZPv", "return void* bug14.Class.bar()"], @@ -2482,6 +2509,8 @@ version (unittest) "pure @safe int std.format.getNth!(\"integer width\", std.traits.isIntegral, int, uint, uint).getNth(uint, uint, uint)"], ["_D3std11parallelism42__T16RoundRobinBufferTDFKAaZvTDxFNaNdNeZbZ16RoundRobinBuffer5primeMFZv", "void std.parallelism.RoundRobinBuffer!(void delegate(ref char[]), bool delegate() pure @property @trusted const).RoundRobinBuffer.prime()"], + ["_D6mangle__T8fun21753VSQv6S21753S1f_DQBj10__lambda71MFNaNbNiNfZvZQCbQp", + "void function() pure nothrow @nogc @safe mangle.fun21753!(mangle.S21753(mangle.__lambda71())).fun21753"], // Lname '0' ["_D3std9algorithm9iteration__T9MapResultSQBmQBlQBe005stripTAAyaZQBi7opSliceMFNaNbNiNfmmZSQDiQDhQDa__TQCtSQDyQDxQDq00QCmTQCjZQDq", "pure nothrow @nogc @safe std.algorithm.iteration.MapResult!(std.algorithm.iteration.__anonymous.strip, " @@ -2541,23 +2570,56 @@ version (unittest) else alias staticIota = Seq!(staticIota!(x - 1), x - 1); } -} -@safe pure nothrow unittest -{ foreach ( i, name; table ) { auto r = demangle( name[0] ); assert( r == name[1], - "demangled \"" ~ name[0] ~ "\" as \"" ~ r ~ "\" but expected \"" ~ name[1] ~ "\""); + "demangled `" ~ name[0] ~ "` as `" ~ r ~ "` but expected `" ~ name[1] ~ "`"); } foreach ( i; staticIota!(table.length) ) { enum r = demangle( table[i][0] ); static assert( r == table[i][1], - "demangled \"" ~ table[i][0] ~ "\" as \"" ~ r ~ "\" but expected \"" ~ table[i][1] ~ "\""); + "demangled `" ~ table[i][0] ~ "` as `" ~ r ~ "` but expected `" ~ table[i][1] ~ "`"); + } + + { + // https://issues.dlang.org/show_bug.cgi?id=18531 + auto symbol = `_D3std3uni__T6toCaseS_DQvQt12toLowerIndexFNaNbNiNewZtVii1043S_DQCjQCi10toLowerTabFNaNbNiNemZwSQDo5ascii7toLowerTAyaZQDzFNaNeQmZ14__foreachbody2MFNaNeKmKwZ14__foreachbody3MFNaNeKwZi`; + auto demangled = `pure @trusted int std.uni.toCase!(std.uni.toLowerIndex(dchar), 1043, std.uni.toLowerTab(ulong), std.ascii.toLower, immutable(char)[]).toCase(immutable(char)[]).__foreachbody2(ref ulong, ref dchar).__foreachbody3(ref dchar)`; + auto dst = new char[200]; + auto ret = demangle( symbol, dst); + assert( ret == demangled ); } } +unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=18300 + string s = demangle.mangleof; + foreach (i; 1..77) + { + char[] buf = new char[i]; + auto ds = demangle(s, buf); + assert(ds == "pure nothrow @safe char[] core.demangle.demangle(scope return const(char)[], scope return char[])" || + ds == "pure nothrow @safe char[] core.demangle.demangle(return scope const(char)[], return scope char[])"); + } +} + +unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=18300 + string s = "_D1"; + string expected = "int "; + foreach (_; 0..10_000) + { + s ~= "a1"; + expected ~= "a."; + } + s ~= "FiZi"; + expected ~= "F"; + assert(s.demangle == expected); +} /* * diff --git a/libphobos/libdruntime/core/exception.d b/libphobos/libdruntime/core/exception.d index f21c43e..fe298d4 100644 --- a/libphobos/libdruntime/core/exception.d +++ b/libphobos/libdruntime/core/exception.d @@ -1,29 +1,61 @@ /** - * The exception module defines all system-level exceptions and provides a - * mechanism to alter system-level error handling. - * - * Copyright: Copyright Sean Kelly 2005 - 2013. - * License: Distributed under the - * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). - * (See accompanying file LICENSE) - * Authors: Sean Kelly and Jonathan M Davis - * Source: $(DRUNTIMESRC core/_exception.d) - */ + The exception module defines all system-level exceptions and provides a + mechanism to alter system-level error handling. -/* NOTE: This file has been patched from the original DMD distribution to - * work with the GDC compiler. + Copyright: Copyright Sean Kelly 2005 - 2013. + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Sean Kelly and $(HTTP jmdavisprog.com, Jonathan M Davis) + Source: $(DRUNTIMESRC core/_exception.d) */ module core.exception; +// Compiler lowers final switch default case to this (which is a runtime error) +void __switch_errorT()(string file = __FILE__, size_t line = __LINE__) @trusted +{ + // Consider making this a compile time check. + version (D_Exceptions) + throw staticError!SwitchError(file, line, null); + else + assert(0, "No appropriate switch clause found"); +} + +version (D_BetterC) +{ + // When compiling with -betterC we use template functions so if they are + // used the bodies are copied into the user's program so there is no need + // for the D runtime during linking. + + // In the future we might want to convert all functions in this module to + // templates even for ordinary builds instead of providing them as an + // extern(C) library. + + void onOutOfMemoryError()(void* pretend_sideffect = null) @nogc nothrow pure @trusted + { + assert(0, "Memory allocation failed"); + } + alias onOutOfMemoryErrorNoGC = onOutOfMemoryError; + + void onInvalidMemoryOperationError()(void* pretend_sideffect = null) @nogc nothrow pure @trusted + { + assert(0, "Invalid memory operation"); + } +} +else: + /** * Thrown on a range error. */ class RangeError : Error { - @safe pure nothrow this( string file = __FILE__, size_t line = __LINE__, Throwable next = null ) + this( string file = __FILE__, size_t line = __LINE__, Throwable next = null ) @nogc nothrow pure @safe { super( "Range violation", file, line, next ); } + + protected this( string msg, string file, size_t line, Throwable next = null ) @nogc nothrow pure @safe + { + super( msg, file, line, next ); + } } unittest @@ -45,6 +77,142 @@ unittest } } +/** + * Thrown when an out of bounds array index is accessed. + */ +class ArrayIndexError : RangeError +{ + /// Index into array + const size_t index; + /// Length of indexed array + const size_t length; + + // Buffer to avoid GC allocations + private immutable char[100] msgBuf = '\0'; + + this(size_t index, size_t length, string file = __FILE__, + size_t line = __LINE__, Throwable next = null) @nogc nothrow pure @safe + { + this.index = index; + this.length = length; + + // Constructing the message is a bit clumsy: + // It's essentially `printf("index [%zu] exceeds array of length [%zu]", index, length)`, + // but even `snprintf` isn't `pure`. + // Also string concatenation isn't `@nogc`, and casting to/from immutable isn't `@safe` + import core.internal.string : unsignedToTempString; + char[msgBuf.length] buf = void; + char[20] tmpBuf = void; + char[] sink = buf[]; + sink.rangeMsgPut("index ["); + sink.rangeMsgPut(unsignedToTempString!10(index, tmpBuf)); + sink.rangeMsgPut("]"); + sink.rangeMsgPut(" exceeds array of length "); + sink.rangeMsgPut(unsignedToTempString!10(length, tmpBuf)); + this.msgBuf = buf; + super(msgBuf[0..$-sink.length], file, line, next); + } +} + +@safe pure unittest +{ + assert(new ArrayIndexError(900, 700).msg == "index [900] exceeds array of length 700"); + // Ensure msg buffer doesn't overflow on large numbers + assert(new ArrayIndexError(size_t.max, size_t.max-1).msg); +} + +unittest +{ + try + { + _d_arraybounds_indexp("test", 400, 9, 3); + assert(0, "no ArrayIndexError thrown"); + } + catch (ArrayIndexError re) + { + assert(re.file == "test"); + assert(re.line == 400); + assert(re.index == 9); + assert(re.length == 3); + } +} + +/** + * Thrown when an out of bounds array slice is created + */ +class ArraySliceError : RangeError +{ + /// Lower/upper bound passed to slice: `array[lower .. upper]` + const size_t lower, upper; + /// Length of sliced array + const size_t length; + + private immutable char[120] msgBuf = '\0'; + + this(size_t lower, size_t upper, size_t length, string file = __FILE__, + size_t line = __LINE__, Throwable next = null) @nogc nothrow pure @safe + { + this.lower = lower; + this.upper = upper; + this.length = length; + + // Constructing the message is a bit clumsy for the same reasons as ArrayIndexError + import core.internal.string : unsignedToTempString; + char[msgBuf.length] buf = void; + char[20] tmpBuf = void; + char[] sink = buf; + sink.rangeMsgPut("slice ["); + sink.rangeMsgPut(unsignedToTempString!10(lower, tmpBuf)); + sink.rangeMsgPut(" .. "); + sink.rangeMsgPut(unsignedToTempString!10(upper, tmpBuf)); + sink.rangeMsgPut("] "); + if (lower > upper) + { + sink.rangeMsgPut("has a larger lower index than upper index"); + } + else + { + sink.rangeMsgPut("extends past source array of length "); + sink.rangeMsgPut(unsignedToTempString!10(length, tmpBuf)); + } + + this.msgBuf = buf; + super(msgBuf[0..$-sink.length], file, line, next); + } +} + +@safe pure unittest +{ + assert(new ArraySliceError(40, 80, 20).msg == "slice [40 .. 80] extends past source array of length 20"); + assert(new ArraySliceError(90, 70, 20).msg == "slice [90 .. 70] has a larger lower index than upper index"); + // Ensure msg buffer doesn't overflow on large numbers + assert(new ArraySliceError(size_t.max, size_t.max, size_t.max-1).msg); +} + +unittest +{ + try + { + _d_arraybounds_slicep("test", 400, 1, 7, 3); + assert(0, "no ArraySliceError thrown"); + } + catch (ArraySliceError re) + { + assert(re.file == "test"); + assert(re.line == 400); + assert(re.lower == 1); + assert(re.upper == 7); + assert(re.length == 3); + } +} + +/// Mini `std.range.primitives: put` for constructor of ArraySliceError / ArrayIndexError +private void rangeMsgPut(ref char[] r, scope const(char)[] e) @nogc nothrow pure @safe +{ + assert(r.length >= e.length); // don't throw ArraySliceError inside ArrayIndexError ctor + r[0 .. e.length] = e[]; + r = r[e.length .. $]; +} /** * Thrown on an assert error. @@ -187,32 +355,6 @@ unittest } /** - * Thrown on hidden function error. - * $(RED Deprecated. - * This feature is not longer part of the language.) - */ -deprecated class HiddenFuncError : Error -{ - @safe pure nothrow this( ClassInfo ci ) - { - super( "Hidden method called for " ~ ci.name ); - } -} - -deprecated unittest -{ - ClassInfo info = new ClassInfo; - info.name = "testInfo"; - - { - auto hfe = new HiddenFuncError(info); - assert(hfe.next is null); - assert(hfe.msg == "Hidden method called for testInfo"); - } -} - - -/** * Thrown on an out of memory error. */ class OutOfMemoryError : Error @@ -312,11 +454,23 @@ unittest /** +* Thrown on a configuration error. +*/ +class ForkError : Error +{ + this( string file = __FILE__, size_t line = __LINE__, Throwable next = null ) @nogc nothrow pure @safe + { + super( "fork() failed", file, line, next ); + } +} + + +/** * Thrown on a switch error. */ class SwitchError : Error { - @safe pure nothrow this( string file = __FILE__, size_t line = __LINE__, Throwable next = null ) + @safe pure nothrow @nogc this( string file = __FILE__, size_t line = __LINE__, Throwable next = null ) { super( "No appropriate switch clause found", file, line, next ); } @@ -349,7 +503,7 @@ class UnicodeException : Exception { size_t idx; - this( string msg, size_t idx, string file = __FILE__, size_t line = __LINE__, Throwable next = null ) @safe pure nothrow + this( string msg, size_t idx, string file = __FILE__, size_t line = __LINE__, Throwable next = null ) @safe pure nothrow @nogc { super( msg, file, line, next ); this.idx = idx; @@ -407,19 +561,6 @@ alias AssertHandler = void function(string file, size_t line, string msg) nothro _assertHandler = handler; } -/** - * Overrides the default assert hander with a user-supplied version. - * $(RED Deprecated. - * Please use $(LREF assertHandler) instead.) - * - * Params: - * h = The new assert handler. Set to null to use the default handler. - */ -deprecated void setAssertHandler( AssertHandler h ) @trusted nothrow @nogc -{ - assertHandler = h; -} - /////////////////////////////////////////////////////////////////////////////// // Overridable Callbacks @@ -438,7 +579,7 @@ deprecated void setAssertHandler( AssertHandler h ) @trusted nothrow @nogc extern (C) void onAssertError( string file = __FILE__, size_t line = __LINE__ ) nothrow { if ( _assertHandler is null ) - throw new AssertError( file, line ); + throw staticError!AssertError(file, line); _assertHandler( file, line, null); } @@ -456,7 +597,7 @@ extern (C) void onAssertError( string file = __FILE__, size_t line = __LINE__ ) extern (C) void onAssertErrorMsg( string file, size_t line, string msg ) nothrow { if ( _assertHandler is null ) - throw new AssertError( msg, file, line ); + throw staticError!AssertError(msg, file, line); _assertHandler( file, line, msg ); } @@ -482,7 +623,7 @@ extern (C) void onUnittestErrorMsg( string file, size_t line, string msg ) nothr /////////////////////////////////////////////////////////////////////////////// /** - * A callback for array bounds errors in D. A $(LREF RangeError) will be thrown. + * A callback for general array bounds errors in D. A $(LREF RangeError) will be thrown. * * Params: * file = The name of the file that signaled this error. @@ -491,11 +632,47 @@ extern (C) void onUnittestErrorMsg( string file, size_t line, string msg ) nothr * Throws: * $(LREF RangeError). */ -extern (C) void onRangeError( string file = __FILE__, size_t line = __LINE__ ) @safe pure nothrow +extern (C) void onRangeError( string file = __FILE__, size_t line = __LINE__ ) @trusted pure nothrow @nogc +{ + throw staticError!RangeError(file, line, null); +} + +/** + * A callback for array slice out of bounds errors in D. + * + * Params: + * lower = the lower bound of the index passed of a slice + * upper = the upper bound of the index passed of a slice or the index if not a slice + * length = length of the array + * file = The name of the file that signaled this error. + * line = The line number on which this error occurred. + * + * Throws: + * $(LREF ArraySliceError). + */ +extern (C) void onArraySliceError( size_t lower = 0, size_t upper = 0, size_t length = 0, + string file = __FILE__, size_t line = __LINE__ ) @trusted pure nothrow @nogc { - throw new RangeError( file, line, null ); + throw staticError!ArraySliceError(lower, upper, length, file, line, null); } +/** + * A callback for array index out of bounds errors in D. + * + * Params: + * index = index in the array + * length = length of the array + * file = The name of the file that signaled this error. + * line = The line number on which this error occurred. + * + * Throws: + * $(LREF ArrayIndexError). + */ +extern (C) void onArrayIndexError( size_t index = 0, size_t length = 0, + string file = __FILE__, size_t line = __LINE__ ) @trusted pure nothrow @nogc +{ + throw staticError!ArrayIndexError(index, length, file, line, null); +} /** * A callback for finalize errors in D. A $(LREF FinalizeError) will be thrown. @@ -516,22 +693,6 @@ extern (C) void onFinalizeError( TypeInfo info, Throwable e, string file = __FIL throw staticError!FinalizeError(info, e, file, line); } - -/** - * A callback for hidden function errors in D. A $(LREF HiddenFuncError) will be - * thrown. - * $(RED Deprecated. - * This feature is not longer part of the language.) - * - * Throws: - * $(LREF HiddenFuncError). - */ -deprecated extern (C) void onHiddenFuncError( Object o ) @safe pure nothrow -{ - throw new HiddenFuncError( typeid(o) ); -} - - /** * A callback for out of memory errors in D. An $(LREF OutOfMemoryError) will be * thrown. @@ -569,21 +730,20 @@ extern (C) void onInvalidMemoryOperationError(void* pretend_sideffect = null) @t /** - * A callback for switch errors in D. A $(LREF SwitchError) will be thrown. + * A callback for errors in the case of a failed fork in D. A $(LREF ForkError) will be thrown. * * Params: * file = The name of the file that signaled this error. * line = The line number on which this error occurred. * * Throws: - * $(LREF SwitchError). + * $(LREF ConfigurationError). */ -extern (C) void onSwitchError( string file = __FILE__, size_t line = __LINE__ ) @safe pure nothrow +extern (C) void onForkError( string file = __FILE__, size_t line = __LINE__ ) @trusted pure nothrow @nogc { - throw new SwitchError( file, line, null ); + throw staticError!ForkError( file, line, null ); } - /** * A callback for unicode errors in D. A $(LREF UnicodeException) will be thrown. * @@ -611,7 +771,6 @@ extern (C) void onAssertErrorMsg(string file, size_t line, string msg); extern (C) void onUnittestErrorMsg(string file, size_t line, string msg); extern (C) void onRangeError(string file, size_t line); extern (C) void onHiddenFuncError(Object o); -extern (C) void onSwitchError(string file, size_t line); +/ /*********************************** @@ -621,8 +780,6 @@ extern (C) void onSwitchError(string file, size_t line); extern (C) { - // Use ModuleInfo to get file name for "m" versions - /* One of these three is called upon an assert() fail. */ void _d_assertp(immutable(char)* file, uint line) @@ -659,34 +816,36 @@ extern (C) _d_unittest_msg("unittest failure", file, line); } - /* Called when an array index is out of bounds - */ + /// Called when an invalid array index/slice or associative array key is accessed void _d_arrayboundsp(immutable(char*) file, uint line) { import core.stdc.string : strlen; onRangeError(file[0 .. strlen(file)], line); } + /// ditto void _d_arraybounds(string file, uint line) { onRangeError(file, line); } - /* Called when a switch statement has no DefaultStatement, yet none of the cases match - */ - void _d_switch_errorm(immutable(ModuleInfo)* m, uint line) + /// Called when an out of range slice of an array is created + void _d_arraybounds_slicep(immutable(char*) file, uint line, size_t lower, size_t upper, size_t length) { - onSwitchError(m.name, line); + import core.stdc.string : strlen; + onArraySliceError(lower, upper, length, file[0 .. strlen(file)], line); } - void _d_switch_error(string file, uint line) + /// Called when an out of range array index is accessed + void _d_arraybounds_indexp(immutable(char*) file, uint line, size_t index, size_t length) { - onSwitchError(file, line); + import core.stdc.string : strlen; + onArrayIndexError(index, length, file[0 .. strlen(file)], line); } } // TLS storage shared for all errors, chaining might create circular reference -private void[128] _store; +private align(2 * size_t.sizeof) void[256] _store; // only Errors for now as those are rarely chained private T staticError(T, Args...)(auto ref Args args) @@ -698,11 +857,11 @@ private T staticError(T, Args...)(auto ref Args args) static assert(__traits(classInstanceSize, T) <= _store.length, T.stringof ~ " is too large for staticError()"); - _store[0 .. __traits(classInstanceSize, T)] = typeid(T).initializer[]; return cast(T) _store.ptr; } auto res = (cast(T function() @trusted pure nothrow @nogc) &get)(); - res.__ctor(args); + import core.lifetime : emplace; + emplace(res, args); return res; } diff --git a/libphobos/libdruntime/core/gc/config.d b/libphobos/libdruntime/core/gc/config.d new file mode 100644 index 0000000..258183f --- /dev/null +++ b/libphobos/libdruntime/core/gc/config.d @@ -0,0 +1,129 @@ +/** +* Contains the garbage collector configuration. +* +* Copyright: Copyright Digital Mars 2016 +* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +*/ + +module core.gc.config; + +import core.stdc.stdio; +import core.internal.parseoptions; + +__gshared Config config; + +struct Config +{ + bool disable; // start disabled + bool fork = false; // optional concurrent behaviour + ubyte profile; // enable profiling with summary when terminating program + string gc = "conservative"; // select gc implementation conservative|precise|manual + + @MemVal size_t initReserve; // initial reserve (bytes) + @MemVal size_t minPoolSize = 1 << 20; // initial and minimum pool size (bytes) + @MemVal size_t maxPoolSize = 64 << 20; // maximum pool size (bytes) + @MemVal size_t incPoolSize = 3 << 20; // pool size increment (bytes) + uint parallel = 99; // number of additional threads for marking (limited by cpuid.threadsPerCPU-1) + float heapSizeFactor = 2.0; // heap size to used memory ratio + string cleanup = "collect"; // select gc cleanup method none|collect|finalize + +@nogc nothrow: + + bool initialize() + { + return initConfigOptions(this, "gcopt"); + } + + void help() @nogc nothrow + { + import core.gc.registry : registeredGCFactories; + + printf("GC options are specified as white space separated assignments: + disable:0|1 - start disabled (%d) + fork:0|1 - set fork behaviour (%d) + profile:0|1|2 - enable profiling with summary when terminating program (%d) + gc:".ptr, disable, fork, profile); + foreach (i, entry; registeredGCFactories) + { + if (i) printf("|"); + printf("%.*s", cast(int) entry.name.length, entry.name.ptr); + } + auto _initReserve = initReserve.bytes2prettyStruct; + auto _minPoolSize = minPoolSize.bytes2prettyStruct; + auto _maxPoolSize = maxPoolSize.bytes2prettyStruct; + auto _incPoolSize = incPoolSize.bytes2prettyStruct; + printf(" - select gc implementation (default = conservative) + + initReserve:N - initial memory to reserve in MB (%lld%c) + minPoolSize:N - initial and minimum pool size in MB (%lld%c) + maxPoolSize:N - maximum pool size in MB (%lld%c) + incPoolSize:N - pool size increment MB (%lld%c) + parallel:N - number of additional threads for marking (%lld) + heapSizeFactor:N - targeted heap size to used memory ratio (%g) + cleanup:none|collect|finalize - how to treat live objects when terminating (collect) + + Memory-related values can use B, K, M or G suffixes. +".ptr, + _initReserve.v, _initReserve.u, + _minPoolSize.v, _minPoolSize.u, + _maxPoolSize.v, _maxPoolSize.u, + _incPoolSize.v, _incPoolSize.u, + cast(long)parallel, heapSizeFactor); + } + + string errorName() @nogc nothrow { return "GC"; } +} + +private struct PrettyBytes +{ + long v; + char u; /// unit +} + +pure @nogc nothrow: + +private PrettyBytes bytes2prettyStruct(size_t val) +{ + char c = prettyBytes(val); + + return PrettyBytes(val, c); +} + +private static char prettyBytes(ref size_t val) +{ + char sym = 'B'; + + if (val == 0) + return sym; + + char[3] units = ['K', 'M', 'G']; + + foreach (u; units) + if (val % (1 << 10) == 0) + { + val /= (1 << 10); + sym = u; + } + else if (sym != 'B') + break; + + return sym; +} +unittest +{ + size_t v = 1024; + assert(prettyBytes(v) == 'K'); + assert(v == 1); + + v = 1025; + assert(prettyBytes(v) == 'B'); + assert(v == 1025); + + v = 1024UL * 1024 * 1024 * 3; + assert(prettyBytes(v) == 'G'); + assert(v == 3); + + v = 1024 * 1024 + 1; + assert(prettyBytes(v) == 'B'); + assert(v == 1024 * 1024 + 1); +} diff --git a/libphobos/libdruntime/core/gc/gcinterface.d b/libphobos/libdruntime/core/gc/gcinterface.d new file mode 100644 index 0000000..e8cdf11 --- /dev/null +++ b/libphobos/libdruntime/core/gc/gcinterface.d @@ -0,0 +1,198 @@ +/** + * Contains the internal GC interface. + * + * Copyright: Copyright Digital Mars 2016. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, Sean Kelly, Jeremy DeHaan + */ + + /* Copyright Digital Mars 2016. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +module core.gc.gcinterface; + +static import core.memory; +alias BlkAttr = core.memory.GC.BlkAttr; +alias BlkInfo = core.memory.GC.BlkInfo; + +alias RootIterator = int delegate(scope int delegate(ref Root) nothrow dg); +alias RangeIterator = int delegate(scope int delegate(ref Range) nothrow dg); + + +struct Root +{ + void* proot; + alias proot this; +} + +struct Range +{ + void* pbot; + void* ptop; + TypeInfo ti; // should be tail const, but doesn't exist for references + alias pbot this; // only consider pbot for relative ordering (opCmp) + bool opEquals(const scope Range rhs) nothrow const { return pbot == rhs.pbot; } +} + +interface GC +{ + /** + * + */ + void enable(); + + /** + * + */ + void disable(); + + /** + * + */ + void collect() nothrow; + + /** + * + */ + void collectNoStack() nothrow; + + /** + * minimize free space usage + */ + void minimize() nothrow; + + /** + * + */ + uint getAttr(void* p) nothrow; + + /** + * + */ + uint setAttr(void* p, uint mask) nothrow; + + /** + * + */ + uint clrAttr(void* p, uint mask) nothrow; + + /** + * + */ + void* malloc(size_t size, uint bits, const TypeInfo ti) nothrow; + + /* + * + */ + BlkInfo qalloc(size_t size, uint bits, const scope TypeInfo ti) nothrow; + + /* + * + */ + void* calloc(size_t size, uint bits, const TypeInfo ti) nothrow; + + /* + * + */ + void* realloc(void* p, size_t size, uint bits, const TypeInfo ti) nothrow; + + /** + * Attempt to in-place enlarge the memory block pointed to by p by at least + * minsize bytes, up to a maximum of maxsize additional bytes. + * This does not attempt to move the memory block (like realloc() does). + * + * Returns: + * 0 if could not extend p, + * total size of entire memory block if successful. + */ + size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow; + + /** + * + */ + size_t reserve(size_t size) nothrow; + + /** + * + */ + void free(void* p) nothrow @nogc; + + /** + * Determine the base address of the block containing p. If p is not a gc + * allocated pointer, return null. + */ + void* addrOf(void* p) nothrow @nogc; + + /** + * Determine the allocated size of pointer p. If p is an interior pointer + * or not a gc allocated pointer, return 0. + */ + size_t sizeOf(void* p) nothrow @nogc; + + /** + * Determine the base address of the block containing p. If p is not a gc + * allocated pointer, return null. + */ + BlkInfo query(void* p) nothrow; + + /** + * Retrieve statistics about garbage collection. + * Useful for debugging and tuning. + */ + core.memory.GC.Stats stats() nothrow; + + /** + * Retrieve profile statistics about garbage collection. + * Useful for debugging and tuning. + */ + core.memory.GC.ProfileStats profileStats() nothrow @safe; + + /** + * add p to list of roots + */ + void addRoot(void* p) nothrow @nogc; + + /** + * remove p from list of roots + */ + void removeRoot(void* p) nothrow @nogc; + + /** + * + */ + @property RootIterator rootIter() @nogc; + + /** + * add range to scan for roots + */ + void addRange(void* p, size_t sz, const TypeInfo ti) nothrow @nogc; + + /** + * remove range + */ + void removeRange(void* p) nothrow @nogc; + + /** + * + */ + @property RangeIterator rangeIter() @nogc; + + /** + * run finalizers + */ + void runFinalizers(const scope void[] segment) nothrow; + + /* + * + */ + bool inFinalizer() nothrow @nogc @safe; + + /** + * Returns the number of bytes allocated for the current thread + * since program start. It is the same as + * GC.stats().allocatedInCurrentThread, but faster. + */ + ulong allocatedInCurrentThread() nothrow; +} diff --git a/libphobos/libdruntime/core/gc/registry.d b/libphobos/libdruntime/core/gc/registry.d new file mode 100644 index 0000000..da2dcff --- /dev/null +++ b/libphobos/libdruntime/core/gc/registry.d @@ -0,0 +1,87 @@ +/** + * Contains a registry for GC factories. + * + * Copyright: Copyright Digital Mars 2016. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak + */ +module core.gc.registry; + +import core.gc.gcinterface : GC; + +/*@nogc nothrow:*/ + +/** + * A factory function that instantiates an implementation of the GC interface. + * In case the instance was allocated on the C heap, it is supposed to + * free itself upon calling it's destructor. + * + * The factory should print an error and abort the program if it + * cannot successfully initialize the GC instance. + */ +alias GCFactory = GC function(); + +/** + * Register a GC factory under the given `name`. This function must be called + * from a C constructor before druntime is initialized. + * + * To use the registered GC, it's name must be specified gcopt runtime option, + * e.g. by passing $(TT, --DRT-gcopt=gc:my_gc_name) as application argument. + * + * Params: + * name = name of the GC implementation; should be unique + * factory = function to instantiate the implementation + * Note: The registry does not perform synchronization, as registration is + * assumed to be executed serially, as is the case for C constructors. + * See_Also: + * $(LINK2 https://dlang.org/spec/garbage.html#gc_config, Configuring the Garbage Collector) + */ +void registerGCFactory(string name, GCFactory factory) nothrow @nogc +{ + import core.stdc.stdlib : realloc; + + auto ptr = cast(Entry*)realloc(entries.ptr, (entries.length + 1) * Entry.sizeof); + entries = ptr[0 .. entries.length + 1]; + entries[$ - 1] = Entry(name, factory); +} + +/** + * Called during runtime initialization to initialize a GC instance of given `name`. + * + * Params: + * name = name of the GC to instantiate + * Returns: + * The created GC instance or `null` if no factory for that name was registered + */ +GC createGCInstance(string name) +{ + import core.stdc.stdlib : free; + + foreach (entry; entries) + { + if (entry.name != name) + continue; + auto instance = entry.factory(); + // only one GC at a time for now, so free the registry to not leak + free(entries.ptr); + entries = null; + return instance; + } + return null; +} + +// list of all registerd GCs +const(Entry[]) registeredGCFactories(scope int dummy=0) nothrow @nogc +{ + return entries; +} + +private: + +struct Entry +{ + string name; + GCFactory factory; +} + +__gshared Entry[] entries; diff --git a/libphobos/libdruntime/core/internal/abort.d b/libphobos/libdruntime/core/internal/abort.d index 8ee1684..6942f7e 100644 --- a/libphobos/libdruntime/core/internal/abort.d +++ b/libphobos/libdruntime/core/internal/abort.d @@ -11,7 +11,7 @@ void abort(scope string msg, scope string filename = __FILE__, size_t line = __L version (Posix) { import core.sys.posix.unistd: write; - static void writeStr(const(char)[][] m...) @nogc nothrow @trusted + static void writeStr(scope const(char)[][] m...) @nogc nothrow @trusted { foreach (s; m) write(2, s.ptr, s.length); @@ -19,12 +19,20 @@ void abort(scope string msg, scope string filename = __FILE__, size_t line = __L } else version (Windows) { - import core.sys.windows.windows: GetStdHandle, STD_ERROR_HANDLE, WriteFile, INVALID_HANDLE_VALUE; + import core.sys.windows.winbase : GetStdHandle, STD_ERROR_HANDLE, WriteFile, INVALID_HANDLE_VALUE; auto h = (() @trusted => GetStdHandle(STD_ERROR_HANDLE))(); if (h == INVALID_HANDLE_VALUE) + { // attempt best we can to print the message - assert(0, msg); - void writeStr(const(char)[][] m...) @nogc nothrow @trusted + + /* Note that msg is scope. + * assert() calls _d_assert_msg() calls onAssertErrorMsg() calls _assertHandler() but + * msg parameter isn't scope and can escape. + * Give up and use our own immutable message instead. + */ + assert(0, "Cannot get stderr handle for message"); + } + void writeStr(scope const(char)[][] m...) @nogc nothrow @trusted { foreach (s; m) { @@ -37,9 +45,9 @@ void abort(scope string msg, scope string filename = __FILE__, size_t line = __L static assert(0, "Unsupported OS"); import core.internal.string; - UnsignedStringBuf strbuff; + UnsignedStringBuf strbuff = void; // write an appropriate message, then abort the program - writeStr("Aborting from ", filename, "(", line.unsignedToTempString(strbuff, 10), ") ", msg); + writeStr("Aborting from ", filename, "(", line.unsignedToTempString(strbuff), ") ", msg); c_abort(); } diff --git a/libphobos/libdruntime/core/internal/array/appending.d b/libphobos/libdruntime/core/internal/array/appending.d new file mode 100644 index 0000000..1e58ddc --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/appending.d @@ -0,0 +1,222 @@ +/** + This module contains support for controlling dynamic arrays' appending + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/_internal/_array/_appending.d) +*/ +module core.internal.array.appending; + +/// See $(REF _d_arrayappendcTX, rt,lifetime,_d_arrayappendcTX) +private extern (C) byte[] _d_arrayappendcTX(const TypeInfo ti, ref return scope byte[] px, size_t n) @trusted pure nothrow; + +private enum isCopyingNothrow(T) = __traits(compiles, (ref T rhs) nothrow { T lhs = rhs; }); + +/// Implementation of `_d_arrayappendcTX` and `_d_arrayappendcTXTrace` +template _d_arrayappendcTXImpl(Tarr : T[], T) +{ + import core.internal.array.utils : _d_HookTraceImpl; + + private enum errorMessage = "Cannot append to array if compiling without support for runtime type information!"; + + /** + * Extend an array `px` by `n` elements. + * Caller must initialize those elements. + * Params: + * px = the array that will be extended, taken as a reference + * n = how many new elements to extend it with + * Returns: + * The new value of `px` + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations. + */ + static if (isCopyingNothrow!T) // `nothrow` deduction doesn't work, so this is needed + ref Tarr _d_arrayappendcTX(return scope ref Tarr px, size_t n) @trusted pure nothrow + { + pragma(inline, false); + + mixin(_d_arrayappendcTXBody); + } + else + ref Tarr _d_arrayappendcTX(return scope ref Tarr px, size_t n) @trusted pure nothrow + { + pragma(inline, false); + + mixin(_d_arrayappendcTXBody); + } + + private enum _d_arrayappendcTXBody = q{ + version (D_TypeInfo) + { + auto ti = typeid(Tarr); + + // _d_arrayappendcTX takes the `px` as a ref byte[], but its length + // should still be the original length + auto pxx = (cast(byte*)px.ptr)[0 .. px.length]; + ._d_arrayappendcTX(ti, pxx, n); + px = (cast(T*)pxx.ptr)[0 .. pxx.length]; + + return px; + } + else + assert(0, "Cannot append arrays if compiling without support for runtime type information!"); + }; + + /** + * TraceGC wrapper around $(REF _d_arrayappendcTX, rt,array,appending,_d_arrayappendcTXImpl). + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations. + */ + alias _d_arrayappendcTXTrace = _d_HookTraceImpl!(Tarr, _d_arrayappendcTX, errorMessage); +} + +/// Implementation of `_d_arrayappendT` and `_d_arrayappendTTrace` +template _d_arrayappendTImpl(Tarr : T[], T) +{ + import core.internal.array.utils : _d_HookTraceImpl; + + private enum errorMessage = "Cannot append to array if compiling without support for runtime type information!"; + + /** + * Append array `y` to array `x`. + * Params: + * x = what array to append to, taken as a reference + * y = what should be appended + * Returns: + * The new value of `x` + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations. + */ + static if (isCopyingNothrow!T) + ref Tarr _d_arrayappendT(return scope ref Tarr x, scope Tarr y) @trusted pure nothrow + { + pragma(inline, false); + + mixin(_d_arrayappendTBody); + } + else + ref Tarr _d_arrayappendT(return scope ref Tarr x, scope Tarr y) @trusted pure + { + pragma(inline, false); + + mixin(_d_arrayappendTBody); + } + + private enum _d_arrayappendTBody = q{ + import core.stdc.string : memcpy; + import core.internal.traits : hasElaborateCopyConstructor, Unqual; + import core.lifetime : copyEmplace; + + auto length = x.length; + + _d_arrayappendcTXImpl!Tarr._d_arrayappendcTX(x, y.length); + + static if (hasElaborateCopyConstructor!T) + { + foreach (i; 0 .. y.length) + copyEmplace(y[i], x[length + i]); + } + else + { + // blit all elements at once + if (y.length) + memcpy(cast(Unqual!T *)&x[length], cast(Unqual!T *)&y[0], y.length * T.sizeof); + } + + return x; + }; + + /** + * TraceGC wrapper around $(REF _d_arrayappendT, rt,array,appending,_d_arrayappendTImpl). + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations. + */ + alias _d_arrayappendTTrace = _d_HookTraceImpl!(Tarr, _d_arrayappendT, errorMessage); +} + +@safe unittest +{ + double[] arr1; + foreach (i; 0 .. 4) + _d_arrayappendTImpl!(typeof(arr1))._d_arrayappendT(arr1, [cast(double)i]); + assert(arr1 == [0.0, 1.0, 2.0, 3.0]); +} + +@safe unittest +{ + int blitted; + struct Item + { + this(this) + { + blitted++; + } + } + + Item[] arr1 = [Item(), Item()]; + Item[] arr2 = [Item(), Item()]; + Item[] arr1_org = [Item(), Item()]; + arr1_org ~= arr2; + _d_arrayappendTImpl!(typeof(arr1))._d_arrayappendT(arr1, arr2); + + // postblit should have triggered on at least the items in arr2 + assert(blitted >= arr2.length); +} + +@safe nothrow unittest +{ + int blitted; + struct Item + { + this(this) nothrow + { + blitted++; + } + } + + Item[][] arr1 = [[Item()]]; + Item[][] arr2 = [[Item()]]; + + _d_arrayappendTImpl!(typeof(arr1))._d_arrayappendT(arr1, arr2); + + // no postblit should have happened because arr{1,2} contain dynamic arrays + assert(blitted == 0); +} + +@safe nothrow unittest +{ + int copied; + struct Item + { + this(const scope ref Item) nothrow + { + copied++; + } + } + + Item[1][] arr1 = [[Item()]]; + Item[1][] arr2 = [[Item()]]; + + _d_arrayappendTImpl!(typeof(arr1))._d_arrayappendT(arr1, arr2); + // copy constructor should have been invoked because arr{1,2} contain static arrays + assert(copied >= arr2.length); +} + +@safe nothrow unittest +{ + string str; + _d_arrayappendTImpl!(typeof(str))._d_arrayappendT(str, "a"); + _d_arrayappendTImpl!(typeof(str))._d_arrayappendT(str, "b"); + _d_arrayappendTImpl!(typeof(str))._d_arrayappendT(str, "c"); + assert(str == "abc"); +} diff --git a/libphobos/libdruntime/core/internal/array/capacity.d b/libphobos/libdruntime/core/internal/array/capacity.d new file mode 100644 index 0000000..9440428 --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/capacity.d @@ -0,0 +1,85 @@ +/** + This module contains support for controlling dynamic arrays' capacity and length + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/internal/_array/_capacity.d) +*/ +module core.internal.array.capacity; + +// HACK: `nothrow` and `pure` is faked. +private extern (C) void[] _d_arraysetlengthT(const TypeInfo ti, size_t newlength, void[]* p) nothrow pure; +private extern (C) void[] _d_arraysetlengthiT(const TypeInfo ti, size_t newlength, void[]* p) nothrow pure; + +/* + * This template is needed because there need to be a `_d_arraysetlengthTTrace!Tarr` instance for every + * `_d_arraysetlengthT!Tarr`. By wrapping both of these functions inside of this template we force the + * compiler to create a instance of both function for every type that is used. + */ + +/// Implementation of `_d_arraysetlengthT` and `_d_arraysetlengthTTrace` +template _d_arraysetlengthTImpl(Tarr : T[], T) +{ + import core.internal.array.utils : _d_HookTraceImpl; + + private enum errorMessage = "Cannot resize arrays if compiling without support for runtime type information!"; + + /** + * Resize dynamic array + * Params: + * arr = the array that will be resized, taken as a reference + * newlength = new length of array + * Returns: + * The new length of the array + * Bugs: + * The safety level of this function is faked. It shows itself as `@trusted pure nothrow` to not break existing code. + */ + size_t _d_arraysetlengthT(return scope ref Tarr arr, size_t newlength) @trusted pure nothrow + { + pragma(inline, false); + version (D_TypeInfo) + { + auto ti = typeid(Tarr); + + static if (__traits(isZeroInit, T)) + ._d_arraysetlengthT(ti, newlength, cast(void[]*)&arr); + else + ._d_arraysetlengthiT(ti, newlength, cast(void[]*)&arr); + + return arr.length; + } + else + assert(0, errorMessage); + } + + /** + * TraceGC wrapper around $(REF _d_arraysetlengthT, core,internal,array,core.internal.array.capacity). + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure nothrow` until the implementation can be brought up to modern D expectations. + */ + alias _d_arraysetlengthTTrace = _d_HookTraceImpl!(Tarr, _d_arraysetlengthT, errorMessage); +} + +@safe unittest +{ + struct S + { + float f = 1.0; + } + + int[] arr; + _d_arraysetlengthTImpl!(typeof(arr))._d_arraysetlengthT(arr, 16); + assert(arr.length == 16); + foreach (int i; arr) + assert(i == int.init); + + shared S[] arr2; + _d_arraysetlengthTImpl!(typeof(arr2))._d_arraysetlengthT(arr2, 16); + assert(arr2.length == 16); + foreach (s; arr2) + assert(s == S.init); +} diff --git a/libphobos/libdruntime/core/internal/array/casting.d b/libphobos/libdruntime/core/internal/array/casting.d new file mode 100644 index 0000000..e862f8e --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/casting.d @@ -0,0 +1,115 @@ +/** + This module contains compiler support for casting dynamic arrays + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/internal/_array/_casting.d) +*/ +module core.internal.array.casting; + +/** +Used by `__ArrayCast` to emit a descriptive error message. + +It is a template so it can be used by `__ArrayCast` in -betterC +builds. It is separate from `__ArrayCast` to minimize code +bloat. + +Params: + fromType = name of the type being cast from + fromSize = total size in bytes of the array being cast from + toType = name of the type being cast o + toSize = total size in bytes of the array being cast to + */ +private void onArrayCastError()(string fromType, size_t fromSize, string toType, size_t toSize) @trusted +{ + import core.internal.string : unsignedToTempString; + import core.memory : pureMalloc; + + // convert discontiguous `msgComponents` to contiguous string on the C heap + enum msgLength = 2048; + // note: never freed! + char* msg = cast(char *)pureMalloc(msgLength); + + size_t index = 0; + void add(const(char)[] m) + { + import core.stdc.string : memcpy; + + auto N = msgLength - 1 - index; + if (N > m.length) + N = m.length; + // prevent superfluous and betterC-unfriendly checks via direct memcpy + memcpy(msg + index, m.ptr, N); + index += N; + } + + add("An array of size "); + auto s = unsignedToTempString(fromSize); + add(s[]); + add(" does not align on an array of size "); + s = unsignedToTempString(toSize); + add(s[]); + add(", so `"); + add(fromType); + add("` cannot be cast to `"); + add(toType); + add("`"); + msg[index] = '\0'; // null-termination + + // first argument must evaluate to `false` at compile-time to maintain memory safety in release builds + assert(false, msg[0 .. index]); +} + +/** +The compiler lowers expressions of `cast(TTo[])TFrom[]` to +this implementation. + +Params: + from = the array to reinterpret-cast + +Returns: + `from` reinterpreted as `TTo[]` + */ +TTo[] __ArrayCast(TFrom, TTo)(return scope TFrom[] from) @nogc pure @trusted +{ + const fromSize = from.length * TFrom.sizeof; + const toLength = fromSize / TTo.sizeof; + + if ((fromSize % TTo.sizeof) != 0) + { + onArrayCastError(TFrom.stringof, fromSize, TTo.stringof, toLength * TTo.sizeof); + } + + struct Array + { + size_t length; + void* ptr; + } + auto a = cast(Array*)&from; + a.length = toLength; // jam new length + return *cast(TTo[]*)a; +} + +@safe @nogc pure nothrow unittest +{ + byte[int.sizeof * 3] b = cast(byte) 0xab; + int[] i; + short[] s; + + i = __ArrayCast!(byte, int)(b); + assert(i.length == 3); + foreach (v; i) + assert(v == cast(int) 0xabab_abab); + + s = __ArrayCast!(byte, short)(b); + assert(s.length == 6); + foreach (v; s) + assert(v == cast(short) 0xabab); + + s = __ArrayCast!(int, short)(i); + assert(s.length == 6); + foreach (v; s) + assert(v == cast(short) 0xabab); +} diff --git a/libphobos/libdruntime/core/internal/array/comparison.d b/libphobos/libdruntime/core/internal/array/comparison.d new file mode 100644 index 0000000..1a68b9b --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/comparison.d @@ -0,0 +1,242 @@ +/** + * This module contains compiler support for comparing dynamic arrays + * + * Copyright: Copyright Digital Mars 2000 - 2019. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Source: $(DRUNTIMESRC core/internal/_array/_comparison.d) + */ + +module core.internal.array.comparison; + +int __cmp(T)(scope const T[] lhs, scope const T[] rhs) @trusted + if (__traits(isScalar, T)) +{ + // Compute U as the implementation type for T + static if (is(T == ubyte) || is(T == void) || is(T == bool)) + alias U = char; + else static if (is(T == wchar)) + alias U = ushort; + else static if (is(T == dchar)) + alias U = uint; + else static if (is(T == ifloat)) + alias U = float; + else static if (is(T == idouble)) + alias U = double; + else static if (is(T == ireal)) + alias U = real; + else + alias U = T; + + static if (is(U == char)) + { + import core.internal.string : dstrcmp; + return dstrcmp(cast(char[]) lhs, cast(char[]) rhs); + } + else static if (!is(U == T)) + { + // Reuse another implementation + return __cmp(cast(U[]) lhs, cast(U[]) rhs); + } + else + { + version (BigEndian) + static if (__traits(isUnsigned, T) ? !is(T == __vector) : is(T : P*, P)) + { + if (!__ctfe) + { + import core.stdc.string : memcmp; + int c = memcmp(lhs.ptr, rhs.ptr, (lhs.length <= rhs.length ? lhs.length : rhs.length) * T.sizeof); + if (c) + return c; + static if (size_t.sizeof <= uint.sizeof && T.sizeof >= 2) + return cast(int) lhs.length - cast(int) rhs.length; + else + return int(lhs.length > rhs.length) - int(lhs.length < rhs.length); + } + } + + immutable len = lhs.length <= rhs.length ? lhs.length : rhs.length; + foreach (const u; 0 .. len) + { + static if (__traits(isFloating, T)) + { + immutable a = lhs.ptr[u], b = rhs.ptr[u]; + static if (is(T == cfloat) || is(T == cdouble) + || is(T == creal)) + { + // Use rt.cmath2._Ccmp instead ? + auto r = (a.re > b.re) - (a.re < b.re); + if (!r) r = (a.im > b.im) - (a.im < b.im); + } + else + { + const r = (a > b) - (a < b); + } + if (r) return r; + } + else if (lhs.ptr[u] != rhs.ptr[u]) + return lhs.ptr[u] < rhs.ptr[u] ? -1 : 1; + } + return (lhs.length > rhs.length) - (lhs.length < rhs.length); + } +} + +// This function is called by the compiler when dealing with array +// comparisons in the semantic analysis phase of CmpExp. The ordering +// comparison is lowered to a call to this template. +int __cmp(T1, T2)(T1[] s1, T2[] s2) +if (!__traits(isScalar, T1) && !__traits(isScalar, T2)) +{ + import core.internal.traits : Unqual; + alias U1 = Unqual!T1; + alias U2 = Unqual!T2; + + static if (is(U1 == void) && is(U2 == void)) + static @trusted ref inout(ubyte) at(inout(void)[] r, size_t i) { return (cast(inout(ubyte)*) r.ptr)[i]; } + else + static @trusted ref R at(R)(R[] r, size_t i) { return r.ptr[i]; } + + // All unsigned byte-wide types = > dstrcmp + immutable len = s1.length <= s2.length ? s1.length : s2.length; + + foreach (const u; 0 .. len) + { + static if (__traits(compiles, __cmp(at(s1, u), at(s2, u)))) + { + auto c = __cmp(at(s1, u), at(s2, u)); + if (c != 0) + return c; + } + else static if (__traits(compiles, at(s1, u).opCmp(at(s2, u)))) + { + auto c = at(s1, u).opCmp(at(s2, u)); + if (c != 0) + return c; + } + else static if (__traits(compiles, at(s1, u) < at(s2, u))) + { + if (at(s1, u) != at(s2, u)) + return at(s1, u) < at(s2, u) ? -1 : 1; + } + else + { + // TODO: fix this legacy bad behavior, see + // https://issues.dlang.org/show_bug.cgi?id=17244 + static assert(is(U1 == U2), "Internal error."); + import core.stdc.string : memcmp; + auto c = (() @trusted => memcmp(&at(s1, u), &at(s2, u), U1.sizeof))(); + if (c != 0) + return c; + } + } + return (s1.length > s2.length) - (s1.length < s2.length); +} + +// integral types +@safe unittest +{ + void compareMinMax(T)() + { + T[2] a = [T.max, T.max]; + T[2] b = [T.min, T.min]; + + assert(__cmp(a, b) > 0); + assert(__cmp(b, a) < 0); + } + + compareMinMax!int; + compareMinMax!uint; + compareMinMax!long; + compareMinMax!ulong; + compareMinMax!short; + compareMinMax!ushort; + compareMinMax!byte; + compareMinMax!dchar; + compareMinMax!wchar; +} + +// char types (dstrcmp) +@safe unittest +{ + void compareMinMax(T)() + { + T[2] a = [T.max, T.max]; + T[2] b = [T.min, T.min]; + + assert(__cmp(a, b) > 0); + assert(__cmp(b, a) < 0); + } + + compareMinMax!ubyte; + compareMinMax!bool; + compareMinMax!char; + compareMinMax!(const char); + + string s1 = "aaaa"; + string s2 = "bbbb"; + assert(__cmp(s2, s1) > 0); + assert(__cmp(s1, s2) < 0); +} + +// fp types +@safe unittest +{ + void compareMinMax(T)() + { + T[2] a = [T.max, T.max]; + T[2] b = [T.min_normal, T.min_normal]; + T[2] c = [T.max, T.min_normal]; + T[1] d = [T.max]; + + assert(__cmp(a, b) > 0); + assert(__cmp(b, a) < 0); + assert(__cmp(a, c) > 0); + assert(__cmp(a, d) > 0); + assert(__cmp(d, c) < 0); + assert(__cmp(c, c) == 0); + } + + compareMinMax!real; + compareMinMax!float; + compareMinMax!double; + + // qualifiers + compareMinMax!(const real); + compareMinMax!(immutable real); +} + +// void[] +@safe unittest +{ + void[] a; + const(void)[] b; + + (() @trusted + { + a = cast(void[]) "bb"; + b = cast(const(void)[]) "aa"; + })(); + + assert(__cmp(a, b) > 0); + assert(__cmp(b, a) < 0); +} + +// arrays of arrays with mixed modifiers +@safe unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=17876 + bool less1(immutable size_t[][] a, size_t[][] b) { return a < b; } + bool less2(const void[][] a, void[][] b) { return a < b; } + bool less3(inout size_t[][] a, size_t[][] b) { return a < b; } + + immutable size_t[][] a = [[1, 2], [3, 4]]; + size_t[][] b = [[1, 2], [3, 5]]; + assert(less1(a, b)); + assert(less3(a, b)); + + auto va = [cast(immutable void[])a[0], a[1]]; + auto vb = [cast(void[])b[0], b[1]]; + assert(less2(va, vb)); +} diff --git a/libphobos/libdruntime/core/internal/array/concatenation.d b/libphobos/libdruntime/core/internal/array/concatenation.d new file mode 100644 index 0000000..955e381 --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/concatenation.d @@ -0,0 +1,75 @@ +/** + This module contains support for controlling dynamic arrays' concatenation + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/internal/_array/_concatenation.d) +*/ +module core.internal.array.concatenation; + +/// See $(REF _d_arraycatnTX, rt,lifetime) +private extern (C) void[] _d_arraycatnTX(const TypeInfo ti, scope byte[][] arrs) pure nothrow; + +/// Implementation of `_d_arraycatnTX` and `_d_arraycatnTXTrace` +template _d_arraycatnTXImpl(Tarr : ResultArrT[], ResultArrT : T[], T) +{ + import core.internal.array.utils : _d_HookTraceImpl; + + private enum errorMessage = "Cannot concatenate arrays if compiling without support for runtime type information!"; + + /** + * Concatenating the arrays inside of `arrs`. + * `_d_arraycatnTX([a, b, c])` means `a ~ b ~ c`. + * Params: + * arrs = Array containing arrays that will be concatenated. + * Returns: + * A newly allocated array that contains all the elements from all the arrays in `arrs`. + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure nothrow` until the implementation can be brought up to modern D expectations. + */ + ResultArrT _d_arraycatnTX(scope const Tarr arrs) @trusted pure nothrow + { + pragma(inline, false); + version (D_TypeInfo) + { + auto ti = typeid(ResultArrT); + + byte[][] arrs2 = (cast(byte[]*)arrs.ptr)[0 .. arrs.length]; + void[] result = ._d_arraycatnTX(ti, arrs2); + return (cast(T*)result.ptr)[0 .. result.length]; + } + else + assert(0, errorMessage); + } + + /** + * TraceGC wrapper around $(REF _d_arraycatnTX, core,internal,array,concat). + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure nothrow` until the implementation can be brought up to modern D expectations. + */ + alias _d_arraycatnTXTrace = _d_HookTraceImpl!(ResultArrT, _d_arraycatnTX, errorMessage); +} + +@safe unittest +{ + int counter; + struct S + { + int val; + this(this) + { + counter++; + } + } + + S[][] arr = [[S(0), S(1), S(2), S(3)], [S(4), S(5), S(6), S(7)]]; + S[] result = _d_arraycatnTXImpl!(typeof(arr))._d_arraycatnTX(arr); + + assert(counter == 8); + assert(result == [S(0), S(1), S(2), S(3), S(4), S(5), S(6), S(7)]); +} diff --git a/libphobos/libdruntime/core/internal/array/construction.d b/libphobos/libdruntime/core/internal/array/construction.d new file mode 100644 index 0000000..b58ed51 --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/construction.d @@ -0,0 +1,307 @@ +/** + This module contains compiler support for constructing dynamic arrays + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/internal/_array/_construction.d) +*/ +module core.internal.array.construction; + +/** + * Does array initialization (not assignment) from another array of the same element type. + * Params: + * to = what array to initialize + * from = what data the array should be initialized with + * Returns: + * The constructed `to` + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted` until the implementation can be brought up to modern D expectations. + */ +Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + pragma(inline, false); + import core.internal.traits : hasElaborateCopyConstructor, Unqual; + import core.lifetime : copyEmplace; + import core.stdc.string : memcpy; + debug(PRINTF) import core.stdc.stdio; + + // Force `enforceRawArraysConformable` to be `pure` + void enforceRawArraysConformable(const char[] action, const size_t elementSize, const void[] a1, const void[] a2, in bool allowOverlap = false) @trusted + { + import core.internal.util.array : enforceRawArraysConformable; + + alias Type = void function(const char[] action, const size_t elementSize, const void[] a1, const void[] a2, in bool allowOverlap = false) pure nothrow; + (cast(Type)&enforceRawArraysConformable)(action, elementSize, a1, a2, allowOverlap); + } + + debug(PRINTF) printf("_d_arrayctor(to = %p,%d, from = %p,%d) size = %d\n", from.ptr, from.length, to.ptr, to.length, T.tsize); + + auto element_size = T.sizeof; + + void[] vFrom = (cast(void*)from.ptr)[0..from.length]; + void[] vTo = (cast(void*)to.ptr)[0..to.length]; + enforceRawArraysConformable("initialization", element_size, vFrom, vTo, false); + + static if (hasElaborateCopyConstructor!T) + { + size_t i; + try + { + for (i = 0; i < to.length; i++) + copyEmplace(from[i], to[i]); + } + catch (Exception o) + { + /* Destroy, in reverse order, what we've constructed so far + */ + while (i--) + { + auto elem = cast(Unqual!T*)&to[i]; + destroy(*elem); + } + + throw o; + } + } + else + { + // blit all elements at once + memcpy(cast(void*) to.ptr, from.ptr, to.length * T.sizeof); + } + + return to; +} + +// postblit +@safe unittest +{ + int counter; + struct S + { + int val; + this(this) { counter++; } + } + + S[4] arr1; + S[4] arr2 = [S(0), S(1), S(2), S(3)]; + _d_arrayctor(arr1[], arr2[]); + + assert(counter == 4); + assert(arr1 == arr2); +} + +// copy constructor +@safe unittest +{ + int counter; + struct S + { + int val; + this(int val) { this.val = val; } + this(const scope ref S rhs) + { + val = rhs.val; + counter++; + } + } + + S[4] arr1; + S[4] arr2 = [S(0), S(1), S(2), S(3)]; + _d_arrayctor(arr1[], arr2[]); + + assert(counter == 4); + assert(arr1 == arr2); +} + +@safe nothrow unittest +{ + // Test that throwing works + int counter; + bool didThrow; + + struct Throw + { + int val; + this(this) + { + counter++; + if (counter == 2) + throw new Exception(""); + } + } + try + { + Throw[4] a; + Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)]; + _d_arrayctor(a[], b[]); + } + catch (Exception) + { + didThrow = true; + } + assert(didThrow); + assert(counter == 2); + + + // Test that `nothrow` works + didThrow = false; + counter = 0; + struct NoThrow + { + int val; + this(this) + { + counter++; + } + } + try + { + NoThrow[4] a; + NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; + _d_arrayctor(a[], b[]); + } + catch (Exception) + { + didThrow = false; + } + assert(!didThrow); + assert(counter == 4); +} + +/** + * Do construction of an array. + * ti[count] p = value; + * Params: + * p = what array to initialize + * value = what data to construct the array with + * Bugs: + * This function template was ported from a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted` until the implementation can be brought up to modern D expectations. + */ +void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted +{ + pragma(inline, false); + import core.internal.traits : Unqual; + import core.lifetime : copyEmplace; + + size_t i; + try + { + for (i = 0; i < p.length; i++) + copyEmplace(value, p[i]); + } + catch (Exception o) + { + // Destroy, in reverse order, what we've constructed so far + while (i--) + { + auto elem = cast(Unqual!T*)&p[i]; + destroy(*elem); + } + + throw o; + } +} + +// postblit +@safe unittest +{ + int counter; + struct S + { + int val; + this(this) + { + counter++; + } + } + + S[4] arr; + S s = S(1234); + _d_arraysetctor(arr[], s); + assert(counter == arr.length); + assert(arr == [S(1234), S(1234), S(1234), S(1234)]); +} + +// copy constructor +@safe unittest +{ + int counter; + struct S + { + int val; + this(int val) { this.val = val; } + this(const scope ref S rhs) + { + val = rhs.val; + counter++; + } + } + + S[4] arr; + S s = S(1234); + _d_arraysetctor(arr[], s); + assert(counter == arr.length); + assert(arr == [S(1234), S(1234), S(1234), S(1234)]); +} + +@safe nothrow unittest +{ + // Test that throwing works + int counter; + bool didThrow; + struct Throw + { + int val; + this(this) + { + counter++; + if (counter == 2) + throw new Exception("Oh no."); + } + } + try + { + Throw[4] a; + Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)]; + _d_arrayctor(a[], b[]); + } + catch (Exception) + { + didThrow = true; + } + assert(didThrow); + assert(counter == 2); + + + // Test that `nothrow` works + didThrow = false; + counter = 0; + struct NoThrow + { + int val; + this(this) + { + counter++; + } + } + try + { + NoThrow[4] a; + NoThrow b = NoThrow(1); + _d_arraysetctor(a[], b); + foreach (ref e; a) + assert(e == NoThrow(1)); + } + catch (Exception) + { + didThrow = false; + } + assert(!didThrow); + assert(counter == 4); +} diff --git a/libphobos/libdruntime/core/internal/array/equality.d b/libphobos/libdruntime/core/internal/array/equality.d new file mode 100644 index 0000000..b12e2f2 --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/equality.d @@ -0,0 +1,237 @@ +/** + * This module contains compiler support determining equality of arrays. + * + * Copyright: Copyright Digital Mars 2000 - 2020. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Source: $(DRUNTIMESRC core/internal/_array/_equality.d) + */ + +module core.internal.array.equality; + +// The compiler lowers `lhs == rhs` to `__equals(lhs, rhs)` for +// * dynamic arrays, +// * (most) arrays of different (unqualified) element types, and +// * arrays of structs with custom opEquals. + + // The scalar-only overload takes advantage of known properties of scalars to + // reduce template instantiation. This is expected to be the most common case. +bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) +@nogc nothrow pure @trusted +if (__traits(isScalar, T1) && __traits(isScalar, T2)) +{ + if (lhs.length != rhs.length) + return false; + + static if (T1.sizeof == T2.sizeof + // Signedness needs to match for types that promote to int. + // (Actually it would be okay to memcmp bool[] and byte[] but that is + // probably too uncommon to be worth checking for.) + && (T1.sizeof >= 4 || __traits(isUnsigned, T1) == __traits(isUnsigned, T2)) + && !__traits(isFloating, T1) && !__traits(isFloating, T2)) + { + if (!__ctfe) + { + // This would improperly allow equality of integers and pointers + // but the CTFE branch will stop this function from compiling then. + import core.stdc.string : memcmp; + return lhs.length == 0 || + 0 == memcmp(cast(const void*) lhs.ptr, cast(const void*) rhs.ptr, lhs.length * T1.sizeof); + } + } + + foreach (const i; 0 .. lhs.length) + if (lhs.ptr[i] != rhs.ptr[i]) + return false; + return true; +} + +bool __equals(T1, T2)(scope T1[] lhs, scope T2[] rhs) +if (!__traits(isScalar, T1) || !__traits(isScalar, T2)) +{ + if (lhs.length != rhs.length) + return false; + + if (lhs.length == 0) + return true; + + static if (useMemcmp!(T1, T2)) + { + if (!__ctfe) + { + static bool trustedMemcmp(scope T1[] lhs, scope T2[] rhs) @trusted @nogc nothrow pure + { + pragma(inline, true); + import core.stdc.string : memcmp; + return memcmp(cast(void*) lhs.ptr, cast(void*) rhs.ptr, lhs.length * T1.sizeof) == 0; + } + return trustedMemcmp(lhs, rhs); + } + else + { + foreach (const i; 0 .. lhs.length) + { + if (at(lhs, i) != at(rhs, i)) + return false; + } + return true; + } + } + else + { + foreach (const i; 0 .. lhs.length) + { + if (at(lhs, i) != at(rhs, i)) + return false; + } + return true; + } +} + +@safe unittest +{ + assert(__equals([], [])); + assert(!__equals([1, 2], [1, 2, 3])); +} + +@safe unittest +{ + auto a = "hello"c; + + assert(a != "hel"); + assert(a != "helloo"); + assert(a != "betty"); + assert(a == "hello"); + assert(a != "hxxxx"); + + float[] fa = [float.nan]; + assert(fa != fa); +} + +@safe unittest +{ + struct A + { + int a; + } + + auto arr1 = [A(0), A(2)]; + auto arr2 = [A(0), A(1)]; + auto arr3 = [A(0), A(1)]; + + assert(arr1 != arr2); + assert(arr2 == arr3); +} + +@safe unittest +{ + struct A + { + int a; + int b; + + bool opEquals(const A other) + { + return this.a == other.b && this.b == other.a; + } + } + + auto arr1 = [A(1, 0), A(0, 1)]; + auto arr2 = [A(1, 0), A(0, 1)]; + auto arr3 = [A(0, 1), A(1, 0)]; + + assert(arr1 != arr2); + assert(arr2 == arr3); +} + +// https://issues.dlang.org/show_bug.cgi?id=18252 +@safe unittest +{ + string[int][] a1, a2; + assert(__equals(a1, a2)); + assert(a1 == a2); + a1 ~= [0: "zero"]; + a2 ~= [0: "zero"]; + assert(__equals(a1, a2)); + assert(a1 == a2); + a2[0][1] = "one"; + assert(!__equals(a1, a2)); + assert(a1 != a2); +} + + +private: + +// - Recursively folds static array types to their element type, +// - maps void to ubyte, and +// - pointers to size_t. +template BaseType(T) +{ + static if (__traits(isStaticArray, T)) + alias BaseType = BaseType!(typeof(T.init[0])); + else static if (is(immutable T == immutable void)) + alias BaseType = ubyte; + else static if (is(T == E*, E)) + alias BaseType = size_t; + else + alias BaseType = T; +} + +// Use memcmp if the element sizes match and both base element types are integral. +// Due to int promotion, disallow small integers of diverging signed-ness though. +template useMemcmp(T1, T2) +{ + static if (T1.sizeof != T2.sizeof) + enum useMemcmp = false; + else + { + alias B1 = BaseType!T1; + alias B2 = BaseType!T2; + enum useMemcmp = __traits(isIntegral, B1) && __traits(isIntegral, B2) + && !( (B1.sizeof < 4 || B2.sizeof < 4) && __traits(isUnsigned, B1) != __traits(isUnsigned, B2) ); + } +} + +unittest +{ + enum E { foo, bar } + + static assert(useMemcmp!(byte, byte)); + static assert(useMemcmp!(ubyte, ubyte)); + static assert(useMemcmp!(void, const void)); + static assert(useMemcmp!(void, immutable bool)); + static assert(useMemcmp!(void, inout char)); + static assert(useMemcmp!(void, shared ubyte)); + static assert(!useMemcmp!(void, byte)); // differing signed-ness + static assert(!useMemcmp!(char[8], byte[8])); // ditto + + static assert(useMemcmp!(short, short)); + static assert(useMemcmp!(wchar, ushort)); + static assert(!useMemcmp!(wchar, short)); // differing signed-ness + + static assert(useMemcmp!(int, uint)); // no promotion, ignoring signed-ness + static assert(useMemcmp!(dchar, E)); + + static assert(useMemcmp!(immutable void*, size_t)); + static assert(useMemcmp!(double*, ptrdiff_t)); + static assert(useMemcmp!(long[2][3], const(ulong)[2][3])); + + static assert(!useMemcmp!(float, float)); + static assert(!useMemcmp!(double[2], double[2])); + static assert(!useMemcmp!(Object, Object)); + static assert(!useMemcmp!(int[], int[])); +} + +// Returns a reference to an array element, eliding bounds check and +// casting void to ubyte. +pragma(inline, true) +ref at(T)(T[] r, size_t i) @trusted + // exclude opaque structs due to https://issues.dlang.org/show_bug.cgi?id=20959 + if (!(is(T == struct) && !is(typeof(T.sizeof)))) +{ + static if (is(immutable T == immutable void)) + return (cast(ubyte*) r.ptr)[i]; + else + return r.ptr[i]; +} diff --git a/libphobos/libdruntime/core/internal/array/operations.d b/libphobos/libdruntime/core/internal/array/operations.d new file mode 100644 index 0000000..3e23314 --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/operations.d @@ -0,0 +1,670 @@ +/** + This module contains support array (vector) operations + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/_internal/_array/_operations.d) +*/ +module core.internal.array.operations; +import core.internal.traits : Filter, staticMap, Unqual; + +version (GNU) version = GNU_OR_LDC; +version (LDC) version = GNU_OR_LDC; + +/** + * Perform array (vector) operations and store the result in `res`. Operand + * types and operations are passed as template arguments in Reverse Polish + * Notation (RPN). + + * Operands can be slices or scalar types. The element types of all + * slices and all scalar types must be implicitly convertible to `T`. + * + * Operations are encoded as strings, e.g. `"+"`, `"%"`, `"*="`. Unary + * operations are prefixed with "u", e.g. `"u-"`, `"u~"`. Only the last + * operation can and must be an assignment (`"="`) or op-assignment (`"op="`). + * + * All slice operands must have the same length as the result slice. + * + * Params: T[] = type of result slice + * Args = operand types and operations in RPN + * res = the slice in which to store the results + * args = operand values + * + * Returns: the slice containing the result + */ +T[] arrayOp(T : T[], Args...)(T[] res, Filter!(isType, Args) args) @trusted @nogc pure nothrow +{ + alias scalarizedExp = staticMap!(toElementType, Args); + alias check = typeCheck!(true, T, scalarizedExp); // must support all scalar ops + + foreach (argsIdx, arg; typeof(args)) + { + static if (is(arg == U[], U)) + { + assert(res.length == args[argsIdx].length, "Mismatched array lengths for vector operation"); + } + } + + size_t pos; + static if (vectorizeable!(T[], Args)) + { + alias vec = .vec!T; + alias load = .load!(T, vec.length); + alias store = .store!(T, vec.length); + + // Given that there are at most as many scalars broadcast as there are + // operations in any `ary[] = ary[] op const op const`, it should always be + // worthwhile to choose vector operations. + if (!__ctfe && res.length >= vec.length) + { + mixin(initScalarVecs!Args); + + auto n = res.length / vec.length; + do + { + mixin(vectorExp!Args ~ ";"); + pos += vec.length; + } + while (--n); + } + } + for (; pos < res.length; ++pos) + mixin(scalarExp!Args ~ ";"); + + return res; +} + +private: + +// SIMD helpers + +version (DigitalMars) +{ + import core.simd; + + template vec(T) + { + enum regsz = 16; // SSE2 + enum N = regsz / T.sizeof; + alias vec = __vector(T[N]); + } + + void store(T, size_t N)(T* p, const scope __vector(T[N]) val) + { + pragma(inline, true); + alias vec = __vector(T[N]); + + static if (is(T == float)) + cast(void) __simd_sto(XMM.STOUPS, *cast(vec*) p, val); + else static if (is(T == double)) + cast(void) __simd_sto(XMM.STOUPD, *cast(vec*) p, val); + else + cast(void) __simd_sto(XMM.STODQU, *cast(vec*) p, val); + } + + const(__vector(T[N])) load(T, size_t N)(const scope T* p) + { + import core.simd; + + pragma(inline, true); + alias vec = __vector(T[N]); + + static if (is(T == float)) + return cast(typeof(return)) __simd(XMM.LODUPS, *cast(const vec*) p); + else static if (is(T == double)) + return cast(typeof(return)) __simd(XMM.LODUPD, *cast(const vec*) p); + else + return cast(typeof(return)) __simd(XMM.LODDQU, *cast(const vec*) p); + } + + __vector(T[N]) binop(string op, T, size_t N)(const scope __vector(T[N]) a, const scope __vector(T[N]) b) + { + pragma(inline, true); + return mixin("a " ~ op ~ " b"); + } + + __vector(T[N]) unaop(string op, T, size_t N)(const scope __vector(T[N]) a) + if (op[0] == 'u') + { + pragma(inline, true); + return mixin(op[1 .. $] ~ "a"); + } +} + +// mixin gen + +/** +Check whether operations on operand types are supported. This +template recursively reduces the expression tree and determines +intermediate types. +Type checking is done here rather than in the compiler to provide more +detailed error messages. + +Params: + fail = whether to fail (static assert) with a human-friendly error message + T = type of result + Args = operand types and operations in RPN +Returns: + The resulting type of the expression +See_Also: + $(LREF arrayOp) +*/ +template typeCheck(bool fail, T, Args...) +{ + enum idx = staticIndexOf!(not!isType, Args); + static if (isUnaryOp(Args[idx])) + { + alias UT = Args[idx - 1]; + enum op = Args[idx][1 .. $]; + static if (is(typeof((UT a) => mixin(op ~ "cast(int) a")) RT == return)) + alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 1], RT, Args[idx + 1 .. $]); + else static if (fail) + static assert(0, "Unary `" ~ op ~ "` not supported for type `" ~ UT.stringof ~ "`."); + } + else static if (isBinaryOp(Args[idx])) + { + alias LHT = Args[idx - 2]; + alias RHT = Args[idx - 1]; + enum op = Args[idx]; + static if (is(typeof((LHT a, RHT b) => mixin("a " ~ op ~ " b")) RT == return)) + alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 2], RT, Args[idx + 1 .. $]); + else static if (fail) + static assert(0, + "Binary `" ~ op ~ "` not supported for types `" + ~ LHT.stringof ~ "` and `" ~ RHT.stringof ~ "`."); + } + else static if (Args[idx] == "=" || isBinaryAssignOp(Args[idx])) + { + alias RHT = Args[idx - 1]; + enum op = Args[idx]; + static if (is(T == __vector(ET[N]), ET, size_t N)) + { + // no `cast(T)` before assignment for vectors + static if (is(typeof((T res, RHT b) => mixin("res " ~ op ~ " b")) RT == return) + && // workaround https://issues.dlang.org/show_bug.cgi?id=17758 + (op != "=" || is(Unqual!T == Unqual!RHT))) + alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 1], RT, Args[idx + 1 .. $]); + else static if (fail) + static assert(0, + "Binary op `" ~ op ~ "` not supported for types `" + ~ T.stringof ~ "` and `" ~ RHT.stringof ~ "`."); + } + else + { + static if (is(typeof((RHT b) => mixin("cast(T) b")))) + { + static if (is(typeof((T res, T b) => mixin("res " ~ op ~ " b")) RT == return)) + alias typeCheck = typeCheck!(fail, T, Args[0 .. idx - 1], RT, Args[idx + 1 .. $]); + else static if (fail) + static assert(0, + "Binary op `" ~ op ~ "` not supported for types `" + ~ T.stringof ~ "` and `" ~ T.stringof ~ "`."); + } + else static if (fail) + static assert(0, + "`cast(" ~ T.stringof ~ ")` not supported for type `" ~ RHT.stringof ~ "`."); + } + } + else + static assert(0); +} +/// ditto +template typeCheck(bool fail, T, ResultType) +{ + alias typeCheck = ResultType; +} + +version (GNU_OR_LDC) +{ + // leave it to the auto-vectorizer + enum vectorizeable(E : E[], Args...) = false; +} +else +{ + // check whether arrayOp is vectorizable + template vectorizeable(E : E[], Args...) + { + static if (is(vec!E)) + { + // type check with vector types + enum vectorizeable = is(typeCheck!(false, vec!E, staticMap!(toVecType, Args))); + } + else + enum vectorizeable = false; + } + + version (X86_64) unittest + { + static assert(vectorizeable!(double[], const(double)[], double[], "+", "=")); + static assert(!vectorizeable!(double[], const(ulong)[], double[], "+", "=")); + // Vector type are (atm.) not implicitly convertible and would require + // lots of SIMD intrinsics. Therefor leave mixed type array ops to + // GDC/LDC's auto-vectorizers. + static assert(!vectorizeable!(double[], const(uint)[], uint, "+", "=")); + } +} + +bool isUnaryOp(scope string op) pure nothrow @safe @nogc +{ + return op[0] == 'u'; +} + +bool isBinaryOp(scope string op) pure nothrow @safe @nogc +{ + if (op == "^^") + return true; + if (op.length != 1) + return false; + switch (op[0]) + { + case '+', '-', '*', '/', '%', '|', '&', '^': + return true; + default: + return false; + } +} + +bool isBinaryAssignOp(string op) +{ + return op.length >= 2 && op[$ - 1] == '=' && isBinaryOp(op[0 .. $ - 1]); +} + +// Generate mixin expression to perform scalar arrayOp loop expression, assumes +// `pos` to be the current slice index, `args` to contain operand values, and +// `res` the target slice. +enum scalarExp(Args...) = +(){ + string[] stack; + size_t argsIdx; + + static if (is(Args[0] == U[], U)) + alias Type = U; + else + alias Type = Args[0]; + + foreach (i, arg; Args) + { + static if (is(arg == T[], T)) + stack ~= "args[" ~ argsIdx++.toString ~ "][pos]"; + else static if (is(arg)) + stack ~= "args[" ~ argsIdx++.toString ~ "]"; + else static if (isUnaryOp(arg)) + { + auto op = arg[0] == 'u' ? arg[1 .. $] : arg; + // Explicitly use the old integral promotion rules + // See also: https://dlang.org/changelog/2.078.0.html#fix16997 + static if (is(Type : int)) + stack[$ - 1] = "cast(typeof(" ~ stack[$ -1] ~ "))" ~ op ~ "cast(int)("~ stack[$ - 1] ~ ")"; + else + stack[$ - 1] = op ~ stack[$ - 1]; + } + else static if (arg == "=") + { + stack[$ - 1] = "res[pos] = cast(T)(" ~ stack[$ - 1] ~ ")"; + } + else static if (isBinaryAssignOp(arg)) + { + stack[$ - 1] = "res[pos] " ~ arg ~ " cast(T)(" ~ stack[$ - 1] ~ ")"; + } + else static if (isBinaryOp(arg)) + { + stack[$ - 2] = "(" ~ stack[$ - 2] ~ " " ~ arg ~ " " ~ stack[$ - 1] ~ ")"; + stack.length -= 1; + } + else + assert(0, "Unexpected op " ~ arg); + } + assert(stack.length == 1); + return stack[0]; +}(); + +// Generate mixin statement to perform vector loop initialization, assumes +// `args` to contain operand values. +enum initScalarVecs(Args...) = +() { + size_t scalarsIdx, argsIdx; + string res; + foreach (arg; Args) + { + static if (is(arg == T[], T)) + { + ++argsIdx; + } + else static if (is(arg)) + res ~= "immutable vec scalar" ~ scalarsIdx++.toString ~ " = args[" + ~ argsIdx++.toString ~ "];\n"; + } + return res; +}(); + +// Generate mixin expression to perform vector arrayOp loop expression, assumes +// `pos` to be the current slice index, `args` to contain operand values, and +// `res` the target slice. +enum vectorExp(Args...) = +() { + size_t scalarsIdx, argsIdx; + string[] stack; + foreach (arg; Args) + { + static if (is(arg == T[], T)) + stack ~= "load(&args[" ~ argsIdx++.toString ~ "][pos])"; + else static if (is(arg)) + { + ++argsIdx; + stack ~= "scalar" ~ scalarsIdx++.toString; + } + else static if (isUnaryOp(arg)) + { + auto op = arg[0] == 'u' ? arg[1 .. $] : arg; + stack[$ - 1] = "unaop!\"" ~ arg ~ "\"(" ~ stack[$ - 1] ~ ")"; + } + else static if (arg == "=") + { + stack[$ - 1] = "store(&res[pos], " ~ stack[$ - 1] ~ ")"; + } + else static if (isBinaryAssignOp(arg)) + { + stack[$ - 1] = "store(&res[pos], binop!\"" ~ arg[0 .. $ - 1] + ~ "\"(load(&res[pos]), " ~ stack[$ - 1] ~ "))"; + } + else static if (isBinaryOp(arg)) + { + stack[$ - 2] = "binop!\"" ~ arg ~ "\"(" ~ stack[$ - 2] ~ ", " ~ stack[$ - 1] ~ ")"; + stack.length -= 1; + } + else + assert(0, "Unexpected op " ~ arg); + } + assert(stack.length == 1); + return stack[0]; +}(); + +// other helpers + +enum isType(T) = true; +enum isType(alias a) = false; +template not(alias tmlp) +{ + enum not(Args...) = !tmlp!Args; +} +/** +Find element in `haystack` for which `pred` is true. + +Params: + pred = the template predicate + haystack = elements to search +Returns: + The first index for which `pred!haystack[index]` is true or -1. + */ +template staticIndexOf(alias pred, haystack...) +{ + static if (pred!(haystack[0])) + enum staticIndexOf = 0; + else + { + enum next = staticIndexOf!(pred, haystack[1 .. $]); + enum staticIndexOf = next == -1 ? -1 : next + 1; + } +} +/// converts slice types to their element type, preserves anything else +alias toElementType(E : E[]) = E; +alias toElementType(S) = S; +alias toElementType(alias op) = op; +/// converts slice types to their element type, preserves anything else +alias toVecType(E : E[]) = vec!E; +alias toVecType(S) = vec!S; +alias toVecType(alias op) = op; + +string toString(size_t num) +{ + import core.internal.string : unsignedToTempString; + version (D_BetterC) + { + // Workaround for https://issues.dlang.org/show_bug.cgi?id=19268 + if (__ctfe) + { + char[20] fixedbuf = void; + char[] buf = unsignedToTempString(num, fixedbuf); + char[] result = new char[buf.length]; + result[] = buf[]; + return (() @trusted => cast(string) result)(); + } + else + { + // Failing at execution rather than during compilation is + // not good, but this is in `core.internal` so it should + // not be used by the unwary. + assert(0, __FUNCTION__ ~ " not available in -betterC except during CTFE."); + } + } + else + { + char[20] buf = void; + return unsignedToTempString(num, buf).idup; + } +} + +bool contains(T)(const scope T[] ary, const scope T[] vals...) +{ + foreach (v1; ary) + foreach (v2; vals) + if (v1 == v2) + return true; + return false; +} + +// tests + +version (CoreUnittest) template TT(T...) +{ + alias TT = T; +} + +version (CoreUnittest) template _arrayOp(Args...) +{ + alias _arrayOp = arrayOp!Args; +} + +unittest +{ + static void check(string op, TA, TB, T, size_t N)(TA a, TB b, const scope ref T[N] exp) + { + T[N] res; + _arrayOp!(T[], TA, TB, op, "=")(res[], a, b); + foreach (i; 0 .. N) + assert(res[i] == exp[i]); + } + + static void check2(string unaOp, string binOp, TA, TB, T, size_t N)(TA a, TB b, const scope ref T[N] exp) + { + T[N] res; + _arrayOp!(T[], TA, TB, unaOp, binOp, "=")(res[], a, b); + foreach (i; 0 .. N) + assert(res[i] == exp[i]); + } + + static void test(T, string op, size_t N = 16)(T a, T b, T exp) + { + T[N] va = a, vb = b, vexp = exp; + + check!op(va[], vb[], vexp); + check!op(va[], b, vexp); + check!op(a, vb[], vexp); + } + + static void test2(T, string unaOp, string binOp, size_t N = 16)(T a, T b, T exp) + { + T[N] va = a, vb = b, vexp = exp; + + check2!(unaOp, binOp)(va[], vb[], vexp); + check2!(unaOp, binOp)(va[], b, vexp); + check2!(unaOp, binOp)(a, vb[], vexp); + } + + alias UINTS = TT!(ubyte, ushort, uint, ulong); + alias INTS = TT!(byte, short, int, long); + alias FLOATS = TT!(float, double); + + foreach (T; TT!(UINTS, INTS, FLOATS)) + { + test!(T, "+")(1, 2, 3); + test!(T, "-")(3, 2, 1); + static if (__traits(compiles, { import std.math; })) + test!(T, "^^")(2, 3, 8); + + test2!(T, "u-", "+")(3, 2, 1); + } + + foreach (T; TT!(UINTS, INTS)) + { + test!(T, "|")(1, 2, 3); + test!(T, "&")(3, 1, 1); + test!(T, "^")(3, 1, 2); + + test2!(T, "u~", "+")(3, cast(T)~2, 5); + } + + foreach (T; TT!(INTS, FLOATS)) + { + test!(T, "-")(1, 2, -1); + test2!(T, "u-", "+")(-3, -2, -1); + test2!(T, "u-", "*")(-3, -2, -6); + } + + foreach (T; TT!(UINTS, INTS, FLOATS)) + { + test!(T, "*")(2, 3, 6); + test!(T, "/")(8, 4, 2); + test!(T, "%")(8, 6, 2); + } +} + +// test handling of v op= exp +unittest +{ + uint[32] c; + arrayOp!(uint[], uint, "+=")(c[], 2); + foreach (v; c) + assert(v == 2); + static if (__traits(compiles, { import std.math; })) + { + arrayOp!(uint[], uint, "^^=")(c[], 3); + foreach (v; c) + assert(v == 8); + } +} + +// proper error message for UDT lacking certain ops +unittest +{ + static assert(!is(typeof(&arrayOp!(int[4][], int[4], "+=")))); + static assert(!is(typeof(&arrayOp!(int[4][], int[4], "u-", "=")))); + + static struct S + { + } + + static assert(!is(typeof(&arrayOp!(S[], S, "+=")))); + static assert(!is(typeof(&arrayOp!(S[], S[], "*", S, "+=")))); + static struct S2 + { + S2 opBinary(string op)(in S2) @nogc pure nothrow + { + return this; + } + + ref S2 opOpAssign(string op)(in S2) @nogc pure nothrow + { + return this; + } + } + + static assert(is(typeof(&arrayOp!(S2[], S2[], S2[], S2, "*", "+", "=")))); + static assert(is(typeof(&arrayOp!(S2[], S2[], S2, "*", "+=")))); +} + +// test mixed type array op +unittest +{ + uint[32] a = 0xF; + float[32] res = 2.0f; + arrayOp!(float[], const(uint)[], uint, "&", "*=")(res[], a[], 12); + foreach (v; res[]) + assert(v == 24.0f); +} + +// test mixed type array op +unittest +{ + static struct S + { + float opBinary(string op)(in S) @nogc const pure nothrow + { + return 2.0f; + } + } + + float[32] res = 24.0f; + S[32] s; + arrayOp!(float[], const(S)[], const(S)[], "+", "/=")(res[], s[], s[]); + foreach (v; res[]) + assert(v == 12.0f); +} + +// test scalar after operation argument +unittest +{ + float[32] res, a = 2, b = 3; + float c = 4; + arrayOp!(float[], const(float)[], const(float)[], "*", float, "+", "=")(res[], a[], b[], c); + foreach (v; res[]) + assert(v == 2 * 3 + 4); +} + +unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=17964 + uint bug(){ + uint[] a = [1, 2, 3, 5, 6, 7]; + uint[] b = [1, 2, 3, 5, 6, 7]; + a[] |= ~b[]; + return a[1]; + } + enum x = bug(); +} + +// https://issues.dlang.org/show_bug.cgi?id=19796 +unittest +{ + double[] data = [0.5]; + double[] result; + result.length = data.length; + result[] = -data[]; + assert(result[0] == -0.5); +} + +// https://issues.dlang.org/show_bug.cgi?id=21110 +unittest +{ + import core.exception; + + static void assertThrown(T : Throwable, E)(lazy E expression, string msg) + { + try + expression; + catch (T) + return; + assert(0, "msg"); + } + + int[] dst; + int[] a; + int[] b; + a.length = 3; + b.length = 3; + dst.length = 4; + + void func() { dst[] = a[] + b[]; } + assertThrown!AssertError(func(), "Array operations with mismatched lengths must throw an error"); +} diff --git a/libphobos/libdruntime/core/internal/array/utils.d b/libphobos/libdruntime/core/internal/array/utils.d new file mode 100644 index 0000000..7a829a0 --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/utils.d @@ -0,0 +1,121 @@ +/** + This module contains utility functions to help the implementation of the runtime hook + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/internal/_array/_utils.d) +*/ +module core.internal.array.utils; + +import core.internal.traits : Parameters; + +private auto gcStatsPure() nothrow pure +{ + import core.memory : GC; + + auto impureBypass = cast(GC.Stats function() pure nothrow)&GC.stats; + return impureBypass(); +} + +private ulong accumulatePure(string file, int line, string funcname, string name, ulong size) nothrow pure +{ + static ulong impureBypass(string file, int line, string funcname, string name, ulong size) @nogc nothrow + { + import core.internal.traits : externDFunc; + + alias accumulate = externDFunc!("rt.profilegc.accumulate", void function(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow); + accumulate(file, line, funcname, name, size); + return size; + } + + auto func = cast(ulong function(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure)&impureBypass; + return func(file, line, funcname, name, size); +} + +/** + * TraceGC wrapper around runtime hook `Hook`. + * Params: + * T = Type of hook to report to accumulate + * Hook = The hook to wrap + * errorMessage = The error message incase `version != D_TypeInfo` + * file = File that called `_d_HookTraceImpl` + * line = Line inside of `file` that called `_d_HookTraceImpl` + * funcname = Function that called `_d_HookTraceImpl` + * parameters = Parameters that will be used to call `Hook` + * Bugs: + * This function template needs be between the compiler and a much older runtime hook that bypassed safety, + * purity, and throwabilty checks. To prevent breaking existing code, this function template + * is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations. +*/ +auto _d_HookTraceImpl(T, alias Hook, string errorMessage)(string file, int line, string funcname, Parameters!Hook parameters) @trusted pure +{ + version (D_TypeInfo) + { + pragma(inline, false); + string name = T.stringof; + + // FIXME: use rt.tracegc.accumulator when it is accessable in the future. + version (tracegc) + { + import core.stdc.stdio; + + printf("%sTrace file = '%.*s' line = %d function = '%.*s' type = %.*s\n", + Hook.stringof.ptr, + file.length, file.ptr, + line, + funcname.length, funcname.ptr, + name.length, name.ptr + ); + } + + ulong currentlyAllocated = gcStatsPure().allocatedInCurrentThread; + + scope(exit) + { + ulong size = gcStatsPure().allocatedInCurrentThread - currentlyAllocated; + if (size > 0) + if (!accumulatePure(file, line, funcname, name, size)) { + // This 'if' and 'assert' is needed to force the compiler to not remove the call to + // `accumulatePure`. It really want to do that while optimizing as the function is + // `pure` and it does not influence the result of this hook. + + // `accumulatePure` returns the value of `size`, which can never be zero due to the + // previous 'if'. So this assert will never be triggered. + assert(0); + } + } + return Hook(parameters); + } + else + assert(0, errorMessage); +} + +/** + * Check if the function `F` is calleable in a `nothrow` scope. + * Params: + * F = Function that does not take any parameters + * Returns: + * if the function is callable in a `nothrow` scope. + */ +enum isNoThrow(alias F) = is(typeof(() nothrow { F(); })); + +/** + * Check if the type `T`'s postblit is called in nothrow, if it exist + * Params: + * T = Type to check + * Returns: + * if the postblit is callable in a `nothrow` scope, if it exist. + * if it does not exist, return true. + */ +template isPostblitNoThrow(T) { + static if (__traits(isStaticArray, T)) + enum isPostblitNoThrow = isPostblitNoThrow!(typeof(T.init[0])); + else static if (__traits(hasMember, T, "__xpostblit") && + // Bugzilla 14746: Check that it's the exact member of S. + __traits(isSame, T, __traits(parent, T.init.__xpostblit))) + enum isPostblitNoThrow = isNoThrow!(T.init.__xpostblit); + else + enum isPostblitNoThrow = true; +} diff --git a/libphobos/libdruntime/core/internal/arrayop.d b/libphobos/libdruntime/core/internal/arrayop.d deleted file mode 100644 index 34531d8..0000000 --- a/libphobos/libdruntime/core/internal/arrayop.d +++ /dev/null @@ -1,451 +0,0 @@ -module core.internal.arrayop; -import core.internal.traits : Filter, Unqual; - -version (GNU) version = GNU_OR_LDC; -version (LDC) version = GNU_OR_LDC; - -/** - * Perform array (vector) operations and store the result in `res`. Operand - * types and operations are passed as template arguments in Reverse Polish - * Notation (RPN). - - * Operands can be slices or scalar types. The unqualified element types of all - * slices must be `T`, scalar types must be implicitly convertible to `T`. - * - * Operations are encoded as strings, e.g. `"+"`, `"%"`, `"*="`. Unary - * operations are prefixed with "u", e.g. `"u-"`, `"u~"`. Only the last - * operation can and must be an assignment (`"="`) or op-assignment (`"op="`). - * - * All slice operands must have the same length as the result slice. - * - * Params: T[] = type of result slice - * Args = operand types and operations in RPN - * res = the slice in which to store the results - * args = operand values - * - * Returns: the slice containing the result - */ -T[] arrayOp(T : T[], Args...)(T[] res, Filter!(isType, Args) args) @trusted @nogc pure nothrow -{ - enum check = opsSupported!(true, T, Filter!(not!isType, Args)); // must support all scalar ops - - size_t pos; - static if (vectorizeable!(T[], Args)) - { - alias vec = .vec!T; - alias load = .load!(T, vec.length); - alias store = .store!(T, vec.length); - - // Given that there are at most as many scalars broadcast as there are - // operations in any `ary[] = ary[] op const op const`, it should always be - // worthwhile to choose vector operations. - if (res.length >= vec.length) - { - mixin(initScalarVecs!Args); - - auto n = res.length / vec.length; - do - { - mixin(vectorExp!Args ~ ";"); - pos += vec.length; - } - while (--n); - } - } - for (; pos < res.length; ++pos) - mixin(scalarExp!Args ~ ";"); - - return res; -} - -private: - -// SIMD helpers - -version (DigitalMars) -{ - import core.simd; - - template vec(T) - { - enum regsz = 16; // SSE2 - enum N = regsz / T.sizeof; - alias vec = __vector(T[N]); - } - - void store(T, size_t N)(T* p, in __vector(T[N]) val) - { - pragma(inline, true); - alias vec = __vector(T[N]); - - static if (is(T == float)) - cast(void) __simd_sto(XMM.STOUPS, *cast(vec*) p, val); - else static if (is(T == double)) - cast(void) __simd_sto(XMM.STOUPD, *cast(vec*) p, val); - else - cast(void) __simd_sto(XMM.STODQU, *cast(vec*) p, val); - } - - const(__vector(T[N])) load(T, size_t N)(in T* p) - { - import core.simd; - - pragma(inline, true); - alias vec = __vector(T[N]); - - static if (is(T == float)) - return __simd(XMM.LODUPS, *cast(const vec*) p); - else static if (is(T == double)) - return __simd(XMM.LODUPD, *cast(const vec*) p); - else - return __simd(XMM.LODDQU, *cast(const vec*) p); - } - - __vector(T[N]) binop(string op, T, size_t N)(in __vector(T[N]) a, in __vector(T[N]) b) - { - pragma(inline, true); - return mixin("a " ~ op ~ " b"); - } - - __vector(T[N]) unaop(string op, T, size_t N)(in __vector(T[N]) a) - if (op[0] == 'u') - { - pragma(inline, true); - return mixin(op[1 .. $] ~ "a"); - } -} - -// mixin gen - -// Check whether operations `ops` are supported for type `T`. Fails with a human-friendly static assert message, if `fail` is true. -template opsSupported(bool fail, T, ops...) if (ops.length > 1) -{ - enum opsSupported = opsSupported!(fail, T, ops[0 .. $ / 2]) - && opsSupported!(fail, T, ops[$ / 2 .. $]); -} - -template opsSupported(bool fail, T, string op) -{ - static if (isUnaryOp(op)) - { - enum opsSupported = is(typeof((T a) => mixin(op[1 .. $] ~ " a"))); - static assert(!fail || opsSupported, - "Unary op `" ~ op[1 .. $] ~ "` not supported for element type " ~ T.stringof ~ "."); - } - else - { - enum opsSupported = is(typeof((T a, T b) => mixin("a " ~ op ~ " b"))); - static assert(!fail || opsSupported, - "Binary op `" ~ op ~ "` not supported for element type " ~ T.stringof ~ "."); - } -} - -// check whether slices have the unqualified element type `E` and scalars are implicitly convertible to `E` -// i.e. filter out things like float[] = float[] / size_t[] -enum compatibleVecTypes(E, T : T[]) = is(Unqual!T == Unqual!E); // array elem types must be same (maybe add cvtpi2ps) -enum compatibleVecTypes(E, T) = is(T : E); // scalar must be convertible to target elem type -enum compatibleVecTypes(E, Types...) = compatibleVecTypes!(E, Types[0 .. $ / 2]) - && compatibleVecTypes!(E, Types[$ / 2 .. $]); - -version (GNU_OR_LDC) -{ - // leave it to the auto-vectorizer - enum vectorizeable(E : E[], Args...) = false; -} -else -{ - // check whether arrayOp is vectorizable - template vectorizeable(E : E[], Args...) - { - static if (is(vec!E)) - enum vectorizeable = opsSupported!(false, vec!E, Filter!(not!isType, Args)) - && compatibleVecTypes!(E, Filter!(isType, Args)); - else - enum vectorizeable = false; - } - - version (X86_64) unittest - { - static assert(vectorizeable!(double[], const(double)[], double[], "+", "=")); - static assert(!vectorizeable!(double[], const(ulong)[], double[], "+", "=")); - } -} - -bool isUnaryOp(string op) -{ - return op[0] == 'u'; -} - -bool isBinaryOp(string op) -{ - if (op == "^^") - return true; - if (op.length != 1) - return false; - switch (op[0]) - { - case '+', '-', '*', '/', '%', '|', '&', '^': - return true; - default: - return false; - } -} - -bool isBinaryAssignOp(string op) -{ - return op.length >= 2 && op[$ - 1] == '=' && isBinaryOp(op[0 .. $ - 1]); -} - -// Generate mixin expression to perform scalar arrayOp loop expression, assumes -// `pos` to be the current slice index, `args` to contain operand values, and -// `res` the target slice. -string scalarExp(Args...)() -{ - string[] stack; - size_t argsIdx; - foreach (i, arg; Args) - { - static if (is(arg == T[], T)) - stack ~= "args[" ~ argsIdx++.toString ~ "][pos]"; - else static if (is(arg)) - stack ~= "args[" ~ argsIdx++.toString ~ "]"; - else static if (isUnaryOp(arg)) - { - auto op = arg[0] == 'u' ? arg[1 .. $] : arg; - stack[$ - 1] = op ~ stack[$ - 1]; - } - else static if (arg == "=") - { - stack[$ - 1] = "res[pos] = cast(T)(" ~ stack[$ - 1] ~ ")"; - } - else static if (isBinaryAssignOp(arg)) - { - stack[$ - 1] = "res[pos] " ~ arg ~ " cast(T)(" ~ stack[$ - 1] ~ ")"; - } - else static if (isBinaryOp(arg)) - { - stack[$ - 2] = "(cast(T)(" ~ stack[$ - 2] ~ " " ~ arg ~ " " ~ stack[$ - 1] ~ "))"; - stack.length -= 1; - } - else - assert(0, "Unexpected op " ~ arg); - } - assert(stack.length == 1); - return stack[0]; -} - -// Generate mixin statement to perform vector loop initialization, assumes -// `args` to contain operand values. -string initScalarVecs(Args...)() -{ - size_t scalarsIdx; - string res; - foreach (aidx, arg; Args) - { - static if (is(arg == T[], T)) - { - } - else static if (is(arg)) - res ~= "immutable vec scalar" ~ scalarsIdx++.toString ~ " = args[" - ~ aidx.toString ~ "];\n"; - } - return res; -} - -// Generate mixin expression to perform vector arrayOp loop expression, assumes -// `pos` to be the current slice index, `args` to contain operand values, and -// `res` the target slice. -string vectorExp(Args...)() -{ - size_t scalarsIdx, argsIdx; - string[] stack; - foreach (i, arg; Args) - { - static if (is(arg == T[], T)) - stack ~= "load(&args[" ~ argsIdx++.toString ~ "][pos])"; - else static if (is(arg)) - { - ++argsIdx; - stack ~= "scalar" ~ scalarsIdx++.toString; - } - else static if (isUnaryOp(arg)) - { - auto op = arg[0] == 'u' ? arg[1 .. $] : arg; - stack[$ - 1] = "unaop!\"" ~ arg ~ "\"(" ~ stack[$ - 1] ~ ")"; - } - else static if (arg == "=") - { - stack[$ - 1] = "store(&res[pos], " ~ stack[$ - 1] ~ ")"; - } - else static if (isBinaryAssignOp(arg)) - { - stack[$ - 1] = "store(&res[pos], binop!\"" ~ arg[0 .. $ - 1] - ~ "\"(load(&res[pos]), " ~ stack[$ - 1] ~ "))"; - } - else static if (isBinaryOp(arg)) - { - stack[$ - 2] = "binop!\"" ~ arg ~ "\"(" ~ stack[$ - 2] ~ ", " ~ stack[$ - 1] ~ ")"; - stack.length -= 1; - } - else - assert(0, "Unexpected op " ~ arg); - } - assert(stack.length == 1); - return stack[0]; -} - -// other helpers - -enum isType(T) = true; -enum isType(alias a) = false; -template not(alias tmlp) -{ - enum not(Args...) = !tmlp!Args; -} - -string toString(size_t num) -{ - import core.internal.string : unsignedToTempString; - - char[20] buf = void; - return unsignedToTempString(num, buf).idup; -} - -bool contains(T)(in T[] ary, in T[] vals...) -{ - foreach (v1; ary) - foreach (v2; vals) - if (v1 == v2) - return true; - return false; -} - -// tests - -version (unittest) template TT(T...) -{ - alias TT = T; -} - -version (unittest) template _arrayOp(Args...) -{ - alias _arrayOp = arrayOp!Args; -} - -unittest -{ - static void check(string op, TA, TB, T, size_t N)(TA a, TB b, in ref T[N] exp) - { - T[N] res; - _arrayOp!(T[], TA, TB, op, "=")(res[], a, b); - foreach (i; 0 .. N) - assert(res[i] == exp[i]); - } - - static void check2(string unaOp, string binOp, TA, TB, T, size_t N)(TA a, TB b, in ref T[N] exp) - { - T[N] res; - _arrayOp!(T[], TA, TB, unaOp, binOp, "=")(res[], a, b); - foreach (i; 0 .. N) - assert(res[i] == exp[i]); - } - - static void test(T, string op, size_t N = 16)(T a, T b, T exp) - { - T[N] va = a, vb = b, vexp = exp; - - check!op(va[], vb[], vexp); - check!op(va[], b, vexp); - check!op(a, vb[], vexp); - } - - static void test2(T, string unaOp, string binOp, size_t N = 16)(T a, T b, T exp) - { - T[N] va = a, vb = b, vexp = exp; - - check2!(unaOp, binOp)(va[], vb[], vexp); - check2!(unaOp, binOp)(va[], b, vexp); - check2!(unaOp, binOp)(a, vb[], vexp); - } - - alias UINTS = TT!(ubyte, ushort, uint, ulong); - alias INTS = TT!(byte, short, int, long); - alias FLOATS = TT!(float, double); - - foreach (T; TT!(UINTS, INTS, FLOATS)) - { - test!(T, "+")(1, 2, 3); - test!(T, "-")(3, 2, 1); - static if (__traits(compiles, { import std.math; })) - test!(T, "^^")(2, 3, 8); - - test2!(T, "u-", "+")(3, 2, 1); - } - - foreach (T; TT!(UINTS, INTS)) - { - test!(T, "|")(1, 2, 3); - test!(T, "&")(3, 1, 1); - test!(T, "^")(3, 1, 2); - - test2!(T, "u~", "+")(3, cast(T)~2, 5); - } - - foreach (T; TT!(INTS, FLOATS)) - { - test!(T, "-")(1, 2, -1); - test2!(T, "u-", "+")(-3, -2, -1); - test2!(T, "u-", "*")(-3, -2, -6); - } - - foreach (T; TT!(UINTS, INTS, FLOATS)) - { - test!(T, "*")(2, 3, 6); - test!(T, "/")(8, 4, 2); - test!(T, "%")(8, 6, 2); - } -} - -// test handling of v op= exp -unittest -{ - uint[32] c; - arrayOp!(uint[], uint, "+=")(c[], 2); - foreach (v; c) - assert(v == 2); - static if (__traits(compiles, { import std.math; })) - { - arrayOp!(uint[], uint, "^^=")(c[], 3); - foreach (v; c) - assert(v == 8); - } -} - -// proper error message for UDT lacking certain ops -unittest -{ - static assert(!is(typeof(&arrayOp!(int[4][], int[4], "+=")))); - static assert(!is(typeof(&arrayOp!(int[4][], int[4], "u-", "=")))); - - static struct S - { - } - - static assert(!is(typeof(&arrayOp!(S[], S, "+=")))); - static assert(!is(typeof(&arrayOp!(S[], S[], "*", S, "+=")))); - static struct S2 - { - S2 opBinary(string op)(in S2) @nogc pure nothrow - { - return this; - } - - ref S2 opOpAssign(string op)(in S2) @nogc pure nothrow - { - return this; - } - } - - static assert(is(typeof(&arrayOp!(S2[], S2[], S2[], S2, "*", "+", "=")))); - static assert(is(typeof(&arrayOp!(S2[], S2[], S2, "*", "+=")))); -} diff --git a/libphobos/libdruntime/core/internal/atomic.d b/libphobos/libdruntime/core/internal/atomic.d new file mode 100644 index 0000000..3036ea7 --- /dev/null +++ b/libphobos/libdruntime/core/internal/atomic.d @@ -0,0 +1,1141 @@ +/** +* The core.internal.atomic module comtains the low-level atomic features available in hardware. +* This module may be a routing layer for compiler intrinsics. +* +* Copyright: Copyright Manu Evans 2019. +* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) +* Authors: Sean Kelly, Alex Rønne Petersen, Manu Evans +* Source: $(DRUNTIMESRC core/internal/_atomic.d) +*/ + +module core.internal.atomic; + +import core.atomic : MemoryOrder, has128BitCAS; + +version (DigitalMars) +{ + private + { + enum : int + { + AX, BX, CX, DX, DI, SI, R8, R9 + } + + immutable string[4][8] registerNames = [ + [ "AL", "AX", "EAX", "RAX" ], + [ "BL", "BX", "EBX", "RBX" ], + [ "CL", "CX", "ECX", "RCX" ], + [ "DL", "DX", "EDX", "RDX" ], + [ "DIL", "DI", "EDI", "RDI" ], + [ "SIL", "SI", "ESI", "RSI" ], + [ "R8B", "R8W", "R8D", "R8" ], + [ "R9B", "R9W", "R9D", "R9" ], + ]; + + template RegIndex(T) + { + static if (T.sizeof == 1) + enum RegIndex = 0; + else static if (T.sizeof == 2) + enum RegIndex = 1; + else static if (T.sizeof == 4) + enum RegIndex = 2; + else static if (T.sizeof == 8) + enum RegIndex = 3; + else + static assert(false, "Invalid type"); + } + + enum SizedReg(int reg, T = size_t) = registerNames[reg][RegIndex!T]; + } + + inout(T) atomicLoad(MemoryOrder order = MemoryOrder.seq, T)(inout(T)* src) pure nothrow @nogc @trusted + if (CanCAS!T) + { + static assert(order != MemoryOrder.rel, "invalid MemoryOrder for atomicLoad()"); + + static if (T.sizeof == size_t.sizeof * 2) + { + version (D_InlineAsm_X86) + { + asm pure nothrow @nogc @trusted + { + push EDI; + push EBX; + mov EBX, 0; + mov ECX, 0; + mov EAX, 0; + mov EDX, 0; + mov EDI, src; + lock; cmpxchg8b [EDI]; + pop EBX; + pop EDI; + } + } + else version (D_InlineAsm_X86_64) + { + version (Windows) + { + static if (RegisterReturn!T) + { + enum SrcPtr = SizedReg!CX; + enum RetPtr = null; + } + else + { + enum SrcPtr = SizedReg!DX; + enum RetPtr = SizedReg!CX; + } + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov R8, %0; + ?1 mov R9, %1; + mov RBX, 0; + mov RCX, 0; + mov RAX, 0; + mov RDX, 0; + lock; cmpxchg16b [R8]; + ?1 mov [R9], RAX; + ?1 mov 8[R9], RDX; + pop RBX; + ret; + } + }, SrcPtr, RetPtr)); + } + else + { + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov RBX, 0; + mov RCX, 0; + mov RAX, 0; + mov RDX, 0; + lock; cmpxchg16b [RDI]; + pop RBX; + ret; + } + } + } + } + else static if (needsLoadBarrier!order) + { + version (D_InlineAsm_X86) + { + enum SrcReg = SizedReg!CX; + enum ZeroReg = SizedReg!(DX, T); + enum ResReg = SizedReg!(AX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + mov %1, 0; + mov %2, 0; + mov %0, src; + lock; cmpxchg [%0], %1; + } + }, SrcReg, ZeroReg, ResReg)); + } + else version (D_InlineAsm_X86_64) + { + version (Windows) + enum SrcReg = SizedReg!CX; + else + enum SrcReg = SizedReg!DI; + enum ZeroReg = SizedReg!(DX, T); + enum ResReg = SizedReg!(AX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + naked; + mov %1, 0; + mov %2, 0; + lock; cmpxchg [%0], %1; + ret; + } + }, SrcReg, ZeroReg, ResReg)); + } + } + else + return *src; + } + + void atomicStore(MemoryOrder order = MemoryOrder.seq, T)(T* dest, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + static assert(order != MemoryOrder.acq, "Invalid MemoryOrder for atomicStore()"); + + static if (T.sizeof == size_t.sizeof * 2) + { + version (D_InlineAsm_X86) + { + asm pure nothrow @nogc @trusted + { + push EDI; + push EBX; + lea EDI, value; + mov EBX, [EDI]; + mov ECX, 4[EDI]; + mov EDI, dest; + mov EAX, [EDI]; + mov EDX, 4[EDI]; + L1: lock; cmpxchg8b [EDI]; + jne L1; + pop EBX; + pop EDI; + } + } + else version (D_InlineAsm_X86_64) + { + version (Windows) + { + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov R8, RDX; + mov RAX, [RDX]; + mov RDX, 8[RDX]; + mov RBX, [RCX]; + mov RCX, 8[RCX]; + L1: lock; cmpxchg16b [R8]; + jne L1; + pop RBX; + ret; + } + } + else + { + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov RBX, RDI; + mov RCX, RSI; + mov RDI, RDX; + mov RAX, [RDX]; + mov RDX, 8[RDX]; + L1: lock; cmpxchg16b [RDI]; + jne L1; + pop RBX; + ret; + } + } + } + } + else static if (needsStoreBarrier!order) + atomicExchange!(order, false)(dest, value); + else + *dest = value; + } + + T atomicFetchAdd(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) pure nothrow @nogc @trusted + if (is(T : ulong)) + { + version (D_InlineAsm_X86) + { + static assert(T.sizeof <= 4, "64bit atomicFetchAdd not supported on 32bit target." ); + + enum DestReg = SizedReg!DX; + enum ValReg = SizedReg!(AX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, dest; + lock; xadd[%0], %1; + } + }, DestReg, ValReg)); + } + else version (D_InlineAsm_X86_64) + { + version (Windows) + { + enum DestReg = SizedReg!DX; + enum ValReg = SizedReg!(CX, T); + } + else + { + enum DestReg = SizedReg!SI; + enum ValReg = SizedReg!(DI, T); + } + enum ResReg = result ? SizedReg!(AX, T) : null; + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + naked; + lock; xadd[%0], %1; + ?2 mov %2, %1; + ret; + } + }, DestReg, ValReg, ResReg)); + } + else + static assert (false, "Unsupported architecture."); + } + + T atomicFetchSub(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) pure nothrow @nogc @trusted + if (is(T : ulong)) + { + return atomicFetchAdd(dest, cast(T)-cast(IntOrLong!T)value); + } + + T atomicExchange(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + version (D_InlineAsm_X86) + { + static assert(T.sizeof <= 4, "64bit atomicExchange not supported on 32bit target." ); + + enum DestReg = SizedReg!CX; + enum ValReg = SizedReg!(AX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, dest; + xchg [%0], %1; + } + }, DestReg, ValReg)); + } + else version (D_InlineAsm_X86_64) + { + version (Windows) + { + enum DestReg = SizedReg!DX; + enum ValReg = SizedReg!(CX, T); + } + else + { + enum DestReg = SizedReg!SI; + enum ValReg = SizedReg!(DI, T); + } + enum ResReg = result ? SizedReg!(AX, T) : null; + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + naked; + xchg [%0], %1; + ?2 mov %2, %1; + ret; + } + }, DestReg, ValReg, ResReg)); + } + else + static assert (false, "Unsupported architecture."); + } + + alias atomicCompareExchangeWeak = atomicCompareExchangeStrong; + + bool atomicCompareExchangeStrong(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T)(T* dest, T* compare, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + version (D_InlineAsm_X86) + { + static if (T.sizeof <= 4) + { + enum DestAddr = SizedReg!CX; + enum CmpAddr = SizedReg!DI; + enum Val = SizedReg!(DX, T); + enum Cmp = SizedReg!(AX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + push %1; + mov %2, value; + mov %1, compare; + mov %3, [%1]; + mov %0, dest; + lock; cmpxchg [%0], %2; + mov [%1], %3; + setz AL; + pop %1; + } + }, DestAddr, CmpAddr, Val, Cmp)); + } + else static if (T.sizeof == 8) + { + asm pure nothrow @nogc @trusted + { + push EDI; + push EBX; + lea EDI, value; + mov EBX, [EDI]; + mov ECX, 4[EDI]; + mov EDI, compare; + mov EAX, [EDI]; + mov EDX, 4[EDI]; + mov EDI, dest; + lock; cmpxchg8b [EDI]; + mov EDI, compare; + mov [EDI], EAX; + mov 4[EDI], EDX; + setz AL; + pop EBX; + pop EDI; + } + } + else + static assert(T.sizeof <= 8, "128bit atomicCompareExchangeStrong not supported on 32bit target." ); + } + else version (D_InlineAsm_X86_64) + { + static if (T.sizeof <= 8) + { + version (Windows) + { + enum DestAddr = SizedReg!R8; + enum CmpAddr = SizedReg!DX; + enum Val = SizedReg!(CX, T); + } + else + { + enum DestAddr = SizedReg!DX; + enum CmpAddr = SizedReg!SI; + enum Val = SizedReg!(DI, T); + } + enum Res = SizedReg!(AX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + naked; + mov %3, [%1]; + lock; cmpxchg [%0], %2; + jne compare_fail; + mov AL, 1; + ret; + compare_fail: + mov [%1], %3; + xor AL, AL; + ret; + } + }, DestAddr, CmpAddr, Val, Res)); + } + else + { + version (Windows) + { + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov R9, RDX; + mov RAX, [RDX]; + mov RDX, 8[RDX]; + mov RBX, [RCX]; + mov RCX, 8[RCX]; + lock; cmpxchg16b [R8]; + pop RBX; + jne compare_fail; + mov AL, 1; + ret; + compare_fail: + mov [R9], RAX; + mov 8[R9], RDX; + xor AL, AL; + ret; + } + } + else + { + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov R8, RCX; + mov R9, RDX; + mov RAX, [RDX]; + mov RDX, 8[RDX]; + mov RBX, RDI; + mov RCX, RSI; + lock; cmpxchg16b [R8]; + pop RBX; + jne compare_fail; + mov AL, 1; + ret; + compare_fail: + mov [R9], RAX; + mov 8[R9], RDX; + xor AL, AL; + ret; + } + } + } + } + else + static assert (false, "Unsupported architecture."); + } + + alias atomicCompareExchangeWeakNoResult = atomicCompareExchangeStrongNoResult; + + bool atomicCompareExchangeStrongNoResult(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T)(T* dest, const T compare, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + version (D_InlineAsm_X86) + { + static if (T.sizeof <= 4) + { + enum DestAddr = SizedReg!CX; + enum Cmp = SizedReg!(AX, T); + enum Val = SizedReg!(DX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + mov %2, value; + mov %1, compare; + mov %0, dest; + lock; cmpxchg [%0], %2; + setz AL; + } + }, DestAddr, Cmp, Val)); + } + else static if (T.sizeof == 8) + { + asm pure nothrow @nogc @trusted + { + push EDI; + push EBX; + lea EDI, value; + mov EBX, [EDI]; + mov ECX, 4[EDI]; + lea EDI, compare; + mov EAX, [EDI]; + mov EDX, 4[EDI]; + mov EDI, dest; + lock; cmpxchg8b [EDI]; + setz AL; + pop EBX; + pop EDI; + } + } + else + static assert(T.sizeof <= 8, "128bit atomicCompareExchangeStrong not supported on 32bit target." ); + } + else version (D_InlineAsm_X86_64) + { + static if (T.sizeof <= 8) + { + version (Windows) + { + enum DestAddr = SizedReg!R8; + enum Cmp = SizedReg!(DX, T); + enum Val = SizedReg!(CX, T); + } + else + { + enum DestAddr = SizedReg!DX; + enum Cmp = SizedReg!(SI, T); + enum Val = SizedReg!(DI, T); + } + enum AXReg = SizedReg!(AX, T); + + mixin (simpleFormat(q{ + asm pure nothrow @nogc @trusted + { + naked; + mov %3, %1; + lock; cmpxchg [%0], %2; + setz AL; + ret; + } + }, DestAddr, Cmp, Val, AXReg)); + } + else + { + version (Windows) + { + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov RAX, [RDX]; + mov RDX, 8[RDX]; + mov RBX, [RCX]; + mov RCX, 8[RCX]; + lock; cmpxchg16b [R8]; + setz AL; + pop RBX; + ret; + } + } + else + { + asm pure nothrow @nogc @trusted + { + naked; + push RBX; + mov RAX, RDX; + mov RDX, RCX; + mov RBX, RDI; + mov RCX, RSI; + lock; cmpxchg16b [R8]; + setz AL; + pop RBX; + ret; + } + } + } + } + else + static assert (false, "Unsupported architecture."); + } + + void atomicFence(MemoryOrder order = MemoryOrder.seq)() pure nothrow @nogc @trusted + { + // TODO: `mfence` should only be required for seq_cst operations, but this depends on + // the compiler's backend knowledge to not reorder code inappropriately, + // so we'll apply it conservatively. + static if (order != MemoryOrder.raw) + { + version (D_InlineAsm_X86) + { + import core.cpuid; + + // TODO: review this implementation; it seems way overly complicated + asm pure nothrow @nogc @trusted + { + naked; + + call sse2; + test AL, AL; + jne Lcpuid; + + // Fast path: We have SSE2, so just use mfence. + mfence; + jmp Lend; + + Lcpuid: + + // Slow path: We use cpuid to serialize. This is + // significantly slower than mfence, but is the + // only serialization facility we have available + // on older non-SSE2 chips. + push EBX; + + mov EAX, 0; + cpuid; + + pop EBX; + + Lend: + + ret; + } + } + else version (D_InlineAsm_X86_64) + { + asm pure nothrow @nogc @trusted + { + naked; + mfence; + ret; + } + } + else + static assert (false, "Unsupported architecture."); + } + } + + void pause() pure nothrow @nogc @trusted + { + version (D_InlineAsm_X86) + { + asm pure nothrow @nogc @trusted + { + naked; + rep; nop; + ret; + } + } + else version (D_InlineAsm_X86_64) + { + asm pure nothrow @nogc @trusted + { + naked; + // pause; // TODO: DMD should add this opcode to its inline asm + rep; nop; + ret; + } + } + else + { + // ARM should `yield` + // other architectures? otherwise some sort of nop... + } + } +} +else version (GNU) +{ + import gcc.builtins; + import gcc.config; + + inout(T) atomicLoad(MemoryOrder order = MemoryOrder.seq, T)(inout(T)* src) pure nothrow @nogc @trusted + if (CanCAS!T) + { + static assert(order != MemoryOrder.rel, "invalid MemoryOrder for atomicLoad()"); + + static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + { + static if (T.sizeof == ubyte.sizeof) + { + ubyte value = __atomic_load_1(cast(shared)src, order); + return *cast(typeof(return)*)&value; + } + else static if (T.sizeof == ushort.sizeof) + { + ushort value = __atomic_load_2(cast(shared)src, order); + return *cast(typeof(return)*)&value; + } + else static if (T.sizeof == uint.sizeof) + { + uint value = __atomic_load_4(cast(shared)src, order); + return *cast(typeof(return)*)&value; + } + else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) + { + ulong value = __atomic_load_8(cast(shared)src, order); + return *cast(typeof(return)*)&value; + } + else static if (GNU_Have_LibAtomic) + { + T value; + __atomic_load(T.sizeof, cast(shared)src, &value, order); + return *cast(typeof(return)*)&value; + } + else + static assert(0, "Invalid template type specified."); + } + else + { + getAtomicMutex.lock(); + scope(exit) getAtomicMutex.unlock(); + return *cast(typeof(return)*)&src; + } + } + + void atomicStore(MemoryOrder order = MemoryOrder.seq, T)(T* dest, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + static assert(order != MemoryOrder.acq, "Invalid MemoryOrder for atomicStore()"); + + static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + { + static if (T.sizeof == ubyte.sizeof) + __atomic_store_1(cast(shared)dest, *cast(ubyte*)&value, order); + else static if (T.sizeof == ushort.sizeof) + __atomic_store_2(cast(shared)dest, *cast(ushort*)&value, order); + else static if (T.sizeof == uint.sizeof) + __atomic_store_4(cast(shared)dest, *cast(uint*)&value, order); + else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) + __atomic_store_8(cast(shared)dest, *cast(ulong*)&value, order); + else static if (GNU_Have_LibAtomic) + __atomic_store(T.sizeof, cast(shared)dest, cast(void*)&value, order); + else + static assert(0, "Invalid template type specified."); + } + else + { + getAtomicMutex.lock(); + *dest = value; + getAtomicMutex.unlock(); + } + } + + T atomicFetchAdd(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) pure nothrow @nogc @trusted + if (is(T : ulong)) + { + static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + { + static if (T.sizeof == ubyte.sizeof) + return __atomic_fetch_add_1(cast(shared)dest, value, order); + else static if (T.sizeof == ushort.sizeof) + return __atomic_fetch_add_2(cast(shared)dest, value, order); + else static if (T.sizeof == uint.sizeof) + return __atomic_fetch_add_4(cast(shared)dest, value, order); + else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) + return __atomic_fetch_add_8(cast(shared)dest, value, order); + else static if (GNU_Have_LibAtomic) + return __atomic_fetch_add(T.sizeof, cast(shared)dest, cast(void*)&value, order); + else + static assert(0, "Invalid template type specified."); + } + else + { + getAtomicMutex.lock(); + scope(exit) getAtomicMutex.unlock(); + T tmp = *dest; + *dest += value; + return tmp; + } + } + + T atomicFetchSub(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) pure nothrow @nogc @trusted + if (is(T : ulong)) + { + static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + { + static if (T.sizeof == ubyte.sizeof) + return __atomic_fetch_sub_1(cast(shared)dest, value, order); + else static if (T.sizeof == ushort.sizeof) + return __atomic_fetch_sub_2(cast(shared)dest, value, order); + else static if (T.sizeof == uint.sizeof) + return __atomic_fetch_sub_4(cast(shared)dest, value, order); + else static if (T.sizeof == ulong.sizeof && GNU_Have_64Bit_Atomics) + return __atomic_fetch_sub_8(cast(shared)dest, value, order); + else static if (GNU_Have_LibAtomic) + return __atomic_fetch_sub(T.sizeof, cast(shared)dest, cast(void*)&value, order); + else + static assert(0, "Invalid template type specified."); + } + else + { + getAtomicMutex.lock(); + scope(exit) getAtomicMutex.unlock(); + T tmp = *dest; + *dest -= value; + return tmp; + } + } + + T atomicExchange(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) pure nothrow @nogc @trusted + if (is(T : ulong) || is(T == class) || is(T == interface) || is(T U : U*)) + { + static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + { + static if (T.sizeof == byte.sizeof) + { + ubyte res = __atomic_exchange_1(cast(shared)dest, *cast(ubyte*)&value, order); + return *cast(typeof(return)*)&res; + } + else static if (T.sizeof == short.sizeof) + { + ushort res = __atomic_exchange_2(cast(shared)dest, *cast(ushort*)&value, order); + return *cast(typeof(return)*)&res; + } + else static if (T.sizeof == int.sizeof) + { + uint res = __atomic_exchange_4(cast(shared)dest, *cast(uint*)&value, order); + return *cast(typeof(return)*)&res; + } + else static if (T.sizeof == long.sizeof && GNU_Have_64Bit_Atomics) + { + ulong res = __atomic_exchange_8(cast(shared)dest, *cast(ulong*)&value, order); + return *cast(typeof(return)*)&res; + } + else static if (GNU_Have_LibAtomic) + { + T res = void; + __atomic_exchange(T.sizeof, cast(shared)dest, cast(void*)&value, &res, order); + return res; + } + else + static assert(0, "Invalid template type specified."); + } + else + { + getAtomicMutex.lock(); + scope(exit) getAtomicMutex.unlock(); + + T res = *dest; + *dest = value; + return res; + } + } + + bool atomicCompareExchangeWeak(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T)(T* dest, T* compare, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + return atomicCompareExchangeImpl!(succ, fail, true)(dest, compare, value); + } + + bool atomicCompareExchangeStrong(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T)(T* dest, T* compare, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + return atomicCompareExchangeImpl!(succ, fail, false)(dest, compare, value); + } + + bool atomicCompareExchangeStrongNoResult(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T)(T* dest, const T compare, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + return atomicCompareExchangeImpl!(succ, fail, false)(dest, cast(T*)&compare, value); + } + + bool atomicCompareExchangeWeakNoResult(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T)(T* dest, const T compare, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + return atomicCompareExchangeImpl!(succ, fail, true)(dest, cast(T*)&compare, value); + } + + private bool atomicCompareExchangeImpl(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, bool weak, T)(T* dest, T* compare, T value) pure nothrow @nogc @trusted + if (CanCAS!T) + { + bool res = void; + + static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + { + static if (T.sizeof == byte.sizeof) + res = __atomic_compare_exchange_1(cast(shared)dest, compare, *cast(ubyte*)&value, + weak, succ, fail); + else static if (T.sizeof == short.sizeof) + res = __atomic_compare_exchange_2(cast(shared)dest, compare, *cast(ushort*)&value, + weak, succ, fail); + else static if (T.sizeof == int.sizeof) + res = __atomic_compare_exchange_4(cast(shared)dest, compare, *cast(uint*)&value, + weak, succ, fail); + else static if (T.sizeof == long.sizeof && GNU_Have_64Bit_Atomics) + res = __atomic_compare_exchange_8(cast(shared)dest, compare, *cast(ulong*)&value, + weak, succ, fail); + else static if (GNU_Have_LibAtomic) + res = __atomic_compare_exchange(T.sizeof, cast(shared)dest, compare, cast(void*)&value, + succ, fail); + else + static assert(0, "Invalid template type specified."); + } + else + { + static if (T.sizeof == byte.sizeof) + alias U = byte; + else static if (T.sizeof == short.sizeof) + alias U = short; + else static if (T.sizeof == int.sizeof) + alias U = int; + else static if (T.sizeof == long.sizeof) + alias U = long; + else + static assert(0, "Invalid template type specified."); + + getAtomicMutex.lock(); + scope(exit) getAtomicMutex.unlock(); + + if (*cast(U*)dest == *cast(U*)&compare) + { + *dest = value; + res = true; + } + else + { + *compare = *dest; + res = false; + } + } + + return res; + } + + void atomicFence(MemoryOrder order = MemoryOrder.seq)() pure nothrow @nogc @trusted + { + static if (GNU_Have_Atomics || GNU_Have_LibAtomic) + __atomic_thread_fence(order); + else + { + getAtomicMutex.lock(); + getAtomicMutex.unlock(); + } + } + + void pause() pure nothrow @nogc @trusted + { + version (X86) + { + __builtin_ia32_pause(); + } + else version (X86_64) + { + __builtin_ia32_pause(); + } + else + { + // Other architectures? Some sort of nop or barrier. + } + } + + static if (!GNU_Have_Atomics && !GNU_Have_LibAtomic) + { + // Use system mutex for atomics, faking the purity of the functions so + // that they can be used in pure/nothrow/@safe code. + extern (C) private pure @trusted @nogc nothrow + { + static if (GNU_Thread_Model == ThreadModel.Posix) + { + import core.sys.posix.pthread; + alias atomicMutexHandle = pthread_mutex_t; + + pragma(mangle, "pthread_mutex_init") int fakePureMutexInit(pthread_mutex_t*, pthread_mutexattr_t*); + pragma(mangle, "pthread_mutex_lock") int fakePureMutexLock(pthread_mutex_t*); + pragma(mangle, "pthread_mutex_unlock") int fakePureMutexUnlock(pthread_mutex_t*); + } + else static if (GNU_Thread_Model == ThreadModel.Win32) + { + import core.sys.windows.winbase; + alias atomicMutexHandle = CRITICAL_SECTION; + + pragma(mangle, "InitializeCriticalSection") int fakePureMutexInit(CRITICAL_SECTION*); + pragma(mangle, "EnterCriticalSection") void fakePureMutexLock(CRITICAL_SECTION*); + pragma(mangle, "LeaveCriticalSection") int fakePureMutexUnlock(CRITICAL_SECTION*); + } + else + { + alias atomicMutexHandle = int; + } + } + + // Implements lock/unlock operations. + private struct AtomicMutex + { + int lock() pure @trusted @nogc nothrow + { + static if (GNU_Thread_Model == ThreadModel.Posix) + { + if (!_inited) + { + fakePureMutexInit(&_handle, null); + _inited = true; + } + return fakePureMutexLock(&_handle); + } + else + { + static if (GNU_Thread_Model == ThreadModel.Win32) + { + if (!_inited) + { + fakePureMutexInit(&_handle); + _inited = true; + } + fakePureMutexLock(&_handle); + } + return 0; + } + } + + int unlock() pure @trusted @nogc nothrow + { + static if (GNU_Thread_Model == ThreadModel.Posix) + return fakePureMutexUnlock(&_handle); + else + { + static if (GNU_Thread_Model == ThreadModel.Win32) + fakePureMutexUnlock(&_handle); + return 0; + } + } + + private: + atomicMutexHandle _handle; + bool _inited; + } + + // Internal static mutex reference. + private AtomicMutex* _getAtomicMutex() @trusted @nogc nothrow + { + __gshared static AtomicMutex mutex; + return &mutex; + } + + // Pure alias for _getAtomicMutex. + pragma(mangle, _getAtomicMutex.mangleof) + private AtomicMutex* getAtomicMutex() pure @trusted @nogc nothrow @property; + } +} + +private: + +version (Windows) +{ + enum RegisterReturn(T) = is(T : U[], U) || is(T : R delegate(A), R, A...); +} + +enum CanCAS(T) = is(T : ulong) || + is(T == class) || + is(T == interface) || + is(T : U*, U) || + is(T : U[], U) || + is(T : R delegate(A), R, A...) || + (is(T == struct) && __traits(isPOD, T) && + (T.sizeof <= size_t.sizeof*2 || // no more than 2 words + (T.sizeof == 16 && has128BitCAS)) && // or supports 128-bit CAS + (T.sizeof & (T.sizeof - 1)) == 0 // is power of 2 + ); + +template IntOrLong(T) +{ + static if (T.sizeof > 4) + alias IntOrLong = long; + else + alias IntOrLong = int; +} + +// NOTE: x86 loads implicitly have acquire semantics so a memory +// barrier is only necessary on releases. +template needsLoadBarrier( MemoryOrder ms ) +{ + enum bool needsLoadBarrier = ms == MemoryOrder.seq; +} + + +// NOTE: x86 stores implicitly have release semantics so a memory +// barrier is only necessary on acquires. +template needsStoreBarrier( MemoryOrder ms ) +{ + enum bool needsStoreBarrier = ms == MemoryOrder.seq; +} + +// this is a helper to build asm blocks +string simpleFormat(string format, string[] args...) +{ + string result; + outer: while (format.length) + { + foreach (i; 0 .. format.length) + { + if (format[i] == '%' || format[i] == '?') + { + bool isQ = format[i] == '?'; + result ~= format[0 .. i++]; + assert (i < format.length, "Invalid format string"); + if (format[i] == '%' || format[i] == '?') + { + assert(!isQ, "Invalid format string"); + result ~= format[i++]; + } + else + { + int index = 0; + assert (format[i] >= '0' && format[i] <= '9', "Invalid format string"); + while (i < format.length && format[i] >= '0' && format[i] <= '9') + index = index * 10 + (ubyte(format[i++]) - ubyte('0')); + if (!isQ) + result ~= args[index]; + else if (!args[index]) + { + size_t j = i; + for (; j < format.length;) + { + if (format[j++] == '\n') + break; + } + i = j; + } + } + format = format[i .. $]; + continue outer; + } + } + result ~= format; + break; + } + return result; +} diff --git a/libphobos/libdruntime/core/internal/container/array.d b/libphobos/libdruntime/core/internal/container/array.d new file mode 100644 index 0000000..27292cd --- /dev/null +++ b/libphobos/libdruntime/core/internal/container/array.d @@ -0,0 +1,232 @@ +/** + * Array container for internal usage. + * + * Copyright: Copyright Martin Nowak 2013. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak + */ +module core.internal.container.array; + +static import common = core.internal.container.common; + +import core.exception : onOutOfMemoryErrorNoGC; + +struct Array(T) +{ +nothrow: + @disable this(this); + + ~this() + { + reset(); + } + + void reset() + { + length = 0; + } + + @property size_t length() const + { + return _length; + } + + @property void length(size_t nlength) + { + import core.checkedint : mulu; + + bool overflow = false; + size_t reqsize = mulu(T.sizeof, nlength, overflow); + if (!overflow) + { + if (nlength < _length) + foreach (ref val; _ptr[nlength .. _length]) common.destroy(val); + _ptr = cast(T*)common.xrealloc(_ptr, reqsize); + if (nlength > _length) + foreach (ref val; _ptr[_length .. nlength]) common.initialize(val); + _length = nlength; + } + else + onOutOfMemoryErrorNoGC(); + + } + + @property bool empty() const + { + return !length; + } + + @property ref inout(T) front() inout + in { assert(!empty); } + do + { + return _ptr[0]; + } + + @property ref inout(T) back() inout + in { assert(!empty); } + do + { + return _ptr[_length - 1]; + } + + ref inout(T) opIndex(size_t idx) inout + in { assert(idx < length); } + do + { + return _ptr[idx]; + } + + inout(T)[] opSlice() inout + { + return _ptr[0 .. _length]; + } + + inout(T)[] opSlice(size_t a, size_t b) inout + in { assert(a < b && b <= length); } + do + { + return _ptr[a .. b]; + } + + alias length opDollar; + + void insertBack()(auto ref T val) + { + import core.checkedint : addu; + + bool overflow = false; + size_t newlength = addu(length, 1, overflow); + if (!overflow) + { + length = newlength; + back = val; + } + else + onOutOfMemoryErrorNoGC(); + } + + void popBack() + { + length = length - 1; + } + + void remove(size_t idx) + in { assert(idx < length); } + do + { + foreach (i; idx .. length - 1) + _ptr[i] = _ptr[i+1]; + popBack(); + } + + void swap(ref Array other) + { + auto ptr = _ptr; + _ptr = other._ptr; + other._ptr = ptr; + immutable len = _length; + _length = other._length; + other._length = len; + } + + invariant + { + assert(!_ptr == !_length); + } + +private: + T* _ptr; + size_t _length; +} + +unittest +{ + Array!size_t ary; + + assert(ary[] == []); + ary.insertBack(5); + assert(ary[] == [5]); + assert(ary[$-1] == 5); + ary.popBack(); + assert(ary[] == []); + ary.insertBack(0); + ary.insertBack(1); + assert(ary[] == [0, 1]); + assert(ary[0 .. 1] == [0]); + assert(ary[1 .. 2] == [1]); + assert(ary[$ - 2 .. $] == [0, 1]); + size_t idx; + foreach (val; ary) assert(idx++ == val); + foreach_reverse (val; ary) assert(--idx == val); + foreach (i, val; ary) assert(i == val); + foreach_reverse (i, val; ary) assert(i == val); + + ary.insertBack(2); + ary.remove(1); + assert(ary[] == [0, 2]); + + assert(!ary.empty); + ary.reset(); + assert(ary.empty); + ary.insertBack(0); + assert(!ary.empty); + destroy(ary); + assert(ary.empty); + + // not copyable + static assert(!__traits(compiles, { Array!size_t ary2 = ary; })); + Array!size_t ary2; + static assert(!__traits(compiles, ary = ary2)); + static void foo(Array!size_t copy) {} + static assert(!__traits(compiles, foo(ary))); + + ary2.insertBack(0); + assert(ary.empty); + assert(ary2[] == [0]); + ary.swap(ary2); + assert(ary[] == [0]); + assert(ary2.empty); +} + +unittest +{ + alias RC = common.RC!(); + Array!RC ary; + + size_t cnt; + assert(cnt == 0); + ary.insertBack(RC(&cnt)); + assert(cnt == 1); + ary.insertBack(RC(&cnt)); + assert(cnt == 2); + ary.back = ary.front; + assert(cnt == 2); + ary.popBack(); + assert(cnt == 1); + ary.popBack(); + assert(cnt == 0); +} + +unittest +{ + import core.exception; + try + { + // Overflow ary.length. + auto ary = Array!size_t(cast(size_t*)0xdeadbeef, -1); + ary.insertBack(0); + } + catch (OutOfMemoryError) + { + } + try + { + // Overflow requested memory size for common.xrealloc(). + auto ary = Array!size_t(cast(size_t*)0xdeadbeef, -2); + ary.insertBack(0); + } + catch (OutOfMemoryError) + { + } +} diff --git a/libphobos/libdruntime/core/internal/container/common.d b/libphobos/libdruntime/core/internal/container/common.d new file mode 100644 index 0000000..582d63b --- /dev/null +++ b/libphobos/libdruntime/core/internal/container/common.d @@ -0,0 +1,63 @@ +/** + * Common code for writing containers. + * + * Copyright: Copyright Martin Nowak 2013. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak + */ +module core.internal.container.common; + +import core.stdc.stdlib : malloc, realloc; +public import core.stdc.stdlib : free; +import core.internal.traits : dtorIsNothrow; +nothrow: + +void* xrealloc(void* ptr, size_t sz) nothrow @nogc +{ + import core.exception; + + if (!sz) { .free(ptr); return null; } + if (auto nptr = .realloc(ptr, sz)) return nptr; + .free(ptr); onOutOfMemoryErrorNoGC(); + assert(0); +} + +void* xmalloc(size_t sz) nothrow @nogc +{ + import core.exception; + if (auto nptr = .malloc(sz)) + return nptr; + onOutOfMemoryErrorNoGC(); + assert(0); +} + +void destroy(T)(ref T t) if (is(T == struct) && dtorIsNothrow!T) +{ + scope (failure) assert(0); // nothrow hack + object.destroy(t); +} + +void destroy(T)(ref T t) if (!is(T == struct)) +{ + t = T.init; +} + +void initialize(T)(ref T t) if (is(T == struct)) +{ + import core.internal.lifetime : emplaceInitializer; + emplaceInitializer(t); +} + +void initialize(T)(ref T t) if (!is(T == struct)) +{ + t = T.init; +} + +version (CoreUnittest) struct RC() +{ +nothrow: + this(size_t* cnt) { ++*(_cnt = cnt); } + ~this() { if (_cnt) --*_cnt; } + this(this) { if (_cnt) ++*_cnt; } + size_t* _cnt; +} diff --git a/libphobos/libdruntime/core/internal/container/hashtab.d b/libphobos/libdruntime/core/internal/container/hashtab.d new file mode 100644 index 0000000..5e91193 --- /dev/null +++ b/libphobos/libdruntime/core/internal/container/hashtab.d @@ -0,0 +1,330 @@ +/** + * HashTab container for internal usage. + * + * Copyright: Copyright Martin Nowak 2013. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak + */ +module core.internal.container.hashtab; + +import core.internal.container.array; +static import common = core.internal.container.common; + +struct HashTab(Key, Value) +{ + static struct Node + { + Key _key; + Value _value; + Node* _next; + } + + @disable this(this); + + ~this() + { + reset(); + } + + void reset() + { + foreach (p; _buckets) + { + while (p !is null) + { + auto pn = p._next; + common.destroy(*p); + common.free(p); + p = pn; + } + } + _buckets.reset(); + _length = 0; + } + + @property size_t length() const + { + return _length; + } + + @property bool empty() const + { + return !_length; + } + + void remove(in Key key) + in { assert(key in this); } + do + { + ensureNotInOpApply(); + + immutable hash = hashOf(key) & mask; + auto pp = &_buckets[hash]; + while (*pp) + { + auto p = *pp; + if (p._key == key) + { + *pp = p._next; + common.destroy(*p); + common.free(p); + if (--_length < _buckets.length && _length >= 4) + shrink(); + return; + } + else + { + pp = &p._next; + } + } + assert(0); + } + + ref inout(Value) opIndex(Key key) inout + { + return *opBinaryRight!("in")(key); + } + + void opIndexAssign(Value value, Key key) + { + *get(key) = value; + } + + inout(Value)* opBinaryRight(string op)(const scope Key key) inout + if (op == "in") + { + if (_buckets.length) + { + immutable hash = hashOf(key) & mask; + for (inout(Node)* p = _buckets[hash]; p !is null; p = p._next) + { + if (p._key == key) + return &p._value; + } + } + return null; + } + + int opApply(scope int delegate(ref Key, ref Value) dg) + { + immutable save = _inOpApply; + _inOpApply = true; + scope (exit) _inOpApply = save; + foreach (p; _buckets) + { + while (p !is null) + { + if (auto res = dg(p._key, p._value)) + return res; + p = p._next; + } + } + return 0; + } + +private: + + Value* get(Key key) + { + if (auto p = opBinaryRight!("in")(key)) + return p; + + ensureNotInOpApply(); + + if (!_buckets.length) + _buckets.length = 4; + + immutable hash = hashOf(key) & mask; + auto p = cast(Node*)common.xmalloc(Node.sizeof); + common.initialize(*p); + p._key = key; + p._next = _buckets[hash]; + _buckets[hash] = p; + if (++_length >= 2 * _buckets.length) + grow(); + return &p._value; + } + + static hash_t hashOf(const scope ref Key key) @trusted + { + static if (is(Key U : U[])) + return .hashOf(key, 0); + else + return .hashOf((&key)[0 .. 1], 0); + } + + @property hash_t mask() const + { + return _buckets.length - 1; + } + + void grow() + in + { + assert(_buckets.length); + } + do + { + immutable ocnt = _buckets.length; + immutable nmask = 2 * ocnt - 1; + _buckets.length = 2 * ocnt; + for (size_t i = 0; i < ocnt; ++i) + { + auto pp = &_buckets[i]; + while (*pp) + { + auto p = *pp; + + immutable nidx = hashOf(p._key) & nmask; + if (nidx != i) + { + *pp = p._next; + p._next = _buckets[nidx]; + _buckets[nidx] = p; + } + else + { + pp = &p._next; + } + } + } + } + + void shrink() + in + { + assert(_buckets.length >= 2); + } + do + { + immutable ocnt = _buckets.length; + immutable ncnt = ocnt >> 1; + immutable nmask = ncnt - 1; + + for (size_t i = ncnt; i < ocnt; ++i) + { + if (auto tail = _buckets[i]) + { + immutable nidx = i & nmask; + auto pp = &_buckets[nidx]; + while (*pp) + pp = &(*pp)._next; + *pp = tail; + _buckets[i] = null; + } + } + _buckets.length = ncnt; + } + + void ensureNotInOpApply() + { + if (_inOpApply) + assert(0, "Invalid HashTab manipulation during opApply iteration."); + } + + Array!(Node*) _buckets; + size_t _length; + bool _inOpApply; +} + +unittest +{ + HashTab!(int, int) tab; + + foreach (i; 0 .. 100) + tab[i] = 100 - i; + + foreach (i; 0 .. 100) + assert(tab[i] == 100 - i); + + foreach (k, v; tab) + assert(v == 100 - k); + + foreach (i; 0 .. 50) + tab.remove(2 * i); + + assert(tab.length == 50); + + foreach (i; 0 .. 50) + assert(tab[2 * i + 1] == 100 - 2 * i - 1); + + assert(tab.length == 50); + + tab.reset(); + assert(tab.empty); + tab[0] = 0; + assert(!tab.empty); + destroy(tab); + assert(tab.empty); + + // not copyable + static assert(!__traits(compiles, { HashTab!(int, int) tab2 = tab; })); + HashTab!(int, int) tab2; + static assert(!__traits(compiles, tab = tab2)); + static void foo(HashTab!(int, int) copy) {} + static assert(!__traits(compiles, foo(tab))); +} + +unittest +{ + HashTab!(string, size_t) tab; + + tab["foo"] = 0; + assert(tab["foo"] == 0); + ++tab["foo"]; + assert(tab["foo"] == 1); + tab["foo"]++; + assert(tab["foo"] == 2); + + auto s = "fo"; + s ~= "o"; + assert(tab[s] == 2); + assert(tab.length == 1); + tab[s] -= 2; + assert(tab[s] == 0); + tab["foo"] = 12; + assert(tab[s] == 12); + + tab.remove("foo"); + assert(tab.empty); +} + +unittest +{ + alias RC = common.RC!(); + HashTab!(size_t, RC) tab; + + size_t cnt; + assert(cnt == 0); + tab[0] = RC(&cnt); + assert(cnt == 1); + tab[1] = tab[0]; + assert(cnt == 2); + tab.remove(0); + assert(cnt == 1); + tab.remove(1); + assert(cnt == 0); +} + +unittest +{ + import core.exception; + + HashTab!(uint, uint) tab; + foreach (i; 0 .. 5) + tab[i] = i; + bool thrown; + foreach (k, v; tab) + { + try + { + if (k == 3) tab.remove(k); + } + catch (AssertError e) + { + thrown = true; + } + } + assert(thrown); + assert(tab[3] == 3); +} diff --git a/libphobos/libdruntime/core/internal/container/treap.d b/libphobos/libdruntime/core/internal/container/treap.d new file mode 100644 index 0000000..1202b85 --- /dev/null +++ b/libphobos/libdruntime/core/internal/container/treap.d @@ -0,0 +1,368 @@ +/** + * Treap container for internal usage. + * + * Copyright: Copyright Digital Mars 2014 - 2014. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + */ +module core.internal.container.treap; + +static import common = core.internal.container.common; +import core.internal.qsort; + +struct Treap(E) +{ +nothrow: + static struct Node + { + Node* left, right; + E element; + uint priority; + } + + @disable this(this); + + ~this() + { + removeAll(); + } + + void initialize(ulong randSeed) + { + Rand _rand = { randSeed }; + rand = _rand; + } + + void insert(E element) @nogc + { + root = insert(root, element); + } + + void remove(E element) + { + remove(&root, element); + } + + int opApply(scope int delegate(ref E) nothrow dg) + { + return (cast(const)&this).opApply((ref const E e) => dg(*cast(E*)&e)); + } + + int opApply(scope int delegate(ref const E) nothrow dg) const + { + return opApplyHelper(root, dg); + } + + version (CoreUnittest) + bool opEquals(E[] elements) + { + size_t i; + foreach (e; this) + { + if (i >= elements.length) + return false; + if (e != elements[i++]) + return false; + } + return i == elements.length; + } + + void removeAll() + { + removeAll(root); + root = null; + } + + version (CoreUnittest) + bool valid() + { + return valid(root); + } + + + version (none) + uint height() + { + static uint height(Node* node) + { + if (!node) + return 0; + auto left = height(node.left); + auto right = height(node.right); + return 1 + (left > right ? left : right); + } + return height(root); + } + + version (none) + size_t count() + { + static size_t count(Node* node) + { + if (!node) + return 0; + return count(node.left) + count(node.right) + 1; + } + return count(root); + } + + +private: + Node* root; + Rand rand; + + Node* allocNode(E element) @nogc + { + Node* node = cast(Node*)common.xmalloc(Node.sizeof); + node.left = node.right = null; + node.priority = rand(); + node.element = element; + return node; + } + + Node* insert(Node* node, E element) @nogc + { + if (!node) + return allocNode(element); + else if (element < node.element) + { + node.left = insert(node.left, element); + if (node.left.priority < node.priority) + node = rotateR(node); + } + else if (element > node.element) + { + node.right = insert(node.right, element); + if (node.right.priority < node.priority) + node = rotateL(node); + } + else + {} // ignore duplicate + + return node; + } + +static: + + void freeNode(Node* node) + { + common.free(node); + } + + Node* rotateL(Node* root) + { + auto pivot = root.right; + root.right = pivot.left; + pivot.left = root; + return pivot; + } + + Node* rotateR(Node* root) + { + auto pivot = root.left; + root.left = pivot.right; + pivot.right = root; + return pivot; + } + + void remove(Node** ppnode, E element) + { + Node* node = *ppnode; + if (!node) + return; // element not in treap + + if (element < node.element) + { + remove(&node.left, element); + } + else if (element > node.element) + { + remove(&node.right, element); + } + else + { + while (node.left && node.right) + { + if (node.left.priority < node.right.priority) + { + *ppnode = rotateR(node); + ppnode = &(*ppnode).right; + } + else + { + *ppnode = rotateL(node); + ppnode = &(*ppnode).left; + } + } + if (!node.left) + *ppnode = node.right; + else + *ppnode = node.left; + freeNode(node); + } + } + + void removeAll(Node* node) + { + if (!node) + return; + removeAll(node.left); + removeAll(node.right); + freeNode(node); + } + + int opApplyHelper(const Node* node, scope int delegate(ref const E) nothrow dg) + { + if (!node) + return 0; + + int result = opApplyHelper(node.left, dg); + if (result) + return result; + result = dg(node.element); + if (result) + return result; + return opApplyHelper(node.right, dg); + } + + version (CoreUnittest) + bool valid(Node* node) + { + if (!node) + return true; + + if (node.left) + { + if (node.left.priority < node.priority) + return false; + if (node.left.element > node.element) + return false; + } + if (node.right) + { + if (node.right.priority < node.priority) + return false; + if (node.right.element < node.element) + return false; + } + return valid(node.left) && valid(node.right); + } +} + +unittest +{ + // randomized unittest for randomized data structure + import /*cstdlib = */core.stdc.stdlib : rand, srand; + import /*ctime = */core.stdc.time : time; + + enum OP { add, remove } + enum initialSize = 1000; + enum randOps = 1000; + + Treap!uint treap; + OP[] ops; + uint[] opdata; + + srand(cast(uint)time(null)); + treap.initialize(rand()); + + uint[] data; +initialLoop: + foreach (i; 0 .. initialSize) + { + data ~= rand(); + treap.insert(data[$-1]); + foreach (e; data[0..$-1]) + if (e == data[$-1]) + { + data = data[0..$-1]; + continue initialLoop; + } + } + _adSort(*cast(void[]*)&data, typeid(data[0])); + assert(treap == data); + assert(treap.valid()); + + for (int i = randOps; i > 0; --i) + { + ops ~= cast(OP)(rand() < uint.max / 2 ? OP.add: OP.remove); + opdata ~= rand(); + } + + foreach (op; ops) + { + if (op == OP.add) + { + treap.insert(opdata[0]); + + size_t i; + for (i = 0; i < data.length; ++i) + if (data[i] >= opdata[0]) + break; + + if (i == data.length || data[i] != opdata[0]) + { // not a duplicate + data.length++; + uint tmp = opdata[0]; + for (; i < data.length; ++i) + { + uint tmp2 = data[i]; + data[i] = tmp; + tmp = tmp2; + } + } + } + else if (!data.length) // nothing to remove + { + opdata = opdata[1..$]; + continue; + } + else + { + uint tmp = data[opdata[0]%data.length]; + treap.remove(tmp); + size_t i; + for (i = 0; data[i] < tmp; ++i) + {} + for (; i < data.length-1; ++i) + data[i] = data[i+1]; + data.length--; + } + assert(treap.valid()); + assert(treap == data); + opdata = opdata[1..$]; + } + + treap.removeAll(); + data.length = 0; + assert(treap == data); +} + +/// Random number generators for internal usage. +private struct Rand +{ + private ulong rng_state; + +@safe @nogc nothrow: +pure: + + auto opCall() + { + auto result = front; + popFront(); + return result; + } + + @property uint front() + { + return cast(uint)(rng_state >> 32); + } + + void popFront() + { + immutable ulong a = 2862933555777941757; + immutable ulong c = 1; + rng_state = a * rng_state + c; + } + + enum empty = false; +} diff --git a/libphobos/libdruntime/core/internal/convert.d b/libphobos/libdruntime/core/internal/convert.d index d922049..2789d29 100644 --- a/libphobos/libdruntime/core/internal/convert.d +++ b/libphobos/libdruntime/core/internal/convert.d @@ -3,18 +3,17 @@ * This module provides functions to converting different values to const(ubyte)[] * * Copyright: Copyright Igor Stepanov 2013-2013. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Igor Stepanov * Source: $(DRUNTIMESRC core/internal/_convert.d) */ module core.internal.convert; -import core.internal.traits : Unqual; /+ A @nogc function can allocate memory during CTFE. +/ @nogc nothrow pure @trusted -private ubyte[] ctfe_alloc()(size_t n) +private ubyte[] ctfe_alloc(size_t n) { if (!__ctfe) { @@ -34,8 +33,7 @@ private ubyte[] ctfe_alloc()(size_t n) } @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real) || - is(Unqual!T == ifloat) || is(Unqual!T == idouble) || is(Unqual!T == ireal)) +const(ubyte)[] toUbyte(T)(const scope ref T val) if (__traits(isFloating, T) && (is(T : real) || is(T : ireal))) { if (__ctfe) { @@ -84,7 +82,7 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == float) || is(Unqua ubyte[] buff = ctfe_alloc(T.sizeof); enum msbSize = double.sizeof; - static if (is(Unqual!T == ireal)) + static if (is(T : ireal)) double hi = toPrec!double(val.im); else double hi = toPrec!double(val); @@ -101,7 +99,7 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == float) || is(Unqua } else { - static if (is(Unqual!T == ireal)) + static if (is(T : ireal)) double low = toPrec!double(val.im - hi); else double low = toPrec!double(val - hi); @@ -183,7 +181,7 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == float) || is(Unqua } @safe pure nothrow @nogc -private Float parse(bool is_denormalized = false, T)(T x) if (is(Unqual!T == ifloat) || is(Unqual!T == idouble) || is(Unqual!T == ireal)) +private Float parse(bool is_denormalized = false, T:ireal)(T x) { return parse(x.im); } @@ -191,6 +189,7 @@ private Float parse(bool is_denormalized = false, T)(T x) if (is(Unqual!T == ifl @safe pure nothrow @nogc private Float parse(bool is_denormalized = false, T:real)(T x_) if (floatFormat!T != FloatFormat.Real80) { + import core.internal.traits : Unqual; Unqual!T x = x_; static assert(floatFormat!T != FloatFormat.DoubleDouble, "doubledouble float format not supported in CTFE"); @@ -249,6 +248,7 @@ private Float parse(bool is_denormalized = false, T:real)(T x_) if (floatFormat! @safe pure nothrow @nogc private Float parse(bool _ = false, T:real)(T x_) if (floatFormat!T == FloatFormat.Real80) { + import core.internal.traits : Unqual; Unqual!T x = x_; //HACK @@@3632@@@ @@ -472,14 +472,14 @@ private Float denormalizedMantissa(T)(T x, uint sign) if (floatFormat!T == Float return Float(fl.mantissa2 & 0x00FFFFFFFFFFFFFFUL , 0, sign, 1); } -version (unittest) +@system unittest { - private const(ubyte)[] toUbyte2(T)(T val) + static const(ubyte)[] toUbyte2(T)(T val) { return toUbyte(val).dup; } - private void testNumberConvert(string v)() + static void testNumberConvert(string v)() { enum ctval = mixin(v); @@ -495,7 +495,7 @@ version (unittest) assert(rtbytes[0..testsize] == ctbytes[0..testsize]); } - private void testConvert() + static void testConvert() { /**Test special values*/ testNumberConvert!("-float.infinity"); @@ -572,11 +572,6 @@ version (unittest) testNumberConvert!("real.min_normal/19"); testNumberConvert!("real.min_normal/17"); - /**Test imaginary values: convert algorithm is same with real values*/ - testNumberConvert!("0.0Fi"); - testNumberConvert!("0.0i"); - testNumberConvert!("0.0Li"); - /**True random values*/ testNumberConvert!("-0x9.0f7ee55df77618fp-13829L"); testNumberConvert!("0x7.36e6e2640120d28p+8797L"); @@ -605,11 +600,7 @@ version (unittest) testNumberConvert!("cast(float)0x9.54bb0d88806f714p-7088L"); } - - unittest - { - testConvert(); - } + testConvert(); } @@ -654,13 +645,13 @@ package template floatSize(T) if (is(T:real) || is(T:ireal)) // all toUbyte functions must be evaluable at compile time @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const T[] arr) if (T.sizeof == 1) +const(ubyte)[] toUbyte(T)(return scope const T[] arr) if (T.sizeof == 1) { return cast(const(ubyte)[])arr; } @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const T[] arr) if (T.sizeof > 1) +const(ubyte)[] toUbyte(T)(return scope const T[] arr) if (T.sizeof > 1) { if (__ctfe) { @@ -692,7 +683,7 @@ const(ubyte)[] toUbyte(T)(const T[] arr) if (T.sizeof > 1) } @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const ref T val) if (__traits(isIntegral, T) && !is(T == enum) && !is(T == __vector)) +const(ubyte)[] toUbyte(T)(const ref scope T val) if (__traits(isIntegral, T) && !is(T == enum) && !is(T == __vector)) { static if (T.sizeof == 1) { @@ -709,6 +700,7 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (__traits(isIntegral, T) && !is(T } else if (__ctfe) { + import core.internal.traits : Unqual; ubyte[] tmp = ctfe_alloc(T.sizeof); Unqual!T val_ = val; for (size_t i = 0; i < T.sizeof; ++i) @@ -728,7 +720,7 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (__traits(isIntegral, T) && !is(T } @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == __vector)) +const(ubyte)[] toUbyte(T)(const ref scope T val) if (is(T == __vector)) { if (!__ctfe) return (cast(const ubyte*) &val)[0 .. T.sizeof]; @@ -749,8 +741,10 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == __vector)) } } +// @@@DEPRECATED_2022-02@@@ +deprecated @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == cfloat) || is(Unqual!T == cdouble) ||is(Unqual!T == creal)) +const(ubyte)[] toUbyte(T)(const ref return scope T val) if (__traits(isFloating, T) && is(T : creal)) { if (__ctfe) { @@ -770,12 +764,12 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (is(Unqual!T == cfloat) || is(Unqu } @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == enum)) +const(ubyte)[] toUbyte(T)(const ref return scope T val) if (is(T == enum)) { if (__ctfe) { static if (is(T V == enum)){} - return toUbyte(cast(const V) val); + return toUbyte(*cast(const V*) &val); } else { @@ -789,7 +783,7 @@ nothrow pure @safe unittest enum Month : uint { jan = 1} Month m = Month.jan; const bytes = toUbyte(m); - enum ctfe_works = (() => { Month x = Month.jan; return toUbyte(x).length > 0; })(); + enum ctfe_works = (() { Month x = Month.jan; return toUbyte(x).length > 0; })(); } @trusted pure nothrow @nogc @@ -807,7 +801,7 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == delegate) || is(T : V*, V } @trusted pure nothrow @nogc -const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == struct) || is(T == union)) +const(ubyte)[] toUbyte(T)(const ref return scope T val) if (is(T == struct) || is(T == union)) { if (__ctfe) { diff --git a/libphobos/libdruntime/core/internal/dassert.d b/libphobos/libdruntime/core/internal/dassert.d new file mode 100644 index 0000000..ac7600f --- /dev/null +++ b/libphobos/libdruntime/core/internal/dassert.d @@ -0,0 +1,590 @@ +/* + * Support for rich error messages generation with `assert` + * + * This module provides the `_d_assert_fail` hooks which are instantiated + * by the compiler whenever `-checkaction=context` is used. + * There are two hooks, one for unary expressions, and one for binary. + * When used, the compiler will rewrite `assert(a >= b)` as + * `assert(a >= b, _d_assert_fail!(typeof(a))(">=", a, b))`. + * Temporaries will be created to avoid side effects if deemed necessary + * by the compiler. + * + * For more information, refer to the implementation in DMD frontend + * for `AssertExpression`'s semantic analysis. + * + * Copyright: D Language Foundation 2018 - 2020 + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(LINK2 https://github.com/dlang/druntime/blob/master/src/core/internal/dassert.d, _dassert.d) + * Documentation: https://dlang.org/phobos/core_internal_dassert.html + */ +module core.internal.dassert; + +/** + * Generates rich assert error messages for unary expressions + * + * The unary expression `assert(!una)` will be turned into + * `assert(!una, _d_assert_fail("!", una))`. + * This routine simply acts as if the user wrote `assert(una == false)`. + * + * Params: + * op = Operator that was used in the expression, currently only "!" + * is supported. + * a = Result of the expression that was used in `assert` before + * its implicit conversion to `bool`. + * + * Returns: + * A string such as "$a != true" or "$a == true". + */ +string _d_assert_fail(A)(const scope string op, auto ref const scope A a) +{ + // Prevent InvalidMemoryOperationError when triggered from a finalizer + if (inFinalizer()) + return "Assertion failed (rich formatting is disabled in finalizers)"; + + string[2] vals = [ miniFormatFakeAttributes(a), "true" ]; + immutable token = op == "!" ? "==" : "!="; + return combine(vals[0 .. 1], token, vals[1 .. $]); +} + +/** + * Generates rich assert error messages for binary expressions + * + * The binary expression `assert(x == y)` will be turned into + * `assert(x == y, _d_assert_fail!(typeof(x))("==", x, y))`. + * + * Params: + * comp = Comparison operator that was used in the expression. + * a = Left hand side operand (can be a tuple). + * b = Right hand side operand (can be a tuple). + * + * Returns: + * A string such as "$a $comp $b". + */ +template _d_assert_fail(A...) +{ + string _d_assert_fail(B...)( + const scope string comp, auto ref const scope A a, auto ref const scope B b) + if (B.length != 0 || A.length != 1) // Resolve ambiguity with unary overload + { + // Prevent InvalidMemoryOperationError when triggered from a finalizer + if (inFinalizer()) + return "Assertion failed (rich formatting is disabled in finalizers)"; + + string[A.length + B.length] vals; + static foreach (idx; 0 .. A.length) + vals[idx] = miniFormatFakeAttributes(a[idx]); + static foreach (idx; 0 .. B.length) + vals[A.length + idx] = miniFormatFakeAttributes(b[idx]); + immutable token = invertCompToken(comp); + return combine(vals[0 .. A.length], token, vals[A.length .. $]); + } +} + +/// Combines the supplied arguments into one string `"valA token valB"` +private string combine(const scope string[] valA, const scope string token, + const scope string[] valB) pure nothrow @nogc @safe +{ + // Each separator is 2 chars (", "), plus the two spaces around the token. + size_t totalLen = (valA.length - 1) * 2 + + (valB.length - 1) * 2 + 2 + token.length; + + // Empty arrays are printed as () + if (valA.length == 0) totalLen += 2; + if (valB.length == 0) totalLen += 2; + + foreach (v; valA) totalLen += v.length; + foreach (v; valB) totalLen += v.length; + + // Include braces when printing tuples + const printBraces = (valA.length + valB.length) != 2; + if (printBraces) totalLen += 4; // '(', ')' for both tuples + + char[] buffer = cast(char[]) pureAlloc(totalLen)[0 .. totalLen]; + // @nogc-concat of " " + static void formatTuple (scope char[] buffer, ref size_t n, in string[] vals, in bool printBraces) + { + if (printBraces) buffer[n++] = '('; + foreach (idx, v; vals) + { + if (idx) + { + buffer[n++] = ','; + buffer[n++] = ' '; + } + buffer[n .. n + v.length] = v; + n += v.length; + } + if (printBraces) buffer[n++] = ')'; + } + + size_t n; + formatTuple(buffer, n, valA, printBraces); + buffer[n++] = ' '; + buffer[n .. n + token.length] = token; + n += token.length; + buffer[n++] = ' '; + formatTuple(buffer, n, valB, printBraces); + return (() @trusted => cast(string) buffer)(); +} + +/// Yields the appropriate `printf` format token for a type `T` +private template getPrintfFormat(T) +{ + static if (is(T == long)) + { + enum getPrintfFormat = "%lld"; + } + else static if (is(T == ulong)) + { + enum getPrintfFormat = "%llu"; + } + else static if (__traits(isIntegral, T)) + { + static if (__traits(isUnsigned, T)) + { + enum getPrintfFormat = "%u"; + } + else + { + enum getPrintfFormat = "%d"; + } + } + else + { + static assert(0, "Unknown format"); + } +} + +/** + * Generates a textual representation of `v` without relying on Phobos. + * The value is formatted as follows: + * + * - primitive types and arrays yield their respective literals + * - pointers are printed as hexadecimal numbers + * - enum members are represented by their name + * - user-defined types are formatted by either calling `toString` + * if defined or printing all members, e.g. `S(1, 2)` + * + * Note that unions are rejected because this method cannot determine which + * member is valid when calling this method. + * + * Params: + * v = the value to print + * + * Returns: a string respresenting `v` or `V.stringof` if `V` is not supported + */ +private string miniFormat(V)(const scope ref V v) +{ + import core.internal.traits: isAggregateType; + + /// `shared` values are formatted as their base type + static if (is(V == shared T, T)) + { + // Use atomics to avoid race conditions whenever possible + static if (__traits(compiles, atomicLoad(v))) + { + if (!__ctfe) + { + T tmp = cast(T) atomicLoad(v); + return miniFormat(tmp); + } + } + + // Fall back to a simple cast - we're violating the type system anyways + return miniFormat(__ctfe ? cast(const T) v : *cast(const T*) &v); + } + // Format enum members using their name + else static if (is(V BaseType == enum)) + { + // Always generate repeated if's instead of switch to skip the detection + // of non-integral enums. This method doesn't need to be fast. + static foreach (mem; __traits(allMembers, V)) + { + if (v == __traits(getMember, V, mem)) + return mem; + } + + // Format invalid enum values as their base type + enum cast_ = "cast(" ~ V.stringof ~ ")"; + const val = miniFormat(__ctfe ? cast(const BaseType) v : *cast(const BaseType*) &v); + return combine([ cast_ ], "", [ val ]); + } + else static if (is(V == bool)) + { + return v ? "true" : "false"; + } + // Detect vectors which match isIntegral / isFloating + else static if (is(V == __vector(ET[N]), ET, size_t N)) + { + string msg = "["; + foreach (i; 0 .. N) + { + if (i > 0) + msg ~= ", "; + + msg ~= miniFormat(v[i]); + } + msg ~= "]"; + return msg; + } + else static if (__traits(isIntegral, V)) + { + static if (is(V == char)) + { + // Avoid invalid code points + if (v < 0x7F) + return ['\'', v, '\'']; + + uint tmp = v; + return "cast(char) " ~ miniFormat(tmp); + } + else static if (is(V == wchar) || is(V == dchar)) + { + import core.internal.utf: isValidDchar, toUTF8; + + // Avoid invalid code points + if (isValidDchar(v)) + return toUTF8(['\'', v, '\'']); + + uint tmp = v; + return "cast(" ~ V.stringof ~ ") " ~ miniFormat(tmp); + } + else + { + import core.internal.string; + static if (__traits(isUnsigned, V)) + const val = unsignedToTempString(v); + else + const val = signedToTempString(v); + return val.get().idup(); + } + } + else static if (__traits(isFloating, V)) + { + import core.stdc.stdio : sprintf; + import core.stdc.config : LD = c_long_double; + + // No suitable replacement for sprintf in druntime ATM + if (__ctfe) + return '<' ~ V.stringof ~ " not supported>"; + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=20759 + static if (is(LD == real)) + enum realFmt = "%Lg"; + else + enum realFmt = "%g"; + + char[60] val; + int len; + static if (is(V == float) || is(V == double)) + len = sprintf(&val[0], "%g", v); + else static if (is(V == real)) + len = sprintf(&val[0], realFmt, cast(LD) v); + else static if (is(V == cfloat) || is(V == cdouble)) + len = sprintf(&val[0], "%g + %gi", v.re, v.im); + else static if (is(V == creal)) + len = sprintf(&val[0], realFmt ~ " + " ~ realFmt ~ 'i', cast(LD) v.re, cast(LD) v.im); + else static if (is(V == ifloat) || is(V == idouble)) + len = sprintf(&val[0], "%gi", v); + else // ireal + { + static assert(is(V == ireal)); + static if (is(LD == real)) + alias R = ireal; + else + alias R = idouble; + len = sprintf(&val[0], realFmt ~ 'i', cast(R) v); + } + return val.idup[0 .. len]; + } + // special-handling for void-arrays + else static if (is(V == typeof(null))) + { + return "`null`"; + } + else static if (is(V == U*, U)) + { + // Format as ulong and prepend a 0x for pointers + import core.internal.string; + return cast(immutable) ("0x" ~ unsignedToTempString!16(cast(ulong) v)); + } + // toString() isn't always const, e.g. classes inheriting from Object + else static if (__traits(compiles, { string s = V.init.toString(); })) + { + // Object references / struct pointers may be null + static if (is(V == class) || is(V == interface)) + { + if (v is null) + return "`null`"; + } + + try + { + // Prefer const overload of toString + static if (__traits(compiles, { string s = v.toString(); })) + return v.toString(); + else + return (cast() v).toString(); + } + catch (Exception e) + { + return ``; + } + } + // Static arrays or slices (but not aggregates with `alias this`) + else static if (is(V : U[], U) && !isAggregateType!V) + { + import core.internal.traits: Unqual; + alias E = Unqual!U; + + // special-handling for void-arrays + static if (is(E == void)) + { + if (__ctfe) + return ""; + + const bytes = cast(byte[]) v; + return miniFormat(bytes); + } + // anything string-like + else static if (is(E == char) || is(E == dchar) || is(E == wchar)) + { + const s = `"` ~ v ~ `"`; + + // v could be a char[], dchar[] or wchar[] + static if (is(typeof(s) : const char[])) + return cast(immutable) s; + else + { + import core.internal.utf: toUTF8; + return toUTF8(s); + } + } + else + { + string msg = "["; + foreach (i, ref el; v) + { + if (i > 0) + msg ~= ", "; + + // don't fully print big arrays + if (i >= 30) + { + msg ~= "..."; + break; + } + msg ~= miniFormat(el); + } + msg ~= "]"; + return msg; + } + } + else static if (is(V : Val[K], K, Val)) + { + size_t i; + string msg = "["; + foreach (ref k, ref val; v) + { + if (i > 0) + msg ~= ", "; + // don't fully print big AAs + if (i++ >= 30) + { + msg ~= "..."; + break; + } + msg ~= miniFormat(k) ~ ": " ~ miniFormat(val); + } + msg ~= "]"; + return msg; + } + else static if (is(V == struct)) + { + return formatMembers(v); + } + // Extern C++ classes don't have a toString by default + else static if (is(V == class) || is(V == interface)) + { + if (v is null) + return "null"; + + // Extern classes might be opaque + static if (is(typeof(v.tupleof))) + return formatMembers(v); + else + return '<' ~ V.stringof ~ '>'; + } + else + { + return V.stringof; + } +} + +/// Formats `v`'s members as `V(, , ...)` +private string formatMembers(V)(const scope ref V v) +{ + enum ctxPtr = __traits(isNested, V); + enum isOverlapped = calcFieldOverlap([ v.tupleof.offsetof ]); + + string msg = V.stringof ~ "("; + foreach (i, ref field; v.tupleof) + { + if (i > 0) + msg ~= ", "; + + static if (isOverlapped[i]) + { + msg ~= ""; + } + else + { + // Mark context pointer + static if (ctxPtr && i == v.tupleof.length - 1) + msg ~= ": "; + + msg ~= miniFormat(field); + } + } + msg ~= ")"; + return msg; +} + +/** + * Calculates whether fields are overlapped based on the passed offsets. + * + * Params: + * offsets = offsets of all fields matching the order of `.tupleof` + * + * Returns: an array such that arr[n] = true indicates that the n'th field + * overlaps with an adjacent field + **/ +private bool[] calcFieldOverlap(const scope size_t[] offsets) +{ + bool[] overlaps = new bool[](offsets.length); + + foreach (const idx; 1 .. overlaps.length) + { + if (offsets[idx - 1] == offsets[idx]) + overlaps[idx - 1] = overlaps[idx] = true; + } + + return overlaps; +} + +// This should be a local import in miniFormat but fails with a cyclic dependency error +// core.thread.osthread -> core.time -> object -> core.internal.array.capacity +// -> core.atomic -> core.thread -> core.thread.osthread +import core.atomic : atomicLoad; + +/// Negates a comparison token, e.g. `==` is mapped to `!=` +private string invertCompToken(scope string comp) pure nothrow @nogc @safe +{ + switch (comp) + { + case "==": + return "!="; + case "!=": + return "=="; + case "<": + return ">="; + case "<=": + return ">"; + case ">": + return "<="; + case ">=": + return "<"; + case "is": + return "!is"; + case "!is": + return "is"; + case "in": + return "!in"; + case "!in": + return "in"; + default: + assert(0, combine(["Invalid comparison operator '"], comp, ["'"])); + } +} + +/// Casts the function pointer to include `@safe`, `@nogc`, ... +private auto assumeFakeAttributes(T)(T t) @trusted +{ + import core.internal.traits : Parameters, ReturnType; + alias RT = ReturnType!T; + alias P = Parameters!T; + alias type = RT function(P) nothrow @nogc @safe pure; + return cast(type) t; +} + +/// Wrapper for `miniFormat` which assumes that the implementation is `@safe`, `@nogc`, ... +/// s.t. it does not violate the constraints of the the function containing the `assert`. +private string miniFormatFakeAttributes(T)(const scope ref T t) +{ + alias miniT = miniFormat!T; + return assumeFakeAttributes(&miniT)(t); +} + +/// Allocates an array of `t` bytes while pretending to be `@safe`, `@nogc`, ... +private auto pureAlloc(size_t t) +{ + static auto alloc(size_t len) + { + return new ubyte[len]; + } + return assumeFakeAttributes(&alloc)(t); +} + +/// Wrapper for GC.inFinalizer that fakes purity +private bool inFinalizer()() pure nothrow @nogc @safe +{ + // CTFE doesn't trigger InvalidMemoryErrors + import core.memory : GC; + return !__ctfe && assumeFakeAttributes(&GC.inFinalizer)(); +} + +// https://issues.dlang.org/show_bug.cgi?id=21544 +unittest +{ + // Normal enum values + enum E { A, BCDE } + E e = E.A; + assert(miniFormat(e) == "A"); + e = E.BCDE; + assert(miniFormat(e) == "BCDE"); + + // Invalid enum value is printed as their implicit base type (int) + e = cast(E) 3; + assert(miniFormat(e) == "cast(E) 3"); + + // Non-integral enums work as well + static struct S + { + int a; + string str; + } + + enum E2 : S { a2 = S(1, "Hello") } + E2 es = E2.a2; + assert(miniFormat(es) == `a2`); + + // Even invalid values + es = cast(E2) S(2, "World"); + assert(miniFormat(es) == `cast(E2) S(2, "World")`); +} + +// vectors +unittest +{ + static if (is(__vector(float[4]))) + { + __vector(float[4]) f = [-1.5f, 0.5f, 1.0f, 0.125f]; + assert(miniFormat(f) == "[-1.5, 0.5, 1, 0.125]"); + } + + static if (is(__vector(int[4]))) + { + __vector(int[4]) i = [-1, 0, 1, 3]; + assert(miniFormat(i) == "[-1, 0, 1, 3]"); + } +} diff --git a/libphobos/libdruntime/core/internal/destruction.d b/libphobos/libdruntime/core/internal/destruction.d new file mode 100644 index 0000000..5c5932d --- /dev/null +++ b/libphobos/libdruntime/core/internal/destruction.d @@ -0,0 +1,47 @@ +/** + This module contains implementations for destroying instances of types + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/_internal/_destruction.d) +*/ +module core.internal.destruction; + +// compiler frontend lowers dynamic array deconstruction to this +void __ArrayDtor(T)(scope T[] a) +{ + foreach_reverse (ref T e; a) + e.__xdtor(); +} + +public void destructRecurse(E, size_t n)(ref E[n] arr) +{ + import core.internal.traits : hasElaborateDestructor; + + static if (hasElaborateDestructor!E) + { + foreach_reverse (ref elem; arr) + destructRecurse(elem); + } +} + +public void destructRecurse(S)(ref S s) + if (is(S == struct)) +{ + static if (__traits(hasMember, S, "__xdtor") && + // Bugzilla 14746: Check that it's the exact member of S. + __traits(isSame, S, __traits(parent, s.__xdtor))) + s.__xdtor(); +} + +// Test static struct +nothrow @safe @nogc unittest +{ + static int i = 0; + static struct S { ~this() nothrow @safe @nogc { i = 42; } } + S s; + destructRecurse(s); + assert(i == 42); +} diff --git a/libphobos/libdruntime/core/internal/entrypoint.d b/libphobos/libdruntime/core/internal/entrypoint.d new file mode 100644 index 0000000..839c120 --- /dev/null +++ b/libphobos/libdruntime/core/internal/entrypoint.d @@ -0,0 +1,41 @@ +/** + This module contains the code for C main and any call(s) to initialize the + D runtime and call D main. + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/_internal/_entrypoint.d) +*/ +module core.internal.entrypoint; + +/** +A template containing C main and any call(s) to initialize druntime and +call D main. Any module containing a D main function declaration will +cause the compiler to generate a `mixin _d_cmain();` statement to inject +this code into the module. +*/ +template _d_cmain() +{ + extern(C) + { + int _d_run_main(int argc, char **argv, void* mainFunc); + + int _Dmain(char[][] args); + + int main(int argc, char **argv) + { + return _d_run_main(argc, argv, &_Dmain); + } + + // Solaris, for unknown reasons, requires both a main() and an _main() + version (Solaris) + { + int _main(int argc, char** argv) + { + return main(argc, argv); + } + } + } +} diff --git a/libphobos/libdruntime/core/internal/gc/bits.d b/libphobos/libdruntime/core/internal/gc/bits.d new file mode 100644 index 0000000..d50c38f --- /dev/null +++ b/libphobos/libdruntime/core/internal/gc/bits.d @@ -0,0 +1,493 @@ +/** + * Contains a bitfield used by the GC. + * + * Copyright: D Language Foundation 2005 - 2021. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, David Friedman, Sean Kelly + */ +module core.internal.gc.bits; + +import core.internal.gc.os : os_mem_map, os_mem_unmap, HaveFork; + +import core.bitop; +import core.stdc.string; +import core.stdc.stdlib; +import core.exception : onOutOfMemoryError; + +// use version gcbitsSingleBitOperation to disable optimizations that use +// word operands on bulk operation copyRange, setRange, clrRange, etc. +// version = gcbitsSingleBitOperation; + +struct GCBits +{ +@nogc: + alias size_t wordtype; + + enum BITS_PER_WORD = (wordtype.sizeof * 8); + enum BITS_SHIFT = (wordtype.sizeof == 8 ? 6 : 5); + enum BITS_MASK = (BITS_PER_WORD - 1); + enum BITS_0 = cast(wordtype)0; + enum BITS_1 = cast(wordtype)1; + enum BITS_2 = cast(wordtype)2; + + wordtype* data; + size_t nbits; + + void Dtor(bool share = false) nothrow + { + if (data) + { + static if (!HaveFork) + free(data); + else if (share) + os_mem_unmap(data, nwords * data[0].sizeof); + else + free(data); + data = null; + } + } + + void alloc(size_t nbits, bool share = false) nothrow + { + this.nbits = nbits; + static if (!HaveFork) + data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); + else if (share) + data = cast(typeof(data[0])*)os_mem_map(nwords * data[0].sizeof, true); // Allocate as MAP_SHARED + else + data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); + if (!data) + onOutOfMemoryError(); + } + + wordtype test(size_t i) const nothrow + in + { + assert(i < nbits); + } + do + { + return core.bitop.bt(data, i); + } + + int set(size_t i) nothrow + in + { + assert(i < nbits); + } + do + { + return core.bitop.bts(data, i); + } + + int clear(size_t i) nothrow + in + { + assert(i <= nbits); + } + do + { + return core.bitop.btr(data, i); + } + + // return non-zero if bit already set + size_t setLocked(size_t i) nothrow + { + version (GNU) + { + import gcc.builtins; + const pos = i >> BITS_SHIFT; + const mask = BITS_1 << (i & BITS_MASK); + mixin("auto val = __atomic_fetch_or_" ~ size_t.sizeof.stringof[0] + ~ "(cast(shared)(data + pos), mask, 3);"); + return (val & mask) != 0; + } + else version (LDC) + { + import ldc.intrinsics; + const pos = i >> BITS_SHIFT; + const mask = BITS_1 << (i & BITS_MASK); + auto val = llvm_atomic_rmw_or(cast(shared)(data + pos), mask); + return (val & mask) != 0; + } + else version (D_InlineAsm_X86) + { + asm @nogc nothrow { + mov EAX, this; + mov ECX, data[EAX]; + mov EDX, i; + lock; + bts dword ptr[ECX], EDX; + sbb EAX,EAX; + } + } + else version (D_InlineAsm_X86_64) + { + asm @nogc nothrow { + mov RAX, this; + mov RAX, data[RAX]; + mov RDX, i; + lock; + bts qword ptr[RAX], RDX; + sbb RAX,RAX; + } + } + else + { + auto pos = i >> BITS_SHIFT; + auto pdata = cast(shared)(data + pos); + auto mask = BITS_1 << (i & BITS_MASK); + auto state = *pdata; + if (state & mask) + return state; + + import core.atomic; + auto newstate = state | mask; + while (!cas(pdata, state, newstate)) + { + state = *pdata; + if (state & mask) + return state; + newstate = state | mask; + } + return 0; + } + } + + template testAndSet(bool locked) + { + static if (locked) + alias testAndSet = setLocked; + else + alias testAndSet = set; + } + + + mixin template RangeVars() + { + size_t firstWord = (target >> BITS_SHIFT); + size_t firstOff = target & BITS_MASK; + size_t last = target + len - 1; + size_t lastWord = (last >> BITS_SHIFT); + size_t lastOff = last & BITS_MASK; + } + + // extract loops to allow inlining the rest + void clearWords(size_t firstWord, size_t lastWord) nothrow + { + for (size_t w = firstWord; w < lastWord; w++) + data[w] = 0; + } + + void setWords(size_t firstWord, size_t lastWord) nothrow + { + for (size_t w = firstWord; w < lastWord; w++) + data[w] = ~0; + } + + void copyWords(size_t firstWord, size_t lastWord, const(wordtype)* source) nothrow + { + for (size_t w = firstWord; w < lastWord; w++) + data[w] = source[w - firstWord]; + } + + void copyWordsShifted(size_t firstWord, size_t cntWords, size_t firstOff, const(wordtype)* source) nothrow + { + wordtype mask = ~BITS_0 << firstOff; + data[firstWord] = (data[firstWord] & ~mask) | (source[0] << firstOff); + for (size_t w = 1; w < cntWords; w++) + data[firstWord + w] = (source[w - 1] >> (BITS_PER_WORD - firstOff)) | (source[w] << firstOff); + } + + // target = the biti to start the copy to + // destlen = the number of bits to copy from source + void copyRange(size_t target, size_t len, const(wordtype)* source) nothrow + { + version (gcbitsSingleBitOperation) + { + for (size_t i = 0; i < len; i++) + if (source[(i >> BITS_SHIFT)] & (BITS_1 << (i & BITS_MASK))) + set(target+i); + else + clear(target+i); + } + else + { + if (len > 0) + copyRangeZ(target, len, source); + } + } + + void copyRangeZ(size_t target, size_t len, const(wordtype)* source) nothrow + { + mixin RangeVars!(); + + if (firstWord == lastWord) + { + wordtype mask = ((BITS_2 << (lastOff - firstOff)) - 1) << firstOff; + data[firstWord] = (data[firstWord] & ~mask) | ((source[0] << firstOff) & mask); + } + else if (firstOff == 0) + { + copyWords(firstWord, lastWord, source); + + wordtype mask = (BITS_2 << lastOff) - 1; + data[lastWord] = (data[lastWord] & ~mask) | (source[lastWord - firstWord] & mask); + } + else + { + size_t cntWords = lastWord - firstWord; + copyWordsShifted(firstWord, cntWords, firstOff, source); + + wordtype src = (source[cntWords - 1] >> (BITS_PER_WORD - firstOff)) | (source[cntWords] << firstOff); + wordtype mask = (BITS_2 << lastOff) - 1; + data[lastWord] = (data[lastWord] & ~mask) | (src & mask); + } + } + + void copyRangeRepeating(size_t target, size_t destlen, const(wordtype)* source, size_t sourcelen) nothrow + { + version (gcbitsSingleBitOperation) + { + for (size_t i=0; i < destlen; i++) + { + bool b; + size_t j = i % sourcelen; + b = (source[j >> BITS_SHIFT] & (BITS_1 << (j & BITS_MASK))) != 0; + if (b) set(target+i); + else clear(target+i); + } + } + else + { + while (destlen > sourcelen) + { + copyRange(target, sourcelen, source); + target += sourcelen; + destlen -= sourcelen; + } + copyRange(target, destlen, source); + } + } + + unittest + { + // simulate broken array append test case in vibe.d + GCBits bits; + bits.alloc(10000); + auto data = bits.data; + + GCBits src; + src.alloc(67); + src.data[0] = 0x4; + + bits.copyRangeRepeating(2, 10000, src.data, 67); + + foreach (i; 0 .. 10000) + if ((i - 2) % 67 == 2) + assert(bits.test(i)); + else + assert(!bits.test(i)); + } + + void setRange(size_t target, size_t len) nothrow + { + version (gcbitsSingleBitOperation) + { + for (size_t i = 0; i < len; i++) + set(target+i); + } + else + { + if (len > 0) + setRangeZ(target, len); + } + } + + void setRangeZ(size_t target, size_t len) nothrow + { + mixin RangeVars!(); + + if (firstWord == lastWord) + { + wordtype mask = ((BITS_2 << (lastOff - firstOff)) - 1) << firstOff; + data[firstWord] |= mask; + } + else + { + data[firstWord] |= ~BITS_0 << firstOff; + setWords(firstWord + 1, lastWord); + wordtype mask = (BITS_2 << lastOff) - 1; + data[lastWord] |= mask; + } + } + + void clrRange(size_t target, size_t len) nothrow + { + version (gcbitsSingleBitOperation) + { + for (size_t i = 0; i < len; i++) + clear(target+i); + } + else + { + if (len > 0) + clrRangeZ(target, len); + } + } + + void clrRangeZ(size_t target, size_t len) nothrow + { + mixin RangeVars!(); + if (firstWord == lastWord) + { + wordtype mask = ((BITS_2 << (lastOff - firstOff)) - 1) << firstOff; + data[firstWord] &= ~mask; + } + else + { + data[firstWord] &= ~(~BITS_0 << firstOff); + clearWords(firstWord + 1, lastWord); + wordtype mask = (BITS_2 << lastOff) - 1; + data[lastWord] &= ~mask; + } + } + + unittest + { + GCBits bits; + bits.alloc(1000); + auto data = bits.data; + + bits.setRange(0,1); + assert(data[0] == 1); + + bits.clrRange(0,1); + assert(data[0] == 0); + + bits.setRange(BITS_PER_WORD-1,1); + assert(data[0] == BITS_1 << (BITS_PER_WORD-1)); + + bits.clrRange(BITS_PER_WORD-1,1); + assert(data[0] == 0); + + bits.setRange(12,7); + assert(data[0] == 0b0111_1111_0000_0000_0000); + + bits.clrRange(14,4); + assert(data[0] == 0b0100_0011_0000_0000_0000); + + bits.clrRange(0,BITS_PER_WORD); + assert(data[0] == 0); + + bits.setRange(0,BITS_PER_WORD); + assert(data[0] == ~0); + assert(data[1] == 0); + + bits.setRange(BITS_PER_WORD,BITS_PER_WORD); + assert(data[0] == ~0); + assert(data[1] == ~0); + assert(data[2] == 0); + bits.clrRange(BITS_PER_WORD/2,BITS_PER_WORD); + assert(data[0] == (BITS_1 << (BITS_PER_WORD/2)) - 1); + assert(data[1] == ~data[0]); + assert(data[2] == 0); + + bits.setRange(8*BITS_PER_WORD+1,4*BITS_PER_WORD-2); + assert(data[8] == ~0 << 1); + assert(data[9] == ~0); + assert(data[10] == ~0); + assert(data[11] == cast(wordtype)~0 >> 1); + + bits.clrRange(9*BITS_PER_WORD+1,2*BITS_PER_WORD); + assert(data[8] == ~0 << 1); + assert(data[9] == 1); + assert(data[10] == 0); + assert(data[11] == ((cast(wordtype)~0 >> 1) & ~1)); + + wordtype[4] src = [ 0xa, 0x5, 0xaa, 0x55 ]; + + void testCopyRange(size_t start, size_t len, int repeat = 1) + { + bits.setRange(0, bits.nbits); + if (repeat > 1) + bits.copyRangeRepeating(start, repeat * len, src.ptr, len); + else + bits.copyRange(start, len, src.ptr); + foreach (i; 0 .. start) + assert(bits.test(i)); + foreach (r; 0 .. repeat) + foreach (i; 0 .. len) + assert(!bits.test(start + r*len + i) == !core.bitop.bt(src.ptr, i)); + foreach (i; start + repeat*len .. 10*BITS_PER_WORD) + assert(bits.test(i)); + } + + testCopyRange(20, 10); // short copy range within same word + testCopyRange(50, 20); // short copy range spanning two words + testCopyRange(64, 3 * BITS_PER_WORD + 3); // aligned copy range + testCopyRange(77, 2 * BITS_PER_WORD + 15); // unaligned copy range + testCopyRange(64, 127); // copy range within critical end alignment + + testCopyRange(10, 4, 5); // repeating small range within same word + testCopyRange(20, 5, 10); // repeating small range spanning two words + testCopyRange(40, 21, 7); // repeating medium range + testCopyRange(73, 2 * BITS_PER_WORD + 15, 5); // repeating multi-word range + + testCopyRange(2, 3, 166); // failed with assert + } + + void zero() nothrow + { + memset(data, 0, nwords * wordtype.sizeof); + } + + void setAll() nothrow + { + memset(data, 0xFF, nwords * wordtype.sizeof); + } + + void copy(GCBits *f) nothrow + in + { + assert(nwords == f.nwords); + } + do + { + memcpy(data, f.data, nwords * wordtype.sizeof); + } + + @property size_t nwords() const pure nothrow + { + return (nbits + (BITS_PER_WORD - 1)) >> BITS_SHIFT; + } +} + +unittest +{ + GCBits b; + + b.alloc(786); + assert(!b.test(123)); + assert(!b.clear(123)); + assert(!b.set(123)); + assert(b.test(123)); + assert(b.clear(123)); + assert(!b.test(123)); + + b.set(785); + b.set(0); + assert(b.test(785)); + assert(b.test(0)); + b.zero(); + assert(!b.test(785)); + assert(!b.test(0)); + + GCBits b2; + b2.alloc(786); + b2.set(38); + b.copy(&b2); + assert(b.test(38)); + b2.Dtor(); + b.Dtor(); +} diff --git a/libphobos/libdruntime/core/internal/gc/impl/conservative/gc.d b/libphobos/libdruntime/core/internal/gc/impl/conservative/gc.d new file mode 100644 index 0000000..0c49955 --- /dev/null +++ b/libphobos/libdruntime/core/internal/gc/impl/conservative/gc.d @@ -0,0 +1,4836 @@ +/** + * Contains the garbage collector implementation. + * + * Copyright: D Language Foundation 2001 - 2021. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, David Friedman, Sean Kelly + */ +module core.internal.gc.impl.conservative.gc; + +// D Programming Language Garbage Collector implementation + +/************** Debugging ***************************/ + +//debug = PRINTF; // turn on printf's +//debug = PARALLEL_PRINTF; // turn on printf's +//debug = COLLECT_PRINTF; // turn on printf's +//debug = MARK_PRINTF; // turn on printf's +//debug = PRINTF_TO_FILE; // redirect printf's ouptut to file "gcx.log" +//debug = LOGGING; // log allocations / frees +//debug = MEMSTOMP; // stomp on memory +//debug = SENTINEL; // add underrun/overrrun protection + // NOTE: this needs to be enabled globally in the makefiles + // (-debug=SENTINEL) to pass druntime's unittests. +//debug = PTRCHECK; // more pointer checking +//debug = PTRCHECK2; // thorough but slow pointer checking +//debug = INVARIANT; // enable invariants +//debug = PROFILE_API; // profile API calls for config.profile > 1 +//debug = GC_RECURSIVE_LOCK; // check for recursive locking on the same thread + +/***************************************************/ +version = COLLECT_PARALLEL; // parallel scanning +version (Posix) + version = COLLECT_FORK; + +import core.internal.gc.bits; +import core.internal.gc.os; +import core.gc.config; +import core.gc.gcinterface; + +import core.internal.container.treap; + +import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc; +import core.stdc.string : memcpy, memset, memmove; +import core.bitop; +import core.thread; +static import core.memory; + +version (GNU) import gcc.builtins; + +debug (PRINTF_TO_FILE) import core.stdc.stdio : sprintf, fprintf, fopen, fflush, FILE; +else import core.stdc.stdio : sprintf, printf; // needed to output profiling results + +import core.time; +alias currTime = MonoTime.currTime; + +// Track total time spent preparing for GC, +// marking, sweeping and recovering pages. +__gshared Duration prepTime; +__gshared Duration markTime; +__gshared Duration sweepTime; +__gshared Duration pauseTime; +__gshared Duration maxPauseTime; +__gshared Duration maxCollectionTime; +__gshared size_t numCollections; +__gshared size_t maxPoolMemory; + +__gshared long numMallocs; +__gshared long numFrees; +__gshared long numReallocs; +__gshared long numExtends; +__gshared long numOthers; +__gshared long mallocTime; // using ticks instead of MonoTime for better performance +__gshared long freeTime; +__gshared long reallocTime; +__gshared long extendTime; +__gshared long otherTime; +__gshared long lockTime; + +ulong bytesAllocated; // thread local counter + +private +{ + extern (C) + { + // to allow compilation of this module without access to the rt package, + // make these functions available from rt.lifetime + void rt_finalizeFromGC(void* p, size_t size, uint attr) nothrow; + int rt_hasFinalizerInSegment(void* p, size_t size, uint attr, const scope void[] segment) nothrow; + + // Declared as an extern instead of importing core.exception + // to avoid inlining - see issue 13725. + void onInvalidMemoryOperationError(void* pretend_sideffect = null) @trusted pure nothrow @nogc; + void onOutOfMemoryErrorNoGC() @trusted nothrow @nogc; + + version (COLLECT_FORK) + version (OSX) + pid_t __fork() nothrow; + } + + enum + { + OPFAIL = ~cast(size_t)0 + } +} + +alias GC gc_t; + +/* ============================ GC =============================== */ + +// register GC in C constructor (_STI_) +extern(C) pragma(crt_constructor) void _d_register_conservative_gc() +{ + import core.gc.registry; + registerGCFactory("conservative", &initialize); +} + +extern(C) pragma(crt_constructor) void _d_register_precise_gc() +{ + import core.gc.registry; + registerGCFactory("precise", &initialize_precise); +} + +private GC initialize() +{ + import core.lifetime : emplace; + + auto gc = cast(ConservativeGC) cstdlib.malloc(__traits(classInstanceSize, ConservativeGC)); + if (!gc) + onOutOfMemoryErrorNoGC(); + + return emplace(gc); +} + +private GC initialize_precise() +{ + ConservativeGC.isPrecise = true; + return initialize(); +} + +class ConservativeGC : GC +{ + // For passing to debug code (not thread safe) + __gshared size_t line; + __gshared char* file; + + Gcx *gcx; // implementation + + import core.internal.spinlock; + static gcLock = shared(AlignedSpinLock)(SpinLock.Contention.lengthy); + static bool _inFinalizer; + __gshared bool isPrecise = false; + + // lock GC, throw InvalidMemoryOperationError on recursive locking during finalization + static void lockNR() @nogc nothrow + { + if (_inFinalizer) + onInvalidMemoryOperationError(); + gcLock.lock(); + } + + this() + { + //config is assumed to have already been initialized + + gcx = cast(Gcx*)cstdlib.calloc(1, Gcx.sizeof); + if (!gcx) + onOutOfMemoryErrorNoGC(); + gcx.initialize(); + + if (config.initReserve) + gcx.reserve(config.initReserve); + if (config.disable) + gcx.disabled++; + } + + + ~this() + { + version (linux) + { + //debug(PRINTF) printf("Thread %x ", pthread_self()); + //debug(PRINTF) printf("GC.Dtor()\n"); + } + + if (gcx) + { + gcx.Dtor(); + cstdlib.free(gcx); + gcx = null; + } + // TODO: cannot free as memory is overwritten and + // the monitor is still read in rt_finalize (called by destroy) + // cstdlib.free(cast(void*) this); + } + + + void enable() + { + static void go(Gcx* gcx) nothrow + { + assert(gcx.disabled > 0); + gcx.disabled--; + } + runLocked!(go, otherTime, numOthers)(gcx); + } + + + void disable() + { + static void go(Gcx* gcx) nothrow + { + gcx.disabled++; + } + runLocked!(go, otherTime, numOthers)(gcx); + } + + debug (GC_RECURSIVE_LOCK) static bool lockedOnThisThread; + + auto runLocked(alias func, Args...)(auto ref Args args) + { + debug(PROFILE_API) immutable tm = (config.profile > 1 ? currTime.ticks : 0); + debug(GC_RECURSIVE_LOCK) + { + if (lockedOnThisThread) + onInvalidMemoryOperationError(); + lockedOnThisThread = true; + } + lockNR(); + scope (failure) gcLock.unlock(); + debug(PROFILE_API) immutable tm2 = (config.profile > 1 ? currTime.ticks : 0); + + static if (is(typeof(func(args)) == void)) + func(args); + else + auto res = func(args); + + debug(PROFILE_API) if (config.profile > 1) + lockTime += tm2 - tm; + gcLock.unlock(); + debug(GC_RECURSIVE_LOCK) + { + if (!lockedOnThisThread) + onInvalidMemoryOperationError(); + lockedOnThisThread = false; + } + + static if (!is(typeof(func(args)) == void)) + return res; + } + + + auto runLocked(alias func, alias time, alias count, Args...)(auto ref Args args) + { + debug(PROFILE_API) immutable tm = (config.profile > 1 ? currTime.ticks : 0); + debug(GC_RECURSIVE_LOCK) + { + if (lockedOnThisThread) + onInvalidMemoryOperationError(); + lockedOnThisThread = true; + } + lockNR(); + scope (failure) gcLock.unlock(); + debug(PROFILE_API) immutable tm2 = (config.profile > 1 ? currTime.ticks : 0); + + static if (is(typeof(func(args)) == void)) + func(args); + else + auto res = func(args); + + debug(PROFILE_API) if (config.profile > 1) + { + count++; + immutable now = currTime.ticks; + lockTime += tm2 - tm; + time += now - tm2; + } + gcLock.unlock(); + debug(GC_RECURSIVE_LOCK) + { + if (!lockedOnThisThread) + onInvalidMemoryOperationError(); + lockedOnThisThread = false; + } + + static if (!is(typeof(func(args)) == void)) + return res; + } + + + uint getAttr(void* p) nothrow + { + if (!p) + { + return 0; + } + + static uint go(Gcx* gcx, void* p) nothrow + { + Pool* pool = gcx.findPool(p); + uint oldb = 0; + + if (pool) + { + p = sentinel_sub(p); + if (p != pool.findBase(p)) + return 0; + auto biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; + + oldb = pool.getBits(biti); + } + return oldb; + } + + return runLocked!(go, otherTime, numOthers)(gcx, p); + } + + + uint setAttr(void* p, uint mask) nothrow + { + if (!p) + { + return 0; + } + + static uint go(Gcx* gcx, void* p, uint mask) nothrow + { + Pool* pool = gcx.findPool(p); + uint oldb = 0; + + if (pool) + { + p = sentinel_sub(p); + if (p != pool.findBase(p)) + return 0; + auto biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; + + oldb = pool.getBits(biti); + pool.setBits(biti, mask); + } + return oldb; + } + + return runLocked!(go, otherTime, numOthers)(gcx, p, mask); + } + + + uint clrAttr(void* p, uint mask) nothrow + { + if (!p) + { + return 0; + } + + static uint go(Gcx* gcx, void* p, uint mask) nothrow + { + Pool* pool = gcx.findPool(p); + uint oldb = 0; + + if (pool) + { + p = sentinel_sub(p); + if (p != pool.findBase(p)) + return 0; + auto biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; + + oldb = pool.getBits(biti); + pool.clrBits(biti, mask); + } + return oldb; + } + + return runLocked!(go, otherTime, numOthers)(gcx, p, mask); + } + + + void *malloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + if (!size) + { + return null; + } + + size_t localAllocSize = void; + + auto p = runLocked!(mallocNoSync, mallocTime, numMallocs)(size, bits, localAllocSize, ti); + + if (!(bits & BlkAttr.NO_SCAN)) + { + memset(p + size, 0, localAllocSize - size); + } + + return p; + } + + + // + // + // + private void *mallocNoSync(size_t size, uint bits, ref size_t alloc_size, const TypeInfo ti = null) nothrow + { + assert(size != 0); + + debug(PRINTF) + printf("GC::malloc(gcx = %p, size = %d bits = %x, ti = %s)\n", gcx, size, bits, debugTypeName(ti).ptr); + + assert(gcx); + //debug(PRINTF) printf("gcx.self = %x, pthread_self() = %x\n", gcx.self, pthread_self()); + + auto p = gcx.alloc(size + SENTINEL_EXTRA, alloc_size, bits, ti); + if (!p) + onOutOfMemoryErrorNoGC(); + + debug (SENTINEL) + { + p = sentinel_add(p); + sentinel_init(p, size); + alloc_size = size; + } + gcx.leakDetector.log_malloc(p, size); + bytesAllocated += alloc_size; + + debug(PRINTF) printf(" => p = %p\n", p); + return p; + } + + + BlkInfo qalloc( size_t size, uint bits, const scope TypeInfo ti) nothrow + { + + if (!size) + { + return BlkInfo.init; + } + + BlkInfo retval; + + retval.base = runLocked!(mallocNoSync, mallocTime, numMallocs)(size, bits, retval.size, ti); + + if (!(bits & BlkAttr.NO_SCAN)) + { + memset(retval.base + size, 0, retval.size - size); + } + + retval.attr = bits; + return retval; + } + + + void *calloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + if (!size) + { + return null; + } + + size_t localAllocSize = void; + + auto p = runLocked!(mallocNoSync, mallocTime, numMallocs)(size, bits, localAllocSize, ti); + + memset(p, 0, size); + if (!(bits & BlkAttr.NO_SCAN)) + { + memset(p + size, 0, localAllocSize - size); + } + + return p; + } + + + void *realloc(void *p, size_t size, uint bits, const TypeInfo ti) nothrow + { + size_t localAllocSize = void; + auto oldp = p; + + p = runLocked!(reallocNoSync, mallocTime, numMallocs)(p, size, bits, localAllocSize, ti); + + if (p && !(bits & BlkAttr.NO_SCAN)) + { + memset(p + size, 0, localAllocSize - size); + } + + return p; + } + + + // + // bits will be set to the resulting bits of the new block + // + private void *reallocNoSync(void *p, size_t size, ref uint bits, ref size_t alloc_size, const TypeInfo ti = null) nothrow + { + if (!size) + { + if (p) + freeNoSync(p); + alloc_size = 0; + return null; + } + if (!p) + return mallocNoSync(size, bits, alloc_size, ti); + + debug(PRINTF) printf("GC::realloc(p = %p, size = %llu)\n", p, cast(ulong)size); + + Pool *pool = gcx.findPool(p); + if (!pool) + return null; + + size_t psize; + size_t biti; + + debug(SENTINEL) + { + void* q = p; + p = sentinel_sub(p); + bool alwaysMalloc = true; + } + else + { + alias q = p; + enum alwaysMalloc = false; + } + + void* doMalloc() + { + if (!bits) + bits = pool.getBits(biti); + + void* p2 = mallocNoSync(size, bits, alloc_size, ti); + debug (SENTINEL) + psize = sentinel_size(q, psize); + if (psize < size) + size = psize; + //debug(PRINTF) printf("\tcopying %d bytes\n",size); + memcpy(p2, q, size); + freeNoSync(q); + return p2; + } + + if (pool.isLargeObject) + { + auto lpool = cast(LargeObjectPool*) pool; + auto psz = lpool.getPages(p); // get allocated size + if (psz == 0) + return null; // interior pointer + psize = psz * PAGESIZE; + + alias pagenum = biti; // happens to be the same, but rename for clarity + pagenum = lpool.pagenumOf(p); + + if (size <= PAGESIZE / 2 || alwaysMalloc) + return doMalloc(); // switching from large object pool to small object pool + + auto newsz = lpool.numPages(size); + if (newsz == psz) + { + // nothing to do + } + else if (newsz < psz) + { + // Shrink in place + debug (MEMSTOMP) memset(p + size, 0xF2, psize - size); + lpool.freePages(pagenum + newsz, psz - newsz); + lpool.mergeFreePageOffsets!(false, true)(pagenum + newsz, psz - newsz); + lpool.bPageOffsets[pagenum] = cast(uint) newsz; + } + else if (pagenum + newsz <= pool.npages) + { + // Attempt to expand in place (TODO: merge with extend) + if (lpool.pagetable[pagenum + psz] != B_FREE) + return doMalloc(); + + auto newPages = newsz - psz; + auto freesz = lpool.bPageOffsets[pagenum + psz]; + if (freesz < newPages) + return doMalloc(); // free range too small + + debug (MEMSTOMP) memset(p + psize, 0xF0, size - psize); + debug (PRINTF) printFreeInfo(pool); + memset(&lpool.pagetable[pagenum + psz], B_PAGEPLUS, newPages); + lpool.bPageOffsets[pagenum] = cast(uint) newsz; + for (auto offset = psz; offset < newsz; offset++) + lpool.bPageOffsets[pagenum + offset] = cast(uint) offset; + if (freesz > newPages) + lpool.setFreePageOffsets(pagenum + newsz, freesz - newPages); + gcx.usedLargePages += newPages; + lpool.freepages -= newPages; + debug (PRINTF) printFreeInfo(pool); + } + else + return doMalloc(); // does not fit into current pool + + alloc_size = newsz * PAGESIZE; + } + else + { + psize = (cast(SmallObjectPool*) pool).getSize(p); // get allocated bin size + if (psize == 0) + return null; // interior pointer + biti = cast(size_t)(p - pool.baseAddr) >> Pool.ShiftBy.Small; + if (pool.freebits.test (biti)) + return null; + + // allocate if new size is bigger or less than half + if (psize < size || psize > size * 2 || alwaysMalloc) + return doMalloc(); + + alloc_size = psize; + if (isPrecise) + pool.setPointerBitmapSmall(p, size, psize, bits, ti); + } + + if (bits) + { + pool.clrBits(biti, ~BlkAttr.NONE); + pool.setBits(biti, bits); + } + return p; + } + + + size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow + { + return runLocked!(extendNoSync, extendTime, numExtends)(p, minsize, maxsize, ti); + } + + + // + // + // + private size_t extendNoSync(void* p, size_t minsize, size_t maxsize, const TypeInfo ti = null) nothrow + in + { + assert(minsize <= maxsize); + } + do + { + debug(PRINTF) printf("GC::extend(p = %p, minsize = %zu, maxsize = %zu)\n", p, minsize, maxsize); + debug (SENTINEL) + { + return 0; + } + else + { + auto pool = gcx.findPool(p); + if (!pool || !pool.isLargeObject) + return 0; + + auto lpool = cast(LargeObjectPool*) pool; + size_t pagenum = lpool.pagenumOf(p); + if (lpool.pagetable[pagenum] != B_PAGE) + return 0; + + size_t psz = lpool.bPageOffsets[pagenum]; + assert(psz > 0); + + auto minsz = lpool.numPages(minsize); + auto maxsz = lpool.numPages(maxsize); + + if (pagenum + psz >= lpool.npages) + return 0; + if (lpool.pagetable[pagenum + psz] != B_FREE) + return 0; + + size_t freesz = lpool.bPageOffsets[pagenum + psz]; + if (freesz < minsz) + return 0; + size_t sz = freesz > maxsz ? maxsz : freesz; + debug (MEMSTOMP) memset(pool.baseAddr + (pagenum + psz) * PAGESIZE, 0xF0, sz * PAGESIZE); + memset(lpool.pagetable + pagenum + psz, B_PAGEPLUS, sz); + lpool.bPageOffsets[pagenum] = cast(uint) (psz + sz); + for (auto offset = psz; offset < psz + sz; offset++) + lpool.bPageOffsets[pagenum + offset] = cast(uint) offset; + if (freesz > sz) + lpool.setFreePageOffsets(pagenum + psz + sz, freesz - sz); + lpool.freepages -= sz; + gcx.usedLargePages += sz; + return (psz + sz) * PAGESIZE; + } + } + + + size_t reserve(size_t size) nothrow + { + if (!size) + { + return 0; + } + + return runLocked!(reserveNoSync, otherTime, numOthers)(size); + } + + + // + // + // + private size_t reserveNoSync(size_t size) nothrow + { + assert(size != 0); + assert(gcx); + + return gcx.reserve(size); + } + + + void free(void *p) nothrow @nogc + { + if (!p || _inFinalizer) + { + return; + } + + return runLocked!(freeNoSync, freeTime, numFrees)(p); + } + + + // + // + // + private void freeNoSync(void *p) nothrow @nogc + { + debug(PRINTF) printf("Freeing %p\n", cast(size_t) p); + assert (p); + + Pool* pool; + size_t pagenum; + Bins bin; + size_t biti; + + // Find which page it is in + pool = gcx.findPool(p); + if (!pool) // if not one of ours + return; // ignore + + pagenum = pool.pagenumOf(p); + + debug(PRINTF) printf("pool base = %p, PAGENUM = %d of %d, bin = %d\n", pool.baseAddr, pagenum, pool.npages, pool.pagetable[pagenum]); + debug(PRINTF) if (pool.isLargeObject) printf("Block size = %d\n", pool.bPageOffsets[pagenum]); + + bin = cast(Bins)pool.pagetable[pagenum]; + + // Verify that the pointer is at the beginning of a block, + // no action should be taken if p is an interior pointer + if (bin > B_PAGE) // B_PAGEPLUS or B_FREE + return; + size_t off = (sentinel_sub(p) - pool.baseAddr); + size_t base = baseOffset(off, bin); + if (off != base) + return; + + sentinel_Invariant(p); + auto q = p; + p = sentinel_sub(p); + size_t ssize; + + if (pool.isLargeObject) // if large alloc + { + biti = cast(size_t)(p - pool.baseAddr) >> pool.ShiftBy.Large; + assert(bin == B_PAGE); + auto lpool = cast(LargeObjectPool*) pool; + + // Free pages + size_t npages = lpool.bPageOffsets[pagenum]; + auto size = npages * PAGESIZE; + ssize = sentinel_size(q, size); + debug (MEMSTOMP) memset(p, 0xF2, size); + lpool.freePages(pagenum, npages); + lpool.mergeFreePageOffsets!(true, true)(pagenum, npages); + } + else + { + biti = cast(size_t)(p - pool.baseAddr) >> pool.ShiftBy.Small; + if (pool.freebits.test (biti)) + return; + // Add to free list + List *list = cast(List*)p; + + auto size = binsize[bin]; + ssize = sentinel_size(q, size); + debug (MEMSTOMP) memset(p, 0xF2, size); + + // in case the page hasn't been recovered yet, don't add the object to the free list + if (!gcx.recoverPool[bin] || pool.binPageChain[pagenum] == Pool.PageRecovered) + { + list.next = gcx.bucket[bin]; + list.pool = pool; + gcx.bucket[bin] = list; + } + pool.freebits.set(biti); + } + pool.clrBits(biti, ~BlkAttr.NONE); + + gcx.leakDetector.log_free(sentinel_add(p), ssize); + } + + + void* addrOf(void *p) nothrow @nogc + { + if (!p) + { + return null; + } + + return runLocked!(addrOfNoSync, otherTime, numOthers)(p); + } + + + // + // + // + void* addrOfNoSync(void *p) nothrow @nogc + { + if (!p) + { + return null; + } + + auto q = gcx.findBase(p); + if (q) + q = sentinel_add(q); + return q; + } + + + size_t sizeOf(void *p) nothrow @nogc + { + if (!p) + { + return 0; + } + + return runLocked!(sizeOfNoSync, otherTime, numOthers)(p); + } + + + // + // + // + private size_t sizeOfNoSync(void *p) nothrow @nogc + { + assert (p); + + debug (SENTINEL) + { + p = sentinel_sub(p); + size_t size = gcx.findSize(p); + return size ? size - SENTINEL_EXTRA : 0; + } + else + { + size_t size = gcx.findSize(p); + return size; + } + } + + + BlkInfo query(void *p) nothrow + { + if (!p) + { + BlkInfo i; + return i; + } + + return runLocked!(queryNoSync, otherTime, numOthers)(p); + } + + // + // + // + BlkInfo queryNoSync(void *p) nothrow + { + assert(p); + + BlkInfo info = gcx.getInfo(p); + debug(SENTINEL) + { + if (info.base) + { + info.base = sentinel_add(info.base); + info.size = *sentinel_psize(info.base); + } + } + return info; + } + + + /** + * Verify that pointer p: + * 1) belongs to this memory pool + * 2) points to the start of an allocated piece of memory + * 3) is not on a free list + */ + void check(void *p) nothrow + { + if (!p) + { + return; + } + + return runLocked!(checkNoSync, otherTime, numOthers)(p); + } + + + // + // + // + private void checkNoSync(void *p) nothrow + { + assert(p); + + sentinel_Invariant(p); + debug (PTRCHECK) + { + Pool* pool; + size_t pagenum; + Bins bin; + + p = sentinel_sub(p); + pool = gcx.findPool(p); + assert(pool); + pagenum = pool.pagenumOf(p); + bin = cast(Bins)pool.pagetable[pagenum]; + assert(bin <= B_PAGE); + assert(p == cast(void*)baseOffset(cast(size_t)p, bin)); + + debug (PTRCHECK2) + { + if (bin < B_PAGE) + { + // Check that p is not on a free list + List *list; + + for (list = gcx.bucket[bin]; list; list = list.next) + { + assert(cast(void*)list != p); + } + } + } + } + } + + + void addRoot(void *p) nothrow @nogc + { + if (!p) + { + return; + } + + gcx.addRoot(p); + } + + + void removeRoot(void *p) nothrow @nogc + { + if (!p) + { + return; + } + + gcx.removeRoot(p); + } + + + @property RootIterator rootIter() @nogc + { + return &gcx.rootsApply; + } + + + void addRange(void *p, size_t sz, const TypeInfo ti = null) nothrow @nogc + { + if (!p || !sz) + { + return; + } + + gcx.addRange(p, p + sz, ti); + } + + + void removeRange(void *p) nothrow @nogc + { + if (!p) + { + return; + } + + gcx.removeRange(p); + } + + + @property RangeIterator rangeIter() @nogc + { + return &gcx.rangesApply; + } + + + void runFinalizers(const scope void[] segment) nothrow + { + static void go(Gcx* gcx, const scope void[] segment) nothrow + { + gcx.runFinalizers(segment); + } + return runLocked!(go, otherTime, numOthers)(gcx, segment); + } + + + bool inFinalizer() nothrow @nogc + { + return _inFinalizer; + } + + + void collect() nothrow + { + fullCollect(); + } + + + void collectNoStack() nothrow + { + fullCollectNoStack(); + } + + + /** + * Do full garbage collection. + * Return number of pages free'd. + */ + size_t fullCollect() nothrow + { + debug(PRINTF) printf("GC.fullCollect()\n"); + + // Since a finalizer could launch a new thread, we always need to lock + // when collecting. + static size_t go(Gcx* gcx) nothrow + { + return gcx.fullcollect(false, true); // standard stop the world + } + immutable result = runLocked!go(gcx); + + version (none) + { + GCStats stats; + + getStats(stats); + debug(PRINTF) printf("heapSize = %zx, freeSize = %zx\n", + stats.heapSize, stats.freeSize); + } + + gcx.leakDetector.log_collect(); + return result; + } + + + /** + * do full garbage collection ignoring roots + */ + void fullCollectNoStack() nothrow + { + // Since a finalizer could launch a new thread, we always need to lock + // when collecting. + static size_t go(Gcx* gcx) nothrow + { + return gcx.fullcollect(true, true, true); // standard stop the world + } + runLocked!go(gcx); + } + + + void minimize() nothrow + { + static void go(Gcx* gcx) nothrow + { + gcx.minimize(); + } + runLocked!(go, otherTime, numOthers)(gcx); + } + + + core.memory.GC.Stats stats() nothrow + { + typeof(return) ret; + + runLocked!(getStatsNoSync, otherTime, numOthers)(ret); + + return ret; + } + + + core.memory.GC.ProfileStats profileStats() nothrow @trusted + { + typeof(return) ret; + + ret.numCollections = numCollections; + ret.totalCollectionTime = prepTime + markTime + sweepTime; + ret.totalPauseTime = pauseTime; + ret.maxCollectionTime = maxCollectionTime; + ret.maxPauseTime = maxPauseTime; + + return ret; + } + + + ulong allocatedInCurrentThread() nothrow + { + return bytesAllocated; + } + + + // + // + // + private void getStatsNoSync(out core.memory.GC.Stats stats) nothrow + { + foreach (pool; gcx.pooltable[0 .. gcx.npools]) + { + foreach (bin; pool.pagetable[0 .. pool.npages]) + { + if (bin == B_FREE) + stats.freeSize += PAGESIZE; + else + stats.usedSize += PAGESIZE; + } + } + + size_t freeListSize; + foreach (n; 0 .. B_PAGE) + { + immutable sz = binsize[n]; + for (List *list = gcx.bucket[n]; list; list = list.next) + freeListSize += sz; + + foreach (pool; gcx.pooltable[0 .. gcx.npools]) + { + if (pool.isLargeObject) + continue; + for (uint pn = pool.recoverPageFirst[n]; pn < pool.npages; pn = pool.binPageChain[pn]) + { + const bitbase = pn * PAGESIZE / 16; + const top = PAGESIZE - sz + 1; // ensure bytes available even if unaligned + for (size_t u = 0; u < top; u += sz) + if (pool.freebits.test(bitbase + u / 16)) + freeListSize += sz; + } + } + } + + stats.usedSize -= freeListSize; + stats.freeSize += freeListSize; + stats.allocatedInCurrentThread = bytesAllocated; + } +} + + +/* ============================ Gcx =============================== */ + +enum +{ PAGESIZE = 4096, +} + + +enum +{ + B_16, + B_32, + B_48, + B_64, + B_96, + B_128, + B_176, + B_256, + B_368, + B_512, + B_816, + B_1024, + B_1360, + B_2048, + B_NUMSMALL, + + B_PAGE = B_NUMSMALL,// start of large alloc + B_PAGEPLUS, // continuation of large alloc + B_FREE, // free page + B_MAX, +} + + +alias ubyte Bins; + + +struct List +{ + List *next; + Pool *pool; +} + +// non power of two sizes optimized for small remainder within page (<= 64 bytes) +immutable short[B_NUMSMALL + 1] binsize = [ 16, 32, 48, 64, 96, 128, 176, 256, 368, 512, 816, 1024, 1360, 2048, 4096 ]; +immutable short[PAGESIZE / 16][B_NUMSMALL + 1] binbase = calcBinBase(); + +short[PAGESIZE / 16][B_NUMSMALL + 1] calcBinBase() +{ + short[PAGESIZE / 16][B_NUMSMALL + 1] bin; + + foreach (i, size; binsize) + { + short end = (PAGESIZE / size) * size; + short bsz = size / 16; + foreach (off; 0..PAGESIZE/16) + { + // add the remainder to the last bin, so no check during scanning + // is needed if a false pointer targets that area + const base = (off - off % bsz) * 16; + bin[i][off] = cast(short)(base < end ? base : end - size); + } + } + return bin; +} + +size_t baseOffset(size_t offset, Bins bin) @nogc nothrow +{ + assert(bin <= B_PAGE); + return (offset & ~(PAGESIZE - 1)) + binbase[bin][(offset & (PAGESIZE - 1)) >> 4]; +} + +alias PageBits = GCBits.wordtype[PAGESIZE / 16 / GCBits.BITS_PER_WORD]; +static assert(PAGESIZE % (GCBits.BITS_PER_WORD * 16) == 0); + +// bitmask with bits set at base offsets of objects +immutable PageBits[B_NUMSMALL] baseOffsetBits = (){ + PageBits[B_NUMSMALL] bits; + foreach (bin; 0..B_NUMSMALL) + { + size_t size = binsize[bin]; + const top = PAGESIZE - size + 1; // ensure bytes available even if unaligned + for (size_t u = 0; u < top; u += size) + { + size_t biti = u / 16; + size_t off = biti / GCBits.BITS_PER_WORD; + size_t mod = biti % GCBits.BITS_PER_WORD; + bits[bin][off] |= GCBits.BITS_1 << mod; + } + } + return bits; +}(); + +private void set(ref PageBits bits, size_t i) @nogc pure nothrow +{ + assert(i < PageBits.sizeof * 8); + bts(bits.ptr, i); +} + +/* ============================ Gcx =============================== */ + +struct Gcx +{ + import core.internal.spinlock; + auto rootsLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); + auto rangesLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); + Treap!Root roots; + Treap!Range ranges; + bool minimizeAfterNextCollection = false; + version (COLLECT_FORK) + { + private pid_t markProcPid = 0; + bool shouldFork; + } + + debug(INVARIANT) bool initialized; + debug(INVARIANT) bool inCollection; + uint disabled; // turn off collections if >0 + + import core.internal.gc.pooltable; + private @property size_t npools() pure const nothrow { return pooltable.length; } + PoolTable!Pool pooltable; + + List*[B_NUMSMALL] bucket; // free list for each small size + + // run a collection when reaching those thresholds (number of used pages) + float smallCollectThreshold, largeCollectThreshold; + uint usedSmallPages, usedLargePages; + // total number of mapped pages + uint mappedPages; + + debug (LOGGING) + LeakDetector leakDetector; + else + alias leakDetector = LeakDetector; + + SmallObjectPool*[B_NUMSMALL] recoverPool; + version (Posix) __gshared Gcx* instance; + + void initialize() + { + (cast(byte*)&this)[0 .. Gcx.sizeof] = 0; + leakDetector.initialize(&this); + roots.initialize(0x243F6A8885A308D3UL); + ranges.initialize(0x13198A2E03707344UL); + smallCollectThreshold = largeCollectThreshold = 0.0f; + usedSmallPages = usedLargePages = 0; + mappedPages = 0; + //printf("gcx = %p, self = %x\n", &this, self); + version (Posix) + { + import core.sys.posix.pthread : pthread_atfork; + instance = &this; + __gshared atforkHandlersInstalled = false; + if (!atforkHandlersInstalled) + { + pthread_atfork( + &_d_gcx_atfork_prepare, + &_d_gcx_atfork_parent, + &_d_gcx_atfork_child); + atforkHandlersInstalled = true; + } + } + debug(INVARIANT) initialized = true; + version (COLLECT_FORK) + shouldFork = config.fork; + + } + + void Dtor() + { + if (config.profile) + { + printf("\tNumber of collections: %llu\n", cast(ulong)numCollections); + printf("\tTotal GC prep time: %lld milliseconds\n", + prepTime.total!("msecs")); + printf("\tTotal mark time: %lld milliseconds\n", + markTime.total!("msecs")); + printf("\tTotal sweep time: %lld milliseconds\n", + sweepTime.total!("msecs")); + long maxPause = maxPauseTime.total!("msecs"); + printf("\tMax Pause Time: %lld milliseconds\n", maxPause); + long gcTime = (sweepTime + markTime + prepTime).total!("msecs"); + printf("\tGrand total GC time: %lld milliseconds\n", gcTime); + long pauseTime = (markTime + prepTime).total!("msecs"); + + char[30] apitxt = void; + apitxt[0] = 0; + debug(PROFILE_API) if (config.profile > 1) + { + static Duration toDuration(long dur) + { + return MonoTime(dur) - MonoTime(0); + } + + printf("\n"); + printf("\tmalloc: %llu calls, %lld ms\n", cast(ulong)numMallocs, toDuration(mallocTime).total!"msecs"); + printf("\trealloc: %llu calls, %lld ms\n", cast(ulong)numReallocs, toDuration(reallocTime).total!"msecs"); + printf("\tfree: %llu calls, %lld ms\n", cast(ulong)numFrees, toDuration(freeTime).total!"msecs"); + printf("\textend: %llu calls, %lld ms\n", cast(ulong)numExtends, toDuration(extendTime).total!"msecs"); + printf("\tother: %llu calls, %lld ms\n", cast(ulong)numOthers, toDuration(otherTime).total!"msecs"); + printf("\tlock time: %lld ms\n", toDuration(lockTime).total!"msecs"); + + long apiTime = mallocTime + reallocTime + freeTime + extendTime + otherTime + lockTime; + printf("\tGC API: %lld ms\n", toDuration(apiTime).total!"msecs"); + sprintf(apitxt.ptr, " API%5ld ms", toDuration(apiTime).total!"msecs"); + } + + printf("GC summary:%5lld MB,%5lld GC%5lld ms, Pauses%5lld ms <%5lld ms%s\n", + cast(long) maxPoolMemory >> 20, cast(ulong)numCollections, gcTime, + pauseTime, maxPause, apitxt.ptr); + } + + version (Posix) + instance = null; + version (COLLECT_PARALLEL) + stopScanThreads(); + + debug(INVARIANT) initialized = false; + + for (size_t i = 0; i < npools; i++) + { + Pool *pool = pooltable[i]; + mappedPages -= pool.npages; + pool.Dtor(); + cstdlib.free(pool); + } + assert(!mappedPages); + pooltable.Dtor(); + + roots.removeAll(); + ranges.removeAll(); + toscanConservative.reset(); + toscanPrecise.reset(); + } + + + void Invariant() const { } + + debug(INVARIANT) + invariant() + { + if (initialized) + { + //printf("Gcx.invariant(): this = %p\n", &this); + pooltable.Invariant(); + for (size_t p = 0; p < pooltable.length; p++) + if (pooltable.pools[p].isLargeObject) + (cast(LargeObjectPool*)(pooltable.pools[p])).Invariant(); + else + (cast(SmallObjectPool*)(pooltable.pools[p])).Invariant(); + + if (!inCollection) + (cast()rangesLock).lock(); + foreach (range; ranges) + { + assert(range.pbot); + assert(range.ptop); + assert(range.pbot <= range.ptop); + } + if (!inCollection) + (cast()rangesLock).unlock(); + + for (size_t i = 0; i < B_NUMSMALL; i++) + { + size_t j = 0; + List* prev, pprev, ppprev; // keep a short history to inspect in the debugger + for (auto list = cast(List*)bucket[i]; list; list = list.next) + { + auto pool = list.pool; + auto biti = cast(size_t)(cast(void*)list - pool.baseAddr) >> Pool.ShiftBy.Small; + assert(pool.freebits.test(biti)); + ppprev = pprev; + pprev = prev; + prev = list; + } + } + } + } + + @property bool collectInProgress() const nothrow + { + version (COLLECT_FORK) + return markProcPid != 0; + else + return false; + } + + + /** + * + */ + void addRoot(void *p) nothrow @nogc + { + rootsLock.lock(); + scope (failure) rootsLock.unlock(); + roots.insert(Root(p)); + rootsLock.unlock(); + } + + + /** + * + */ + void removeRoot(void *p) nothrow @nogc + { + rootsLock.lock(); + scope (failure) rootsLock.unlock(); + roots.remove(Root(p)); + rootsLock.unlock(); + } + + + /** + * + */ + int rootsApply(scope int delegate(ref Root) nothrow dg) nothrow + { + rootsLock.lock(); + scope (failure) rootsLock.unlock(); + auto ret = roots.opApply(dg); + rootsLock.unlock(); + return ret; + } + + + /** + * + */ + void addRange(void *pbot, void *ptop, const TypeInfo ti) nothrow @nogc + { + //debug(PRINTF) printf("Thread %x ", pthread_self()); + debug(PRINTF) printf("%p.Gcx::addRange(%p, %p)\n", &this, pbot, ptop); + rangesLock.lock(); + scope (failure) rangesLock.unlock(); + ranges.insert(Range(pbot, ptop)); + rangesLock.unlock(); + } + + + /** + * + */ + void removeRange(void *pbot) nothrow @nogc + { + //debug(PRINTF) printf("Thread %x ", pthread_self()); + debug(PRINTF) printf("Gcx.removeRange(%p)\n", pbot); + rangesLock.lock(); + scope (failure) rangesLock.unlock(); + ranges.remove(Range(pbot, pbot)); // only pbot is used, see Range.opCmp + rangesLock.unlock(); + + // debug(PRINTF) printf("Wrong thread\n"); + // This is a fatal error, but ignore it. + // The problem is that we can get a Close() call on a thread + // other than the one the range was allocated on. + //assert(zero); + } + + /** + * + */ + int rangesApply(scope int delegate(ref Range) nothrow dg) nothrow + { + rangesLock.lock(); + scope (failure) rangesLock.unlock(); + auto ret = ranges.opApply(dg); + rangesLock.unlock(); + return ret; + } + + + /** + * + */ + void runFinalizers(const scope void[] segment) nothrow + { + ConservativeGC._inFinalizer = true; + scope (failure) ConservativeGC._inFinalizer = false; + + foreach (pool; pooltable[0 .. npools]) + { + if (!pool.finals.nbits) continue; + + if (pool.isLargeObject) + { + auto lpool = cast(LargeObjectPool*) pool; + lpool.runFinalizers(segment); + } + else + { + auto spool = cast(SmallObjectPool*) pool; + spool.runFinalizers(segment); + } + } + ConservativeGC._inFinalizer = false; + } + + Pool* findPool(void* p) pure nothrow @nogc + { + return pooltable.findPool(p); + } + + /** + * Find base address of block containing pointer p. + * Returns null if not a gc'd pointer + */ + void* findBase(void *p) nothrow @nogc + { + Pool *pool; + + pool = findPool(p); + if (pool) + return pool.findBase(p); + return null; + } + + + /** + * Find size of pointer p. + * Returns 0 if not a gc'd pointer + */ + size_t findSize(void *p) nothrow @nogc + { + Pool* pool = findPool(p); + if (pool) + return pool.slGetSize(p); + return 0; + } + + /** + * + */ + BlkInfo getInfo(void* p) nothrow + { + Pool* pool = findPool(p); + if (pool) + return pool.slGetInfo(p); + return BlkInfo(); + } + + /** + * Computes the bin table using CTFE. + */ + static byte[2049] ctfeBins() nothrow + { + byte[2049] ret; + size_t p = 0; + for (Bins b = B_16; b <= B_2048; b++) + for ( ; p <= binsize[b]; p++) + ret[p] = b; + + return ret; + } + + static const byte[2049] binTable = ctfeBins(); + + /** + * Allocate a new pool of at least size bytes. + * Sort it into pooltable[]. + * Mark all memory in the pool as B_FREE. + * Return the actual number of bytes reserved or 0 on error. + */ + size_t reserve(size_t size) nothrow + { + size_t npages = (size + PAGESIZE - 1) / PAGESIZE; + + // Assume reserve() is for small objects. + Pool* pool = newPool(npages, false); + + if (!pool) + return 0; + return pool.npages * PAGESIZE; + } + + /** + * Update the thresholds for when to collect the next time + */ + void updateCollectThresholds() nothrow + { + static float max(float a, float b) nothrow + { + return a >= b ? a : b; + } + + // instantly increases, slowly decreases + static float smoothDecay(float oldVal, float newVal) nothrow + { + // decay to 63.2% of newVal over 5 collections + // http://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter + enum alpha = 1.0 / (5 + 1); + immutable decay = (newVal - oldVal) * alpha + oldVal; + return max(newVal, decay); + } + + immutable smTarget = usedSmallPages * config.heapSizeFactor; + smallCollectThreshold = smoothDecay(smallCollectThreshold, smTarget); + immutable lgTarget = usedLargePages * config.heapSizeFactor; + largeCollectThreshold = smoothDecay(largeCollectThreshold, lgTarget); + } + + /** + * Minimizes physical memory usage by returning free pools to the OS. + */ + void minimize() nothrow + { + debug(PRINTF) printf("Minimizing.\n"); + + foreach (pool; pooltable.minimize()) + { + debug(PRINTF) printFreeInfo(pool); + mappedPages -= pool.npages; + pool.Dtor(); + cstdlib.free(pool); + } + + debug(PRINTF) printf("Done minimizing.\n"); + } + + private @property bool lowMem() const nothrow + { + return isLowOnMem(cast(size_t)mappedPages * PAGESIZE); + } + + void* alloc(size_t size, ref size_t alloc_size, uint bits, const TypeInfo ti) nothrow + { + return size <= PAGESIZE/2 ? smallAlloc(size, alloc_size, bits, ti) + : bigAlloc(size, alloc_size, bits, ti); + } + + void* smallAlloc(size_t size, ref size_t alloc_size, uint bits, const TypeInfo ti) nothrow + { + immutable bin = binTable[size]; + alloc_size = binsize[bin]; + + void* p = bucket[bin]; + if (p) + goto L_hasBin; + + if (recoverPool[bin]) + recoverNextPage(bin); + + bool tryAlloc() nothrow + { + if (!bucket[bin]) + { + bucket[bin] = allocPage(bin); + if (!bucket[bin]) + return false; + } + p = bucket[bin]; + return true; + } + + if (!tryAlloc()) + { + if (!lowMem && (disabled || usedSmallPages < smallCollectThreshold)) + { + // disabled or threshold not reached => allocate a new pool instead of collecting + if (!newPool(1, false)) + { + // out of memory => try to free some memory + fullcollect(false, true); // stop the world + if (lowMem) + minimize(); + recoverNextPage(bin); + } + } + else if (usedSmallPages > 0) + { + fullcollect(); + if (lowMem) + minimize(); + recoverNextPage(bin); + } + // tryAlloc will succeed if a new pool was allocated above, if it fails allocate a new pool now + if (!tryAlloc() && (!newPool(1, false) || !tryAlloc())) + // out of luck or memory + onOutOfMemoryErrorNoGC(); + } + assert(p !is null); + L_hasBin: + // Return next item from free list + bucket[bin] = (cast(List*)p).next; + auto pool = (cast(List*)p).pool; + + auto biti = (p - pool.baseAddr) >> pool.shiftBy; + assert(pool.freebits.test(biti)); + if (collectInProgress) + pool.mark.setLocked(biti); // be sure that the child is aware of the page being used + pool.freebits.clear(biti); + if (bits) + pool.setBits(biti, bits); + //debug(PRINTF) printf("\tmalloc => %p\n", p); + debug (MEMSTOMP) memset(p, 0xF0, alloc_size); + + if (ConservativeGC.isPrecise) + { + debug(SENTINEL) + pool.setPointerBitmapSmall(sentinel_add(p), size - SENTINEL_EXTRA, size - SENTINEL_EXTRA, bits, ti); + else + pool.setPointerBitmapSmall(p, size, alloc_size, bits, ti); + } + return p; + } + + /** + * Allocate a chunk of memory that is larger than a page. + * Return null if out of memory. + */ + void* bigAlloc(size_t size, ref size_t alloc_size, uint bits, const TypeInfo ti = null) nothrow + { + debug(PRINTF) printf("In bigAlloc. Size: %d\n", size); + + LargeObjectPool* pool; + size_t pn; + immutable npages = LargeObjectPool.numPages(size); + if (npages == size_t.max) + onOutOfMemoryErrorNoGC(); // size just below size_t.max requested + + bool tryAlloc() nothrow + { + foreach (p; pooltable[0 .. npools]) + { + if (!p.isLargeObject || p.freepages < npages) + continue; + auto lpool = cast(LargeObjectPool*) p; + if ((pn = lpool.allocPages(npages)) == OPFAIL) + continue; + pool = lpool; + return true; + } + return false; + } + + bool tryAllocNewPool() nothrow + { + pool = cast(LargeObjectPool*) newPool(npages, true); + if (!pool) return false; + pn = pool.allocPages(npages); + assert(pn != OPFAIL); + return true; + } + + if (!tryAlloc()) + { + if (!lowMem && (disabled || usedLargePages < largeCollectThreshold)) + { + // disabled or threshold not reached => allocate a new pool instead of collecting + if (!tryAllocNewPool()) + { + // disabled but out of memory => try to free some memory + minimizeAfterNextCollection = true; + fullcollect(false, true); + } + } + else if (usedLargePages > 0) + { + minimizeAfterNextCollection = true; + fullcollect(); + } + // If alloc didn't yet succeed retry now that we collected/minimized + if (!pool && !tryAlloc() && !tryAllocNewPool()) + // out of luck or memory + return null; + } + assert(pool); + + debug(PRINTF) printFreeInfo(&pool.base); + if (collectInProgress) + pool.mark.setLocked(pn); + usedLargePages += npages; + + debug(PRINTF) printFreeInfo(&pool.base); + + auto p = pool.baseAddr + pn * PAGESIZE; + debug(PRINTF) printf("Got large alloc: %p, pt = %d, np = %d\n", p, pool.pagetable[pn], npages); + debug (MEMSTOMP) memset(p, 0xF1, size); + alloc_size = npages * PAGESIZE; + //debug(PRINTF) printf("\tp = %p\n", p); + + if (bits) + pool.setBits(pn, bits); + + if (ConservativeGC.isPrecise) + { + // an array of classes is in fact an array of pointers + immutable(void)* rtinfo; + if (!ti) + rtinfo = rtinfoHasPointers; + else if ((bits & BlkAttr.APPENDABLE) && (typeid(ti) is typeid(TypeInfo_Class))) + rtinfo = rtinfoHasPointers; + else + rtinfo = ti.rtInfo(); + pool.rtinfo[pn] = cast(immutable(size_t)*)rtinfo; + } + + return p; + } + + + /** + * Allocate a new pool with at least npages in it. + * Sort it into pooltable[]. + * Return null if failed. + */ + Pool *newPool(size_t npages, bool isLargeObject) nothrow + { + //debug(PRINTF) printf("************Gcx::newPool(npages = %d)****************\n", npages); + + // Minimum of POOLSIZE + size_t minPages = config.minPoolSize / PAGESIZE; + if (npages < minPages) + npages = minPages; + else if (npages > minPages) + { // Give us 150% of requested size, so there's room to extend + auto n = npages + (npages >> 1); + if (n < size_t.max/PAGESIZE) + npages = n; + } + + // Allocate successively larger pools up to 8 megs + if (npools) + { size_t n; + + n = config.minPoolSize + config.incPoolSize * npools; + if (n > config.maxPoolSize) + n = config.maxPoolSize; // cap pool size + n /= PAGESIZE; // convert bytes to pages + if (npages < n) + npages = n; + } + + //printf("npages = %d\n", npages); + + auto pool = cast(Pool *)cstdlib.calloc(1, isLargeObject ? LargeObjectPool.sizeof : SmallObjectPool.sizeof); + if (pool) + { + pool.initialize(npages, isLargeObject); + if (collectInProgress) + pool.mark.setAll(); + if (!pool.baseAddr || !pooltable.insert(pool)) + { + pool.Dtor(); + cstdlib.free(pool); + return null; + } + } + + mappedPages += npages; + + if (config.profile) + { + if (cast(size_t)mappedPages * PAGESIZE > maxPoolMemory) + maxPoolMemory = cast(size_t)mappedPages * PAGESIZE; + } + return pool; + } + + /** + * Allocate a page of bin's. + * Returns: + * head of a single linked list of new entries + */ + List* allocPage(Bins bin) nothrow + { + //debug(PRINTF) printf("Gcx::allocPage(bin = %d)\n", bin); + for (size_t n = 0; n < npools; n++) + { + Pool* pool = pooltable[n]; + if (pool.isLargeObject) + continue; + if (List* p = (cast(SmallObjectPool*)pool).allocPage(bin)) + { + ++usedSmallPages; + return p; + } + } + return null; + } + + static struct ScanRange(bool precise) + { + void* pbot; + void* ptop; + static if (precise) + { + void** pbase; // start of memory described by ptrbitmap + size_t* ptrbmp; // bits from is_pointer or rtinfo + size_t bmplength; // number of valid bits + } + } + + static struct ToScanStack(RANGE) + { + nothrow: + @disable this(this); + auto stackLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); + + void reset() + { + _length = 0; + if (_p) + { + os_mem_unmap(_p, _cap * RANGE.sizeof); + _p = null; + } + _cap = 0; + } + void clear() + { + _length = 0; + } + + void push(RANGE rng) + { + if (_length == _cap) grow(); + _p[_length++] = rng; + } + + RANGE pop() + in { assert(!empty); } + do + { + return _p[--_length]; + } + + bool popLocked(ref RANGE rng) + { + if (_length == 0) + return false; + + stackLock.lock(); + scope(exit) stackLock.unlock(); + if (_length == 0) + return false; + rng = _p[--_length]; + return true; + } + + ref inout(RANGE) opIndex(size_t idx) inout + in { assert(idx < _length); } + do + { + return _p[idx]; + } + + @property size_t length() const { return _length; } + @property bool empty() const { return !length; } + + private: + void grow() + { + pragma(inline, false); + + enum initSize = 64 * 1024; // Windows VirtualAlloc granularity + immutable ncap = _cap ? 2 * _cap : initSize / RANGE.sizeof; + auto p = cast(RANGE*)os_mem_map(ncap * RANGE.sizeof); + if (p is null) onOutOfMemoryErrorNoGC(); + if (_p !is null) + { + p[0 .. _length] = _p[0 .. _length]; + os_mem_unmap(_p, _cap * RANGE.sizeof); + } + _p = p; + _cap = ncap; + } + + size_t _length; + RANGE* _p; + size_t _cap; + } + + ToScanStack!(ScanRange!false) toscanConservative; + ToScanStack!(ScanRange!true) toscanPrecise; + + template scanStack(bool precise) + { + static if (precise) + alias scanStack = toscanPrecise; + else + alias scanStack = toscanConservative; + } + + /** + * Search a range of memory values and mark any pointers into the GC pool. + */ + private void mark(bool precise, bool parallel, bool shared_mem)(ScanRange!precise rng) scope nothrow + { + alias toscan = scanStack!precise; + + debug(MARK_PRINTF) + printf("marking range: [%p..%p] (%#llx)\n", pbot, ptop, cast(long)(ptop - pbot)); + + // limit the amount of ranges added to the toscan stack + enum FANOUT_LIMIT = 32; + size_t stackPos; + ScanRange!precise[FANOUT_LIMIT] stack = void; + + size_t pcache = 0; + + // let dmd allocate a register for this.pools + auto pools = pooltable.pools; + const highpool = pooltable.npools - 1; + const minAddr = pooltable.minAddr; + size_t memSize = pooltable.maxAddr - minAddr; + Pool* pool = null; + + // properties of allocation pointed to + ScanRange!precise tgt = void; + + for (;;) + { + auto p = *cast(void**)(rng.pbot); + + debug(MARK_PRINTF) printf("\tmark %p: %p\n", rng.pbot, p); + + if (cast(size_t)(p - minAddr) < memSize && + (cast(size_t)p & ~cast(size_t)(PAGESIZE-1)) != pcache) + { + static if (precise) if (rng.pbase) + { + size_t bitpos = cast(void**)rng.pbot - rng.pbase; + while (bitpos >= rng.bmplength) + { + bitpos -= rng.bmplength; + rng.pbase += rng.bmplength; + } + import core.bitop; + if (!core.bitop.bt(rng.ptrbmp, bitpos)) + { + debug(MARK_PRINTF) printf("\t\tskipping non-pointer\n"); + goto LnextPtr; + } + } + + if (!pool || p < pool.baseAddr || p >= pool.topAddr) + { + size_t low = 0; + size_t high = highpool; + while (true) + { + size_t mid = (low + high) >> 1; + pool = pools[mid]; + if (p < pool.baseAddr) + high = mid - 1; + else if (p >= pool.topAddr) + low = mid + 1; + else break; + + if (low > high) + goto LnextPtr; + } + } + size_t offset = cast(size_t)(p - pool.baseAddr); + size_t biti = void; + size_t pn = offset / PAGESIZE; + size_t bin = pool.pagetable[pn]; // not Bins to avoid multiple size extension instructions + + debug(MARK_PRINTF) + printf("\t\tfound pool %p, base=%p, pn = %lld, bin = %d\n", pool, pool.baseAddr, cast(long)pn, bin); + + // Adjust bit to be at start of allocated memory block + if (bin < B_PAGE) + { + // We don't care abou setting pointsToBase correctly + // because it's ignored for small object pools anyhow. + auto offsetBase = baseOffset(offset, cast(Bins)bin); + biti = offsetBase >> Pool.ShiftBy.Small; + //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); + + if (!pool.mark.testAndSet!shared_mem(biti) && !pool.noscan.test(biti)) + { + tgt.pbot = pool.baseAddr + offsetBase; + tgt.ptop = tgt.pbot + binsize[bin]; + static if (precise) + { + tgt.pbase = cast(void**)pool.baseAddr; + tgt.ptrbmp = pool.is_pointer.data; + tgt.bmplength = size_t.max; // no repetition + } + goto LaddRange; + } + } + else if (bin == B_PAGE) + { + biti = offset >> Pool.ShiftBy.Large; + //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); + + pcache = cast(size_t)p & ~cast(size_t)(PAGESIZE-1); + tgt.pbot = cast(void*)pcache; + + // For the NO_INTERIOR attribute. This tracks whether + // the pointer is an interior pointer or points to the + // base address of a block. + if (tgt.pbot != sentinel_sub(p) && pool.nointerior.nbits && pool.nointerior.test(biti)) + goto LnextPtr; + + if (!pool.mark.testAndSet!shared_mem(biti) && !pool.noscan.test(biti)) + { + tgt.ptop = tgt.pbot + (cast(LargeObjectPool*)pool).getSize(pn); + goto LaddLargeRange; + } + } + else if (bin == B_PAGEPLUS) + { + pn -= pool.bPageOffsets[pn]; + biti = pn * (PAGESIZE >> Pool.ShiftBy.Large); + + pcache = cast(size_t)p & ~cast(size_t)(PAGESIZE-1); + if (pool.nointerior.nbits && pool.nointerior.test(biti)) + goto LnextPtr; + + if (!pool.mark.testAndSet!shared_mem(biti) && !pool.noscan.test(biti)) + { + tgt.pbot = pool.baseAddr + (pn * PAGESIZE); + tgt.ptop = tgt.pbot + (cast(LargeObjectPool*)pool).getSize(pn); + LaddLargeRange: + static if (precise) + { + auto rtinfo = pool.rtinfo[biti]; + if (rtinfo is rtinfoNoPointers) + goto LnextPtr; // only if inconsistent with noscan + if (rtinfo is rtinfoHasPointers) + { + tgt.pbase = null; // conservative + } + else + { + tgt.ptrbmp = cast(size_t*)rtinfo; + size_t element_size = *tgt.ptrbmp++; + tgt.bmplength = (element_size + (void*).sizeof - 1) / (void*).sizeof; + assert(tgt.bmplength); + + debug(SENTINEL) + tgt.pbot = sentinel_add(tgt.pbot); + if (pool.appendable.test(biti)) + { + // take advantage of knowing array layout in rt.lifetime + void* arrtop = tgt.pbot + 16 + *cast(size_t*)tgt.pbot; + assert (arrtop > tgt.pbot && arrtop <= tgt.ptop); + tgt.pbot += 16; + tgt.ptop = arrtop; + } + else + { + tgt.ptop = tgt.pbot + element_size; + } + tgt.pbase = cast(void**)tgt.pbot; + } + } + goto LaddRange; + } + } + else + { + // Don't mark bits in B_FREE pages + assert(bin == B_FREE); + } + } + LnextPtr: + rng.pbot += (void*).sizeof; + if (rng.pbot < rng.ptop) + continue; + + LnextRange: + if (stackPos) + { + // pop range from local stack and recurse + rng = stack[--stackPos]; + } + else + { + static if (parallel) + { + if (!toscan.popLocked(rng)) + break; // nothing more to do + } + else + { + if (toscan.empty) + break; // nothing more to do + + // pop range from global stack and recurse + rng = toscan.pop(); + } + } + // printf(" pop [%p..%p] (%#zx)\n", p1, p2, cast(size_t)p2 - cast(size_t)p1); + goto LcontRange; + + LaddRange: + rng.pbot += (void*).sizeof; + if (rng.pbot < rng.ptop) + { + if (stackPos < stack.length) + { + stack[stackPos] = tgt; + stackPos++; + continue; + } + static if (parallel) + { + toscan.stackLock.lock(); + scope(exit) toscan.stackLock.unlock(); + } + toscan.push(rng); + // reverse order for depth-first-order traversal + foreach_reverse (ref range; stack) + toscan.push(range); + stackPos = 0; + } + LendOfRange: + // continue with last found range + rng = tgt; + + LcontRange: + pcache = 0; + } + } + + void markConservative(bool shared_mem)(void *pbot, void *ptop) scope nothrow + { + if (pbot < ptop) + mark!(false, false, shared_mem)(ScanRange!false(pbot, ptop)); + } + + void markPrecise(bool shared_mem)(void *pbot, void *ptop) scope nothrow + { + if (pbot < ptop) + mark!(true, false, shared_mem)(ScanRange!true(pbot, ptop, null)); + } + + version (COLLECT_PARALLEL) + ToScanStack!(void*) toscanRoots; + + version (COLLECT_PARALLEL) + void collectRoots(void *pbot, void *ptop) scope nothrow + { + const minAddr = pooltable.minAddr; + size_t memSize = pooltable.maxAddr - minAddr; + + for (auto p = cast(void**)pbot; cast(void*)p < ptop; p++) + { + auto ptr = *p; + if (cast(size_t)(ptr - minAddr) < memSize) + toscanRoots.push(ptr); + } + } + + // collection step 1: prepare freebits and mark bits + void prepare() nothrow + { + debug(COLLECT_PRINTF) printf("preparing mark.\n"); + + for (size_t n = 0; n < npools; n++) + { + Pool* pool = pooltable[n]; + if (pool.isLargeObject) + pool.mark.zero(); + else + pool.mark.copy(&pool.freebits); + } + } + + // collection step 2: mark roots and heap + void markAll(alias markFn)(bool nostack) nothrow + { + if (!nostack) + { + debug(COLLECT_PRINTF) printf("\tscan stacks.\n"); + // Scan stacks and registers for each paused thread + thread_scanAll(&markFn); + } + + // Scan roots[] + debug(COLLECT_PRINTF) printf("\tscan roots[]\n"); + foreach (root; roots) + { + markFn(cast(void*)&root.proot, cast(void*)(&root.proot + 1)); + } + + // Scan ranges[] + debug(COLLECT_PRINTF) printf("\tscan ranges[]\n"); + //log++; + foreach (range; ranges) + { + debug(COLLECT_PRINTF) printf("\t\t%p .. %p\n", range.pbot, range.ptop); + markFn(range.pbot, range.ptop); + } + //log--; + } + + version (COLLECT_PARALLEL) + void collectAllRoots(bool nostack) nothrow + { + if (!nostack) + { + debug(COLLECT_PRINTF) printf("\tcollect stacks.\n"); + // Scan stacks and registers for each paused thread + thread_scanAll(&collectRoots); + } + + // Scan roots[] + debug(COLLECT_PRINTF) printf("\tcollect roots[]\n"); + foreach (root; roots) + { + toscanRoots.push(root); + } + + // Scan ranges[] + debug(COLLECT_PRINTF) printf("\tcollect ranges[]\n"); + foreach (range; ranges) + { + debug(COLLECT_PRINTF) printf("\t\t%p .. %p\n", range.pbot, range.ptop); + collectRoots(range.pbot, range.ptop); + } + } + + // collection step 3: finalize unreferenced objects, recover full pages with no live objects + size_t sweep() nothrow + { + // Free up everything not marked + debug(COLLECT_PRINTF) printf("\tfree'ing\n"); + size_t freedLargePages; + size_t freedSmallPages; + size_t freed; + for (size_t n = 0; n < npools; n++) + { + size_t pn; + Pool* pool = pooltable[n]; + + if (pool.isLargeObject) + { + auto lpool = cast(LargeObjectPool*)pool; + size_t numFree = 0; + size_t npages; + for (pn = 0; pn < pool.npages; pn += npages) + { + npages = pool.bPageOffsets[pn]; + Bins bin = cast(Bins)pool.pagetable[pn]; + if (bin == B_FREE) + { + numFree += npages; + continue; + } + assert(bin == B_PAGE); + size_t biti = pn; + + if (!pool.mark.test(biti)) + { + void *p = pool.baseAddr + pn * PAGESIZE; + void* q = sentinel_add(p); + sentinel_Invariant(q); + + if (pool.finals.nbits && pool.finals.clear(biti)) + { + size_t size = npages * PAGESIZE - SENTINEL_EXTRA; + uint attr = pool.getBits(biti); + rt_finalizeFromGC(q, sentinel_size(q, size), attr); + } + + pool.clrBits(biti, ~BlkAttr.NONE ^ BlkAttr.FINALIZE); + + debug(COLLECT_PRINTF) printf("\tcollecting big %p\n", p); + leakDetector.log_free(q, sentinel_size(q, npages * PAGESIZE - SENTINEL_EXTRA)); + pool.pagetable[pn..pn+npages] = B_FREE; + if (pn < pool.searchStart) pool.searchStart = pn; + freedLargePages += npages; + pool.freepages += npages; + numFree += npages; + + debug (MEMSTOMP) memset(p, 0xF3, npages * PAGESIZE); + // Don't need to update searchStart here because + // pn is guaranteed to be greater than last time + // we updated it. + + pool.largestFree = pool.freepages; // invalidate + } + else + { + if (numFree > 0) + { + lpool.setFreePageOffsets(pn - numFree, numFree); + numFree = 0; + } + } + } + if (numFree > 0) + lpool.setFreePageOffsets(pn - numFree, numFree); + } + else + { + // reinit chain of pages to rebuild free list + pool.recoverPageFirst[] = cast(uint)pool.npages; + + for (pn = 0; pn < pool.npages; pn++) + { + Bins bin = cast(Bins)pool.pagetable[pn]; + + if (bin < B_PAGE) + { + auto freebitsdata = pool.freebits.data + pn * PageBits.length; + auto markdata = pool.mark.data + pn * PageBits.length; + + // the entries to free are allocated objects (freebits == false) + // that are not marked (mark == false) + PageBits toFree; + static foreach (w; 0 .. PageBits.length) + toFree[w] = (~freebitsdata[w] & ~markdata[w]); + + // the page is unchanged if there is nothing to free + bool unchanged = true; + static foreach (w; 0 .. PageBits.length) + unchanged = unchanged && (toFree[w] == 0); + if (unchanged) + { + bool hasDead = false; + static foreach (w; 0 .. PageBits.length) + hasDead = hasDead || (~freebitsdata[w] != baseOffsetBits[bin][w]); + if (hasDead) + { + // add to recover chain + pool.binPageChain[pn] = pool.recoverPageFirst[bin]; + pool.recoverPageFirst[bin] = cast(uint)pn; + } + else + { + pool.binPageChain[pn] = Pool.PageRecovered; + } + continue; + } + + // the page can be recovered if all of the allocated objects (freebits == false) + // are freed + bool recoverPage = true; + static foreach (w; 0 .. PageBits.length) + recoverPage = recoverPage && (~freebitsdata[w] == toFree[w]); + + // We need to loop through each object if any have a finalizer, + // or, if any of the debug hooks are enabled. + bool doLoop = false; + debug (SENTINEL) + doLoop = true; + else version (assert) + doLoop = true; + else debug (COLLECT_PRINTF) // need output for each object + doLoop = true; + else debug (LOGGING) + doLoop = true; + else debug (MEMSTOMP) + doLoop = true; + else if (pool.finals.data) + { + // finalizers must be called on objects that are about to be freed + auto finalsdata = pool.finals.data + pn * PageBits.length; + static foreach (w; 0 .. PageBits.length) + doLoop = doLoop || (toFree[w] & finalsdata[w]) != 0; + } + + if (doLoop) + { + immutable size = binsize[bin]; + void *p = pool.baseAddr + pn * PAGESIZE; + immutable base = pn * (PAGESIZE/16); + immutable bitstride = size / 16; + + // ensure that there are at least bytes for every address + // below ptop even if unaligned + void *ptop = p + PAGESIZE - size + 1; + for (size_t i; p < ptop; p += size, i += bitstride) + { + immutable biti = base + i; + + if (!pool.mark.test(biti)) + { + void* q = sentinel_add(p); + sentinel_Invariant(q); + + if (pool.finals.nbits && pool.finals.test(biti)) + rt_finalizeFromGC(q, sentinel_size(q, size), pool.getBits(biti)); + + assert(core.bitop.bt(toFree.ptr, i)); + + debug(COLLECT_PRINTF) printf("\tcollecting %p\n", p); + leakDetector.log_free(q, sentinel_size(q, size)); + + debug (MEMSTOMP) memset(p, 0xF3, size); + } + } + } + + if (recoverPage) + { + pool.freeAllPageBits(pn); + + pool.pagetable[pn] = B_FREE; + // add to free chain + pool.binPageChain[pn] = cast(uint) pool.searchStart; + pool.searchStart = pn; + pool.freepages++; + freedSmallPages++; + } + else + { + pool.freePageBits(pn, toFree); + + // add to recover chain + pool.binPageChain[pn] = pool.recoverPageFirst[bin]; + pool.recoverPageFirst[bin] = cast(uint)pn; + } + } + } + } + } + + assert(freedLargePages <= usedLargePages); + usedLargePages -= freedLargePages; + debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedLargePages, npools); + + assert(freedSmallPages <= usedSmallPages); + usedSmallPages -= freedSmallPages; + debug(COLLECT_PRINTF) printf("\trecovered small pages = %d\n", freedSmallPages); + + return freedLargePages + freedSmallPages; + } + + bool recoverPage(SmallObjectPool* pool, size_t pn, Bins bin) nothrow + { + size_t size = binsize[bin]; + size_t bitbase = pn * (PAGESIZE / 16); + + auto freebitsdata = pool.freebits.data + pn * PageBits.length; + + // the page had dead objects when collecting, these cannot have been resurrected + bool hasDead = false; + static foreach (w; 0 .. PageBits.length) + hasDead = hasDead || (freebitsdata[w] != 0); + assert(hasDead); + + // prepend to buckets, but with forward addresses inside the page + assert(bucket[bin] is null); + List** bucketTail = &bucket[bin]; + + void* p = pool.baseAddr + pn * PAGESIZE; + const top = PAGESIZE - size + 1; // ensure bytes available even if unaligned + for (size_t u = 0; u < top; u += size) + { + if (!core.bitop.bt(freebitsdata, u / 16)) + continue; + auto elem = cast(List *)(p + u); + elem.pool = &pool.base; + *bucketTail = elem; + bucketTail = &elem.next; + } + *bucketTail = null; + assert(bucket[bin] !is null); + return true; + } + + bool recoverNextPage(Bins bin) nothrow + { + SmallObjectPool* pool = recoverPool[bin]; + while (pool) + { + auto pn = pool.recoverPageFirst[bin]; + while (pn < pool.npages) + { + auto next = pool.binPageChain[pn]; + pool.binPageChain[pn] = Pool.PageRecovered; + pool.recoverPageFirst[bin] = next; + if (recoverPage(pool, pn, bin)) + return true; + pn = next; + } + pool = setNextRecoverPool(bin, pool.ptIndex + 1); + } + return false; + } + + private SmallObjectPool* setNextRecoverPool(Bins bin, size_t poolIndex) nothrow + { + Pool* pool; + while (poolIndex < npools && + ((pool = pooltable[poolIndex]).isLargeObject || + pool.recoverPageFirst[bin] >= pool.npages)) + poolIndex++; + + return recoverPool[bin] = poolIndex < npools ? cast(SmallObjectPool*)pool : null; + } + + version (COLLECT_FORK) + void disableFork() nothrow + { + markProcPid = 0; + shouldFork = false; + } + + version (COLLECT_FORK) + ChildStatus collectFork(bool block) nothrow + { + typeof(return) rc = wait_pid(markProcPid, block); + final switch (rc) + { + case ChildStatus.done: + debug(COLLECT_PRINTF) printf("\t\tmark proc DONE (block=%d)\n", + cast(int) block); + markProcPid = 0; + // process GC marks then sweep + thread_suspendAll(); + thread_processGCMarks(&isMarked); + thread_resumeAll(); + break; + case ChildStatus.running: + debug(COLLECT_PRINTF) printf("\t\tmark proc RUNNING\n"); + if (!block) + break; + // Something went wrong, if block is true, wait() should never + // return RUNNING. + goto case ChildStatus.error; + case ChildStatus.error: + debug(COLLECT_PRINTF) printf("\t\tmark proc ERROR\n"); + // Try to keep going without forking + // and do the marking in this thread + break; + } + return rc; + } + + version (COLLECT_FORK) + ChildStatus markFork(bool nostack, bool block, bool doParallel) nothrow + { + // Forking is enabled, so we fork() and start a new concurrent mark phase + // in the child. If the collection should not block, the parent process + // tells the caller no memory could be recycled immediately (if this collection + // was triggered by an allocation, the caller should allocate more memory + // to fulfill the request). + // If the collection should block, the parent will wait for the mark phase + // to finish before returning control to the mutator, + // but other threads are restarted and may run in parallel with the mark phase + // (unless they allocate or use the GC themselves, in which case + // the global GC lock will stop them). + // fork now and sweep later + int child_mark() scope + { + if (doParallel) + markParallel(nostack); + else if (ConservativeGC.isPrecise) + markAll!(markPrecise!true)(nostack); + else + markAll!(markConservative!true)(nostack); + return 0; + } + + import core.stdc.stdlib : _Exit; + debug (PRINTF_TO_FILE) + { + import core.stdc.stdio : fflush; + fflush(null); // avoid duplicated FILE* output + } + version (OSX) + { + auto pid = __fork(); // avoids calling handlers (from libc source code) + } + else version (linux) + { + // clone() fits better as we don't want to do anything but scanning in the child process. + // no fork-handlera are called, so we can avoid deadlocks due to malloc locks. Probably related: + // https://sourceware.org/bugzilla/show_bug.cgi?id=4737 + import core.sys.linux.sched : clone; + import core.sys.posix.signal : SIGCHLD; + enum CLONE_CHILD_CLEARTID = 0x00200000; /* Register exit futex and memory */ + const flags = CLONE_CHILD_CLEARTID | SIGCHLD; // child thread id not needed + scope int delegate() scope dg = &child_mark; + extern(C) static int wrap_delegate(void* arg) + { + auto dg = cast(int delegate() scope*)arg; + return (*dg)(); + } + char[256] stackbuf; // enough stack space for clone() to place some info for the child without stomping the parent stack + auto stack = stackbuf.ptr + (isStackGrowingDown ? stackbuf.length : 0); + auto pid = clone(&wrap_delegate, stack, flags, &dg); + } + else + { + fork_needs_lock = false; + auto pid = fork(); + fork_needs_lock = true; + } + assert(pid != -1); + switch (pid) + { + case -1: // fork() failed, retry without forking + return ChildStatus.error; + case 0: // child process (not run with clone) + child_mark(); + _Exit(0); + default: // the parent + thread_resumeAll(); + if (!block) + { + markProcPid = pid; + return ChildStatus.running; + } + ChildStatus r = wait_pid(pid); // block until marking is done + if (r == ChildStatus.error) + { + thread_suspendAll(); + // there was an error + // do the marking in this thread + disableFork(); + if (doParallel) + markParallel(nostack); + else if (ConservativeGC.isPrecise) + markAll!(markPrecise!false)(nostack); + else + markAll!(markConservative!false)(nostack); + } else { + assert(r == ChildStatus.done); + assert(r != ChildStatus.running); + } + } + return ChildStatus.done; // waited for the child + } + + /** + * Return number of full pages free'd. + * The collection is done concurrently only if block and isFinal are false. + */ + size_t fullcollect(bool nostack = false, bool block = false, bool isFinal = false) nothrow + { + // It is possible that `fullcollect` will be called from a thread which + // is not yet registered in runtime (because allocating `new Thread` is + // part of `thread_attachThis` implementation). In that case it is + // better not to try actually collecting anything + + if (Thread.getThis() is null) + return 0; + + MonoTime start, stop, begin; + begin = start = currTime; + + debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n"); + version (COLLECT_PARALLEL) + { + bool doParallel = config.parallel > 0 && !config.fork; + if (doParallel && !scanThreadData) + { + if (isFinal) // avoid starting threads for parallel marking + doParallel = false; + else + startScanThreads(); + } + } + else + enum doParallel = false; + + //printf("\tpool address range = %p .. %p\n", minAddr, maxAddr); + + version (COLLECT_FORK) + bool doFork = shouldFork; + else + enum doFork = false; + + if (doFork && collectInProgress) + { + version (COLLECT_FORK) + { + // If there is a mark process running, check if it already finished. + // If that is the case, we move to the sweep phase. + // If it's still running, either we block until the mark phase is + // done (and then sweep to finish the collection), or in case of error + // we redo the mark phase without forking. + ChildStatus rc = collectFork(block); + final switch (rc) + { + case ChildStatus.done: + break; + case ChildStatus.running: + return 0; + case ChildStatus.error: + disableFork(); + goto Lmark; + } + } + } + else + { +Lmark: + // lock roots and ranges around suspending threads b/c they're not reentrant safe + rangesLock.lock(); + rootsLock.lock(); + debug(INVARIANT) inCollection = true; + scope (exit) + { + debug(INVARIANT) inCollection = false; + rangesLock.unlock(); + rootsLock.unlock(); + } + thread_suspendAll(); + + prepare(); + + stop = currTime; + prepTime += (stop - start); + start = stop; + + if (doFork && !isFinal && !block) // don't start a new fork during termination + { + version (COLLECT_FORK) + { + auto forkResult = markFork(nostack, block, doParallel); + final switch (forkResult) + { + case ChildStatus.error: + disableFork(); + goto Lmark; + case ChildStatus.running: + // update profiling informations + stop = currTime; + markTime += (stop - start); + Duration pause = stop - begin; + if (pause > maxPauseTime) + maxPauseTime = pause; + pauseTime += pause; + return 0; + case ChildStatus.done: + break; + } + // if we get here, forking failed and a standard STW collection got issued + // threads were suspended again, restart them + thread_suspendAll(); + } + } + else if (doParallel) + { + version (COLLECT_PARALLEL) + markParallel(nostack); + } + else + { + if (ConservativeGC.isPrecise) + markAll!(markPrecise!false)(nostack); + else + markAll!(markConservative!false)(nostack); + } + + thread_processGCMarks(&isMarked); + thread_resumeAll(); + isFinal = false; + } + + // If we get here with the forking GC, the child process has finished the marking phase + // or block == true and we are using standard stop the world collection. + // It is time to sweep + + stop = currTime; + markTime += (stop - start); + Duration pause = stop - begin; + if (pause > maxPauseTime) + maxPauseTime = pause; + pauseTime += pause; + start = stop; + + ConservativeGC._inFinalizer = true; + size_t freedPages = void; + { + scope (failure) ConservativeGC._inFinalizer = false; + freedPages = sweep(); + ConservativeGC._inFinalizer = false; + } + + // minimize() should be called only after a call to fullcollect + // terminates with a sweep + if (minimizeAfterNextCollection || lowMem) + { + minimizeAfterNextCollection = false; + minimize(); + } + + // init bucket lists + bucket[] = null; + foreach (Bins bin; 0..B_NUMSMALL) + setNextRecoverPool(bin, 0); + + stop = currTime; + sweepTime += (stop - start); + + Duration collectionTime = stop - begin; + if (collectionTime > maxCollectionTime) + maxCollectionTime = collectionTime; + + ++numCollections; + + updateCollectThresholds(); + if (doFork && isFinal) + return fullcollect(true, true, false); + return freedPages; + } + + /** + * Returns true if the addr lies within a marked block. + * + * Warning! This should only be called while the world is stopped inside + * the fullcollect function after all live objects have been marked, but before sweeping. + */ + int isMarked(void *addr) scope nothrow + { + // first, we find the Pool this block is in, then check to see if the + // mark bit is clear. + auto pool = findPool(addr); + if (pool) + { + auto offset = cast(size_t)(addr - pool.baseAddr); + auto pn = offset / PAGESIZE; + auto bins = cast(Bins)pool.pagetable[pn]; + size_t biti = void; + if (bins < B_PAGE) + { + biti = baseOffset(offset, bins) >> pool.ShiftBy.Small; + // doesn't need to check freebits because no pointer must exist + // to a block that was free before starting the collection + } + else if (bins == B_PAGE) + { + biti = pn * (PAGESIZE >> pool.ShiftBy.Large); + } + else if (bins == B_PAGEPLUS) + { + pn -= pool.bPageOffsets[pn]; + biti = pn * (PAGESIZE >> pool.ShiftBy.Large); + } + else // bins == B_FREE + { + assert(bins == B_FREE); + return IsMarked.no; + } + return pool.mark.test(biti) ? IsMarked.yes : IsMarked.no; + } + return IsMarked.unknown; + } + + version (Posix) + { + // A fork might happen while GC code is running in a different thread. + // Because that would leave the GC in an inconsistent state, + // make sure no GC code is running by acquiring the lock here, + // before a fork. + // This must not happen if fork is called from the GC with the lock already held + + __gshared bool fork_needs_lock = true; // racing condition with cocurrent calls of fork? + + + extern(C) static void _d_gcx_atfork_prepare() + { + if (instance && fork_needs_lock) + ConservativeGC.lockNR(); + } + + extern(C) static void _d_gcx_atfork_parent() + { + if (instance && fork_needs_lock) + ConservativeGC.gcLock.unlock(); + } + + extern(C) static void _d_gcx_atfork_child() + { + if (instance && fork_needs_lock) + { + ConservativeGC.gcLock.unlock(); + + // make sure the threads and event handles are reinitialized in a fork + version (COLLECT_PARALLEL) + { + if (Gcx.instance.scanThreadData) + { + cstdlib.free(Gcx.instance.scanThreadData); + Gcx.instance.numScanThreads = 0; + Gcx.instance.scanThreadData = null; + Gcx.instance.busyThreads = 0; + + memset(&Gcx.instance.evStart, 0, Gcx.instance.evStart.sizeof); + memset(&Gcx.instance.evDone, 0, Gcx.instance.evDone.sizeof); + } + } + } + } + } + + /* ============================ Parallel scanning =============================== */ + version (COLLECT_PARALLEL): + import core.sync.event; + import core.atomic; + private: // disable invariants for background threads + + static struct ScanThreadData + { + ThreadID tid; + } + uint numScanThreads; + ScanThreadData* scanThreadData; + + Event evStart; + Event evDone; + + shared uint busyThreads; + shared uint stoppedThreads; + bool stopGC; + + void markParallel(bool nostack) nothrow + { + toscanRoots.clear(); + collectAllRoots(nostack); + if (toscanRoots.empty) + return; + + void** pbot = toscanRoots._p; + void** ptop = toscanRoots._p + toscanRoots._length; + + debug(PARALLEL_PRINTF) printf("markParallel\n"); + + size_t pointersPerThread = toscanRoots._length / (numScanThreads + 1); + if (pointersPerThread > 0) + { + void pushRanges(bool precise)() + { + alias toscan = scanStack!precise; + toscan.stackLock.lock(); + + for (int idx = 0; idx < numScanThreads; idx++) + { + toscan.push(ScanRange!precise(pbot, pbot + pointersPerThread)); + pbot += pointersPerThread; + } + toscan.stackLock.unlock(); + } + if (ConservativeGC.isPrecise) + pushRanges!true(); + else + pushRanges!false(); + } + assert(pbot < ptop); + + busyThreads.atomicOp!"+="(1); // main thread is busy + + evStart.set(); + + debug(PARALLEL_PRINTF) printf("mark %lld roots\n", cast(ulong)(ptop - pbot)); + + if (ConservativeGC.isPrecise) + mark!(true, true, true)(ScanRange!true(pbot, ptop, null)); + else + mark!(false, true, true)(ScanRange!false(pbot, ptop)); + + busyThreads.atomicOp!"-="(1); + + debug(PARALLEL_PRINTF) printf("waitForScanDone\n"); + pullFromScanStack(); + debug(PARALLEL_PRINTF) printf("waitForScanDone done\n"); + } + + int maxParallelThreads() nothrow + { + import core.cpuid; + auto threads = threadsPerCPU(); + + if (threads == 0) + { + // If the GC is called by module ctors no explicit + // import dependency on the GC is generated. So the + // GC module is not correctly inserted into the module + // initialization chain. As it relies on core.cpuid being + // initialized, force this here. + try + { + foreach (m; ModuleInfo) + if (m.name == "core.cpuid") + if (auto ctor = m.ctor()) + { + ctor(); + threads = threadsPerCPU(); + break; + } + } + catch (Exception) + { + assert(false, "unexpected exception iterating ModuleInfo"); + } + } + return threads; + } + + + void startScanThreads() nothrow + { + auto threads = maxParallelThreads(); + debug(PARALLEL_PRINTF) printf("startScanThreads: %d threads per CPU\n", threads); + if (threads <= 1) + return; // either core.cpuid not initialized or single core + + numScanThreads = threads >= config.parallel ? config.parallel : threads - 1; + + scanThreadData = cast(ScanThreadData*) cstdlib.calloc(numScanThreads, ScanThreadData.sizeof); + if (!scanThreadData) + onOutOfMemoryErrorNoGC(); + + evStart.initialize(false, false); + evDone.initialize(false, false); + + version (Posix) + { + import core.sys.posix.signal; + // block all signals, scanBackground inherits this mask. + // see https://issues.dlang.org/show_bug.cgi?id=20256 + sigset_t new_mask, old_mask; + sigfillset(&new_mask); + auto sigmask_rc = pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask); + assert(sigmask_rc == 0, "failed to set up GC scan thread sigmask"); + } + + for (int idx = 0; idx < numScanThreads; idx++) + scanThreadData[idx].tid = createLowLevelThread(&scanBackground, 0x4000, &stopScanThreads); + + version (Posix) + { + sigmask_rc = pthread_sigmask(SIG_SETMASK, &old_mask, null); + assert(sigmask_rc == 0, "failed to set up GC scan thread sigmask"); + } + } + + void stopScanThreads() nothrow + { + if (!numScanThreads) + return; + + debug(PARALLEL_PRINTF) printf("stopScanThreads\n"); + int startedThreads = 0; + for (int idx = 0; idx < numScanThreads; idx++) + if (scanThreadData[idx].tid != scanThreadData[idx].tid.init) + startedThreads++; + + version (Windows) + alias allThreadsDead = thread_DLLProcessDetaching; + else + enum allThreadsDead = false; + stopGC = true; + while (atomicLoad(stoppedThreads) < startedThreads && !allThreadsDead) + { + evStart.set(); + evDone.wait(dur!"msecs"(1)); + } + + for (int idx = 0; idx < numScanThreads; idx++) + { + if (scanThreadData[idx].tid != scanThreadData[idx].tid.init) + { + joinLowLevelThread(scanThreadData[idx].tid); + scanThreadData[idx].tid = scanThreadData[idx].tid.init; + } + } + + evDone.terminate(); + evStart.terminate(); + + cstdlib.free(scanThreadData); + // scanThreadData = null; // keep non-null to not start again after shutdown + numScanThreads = 0; + + debug(PARALLEL_PRINTF) printf("stopScanThreads done\n"); + } + + void scanBackground() nothrow + { + while (!stopGC) + { + evStart.wait(); + pullFromScanStack(); + evDone.set(); + } + stoppedThreads.atomicOp!"+="(1); + } + + void pullFromScanStack() nothrow + { + if (ConservativeGC.isPrecise) + pullFromScanStackImpl!true(); + else + pullFromScanStackImpl!false(); + } + + void pullFromScanStackImpl(bool precise)() nothrow + { + if (atomicLoad(busyThreads) == 0) + return; + + debug(PARALLEL_PRINTF) + pthread_t threadId = pthread_self(); + debug(PARALLEL_PRINTF) printf("scanBackground thread %d start\n", threadId); + + ScanRange!precise rng; + alias toscan = scanStack!precise; + + while (atomicLoad(busyThreads) > 0) + { + if (toscan.empty) + { + evDone.wait(dur!"msecs"(1)); + continue; + } + + busyThreads.atomicOp!"+="(1); + if (toscan.popLocked(rng)) + { + debug(PARALLEL_PRINTF) printf("scanBackground thread %d scanning range [%p,%lld] from stack\n", threadId, + rng.pbot, cast(long) (rng.ptop - rng.pbot)); + mark!(precise, true, true)(rng); + } + busyThreads.atomicOp!"-="(1); + } + debug(PARALLEL_PRINTF) printf("scanBackground thread %d done\n", threadId); + } +} + +/* ============================ Pool =============================== */ + +struct Pool +{ + void* baseAddr; + void* topAddr; + size_t ptIndex; // index in pool table + GCBits mark; // entries already scanned, or should not be scanned + GCBits freebits; // entries that are on the free list (all bits set but for allocated objects at their base offset) + GCBits finals; // entries that need finalizer run on them + GCBits structFinals;// struct entries that need a finalzier run on them + GCBits noscan; // entries that should not be scanned + GCBits appendable; // entries that are appendable + GCBits nointerior; // interior pointers should be ignored. + // Only implemented for large object pools. + GCBits is_pointer; // precise GC only: per-word, not per-block like the rest of them (SmallObjectPool only) + size_t npages; + size_t freepages; // The number of pages not in use. + ubyte* pagetable; + + bool isLargeObject; + + enum ShiftBy + { + Small = 4, + Large = 12 + } + ShiftBy shiftBy; // shift count for the divisor used for determining bit indices. + + // This tracks how far back we have to go to find the nearest B_PAGE at + // a smaller address than a B_PAGEPLUS. To save space, we use a uint. + // This limits individual allocations to 16 terabytes, assuming a 4k + // pagesize. (LargeObjectPool only) + // For B_PAGE and B_FREE, this specifies the number of pages in this block. + // As an optimization, a contiguous range of free pages tracks this information + // only for the first and the last page. + uint* bPageOffsets; + + // The small object pool uses the same array to keep a chain of + // - pages with the same bin size that are still to be recovered + // - free pages (searchStart is first free page) + // other pages are marked by value PageRecovered + alias binPageChain = bPageOffsets; + + enum PageRecovered = uint.max; + + // first of chain of pages to recover (SmallObjectPool only) + uint[B_NUMSMALL] recoverPageFirst; + + // precise GC: TypeInfo.rtInfo for allocation (LargeObjectPool only) + immutable(size_t)** rtinfo; + + // This variable tracks a conservative estimate of where the first free + // page in this pool is, so that if a lot of pages towards the beginning + // are occupied, we can bypass them in O(1). + size_t searchStart; + size_t largestFree; // upper limit for largest free chunk in large object pool + + void initialize(size_t npages, bool isLargeObject) nothrow + { + assert(npages >= 256); + + this.isLargeObject = isLargeObject; + size_t poolsize; + + shiftBy = isLargeObject ? ShiftBy.Large : ShiftBy.Small; + + //debug(PRINTF) printf("Pool::Pool(%u)\n", npages); + poolsize = npages * PAGESIZE; + baseAddr = cast(byte *)os_mem_map(poolsize); + + // Some of the code depends on page alignment of memory pools + assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0); + + if (!baseAddr) + { + //debug(PRINTF) printf("GC fail: poolsize = x%zx, errno = %d\n", poolsize, errno); + //debug(PRINTF) printf("message = '%s'\n", sys_errlist[errno]); + + npages = 0; + poolsize = 0; + } + //assert(baseAddr); + topAddr = baseAddr + poolsize; + auto nbits = cast(size_t)poolsize >> shiftBy; + + version (COLLECT_FORK) + mark.alloc(nbits, config.fork); + else + mark.alloc(nbits); + if (ConservativeGC.isPrecise) + { + if (isLargeObject) + { + rtinfo = cast(immutable(size_t)**)cstdlib.malloc(npages * (size_t*).sizeof); + if (!rtinfo) + onOutOfMemoryErrorNoGC(); + memset(rtinfo, 0, npages * (size_t*).sizeof); + } + else + { + is_pointer.alloc(cast(size_t)poolsize/(void*).sizeof); + is_pointer.clrRange(0, is_pointer.nbits); + } + } + + // pagetable already keeps track of what's free for the large object + // pool. + if (!isLargeObject) + { + freebits.alloc(nbits); + freebits.setRange(0, nbits); + } + + noscan.alloc(nbits); + appendable.alloc(nbits); + + pagetable = cast(ubyte*)cstdlib.malloc(npages); + if (!pagetable) + onOutOfMemoryErrorNoGC(); + + if (npages > 0) + { + bPageOffsets = cast(uint*)cstdlib.malloc(npages * uint.sizeof); + if (!bPageOffsets) + onOutOfMemoryErrorNoGC(); + + if (isLargeObject) + { + bPageOffsets[0] = cast(uint)npages; + bPageOffsets[npages-1] = cast(uint)npages; + } + else + { + // all pages free + foreach (n; 0..npages) + binPageChain[n] = cast(uint)(n + 1); + recoverPageFirst[] = cast(uint)npages; + } + } + + memset(pagetable, B_FREE, npages); + + this.npages = npages; + this.freepages = npages; + this.searchStart = 0; + this.largestFree = npages; + } + + + void Dtor() nothrow + { + if (baseAddr) + { + int result; + + if (npages) + { + result = os_mem_unmap(baseAddr, npages * PAGESIZE); + assert(result == 0); + npages = 0; + } + + baseAddr = null; + topAddr = null; + } + if (pagetable) + { + cstdlib.free(pagetable); + pagetable = null; + } + + if (bPageOffsets) + { + cstdlib.free(bPageOffsets); + bPageOffsets = null; + } + + mark.Dtor(config.fork); + if (ConservativeGC.isPrecise) + { + if (isLargeObject) + cstdlib.free(rtinfo); + else + is_pointer.Dtor(); + } + if (isLargeObject) + { + nointerior.Dtor(); + } + else + { + freebits.Dtor(); + } + finals.Dtor(); + structFinals.Dtor(); + noscan.Dtor(); + appendable.Dtor(); + } + + /** + * + */ + uint getBits(size_t biti) nothrow + { + uint bits; + + if (finals.nbits && finals.test(biti)) + bits |= BlkAttr.FINALIZE; + if (structFinals.nbits && structFinals.test(biti)) + bits |= BlkAttr.STRUCTFINAL; + if (noscan.test(biti)) + bits |= BlkAttr.NO_SCAN; + if (nointerior.nbits && nointerior.test(biti)) + bits |= BlkAttr.NO_INTERIOR; + if (appendable.test(biti)) + bits |= BlkAttr.APPENDABLE; + return bits; + } + + /** + * + */ + void clrBits(size_t biti, uint mask) nothrow @nogc + { + immutable dataIndex = biti >> GCBits.BITS_SHIFT; + immutable bitOffset = biti & GCBits.BITS_MASK; + immutable keep = ~(GCBits.BITS_1 << bitOffset); + + if (mask & BlkAttr.FINALIZE && finals.nbits) + finals.data[dataIndex] &= keep; + + if (structFinals.nbits && (mask & BlkAttr.STRUCTFINAL)) + structFinals.data[dataIndex] &= keep; + + if (mask & BlkAttr.NO_SCAN) + noscan.data[dataIndex] &= keep; + if (mask & BlkAttr.APPENDABLE) + appendable.data[dataIndex] &= keep; + if (nointerior.nbits && (mask & BlkAttr.NO_INTERIOR)) + nointerior.data[dataIndex] &= keep; + } + + /** + * + */ + void setBits(size_t biti, uint mask) nothrow + { + // Calculate the mask and bit offset once and then use it to + // set all of the bits we need to set. + immutable dataIndex = biti >> GCBits.BITS_SHIFT; + immutable bitOffset = biti & GCBits.BITS_MASK; + immutable orWith = GCBits.BITS_1 << bitOffset; + + if (mask & BlkAttr.STRUCTFINAL) + { + if (!structFinals.nbits) + structFinals.alloc(mark.nbits); + structFinals.data[dataIndex] |= orWith; + } + + if (mask & BlkAttr.FINALIZE) + { + if (!finals.nbits) + finals.alloc(mark.nbits); + finals.data[dataIndex] |= orWith; + } + + if (mask & BlkAttr.NO_SCAN) + { + noscan.data[dataIndex] |= orWith; + } +// if (mask & BlkAttr.NO_MOVE) +// { +// if (!nomove.nbits) +// nomove.alloc(mark.nbits); +// nomove.data[dataIndex] |= orWith; +// } + if (mask & BlkAttr.APPENDABLE) + { + appendable.data[dataIndex] |= orWith; + } + + if (isLargeObject && (mask & BlkAttr.NO_INTERIOR)) + { + if (!nointerior.nbits) + nointerior.alloc(mark.nbits); + nointerior.data[dataIndex] |= orWith; + } + } + + void freePageBits(size_t pagenum, const scope ref PageBits toFree) nothrow + { + assert(!isLargeObject); + assert(!nointerior.nbits); // only for large objects + + import core.internal.traits : staticIota; + immutable beg = pagenum * (PAGESIZE / 16 / GCBits.BITS_PER_WORD); + foreach (i; staticIota!(0, PageBits.length)) + { + immutable w = toFree[i]; + if (!w) continue; + + immutable wi = beg + i; + freebits.data[wi] |= w; + noscan.data[wi] &= ~w; + appendable.data[wi] &= ~w; + } + + if (finals.nbits) + { + foreach (i; staticIota!(0, PageBits.length)) + if (toFree[i]) + finals.data[beg + i] &= ~toFree[i]; + } + + if (structFinals.nbits) + { + foreach (i; staticIota!(0, PageBits.length)) + if (toFree[i]) + structFinals.data[beg + i] &= ~toFree[i]; + } + } + + void freeAllPageBits(size_t pagenum) nothrow + { + assert(!isLargeObject); + assert(!nointerior.nbits); // only for large objects + + immutable beg = pagenum * PageBits.length; + static foreach (i; 0 .. PageBits.length) + {{ + immutable w = beg + i; + freebits.data[w] = ~0; + noscan.data[w] = 0; + appendable.data[w] = 0; + if (finals.data) + finals.data[w] = 0; + if (structFinals.data) + structFinals.data[w] = 0; + }} + } + + /** + * Given a pointer p in the p, return the pagenum. + */ + size_t pagenumOf(void *p) const nothrow @nogc + in + { + assert(p >= baseAddr); + assert(p < topAddr); + } + do + { + return cast(size_t)(p - baseAddr) / PAGESIZE; + } + + public + @property bool isFree() const pure nothrow + { + return npages == freepages; + } + + /** + * Return number of pages necessary for an allocation of the given size + * + * returns size_t.max if more than uint.max pages are requested + * (return type is still size_t to avoid truncation when being used + * in calculations, e.g. npages * PAGESIZE) + */ + static size_t numPages(size_t size) nothrow @nogc + { + version (D_LP64) + { + if (size > PAGESIZE * cast(size_t)uint.max) + return size_t.max; + } + else + { + if (size > size_t.max - PAGESIZE) + return size_t.max; + } + return (size + PAGESIZE - 1) / PAGESIZE; + } + + void* findBase(void* p) nothrow @nogc + { + size_t offset = cast(size_t)(p - baseAddr); + size_t pn = offset / PAGESIZE; + Bins bin = cast(Bins)pagetable[pn]; + + // Adjust bit to be at start of allocated memory block + if (bin < B_NUMSMALL) + { + auto baseOff = baseOffset(offset, bin); + const biti = baseOff >> Pool.ShiftBy.Small; + if (freebits.test (biti)) + return null; + return baseAddr + baseOff; + } + if (bin == B_PAGE) + { + return baseAddr + (offset & (offset.max ^ (PAGESIZE-1))); + } + if (bin == B_PAGEPLUS) + { + size_t pageOffset = bPageOffsets[pn]; + offset -= pageOffset * PAGESIZE; + pn -= pageOffset; + + return baseAddr + (offset & (offset.max ^ (PAGESIZE-1))); + } + // we are in a B_FREE page + assert(bin == B_FREE); + return null; + } + + size_t slGetSize(void* p) nothrow @nogc + { + if (isLargeObject) + return (cast(LargeObjectPool*)&this).getPages(p) * PAGESIZE; + else + return (cast(SmallObjectPool*)&this).getSize(p); + } + + BlkInfo slGetInfo(void* p) nothrow + { + if (isLargeObject) + return (cast(LargeObjectPool*)&this).getInfo(p); + else + return (cast(SmallObjectPool*)&this).getInfo(p); + } + + + void Invariant() const {} + + debug(INVARIANT) + invariant() + { + if (baseAddr) + { + //if (baseAddr + npages * PAGESIZE != topAddr) + //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr); + assert(baseAddr + npages * PAGESIZE == topAddr); + } + + if (pagetable !is null) + { + for (size_t i = 0; i < npages; i++) + { + Bins bin = cast(Bins)pagetable[i]; + assert(bin < B_MAX); + } + } + } + + void setPointerBitmapSmall(void* p, size_t s, size_t allocSize, uint attr, const TypeInfo ti) nothrow + { + if (!(attr & BlkAttr.NO_SCAN)) + setPointerBitmap(p, s, allocSize, ti, attr); + } + + pragma(inline,false) + void setPointerBitmap(void* p, size_t s, size_t allocSize, const TypeInfo ti, uint attr) nothrow + { + size_t offset = p - baseAddr; + //debug(PRINTF) printGCBits(&pool.is_pointer); + + debug(PRINTF) + printf("Setting a pointer bitmap for %s at %p + %llu\n", debugTypeName(ti).ptr, p, cast(ulong)s); + + if (ti) + { + if (attr & BlkAttr.APPENDABLE) + { + // an array of classes is in fact an array of pointers + if (typeid(ti) is typeid(TypeInfo_Class)) + goto L_conservative; + s = allocSize; + } + + auto rtInfo = cast(const(size_t)*)ti.rtInfo(); + + if (rtInfo is rtinfoNoPointers) + { + debug(PRINTF) printf("\tCompiler generated rtInfo: no pointers\n"); + is_pointer.clrRange(offset/(void*).sizeof, s/(void*).sizeof); + } + else if (rtInfo is rtinfoHasPointers) + { + debug(PRINTF) printf("\tCompiler generated rtInfo: has pointers\n"); + is_pointer.setRange(offset/(void*).sizeof, s/(void*).sizeof); + } + else + { + const(size_t)* bitmap = cast (size_t*) rtInfo; + //first element of rtInfo is the size of the object the bitmap encodes + size_t element_size = * bitmap; + bitmap++; + size_t tocopy; + if (attr & BlkAttr.APPENDABLE) + { + tocopy = s/(void*).sizeof; + is_pointer.copyRangeRepeating(offset/(void*).sizeof, tocopy, bitmap, element_size/(void*).sizeof); + } + else + { + tocopy = (s < element_size ? s : element_size)/(void*).sizeof; + is_pointer.copyRange(offset/(void*).sizeof, tocopy, bitmap); + } + + debug(PRINTF) printf("\tSetting bitmap for new object (%s)\n\t\tat %p\t\tcopying from %p + %llu: ", + debugTypeName(ti).ptr, p, bitmap, cast(ulong)element_size); + debug(PRINTF) + for (size_t i = 0; i < element_size/((void*).sizeof); i++) + printf("%d", (bitmap[i/(8*size_t.sizeof)] >> (i%(8*size_t.sizeof))) & 1); + debug(PRINTF) printf("\n"); + + if (tocopy * (void*).sizeof < s) // better safe than sorry: if allocated more, assume pointers inside + { + debug(PRINTF) printf(" Appending %d pointer bits\n", s/(void*).sizeof - tocopy); + is_pointer.setRange(offset/(void*).sizeof + tocopy, s/(void*).sizeof - tocopy); + } + } + + if (s < allocSize) + { + offset = (offset + s + (void*).sizeof - 1) & ~((void*).sizeof - 1); + is_pointer.clrRange(offset/(void*).sizeof, (allocSize - s)/(void*).sizeof); + } + } + else + { + L_conservative: + // limit pointers to actual size of allocation? might fail for arrays that append + // without notifying the GC + s = allocSize; + + debug(PRINTF) printf("Allocating a block without TypeInfo\n"); + is_pointer.setRange(offset/(void*).sizeof, s/(void*).sizeof); + } + //debug(PRINTF) printGCBits(&pool.is_pointer); + } +} + +struct LargeObjectPool +{ + Pool base; + alias base this; + + debug(INVARIANT) + void Invariant() + { + //base.Invariant(); + for (size_t n = 0; n < npages; ) + { + uint np = bPageOffsets[n]; + assert(np > 0 && np <= npages - n); + + if (pagetable[n] == B_PAGE) + { + for (uint p = 1; p < np; p++) + { + assert(pagetable[n + p] == B_PAGEPLUS); + assert(bPageOffsets[n + p] == p); + } + } + else if (pagetable[n] == B_FREE) + { + for (uint p = 1; p < np; p++) + { + assert(pagetable[n + p] == B_FREE); + } + assert(bPageOffsets[n + np - 1] == np); + } + else + assert(false); + n += np; + } + } + + /** + * Allocate n pages from Pool. + * Returns OPFAIL on failure. + */ + size_t allocPages(size_t n) nothrow + { + if (largestFree < n || searchStart + n > npages) + return OPFAIL; + + //debug(PRINTF) printf("Pool::allocPages(n = %d)\n", n); + size_t largest = 0; + if (pagetable[searchStart] == B_PAGEPLUS) + { + searchStart -= bPageOffsets[searchStart]; // jump to B_PAGE + searchStart += bPageOffsets[searchStart]; + } + while (searchStart < npages && pagetable[searchStart] == B_PAGE) + searchStart += bPageOffsets[searchStart]; + + for (size_t i = searchStart; i < npages; ) + { + assert(pagetable[i] == B_FREE); + + auto p = bPageOffsets[i]; + if (p > n) + { + setFreePageOffsets(i + n, p - n); + goto L_found; + } + if (p == n) + { + L_found: + pagetable[i] = B_PAGE; + bPageOffsets[i] = cast(uint) n; + if (n > 1) + { + memset(&pagetable[i + 1], B_PAGEPLUS, n - 1); + for (auto offset = 1; offset < n; offset++) + bPageOffsets[i + offset] = cast(uint) offset; + } + freepages -= n; + return i; + } + if (p > largest) + largest = p; + + i += p; + while (i < npages && pagetable[i] == B_PAGE) + { + // we have the size information, so we skip a whole bunch of pages. + i += bPageOffsets[i]; + } + } + + // not enough free pages found, remember largest free chunk + largestFree = largest; + return OPFAIL; + } + + /** + * Free npages pages starting with pagenum. + */ + void freePages(size_t pagenum, size_t npages) nothrow @nogc + { + //memset(&pagetable[pagenum], B_FREE, npages); + if (pagenum < searchStart) + searchStart = pagenum; + + for (size_t i = pagenum; i < npages + pagenum; i++) + { + assert(pagetable[i] < B_FREE); + pagetable[i] = B_FREE; + } + freepages += npages; + largestFree = freepages; // invalidate + } + + /** + * Set the first and the last entry of a B_FREE block to the size + */ + void setFreePageOffsets(size_t page, size_t num) nothrow @nogc + { + assert(pagetable[page] == B_FREE); + assert(pagetable[page + num - 1] == B_FREE); + bPageOffsets[page] = cast(uint)num; + if (num > 1) + bPageOffsets[page + num - 1] = cast(uint)num; + } + + void mergeFreePageOffsets(bool bwd, bool fwd)(size_t page, size_t num) nothrow @nogc + { + static if (bwd) + { + if (page > 0 && pagetable[page - 1] == B_FREE) + { + auto sz = bPageOffsets[page - 1]; + page -= sz; + num += sz; + } + } + static if (fwd) + { + if (page + num < npages && pagetable[page + num] == B_FREE) + num += bPageOffsets[page + num]; + } + setFreePageOffsets(page, num); + } + + /** + * Get pages of allocation at pointer p in pool. + */ + size_t getPages(void *p) const nothrow @nogc + in + { + assert(p >= baseAddr); + assert(p < topAddr); + } + do + { + if (cast(size_t)p & (PAGESIZE - 1)) // check for interior pointer + return 0; + size_t pagenum = pagenumOf(p); + Bins bin = cast(Bins)pagetable[pagenum]; + if (bin != B_PAGE) + return 0; + return bPageOffsets[pagenum]; + } + + /** + * Get size of allocation at page pn in pool. + */ + size_t getSize(size_t pn) const nothrow @nogc + { + assert(pagetable[pn] == B_PAGE); + return cast(size_t) bPageOffsets[pn] * PAGESIZE; + } + + /** + * + */ + BlkInfo getInfo(void* p) nothrow + { + BlkInfo info; + + size_t offset = cast(size_t)(p - baseAddr); + size_t pn = offset / PAGESIZE; + Bins bin = cast(Bins)pagetable[pn]; + + if (bin == B_PAGEPLUS) + pn -= bPageOffsets[pn]; + else if (bin != B_PAGE) + return info; // no info for free pages + + info.base = baseAddr + pn * PAGESIZE; + info.size = getSize(pn); + info.attr = getBits(pn); + return info; + } + + void runFinalizers(const scope void[] segment) nothrow + { + foreach (pn; 0 .. npages) + { + Bins bin = cast(Bins)pagetable[pn]; + if (bin > B_PAGE) + continue; + size_t biti = pn; + + if (!finals.test(biti)) + continue; + + auto p = sentinel_add(baseAddr + pn * PAGESIZE); + size_t size = sentinel_size(p, getSize(pn)); + uint attr = getBits(biti); + + if (!rt_hasFinalizerInSegment(p, size, attr, segment)) + continue; + + rt_finalizeFromGC(p, size, attr); + + clrBits(biti, ~BlkAttr.NONE); + + if (pn < searchStart) + searchStart = pn; + + debug(COLLECT_PRINTF) printf("\tcollecting big %p\n", p); + //log_free(sentinel_add(p)); + + size_t n = 1; + for (; pn + n < npages; ++n) + if (pagetable[pn + n] != B_PAGEPLUS) + break; + debug (MEMSTOMP) memset(baseAddr + pn * PAGESIZE, 0xF3, n * PAGESIZE); + freePages(pn, n); + mergeFreePageOffsets!(true, true)(pn, n); + } + } +} + + +struct SmallObjectPool +{ + Pool base; + alias base this; + + debug(INVARIANT) + void Invariant() + { + //base.Invariant(); + uint cntRecover = 0; + foreach (Bins bin; 0 .. B_NUMSMALL) + { + for (auto pn = recoverPageFirst[bin]; pn < npages; pn = binPageChain[pn]) + { + assert(pagetable[pn] == bin); + cntRecover++; + } + } + uint cntFree = 0; + for (auto pn = searchStart; pn < npages; pn = binPageChain[pn]) + { + assert(pagetable[pn] == B_FREE); + cntFree++; + } + assert(cntFree == freepages); + assert(cntFree + cntRecover <= npages); + } + + /** + * Get size of pointer p in pool. + */ + size_t getSize(void *p) const nothrow @nogc + in + { + assert(p >= baseAddr); + assert(p < topAddr); + } + do + { + size_t pagenum = pagenumOf(p); + Bins bin = cast(Bins)pagetable[pagenum]; + assert(bin < B_PAGE); + if (p != cast(void*)baseOffset(cast(size_t)p, bin)) // check for interior pointer + return 0; + const biti = cast(size_t)(p - baseAddr) >> ShiftBy.Small; + if (freebits.test (biti)) + return 0; + return binsize[bin]; + } + + BlkInfo getInfo(void* p) nothrow + { + BlkInfo info; + size_t offset = cast(size_t)(p - baseAddr); + size_t pn = offset / PAGESIZE; + Bins bin = cast(Bins)pagetable[pn]; + + if (bin >= B_PAGE) + return info; + + auto base = cast(void*)baseOffset(cast(size_t)p, bin); + const biti = cast(size_t)(base - baseAddr) >> ShiftBy.Small; + if (freebits.test (biti)) + return info; + + info.base = base; + info.size = binsize[bin]; + offset = info.base - baseAddr; + info.attr = getBits(biti); + + return info; + } + + void runFinalizers(const scope void[] segment) nothrow + { + foreach (pn; 0 .. npages) + { + Bins bin = cast(Bins)pagetable[pn]; + if (bin >= B_PAGE) + continue; + + immutable size = binsize[bin]; + auto p = baseAddr + pn * PAGESIZE; + const ptop = p + PAGESIZE - size + 1; + immutable base = pn * (PAGESIZE/16); + immutable bitstride = size / 16; + + bool freeBits; + PageBits toFree; + + for (size_t i; p < ptop; p += size, i += bitstride) + { + immutable biti = base + i; + + if (!finals.test(biti)) + continue; + + auto q = sentinel_add(p); + uint attr = getBits(biti); + const ssize = sentinel_size(q, size); + if (!rt_hasFinalizerInSegment(q, ssize, attr, segment)) + continue; + + rt_finalizeFromGC(q, ssize, attr); + + freeBits = true; + toFree.set(i); + + debug(COLLECT_PRINTF) printf("\tcollecting %p\n", p); + //log_free(sentinel_add(p)); + + debug (MEMSTOMP) memset(p, 0xF3, size); + } + + if (freeBits) + freePageBits(pn, toFree); + } + } + + /** + * Allocate a page of bin's. + * Returns: + * head of a single linked list of new entries + */ + List* allocPage(Bins bin) nothrow + { + if (searchStart >= npages) + return null; + + assert(pagetable[searchStart] == B_FREE); + + L1: + size_t pn = searchStart; + searchStart = binPageChain[searchStart]; + binPageChain[pn] = Pool.PageRecovered; + pagetable[pn] = cast(ubyte)bin; + freepages--; + + // Convert page to free list + size_t size = binsize[bin]; + void* p = baseAddr + pn * PAGESIZE; + auto first = cast(List*) p; + + // ensure 2 bytes blocks are available below ptop, one + // being set in the loop, and one for the tail block + void* ptop = p + PAGESIZE - 2 * size + 1; + for (; p < ptop; p += size) + { + (cast(List *)p).next = cast(List *)(p + size); + (cast(List *)p).pool = &base; + } + (cast(List *)p).next = null; + (cast(List *)p).pool = &base; + return first; + } +} + +debug(SENTINEL) {} else // no additional capacity with SENTINEL +unittest // bugzilla 14467 +{ + int[] arr = new int[10]; + assert(arr.capacity); + arr = arr[$..$]; + assert(arr.capacity); +} + +unittest // bugzilla 15353 +{ + import core.memory : GC; + + static struct Foo + { + ~this() + { + GC.free(buf); // ignored in finalizer + } + + void* buf; + } + new Foo(GC.malloc(10)); + GC.collect(); +} + +unittest // bugzilla 15822 +{ + import core.memory : GC; + + __gshared ubyte[16] buf; + static struct Foo + { + ~this() + { + GC.removeRange(ptr); + GC.removeRoot(ptr); + } + + ubyte* ptr; + } + GC.addRoot(buf.ptr); + GC.addRange(buf.ptr, buf.length); + new Foo(buf.ptr); + GC.collect(); +} + +unittest // bugzilla 1180 +{ + import core.exception; + try + { + size_t x = size_t.max - 100; + byte[] big_buf = new byte[x]; + } + catch (OutOfMemoryError) + { + } +} + +/* ============================ PRINTF =============================== */ + +debug(PRINTF_TO_FILE) +{ + private __gshared MonoTime gcStartTick; + private __gshared FILE* gcx_fh; + private __gshared bool hadNewline = false; + import core.internal.spinlock; + static printLock = shared(AlignedSpinLock)(SpinLock.Contention.lengthy); + + private int printf(ARGS...)(const char* fmt, ARGS args) nothrow + { + printLock.lock(); + scope(exit) printLock.unlock(); + + if (!gcx_fh) + gcx_fh = fopen("gcx.log", "w"); + if (!gcx_fh) + return 0; + + int len; + if (MonoTime.ticksPerSecond == 0) + { + len = fprintf(gcx_fh, "before init: "); + } + else if (hadNewline) + { + if (gcStartTick == MonoTime.init) + gcStartTick = MonoTime.currTime; + immutable timeElapsed = MonoTime.currTime - gcStartTick; + immutable secondsAsDouble = timeElapsed.total!"hnsecs" / cast(double)convert!("seconds", "hnsecs")(1); + len = fprintf(gcx_fh, "%10.6f: ", secondsAsDouble); + } + len += fprintf(gcx_fh, fmt, args); + fflush(gcx_fh); + import core.stdc.string; + hadNewline = fmt && fmt[0] && fmt[strlen(fmt) - 1] == '\n'; + return len; + } +} + +debug(PRINTF) void printFreeInfo(Pool* pool) nothrow +{ + uint nReallyFree; + foreach (i; 0..pool.npages) { + if (pool.pagetable[i] >= B_FREE) nReallyFree++; + } + + printf("Pool %p: %d really free, %d supposedly free\n", pool, nReallyFree, pool.freepages); +} + +debug(PRINTF) +void printGCBits(GCBits* bits) +{ + for (size_t i = 0; i < bits.nwords; i++) + { + if (i % 32 == 0) printf("\n\t"); + printf("%x ", bits.data[i]); + } + printf("\n"); +} + +// we can assume the name is always from a literal, so it is zero terminated +debug(PRINTF) +string debugTypeName(const(TypeInfo) ti) nothrow +{ + string name; + if (ti is null) + name = "null"; + else if (auto ci = cast(TypeInfo_Class)ti) + name = ci.name; + else if (auto si = cast(TypeInfo_Struct)ti) + name = si.mangledName; // .name() might GC-allocate, avoid deadlock + else if (auto ci = cast(TypeInfo_Const)ti) + static if (__traits(compiles,ci.base)) // different whether compiled with object.di or object.d + return debugTypeName(ci.base); + else + return debugTypeName(ci.next); + else + name = ti.classinfo.name; + return name; +} + +/* ======================= Leak Detector =========================== */ + +debug (LOGGING) +{ + struct Log + { + void* p; + size_t size; + size_t line; + char* file; + void* parent; + + void print() nothrow + { + printf(" p = %p, size = %lld, parent = %p ", p, cast(ulong)size, parent); + if (file) + { + printf("%s(%u)", file, cast(uint)line); + } + printf("\n"); + } + } + + + struct LogArray + { + size_t dim; + size_t allocdim; + Log *data; + + void Dtor() nothrow @nogc + { + if (data) + cstdlib.free(data); + data = null; + } + + void reserve(size_t nentries) nothrow @nogc + { + assert(dim <= allocdim); + if (allocdim - dim < nentries) + { + allocdim = (dim + nentries) * 2; + assert(dim + nentries <= allocdim); + if (!data) + { + data = cast(Log*)cstdlib.malloc(allocdim * Log.sizeof); + if (!data && allocdim) + onOutOfMemoryErrorNoGC(); + } + else + { Log *newdata; + + newdata = cast(Log*)cstdlib.malloc(allocdim * Log.sizeof); + if (!newdata && allocdim) + onOutOfMemoryErrorNoGC(); + memcpy(newdata, data, dim * Log.sizeof); + cstdlib.free(data); + data = newdata; + } + } + } + + + void push(Log log) nothrow @nogc + { + reserve(1); + data[dim++] = log; + } + + void remove(size_t i) nothrow @nogc + { + memmove(data + i, data + i + 1, (dim - i) * Log.sizeof); + dim--; + } + + + size_t find(void *p) nothrow @nogc + { + for (size_t i = 0; i < dim; i++) + { + if (data[i].p == p) + return i; + } + return OPFAIL; // not found + } + + + void copy(LogArray *from) nothrow @nogc + { + if (allocdim < from.dim) + reserve(from.dim - dim); + assert(from.dim <= allocdim); + memcpy(data, from.data, from.dim * Log.sizeof); + dim = from.dim; + } + } + + struct LeakDetector + { + Gcx* gcx; + LogArray current; + LogArray prev; + + private void initialize(Gcx* gc) + { + gcx = gc; + //debug(PRINTF) printf("+log_init()\n"); + current.reserve(1000); + prev.reserve(1000); + //debug(PRINTF) printf("-log_init()\n"); + } + + + private void log_malloc(void *p, size_t size) nothrow + { + //debug(PRINTF) printf("+log_malloc(p = %p, size = %zd)\n", p, size); + Log log; + + log.p = p; + log.size = size; + log.line = ConservativeGC.line; + log.file = ConservativeGC.file; + log.parent = null; + + ConservativeGC.line = 0; + ConservativeGC.file = null; + + current.push(log); + //debug(PRINTF) printf("-log_malloc()\n"); + } + + + private void log_free(void *p, size_t size) nothrow @nogc + { + //debug(PRINTF) printf("+log_free(%p)\n", p); + auto i = current.find(p); + if (i == OPFAIL) + { + debug(PRINTF) printf("free'ing unallocated memory %p (size %zu)\n", p, size); + } + else + current.remove(i); + //debug(PRINTF) printf("-log_free()\n"); + } + + + private void log_collect() nothrow + { + //debug(PRINTF) printf("+log_collect()\n"); + // Print everything in current that is not in prev + + debug(PRINTF) printf("New pointers this cycle: --------------------------------\n"); + size_t used = 0; + for (size_t i = 0; i < current.dim; i++) + { + auto j = prev.find(current.data[i].p); + if (j == OPFAIL) + current.data[i].print(); + else + used++; + } + + debug(PRINTF) printf("All roots this cycle: --------------------------------\n"); + for (size_t i = 0; i < current.dim; i++) + { + void* p = current.data[i].p; + if (!gcx.findPool(current.data[i].parent)) + { + auto j = prev.find(current.data[i].p); + debug(PRINTF) printf(j == OPFAIL ? "N" : " "); + current.data[i].print(); + } + } + + debug(PRINTF) printf("Used = %d-------------------------------------------------\n", used); + prev.copy(¤t); + + debug(PRINTF) printf("-log_collect()\n"); + } + + + private void log_parent(void *p, void *parent) nothrow + { + //debug(PRINTF) printf("+log_parent()\n"); + auto i = current.find(p); + if (i == OPFAIL) + { + debug(PRINTF) printf("parent'ing unallocated memory %p, parent = %p\n", p, parent); + Pool *pool; + pool = gcx.findPool(p); + assert(pool); + size_t offset = cast(size_t)(p - pool.baseAddr); + size_t biti; + size_t pn = offset / PAGESIZE; + Bins bin = cast(Bins)pool.pagetable[pn]; + biti = (offset & (PAGESIZE - 1)) >> pool.shiftBy; + debug(PRINTF) printf("\tbin = %d, offset = x%x, biti = x%x\n", bin, offset, biti); + } + else + { + current.data[i].parent = parent; + } + //debug(PRINTF) printf("-log_parent()\n"); + } + } +} +else +{ + struct LeakDetector + { + static void initialize(Gcx* gcx) nothrow { } + static void log_malloc(void *p, size_t size) nothrow { } + static void log_free(void *p, size_t size) nothrow @nogc {} + static void log_collect() nothrow { } + static void log_parent(void *p, void *parent) nothrow { } + } +} + +/* ============================ SENTINEL =============================== */ + +debug (SENTINEL) +{ + // pre-sentinel must be smaller than 16 bytes so that the same GC bits + // are used for the allocated pointer and the user pointer + // so use uint for both 32 and 64 bit platforms, limiting usage to < 4GB + const uint SENTINEL_PRE = 0xF4F4F4F4; + const ubyte SENTINEL_POST = 0xF5; // 8 bits + const uint SENTINEL_EXTRA = 2 * uint.sizeof + 1; + + + inout(uint*) sentinel_psize(inout void *p) nothrow @nogc { return &(cast(inout uint *)p)[-2]; } + inout(uint*) sentinel_pre(inout void *p) nothrow @nogc { return &(cast(inout uint *)p)[-1]; } + inout(ubyte*) sentinel_post(inout void *p) nothrow @nogc { return &(cast(inout ubyte *)p)[*sentinel_psize(p)]; } + + + void sentinel_init(void *p, size_t size) nothrow @nogc + { + assert(size <= uint.max); + *sentinel_psize(p) = cast(uint)size; + *sentinel_pre(p) = SENTINEL_PRE; + *sentinel_post(p) = SENTINEL_POST; + } + + + void sentinel_Invariant(const void *p) nothrow @nogc + { + debug + { + assert(*sentinel_pre(p) == SENTINEL_PRE); + assert(*sentinel_post(p) == SENTINEL_POST); + } + else if (*sentinel_pre(p) != SENTINEL_PRE || *sentinel_post(p) != SENTINEL_POST) + onInvalidMemoryOperationError(); // also trigger in release build + } + + size_t sentinel_size(const void *p, size_t alloc_size) nothrow @nogc + { + return *sentinel_psize(p); + } + + void *sentinel_add(void *p) nothrow @nogc + { + return p + 2 * uint.sizeof; + } + + + void *sentinel_sub(void *p) nothrow @nogc + { + return p - 2 * uint.sizeof; + } +} +else +{ + const uint SENTINEL_EXTRA = 0; + + + void sentinel_init(void *p, size_t size) nothrow @nogc + { + } + + + void sentinel_Invariant(const void *p) nothrow @nogc + { + } + + size_t sentinel_size(const void *p, size_t alloc_size) nothrow @nogc + { + return alloc_size; + } + + void *sentinel_add(void *p) nothrow @nogc + { + return p; + } + + + void *sentinel_sub(void *p) nothrow @nogc + { + return p; + } +} + +debug (MEMSTOMP) +unittest +{ + import core.memory; + auto p = cast(size_t*)GC.malloc(size_t.sizeof*3); + assert(*p == cast(size_t)0xF0F0F0F0F0F0F0F0); + p[2] = 0; // First two will be used for free list + GC.free(p); + assert(p[2] == cast(size_t)0xF2F2F2F2F2F2F2F2); +} + +debug (SENTINEL) +unittest +{ + import core.memory; + auto p = cast(ubyte*)GC.malloc(1); + assert(p[-1] == 0xF4); + assert(p[ 1] == 0xF5); + + // See also stand-alone tests in test/gc +} + +unittest +{ + import core.memory; + + // https://issues.dlang.org/show_bug.cgi?id=9275 + GC.removeRoot(null); + GC.removeRoot(cast(void*)13); +} + +// improve predictability of coverage of code that is eventually not hit by other tests +debug (SENTINEL) {} else // cannot extend with SENTINEL +debug (MARK_PRINTF) {} else // takes forever +unittest +{ + import core.memory; + auto p = GC.malloc(260 << 20); // new pool has 390 MB + auto q = GC.malloc(65 << 20); // next chunk (larger than 64MB to ensure the same pool is used) + auto r = GC.malloc(65 << 20); // another chunk in same pool + assert(p + (260 << 20) == q); + assert(q + (65 << 20) == r); + GC.free(q); + // should trigger "assert(bin == B_FREE);" in mark due to dangling pointer q: + GC.collect(); + // should trigger "break;" in extendNoSync: + size_t sz = GC.extend(p, 64 << 20, 66 << 20); // trigger size after p large enough (but limited) + assert(sz == 325 << 20); + GC.free(p); + GC.free(r); + r = q; // ensure q is not trashed before collection above + + p = GC.malloc(70 << 20); // from the same pool + q = GC.malloc(70 << 20); + r = GC.malloc(70 << 20); + auto s = GC.malloc(70 << 20); + auto t = GC.malloc(70 << 20); // 350 MB of 390 MB used + assert(p + (70 << 20) == q); + assert(q + (70 << 20) == r); + assert(r + (70 << 20) == s); + assert(s + (70 << 20) == t); + GC.free(r); // ensure recalculation of largestFree in nxxt allocPages + auto z = GC.malloc(75 << 20); // needs new pool + + GC.free(p); + GC.free(q); + GC.free(s); + GC.free(t); + GC.free(z); + GC.minimize(); // release huge pool +} + +// https://issues.dlang.org/show_bug.cgi?id=19281 +debug (SENTINEL) {} else // cannot allow >= 4 GB with SENTINEL +debug (MEMSTOMP) {} else // might take too long to actually touch the memory +version (D_LP64) unittest +{ + static if (__traits(compiles, os_physical_mem)) + { + // only run if the system has enough physical memory + size_t sz = 2L^^32; + //import core.stdc.stdio; + //printf("availphys = %lld", os_physical_mem()); + if (os_physical_mem() > sz) + { + import core.memory; + GC.collect(); + GC.minimize(); + auto stats = GC.stats(); + auto ptr = GC.malloc(sz, BlkAttr.NO_SCAN); + auto info = GC.query(ptr); + //printf("info.size = %lld", info.size); + assert(info.size >= sz); + GC.free(ptr); + GC.minimize(); + auto nstats = GC.stats(); + assert(nstats.usedSize == stats.usedSize); + assert(nstats.freeSize == stats.freeSize); + assert(nstats.allocatedInCurrentThread - sz == stats.allocatedInCurrentThread); + } + } +} + +// https://issues.dlang.org/show_bug.cgi?id=19522 +unittest +{ + import core.memory; + + void test(void* p) + { + assert(GC.getAttr(p) == BlkAttr.NO_SCAN); + assert(GC.setAttr(p + 4, BlkAttr.NO_SCAN) == 0); // interior pointer should fail + assert(GC.clrAttr(p + 4, BlkAttr.NO_SCAN) == 0); // interior pointer should fail + GC.free(p); + assert(GC.query(p).base == null); + assert(GC.query(p).size == 0); + assert(GC.addrOf(p) == null); + assert(GC.sizeOf(p) == 0); // fails + assert(GC.getAttr(p) == 0); + assert(GC.setAttr(p, BlkAttr.NO_SCAN) == 0); + assert(GC.clrAttr(p, BlkAttr.NO_SCAN) == 0); + } + void* large = GC.malloc(10000, BlkAttr.NO_SCAN); + test(large); + + void* small = GC.malloc(100, BlkAttr.NO_SCAN); + test(small); +} + +unittest +{ + import core.memory; + + auto now = currTime; + GC.ProfileStats stats1 = GC.profileStats(); + GC.collect(); + GC.ProfileStats stats2 = GC.profileStats(); + auto diff = currTime - now; + + assert(stats2.totalCollectionTime - stats1.totalCollectionTime <= diff); + assert(stats2.totalPauseTime - stats1.totalPauseTime <= stats2.totalCollectionTime - stats1.totalCollectionTime); + + assert(stats2.maxPauseTime >= stats1.maxPauseTime); + assert(stats2.maxCollectionTime >= stats1.maxCollectionTime); +} + +// https://issues.dlang.org/show_bug.cgi?id=20214 +unittest +{ + import core.memory; + import core.stdc.stdio; + + // allocate from large pool + auto o = GC.malloc(10); + auto p = (cast(void**)GC.malloc(4096 * (void*).sizeof))[0 .. 4096]; + auto q = (cast(void**)GC.malloc(4096 * (void*).sizeof))[0 .. 4096]; + if (p.ptr + p.length is q.ptr) + { + q[] = o; // fill with pointers + + // shrink, unused area cleared? + auto nq = (cast(void**)GC.realloc(q.ptr, 4000 * (void*).sizeof))[0 .. 4000]; + assert(q.ptr is nq.ptr); + assert(q.ptr[4095] !is o); + + GC.free(q.ptr); + // expected to extend in place + auto np = (cast(void**)GC.realloc(p.ptr, 4200 * (void*).sizeof))[0 .. 4200]; + assert(p.ptr is np.ptr); + assert(q.ptr[4200] !is o); + } + else + { + // adjacent allocations likely but not guaranteed + printf("unexpected pointers %p and %p\n", p.ptr, q.ptr); + } +} diff --git a/libphobos/libdruntime/core/internal/gc/impl/manual/gc.d b/libphobos/libdruntime/core/internal/gc/impl/manual/gc.d new file mode 100644 index 0000000..a65c636 --- /dev/null +++ b/libphobos/libdruntime/core/internal/gc/impl/manual/gc.d @@ -0,0 +1,269 @@ +/** + * This module contains a minimal garbage collector implementation according to + * published requirements. This library is mostly intended to serve as an + * example, but it is usable in applications which do not rely on a garbage + * collector to clean up memory (ie. when dynamic array resizing is not used, + * and all memory allocated with 'new' is freed deterministically with + * 'delete'). + * + * Please note that block attribute data must be tracked, or at a minimum, the + * FINALIZE bit must be tracked for any allocated memory block because calling + * rt_finalize on a non-object block can result in an access violation. In the + * allocator below, this tracking is done via a leading uint bitmask. A real + * allocator may do better to store this data separately, similar to the basic + * GC. + * + * Copyright: Copyright Sean Kelly 2005 - 2016. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Sean Kelly + */ +module core.internal.gc.impl.manual.gc; + +import core.gc.gcinterface; + +import core.internal.container.array; + +import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc; +static import core.memory; + +extern (C) void onOutOfMemoryError(void* pretend_sideffect = null) @trusted pure nothrow @nogc; /* dmd @@@BUG11461@@@ */ + +// register GC in C constructor (_STI_) +extern(C) pragma(crt_constructor) void _d_register_manual_gc() +{ + import core.gc.registry; + registerGCFactory("manual", &initialize); +} + +private GC initialize() +{ + import core.lifetime : emplace; + + auto gc = cast(ManualGC) cstdlib.malloc(__traits(classInstanceSize, ManualGC)); + if (!gc) + onOutOfMemoryError(); + + return emplace(gc); +} + +class ManualGC : GC +{ + Array!Root roots; + Array!Range ranges; + + this() + { + } + + ~this() + { + // TODO: cannot free as memory is overwritten and + // the monitor is still read in rt_finalize (called by destroy) + // cstdlib.free(cast(void*) this); + } + + void enable() + { + } + + void disable() + { + } + + void collect() nothrow + { + } + + void collectNoStack() nothrow + { + } + + void minimize() nothrow + { + } + + uint getAttr(void* p) nothrow + { + return 0; + } + + uint setAttr(void* p, uint mask) nothrow + { + return 0; + } + + uint clrAttr(void* p, uint mask) nothrow + { + return 0; + } + + void* malloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + void* p = cstdlib.malloc(size); + + if (size && p is null) + onOutOfMemoryError(); + return p; + } + + BlkInfo qalloc(size_t size, uint bits, const scope TypeInfo ti) nothrow + { + BlkInfo retval; + retval.base = malloc(size, bits, ti); + retval.size = size; + retval.attr = bits; + return retval; + } + + void* calloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + void* p = cstdlib.calloc(1, size); + + if (size && p is null) + onOutOfMemoryError(); + return p; + } + + void* realloc(void* p, size_t size, uint bits, const TypeInfo ti) nothrow + { + p = cstdlib.realloc(p, size); + + if (size && p is null) + onOutOfMemoryError(); + return p; + } + + size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow + { + return 0; + } + + size_t reserve(size_t size) nothrow + { + return 0; + } + + void free(void* p) nothrow @nogc + { + cstdlib.free(p); + } + + /** + * Determine the base address of the block containing p. If p is not a gc + * allocated pointer, return null. + */ + void* addrOf(void* p) nothrow @nogc + { + return null; + } + + /** + * Determine the allocated size of pointer p. If p is an interior pointer + * or not a gc allocated pointer, return 0. + */ + size_t sizeOf(void* p) nothrow @nogc + { + return 0; + } + + /** + * Determine the base address of the block containing p. If p is not a gc + * allocated pointer, return null. + */ + BlkInfo query(void* p) nothrow + { + return BlkInfo.init; + } + + core.memory.GC.Stats stats() nothrow + { + return typeof(return).init; + } + + core.memory.GC.ProfileStats profileStats() nothrow + { + return typeof(return).init; + } + + void addRoot(void* p) nothrow @nogc + { + roots.insertBack(Root(p)); + } + + void removeRoot(void* p) nothrow @nogc + { + foreach (ref r; roots) + { + if (r is p) + { + r = roots.back; + roots.popBack(); + return; + } + } + assert(false); + } + + @property RootIterator rootIter() return @nogc + { + return &rootsApply; + } + + private int rootsApply(scope int delegate(ref Root) nothrow dg) + { + foreach (ref r; roots) + { + if (auto result = dg(r)) + return result; + } + return 0; + } + + void addRange(void* p, size_t sz, const TypeInfo ti = null) nothrow @nogc + { + ranges.insertBack(Range(p, p + sz, cast() ti)); + } + + void removeRange(void* p) nothrow @nogc + { + foreach (ref r; ranges) + { + if (r.pbot is p) + { + r = ranges.back; + ranges.popBack(); + return; + } + } + assert(false); + } + + @property RangeIterator rangeIter() return @nogc + { + return &rangesApply; + } + + private int rangesApply(scope int delegate(ref Range) nothrow dg) + { + foreach (ref r; ranges) + { + if (auto result = dg(r)) + return result; + } + return 0; + } + + void runFinalizers(const scope void[] segment) nothrow + { + } + + bool inFinalizer() nothrow + { + return false; + } + + ulong allocatedInCurrentThread() nothrow + { + return typeof(return).init; + } +} diff --git a/libphobos/libdruntime/core/internal/gc/impl/proto/gc.d b/libphobos/libdruntime/core/internal/gc/impl/proto/gc.d new file mode 100644 index 0000000..ff044d9 --- /dev/null +++ b/libphobos/libdruntime/core/internal/gc/impl/proto/gc.d @@ -0,0 +1,248 @@ + +module core.internal.gc.impl.proto.gc; + +import core.gc.gcinterface; + +import core.internal.container.array; + +import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc; +static import core.memory; + +extern (C) void onOutOfMemoryError(void* pretend_sideffect = null) @trusted pure nothrow @nogc; /* dmd @@@BUG11461@@@ */ + +private +{ + extern (C) void gc_init_nothrow() nothrow @nogc; + extern (C) void gc_term(); + + extern (C) void gc_enable() nothrow; + extern (C) void gc_disable() nothrow; + + extern (C) void* gc_malloc( size_t sz, uint ba = 0, const scope TypeInfo = null ) pure nothrow; + extern (C) void* gc_calloc( size_t sz, uint ba = 0, const scope TypeInfo = null ) pure nothrow; + extern (C) BlkInfo gc_qalloc( size_t sz, uint ba = 0, const scope TypeInfo = null ) pure nothrow; + extern (C) void* gc_realloc(return scope void* p, size_t sz, uint ba = 0, const scope TypeInfo = null ) pure nothrow; + extern (C) size_t gc_reserve( size_t sz ) nothrow; + + extern (C) void gc_addRange(const void* p, size_t sz, const scope TypeInfo ti = null ) nothrow @nogc; + extern (C) void gc_addRoot(const void* p ) nothrow @nogc; +} + +class ProtoGC : GC +{ + Array!Root roots; + Array!Range ranges; + + // Call this function when initializing the real GC + // upon ProtoGC term. This function should be called + // after the real GC is in place. + void transferRangesAndRoots() + { + // Transfer all ranges + foreach (ref r; ranges) + { + // Range(p, p + sz, cast() ti) + gc_addRange(r.pbot, r.ptop - r.pbot, r.ti); + } + + // Transfer all roots + foreach (ref r; roots) + { + gc_addRoot(r.proot); + } + } + + this() + { + } + + void Dtor() + { + } + + void enable() + { + .gc_init_nothrow(); + .gc_enable(); + } + + void disable() + { + .gc_init_nothrow(); + .gc_disable(); + } + + void collect() nothrow + { + } + + void collectNoStack() nothrow + { + } + + void minimize() nothrow + { + } + + uint getAttr(void* p) nothrow + { + return 0; + } + + uint setAttr(void* p, uint mask) nothrow + { + return 0; + } + + uint clrAttr(void* p, uint mask) nothrow + { + return 0; + } + + void* malloc(size_t size, uint bits, const scope TypeInfo ti) nothrow + { + .gc_init_nothrow(); + return .gc_malloc(size, bits, ti); + } + + BlkInfo qalloc(size_t size, uint bits, const scope TypeInfo ti) nothrow + { + .gc_init_nothrow(); + return .gc_qalloc(size, bits, ti); + } + + void* calloc(size_t size, uint bits, const scope TypeInfo ti) nothrow + { + .gc_init_nothrow(); + return .gc_calloc(size, bits, ti); + } + + void* realloc(void* p, size_t size, uint bits, const scope TypeInfo ti) nothrow + { + .gc_init_nothrow(); + return .gc_realloc(p, size, bits, ti); + } + + size_t extend(void* p, size_t minsize, size_t maxsize, const scope TypeInfo ti) nothrow + { + return 0; + } + + size_t reserve(size_t size) nothrow + { + .gc_init_nothrow(); + return .gc_reserve(size); + } + + void free(void* p) nothrow @nogc + { + if (p) assert(false, "Invalid memory deallocation"); + } + + void* addrOf(void* p) nothrow @nogc + { + return null; + } + + size_t sizeOf(void* p) nothrow @nogc + { + return 0; + } + + BlkInfo query(void* p) nothrow + { + return BlkInfo.init; + } + + core.memory.GC.Stats stats() nothrow + { + return typeof(return).init; + } + + + core.memory.GC.ProfileStats profileStats() nothrow + { + return typeof(return).init; + } + + + void addRoot(void* p) nothrow @nogc + { + roots.insertBack(Root(p)); + } + + void removeRoot(void* p) nothrow @nogc + { + foreach (ref r; roots) + { + if (r is p) + { + r = roots.back; + roots.popBack(); + return; + } + } + } + + @property RootIterator rootIter() return @nogc + { + return &rootsApply; + } + + private int rootsApply(scope int delegate(ref Root) nothrow dg) + { + foreach (ref r; roots) + { + if (auto result = dg(r)) + return result; + } + return 0; + } + + void addRange(void* p, size_t sz, const TypeInfo ti = null) nothrow @nogc + { + ranges.insertBack(Range(p, p + sz, cast() ti)); + } + + void removeRange(void* p) nothrow @nogc + { + foreach (ref r; ranges) + { + if (r.pbot is p) + { + r = ranges.back; + ranges.popBack(); + return; + } + } + } + + @property RangeIterator rangeIter() return @nogc + { + return &rangesApply; + } + + private int rangesApply(scope int delegate(ref Range) nothrow dg) + { + foreach (ref r; ranges) + { + if (auto result = dg(r)) + return result; + } + return 0; + } + + void runFinalizers(const scope void[] segment) nothrow + { + } + + bool inFinalizer() nothrow + { + return false; + } + + ulong allocatedInCurrentThread() nothrow + { + return stats().allocatedInCurrentThread; + } +} diff --git a/libphobos/libdruntime/core/internal/gc/os.d b/libphobos/libdruntime/core/internal/gc/os.d new file mode 100644 index 0000000..ca4cbe2 --- /dev/null +++ b/libphobos/libdruntime/core/internal/gc/os.d @@ -0,0 +1,308 @@ +/** + * Contains OS-level routines needed by the garbage collector. + * + * Copyright: D Language Foundation 2005 - 2021. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, David Friedman, Sean Kelly, Leandro Lucarella + */ +module core.internal.gc.os; + + +version (Windows) +{ + import core.sys.windows.winbase : GetCurrentThreadId, VirtualAlloc, VirtualFree; + import core.sys.windows.winnt : MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE; + + alias int pthread_t; + + pthread_t pthread_self() nothrow + { + return cast(pthread_t) GetCurrentThreadId(); + } + + //version = GC_Use_Alloc_Win32; +} +else version (Posix) +{ + version (OSX) + version = Darwin; + else version (iOS) + version = Darwin; + else version (TVOS) + version = Darwin; + else version (WatchOS) + version = Darwin; + + import core.sys.posix.sys.mman; + import core.stdc.stdlib; + + + /// Possible results for the wait_pid() function. + enum ChildStatus + { + done, /// The process has finished successfully + running, /// The process is still running + error /// There was an error waiting for the process + } + + /** + * Wait for a process with PID pid to finish. + * + * If block is false, this function will not block, and return ChildStatus.running if + * the process is still running. Otherwise it will return always ChildStatus.done + * (unless there is an error, in which case ChildStatus.error is returned). + */ + ChildStatus wait_pid(pid_t pid, bool block = true) nothrow @nogc + { + import core.exception : onForkError; + + int status = void; + pid_t waited_pid = void; + // In the case where we are blocking, we need to consider signals + // arriving while we wait, and resume the waiting if EINTR is returned + do { + errno = 0; + waited_pid = waitpid(pid, &status, block ? 0 : WNOHANG); + } + while (waited_pid == -1 && errno == EINTR); + if (waited_pid == 0) + return ChildStatus.running; + else if (errno == ECHILD) + return ChildStatus.done; // someone called posix.syswait + else if (waited_pid != pid || status != 0) + { + onForkError(); + return ChildStatus.error; + } + return ChildStatus.done; + } + + public import core.sys.posix.unistd: pid_t, fork; + import core.sys.posix.sys.wait: waitpid, WNOHANG; + import core.stdc.errno: errno, EINTR, ECHILD; + + //version = GC_Use_Alloc_MMap; +} +else +{ + import core.stdc.stdlib; + + //version = GC_Use_Alloc_Malloc; +} + +/+ +static if (is(typeof(VirtualAlloc))) + version = GC_Use_Alloc_Win32; +else static if (is(typeof(mmap))) + version = GC_Use_Alloc_MMap; +else static if (is(typeof(valloc))) + version = GC_Use_Alloc_Valloc; +else static if (is(typeof(malloc))) + version = GC_Use_Alloc_Malloc; +else static assert(false, "No supported allocation methods available."); ++/ + +static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) +{ + /** + * Indicates if an implementation supports fork(). + * + * The value shown here is just demostrative, the real value is defined based + * on the OS it's being compiled in. + * enum HaveFork = true; + */ + enum HaveFork = false; + + /** + * Map memory. + */ + void *os_mem_map(size_t nbytes) nothrow @nogc + { + return VirtualAlloc(null, nbytes, MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + } + + + /** + * Unmap memory allocated with os_mem_map(). + * Returns: + * 0 success + * !=0 failure + */ + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc + { + return cast(int)(VirtualFree(base, 0, MEM_RELEASE) == 0); + } +} +else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) +{ + enum HaveFork = true; + + void *os_mem_map(size_t nbytes, bool share = false) nothrow @nogc + { void *p; + + auto map_f = share ? MAP_SHARED : MAP_PRIVATE; + p = mmap(null, nbytes, PROT_READ | PROT_WRITE, map_f | MAP_ANON, -1, 0); + return (p == MAP_FAILED) ? null : p; + } + + + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc + { + return munmap(base, nbytes); + } +} +else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc) +{ + enum HaveFork = false; + + void *os_mem_map(size_t nbytes) nothrow @nogc + { + return valloc(nbytes); + } + + + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc + { + free(base); + return 0; + } +} +else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc) +{ + // NOTE: This assumes malloc granularity is at least (void*).sizeof. If + // (req_size + PAGESIZE) is allocated, and the pointer is rounded up + // to PAGESIZE alignment, there will be space for a void* at the end + // after PAGESIZE bytes used by the GC. + + enum HaveFork = false; + + import core.internal.gc.impl.conservative.gc; + + + const size_t PAGE_MASK = PAGESIZE - 1; + + + void *os_mem_map(size_t nbytes) nothrow @nogc + { byte *p, q; + p = cast(byte *) malloc(nbytes + PAGESIZE); + if (!p) + return null; + q = p + ((PAGESIZE - ((cast(size_t) p & PAGE_MASK))) & PAGE_MASK); + * cast(void**)(q + nbytes) = p; + return q; + } + + + int os_mem_unmap(void *base, size_t nbytes) nothrow @nogc + { + free( *cast(void**)( cast(byte*) base + nbytes ) ); + return 0; + } +} +else +{ + static assert(false, "No supported allocation methods available."); +} + +/** + Check for any kind of memory pressure. + + Params: + mapped = the amount of memory mapped by the GC in bytes + Returns: + true if memory is scarce +*/ +// TOOD: get virtual mem sizes and current usage from OS +// TODO: compare current RSS and avail. physical memory +version (Windows) +{ + bool isLowOnMem(size_t mapped) nothrow @nogc + { + version (D_LP64) + return false; + else + { + import core.sys.windows.winbase : GlobalMemoryStatus, MEMORYSTATUS; + MEMORYSTATUS stat; + GlobalMemoryStatus(&stat); + // Less than 5 % of virtual address space available + return stat.dwAvailVirtual < stat.dwTotalVirtual / 20; + } + } +} +else version (Darwin) +{ + bool isLowOnMem(size_t mapped) nothrow @nogc + { + enum GB = 2 ^^ 30; + version (D_LP64) + return false; + else + { + // 80 % of available 4GB is used for GC (excluding malloc and mmap) + enum size_t limit = 4UL * GB * 8 / 10; + return mapped > limit; + } + } +} +else +{ + bool isLowOnMem(size_t mapped) nothrow @nogc + { + enum GB = 2 ^^ 30; + version (D_LP64) + return false; + else + { + // be conservative and assume 3GB + enum size_t limit = 3UL * GB * 8 / 10; + return mapped > limit; + } + } +} + +/** + Get the size of available physical memory + + Returns: + size of installed physical RAM +*/ +version (Windows) +{ + ulong os_physical_mem() nothrow @nogc + { + import core.sys.windows.winbase : GlobalMemoryStatus, MEMORYSTATUS; + MEMORYSTATUS stat; + GlobalMemoryStatus(&stat); + return stat.dwTotalPhys; // limited to 4GB for Win32 + } +} +else version (Darwin) +{ + extern (C) int sysctl(const int* name, uint namelen, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) @nogc nothrow; + ulong os_physical_mem() nothrow @nogc + { + enum + { + CTL_HW = 6, + HW_MEMSIZE = 24, + } + int[2] mib = [ CTL_HW, HW_MEMSIZE ]; + ulong system_memory_bytes; + size_t len = system_memory_bytes.sizeof; + if (sysctl(mib.ptr, 2, &system_memory_bytes, &len, null, 0) != 0) + return 0; + return system_memory_bytes; + } +} +else version (Posix) +{ + ulong os_physical_mem() nothrow @nogc + { + import core.sys.posix.unistd : sysconf, _SC_PAGESIZE, _SC_PHYS_PAGES; + const pageSize = sysconf(_SC_PAGESIZE); + const pages = sysconf(_SC_PHYS_PAGES); + return pageSize * pages; + } +} diff --git a/libphobos/libdruntime/core/internal/gc/pooltable.d b/libphobos/libdruntime/core/internal/gc/pooltable.d new file mode 100644 index 0000000..5924f9c --- /dev/null +++ b/libphobos/libdruntime/core/internal/gc/pooltable.d @@ -0,0 +1,295 @@ +/** + * A sorted array to quickly lookup pools. + * + * Copyright: D Language Foundation 2001 - 2021 + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, David Friedman, Sean Kelly, Martin Nowak + */ +module core.internal.gc.pooltable; + +static import cstdlib=core.stdc.stdlib; + +struct PoolTable(Pool) +{ + import core.stdc.string : memmove; + +nothrow: + void Dtor() + { + cstdlib.free(pools); + pools = null; + npools = 0; + } + + bool insert(Pool* pool) + { + auto newpools = cast(Pool **)cstdlib.realloc(pools, (npools + 1) * pools[0].sizeof); + if (!newpools) + return false; + + pools = newpools; + + // Sort pool into newpooltable[] + size_t i; + for (; i < npools; ++i) + { + if (pool.baseAddr < pools[i].baseAddr) + break; + } + if (i != npools) + memmove(pools + i + 1, pools + i, (npools - i) * pools[0].sizeof); + pools[i] = pool; + + ++npools; + + foreach (idx; i .. npools) + pools[idx].ptIndex = idx; + + _minAddr = pools[0].baseAddr; + _maxAddr = pools[npools - 1].topAddr; + + return true; + } + + @property size_t length() pure const + { + return npools; + } + + ref inout(Pool*) opIndex(size_t idx) inout pure + in { assert(idx < length); } + do + { + return pools[idx]; + } + + inout(Pool*)[] opSlice(size_t a, size_t b) inout pure + in { assert(a <= length && b <= length); } + do + { + return pools[a .. b]; + } + + alias opDollar = length; + + /** + * Find Pool that pointer is in. + * Return null if not in a Pool. + * Assume pooltable[] is sorted. + */ + Pool *findPool(void *p) nothrow + { + if (p >= minAddr && p < maxAddr) + { + assert(npools); + + // let dmd allocate a register for this.pools + auto pools = this.pools; + + if (npools == 1) + return pools[0]; + + /* The pooltable[] is sorted by address, so do a binary search + */ + size_t low = 0; + size_t high = npools - 1; + while (low <= high) + { + size_t mid = (low + high) >> 1; + auto pool = pools[mid]; + if (p < pool.baseAddr) + high = mid - 1; + else if (p >= pool.topAddr) + low = mid + 1; + else + return pool; + } + } + return null; + } + + // semi-stable partition, returns right half for which pred is false + Pool*[] minimize() pure + { + static void swap(ref Pool* a, ref Pool* b) + { + auto c = a; a = b; b = c; + } + + size_t i; + // find first bad entry + for (; i < npools; ++i) + if (pools[i].isFree) break; + + // move good in front of bad entries + size_t j = i + 1; + for (; j < npools; ++j) + { + if (!pools[j].isFree) // keep + { + swap(pools[i], pools[j]); + pools[i].ptIndex = i; + ++i; + } + } + // npooltable[0 .. i] => used pools + // npooltable[i .. npools] => free pools + + if (i) + { + _minAddr = pools[0].baseAddr; + _maxAddr = pools[i - 1].topAddr; + } + else + { + _minAddr = _maxAddr = null; + } + + immutable len = npools; + npools = i; + // return freed pools to the caller + return pools[npools .. len]; + } + + void Invariant() const + { + if (!npools) return; + + foreach (i; 0 .. npools) + assert(pools[i].ptIndex == i); + + foreach (i, pool; pools[0 .. npools - 1]) + assert(pool.baseAddr < pools[i + 1].baseAddr); + + assert(_minAddr == pools[0].baseAddr); + assert(_maxAddr == pools[npools - 1].topAddr); + } + + @property const(void)* minAddr() pure const { return _minAddr; } + @property const(void)* maxAddr() pure const { return _maxAddr; } + +package: + Pool** pools; + size_t npools; + void* _minAddr, _maxAddr; +} + +unittest +{ + enum NPOOLS = 6; + enum NPAGES = 10; + enum PAGESIZE = 4096; + + static struct MockPool + { + byte* baseAddr, topAddr; + size_t freepages, npages, ptIndex; + @property bool isFree() const pure nothrow { return freepages == npages; } + } + PoolTable!MockPool pooltable; + + void reset() + { + foreach (ref pool; pooltable[0 .. $]) + pool.freepages = pool.npages; + pooltable.minimize(); + assert(pooltable.length == 0); + + foreach (i; 0 .. NPOOLS) + { + auto pool = cast(MockPool*)cstdlib.malloc(MockPool.sizeof); + *pool = MockPool.init; + assert(pooltable.insert(pool)); + } + } + + void usePools() + { + foreach (pool; pooltable[0 .. $]) + { + pool.npages = NPAGES; + pool.freepages = NPAGES / 2; + } + } + + // all pools are free + reset(); + assert(pooltable.length == NPOOLS); + auto freed = pooltable.minimize(); + assert(freed.length == NPOOLS); + assert(pooltable.length == 0); + + // all pools used + reset(); + usePools(); + assert(pooltable.length == NPOOLS); + freed = pooltable.minimize(); + assert(freed.length == 0); + assert(pooltable.length == NPOOLS); + + // preserves order of used pools + reset(); + usePools(); + + { + MockPool*[NPOOLS] opools = pooltable[0 .. NPOOLS]; + // make the 2nd pool free + pooltable[2].freepages = NPAGES; + + pooltable.minimize(); + assert(pooltable.length == NPOOLS - 1); + assert(pooltable[0] == opools[0]); + assert(pooltable[1] == opools[1]); + assert(pooltable[2] == opools[3]); + } + + // test that PoolTable reduces min/max address span + reset(); + usePools(); + + byte* base, top; + + { + // fill with fake addresses + size_t i; + foreach (pool; pooltable[0 .. NPOOLS]) + { + pool.baseAddr = cast(byte*)(i++ * NPAGES * PAGESIZE); + pool.topAddr = pool.baseAddr + NPAGES * PAGESIZE; + } + base = pooltable[0].baseAddr; + top = pooltable[NPOOLS - 1].topAddr; + } + + freed = pooltable.minimize(); + assert(freed.length == 0); + assert(pooltable.length == NPOOLS); + assert(pooltable.minAddr == base); + assert(pooltable.maxAddr == top); + + pooltable[NPOOLS - 1].freepages = NPAGES; + pooltable[NPOOLS - 2].freepages = NPAGES; + + freed = pooltable.minimize(); + assert(freed.length == 2); + assert(pooltable.length == NPOOLS - 2); + assert(pooltable.minAddr == base); + assert(pooltable.maxAddr == pooltable[NPOOLS - 3].topAddr); + + pooltable[0].freepages = NPAGES; + + freed = pooltable.minimize(); + assert(freed.length == 1); + assert(pooltable.length == NPOOLS - 3); + assert(pooltable.minAddr != base); + assert(pooltable.minAddr == pooltable[0].baseAddr); + assert(pooltable.maxAddr == pooltable[NPOOLS - 4].topAddr); + + // free all + foreach (pool; pooltable[0 .. $]) + pool.freepages = NPAGES; + freed = pooltable.minimize(); + assert(freed.length == NPOOLS - 3); + assert(pooltable.length == 0); + pooltable.Dtor(); +} diff --git a/libphobos/libdruntime/core/internal/gc/proxy.d b/libphobos/libdruntime/core/internal/gc/proxy.d new file mode 100644 index 0000000..2c89472 --- /dev/null +++ b/libphobos/libdruntime/core/internal/gc/proxy.d @@ -0,0 +1,296 @@ +/** + * Contains the external GC interface. + * + * Copyright: D Language Foundation 2005 - 2021. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, Sean Kelly + */ +module core.internal.gc.proxy; + +import core.internal.gc.impl.proto.gc; +import core.gc.config; +import core.gc.gcinterface; +import core.gc.registry : createGCInstance; + +static import core.memory; + +private +{ + static import core.memory; + alias BlkInfo = core.memory.GC.BlkInfo; + + import core.internal.spinlock; + static SpinLock instanceLock; + + __gshared bool isInstanceInit = false; + __gshared GC _instance = new ProtoGC(); + __gshared GC proxiedGC; // used to iterate roots of Windows DLLs + + pragma (inline, true) @trusted @nogc nothrow + GC instance() { return _instance; } +} + +extern (C) +{ + import core.attribute : weak; + + // do not import GC modules, they might add a dependency to this whole module + void _d_register_conservative_gc(); + void _d_register_manual_gc(); + + // if you don't want to include the default GCs, replace during link by another implementation + void* register_default_gcs() @weak + { + pragma(inline, false); + // do not call, they register implicitly through pragma(crt_constructor) + // avoid being optimized away + auto reg1 = &_d_register_conservative_gc; + auto reg2 = &_d_register_manual_gc; + return reg1 < reg2 ? reg1 : reg2; + } + + void gc_init() + { + instanceLock.lock(); + if (!isInstanceInit) + { + register_default_gcs(); + config.initialize(); + auto protoInstance = instance; + auto newInstance = createGCInstance(config.gc); + if (newInstance is null) + { + import core.stdc.stdio : fprintf, stderr; + import core.stdc.stdlib : exit; + + fprintf(stderr, "No GC was initialized, please recheck the name of the selected GC ('%.*s').\n", cast(int)config.gc.length, config.gc.ptr); + instanceLock.unlock(); + exit(1); + + // Shouldn't get here. + assert(0); + } + _instance = newInstance; + // Transfer all ranges and roots to the real GC. + (cast(ProtoGC) protoInstance).transferRangesAndRoots(); + isInstanceInit = true; + } + instanceLock.unlock(); + } + + void gc_init_nothrow() nothrow + { + scope(failure) + { + import core.internal.abort; + abort("Cannot initialize the garbage collector.\n"); + assert(0); + } + gc_init(); + } + + void gc_term() + { + if (isInstanceInit) + { + switch (config.cleanup) + { + default: + import core.stdc.stdio : fprintf, stderr; + fprintf(stderr, "Unknown GC cleanup method, please recheck ('%.*s').\n", + cast(int)config.cleanup.length, config.cleanup.ptr); + break; + case "none": + break; + case "collect": + // NOTE: There may be daemons threads still running when this routine is + // called. If so, cleaning memory out from under then is a good + // way to make them crash horribly. This probably doesn't matter + // much since the app is supposed to be shutting down anyway, but + // I'm disabling cleanup for now until I can think about it some + // more. + // + // NOTE: Due to popular demand, this has been re-enabled. It still has + // the problems mentioned above though, so I guess we'll see. + + instance.collectNoStack(); // not really a 'collect all' -- still scans + // static data area, roots, and ranges. + break; + case "finalize": + instance.runFinalizers((cast(ubyte*)null)[0 .. size_t.max]); + break; + } + destroy(instance); + } + } + + void gc_enable() + { + instance.enable(); + } + + void gc_disable() + { + instance.disable(); + } + + void gc_collect() nothrow + { + instance.collect(); + } + + void gc_minimize() nothrow + { + instance.minimize(); + } + + uint gc_getAttr( void* p ) nothrow + { + return instance.getAttr(p); + } + + uint gc_setAttr( void* p, uint a ) nothrow + { + return instance.setAttr(p, a); + } + + uint gc_clrAttr( void* p, uint a ) nothrow + { + return instance.clrAttr(p, a); + } + + void* gc_malloc( size_t sz, uint ba = 0, const scope TypeInfo ti = null ) nothrow + { + return instance.malloc(sz, ba, ti); + } + + BlkInfo gc_qalloc( size_t sz, uint ba = 0, const scope TypeInfo ti = null ) nothrow + { + return instance.qalloc( sz, ba, ti ); + } + + void* gc_calloc( size_t sz, uint ba = 0, const scope TypeInfo ti = null ) nothrow + { + return instance.calloc( sz, ba, ti ); + } + + void* gc_realloc( void* p, size_t sz, uint ba = 0, const scope TypeInfo ti = null ) nothrow + { + return instance.realloc( p, sz, ba, ti ); + } + + size_t gc_extend( void* p, size_t mx, size_t sz, const scope TypeInfo ti = null ) nothrow + { + return instance.extend( p, mx, sz,ti ); + } + + size_t gc_reserve( size_t sz ) nothrow + { + return instance.reserve( sz ); + } + + void gc_free( void* p ) nothrow @nogc + { + return instance.free( p ); + } + + void* gc_addrOf( void* p ) nothrow @nogc + { + return instance.addrOf( p ); + } + + size_t gc_sizeOf( void* p ) nothrow @nogc + { + return instance.sizeOf( p ); + } + + BlkInfo gc_query( void* p ) nothrow + { + return instance.query( p ); + } + + core.memory.GC.Stats gc_stats() nothrow + { + return instance.stats(); + } + + core.memory.GC.ProfileStats gc_profileStats() nothrow @safe + { + return instance.profileStats(); + } + + void gc_addRoot( void* p ) nothrow @nogc + { + return instance.addRoot( p ); + } + + void gc_addRange( void* p, size_t sz, const TypeInfo ti = null ) nothrow @nogc + { + return instance.addRange( p, sz, ti ); + } + + void gc_removeRoot( void* p ) nothrow + { + return instance.removeRoot( p ); + } + + void gc_removeRange( void* p ) nothrow + { + return instance.removeRange( p ); + } + + void gc_runFinalizers(const scope void[] segment ) nothrow + { + return instance.runFinalizers( segment ); + } + + bool gc_inFinalizer() nothrow @nogc @safe + { + return instance.inFinalizer(); + } + + ulong gc_allocatedInCurrentThread() nothrow + { + return instance.allocatedInCurrentThread(); + } + + GC gc_getProxy() nothrow + { + return instance; + } + + export + { + void gc_setProxy( GC proxy ) + { + foreach (root; instance.rootIter) + { + proxy.addRoot(root); + } + + foreach (range; instance.rangeIter) + { + proxy.addRange(range.pbot, range.ptop - range.pbot, range.ti); + } + + proxiedGC = instance; // remember initial GC to later remove roots + _instance = proxy; + } + + void gc_clrProxy() + { + foreach (root; proxiedGC.rootIter) + { + instance.removeRoot(root); + } + + foreach (range; proxiedGC.rangeIter) + { + instance.removeRange(range); + } + + _instance = proxiedGC; + proxiedGC = null; + } + } +} diff --git a/libphobos/libdruntime/core/internal/hash.d b/libphobos/libdruntime/core/internal/hash.d index 8d0067e..e999f0c 100644 --- a/libphobos/libdruntime/core/internal/hash.d +++ b/libphobos/libdruntime/core/internal/hash.d @@ -3,14 +3,13 @@ * This module provides functions to uniform calculating hash values for different types * * Copyright: Copyright Igor Stepanov 2013-2013. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Igor Stepanov * Source: $(DRUNTIMESRC core/internal/_hash.d) */ module core.internal.hash; -import core.internal.convert; -import core.internal.traits : allSatisfy; +import core.internal.traits : Unconst; // If true ensure that positive zero and negative zero have the same hash. // Historically typeid(float).getHash did this but hashOf(float) did not. @@ -57,6 +56,15 @@ private enum isFinalClassWithAddressBasedHash(T) = __traits(isFinalClass, T) static assert(!isFinalClassWithAddressBasedHash!C3); } +private template isCppClassWithoutHash(T) +{ + static if (!is(T == class) && !is(T == interface)) + enum isCppClassWithoutHash = false; + else + enum bool isCppClassWithoutHash = __traits(getLinkage, T) == "C++" + && !is(immutable T* : immutable Object*) && !hasCallableToHash!T; +} + /+ Is it valid to calculate a hash code for T based on the bits of its representation? Always false for interfaces, dynamic arrays, and @@ -80,18 +88,21 @@ private template canBitwiseHash(T) enum canBitwiseHash = true; else static if (is(T == class)) { - enum canBitwiseHash = isFinalClassWithAddressBasedHash!T; + enum canBitwiseHash = isFinalClassWithAddressBasedHash!T || isCppClassWithoutHash!T; } else static if (is(T == interface)) { - enum canBitwiseHash = false; + enum canBitwiseHash = isCppClassWithoutHash!T; } else static if (is(T == struct)) { static if (hasCallableToHash!T || __traits(isNested, T)) enum canBitwiseHash = false; else + { + import core.internal.traits : allSatisfy; enum canBitwiseHash = allSatisfy!(.canBitwiseHash, typeof(T.tupleof)); + } } else static if (is(T == union)) { @@ -117,107 +128,19 @@ private template canBitwiseHash(T) } } -// Overly restrictive for simplicity: has false negatives but no false positives. -private template useScopeConstPassByValue(T) -{ - static if (__traits(isScalar, T)) - enum useScopeConstPassByValue = true; - else static if (is(T == class) || is(T == interface)) - // Overly restrictive for simplicity. - enum useScopeConstPassByValue = isFinalClassWithAddressBasedHash!T; - else static if (is(T == struct) || is(T == union)) - { - // Overly restrictive for simplicity. - enum useScopeConstPassByValue = T.sizeof <= (int[]).sizeof && - __traits(isPOD, T) && // "isPOD" just to check there's no dtor or postblit. - canBitwiseHash!T; // We can't verify toHash doesn't leak. - } - else static if (is(T : E[], E)) - { - static if (!__traits(isStaticArray, T)) - // Overly restrictive for simplicity. - enum useScopeConstPassByValue = .useScopeConstPassByValue!E; - else static if (T.length == 0) - enum useScopeConstPassByValue = true; - else - enum useScopeConstPassByValue = T.sizeof <= (uint[]).sizeof - && .useScopeConstPassByValue!(typeof(T.init[0])); - } - else static if (is(T : V[K], K, V)) - { - // Overly restrictive for simplicity. - enum useScopeConstPassByValue = .useScopeConstPassByValue!K - && .useScopeConstPassByValue!V; - } - else - { - static assert(is(T == delegate) || is(T : void) || is(T : typeof(null)), - "Internal error: unanticipated type "~T.stringof); - enum useScopeConstPassByValue = true; - } -} - -@safe unittest -{ - static assert(useScopeConstPassByValue!int); - static assert(useScopeConstPassByValue!string); - - static int ctr; - static struct S1 { ~this() { ctr++; } } - static struct S2 { this(this) { ctr++; } } - static assert(!useScopeConstPassByValue!S1, - "Don't default pass by value a struct with a non-vacuous destructor."); - static assert(!useScopeConstPassByValue!S2, - "Don't default pass by value a struct with a non-vacuous postblit."); -} - -//enum hash. CTFE depends on base type -size_t hashOf(T)(scope const T val) -if (is(T EType == enum) && useScopeConstPassByValue!EType) -{ - static if (is(T EType == enum)) //for EType - { - return hashOf(cast(const EType) val); - } - else - { - static assert(0); - } -} - -//enum hash. CTFE depends on base type -size_t hashOf(T)(scope const T val, size_t seed) -if (is(T EType == enum) && useScopeConstPassByValue!EType) -{ - static if (is(T EType == enum)) //for EType - { - return hashOf(cast(const EType) val, seed); - } - else - { - static assert(0); - } -} - //enum hash. CTFE depends on base type size_t hashOf(T)(auto ref T val, size_t seed = 0) -if (is(T EType == enum) && !useScopeConstPassByValue!EType) +if (is(T == enum) && !__traits(isScalar, T)) { - static if (is(T EType == enum)) //for EType - { - EType e_val = cast(EType)val; - return hashOf(e_val, seed); - } - else - { - static assert(0); - } + static if (is(T EType == enum)) {} //for EType + return hashOf(cast(EType) val, seed); } //CTFE ready (depends on base type). size_t hashOf(T)(scope const auto ref T val, size_t seed = 0) if (!is(T == enum) && __traits(isStaticArray, T) && canBitwiseHash!T) { + import core.internal.convert : toUbyte; // FIXME: // We would like to to do this: // @@ -268,10 +191,9 @@ if (!is(T == enum) && __traits(isStaticArray, T) && !canBitwiseHash!T) //dynamic array hash size_t hashOf(T)(scope const T val, size_t seed = 0) -if (!is(T == enum) && !is(T : typeof(null)) && is(T S: S[]) && !__traits(isStaticArray, T) - && !is(T == struct) && !is(T == class) && !is(T == union) - && (__traits(isScalar, S) || canBitwiseHash!S)) +if (is(T == S[], S) && (__traits(isScalar, S) || canBitwiseHash!S)) // excludes enum types { + import core.internal.convert : toUbyte; alias ElementType = typeof(val[0]); static if (!canBitwiseHash!ElementType) { @@ -296,9 +218,7 @@ if (!is(T == enum) && !is(T : typeof(null)) && is(T S: S[]) && !__traits(isStati //dynamic array hash size_t hashOf(T)(T val, size_t seed = 0) -if (!is(T == enum) && !is(T : typeof(null)) && is(T S: S[]) && !__traits(isStaticArray, T) - && !is(T == struct) && !is(T == class) && !is(T == union) - && !(__traits(isScalar, S) || canBitwiseHash!S)) +if (is(T == S[], S) && !(__traits(isScalar, S) || canBitwiseHash!S)) // excludes enum types { size_t hash = seed; foreach (ref o; val) @@ -308,117 +228,148 @@ if (!is(T == enum) && !is(T : typeof(null)) && is(T S: S[]) && !__traits(isStati return hash; } -//arithmetic type hash -@trusted @nogc nothrow pure -size_t hashOf(T)(scope const T val) if (!is(T == enum) && __traits(isArithmetic, T) - && __traits(isIntegral, T) && T.sizeof <= size_t.sizeof && !is(T == __vector)) +// Indicates if F is a built-in complex number type. +private F coalesceFloat(F)(const F val) +if (__traits(isFloating, val) && !is(F == __vector) && !is(F : creal)) { + static if (floatCoalesceZeroes) + if (val == cast(F) 0) + return cast(F) 0; + static if (floatCoalesceNaNs) + if (val != val) + return F.nan; return val; } -//arithmetic type hash +//scalar type hash @trusted @nogc nothrow pure -size_t hashOf(T)(scope const T val, size_t seed) if (!is(T == enum) && __traits(isArithmetic, T) - && __traits(isIntegral, T) && T.sizeof <= size_t.sizeof && !is(T == __vector)) +size_t hashOf(T)(scope const T val) if (__traits(isScalar, T) && !is(T == __vector)) { - static if (size_t.sizeof < ulong.sizeof) + static if (is(T V : V*)) { - //MurmurHash3 32-bit single round - enum uint c1 = 0xcc9e2d51; - enum uint c2 = 0x1b873593; - enum uint c3 = 0xe6546b64; - enum uint r1 = 15; - enum uint r2 = 13; + if (__ctfe) + { + if (val is null) return 0; + assert(0, "Unable to calculate hash of non-null pointer at compile time"); + } + size_t result = cast(size_t) val; + return result ^ (result >> 4); + } + else static if (is(T EType == enum) && is(typeof(val[0]))) + { + // Enum type whose base type is vector. + return hashOf(cast(EType) val); + } + else static if (__traits(isIntegral, T)) + { + static if (T.sizeof <= size_t.sizeof) + return val; + else + return cast(size_t) (val ^ (val >>> (size_t.sizeof * 8))); + } + else static if (is(T : creal)) + { + return hashOf(coalesceFloat(val.re), hashOf(coalesceFloat(val.im))); } else { - //Half of MurmurHash3 64-bit single round - //(omits second interleaved update) - enum ulong c1 = 0x87c37b91114253d5; - enum ulong c2 = 0x4cf5ad432745937f; - enum ulong c3 = 0x52dce729; - enum uint r1 = 31; - enum uint r2 = 27; + static assert(__traits(isFloating, T)); + auto data = coalesceFloat(val); + static if (T.sizeof == float.sizeof && T.mant_dig == float.mant_dig) + return *cast(const uint*) &data; + else static if (T.sizeof == double.sizeof && T.mant_dig == double.mant_dig) + return hashOf(*cast(const ulong*) &data); + else + { + import core.internal.convert : floatSize, toUbyte; + return bytesHashWithExactSizeAndAlignment!T(toUbyte(data)[0 .. floatSize!T], 0); + } } - size_t h = c1 * val; - h = (h << r1) | (h >>> (size_t.sizeof * 8 - r1)); - h = (h * c2) ^ seed; - h = (h << r2) | (h >>> (size_t.sizeof * 8 - r2)); - return h * 5 + c3; } -//arithmetic type hash +//scalar type hash @trusted @nogc nothrow pure -size_t hashOf(T)(scope const T val, size_t seed = 0) if (!is(T == enum) && __traits(isArithmetic, T) - && (!__traits(isIntegral, T) || T.sizeof > size_t.sizeof) && !is(T == __vector)) +size_t hashOf(T)(scope const T val, size_t seed) if (__traits(isScalar, T) && !is(T == __vector)) { - static if (__traits(isFloating, val)) + static if (is(T V : V*)) { - import core.internal.convert : floatSize; - - static if (floatCoalesceZeroes || floatCoalesceNaNs) + if (__ctfe) { - import core.internal.traits : Unqual; - Unqual!T data = val; - // +0.0 and -0.0 become the same. - static if (floatCoalesceZeroes && is(typeof(data = 0))) - if (data == 0) data = 0; - static if (floatCoalesceZeroes && is(typeof(data = 0.0i))) - if (data == 0.0i) data = 0.0i; - static if (floatCoalesceZeroes && is(typeof(data = 0.0 + 0.0i))) - { - if (data.re == 0.0) data = 0.0 + (data.im * 1.0i); - if (data.im == 0.0i) data = data.re + 0.0i; - } - static if (floatCoalesceNaNs) - if (data != data) data = T.nan; // All NaN patterns become the same. + if (val is null) return hashOf(size_t(0), seed); + assert(0, "Unable to calculate hash of non-null pointer at compile time"); + } + return hashOf(cast(size_t) val, seed); + } + else static if (is(T EType == enum) && is(typeof(val[0]))) + { + // Enum type whose base type is vector. + return hashOf(cast(EType) val, seed); + } + else static if (__traits(isIntegral, val) && T.sizeof <= size_t.sizeof) + { + static if (size_t.sizeof < ulong.sizeof) + { + //MurmurHash3 32-bit single round + enum uint c1 = 0xcc9e2d51; + enum uint c2 = 0x1b873593; + enum uint c3 = 0xe6546b64; + enum uint r1 = 15; + enum uint r2 = 13; } else { - alias data = val; + //Half of MurmurHash3 64-bit single round + //(omits second interleaved update) + enum ulong c1 = 0x87c37b91114253d5; + enum ulong c2 = 0x4cf5ad432745937f; + enum ulong c3 = 0x52dce729; + enum uint r1 = 31; + enum uint r2 = 27; } - - static if (T.mant_dig == float.mant_dig && T.sizeof == uint.sizeof) + size_t h = c1 * val; + h = (h << r1) | (h >>> (size_t.sizeof * 8 - r1)); + h = (h * c2) ^ seed; + h = (h << r2) | (h >>> (size_t.sizeof * 8 - r2)); + return h * 5 + c3; + } + else static if (__traits(isIntegral, val) && T.sizeof > size_t.sizeof) + { + static foreach (i; 0 .. T.sizeof / size_t.sizeof) + seed = hashOf(cast(size_t) (val >>> (size_t.sizeof * 8 * i)), seed); + return seed; + } + else static if (is(T : creal)) + { + return hashOf(val.re, hashOf(val.im, seed)); + } + else static if (__traits(isFloating, T)) + { + auto data = coalesceFloat(val); + static if (T.sizeof == float.sizeof && T.mant_dig == float.mant_dig) return hashOf(*cast(const uint*) &data, seed); - else static if (T.mant_dig == double.mant_dig && T.sizeof == ulong.sizeof) + else static if (T.sizeof == double.sizeof && T.mant_dig == double.mant_dig) return hashOf(*cast(const ulong*) &data, seed); else { - static if (is(T : creal) && T.sizeof != 2 * floatSize!(typeof(T.re))) - { - auto h1 = hashOf(data.re); - return hashOf(data.im, h1); - } - else static if (is(T : real) || is(T : ireal)) - { - // Ignore trailing padding - auto bytes = toUbyte(data)[0 .. floatSize!T]; - return bytesHashWithExactSizeAndAlignment!T(bytes, seed); - } - else - { - return bytesHashWithExactSizeAndAlignment!T(toUbyte(data), seed); - } + import core.internal.convert : floatSize, toUbyte; + return bytesHashWithExactSizeAndAlignment!T(toUbyte(data)[0 .. floatSize!T], seed); } } else { - static assert(T.sizeof > size_t.sizeof && __traits(isIntegral, T)); - foreach (i; 0 .. T.sizeof / size_t.sizeof) - seed = hashOf(cast(size_t) (val >>> (size_t.sizeof * 8 * i)), seed); - return seed; + static assert(0); } } -size_t hashOf(T)(scope const auto ref T val, size_t seed = 0) @safe @nogc nothrow pure -if (is(T == __vector) && !is(T == enum)) +size_t hashOf(T)(scope const T val, size_t seed = 0) @safe @nogc nothrow pure +if (is(T == __vector)) // excludes enum types { static if (__traits(isFloating, T) && (floatCoalesceZeroes || floatCoalesceNaNs)) { if (__ctfe) { // Workaround for CTFE bug. - alias E = Unqual!(typeof(val[0])); + static if (is(immutable typeof(val[0]) == immutable E, E)) {} // Get E. E[T.sizeof / E.sizeof] array; foreach (i; 0 .. T.sizeof / E.sizeof) array[i] = val[i]; @@ -428,6 +379,7 @@ if (is(T == __vector) && !is(T == enum)) } else { + import core.internal.convert : toUbyte; return bytesHashAlignedBy!T(toUbyte(val), seed); } } @@ -446,65 +398,48 @@ size_t hashOf(T)(scope const T val, size_t seed) if (!is(T == enum) && is(T : ty return hashOf(size_t(0), seed); } -//Pointers hash. CTFE unsupported if not null -@trusted @nogc nothrow pure -size_t hashOf(T)(scope const T val) -if (!is(T == enum) && is(T V : V*) && !is(T : typeof(null)) - && !is(T == struct) && !is(T == class) && !is(T == union)) -{ - if (__ctfe) - { - if (val is null) - { - return 0; - } - else - { - assert(0, "Unable to calculate hash of non-null pointer at compile time"); - } - - } - auto addr = cast(size_t) val; - return addr ^ (addr >>> 4); -} - -//Pointers hash. CTFE unsupported if not null -@trusted @nogc nothrow pure -size_t hashOf(T)(scope const T val, size_t seed) -if (!is(T == enum) && is(T V : V*) && !is(T : typeof(null)) - && !is(T == struct) && !is(T == class) && !is(T == union)) -{ - if (__ctfe) - { - if (val is null) - { - return hashOf(cast(size_t)0, seed); - } - else - { - assert(0, "Unable to calculate hash of non-null pointer at compile time"); - } - - } - return hashOf(cast(size_t)val, seed); -} - private enum _hashOfStruct = q{ enum bool isChained = is(typeof(seed) : size_t); static if (!isChained) enum size_t seed = 0; - static if (hasCallableToHash!T) //CTFE depends on toHash() + static if (hasCallableToHash!(typeof(val))) //CTFE depends on toHash() { - static if (isChained) - return hashOf(cast(size_t) val.toHash(), seed); + static if (!__traits(isSame, typeof(val), __traits(parent, val.toHash)) + && is(typeof(val is null))) + { + static if (isChained) + return hashOf(__traits(getMember, val, __traits(getAliasThis, typeof(val))), seed); + else + return hashOf(__traits(getMember, val, __traits(getAliasThis, typeof(val)))); + } else - return val.toHash(); + { + static if (isChained) + return hashOf(cast(size_t) val.toHash(), seed); + else + return val.toHash(); + } } else { + import core.internal.convert : toUbyte; static if (__traits(hasMember, T, "toHash") && is(typeof(T.toHash) == function)) { - pragma(msg, "Warning: struct "~__traits(identifier, T)~" has method toHash, however it cannot be called with "~T.stringof~" this."); + // TODO: in the future maybe this should be changed to a static + // assert(0), because if there's a `toHash` the programmer probably + // expected it to be called and a compilation failure here will + // expose a bug in his code. + // In the future we also might want to disallow non-const toHash + // altogether. + pragma(msg, "Warning: struct "~__traits(identifier, T) + ~" has method toHash, however it cannot be called with " + ~typeof(val).stringof~" this."); + static if (__traits(compiles, __traits(getLocation, T.toHash))) + { + enum file = __traits(getLocation, T.toHash)[0]; + enum line = __traits(getLocation, T.toHash)[1].stringof; + pragma(msg, " ",__traits(identifier, T),".toHash defined here: ",file,"(",line,")"); + } } static if (T.tupleof.length == 0) @@ -513,13 +448,12 @@ q{ } else static if ((is(T == struct) && !canBitwiseHash!T) || T.tupleof.length == 1) { - size_t h = void; - static if (isChained) h = seed; - foreach (i, F; typeof(val.tupleof)) + static if (isChained) size_t h = seed; + static foreach (i, F; typeof(val.tupleof)) { static if (__traits(isStaticArray, F)) { - static if (i == 0 && !isChained) h = 0; + static if (i == 0 && !isChained) size_t h = 0; static if (F.sizeof > 0 && canBitwiseHash!F) // May use smallBytesHash instead of bytesHash. h = bytesHashWithExactSizeAndAlignment!F(toUbyte(val.tupleof[i]), h); @@ -533,30 +467,41 @@ q{ { static if (hasCallableToHash!F) { - static if (i == 0 && !isChained) - h = val.tupleof[i].toHash(); + static if (!__traits(isSame, F, __traits(parent, val.tupleof[i].toHash)) + && is(typeof(val.tupleof[i] is null))) + { + static if (i == 0 && !isChained) + size_t h = hashOf(__traits(getMember, val.tupleof[i], __traits(getAliasThis, F))); + else + h = hashOf(__traits(getMember, val.tupleof[i], __traits(getAliasThis, F)), h); + } else - h = hashOf(cast(size_t) val.tupleof[i].toHash(), h); + { + static if (i == 0 && !isChained) + size_t h = val.tupleof[i].toHash(); + else + h = hashOf(cast(size_t) val.tupleof[i].toHash(), h); + } } else static if (F.tupleof.length == 1) { // Handle the single member case separately to avoid unnecessarily using bytesHash. static if (i == 0 && !isChained) - h = hashOf(val.tupleof[i].tupleof[0]); + size_t h = hashOf(val.tupleof[i].tupleof[0]); else h = hashOf(val.tupleof[i].tupleof[0], h); } else static if (canBitwiseHash!F) { // May use smallBytesHash instead of bytesHash. - static if (i == 0 && !isChained) h = 0; + static if (i == 0 && !isChained) size_t h = 0; h = bytesHashWithExactSizeAndAlignment!F(toUbyte(val.tupleof[i]), h); } else { // Nothing special happening. static if (i == 0 && !isChained) - h = hashOf(val.tupleof[i]); + size_t h = hashOf(val.tupleof[i]); else h = hashOf(val.tupleof[i], h); } @@ -565,7 +510,7 @@ q{ { // Nothing special happening. static if (i == 0 && !isChained) - h = hashOf(val.tupleof[i]); + size_t h = hashOf(val.tupleof[i]); else h = hashOf(val.tupleof[i], h); } @@ -592,6 +537,7 @@ q{ //struct or union hash size_t hashOf(T)(scope const auto ref T val, size_t seed = 0) if (!is(T == enum) && (is(T == struct) || is(T == union)) + && !is(T == const) && !is(T == immutable) && canBitwiseHash!T) { mixin(_hashOfStruct); @@ -613,13 +559,25 @@ if (!is(T == enum) && (is(T == struct) || is(T == union)) mixin(_hashOfStruct); } -//delegate hash. CTFE unsupported +//struct or union hash - https://issues.dlang.org/show_bug.cgi?id=19332 (support might be removed in future) +size_t hashOf(T)(scope auto ref T val, size_t seed = 0) +if (!is(T == enum) && (is(T == struct) || is(T == union)) + && (is(T == const) || is(T == immutable)) + && canBitwiseHash!T && !canBitwiseHash!(Unconst!T)) +{ + mixin(_hashOfStruct); +} + +//delegate hash. CTFE only if null. @trusted @nogc nothrow pure size_t hashOf(T)(scope const T val, size_t seed = 0) if (!is(T == enum) && is(T == delegate)) { - assert(!__ctfe, "unable to compute hash of "~T.stringof~" at compile time"); - const(ubyte)[] bytes = (cast(const(ubyte)*)&val)[0 .. T.sizeof]; - return bytesHashWithExactSizeAndAlignment!T(bytes, seed); + if (__ctfe) + { + if (val is null) return hashOf(size_t(0), hashOf(size_t(0), seed)); + assert(0, "unable to compute hash of "~T.stringof~" at compile time"); + } + return hashOf(val.ptr, hashOf(cast(void*) val.funcptr, seed)); } //address-based class hash. CTFE only if null. @@ -648,7 +606,13 @@ if (!is(T == enum) && (is(T == interface) || is(T == class)) && !canBitwiseHash!T) { static if (__traits(compiles, {size_t h = val.toHash();})) - return val ? val.toHash() : 0; + { + static if (is(__traits(parent, val.toHash) P) && !is(immutable T* : immutable P*) + && is(typeof((ref P p) => p is null))) + return val ? hashOf(__traits(getMember, val, __traits(getAliasThis, T))) : 0; + else + return val ? val.toHash() : 0; + } else return val ? (cast(Object)val).toHash() : 0; } @@ -659,7 +623,14 @@ if (!is(T == enum) && (is(T == interface) || is(T == class)) && !canBitwiseHash!T) { static if (__traits(compiles, {size_t h = val.toHash();})) - return hashOf(val ? cast(size_t) val.toHash() : size_t(0), seed); + { + static if (is(__traits(parent, val.toHash) P) && !is(immutable T* : immutable P*) + && is(typeof((ref P p) => p is null))) + return hashOf(val ? hashOf(__traits(getMember, val, __traits(getAliasThis, T))) + : size_t(0), seed); + else + return hashOf(val ? cast(size_t) val.toHash() : size_t(0), seed); + } else return hashOf(val ? (cast(Object)val).toHash() : 0, seed); } diff --git a/libphobos/libdruntime/core/internal/lifetime.d b/libphobos/libdruntime/core/internal/lifetime.d new file mode 100644 index 0000000..7e9b5f2 --- /dev/null +++ b/libphobos/libdruntime/core/internal/lifetime.d @@ -0,0 +1,213 @@ +module core.internal.lifetime; + +import core.lifetime : forward; + +/+ +emplaceRef is a package function for druntime 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 parameter, 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. ++/ +void emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args) +{ + static if (args.length == 0) + { + static assert(is(typeof({static T i;})), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this() is annotated with @disable."); + static if (is(T == class)) static assert(!__traits(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 = forward!(args[0]);})) /* conversions */ + || + is(typeof(T(forward!args))) /* general constructors */) + { + static struct S + { + T payload; + this()(auto ref Args args) + { + static if (__traits(compiles, payload = forward!args)) + payload = forward!args; + else + payload = T(forward!args); + } + } + if (__ctfe) + { + static if (__traits(compiles, chunk = T(forward!args))) + chunk = T(forward!args); + else static if (args.length == 1 && __traits(compiles, chunk = forward!(args[0]))) + chunk = forward!(args[0]); + else assert(0, "CTFE emplace doesn't support " + ~ T.stringof ~ " from " ~ Args.stringof); + } + else + { + S* p = () @trusted { return cast(S*) &chunk; }(); + static if (UT.sizeof > 0) + emplaceInitializer(*p); + p.__ctor(forward!args); + } + } + else static if (is(typeof(chunk.__ctor(forward!args)))) + { + // This catches the rare case of local types that keep a frame pointer + emplaceInitializer(chunk); + chunk.__ctor(forward!args); + } + else + { + //We can't emplace. Try to diagnose a disabled postblit. + static assert(!(Args.length == 1 && is(Args[0] : T)), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this(this) is annotated with @disable."); + + //We can't emplace. + static assert(false, + T.stringof ~ " cannot be emplaced from " ~ Args[].stringof ~ "."); + } +} + +// ditto +static import core.internal.traits; +void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args) +if (is(UT == core.internal.traits.Unqual!UT)) +{ + emplaceRef!(UT, UT)(chunk, forward!args); +} + +/+ +Emplaces T.init. +In contrast to `emplaceRef(chunk)`, there are no checks for disabled default +constructors etc. ++/ +template emplaceInitializer(T) +if (!is(T == const) && !is(T == immutable) && !is(T == inout)) +{ + import core.internal.traits : hasElaborateAssign, Unqual; + + // Avoid stack allocation by hacking to get to the struct/union init symbol. + static if (is(T == struct) || is(T == union)) + { + pragma(mangle, "_D" ~ Unqual!T.mangleof[1..$] ~ "6__initZ") + __gshared extern immutable T initializer; + } + + void emplaceInitializer(scope ref T chunk) nothrow pure @trusted + { + static if (__traits(isZeroInit, T)) + { + import core.stdc.string : memset; + memset(cast(void*) &chunk, 0, T.sizeof); + } + else static if (__traits(isScalar, T) || + T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, (){ T chunk; chunk = T.init; })) + { + chunk = T.init; + } + else static if (__traits(isStaticArray, T)) + { + // For static arrays there is no initializer symbol created. Instead, we emplace elements one-by-one. + foreach (i; 0 .. T.length) + { + emplaceInitializer(chunk[i]); + } + } + else + { + import core.stdc.string : memcpy; + memcpy(cast(void*)&chunk, &initializer, T.sizeof); + } + } +} + +@safe unittest +{ + static void testInitializer(T)() + { + // mutable T + { + T dst = void; + emplaceInitializer(dst); + assert(dst is T.init); + } + + // shared T + { + shared T dst = void; + emplaceInitializer(dst); + assert(dst is shared(T).init); + } + + // const T + { + const T dst = void; + static assert(!__traits(compiles, emplaceInitializer(dst))); + } + } + + static struct ElaborateAndZero + { + int a; + this(this) {} + } + + static struct ElaborateAndNonZero + { + int a = 42; + this(this) {} + } + + static union LargeNonZeroUnion + { + byte[128] a = 1; + } + + testInitializer!int(); + testInitializer!double(); + testInitializer!ElaborateAndZero(); + testInitializer!ElaborateAndNonZero(); + testInitializer!LargeNonZeroUnion(); + + static if (is(__vector(double[4]))) + { + // DMD 2.096 and GDC 11.1 can't compare vectors with `is` so can't use + // testInitializer. + enum VE : __vector(double[4]) + { + a = [1.0, 2.0, 3.0, double.nan], + b = [4.0, 5.0, 6.0, double.nan], + } + const VE expected = VE.a; + VE dst = VE.b; + shared VE sharedDst = VE.b; + emplaceInitializer(dst); + emplaceInitializer(sharedDst); + () @trusted { + import core.stdc.string : memcmp; + assert(memcmp(&expected, &dst, VE.sizeof) == 0); + assert(memcmp(&expected, cast(void*) &sharedDst, VE.sizeof) == 0); + }(); + static assert(!__traits(compiles, emplaceInitializer(expected))); + } +} + +/* +Simple swap function. +*/ +void swap(T)(ref T lhs, ref T rhs) +{ + import core.lifetime : move, moveEmplace; + + T tmp = move(lhs); + moveEmplace(rhs, lhs); + moveEmplace(tmp, rhs); +} diff --git a/libphobos/libdruntime/core/internal/moving.d b/libphobos/libdruntime/core/internal/moving.d new file mode 100644 index 0000000..9c97d29 --- /dev/null +++ b/libphobos/libdruntime/core/internal/moving.d @@ -0,0 +1,147 @@ +/** + This module contains the implementation of move semantics of DIP 1014 + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/_internal/_moving.d) +*/ +module core.internal.moving; + +/** +Recursively calls the `opPostMove` callbacks of a struct and its members if +they're defined. + +When moving a struct instance, the compiler emits a call to this function +after blitting the instance and before releasing the original instance's +memory. + +Params: + newLocation = reference to struct instance being moved into + oldLocation = reference to the original instance + +Note: + This function is tentatively defined as `nothrow` to prevent + `opPostMove` from being defined without `nothrow`, which would allow + for possibly confusing changes in program flow. +*/ +void __move_post_blt(S)(ref S newLocation, ref S oldLocation) nothrow + if (is(S == struct)) +{ + import core.internal.traits : hasElaborateMove; + static foreach (i, M; typeof(S.tupleof)) + { + static if (hasElaborateMove!M) + { + __move_post_blt(newLocation.tupleof[i], oldLocation.tupleof[i]); + } + } + + static if (__traits(hasMember, S, "opPostMove")) + { + import core.internal.traits : lvalueOf, rvalueOf; + static assert( is(typeof(S.init.opPostMove(lvalueOf!S))) && + !is(typeof(S.init.opPostMove(rvalueOf!S))), + "`" ~ S.stringof ~ ".opPostMove` must take exactly one argument of type `" ~ S.stringof ~ "` by reference"); + + newLocation.opPostMove(oldLocation); + } +} + +void __move_post_blt(S)(ref S newLocation, ref S oldLocation) nothrow + if (__traits(isStaticArray, S)) +{ + import core.internal.traits : hasElaborateMove; + static if (S.length && hasElaborateMove!(typeof(newLocation[0]))) + { + foreach (i; 0 .. S.length) + __move_post_blt(newLocation[i], oldLocation[i]); + } +} + +@safe nothrow unittest +{ + struct A + { + bool movedInto; + void opPostMove(const ref A oldLocation) + { + movedInto = true; + } + } + A src, dest; + __move_post_blt(dest, src); + assert(dest.movedInto); +} + +@safe nothrow unittest +{ + struct A + { + bool movedInto; + void opPostMove(const ref A oldLocation) + { + movedInto = true; + } + } + struct B + { + A a; + + bool movedInto; + void opPostMove(const ref B oldLocation) + { + movedInto = true; + } + } + B src, dest; + __move_post_blt(dest, src); + assert(dest.movedInto && dest.a.movedInto); +} + +@safe nothrow unittest +{ + static struct DoNotMove + { + bool movedInto; + void opPostMove(const ref DoNotMove oldLocation) + { + movedInto = true; + } + } + static DoNotMove doNotMove; + + struct A + { + @property ref DoNotMove member() + { + return doNotMove; + } + } + A src, dest; + __move_post_blt(dest, src); + assert(!doNotMove.movedInto); +} + +@safe nothrow unittest +{ + static struct A + { + bool movedInto; + void opPostMove(const ref A oldLocation) + { + movedInto = true; + } + } + static struct B + { + A[2] a; + } + B src, dest; + __move_post_blt(dest, src); + foreach (ref a; src.a) + assert(!a.movedInto); + foreach (ref a; dest.a) + assert(a.movedInto); +} diff --git a/libphobos/libdruntime/core/internal/parseoptions.d b/libphobos/libdruntime/core/internal/parseoptions.d new file mode 100644 index 0000000..4e5105d --- /dev/null +++ b/libphobos/libdruntime/core/internal/parseoptions.d @@ -0,0 +1,422 @@ +/** +* parse configuration options +* +* Copyright: Copyright Digital Mars 2017 +* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +* +* Source: $(DRUNTIMESRC src/core/internal/parseoptions.d) +*/ + +module core.internal.parseoptions; + +import core.stdc.stdlib; +import core.stdc.stdio; +import core.stdc.ctype; +import core.stdc.string; +import core.vararg; +import core.internal.traits : externDFunc, hasUDA; + + +@nogc nothrow: +extern extern(C) string[] rt_args() @system; + +extern extern(C) __gshared bool rt_envvars_enabled; +extern extern(C) __gshared bool rt_cmdline_enabled; +extern extern(C) __gshared string[] rt_options; + +alias rt_configCallBack = string delegate(string) @nogc nothrow; +alias fn_configOption = string function(string opt, scope rt_configCallBack dg, bool reverse) @nogc nothrow; +alias rt_configOption = externDFunc!("rt.config.rt_configOption", fn_configOption); + +/// UDA for field treated as memory value +struct MemVal {} + +/** +* initialize members of struct CFG from rt_config options +* +* options will be read from the environment, the command line or embedded +* into the executable as configured (see rt.config) +* +* fields of the struct are populated by parseOptions(). +*/ +bool initConfigOptions(CFG)(ref CFG cfg, string cfgname) +{ + string parse(string opt) @nogc nothrow + { + if (!parseOptions(cfg, opt)) + return "err"; + return null; // continue processing + } + string s = rt_configOption(cfgname, &parse, true); + return s is null; +} + +/** +* initialize members of struct CFG from a string of sub-options. +* +* fields of the struct are populated by listing them as space separated +* sub-options :value, e.g. "precise:1 profile:1" +* +* supported field value types: +* - strings (without spaces) +* - integer types (positive values only) +* - bool +* - float +* +* If the struct has a member "help" it is called if it is found as a sub-option. +* If the struct has a member "errorName", is used as the name reported in error +* messages. Otherwise the struct name is used. +*/ +bool parseOptions(CFG)(ref CFG cfg, string opt) +{ + static if (is(typeof(__traits(getMember, CFG, "errorName")))) + string errName = cfg.errorName; + else + string errName = CFG.stringof; + opt = skip!isspace(opt); + while (opt.length) + { + auto tail = find!(c => c == ':' || c == '=' || c == ' ')(opt); + auto name = opt[0 .. $ - tail.length]; + static if (is(typeof(__traits(getMember, CFG, "help")))) + if (name == "help") + { + version (CoreUnittest) {} else + cfg.help(); + opt = skip!isspace(tail); + continue; + } + if (tail.length <= 1 || tail[0] == ' ') + return optError("Missing argument for", name, errName); + tail = tail[1 .. $]; + + NAMES_SWITCH: + switch (name) + { + static foreach (field; __traits(allMembers, CFG)) + { + static if (!is(typeof(__traits(getMember, cfg, field)) == function)) + { + case field: + bool r; + + static if (hasUDA!(__traits(getMember, cfg, field), MemVal)) + r = parse(name, tail, __traits(getMember, cfg, field), errName, true); + else + r = parse(name, tail, __traits(getMember, cfg, field), errName); + + if (!r) + return false; + + break NAMES_SWITCH; + } + } + + default: + return optError("Unknown", name, errName); + } + opt = skip!isspace(tail); + } + return true; +} + +/** +Parses an individual option `optname` value from a provided string `str`. +The option type is given by the type `T` of the field `res` to which the parsed +value will be written too. +In case of an error, `errName` will be used to display an error message and +the failure of the parsing will be indicated by a falsy return value. + +For boolean values, '0/n/N' (false) or '1/y/Y' (true) are accepted. + +Params: + optname = name of the option to parse + str = raw string to parse the option value from + res = reference to the resulting data field that the option should be parsed too + errName = full-text name of the option which should be displayed in case of errors + +Returns: `false` if a parsing error happened. +*/ +bool rt_parseOption(T)(const(char)[] optname, ref inout(char)[] str, ref T res, const(char)[] errName) +{ + return parse(optname, str, res, errName); +} + +private: + +bool optError(const scope char[] msg, const scope char[] name, const(char)[] errName) +{ + version (CoreUnittest) if (inUnittest) return false; + + fprintf(stderr, "%.*s %.*s option '%.*s'.\n", + cast(int)msg.length, msg.ptr, + cast(int)errName.length, errName.ptr, + cast(int)name.length, name.ptr); + return false; +} + +inout(char)[] skip(alias pred)(inout(char)[] str) +{ + return find!(c => !pred(c))(str); +} + +inout(char)[] find(alias pred)(inout(char)[] str) +{ + foreach (i; 0 .. str.length) + if (pred(str[i])) return str[i .. $]; + return null; +} + +bool parse(T : size_t)(const(char)[] optname, ref inout(char)[] str, ref T res, const(char)[] errName, bool mayHaveSuffix = false) +in { assert(str.length); } +do +{ + size_t i, v; + + auto tail = find!(c => c == ' ')(str); + size_t len = str.length - tail.length; + + import core.checkedint : mulu; + + bool overflowed; + + for (; i < len; i++) + { + char c = str[i]; + + if (isdigit(c)) + v = 10 * v + c - '0'; + else // non-digit + { + if (mayHaveSuffix && i == len-1) // suffix + { + switch (c) + { + + case 'G': + v = mulu(v, 1024 * 1024 * 1024, overflowed); + break; + + case 'M': + v = mulu(v, 1024 * 1024, overflowed); + break; + + case 'K': + v = mulu(v, 1024, overflowed); + break; + + case 'B': + break; + + default: + return parseError("value with unit type M, K or B", optname, str, "with suffix"); + } + + if (overflowed) + return overflowedError(optname, str); + + i++; + break; + } + else // unexpected non-digit character + { + i = 0; + break; + } + } + } + + if (!i) + return parseError("a number", optname, str, errName); + + if (mayHaveSuffix && isdigit(str[len-1])) + { + // No suffix found, default to megabytes + + v = mulu(v, 1024 * 1024, overflowed); + + if (overflowed) + return overflowedError(optname, str); + } + + if (v > res.max) + return parseError("a number " ~ T.max.stringof ~ " or below", optname, str[0 .. i], errName); + str = str[i .. $]; + res = cast(T) v; + return true; +} + +bool parse(const(char)[] optname, ref inout(char)[] str, ref bool res, const(char)[] errName) +in { assert(str.length); } +do +{ + if (str[0] == '1' || str[0] == 'y' || str[0] == 'Y') + res = true; + else if (str[0] == '0' || str[0] == 'n' || str[0] == 'N') + res = false; + else + return parseError("'0/n/N' or '1/y/Y'", optname, str, errName); + str = str[1 .. $]; + return true; +} + +bool parse(const(char)[] optname, ref inout(char)[] str, ref float res, const(char)[] errName) +in { assert(str.length); } +do +{ + // % uint f %n \0 + char[1 + 10 + 1 + 2 + 1] fmt=void; + // specify max-width + immutable n = snprintf(fmt.ptr, fmt.length, "%%%uf%%n", cast(uint)str.length); + assert(n > 4 && n < fmt.length); + + int nscanned; + version (CRuntime_DigitalMars) + { + /* Older sscanf's in snn.lib can write to its first argument, causing a crash + * if the string is in readonly memory. Recent updates to DMD + * https://github.com/dlang/dmd/pull/6546 + * put string literals in readonly memory. + * Although sscanf has been fixed, + * http://ftp.digitalmars.com/snn.lib + * this workaround is here so it still works with the older snn.lib. + */ + // Create mutable copy of str + const length = str.length; + char* mptr = cast(char*)malloc(length + 1); + assert(mptr); + memcpy(mptr, str.ptr, length); + mptr[length] = 0; + const result = sscanf(mptr, fmt.ptr, &res, &nscanned); + free(mptr); + if (result < 1) + return parseError("a float", optname, str, errName); + } + else + { + if (sscanf(str.ptr, fmt.ptr, &res, &nscanned) < 1) + return parseError("a float", optname, str, errName); + } + str = str[nscanned .. $]; + return true; +} + +bool parse(const(char)[] optname, ref inout(char)[] str, ref inout(char)[] res, const(char)[] errName) +in { assert(str.length); } +do +{ + auto tail = str.find!(c => c == ' '); + res = str[0 .. $ - tail.length]; + if (!res.length) + return parseError("an identifier", optname, str, errName); + str = tail; + return true; +} + +bool parseError(const scope char[] exp, const scope char[] opt, const scope char[] got, const(char)[] errName) +{ + version (CoreUnittest) if (inUnittest) return false; + + fprintf(stderr, "Expecting %.*s as argument for %.*s option '%.*s', got '%.*s' instead.\n", + cast(int)exp.length, exp.ptr, + cast(int)errName.length, errName.ptr, + cast(int)opt.length, opt.ptr, + cast(int)got.length, got.ptr); + return false; +} + +bool overflowedError(const scope char[] opt, const scope char[] got) +{ + version (CoreUnittest) if (inUnittest) return false; + + fprintf(stderr, "Argument for %.*s option '%.*s' is too big.\n", + cast(int)opt.length, opt.ptr, + cast(int)got.length, got.ptr); + return false; +} + +size_t min(size_t a, size_t b) { return a <= b ? a : b; } + +version (CoreUnittest) __gshared bool inUnittest; + +unittest +{ + inUnittest = true; + scope (exit) inUnittest = false; + + static struct Config + { + bool disable; // start disabled + ubyte profile; // enable profiling with summary when terminating program + string gc = "conservative"; // select gc implementation conservative|manual + + @MemVal size_t initReserve; // initial reserve (bytes) + @MemVal size_t minPoolSize = 1 << 20; // initial and minimum pool size (bytes) + float heapSizeFactor = 2.0; // heap size to used memory ratio + + @nogc nothrow: + void help(); + string errorName() @nogc nothrow { return "GC"; } + } + Config conf; + + assert(!conf.parseOptions("disable")); + assert(!conf.parseOptions("disable:")); + assert(!conf.parseOptions("disable:5")); + assert(conf.parseOptions("disable:y") && conf.disable); + assert(conf.parseOptions("disable:n") && !conf.disable); + assert(conf.parseOptions("disable:Y") && conf.disable); + assert(conf.parseOptions("disable:N") && !conf.disable); + assert(conf.parseOptions("disable:1") && conf.disable); + assert(conf.parseOptions("disable:0") && !conf.disable); + + assert(conf.parseOptions("disable=y") && conf.disable); + assert(conf.parseOptions("disable=n") && !conf.disable); + + assert(conf.parseOptions("profile=0") && conf.profile == 0); + assert(conf.parseOptions("profile=1") && conf.profile == 1); + assert(conf.parseOptions("profile=2") && conf.profile == 2); + assert(!conf.parseOptions("profile=256")); + + assert(conf.parseOptions("disable:1 minPoolSize:16")); + assert(conf.disable); + assert(conf.minPoolSize == 1024 * 1024 * 16); + + assert(conf.parseOptions("disable:1 minPoolSize:4096B")); + assert(conf.disable); + assert(conf.minPoolSize == 4096); + + assert(conf.parseOptions("disable:1 minPoolSize:2K help")); + assert(conf.disable); + assert(conf.minPoolSize == 2048); + + assert(conf.parseOptions("minPoolSize:3G help")); + assert(conf.disable); + assert(conf.minPoolSize == 1024UL * 1024 * 1024 * 3); + + assert(!conf.parseOptions("minPoolSize:922337203685477G"), "size_t overflow"); + + assert(conf.parseOptions("heapSizeFactor:3.1")); + assert(conf.heapSizeFactor == 3.1f); + assert(conf.parseOptions("heapSizeFactor:3.1234567890 disable:0")); + assert(conf.heapSizeFactor > 3.123f); + assert(!conf.disable); + assert(!conf.parseOptions("heapSizeFactor:3.0.2.5")); + assert(conf.parseOptions("heapSizeFactor:2")); + assert(conf.heapSizeFactor == 2.0f); + + assert(!conf.parseOptions("initReserve:foo")); + assert(!conf.parseOptions("initReserve:y")); + assert(!conf.parseOptions("initReserve:20.5")); + + assert(conf.parseOptions("help")); + assert(conf.parseOptions("help profile:1")); + assert(conf.parseOptions("help profile:1 help")); + + assert(conf.parseOptions("gc:manual") && conf.gc == "manual"); + assert(conf.parseOptions("gc:my-gc~modified") && conf.gc == "my-gc~modified"); + assert(conf.parseOptions("gc:conservative help profile:1") && conf.gc == "conservative" && conf.profile == 1); + + // the config parse doesn't know all available GC names, so should accept unknown ones + assert(conf.parseOptions("gc:whatever")); +} diff --git a/libphobos/libdruntime/core/internal/postblit.d b/libphobos/libdruntime/core/internal/postblit.d new file mode 100644 index 0000000..ed4ec1b --- /dev/null +++ b/libphobos/libdruntime/core/internal/postblit.d @@ -0,0 +1,274 @@ +/** + This module contains support for D's postblit feature + + Copyright: Copyright Digital Mars 2000 - 2019. + License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) + Source: $(DRUNTIMESRC core/_internal/_destruction.d) +*/ +module core.internal.postblit; + +// compiler frontend lowers struct array postblitting to this +void __ArrayPostblit(T)(T[] a) +{ + foreach (ref T e; a) + e.__xpostblit(); +} + +package void postblitRecurse(S)(ref S s) + if (is(S == struct)) +{ + static if (__traits(hasMember, S, "__xpostblit") && + // Bugzilla 14746: Check that it's the exact member of S. + __traits(isSame, S, __traits(parent, s.__xpostblit))) + s.__xpostblit(); +} + +package void postblitRecurse(E, size_t n)(ref E[n] arr) +{ + import core.internal.destruction: destructRecurse; + import core.internal.traits : hasElaborateCopyConstructor; + + static if (hasElaborateCopyConstructor!E) + { + size_t i; + scope(failure) + { + for (; i != 0; --i) + { + destructRecurse(arr[i - 1]); // What to do if this throws? + } + } + + for (i = 0; i < arr.length; ++i) + postblitRecurse(arr[i]); + } +} + +// Test destruction/postblit order +@safe nothrow pure unittest +{ + string[] order; + + struct InnerTop + { + ~this() @safe nothrow pure + { + order ~= "destroy inner top"; + } + + this(this) @safe nothrow pure + { + order ~= "copy inner top"; + } + } + + struct InnerMiddle {} + + version (none) // https://issues.dlang.org/show_bug.cgi?id=14242 + struct InnerElement + { + static char counter = '1'; + + ~this() @safe nothrow pure + { + order ~= "destroy inner element #" ~ counter++; + } + + this(this) @safe nothrow pure + { + order ~= "copy inner element #" ~ counter++; + } + } + + struct InnerBottom + { + ~this() @safe nothrow pure + { + order ~= "destroy inner bottom"; + } + + this(this) @safe nothrow pure + { + order ~= "copy inner bottom"; + } + } + + struct S + { + char[] s; + InnerTop top; + InnerMiddle middle; + version (none) InnerElement[3] array; // https://issues.dlang.org/show_bug.cgi?id=14242 + int a; + InnerBottom bottom; + ~this() @safe nothrow pure { order ~= "destroy outer"; } + this(this) @safe nothrow pure { order ~= "copy outer"; } + } + + string[] destructRecurseOrder; + { + import core.internal.destruction: destructRecurse; + + S s; + destructRecurse(s); + destructRecurseOrder = order; + order = null; + } + + assert(order.length); + assert(destructRecurseOrder == order); + order = null; + + S s; + postblitRecurse(s); + assert(order.length); + auto postblitRecurseOrder = order; + order = null; + S s2 = s; + assert(order.length); + assert(postblitRecurseOrder == order); +} + +@safe unittest +{ + // Bugzilla 14746 + static struct HasPostblit + { + this(this) { assert(0); } + } + static struct Owner + { + HasPostblit* ptr; + alias ptr this; + } + + Owner o; + assert(o.ptr is null); + postblitRecurse(o); // must not reach in HasPostblit.__postblit() +} + +// Test handling of fixed-length arrays +// Separate from first test because of https://issues.dlang.org/show_bug.cgi?id=14242 +@safe unittest +{ + string[] order; + + struct S + { + char id; + + this(this) + { + order ~= "copy #" ~ id; + } + + ~this() + { + order ~= "destroy #" ~ id; + } + } + + string[] destructRecurseOrder; + { + import core.internal.destruction: destructRecurse; + + S[3] arr = [S('1'), S('2'), S('3')]; + destructRecurse(arr); + destructRecurseOrder = order; + order = null; + } + assert(order.length); + assert(destructRecurseOrder == order); + order = null; + + S[3] arr = [S('1'), S('2'), S('3')]; + postblitRecurse(arr); + assert(order.length); + auto postblitRecurseOrder = order; + order = null; + + auto arrCopy = arr; + assert(order.length); + assert(postblitRecurseOrder == order); +} + +// Test handling of failed postblit +// Not nothrow or @safe because of https://issues.dlang.org/show_bug.cgi?id=14242 +/+ nothrow @safe +/ unittest +{ + static class FailedPostblitException : Exception { this() nothrow @safe { super(null); } } + static string[] order; + static struct Inner + { + char id; + + @safe: + this(this) + { + order ~= "copy inner #" ~ id; + if (id == '2') + throw new FailedPostblitException(); + } + + ~this() nothrow + { + order ~= "destroy inner #" ~ id; + } + } + + static struct Outer + { + Inner inner1, inner2, inner3; + + nothrow @safe: + this(char first, char second, char third) + { + inner1 = Inner(first); + inner2 = Inner(second); + inner3 = Inner(third); + } + + this(this) + { + order ~= "copy outer"; + } + + ~this() + { + order ~= "destroy outer"; + } + } + + auto outer = Outer('1', '2', '3'); + + try postblitRecurse(outer); + catch (FailedPostblitException) {} + catch (Exception) assert(false); + + auto postblitRecurseOrder = order; + order = null; + + try auto copy = outer; + catch (FailedPostblitException) {} + catch (Exception) assert(false); + + assert(postblitRecurseOrder == order); + order = null; + + Outer[3] arr = [Outer('1', '1', '1'), Outer('1', '2', '3'), Outer('3', '3', '3')]; + + try postblitRecurse(arr); + catch (FailedPostblitException) {} + catch (Exception) assert(false); + + postblitRecurseOrder = order; + order = null; + + try auto arrCopy = arr; + catch (FailedPostblitException) {} + catch (Exception) assert(false); + + assert(postblitRecurseOrder == order); +} diff --git a/libphobos/libdruntime/core/internal/qsort.d b/libphobos/libdruntime/core/internal/qsort.d new file mode 100644 index 0000000..ad8307a --- /dev/null +++ b/libphobos/libdruntime/core/internal/qsort.d @@ -0,0 +1,196 @@ +/** + * This is a public domain version of qsort.d. All it does is call C's + * qsort(). + * + * Copyright: Copyright Digital Mars 2000 - 2010. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, Martin Nowak + */ +module core.internal.qsort; + +//debug=qsort; + +import core.stdc.stdlib; + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +// qsort_r was added in glibc in 2.8. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88127 +version (CRuntime_Glibc) +{ + version (GNU) + { + import gcc.config : Have_Qsort_R; + enum Glibc_Qsort_R = Have_Qsort_R; + } + else + { + enum Glibc_Qsort_R = true; + } +} +else +{ + enum Glibc_Qsort_R = false; +} + +static if (Glibc_Qsort_R) +{ + alias extern (C) int function(scope const void *, scope const void *, scope void *) Cmp; + extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, Cmp cmp, scope void *arg); + + extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) + { + extern (C) int cmp(scope const void* p1, scope const void* p2, scope void* ti) + { + return (cast(TypeInfo)ti).compare(p1, p2); + } + qsort_r(a.ptr, a.length, ti.tsize, &cmp, cast(void*)ti); + return a; + } +} +else version (FreeBSD) +{ + alias extern (C) int function(scope void *, scope const void *, scope const void *) Cmp; + extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, scope void *thunk, Cmp cmp); + + extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) + { + extern (C) int cmp(scope void* ti, scope const void* p1, scope const void* p2) + { + return (cast(TypeInfo)ti).compare(p1, p2); + } + qsort_r(a.ptr, a.length, ti.tsize, cast(void*)ti, &cmp); + return a; + } +} +else version (DragonFlyBSD) +{ + alias extern (C) int function(scope void *, scope const void *, scope const void *) Cmp; + extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, scope void *thunk, Cmp cmp); + + extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) + { + extern (C) int cmp(scope void* ti, scope const void* p1, scope const void* p2) + { + return (cast(TypeInfo)ti).compare(p1, p2); + } + qsort_r(a.ptr, a.length, ti.tsize, cast(void*)ti, &cmp); + return a; + } +} +else version (Darwin) +{ + alias extern (C) int function(scope void *, scope const void *, scope const void *) Cmp; + extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, scope void *thunk, Cmp cmp); + + extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) + { + extern (C) int cmp(scope void* ti, scope const void* p1, scope const void* p2) + { + return (cast(TypeInfo)ti).compare(p1, p2); + } + qsort_r(a.ptr, a.length, ti.tsize, cast(void*)ti, &cmp); + return a; + } +} +else version (CRuntime_UClibc) +{ + alias extern (C) int function(scope const void *, scope const void *, scope void *) __compar_d_fn_t; + extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, __compar_d_fn_t cmp, scope void *arg); + + extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) + { + extern (C) int cmp(scope const void* p1, scope const void* p2, scope void* ti) + { + return (cast(TypeInfo)ti).compare(p1, p2); + } + qsort_r(a.ptr, a.length, ti.tsize, &cmp, cast(void*)ti); + return a; + } +} +else +{ + private TypeInfo tiglobal; + + extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) + { + extern (C) int cmp(scope const void* p1, scope const void* p2) + { + return tiglobal.compare(p1, p2); + } + tiglobal = ti; + qsort(a.ptr, a.length, ti.tsize, &cmp); + return a; + } +} + +unittest +{ + debug(qsort) printf("array.sort.unittest()\n"); + + int[] a = new int[10]; + + a[0] = 23; + a[1] = 1; + a[2] = 64; + a[3] = 5; + a[4] = 6; + a[5] = 5; + a[6] = 17; + a[7] = 3; + a[8] = 0; + a[9] = -1; + + _adSort(*cast(void[]*)&a, typeid(a[0])); + + for (int i = 0; i < a.length - 1; i++) + { + //printf("i = %d", i); + //printf(" %d %d\n", a[i], a[i + 1]); + assert(a[i] <= a[i + 1]); + } +} + +unittest +{ + debug(qsort) printf("struct.sort.unittest()\n"); + + static struct TestStruct + { + int value; + + int opCmp(const TestStruct rhs) const + { + return value <= rhs.value ? + value < rhs.value ? -1 : 0 : 1; + } + } + + TestStruct[] a = new TestStruct[10]; + + a[0] = TestStruct(23); + a[1] = TestStruct(1); + a[2] = TestStruct(64); + a[3] = TestStruct(5); + a[4] = TestStruct(6); + a[5] = TestStruct(5); + a[6] = TestStruct(17); + a[7] = TestStruct(3); + a[8] = TestStruct(0); + a[9] = TestStruct(-1); + + _adSort(*cast(void[]*)&a, typeid(TestStruct)); + + for (int i = 0; i < a.length - 1; i++) + { + //printf("i = %d", i); + //printf(" %d %d\n", a[i], a[i + 1]); + assert(a[i] <= a[i + 1]); + } +} diff --git a/libphobos/libdruntime/core/internal/spinlock.d b/libphobos/libdruntime/core/internal/spinlock.d index fb689dc..36d806a 100644 --- a/libphobos/libdruntime/core/internal/spinlock.d +++ b/libphobos/libdruntime/core/internal/spinlock.d @@ -2,7 +2,7 @@ * SpinLock for runtime internal usage. * * Copyright: Copyright Digital Mars 2015 -. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Nowak * Source: $(DRUNTIMESRC core/internal/_spinlock.d) */ @@ -50,8 +50,9 @@ shared struct SpinLock /// yield with backoff void yield(size_t k) { + import core.time; if (k < pauseThresh) - return pause(); + return core.atomic.pause(); else if (k < 32) return Thread.yield(); Thread.sleep(1.msecs); @@ -66,25 +67,9 @@ private: enum X86 = false; static if (X86) - { enum pauseThresh = 16; - void pause() - { - asm @trusted @nogc nothrow - { - // pause instruction - rep; - nop; - } - } - } else - { enum pauseThresh = 4; - void pause() - { - } - } size_t val; Contention contention; @@ -93,7 +78,7 @@ private: // aligned to cacheline to avoid false sharing shared align(64) struct AlignedSpinLock { - this(SpinLock.Contention contention) + this(SpinLock.Contention contention) @trusted @nogc nothrow { impl = shared(SpinLock)(contention); } diff --git a/libphobos/libdruntime/core/internal/string.d b/libphobos/libdruntime/core/internal/string.d index d2144c7..529fee4 100644 --- a/libphobos/libdruntime/core/internal/string.d +++ b/libphobos/libdruntime/core/internal/string.d @@ -2,9 +2,9 @@ * String manipulation and comparison utilities. * * Copyright: Copyright Sean Kelly 2005 - 2009. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Sean Kelly, Walter Bright - * Source: $(DRUNTIMESRC src/rt/util/_string.d) + * Source: $(DRUNTIMESRC rt/util/_string.d) */ module core.internal.string; @@ -15,66 +15,107 @@ nothrow: alias UnsignedStringBuf = char[20]; -char[] unsignedToTempString(ulong value, return char[] buf, uint radix = 10) @safe +/** +Converts an unsigned integer value to a string of characters. + +This implementation is a template so it can be used when compiling with -betterC. + +Params: + value = the unsigned integer value to convert + buf = the pre-allocated buffer used to store the result + radix = the numeric base to use in the conversion (defaults to 10) + +Returns: + The unsigned integer value as a string of characters +*/ +char[] unsignedToTempString(uint radix = 10)(ulong value, return scope char[] buf) @safe +if (radix >= 2 && radix <= 16) { size_t i = buf.length; do { - ubyte x = cast(ubyte)(value % radix); - value = value / radix; - buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); + uint x = void; + if (value < radix) + { + x = cast(uint)value; + value = 0; + } + else + { + x = cast(uint)(value % radix); + value /= radix; + } + buf[--i] = cast(char)((radix <= 10 || x < 10) ? x + '0' : x - 10 + 'a'); } while (value); return buf[i .. $]; } -private struct TempStringNoAlloc +private struct TempStringNoAlloc(ubyte N) { - // need to handle 65 bytes for radix of 2 with negative sign. - private char[65] _buf; + private char[N] _buf = void; private ubyte _len; - auto get() return + inout(char)[] get() inout return { return _buf[$-_len..$]; } alias get this; } -auto unsignedToTempString(ulong value, uint radix) @safe +/** +Converts an unsigned integer value to a string of characters. + +This implementation is a template so it can be used when compiling with -betterC. + +Params: + value = the unsigned integer value to convert + radix = the numeric base to use in the conversion (defaults to 10) + +Returns: + The unsigned integer value as a string of characters +*/ +auto unsignedToTempString(uint radix = 10)(ulong value) @safe { - TempStringNoAlloc result = void; - result._len = unsignedToTempString(value, result._buf, radix).length & 0xff; + // Need a buffer of 65 bytes for radix of 2 with room for + // signedToTempString to possibly add a negative sign. + enum bufferSize = radix >= 10 ? 20 : 65; + TempStringNoAlloc!bufferSize result = void; + result._len = unsignedToTempString!radix(value, result._buf).length & 0xff; return result; } unittest { UnsignedStringBuf buf; - assert(0.unsignedToTempString(buf, 10) == "0"); - assert(1.unsignedToTempString(buf, 10) == "1"); - assert(12.unsignedToTempString(buf, 10) == "12"); - assert(0x12ABCF .unsignedToTempString(buf, 16) == "12abcf"); - assert(long.sizeof.unsignedToTempString(buf, 10) == "8"); - assert(uint.max.unsignedToTempString(buf, 10) == "4294967295"); - assert(ulong.max.unsignedToTempString(buf, 10) == "18446744073709551615"); + assert(0.unsignedToTempString(buf) == "0"); + assert(1.unsignedToTempString(buf) == "1"); + assert(12.unsignedToTempString(buf) == "12"); + assert(0x12ABCF .unsignedToTempString!16(buf) == "12abcf"); + assert(long.sizeof.unsignedToTempString(buf) == "8"); + assert(uint.max.unsignedToTempString(buf) == "4294967295"); + assert(ulong.max.unsignedToTempString(buf) == "18446744073709551615"); // use stack allocated struct version - assert(0.unsignedToTempString(10) == "0"); - assert(1.unsignedToTempString(10) == "1"); - assert(12.unsignedToTempString(10) == "12"); - assert(0x12ABCF .unsignedToTempString(16) == "12abcf"); - assert(long.sizeof.unsignedToTempString(10) == "8"); - assert(uint.max.unsignedToTempString(10) == "4294967295"); - assert(ulong.max.unsignedToTempString(10) == "18446744073709551615"); + assert(0.unsignedToTempString == "0"); + assert(1.unsignedToTempString == "1"); + assert(12.unsignedToTempString == "12"); + assert(0x12ABCF .unsignedToTempString!16 == "12abcf"); + assert(long.sizeof.unsignedToTempString == "8"); + assert(uint.max.unsignedToTempString == "4294967295"); + assert(ulong.max.unsignedToTempString == "18446744073709551615"); + + // test bad radices + assert(!is(typeof(100.unsignedToTempString!1(buf)))); + assert(!is(typeof(100.unsignedToTempString!0(buf) == ""))); } alias SignedStringBuf = char[20]; -char[] signedToTempString(long value, return char[] buf, uint radix) @safe +char[] signedToTempString(uint radix = 10)(long value, return scope char[] buf) @safe { bool neg = value < 0; if (neg) value = cast(ulong)-value; - auto r = unsignedToTempString(value, buf, radix); + auto r = unsignedToTempString!radix(value, buf); if (neg) { // about to do a slice without a bounds check @@ -85,12 +126,12 @@ char[] signedToTempString(long value, return char[] buf, uint radix) @safe return r; } -auto signedToTempString(long value, uint radix) @safe +auto signedToTempString(uint radix = 10)(long value) @safe { bool neg = value < 0; if (neg) value = cast(ulong)-value; - auto r = unsignedToTempString(value, radix); + auto r = unsignedToTempString!radix(value); if (neg) { r._len++; @@ -102,34 +143,34 @@ auto signedToTempString(long value, uint radix) @safe unittest { SignedStringBuf buf; - assert(0.signedToTempString(buf, 10) == "0"); - assert(1.signedToTempString(buf, 10) == "1"); - assert((-1).signedToTempString(buf, 10) == "-1"); - assert(12.signedToTempString(buf, 10) == "12"); - assert((-12).signedToTempString(buf, 10) == "-12"); - assert(0x12ABCF .signedToTempString(buf, 16) == "12abcf"); - assert((-0x12ABCF) .signedToTempString(buf, 16) == "-12abcf"); - assert(long.sizeof.signedToTempString(buf, 10) == "8"); - assert(int.max.signedToTempString(buf, 10) == "2147483647"); - assert(int.min.signedToTempString(buf, 10) == "-2147483648"); - assert(long.max.signedToTempString(buf, 10) == "9223372036854775807"); - assert(long.min.signedToTempString(buf, 10) == "-9223372036854775808"); + assert(0.signedToTempString(buf) == "0"); + assert(1.signedToTempString(buf) == "1"); + assert((-1).signedToTempString(buf) == "-1"); + assert(12.signedToTempString(buf) == "12"); + assert((-12).signedToTempString(buf) == "-12"); + assert(0x12ABCF .signedToTempString!16(buf) == "12abcf"); + assert((-0x12ABCF) .signedToTempString!16(buf) == "-12abcf"); + assert(long.sizeof.signedToTempString(buf) == "8"); + assert(int.max.signedToTempString(buf) == "2147483647"); + assert(int.min.signedToTempString(buf) == "-2147483648"); + assert(long.max.signedToTempString(buf) == "9223372036854775807"); + assert(long.min.signedToTempString(buf) == "-9223372036854775808"); // use stack allocated struct version - assert(0.signedToTempString(10) == "0"); - assert(1.signedToTempString(10) == "1"); - assert((-1).signedToTempString(10) == "-1"); - assert(12.signedToTempString(10) == "12"); - assert((-12).signedToTempString(10) == "-12"); - assert(0x12ABCF .signedToTempString(16) == "12abcf"); - assert((-0x12ABCF) .signedToTempString(16) == "-12abcf"); - assert(long.sizeof.signedToTempString(10) == "8"); - assert(int.max.signedToTempString(10) == "2147483647"); - assert(int.min.signedToTempString(10) == "-2147483648"); - assert(long.max.signedToTempString(10) == "9223372036854775807"); - assert(long.min.signedToTempString(10) == "-9223372036854775808"); - assert(long.max.signedToTempString(2) == "111111111111111111111111111111111111111111111111111111111111111"); - assert(long.min.signedToTempString(2) == "-1000000000000000000000000000000000000000000000000000000000000000"); + assert(0.signedToTempString() == "0"); + assert(1.signedToTempString == "1"); + assert((-1).signedToTempString == "-1"); + assert(12.signedToTempString == "12"); + assert((-12).signedToTempString == "-12"); + assert(0x12ABCF .signedToTempString!16 == "12abcf"); + assert((-0x12ABCF) .signedToTempString!16 == "-12abcf"); + assert(long.sizeof.signedToTempString == "8"); + assert(int.max.signedToTempString == "2147483647"); + assert(int.min.signedToTempString == "-2147483648"); + assert(long.max.signedToTempString == "9223372036854775807"); + assert(long.min.signedToTempString == "-9223372036854775808"); + assert(long.max.signedToTempString!2 == "111111111111111111111111111111111111111111111111111111111111111"); + assert(long.min.signedToTempString!2 == "-1000000000000000000000000000000000000000000000000000000000000000"); } @@ -142,7 +183,7 @@ unittest * Returns: * number of digits */ -int numDigits(uint radix = 10)(ulong value) @safe +int numDigits(uint radix = 10)(ulong value) @safe if (radix >= 2 && radix <= 36) { int n = 1; while (1) @@ -188,9 +229,14 @@ unittest assert(1.numDigits!2 == 1); assert(2.numDigits!2 == 2); assert(3.numDigits!2 == 2); + + // test bad radices + static assert(!__traits(compiles, 100.numDigits!1())); + static assert(!__traits(compiles, 100.numDigits!0())); + static assert(!__traits(compiles, 100.numDigits!37())); } -int dstrcmp( scope const char[] s1, scope const char[] s2 ) @trusted +int dstrcmp()( scope const char[] s1, scope const char[] s2 ) @trusted { immutable len = s1.length <= s2.length ? s1.length : s2.length; if (__ctfe) @@ -209,5 +255,5 @@ int dstrcmp( scope const char[] s1, scope const char[] s2 ) @trusted if ( ret ) return ret; } - return s1.length < s2.length ? -1 : (s1.length > s2.length); + return (s1.length > s2.length) - (s1.length < s2.length); } diff --git a/libphobos/libdruntime/core/internal/switch_.d b/libphobos/libdruntime/core/internal/switch_.d new file mode 100644 index 0000000..c052c58 --- /dev/null +++ b/libphobos/libdruntime/core/internal/switch_.d @@ -0,0 +1,190 @@ +/** +This module contains compiler support for switch...case statements + +Copyright: Copyright Digital Mars 2000 - 2019. +License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) +Source: $(DRUNTIMESRC core/internal/_switch_.d) +*/ +module core.internal.switch_; + +/** +Support for switch statements switching on strings. +Params: + caseLabels = sorted array of strings generated by compiler. Note the + strings are sorted by length first, and then lexicographically. + condition = string to look up in table +Returns: + index of match in caseLabels, a negative integer if not found +*/ +int __switch(T, caseLabels...)(/*in*/ const scope T[] condition) pure nothrow @safe @nogc +{ + // This closes recursion for other cases. + static if (caseLabels.length == 0) + { + return int.min; + } + else static if (caseLabels.length == 1) + { + return __cmp(condition, caseLabels[0]) == 0 ? 0 : int.min; + } + // To be adjusted after measurements + // Compile-time inlined binary search. + else static if (caseLabels.length < 7) + { + int r = void; + enum mid = cast(int)caseLabels.length / 2; + if (condition.length == caseLabels[mid].length) + { + r = __cmp(condition, caseLabels[mid]); + if (r == 0) return mid; + } + else + { + // Equivalent to (but faster than) condition.length > caseLabels[$ / 2].length ? 1 : -1 + r = ((condition.length > caseLabels[mid].length) << 1) - 1; + } + + if (r < 0) + { + // Search the left side + return __switch!(T, caseLabels[0 .. mid])(condition); + } + else + { + // Search the right side + return __switch!(T, caseLabels[mid + 1 .. $])(condition) + mid + 1; + } + } + else + { + // Need immutable array to be accessible in pure code, but case labels are + // currently coerced to the switch condition type (e.g. const(char)[]). + pure @trusted nothrow @nogc asImmutable(scope const(T[])[] items) + { + assert(__ctfe); // only @safe for CTFE + immutable T[][caseLabels.length] result = cast(immutable)(items[]); + return result; + } + static immutable T[][caseLabels.length] cases = asImmutable([caseLabels]); + + // Run-time binary search in a static array of labels. + return __switchSearch!T(cases[], condition); + } +} + +// binary search in sorted string cases, also see `__switch`. +private int __switchSearch(T)(/*in*/ const scope T[][] cases, /*in*/ const scope T[] condition) pure nothrow @safe @nogc +{ + size_t low = 0; + size_t high = cases.length; + + do + { + auto mid = (low + high) / 2; + int r = void; + if (condition.length == cases[mid].length) + { + r = __cmp(condition, cases[mid]); + if (r == 0) return cast(int) mid; + } + else + { + // Generates better code than "expr ? 1 : -1" on dmd and gdc, same with ldc + r = ((condition.length > cases[mid].length) << 1) - 1; + } + + if (r > 0) low = mid + 1; + else high = mid; + } + while (low < high); + + // Not found + return -1; +} + +@system unittest +{ + static void testSwitch(T)() + { + switch (cast(T[]) "c") + { + case "coo": + default: + break; + } + + static int bug5381(immutable(T)[] s) + { + switch (s) + { + case "unittest": return 1; + case "D_Version2": return 2; + case "nonenone": return 3; + case "none": return 4; + case "all": return 5; + default: return 6; + } + } + + int rc = bug5381("unittest"); + assert(rc == 1); + + rc = bug5381("D_Version2"); + assert(rc == 2); + + rc = bug5381("nonenone"); + assert(rc == 3); + + rc = bug5381("none"); + assert(rc == 4); + + rc = bug5381("all"); + assert(rc == 5); + + rc = bug5381("nonerandom"); + assert(rc == 6); + + static int binarySearch(immutable(T)[] s) + { + switch (s) + { + static foreach (i; 0 .. 16) + case i.stringof: return i; + default: return -1; + } + } + static foreach (i; 0 .. 16) + assert(binarySearch(i.stringof) == i); + assert(binarySearch("") == -1); + assert(binarySearch("sth.") == -1); + assert(binarySearch(null) == -1); + + static int bug16739(immutable(T)[] s) + { + switch (s) + { + case "\u0100": return 1; + case "a": return 2; + default: return 3; + } + } + assert(bug16739("\u0100") == 1); + assert(bug16739("a") == 2); + assert(bug16739("foo") == 3); + } + testSwitch!char; + testSwitch!wchar; + testSwitch!dchar; +} + +/** +Compiler lowers final switch default case to this (which is a runtime error) +Old implementation is in core/exception.d +*/ +void __switch_error()(string file = __FILE__, size_t line = __LINE__) +{ + import core.exception : __switch_errorT; + __switch_errorT(file, line); +} diff --git a/libphobos/libdruntime/core/internal/traits.d b/libphobos/libdruntime/core/internal/traits.d index 9f79dd0..1856eb8 100644 --- a/libphobos/libdruntime/core/internal/traits.d +++ b/libphobos/libdruntime/core/internal/traits.d @@ -2,7 +2,7 @@ * Contains traits for runtime internal usage. * * Copyright: Copyright Digital Mars 2014 -. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Nowak * Source: $(DRUNTIMESRC core/internal/_traits.d) */ @@ -25,38 +25,59 @@ T trustedCast(T, U)(auto ref U u) @trusted pure nothrow return cast(T)u; } -template Unconst(T) +alias Unconst(T : const U, U) = U; + +/// taken from std.traits.Unqual +template Unqual(T : const U, U) { - static if (is(T U == immutable U)) alias Unconst = U; - else static if (is(T U == inout const U)) alias Unconst = U; - else static if (is(T U == inout U)) alias Unconst = U; - else static if (is(T U == const U)) alias Unconst = U; - else alias Unconst = T; + static if (is(U == shared V, V)) + alias Unqual = V; + else + alias Unqual = U; } -/// taken from std.traits.Unqual -template Unqual(T) +template BaseElemOf(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; - } + static if (is(T == E[N], E, size_t N)) + alias BaseElemOf = BaseElemOf!E; + else + alias BaseElemOf = T; +} + +unittest +{ + static assert(is(BaseElemOf!(int) == int)); + static assert(is(BaseElemOf!(int[1]) == int)); + static assert(is(BaseElemOf!(int[1][2]) == int)); + static assert(is(BaseElemOf!(int[1][]) == int[1][])); + static assert(is(BaseElemOf!(int[][1]) == int[])); +} + +// [For internal use] +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)); } // Substitute all `inout` qualifiers that appears in T to `const` @@ -129,116 +150,190 @@ template staticIota(int beg, int end) } } +private struct __InoutWorkaroundStruct {} +@property T rvalueOf(T)(T val) { return val; } +@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); +@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); + +// taken from std.traits.isAssignable +template isAssignable(Lhs, Rhs = Lhs) +{ + enum isAssignable = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs) && __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs); +} + +// taken from std.traits.isInnerClass +template isInnerClass(T) if (is(T == class)) +{ + static if (is(typeof(T.outer))) + { + template hasOuterMember(T...) + { + static if (T.length == 0) + enum hasOuterMember = false; + else + enum hasOuterMember = T[0] == "outer" || hasOuterMember!(T[1 .. $]); + } + enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) && !hasOuterMember!(__traits(allMembers, T)); + } + else + enum isInnerClass = false; +} + template dtorIsNothrow(T) { enum dtorIsNothrow = is(typeof(function{T t=void;}) : void function() nothrow); } -/* -Tests whether all given items satisfy a template predicate, i.e. evaluates to -$(D F!(T[0]) && F!(T[1]) && ... && F!(T[$ - 1])). -*/ -package(core.internal) -template allSatisfy(alias F, T...) +// taken from std.meta.allSatisfy +enum allSatisfy(alias pred, items...) = { - static if (T.length == 0) + static foreach (item; items) + static if (!pred!item) + if (__ctfe) return false; + return true; +}(); + +// taken from std.meta.anySatisfy +enum anySatisfy(alias pred, items...) = +{ + static foreach (item; items) + static if (pred!item) + if (__ctfe) return true; + return false; +}(); + +// simplified from std.traits.maxAlignment +template maxAlignment(Ts...) +if (Ts.length > 0) +{ + enum maxAlignment = { - enum allSatisfy = true; + size_t result = 0; + static foreach (T; Ts) + if (T.alignof > result) result = T.alignof; + return result; + }(); +} + +template classInstanceAlignment(T) +if (is(T == class)) +{ + alias classInstanceAlignment = maxAlignment!(void*, typeof(T.tupleof)); +} + +/// See $(REF hasElaborateMove, std,traits) +template hasElaborateMove(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateMove = S.sizeof && hasElaborateMove!(BaseElemOf!S); } - else static if (T.length == 1) + else static if (is(S == struct)) { - enum allSatisfy = F!(T[0]); + enum hasElaborateMove = (is(typeof(S.init.opPostMove(lvalueOf!S))) && + !is(typeof(S.init.opPostMove(rvalueOf!S)))) || + anySatisfy!(.hasElaborateMove, Fields!S); } else { - static if (allSatisfy!(F, T[0 .. $/2])) - enum allSatisfy = allSatisfy!(F, T[$/2 .. $]); - else - enum allSatisfy = false; + enum bool hasElaborateMove = false; } } -template anySatisfy(alias F, T...) +// std.traits.hasElaborateDestructor +template hasElaborateDestructor(S) { - static if (T.length == 0) + static if (__traits(isStaticArray, S)) { - enum anySatisfy = false; + enum bool hasElaborateDestructor = S.sizeof && hasElaborateDestructor!(BaseElemOf!S); } - else static if (T.length == 1) + else static if (is(S == struct)) { - enum anySatisfy = F!(T[0]); + enum hasElaborateDestructor = __traits(hasMember, S, "__dtor") + || anySatisfy!(.hasElaborateDestructor, Fields!S); } else { - enum anySatisfy = - anySatisfy!(F, T[ 0 .. $/2]) || - anySatisfy!(F, T[$/2 .. $ ]); + enum bool hasElaborateDestructor = false; } } -// simplified from std.traits.maxAlignment -template maxAlignment(U...) +// std.traits.hasElaborateCopyDestructor +template hasElaborateCopyConstructor(S) { - static if (U.length == 0) - static assert(0); - else static if (U.length == 1) - enum maxAlignment = U[0].alignof; - else static if (U.length == 2) - enum maxAlignment = U[0].alignof > U[1].alignof ? U[0].alignof : U[1].alignof; + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateCopyConstructor = S.sizeof && hasElaborateCopyConstructor!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + enum hasElaborateCopyConstructor = __traits(hasCopyConstructor, S) || __traits(hasPostblit, S); + } else { - enum a = maxAlignment!(U[0 .. ($+1)/2]); - enum b = maxAlignment!(U[($+1)/2 .. $]); - enum maxAlignment = a > b ? a : b; + enum bool hasElaborateCopyConstructor = false; } } -template classInstanceAlignment(T) -if (is(T == class)) +@safe unittest { - alias classInstanceAlignment = maxAlignment!(void*, typeof(T.tupleof)); -} + static struct S + { + int x; + this(return scope ref typeof(this) rhs) { } + this(int x, int y) {} + } -// Somehow fails for non-static nested structs without support for aliases -template hasElaborateDestructor(T...) -{ - static if (is(T[0])) - alias S = T[0]; - else - alias S = typeof(T[0]); + static assert(hasElaborateCopyConstructor!S); + static assert(!hasElaborateCopyConstructor!(S[0][1])); - static if (is(S : E[n], E, size_t n) && S.length) + static struct S2 { - enum bool hasElaborateDestructor = hasElaborateDestructor!E; + int x; + this(int x, int y) {} } - else static if (is(S == struct)) + + static assert(!hasElaborateCopyConstructor!S2); + + static struct S3 { - enum hasElaborateDestructor = __traits(hasMember, S, "__dtor") - || anySatisfy!(.hasElaborateDestructor, S.tupleof); + int x; + this(return scope ref typeof(this) rhs, int x = 42) { } + this(int x, int y) {} } - else - enum bool hasElaborateDestructor = false; + + static assert(hasElaborateCopyConstructor!S3); } -// Somehow fails for non-static nested structs without support for aliases -template hasElaborateCopyConstructor(T...) +template hasElaborateAssign(S) { - static if (is(T[0])) - alias S = T[0]; - else - alias S = typeof(T[0]); - - static if (is(S : E[n], E, size_t n) && S.length) + static if (__traits(isStaticArray, S)) { - enum bool hasElaborateCopyConstructor = hasElaborateCopyConstructor!E; + enum bool hasElaborateAssign = S.sizeof && hasElaborateAssign!(BaseElemOf!S); } else static if (is(S == struct)) { - enum hasElaborateCopyConstructor = __traits(hasMember, S, "__postblit") - || anySatisfy!(.hasElaborateCopyConstructor, S.tupleof); + enum hasElaborateAssign = is(typeof(S.init.opAssign(rvalueOf!S))) || + is(typeof(S.init.opAssign(lvalueOf!S))) || + anySatisfy!(.hasElaborateAssign, Fields!S); } else - enum bool hasElaborateCopyConstructor = false; + { + enum bool hasElaborateAssign = false; + } +} + +template hasIndirections(T) +{ + static if (is(T == struct) || is(T == union)) + enum hasIndirections = anySatisfy!(.hasIndirections, Fields!T); + else static if (is(T == E[N], E, size_t N)) + enum hasIndirections = T.sizeof && is(E == void) ? true : hasIndirections!(BaseElemOf!E); + else static if (isFunctionPointer!T) + enum hasIndirections = false; + else + enum hasIndirections = isPointer!T || isDelegate!T || isDynamicArray!T || + __traits(isAssociativeArray, T) || is (T == class) || is(T == interface); } template hasUnsharedIndirections(T) @@ -389,3 +484,333 @@ template Filter(alias pred, TList...) Filter!(pred, TList[$/2 .. $ ])); } } + +// std.meta.staticMap +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])); + } + /* Cases 2 to 8 improve compile performance by reducing + * the number of recursive instantiations of staticMap + */ + else static if (T.length == 2) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1])); + } + else static if (T.length == 3) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2])); + } + else static if (T.length == 4) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3])); + } + else static if (T.length == 5) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4])); + } + else static if (T.length == 6) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5])); + } + else static if (T.length == 7) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6])); + } + else static if (T.length == 8) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6]), F!(T[7])); + } + else + { + alias staticMap = + AliasSeq!( + staticMap!(F, T[ 0 .. $/2]), + staticMap!(F, T[$/2 .. $ ])); + } +} + +// std.exception.assertCTFEable +version (CoreUnittest) package(core) +void assertCTFEable(alias dg)() +{ + static assert({ cast(void) dg(); return true; }()); + cast(void) dg(); +} + +// std.traits.FunctionTypeOf +/* +Get the function type from a callable object `func`. + +Using builtin `typeof` on a property function yields the types of the +property value, not of the property function itself. Still, +`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 = __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))); +} + +// std.traits.ReturnType +/* +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 `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)); +} + +// std.traits.Parameters +/* +Get, as a tuple, the types of the parameters to 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 `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); +} + +@safe unittest +{ + int foo(int i, bool b) { return 0; } + static assert(is(Parameters!foo == AliasSeq!(int, bool))); + static assert(is(Parameters!(typeof(&foo)) == AliasSeq!(int, bool))); + + struct S { real opCall(real r, int i) { return 0.0; } } + S s; + static assert(is(Parameters!S == AliasSeq!(real, int))); + static assert(is(Parameters!(S*) == AliasSeq!(real, int))); + static assert(is(Parameters!s == AliasSeq!(real, int))); + + class Test + { + int prop() @property { return 0; } + } + alias P_Test_prop = Parameters!(Test.prop); + static assert(P_Test_prop.length == 0); + + alias P_dglit = Parameters!((int a){}); + static assert(P_dglit.length == 1); + static assert(is(P_dglit[0] == int)); +} + +// Return `true` if `Type` has `member` that evaluates to `true` in a static if condition +enum isTrue(Type, string member) = __traits(compiles, { static if (__traits(getMember, Type, member)) {} else static assert(0); }); + +unittest +{ + static struct T + { + enum a = true; + enum b = false; + enum c = 1; + enum d = 45; + enum e = "true"; + enum f = ""; + enum g = null; + alias h = bool; + } + + static assert( isTrue!(T, "a")); + static assert(!isTrue!(T, "b")); + static assert( isTrue!(T, "c")); + static assert( isTrue!(T, "d")); + static assert( isTrue!(T, "e")); + static assert( isTrue!(T, "f")); + static assert(!isTrue!(T, "g")); + static assert(!isTrue!(T, "h")); +} + +template hasUDA(alias symbol, alias attribute) +{ + alias attrs = __traits(getAttributes, symbol); + + static foreach (a; attrs) + { + static if (is(a == attribute)) + { + enum hasUDA = true; + } + } + + static if (!__traits(compiles, (hasUDA == true))) + enum hasUDA = false; +} + +unittest +{ + struct SomeUDA{} + + struct Test + { + int woUDA; + @SomeUDA int withUDA; + } + + static assert(hasUDA!(Test.withUDA, SomeUDA)); + static assert(!hasUDA!(Test.woUDA, SomeUDA)); +} diff --git a/libphobos/libdruntime/core/internal/utf.d b/libphobos/libdruntime/core/internal/utf.d new file mode 100644 index 0000000..ca0f7f5 --- /dev/null +++ b/libphobos/libdruntime/core/internal/utf.d @@ -0,0 +1,938 @@ +/******************************************** + * Encode and decode UTF-8, UTF-16 and UTF-32 strings. + * + * For Win32 systems, the C wchar_t type is UTF-16 and corresponds to the D + * wchar type. + * For Posix systems, the C wchar_t type is UTF-32 and corresponds to + * the D utf.dchar type. + * + * UTF character support is restricted to (\u0000 <= character <= \U0010FFFF). + * + * See_Also: + * $(LINK2 http://en.wikipedia.org/wiki/Unicode, Wikipedia)
+ * $(LINK http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8)
+ * $(LINK http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335) + * + * Copyright: Copyright Digital Mars 2003 - 2016. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, Sean Kelly + * Source: $(DRUNTIMESRC core/internal/_utf.d) + */ + +module core.internal.utf; + +extern (C) void onUnicodeError( string msg, size_t idx, string file = __FILE__, size_t line = __LINE__ ) @safe pure; + +/******************************* + * Test if c is a valid UTF-32 character. + * + * \uFFFE and \uFFFF are considered valid by this function, + * as they are permitted for internal use by an application, + * but they are not allowed for interchange by the Unicode standard. + * + * Returns: true if it is, false if not. + */ + +@safe @nogc pure nothrow +bool isValidDchar(dchar c) +{ + /* Note: FFFE and FFFF are specifically permitted by the + * Unicode standard for application internal use, but are not + * allowed for interchange. + * (thanks to Arcane Jill) + */ + + return c < 0xD800 || + (c > 0xDFFF && c <= 0x10FFFF /*&& c != 0xFFFE && c != 0xFFFF*/); +} + +unittest +{ + debug(utf) printf("utf.isValidDchar.unittest\n"); + assert(isValidDchar(cast(dchar)'a') == true); + assert(isValidDchar(cast(dchar)0x1FFFFF) == false); +} + + + +static immutable UTF8stride = +[ + cast(ubyte) + 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, + 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, + 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, + 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, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 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,0xFF,0xFF, +]; + +/** + * stride() returns the length of a UTF-8 sequence starting at index i + * in string s. + * Returns: + * The number of bytes in the UTF-8 sequence or + * 0xFF meaning s[i] is not the start of of UTF-8 sequence. + */ +@safe @nogc pure nothrow +uint stride(const scope char[] s, size_t i) +{ + return UTF8stride[s[i]]; +} + +/** + * stride() returns the length of a UTF-16 sequence starting at index i + * in string s. + */ +@safe @nogc pure nothrow +uint stride(const scope wchar[] s, size_t i) +{ uint u = s[i]; + return 1 + (u >= 0xD800 && u <= 0xDBFF); +} + +/** + * stride() returns the length of a UTF-32 sequence starting at index i + * in string s. + * Returns: The return value will always be 1. + */ +@safe @nogc pure nothrow +uint stride(const scope dchar[] s, size_t i) +{ + return 1; +} + +/******************************************* + * Given an index i into an array of characters s[], + * and assuming that index i is at the start of a UTF character, + * determine the number of UCS characters up to that index i. + */ +@safe pure +size_t toUCSindex(const scope char[] s, size_t i) +{ + size_t n; + size_t j; + + for (j = 0; j < i; ) + { + j += stride(s, j); + n++; + } + if (j > i) + { + onUnicodeError("invalid UTF-8 sequence", j); + } + return n; +} + +/** ditto */ +@safe pure +size_t toUCSindex(const scope wchar[] s, size_t i) +{ + size_t n; + size_t j; + + for (j = 0; j < i; ) + { + j += stride(s, j); + n++; + } + if (j > i) + { + onUnicodeError("invalid UTF-16 sequence", j); + } + return n; +} + +/** ditto */ +@safe @nogc pure nothrow +size_t toUCSindex(const scope dchar[] s, size_t i) +{ + return i; +} + +/****************************************** + * Given a UCS index n into an array of characters s[], return the UTF index. + */ +@safe pure +size_t toUTFindex(const scope char[] s, size_t n) +{ + size_t i; + + while (n--) + { + uint j = UTF8stride[s[i]]; + if (j == 0xFF) + onUnicodeError("invalid UTF-8 sequence", i); + i += j; + } + return i; +} + +/** ditto */ +@safe @nogc pure nothrow +size_t toUTFindex(const scope wchar[] s, size_t n) +{ + size_t i; + + while (n--) + { wchar u = s[i]; + + i += 1 + (u >= 0xD800 && u <= 0xDBFF); + } + return i; +} + +/** ditto */ +@safe @nogc pure nothrow +size_t toUTFindex(const scope dchar[] s, size_t n) +{ + return n; +} + +/* =================== Decode ======================= */ + +/*************** + * Decodes and returns character starting at s[idx]. idx is advanced past the + * decoded character. If the character is not well formed, a UtfException is + * thrown and idx remains unchanged. + */ +@safe pure +dchar decode(const scope char[] s, ref size_t idx) + in + { + assert(idx >= 0 && idx < s.length); + } + out (result) + { + assert(isValidDchar(result)); + } + do + { + size_t len = s.length; + dchar V; + size_t i = idx; + char u = s[i]; + + if (u & 0x80) + { uint n; + char u2; + + /* 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 + */ + for (n = 1; ; n++) + { + if (n > 4) + goto Lerr; // only do the first 4 of 6 encodings + if (((u << n) & 0x80) == 0) + { + if (n == 1) + goto Lerr; + break; + } + } + + // Pick off (7 - n) significant bits of B from first byte of octet + V = cast(dchar)(u & ((1 << (7 - n)) - 1)); + + if (i + (n - 1) >= len) + goto Lerr; // off end of string + + /* The following combinations are overlong, and illegal: + * 1100000x (10xxxxxx) + * 11100000 100xxxxx (10xxxxxx) + * 11110000 1000xxxx (10xxxxxx 10xxxxxx) + * 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) + * 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) + */ + u2 = s[i + 1]; + if ((u & 0xFE) == 0xC0 || + (u == 0xE0 && (u2 & 0xE0) == 0x80) || + (u == 0xF0 && (u2 & 0xF0) == 0x80) || + (u == 0xF8 && (u2 & 0xF8) == 0x80) || + (u == 0xFC && (u2 & 0xFC) == 0x80)) + goto Lerr; // overlong combination + + for (uint j = 1; j != n; j++) + { + u = s[i + j]; + if ((u & 0xC0) != 0x80) + goto Lerr; // trailing bytes are 10xxxxxx + V = (V << 6) | (u & 0x3F); + } + if (!isValidDchar(V)) + goto Lerr; + i += n; + } + else + { + V = cast(dchar) u; + i++; + } + + idx = i; + return V; + + Lerr: + onUnicodeError("invalid UTF-8 sequence", i); + return V; // dummy return + } + +unittest +{ size_t i; + dchar c; + + debug(utf) printf("utf.decode.unittest\n"); + + static s1 = "abcd"c; + i = 0; + c = decode(s1, i); + assert(c == cast(dchar)'a'); + assert(i == 1); + c = decode(s1, i); + assert(c == cast(dchar)'b'); + assert(i == 2); + + static s2 = "\xC2\xA9"c; + i = 0; + c = decode(s2, i); + assert(c == cast(dchar)'\u00A9'); + assert(i == 2); + + static s3 = "\xE2\x89\xA0"c; + i = 0; + c = decode(s3, i); + assert(c == cast(dchar)'\u2260'); + assert(i == 3); + + static s4 = + [ "\xE2\x89"c[], // too short + "\xC0\x8A", + "\xE0\x80\x8A", + "\xF0\x80\x80\x8A", + "\xF8\x80\x80\x80\x8A", + "\xFC\x80\x80\x80\x80\x8A", + ]; + + for (int j = 0; j < s4.length; j++) + { + try + { + i = 0; + c = decode(s4[j], i); + assert(0); + } + catch (Throwable o) + { + i = 23; + } + assert(i == 23); + } +} + +/** ditto */ +@safe pure +dchar decode(const scope wchar[] s, ref size_t idx) + in + { + assert(idx >= 0 && idx < s.length); + } + out (result) + { + assert(isValidDchar(result)); + } + do + { + string msg; + dchar V; + size_t i = idx; + uint u = s[i]; + + if (u & ~0x7F) + { if (u >= 0xD800 && u <= 0xDBFF) + { uint u2; + + if (i + 1 == s.length) + { msg = "surrogate UTF-16 high value past end of string"; + goto Lerr; + } + u2 = s[i + 1]; + if (u2 < 0xDC00 || u2 > 0xDFFF) + { msg = "surrogate UTF-16 low value out of range"; + goto Lerr; + } + u = ((u - 0xD7C0) << 10) + (u2 - 0xDC00); + i += 2; + } + else if (u >= 0xDC00 && u <= 0xDFFF) + { msg = "unpaired surrogate UTF-16 value"; + goto Lerr; + } + else if (u == 0xFFFE || u == 0xFFFF) + { msg = "illegal UTF-16 value"; + goto Lerr; + } + else + i++; + } + else + { + i++; + } + + idx = i; + return cast(dchar)u; + + Lerr: + onUnicodeError(msg, i); + return cast(dchar)u; // dummy return + } + +/** ditto */ +@safe pure +dchar decode(const scope dchar[] s, ref size_t idx) + in + { + assert(idx >= 0 && idx < s.length); + } + do + { + size_t i = idx; + dchar c = s[i]; + + if (!isValidDchar(c)) + goto Lerr; + idx = i + 1; + return c; + + Lerr: + onUnicodeError("invalid UTF-32 value", i); + return c; // dummy return + } + + +/* =================== Encode ======================= */ + +/******************************* + * Encodes character c and appends it to array s[]. + */ +@safe pure nothrow +void encode(ref char[] s, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + char[] r = s; + + if (c <= 0x7F) + { + r ~= cast(char) c; + } + else + { + char[4] buf = void; + uint L; + + if (c <= 0x7FF) + { + buf[0] = cast(char)(0xC0 | (c >> 6)); + buf[1] = cast(char)(0x80 | (c & 0x3F)); + L = 2; + } + else if (c <= 0xFFFF) + { + 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) + { + 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(0); + } + r ~= buf[0 .. L]; + } + s = r; + } + +unittest +{ + debug(utf) printf("utf.encode.unittest\n"); + + 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"); +} + +/** ditto */ +@safe pure nothrow +void encode(ref wchar[] s, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + wchar[] r = s; + + if (c <= 0xFFFF) + { + r ~= cast(wchar) c; + } + else + { + wchar[2] buf = void; + + buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); + r ~= buf; + } + s = r; + } + +/** ditto */ +@safe pure nothrow +void encode(ref dchar[] s, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + s ~= c; + } + +/** +Returns the code length of $(D c) in the encoding using $(D C) as a +code point. The code is returned in character count, not in bytes. + */ +@safe pure nothrow @nogc +ubyte codeLength(C)(dchar 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; + } +} + +/* =================== Validation ======================= */ + +/*********************************** +Checks to see if string is well formed or not. $(D S) can be an array + of $(D char), $(D wchar), or $(D dchar). Throws a $(D UtfException) + if it is not. Use to check all untrusted input for correctness. + */ +@safe pure +void validate(S)(const scope S s) +{ + auto len = s.length; + for (size_t i = 0; i < len; ) + { + decode(s, i); + } +} + +/* =================== Conversion to UTF8 ======================= */ + +@safe pure nothrow @nogc +char[] toUTF8(return char[] buf, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + if (c <= 0x7F) + { + buf[0] = cast(char) c; + return buf[0 .. 1]; + } + else if (c <= 0x7FF) + { + buf[0] = cast(char)(0xC0 | (c >> 6)); + buf[1] = cast(char)(0x80 | (c & 0x3F)); + return buf[0 .. 2]; + } + else if (c <= 0xFFFF) + { + buf[0] = cast(char)(0xE0 | (c >> 12)); + buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[2] = cast(char)(0x80 | (c & 0x3F)); + return buf[0 .. 3]; + } + else if (c <= 0x10FFFF) + { + 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 buf[0 .. 4]; + } + assert(0); + } + +/******************* + * Encodes string s into UTF-8 and returns the encoded string. + */ +@safe pure nothrow +string toUTF8(return string s) + in + { + validate(s); + } + do + { + return s; + } + +/** ditto */ +@trusted pure +string toUTF8(const scope wchar[] s) +{ + char[] r; + size_t i; + size_t slen = s.length; + + r.length = slen; + + for (i = 0; i < slen; i++) + { wchar c = s[i]; + + if (c <= 0x7F) + r[i] = cast(char)c; // fast path for ascii + else + { + r.length = i; + foreach (dchar ch; s[i .. slen]) + { + encode(r, ch); + } + break; + } + } + return cast(string)r; +} + +/** ditto */ +@trusted pure +string toUTF8(const scope dchar[] s) +{ + char[] r; + size_t i; + size_t slen = s.length; + + r.length = slen; + + for (i = 0; i < slen; i++) + { dchar c = s[i]; + + if (c <= 0x7F) + r[i] = cast(char)c; // fast path for ascii + else + { + r.length = i; + foreach (dchar d; s[i .. slen]) + { + encode(r, d); + } + break; + } + } + return cast(string)r; +} + +/* =================== Conversion to UTF16 ======================= */ + +@safe pure nothrow @nogc +wchar[] toUTF16(return wchar[] buf, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + if (c <= 0xFFFF) + { + buf[0] = cast(wchar) c; + return buf[0 .. 1]; + } + else + { + buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); + return buf[0 .. 2]; + } + } + +/**************** + * Encodes string s into UTF-16 and returns the encoded string. + * toUTF16z() is suitable for calling the 'W' functions in the Win32 API that take + * an LPWSTR or LPCWSTR argument. + */ +@trusted pure +wstring toUTF16(const scope char[] s) +{ + wchar[] r; + size_t slen = s.length; + + if (!__ctfe) + { + // Reserve still does a lot if slen is zero. + // Return early for that case. + if (0 == slen) + return ""w; + r.reserve(slen); + } + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c <= 0x7F) + { + i++; + r ~= cast(wchar)c; + } + else + { + c = decode(s, i); + encode(r, c); + } + } + return cast(wstring)r; +} + +alias const(wchar)* wptr; +/** ditto */ +@safe pure +wptr toUTF16z(const scope char[] s) +{ + wchar[] r; + size_t slen = s.length; + + if (!__ctfe) + { + // Reserve still does a lot if slen is zero. + // Return early for that case. + if (0 == slen) + return &"\0"w[0]; + r.reserve(slen + 1); + } + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c <= 0x7F) + { + i++; + r ~= cast(wchar)c; + } + else + { + c = decode(s, i); + encode(r, c); + } + } + r ~= '\000'; + return &r[0]; +} + +/** ditto */ +@safe pure nothrow +wstring toUTF16(return wstring s) + in + { + validate(s); + } + do + { + return s; + } + +/** ditto */ +@trusted pure nothrow +wstring toUTF16(const scope dchar[] s) +{ + wchar[] r; + size_t slen = s.length; + + if (!__ctfe) + { + // Reserve still does a lot if slen is zero. + // Return early for that case. + if (0 == slen) + return ""w; + r.reserve(slen); + } + for (size_t i = 0; i < slen; i++) + { + encode(r, s[i]); + } + return cast(wstring)r; +} + +/* =================== Conversion to UTF32 ======================= */ + +/***** + * Encodes string s into UTF-32 and returns the encoded string. + */ +@trusted pure +dstring toUTF32(const scope char[] s) +{ + dchar[] r; + size_t slen = s.length; + size_t j = 0; + + r.length = slen; // r[] will never be longer than s[] + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c >= 0x80) + c = decode(s, i); + else + i++; // c is ascii, no need for decode + r[j++] = c; + } + return cast(dstring)r[0 .. j]; +} + +/** ditto */ +@trusted pure +dstring toUTF32(const scope wchar[] s) +{ + dchar[] r; + size_t slen = s.length; + size_t j = 0; + + r.length = slen; // r[] will never be longer than s[] + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c >= 0x80) + c = decode(s, i); + else + i++; // c is ascii, no need for decode + r[j++] = c; + } + return cast(dstring)r[0 .. j]; +} + +/** ditto */ +@safe pure nothrow +dstring toUTF32(return dstring s) + in + { + validate(s); + } + do + { + return s; + } + +/* ================================ tests ================================== */ + +unittest +{ + debug(utf) printf("utf.toUTF.unittest\n"); + + auto c = "hello"c[]; + auto w = toUTF16(c); + assert(w == "hello"); + auto d = toUTF32(c); + assert(d == "hello"); + + c = toUTF8(w); + assert(c == "hello"); + d = toUTF32(w); + assert(d == "hello"); + + c = toUTF8(d); + assert(c == "hello"); + w = toUTF16(d); + assert(w == "hello"); + + + c = "hel\u1234o"; + w = toUTF16(c); + assert(w == "hel\u1234o"); + d = toUTF32(c); + assert(d == "hel\u1234o"); + + c = toUTF8(w); + assert(c == "hel\u1234o"); + d = toUTF32(w); + assert(d == "hel\u1234o"); + + c = toUTF8(d); + assert(c == "hel\u1234o"); + w = toUTF16(d); + assert(w == "hel\u1234o"); + + + c = "he\U000BAAAAllo"; + w = toUTF16(c); + //foreach (wchar c; w) printf("c = x%x\n", c); + //foreach (wchar c; cast(wstring)"he\U000BAAAAllo") printf("c = x%x\n", c); + assert(w == "he\U000BAAAAllo"); + d = toUTF32(c); + assert(d == "he\U000BAAAAllo"); + + c = toUTF8(w); + assert(c == "he\U000BAAAAllo"); + d = toUTF32(w); + assert(d == "he\U000BAAAAllo"); + + c = toUTF8(d); + assert(c == "he\U000BAAAAllo"); + w = toUTF16(d); + assert(w == "he\U000BAAAAllo"); + + wchar[2] buf; + auto ret = toUTF16(buf, '\U000BAAAA'); + assert(ret == "\U000BAAAA"); +} diff --git a/libphobos/libdruntime/core/internal/util/array.d b/libphobos/libdruntime/core/internal/util/array.d new file mode 100644 index 0000000..bc9b72c --- /dev/null +++ b/libphobos/libdruntime/core/internal/util/array.d @@ -0,0 +1,72 @@ +/** + * Array utilities. + * + * Copyright: Denis Shelomovskij 2013 + * License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Denis Shelomovskij + * Source: $(DRUNTIMESRC core/internal/util/_array.d) + */ +module core.internal.util.array; + + +import core.internal.string; +import core.stdc.stdint; + + +@safe /* pure dmd @@@BUG11461@@@ */ nothrow: + +void enforceTypedArraysConformable(T)(const char[] action, + const T[] a1, const T[] a2, const bool allowOverlap = false) +{ + _enforceSameLength(action, a1.length, a2.length); + if (!allowOverlap) + _enforceNoOverlap(action, arrayToPtr(a1), arrayToPtr(a2), T.sizeof * a1.length); +} + +void enforceRawArraysConformable(const char[] action, const size_t elementSize, + const void[] a1, const void[] a2, const bool allowOverlap = false) +{ + _enforceSameLength(action, a1.length, a2.length); + if (!allowOverlap) + _enforceNoOverlap(action, arrayToPtr(a1), arrayToPtr(a2), elementSize * a1.length); +} + +private void _enforceSameLength(const char[] action, + const size_t length1, const size_t length2) +{ + if (length1 == length2) + return; + + UnsignedStringBuf tmpBuff = void; + string msg = "Array lengths don't match for "; + msg ~= action; + msg ~= ": "; + msg ~= length1.unsignedToTempString(tmpBuff); + msg ~= " != "; + msg ~= length2.unsignedToTempString(tmpBuff); + assert(0, msg); +} + +private void _enforceNoOverlap(const char[] action, + uintptr_t ptr1, uintptr_t ptr2, const size_t bytes) +{ + const d = ptr1 > ptr2 ? ptr1 - ptr2 : ptr2 - ptr1; + if (d >= bytes) + return; + const overlappedBytes = bytes - d; + + UnsignedStringBuf tmpBuff = void; + string msg = "Overlapping arrays in "; + msg ~= action; + msg ~= ": "; + msg ~= overlappedBytes.unsignedToTempString(tmpBuff); + msg ~= " byte(s) overlap of "; + msg ~= bytes.unsignedToTempString(tmpBuff); + assert(0, msg); +} + +private uintptr_t arrayToPtr(const void[] array) @trusted +{ + // Ok because the user will never dereference the pointer + return cast(uintptr_t)array.ptr; +} diff --git a/libphobos/libdruntime/core/internal/util/math.d b/libphobos/libdruntime/core/internal/util/math.d new file mode 100644 index 0000000..416e370 --- /dev/null +++ b/libphobos/libdruntime/core/internal/util/math.d @@ -0,0 +1,53 @@ +// Written in the D programming language + +/** + * Internal math utilities. + * + * Copyright: The D Language Foundation 2021. + * License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Luís Ferreira + * Source: $(DRUNTIMESRC core/internal/util/_math.d) + */ +module core.internal.util.math; + +/** + * Calculates the maximum of the passed arguments + * Params: + * a = first value to select the maximum from + * b = second value to select the maximum from + * Returns: The maximum of the passed-in values. + */ +T max(T)(T a, T b) pure nothrow @nogc @safe +{ + return b > a ? b : a; +} + +/** + * Calculates the minimum of the passed arguments + * Params: + * a = first value to select the minimum from + * b = second value to select the minimum from + * Returns: The minimum of the passed-in values. + */ +T min(T)(T a, T b) pure nothrow @nogc @safe +{ + return b < a ? b : a; +} + +/// +@safe pure @nogc nothrow +unittest +{ + assert(max(1,3) == 3); + assert(max(3,1) == 3); + assert(max(1,1) == 1); +} + +/// +@safe pure @nogc nothrow +unittest +{ + assert(min(1,3) == 1); + assert(min(3,1) == 1); + assert(min(1,1) == 1); +} diff --git a/libphobos/libdruntime/core/lifetime.d b/libphobos/libdruntime/core/lifetime.d new file mode 100644 index 0000000..fc47b1d --- /dev/null +++ b/libphobos/libdruntime/core/lifetime.d @@ -0,0 +1,2201 @@ +module core.lifetime; + +import core.internal.attributes : betterC; + +// emplace +/** +Given a pointer `chunk` to uninitialized memory (but already typed +as `T`), constructs an object of non-`class` type `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 `chunk`). + */ +T* emplace(T)(T* chunk) @safe pure nothrow +{ + import core.internal.lifetime : emplaceRef; + + emplaceRef!T(*chunk); + return chunk; +} + +/// +@betterC +@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 `chunk` to uninitialized memory (but already typed +as a non-class type `T`), constructs an object of type `T` at +that address from arguments `args`. If `T` is a class, initializes +the class reference to `args[0]`. +This function can be `@trusted` if the corresponding constructor of +`T` is `@safe`. +Returns: A pointer to the newly constructed object (which is the same +as `chunk`). + */ +T* emplace(T, Args...)(T* chunk, auto ref Args args) + if (is(T == struct) || Args.length == 1) +{ + import core.internal.lifetime : emplaceRef; + + emplaceRef!T(*chunk, forward!args); + return chunk; +} + +/// +@betterC +@system unittest +{ + int a; + int b = 42; + assert(*emplace!int(&a, b) == 42); +} + +@betterC +@system unittest +{ + shared int i; + emplace(&i, 42); + assert(i == 42); +} + +/** +Given a raw memory area `chunk` (but already typed as a class type `T`), +constructs an object of `class` type `T` at that address. The constructor +is passed the arguments `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. +Note: +This function is `@safe` if the corresponding constructor of `T` is `@safe`. +Returns: The newly constructed object. + */ +T emplace(T, Args...)(T chunk, auto ref Args args) + if (is(T == class)) +{ + import core.internal.traits : isInnerClass; + + static assert(!__traits(isAbstractClass, T), T.stringof ~ + " is abstract and it can't be emplaced"); + + // Initialize the object in its pre-ctor state + enum classSize = __traits(classInstanceSize, T); + (() @trusted => (cast(void*) 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"); + + chunk.outer = args[0]; + alias args1 = args[1..$]; + } + else alias args1 = args; + + // Call the ctor if any + static if (is(typeof(chunk.__ctor(forward!args1)))) + { + // T defines a genuine constructor accepting args + // Go the classic route: write .init first, then call ctor + chunk.__ctor(forward!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 chunk; +} + +/// +@safe unittest +{ + () @safe { + class SafeClass + { + int x; + @safe this(int x) { this.x = x; } + } + + auto buf = new void[__traits(classInstanceSize, SafeClass)]; + auto support = (() @trusted => cast(SafeClass)(buf.ptr))(); + auto safeClass = emplace!SafeClass(support, 5); + assert(safeClass.x == 5); + + class UnsafeClass + { + int x; + @system this(int x) { this.x = x; } + } + + auto buf2 = new void[__traits(classInstanceSize, UnsafeClass)]; + auto support2 = (() @trusted => cast(UnsafeClass)(buf2.ptr))(); + static assert(!__traits(compiles, emplace!UnsafeClass(support2, 5))); + static assert(!__traits(compiles, emplace!UnsafeClass(buf2, 5))); + }(); +} + +@safe unittest +{ + class Outer + { + int i = 3; + class Inner + { + @safe auto getI() { return i; } + } + } + auto outerBuf = new void[__traits(classInstanceSize, Outer)]; + auto outerSupport = (() @trusted => cast(Outer)(outerBuf.ptr))(); + + auto innerBuf = new void[__traits(classInstanceSize, Outer.Inner)]; + auto innerSupport = (() @trusted => cast(Outer.Inner)(innerBuf.ptr))(); + + auto inner = innerSupport.emplace!(Outer.Inner)(outerSupport.emplace!Outer); + assert(inner.getI == 3); +} + +/** +Given a raw memory area `chunk`, constructs an object of `class` type `T` at +that address. The constructor is passed the arguments `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: +`chunk` must be at least as large as `T` needs and should have an alignment +multiple of `T`'s alignment. (The size of a `class` instance is obtained by using +$(D __traits(classInstanceSize, T))). +Note: +This function can be `@trusted` if the corresponding constructor of `T` is `@safe`. +Returns: The newly constructed object. + */ +T emplace(T, Args...)(void[] chunk, auto ref Args args) + if (is(T == class)) +{ + import core.internal.traits : maxAlignment; + + enum classSize = __traits(classInstanceSize, T); + assert(chunk.length >= classSize, "chunk size too small."); + + enum alignment = maxAlignment!(void*, typeof(T.tupleof)); + assert((cast(size_t) chunk.ptr) % alignment == 0, "chunk is not aligned."); + + return emplace!T(cast(T)(chunk.ptr), forward!args); +} + +/// +@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 @safe unittest +{ + static class __conv_EmplaceTestClass + { + @nogc @safe pure nothrow: + int i = 3; + this(int i) + { + assert(this.i == 3); + this.i = 10 + i; + } + this(ref int i) + { + assert(this.i == 3); + this.i = 20 + i; + } + this(int i, ref int j) + { + assert(this.i == 3 && i == 5 && j == 6); + this.i = i; + ++j; + } + } + + int var = 6; + align(__conv_EmplaceTestClass.alignof) ubyte[__traits(classInstanceSize, __conv_EmplaceTestClass)] buf; + auto support = (() @trusted => cast(__conv_EmplaceTestClass)(buf.ptr))(); + + auto fromRval = emplace!__conv_EmplaceTestClass(support, 1); + assert(fromRval.i == 11); + + auto fromLval = emplace!__conv_EmplaceTestClass(support, var); + assert(fromLval.i == 26); + + auto k = emplace!__conv_EmplaceTestClass(support, 5, var); + assert(k.i == 5); + assert(var == 7); +} + +/** +Given a raw memory area `chunk`, constructs an object of non-$(D +class) type `T` at that address. The constructor is passed the +arguments `args`, if any. +Preconditions: +`chunk` must be at least as large +as `T` needs and should have an alignment multiple of `T`'s +alignment. +Note: +This function can be `@trusted` if the corresponding constructor of +`T` is `@safe`. +Returns: A pointer to the newly constructed object. + */ +T* emplace(T, Args...)(void[] chunk, auto ref Args args) + if (!is(T == class)) +{ + import core.internal.traits : Unqual; + import core.internal.lifetime : emplaceRef; + + assert(chunk.length >= T.sizeof, "chunk size too small."); + assert((cast(size_t) chunk.ptr) % T.alignof == 0, "emplace: Chunk is not aligned."); + + emplaceRef!(T, Unqual!T)(*cast(Unqual!T*) chunk.ptr, forward!args); + return cast(T*) chunk.ptr; +} + +/// +@betterC +@system unittest +{ + struct S + { + int a, b; + } + void[S.sizeof] buf = void; + 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 + +@betterC +@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"); +} + +@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)))); +} + +@betterC +@system unittest +{ + struct S { @disable this(); } + S s = void; + static assert(!__traits(compiles, emplace(&s))); + emplace(&s, S.init); +} + +@betterC +@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 +@betterC +@system unittest +{ + struct S + { + double x = 5, y = 6; + this(int a, int b) + { + assert(x == 5 && y == 6); + x = a; + y = b; + } + } + + void[S.sizeof] s1 = void; + 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 +{ + static 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(); + } + + __conv_EmplaceTest k = void; + emplace(&k, 5); + assert(k.i == 5); + + int var = 6; + __conv_EmplaceTest x = void; + emplace(&x, 5, var); + assert(x.i == 5); + assert(var == 7); + + var = 6; + auto z = emplace!__conv_EmplaceTest(new void[__conv_EmplaceTest.sizeof], 5, var); + assert(z.i == 5); + assert(var == 7); +} + +// Test matching fields branch +@betterC +@system unittest +{ + struct S { uint n; } + S s; + emplace!S(&s, 2U); + assert(s.n == 2); +} + +@betterC +@safe unittest +{ + struct S { int a, b; this(int){} } + S s; + static assert(!__traits(compiles, emplace!S(&s, 2, 3))); +} + +@betterC +@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 +@betterC +@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 +@betterC +@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 +@betterC +@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))); // copy disabled + static assert(__traits(compiles, emplace(&s1, move(s1)))); // move not affected + + 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))); // copying disabled + static assert(__traits(compiles, emplace(&ss1, move(ss1)))); // move unaffected + + static struct SS2 + { + S2 s; + } + SS2 ss2 = void; + emplace(&ss2); + static assert(!__traits(compiles, emplace(&ss2, ss2))); // copying disabled + static assert(__traits(compiles, emplace(&ss2, SS2.init))); // move is OK + + + // 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 +@betterC +@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))); + } +} + +@betterC +@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 +@betterC +@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 (CoreUnittest) +{ + //Ambiguity + private 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; + } + } + private 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; + } +} + +@system unittest +{ + static assert(is(__std_conv_SS : __std_conv_S)); + __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 +@betterC +@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); +} + +@betterC +@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 +@betterC +@system unittest +{ + static struct S + { + @disable void opAssign(S); + } + S s; + emplace(&s, S.init); +} + +//opCall +@betterC +@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); + } +} + +//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 core.internal.lifetime : emplaceRef; + + 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[]); +} + +@betterC +pure nothrow @safe @nogc unittest +{ + import core.internal.lifetime : emplaceRef; + + int i; + emplaceRef(i); + emplaceRef!int(i); + emplaceRef(i, 5); + emplaceRef!int(i, 5); +} + +// Test attribute propagation for UDTs +pure nothrow @safe /* @nogc */ unittest +{ + import core.internal.lifetime : emplaceRef; + + 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))); +} + +@betterC +@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]; + + const Node* n = emplace!(const Node)(buf, 42, null, 10); + assert(n.payload == 42); + assert(n.next == null); + assert(n.refs == 10); +} + +@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)))); +} + +//constructor arguments forwarding +@betterC +@system unittest +{ + static struct S + { + this()(auto ref long arg) + { + // assert that arg is an lvalue + static assert(__traits(isRef, arg)); + } + this()(auto ref double arg) + // assert that arg is an rvalue + { + static assert(!__traits(isRef, arg)); + } + } + S obj = void; + long i; + emplace(&obj, i); // lvalue + emplace(&obj, 0.0); // rvalue +} +// Bulk of emplace unittests ends here + +/** + * Emplaces a copy of the specified source value into uninitialized memory, + * i.e., simulates `T target = source` copy-construction for cases where the + * target memory is already allocated and to be initialized with a copy. + * + * Params: + * source = value to be copied into target + * target = uninitialized value to be initialized with a copy of source + */ +void copyEmplace(S, T)(ref S source, ref T target) @system + if (is(immutable S == immutable T)) +{ + import core.internal.traits : BaseElemOf, hasElaborateCopyConstructor, Unconst, Unqual; + + // cannot have the following as simple template constraint due to nested-struct special case... + static if (!__traits(compiles, (ref S src) { T tgt = src; })) + { + alias B = BaseElemOf!T; + enum isNestedStruct = is(B == struct) && __traits(isNested, B); + static assert(isNestedStruct, "cannot copy-construct " ~ T.stringof ~ " from " ~ S.stringof); + } + + void blit() + { + import core.stdc.string : memcpy; + memcpy(cast(Unqual!(T)*) &target, cast(Unqual!(T)*) &source, T.sizeof); + } + + static if (is(T == struct)) + { + static if (__traits(hasPostblit, T)) + { + blit(); + (cast() target).__xpostblit(); + } + else static if (__traits(hasCopyConstructor, T)) + { + emplace(cast(Unqual!(T)*) &target); // blit T.init + static if (__traits(isNested, T)) + { + // copy context pointer + *(cast(void**) &target.tupleof[$-1]) = cast(void*) source.tupleof[$-1]; + } + target.__ctor(source); // invoke copy ctor + } + else + { + blit(); // no opAssign + } + } + else static if (is(T == E[n], E, size_t n)) + { + static if (hasElaborateCopyConstructor!E) + { + size_t i; + try + { + for (i = 0; i < n; i++) + copyEmplace(source[i], target[i]); + } + catch (Exception e) + { + // destroy, in reverse order, what we've constructed so far + while (i--) + destroy(*cast(Unconst!(E)*) &target[i]); + throw e; + } + } + else // trivial copy + { + blit(); // all elements at once + } + } + else + { + *cast(Unconst!(T)*) &target = *cast(Unconst!(T)*) &source; + } +} + +/// +@betterC +@system pure nothrow @nogc unittest +{ + int source = 123; + int target = void; + copyEmplace(source, target); + assert(target == 123); +} + +/// +@betterC +@system pure nothrow @nogc unittest +{ + immutable int[1][1] source = [ [123] ]; + immutable int[1][1] target = void; + copyEmplace(source, target); + assert(target[0][0] == 123); +} + +/// +@betterC +@system pure nothrow @nogc unittest +{ + struct S + { + int x; + void opAssign(const scope ref S rhs) @safe pure nothrow @nogc + { + assert(0); + } + } + + S source = S(42); + S target = void; + copyEmplace(source, target); + assert(target.x == 42); +} + +// preserve shared-ness +@system pure nothrow unittest +{ + auto s = new Object(); + auto ss = new shared Object(); + + Object t; + shared Object st; + + copyEmplace(s, t); + assert(t is s); + + copyEmplace(ss, st); + assert(st is ss); + + static assert(!__traits(compiles, copyEmplace(s, st))); + static assert(!__traits(compiles, copyEmplace(ss, t))); +} + +version (DigitalMars) version (X86) version (Posix) version = DMD_X86_Posix; + +// don't violate immutability for reference types +@system pure nothrow unittest +{ + auto s = new Object(); + auto si = new immutable Object(); + + Object t; + immutable Object ti; + + copyEmplace(s, t); + assert(t is s); + + copyEmplace(si, ti); + version (DMD_X86_Posix) { /* wrongly fails without -O */ } else + assert(ti is si); + + static assert(!__traits(compiles, copyEmplace(s, ti))); + static assert(!__traits(compiles, copyEmplace(si, t))); +} + +version (CoreUnittest) +{ + private void testCopyEmplace(S, T)(const scope T* expected = null) + { + S source; + T target = void; + copyEmplace(source, target); + if (expected) + assert(target == *expected); + else + { + T expectedCopy = source; + assert(target == expectedCopy); + } + } +} + +// postblit +@system pure nothrow @nogc unittest +{ + static struct S + { + @safe pure nothrow @nogc: + int x = 42; + this(this) { x += 10; } + } + + testCopyEmplace!(S, S)(); + testCopyEmplace!(immutable S, S)(); + testCopyEmplace!(S, immutable S)(); + testCopyEmplace!(immutable S, immutable S)(); + + testCopyEmplace!(S[1], S[1])(); + testCopyEmplace!(immutable S[1], S[1])(); + + // copying to an immutable static array works, but `T expected = source` + // wrongly ignores the postblit: https://issues.dlang.org/show_bug.cgi?id=8950 + immutable S[1] expectedImmutable = [S(52)]; + testCopyEmplace!(S[1], immutable S[1])(&expectedImmutable); + testCopyEmplace!(immutable S[1], immutable S[1])(&expectedImmutable); +} + +// copy constructors +@system pure nothrow @nogc unittest +{ + static struct S + { + @safe pure nothrow @nogc: + int x = 42; + this(int x) { this.x = x; } + this(const scope ref S rhs) { x = rhs.x + 10; } + this(const scope ref S rhs) immutable { x = rhs.x + 20; } + } + + testCopyEmplace!(S, S)(); + testCopyEmplace!(immutable S, S)(); + testCopyEmplace!(S, immutable S)(); + testCopyEmplace!(immutable S, immutable S)(); + + // static arrays work, but `T expected = source` wrongly ignores copy ctors + // https://issues.dlang.org/show_bug.cgi?id=20365 + S[1] expectedMutable = [S(52)]; + immutable S[1] expectedImmutable = [immutable S(62)]; + testCopyEmplace!(S[1], S[1])(&expectedMutable); + testCopyEmplace!(immutable S[1], S[1])(&expectedMutable); + testCopyEmplace!(S[1], immutable S[1])(&expectedImmutable); + testCopyEmplace!(immutable S[1], immutable S[1])(&expectedImmutable); +} + +// copy constructor in nested struct +@system pure nothrow unittest +{ + int copies; + struct S + { + @safe pure nothrow @nogc: + size_t x = 42; + this(size_t x) { this.x = x; } + this(const scope ref S rhs) + { + assert(x == 42); // T.init + x = rhs.x; + ++copies; + } + } + + { + copies = 0; + S source = S(123); + immutable S target = void; + copyEmplace(source, target); + assert(target is source); + assert(copies == 1); + } + + { + copies = 0; + immutable S[1] source = [immutable S(456)]; + S[1] target = void; + copyEmplace(source, target); + assert(target[0] is source[0]); + assert(copies == 1); + } +} + +// destruction of partially copied static array +@system unittest +{ + static struct S + { + __gshared int[] deletions; + int x; + this(this) { if (x == 5) throw new Exception(""); } + ~this() { deletions ~= x; } + } + + alias T = immutable S[3][2]; + T source = [ [S(1), S(2), S(3)], [S(4), S(5), S(6)] ]; + T target = void; + try + { + copyEmplace(source, target); + assert(0); + } + catch (Exception) + { + static immutable expectedDeletions = [ 4, 3, 2, 1 ]; + version (DigitalMars) + { + assert(S.deletions == expectedDeletions || + S.deletions == [ 4 ]); // FIXME: happens with -O + } + else + assert(S.deletions == expectedDeletions); + } +} + +/** +Forwards function arguments while keeping `out`, `ref`, and `lazy` on +the parameters. + +Params: + args = a parameter list or an $(REF AliasSeq,std,meta). +Returns: + An `AliasSeq` of `args` with `out`, `ref`, and `lazy` saved. +*/ +template forward(args...) +{ + import core.internal.traits : AliasSeq; + + static if (args.length) + { + alias arg = args[0]; + // by ref || lazy || const/immutable + static if (__traits(isRef, arg) || + __traits(isOut, arg) || + __traits(isLazy, arg) || + !is(typeof(move(arg)))) + alias fwd = arg; + // (r)value + else + @property auto fwd(){ return move(arg); } + + static if (args.length == 1) + alias forward = fwd; + else + 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; } + } + + // with forward + int bar()(auto ref int x) { return C.foo(forward!x); } + + // without forward + int baz()(auto ref int x) { return C.foo(x); } + + int i; + assert(bar(1) == 1); + assert(bar(i) == 2); + + assert(baz(1) == 2); + assert(baz(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"); +} + +@betterC +@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 +} + +/// +@betterC +@safe unittest +{ + struct X { + int i; + this(this) + { + ++i; + } + } + + struct Y + { + private X x_; + this()(auto ref X x) + { + x_ = forward!x; + } + } + + struct Z + { + private const X x_; + this()(auto ref X x) + { + x_ = forward!x; + } + this()(auto const ref X x) + { + x_ = forward!x; + } + } + + X x; + const X cx; + auto constX = (){ const X x; return x; }; + static assert(__traits(compiles, { Y y = x; })); + static assert(__traits(compiles, { Y y = X(); })); + static assert(!__traits(compiles, { Y y = cx; })); + static assert(!__traits(compiles, { Y y = constX(); })); + static assert(__traits(compiles, { Z z = x; })); + static assert(__traits(compiles, { Z z = X(); })); + static assert(__traits(compiles, { Z z = cx; })); + static assert(__traits(compiles, { Z z = constX(); })); + + + Y y1 = x; + // ref lvalue, copy + assert(y1.x_.i == 1); + Y y2 = X(); + // rvalue, move + assert(y2.x_.i == 0); + + Z z1 = x; + // ref lvalue, copy + assert(z1.x_.i == 1); + Z z2 = X(); + // rvalue, move + assert(z2.x_.i == 0); + Z z3 = cx; + // ref const lvalue, copy + assert(z3.x_.i == 1); + Z z4 = constX(); + // const rvalue, copy + assert(z4.x_.i == 1); +} + +// lazy -> lazy +@betterC +@safe unittest +{ + int foo1(lazy int i) { return i; } + int foo2(A)(auto ref A i) { return foo1(forward!i); } + int foo3(lazy int i) { return foo2(i); } + + int numCalls = 0; + assert(foo3({ ++numCalls; return 42; }()) == 42); + assert(numCalls == 1); +} + +// lazy -> non-lazy +@betterC +@safe unittest +{ + int foo1(int a, int b) { return a + b; } + int foo2(A...)(auto ref A args) { return foo1(forward!args); } + int foo3(int a, lazy int b) { return foo2(a, b); } + + int numCalls; + assert(foo3(11, { ++numCalls; return 31; }()) == 42); + assert(numCalls == 1); +} + +// non-lazy -> lazy +@betterC +@safe unittest +{ + int foo1(int a, lazy int b) { return a + b; } + int foo2(A...)(auto ref A args) { return foo1(forward!args); } + int foo3(int a, int b) { return foo2(a, b); } + + assert(foo3(11, 31) == 42); +} + +// out +@betterC +@safe unittest +{ + void foo1(int a, out int b) { b = a; } + void foo2(A...)(auto ref A args) { foo1(forward!args); } + void foo3(int a, out int b) { foo2(a, b); } + + int b; + foo3(42, b); + assert(b == 42); +} + +// 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 and doesn't define +opPostMove, 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) +{ + moveImpl(target, source); +} + +/// 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 core.internal.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)(return scope ref T source) +{ + 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); +} + +// https://issues.dlang.org/show_bug.cgi?id=20869 +// `move` should propagate the attributes of `opPostMove` +@system unittest +{ + static struct S + { + void opPostMove(const ref S old) nothrow @system + { + __gshared int i; + new int(i++); // Force @gc impure @system + } + } + + alias T = void function() @system nothrow; + static assert(is(typeof({ S s; move(s); }) == T)); + static assert(is(typeof({ S s; move(s, s); }) == T)); +} + +private void moveImpl(T)(scope ref T target, return scope ref T source) +{ + import core.internal.traits : hasElaborateDestructor; + + static if (is(T == struct)) + { + // Unsafe when compiling without -dip1000 + if ((() @trusted => &source == &target)()) return; + // Destroy target before overwriting it + static if (hasElaborateDestructor!T) target.__xdtor(); + } + // move and emplace source into target + moveEmplaceImpl(target, source); +} + +private T moveImpl(T)(return scope ref T source) +{ + // Properly infer safety from moveEmplaceImpl as the implementation below + // might void-initialize pointers in result and hence needs to be @trusted + if (false) moveEmplaceImpl(source, source); + + return trustedMoveImpl(source); +} + +private T trustedMoveImpl(T)(return scope ref T source) @trusted +{ + T result = void; + moveEmplaceImpl(result, source); + return result; +} + +@safe unittest +{ + import core.internal.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); +} + +@betterC +@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); +} +/+ this can't be tested in druntime, tests are still run in phobos +@safe unittest//Issue 6217 +{ + import std.algorithm.iteration : map; + auto x = map!"a"([1,2,3]); + x = move(x); +} ++/ +@betterC +@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); +} + +// target must be first-parameter, because in void-functions DMD + dip1000 allows it to take the place of a return-scope +private void moveEmplaceImpl(T)(scope ref T target, return scope ref T source) +{ + import core.stdc.string : memcpy, memset; + import core.internal.traits; + + // TODO: this assert pulls in half of phobos. we need to work out an alternative assert strategy. +// static if (!is(T == class) && hasAliasing!T) if (!__ctfe) +// { +// import std.exception : doesPointTo; +// assert(!doesPointTo(source, source) && !hasElaborateMove!T), +// "Cannot move object with internal pointer unless `opPostMove` is defined."); +// } + + static if (is(T == struct)) + { + // Unsafe when compiling without -dip1000 + assert((() @trusted => &source !is &target)(), "source and target must not be identical"); + + static if (hasElaborateAssign!T || !isAssignable!T) + () @trusted { memcpy(&target, &source, T.sizeof); }(); + else + target = source; + + static if (hasElaborateMove!T) + __move_post_blt(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; + + static if (__traits(isZeroInit, T)) + () @trusted { memset(&source, 0, sz); }(); + else + { + auto init = typeid(T).initializer(); + () @trusted { memcpy(&source, init.ptr, sz); }(); + } + } + } + else static if (__traits(isStaticArray, T)) + { + for (size_t i = 0; i < source.length; ++i) + move(source[i], target[i]); + } + else + { + // Primitive data (including pointers and arrays) or class - + // assignment works great + target = source; + } +} + +/** + * 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 +{ + moveEmplaceImpl(target, source); +} + +/// +@betterC +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); +} + +// issue 18913 +@safe unittest +{ + static struct NoCopy + { + int payload; + ~this() { } + @disable this(this); + } + + static void f(NoCopy[2]) { } + + NoCopy[2] ncarray = [ NoCopy(1), NoCopy(2) ]; + + static assert(!__traits(compiles, f(ncarray))); + f(move(ncarray)); +} diff --git a/libphobos/libdruntime/core/memory.d b/libphobos/libdruntime/core/memory.d index af0fee1..3770c13 100644 --- a/libphobos/libdruntime/core/memory.d +++ b/libphobos/libdruntime/core/memory.d @@ -104,31 +104,26 @@ module core.memory; +version (ARM) + version = AnyARM; +else version (AArch64) + version = AnyARM; + +version (iOS) + version = iOSDerived; +else version (TVOS) + version = iOSDerived; +else version (WatchOS) + version = iOSDerived; private { - extern (C) void gc_init(); - extern (C) void gc_term(); - - extern (C) void gc_enable() nothrow; - extern (C) void gc_disable() nothrow; - extern (C) void gc_collect() nothrow; - extern (C) void gc_minimize() nothrow; - extern (C) uint gc_getAttr( void* p ) pure nothrow; extern (C) uint gc_setAttr( void* p, uint a ) pure nothrow; extern (C) uint gc_clrAttr( void* p, uint a ) pure nothrow; - extern (C) void* gc_malloc( size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow; - extern (C) void* gc_calloc( size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow; - extern (C) BlkInfo_ gc_qalloc( size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow; - extern (C) void* gc_realloc( void* p, size_t sz, uint ba = 0, const TypeInfo = null ) pure nothrow; - extern (C) size_t gc_extend( void* p, size_t mx, size_t sz, const TypeInfo = null ) pure nothrow; - extern (C) size_t gc_reserve( size_t sz ) nothrow; - extern (C) void gc_free( void* p ) pure nothrow; - - extern (C) void* gc_addrOf( void* p ) pure nothrow; - extern (C) size_t gc_sizeOf( void* p ) pure nothrow; + extern (C) void* gc_addrOf( void* p ) pure nothrow @nogc; + extern (C) size_t gc_sizeOf( void* p ) pure nothrow @nogc; struct BlkInfo_ { @@ -137,19 +132,96 @@ private uint attr; } - extern (C) BlkInfo_ gc_query( void* p ) pure nothrow; + extern (C) BlkInfo_ gc_query(return scope void* p) pure nothrow; extern (C) GC.Stats gc_stats ( ) nothrow @nogc; + extern (C) GC.ProfileStats gc_profileStats ( ) nothrow @nogc @safe; +} - extern (C) void gc_addRoot( in void* p ) nothrow @nogc; - extern (C) void gc_addRange( in void* p, size_t sz, const TypeInfo ti = null ) nothrow @nogc; +version (CoreDoc) +{ + /** + * The minimum size of a system page in bytes. + * + * This is a compile time, platform specific value. This value might not + * be accurate, since it might be possible to change this value. Whenever + * possible, please use $(LREF pageSize) instead, which is initialized + * during runtime. + * + * The minimum size is useful when the context requires a compile time known + * value, like the size of a static array: `ubyte[minimumPageSize] buffer`. + */ + enum minimumPageSize : size_t; +} +else version (AnyARM) +{ + version (iOSDerived) + enum size_t minimumPageSize = 16384; + else + enum size_t minimumPageSize = 4096; +} +else + enum size_t minimumPageSize = 4096; - extern (C) void gc_removeRoot( in void* p ) nothrow @nogc; - extern (C) void gc_removeRange( in void* p ) nothrow @nogc; - extern (C) void gc_runFinalizers( in void[] segment ); +/// +unittest +{ + ubyte[minimumPageSize] buffer; +} - package extern (C) bool gc_inFinalizer(); +/** + * The size of a system page in bytes. + * + * This value is set at startup time of the application. It's safe to use + * early in the start process, like in shared module constructors and + * initialization of the D runtime itself. + */ +immutable size_t pageSize; + +/// +unittest +{ + ubyte[] buffer = new ubyte[pageSize]; } +// The reason for this elaborated way of declaring a function is: +// +// * `pragma(crt_constructor)` is used to declare a constructor that is called by +// the C runtime, before C main. This allows the `pageSize` value to be used +// during initialization of the D runtime. This also avoids any issues with +// static module constructors and circular references. +// +// * `pragma(mangle)` is used because `pragma(crt_constructor)` requires a +// function with C linkage. To avoid any name conflict with other C symbols, +// standard D mangling is used. +// +// * The extra function declaration, without the body, is to be able to get the +// D mangling of the function without the need to hardcode the value. +// +// * The extern function declaration also has the side effect of making it +// impossible to manually call the function with standard syntax. This is to +// make it more difficult to call the function again, manually. +private void initialize(); +pragma(crt_constructor) +pragma(mangle, `_D` ~ initialize.mangleof) +private extern (C) void initialize() @system +{ + version (Posix) + { + import core.sys.posix.unistd : sysconf, _SC_PAGESIZE; + + (cast() pageSize) = cast(size_t) sysconf(_SC_PAGESIZE); + } + else version (Windows) + { + import core.sys.windows.winbase : GetSystemInfo, SYSTEM_INFO; + + SYSTEM_INFO si; + GetSystemInfo(&si); + (cast() pageSize) = cast(size_t) si.dwPageSize; + } + else + static assert(false, __FUNCTION__ ~ " is not implemented on this platform"); +} /** * This struct encapsulates all garbage collection functionality for the D @@ -168,18 +240,37 @@ struct GC size_t usedSize; /// number of free bytes on the GC heap (might only get updated after a collection) size_t freeSize; + /// number of bytes allocated for current thread since program start + ulong allocatedInCurrentThread; } /** + * Aggregation of current profile information + */ + static struct ProfileStats + { + import core.time : Duration; + /// total number of GC cycles + size_t numCollections; + /// total time spent doing GC + Duration totalCollectionTime; + /// total time threads were paused doing GC + Duration totalPauseTime; + /// largest time threads were paused during one GC cycle + Duration maxPauseTime; + /// largest time spent doing one GC cycle + Duration maxCollectionTime; + } + +extern(C): + + /** * Enables automatic garbage collection behavior if collections have * previously been suspended by a call to disable. This function is * reentrant, and must be called once for every call to disable before * automatic collections are enabled. */ - static void enable() nothrow /* FIXME pure */ - { - gc_enable(); - } + pragma(mangle, "gc_enable") static void enable() nothrow; /* FIXME pure */ /** @@ -189,10 +280,7 @@ struct GC * such as during an out of memory condition. This function is reentrant, * but enable must be called once for each call to disable. */ - static void disable() nothrow /* FIXME pure */ - { - gc_disable(); - } + pragma(mangle, "gc_disable") static void disable() nothrow; /* FIXME pure */ /** @@ -202,21 +290,16 @@ struct GC * and then to reclaim free space. This action may need to suspend all * running threads for at least part of the collection process. */ - static void collect() nothrow /* FIXME pure */ - { - gc_collect(); - } + pragma(mangle, "gc_collect") static void collect() nothrow; /* FIXME pure */ /** * Indicates that the managed memory space be minimized by returning free * physical memory to the operating system. The amount of free memory * returned depends on the allocator design and on program behavior. */ - static void minimize() nothrow /* FIXME pure */ - { - gc_minimize(); - } + pragma(mangle, "gc_minimize") static void minimize() nothrow; /* FIXME pure */ +extern(D): /** * Elements for a bit field representing memory block attributes. These @@ -288,9 +371,9 @@ struct GC * A bit field containing any bits set for the memory block referenced by * p or zero on error. */ - static uint getAttr( in void* p ) nothrow + static uint getAttr( const scope void* p ) nothrow { - return getAttr(cast()p); + return gc_getAttr(cast(void*) p); } @@ -315,9 +398,9 @@ struct GC * The result of a call to getAttr after the specified bits have been * set. */ - static uint setAttr( in void* p, uint a ) nothrow + static uint setAttr( const scope void* p, uint a ) nothrow { - return setAttr(cast()p, a); + return gc_setAttr(cast(void*) p, a); } @@ -342,9 +425,9 @@ struct GC * The result of a call to getAttr after the specified bits have been * cleared. */ - static uint clrAttr( in void* p, uint a ) nothrow + static uint clrAttr( const scope void* p, uint a ) nothrow { - return clrAttr(cast()p, a); + return gc_clrAttr(cast(void*) p, a); } @@ -354,6 +437,7 @@ struct GC return gc_clrAttr( p, a ); } +extern(C): /** * Requests an aligned block of managed memory from the garbage collector. @@ -375,10 +459,7 @@ struct GC * Throws: * OutOfMemoryError on allocation failure. */ - static void* malloc( size_t sz, uint ba = 0, const TypeInfo ti = null ) pure nothrow - { - return gc_malloc( sz, ba, ti ); - } + pragma(mangle, "gc_malloc") static void* malloc(size_t sz, uint ba = 0, const scope TypeInfo ti = null) pure nothrow; /** @@ -401,10 +482,7 @@ struct GC * Throws: * OutOfMemoryError on allocation failure. */ - static BlkInfo qalloc( size_t sz, uint ba = 0, const TypeInfo ti = null ) pure nothrow - { - return gc_qalloc( sz, ba, ti ); - } + pragma(mangle, "gc_qalloc") static BlkInfo qalloc(size_t sz, uint ba = 0, const scope TypeInfo ti = null) pure nothrow; /** @@ -428,52 +506,55 @@ struct GC * Throws: * OutOfMemoryError on allocation failure. */ - static void* calloc( size_t sz, uint ba = 0, const TypeInfo ti = null ) pure nothrow - { - return gc_calloc( sz, ba, ti ); - } + pragma(mangle, "gc_calloc") static void* calloc(size_t sz, uint ba = 0, const TypeInfo ti = null) pure nothrow; /** - * If sz is zero, the memory referenced by p will be deallocated as if - * by a call to free. A new memory block of size sz will then be - * allocated as if by a call to malloc, or the implementation may instead - * resize the memory block in place. The contents of the new memory block - * will be the same as the contents of the old memory block, up to the - * lesser of the new and old sizes. Note that existing memory will only - * be freed by realloc if sz is equal to zero. The garbage collector is - * otherwise expected to later reclaim the memory block if it is unused. - * If allocation fails, this function will call onOutOfMemory which is - * expected to throw an OutOfMemoryError. If p references memory not - * originally allocated by this garbage collector, or if it points to the - * interior of a memory block, no action will be taken. If ba is zero - * (the default) and p references the head of a valid, known memory block - * then any bits set on the current block will be set on the new block if a - * reallocation is required. If ba is not zero and p references the head - * of a valid, known memory block then the bits in ba will replace those on - * the current memory block and will also be set on the new block if a - * reallocation is required. + * Extend, shrink or allocate a new block of memory keeping the contents of + * an existing block + * + * If `sz` is zero, the memory referenced by p will be deallocated as if + * by a call to `free`. + * If `p` is `null`, new memory will be allocated via `malloc`. + * If `p` is pointing to memory not allocated from the GC or to the interior + * of an allocated memory block, no operation is performed and null is returned. + * + * Otherwise, a new memory block of size `sz` will be allocated as if by a + * call to `malloc`, or the implementation may instead resize or shrink the memory + * block in place. + * The contents of the new memory block will be the same as the contents + * of the old memory block, up to the lesser of the new and old sizes. + * + * The caller guarantees that there are no other live pointers to the + * passed memory block, still it might not be freed immediately by `realloc`. + * The garbage collector can reclaim the memory block in a later + * collection if it is unused. + * If allocation fails, this function will throw an `OutOfMemoryError`. + * + * If `ba` is zero (the default) the attributes of the existing memory + * will be used for an allocation. + * If `ba` is not zero and no new memory is allocated, the bits in ba will + * replace those of the current memory block. * * Params: - * p = A pointer to the root of a valid memory block or to null. + * p = A pointer to the base of a valid memory block or to `null`. * sz = The desired allocation size in bytes. - * ba = A bitmask of the attributes to set on this block. + * ba = A bitmask of the BlkAttr attributes to set on this block. * ti = TypeInfo to describe the memory. The GC might use this information * to improve scanning for pointers or to call finalizers. * * Returns: - * A reference to the allocated memory on success or null if sz is - * zero. On failure, the original value of p is returned. + * A reference to the allocated memory on success or `null` if `sz` is + * zero or the pointer does not point to the base of an GC allocated + * memory block. * * Throws: - * OutOfMemoryError on allocation failure. + * `OutOfMemoryError` on allocation failure. */ - static void* realloc( void* p, size_t sz, uint ba = 0, const TypeInfo ti = null ) pure nothrow - { - return gc_realloc( p, sz, ba, ti ); - } + pragma(mangle, "gc_realloc") static void* realloc(return void* p, size_t sz, uint ba = 0, const TypeInfo ti = null) pure nothrow; - /// Issue 13111 + // https://issues.dlang.org/show_bug.cgi?id=13111 + /// unittest { enum size1 = 1 << 11 + 1; // page in large object pool @@ -482,7 +563,7 @@ struct GC auto data1 = cast(ubyte*)GC.calloc(size1); auto data2 = cast(ubyte*)GC.realloc(data1, size2); - BlkInfo info = query(data2); + GC.BlkInfo info = GC.query(data2); assert(info.size >= size2); } @@ -512,10 +593,7 @@ struct GC * as an indicator of success. $(LREF capacity) should be used to * retrieve actual usable slice capacity. */ - static size_t extend( void* p, size_t mx, size_t sz, const TypeInfo ti = null ) pure nothrow - { - return gc_extend( p, mx, sz, ti ); - } + pragma(mangle, "gc_extend") static size_t extend(void* p, size_t mx, size_t sz, const TypeInfo ti = null) pure nothrow; /// Standard extending unittest { @@ -557,10 +635,7 @@ struct GC * Returns: * The actual number of bytes reserved or zero on error. */ - static size_t reserve( size_t sz ) nothrow /* FIXME pure */ - { - return gc_reserve( sz ); - } + pragma(mangle, "gc_reserve") static size_t reserve(size_t sz) nothrow; /* FIXME pure */ /** @@ -569,16 +644,14 @@ struct GC * collector, if p points to the interior of a memory block, or if this * method is called from a finalizer, no action will be taken. The block * will not be finalized regardless of whether the FINALIZE attribute is - * set. If finalization is desired, use delete instead. + * set. If finalization is desired, call $(REF1 destroy, object) prior to `GC.free`. * * Params: * p = A pointer to the root of a valid memory block or to null. */ - static void free( void* p ) pure nothrow - { - gc_free( p ); - } + pragma(mangle, "gc_free") static void free(void* p) pure nothrow @nogc; +extern(D): /** * Returns the base address of the memory block containing p. This value @@ -595,19 +668,17 @@ struct GC * Returns: * The base address of the memory block referenced by p or null on error. */ - static inout(void)* addrOf( inout(void)* p ) nothrow /* FIXME pure */ + static inout(void)* addrOf( inout(void)* p ) nothrow @nogc pure @trusted { return cast(inout(void)*)gc_addrOf(cast(void*)p); } - /// ditto - static void* addrOf(void* p) pure nothrow + static void* addrOf(void* p) pure nothrow @nogc @trusted { return gc_addrOf(p); } - /** * Returns the true size of the memory block referenced by p. This value * represents the maximum number of bytes for which a call to realloc may @@ -621,14 +692,14 @@ struct GC * Returns: * The size in bytes of the memory block referenced by p or zero on error. */ - static size_t sizeOf( in void* p ) nothrow + static size_t sizeOf( const scope void* p ) nothrow @nogc /* FIXME pure */ { return gc_sizeOf(cast(void*)p); } /// ditto - static size_t sizeOf(void* p) pure nothrow + static size_t sizeOf(void* p) pure nothrow @nogc { return gc_sizeOf( p ); } @@ -659,14 +730,14 @@ struct GC * Information regarding the memory block referenced by p or BlkInfo.init * on error. */ - static BlkInfo query( in void* p ) nothrow + static BlkInfo query(return scope const void* p) nothrow { return gc_query(cast(void*)p); } /// ditto - static BlkInfo query(void* p) pure nothrow + static BlkInfo query(return scope void* p) pure nothrow { return gc_query( p ); } @@ -681,6 +752,17 @@ struct GC } /** + * Returns runtime profile stats for currently active GC implementation + * See `core.memory.GC.ProfileStats` for list of available metrics. + */ + static ProfileStats profileStats() nothrow @nogc @safe + { + return gc_profileStats(); + } + +extern(C): + + /** * Adds an internal root pointing to the GC memory block referenced by p. * As a result, the block referenced by p itself and any blocks accessible * via it will be considered live until the root is removed again. @@ -725,10 +807,7 @@ struct GC * } * --- */ - static void addRoot( in void* p ) nothrow @nogc /* FIXME pure */ - { - gc_addRoot( p ); - } + pragma(mangle, "gc_addRoot") static void addRoot(const void* p) nothrow @nogc; /* FIXME pure */ /** @@ -739,10 +818,7 @@ struct GC * Params: * p = A pointer into a GC-managed memory block or null. */ - static void removeRoot( in void* p ) nothrow @nogc /* FIXME pure */ - { - gc_removeRoot( p ); - } + pragma(mangle, "gc_removeRoot") static void removeRoot(const void* p) nothrow @nogc; /* FIXME pure */ /** @@ -773,10 +849,7 @@ struct GC * // rawMemory will be recognized on collection. * --- */ - static void addRange( in void* p, size_t sz, const TypeInfo ti = null ) @nogc nothrow /* FIXME pure */ - { - gc_addRange( p, sz, ti ); - } + pragma(mangle, "gc_addRange") static void addRange(const void* p, size_t sz, const TypeInfo ti = null) @nogc nothrow; /* FIXME pure */ /** @@ -788,10 +861,7 @@ struct GC * Params: * p = A pointer to a valid memory address or to null. */ - static void removeRange( in void* p ) nothrow @nogc /* FIXME pure */ - { - gc_removeRange( p ); - } + pragma(mangle, "gc_removeRange") static void removeRange(const void* p) nothrow @nogc; /* FIXME pure */ /** @@ -804,9 +874,133 @@ struct GC * Params: * segment = address range of a code segment. */ - static void runFinalizers( in void[] segment ) + pragma(mangle, "gc_runFinalizers") static void runFinalizers(const scope void[] segment); + + /** + * Queries the GC whether the current thread is running object finalization + * as part of a GC collection, or an explicit call to runFinalizers. + * + * As some GC implementations (such as the current conservative one) don't + * support GC memory allocation during object finalization, this function + * can be used to guard against such programming errors. + * + * Returns: + * true if the current thread is in a finalizer, a destructor invoked by + * the GC. + */ + pragma(mangle, "gc_inFinalizer") static bool inFinalizer() nothrow @nogc @safe; + + /// + @safe nothrow @nogc unittest { - gc_runFinalizers( segment ); + // Only code called from a destructor is executed during finalization. + assert(!GC.inFinalizer); + } + + /// + unittest + { + enum Outcome + { + notCalled, + calledManually, + calledFromDruntime + } + + static class Resource + { + static Outcome outcome; + + this() + { + outcome = Outcome.notCalled; + } + + ~this() + { + if (GC.inFinalizer) + { + outcome = Outcome.calledFromDruntime; + + import core.exception : InvalidMemoryOperationError; + try + { + /* + * Presently, allocating GC memory during finalization + * is forbidden and leads to + * `InvalidMemoryOperationError` being thrown. + * + * `GC.inFinalizer` can be used to guard against + * programming erros such as these and is also a more + * efficient way to verify whether a destructor was + * invoked by the GC. + */ + cast(void) GC.malloc(1); + assert(false); + } + catch (InvalidMemoryOperationError e) + { + return; + } + assert(false); + } + else + outcome = Outcome.calledManually; + } + } + + static void createGarbage() + { + auto r = new Resource; + r = null; + } + + assert(Resource.outcome == Outcome.notCalled); + createGarbage(); + GC.collect; + assert( + Resource.outcome == Outcome.notCalled || + Resource.outcome == Outcome.calledFromDruntime); + + auto r = new Resource; + GC.runFinalizers((cast(const void*)typeid(Resource).destructor)[0..1]); + assert(Resource.outcome == Outcome.calledFromDruntime); + Resource.outcome = Outcome.notCalled; + + debug(MEMSTOMP) {} else + { + // assume Resource data is still available + r.destroy; + assert(Resource.outcome == Outcome.notCalled); + } + + r = new Resource; + assert(Resource.outcome == Outcome.notCalled); + r.destroy; + assert(Resource.outcome == Outcome.calledManually); + } + + /** + * Returns the number of bytes allocated for the current thread + * since program start. It is the same as + * GC.stats().allocatedInCurrentThread, but faster. + */ + pragma(mangle, "gc_allocatedInCurrentThread") static ulong allocatedInCurrentThread() nothrow; + + /// Using allocatedInCurrentThread + nothrow unittest + { + ulong currentlyAllocated = GC.allocatedInCurrentThread(); + struct DataStruct + { + long l1; + long l2; + long l3; + long l4; + } + DataStruct* unused = new DataStruct; + assert(GC.allocatedInCurrentThread() == currentlyAllocated + 32); + assert(GC.stats().allocatedInCurrentThread == currentlyAllocated + 32); } } @@ -814,6 +1008,7 @@ struct GC * Pure variants of C's memory allocation functions `malloc`, `calloc`, and * `realloc` and deallocation function `free`. * + * UNIX 98 requires that errno be set to ENOMEM upon failure. * Purity is achieved by saving and restoring the value of `errno`, thus * behaving as if it were never changed. * @@ -821,44 +1016,44 @@ struct GC * $(LINK2 https://dlang.org/spec/function.html#pure-functions, D's rules for purity), * which allow for memory allocation under specific circumstances. */ -void* pureMalloc(size_t size) @trusted pure @nogc nothrow +void* pureMalloc()(size_t size) @trusted pure @nogc nothrow { - const errno = fakePureGetErrno(); + const errnosave = fakePureErrno; void* ret = fakePureMalloc(size); - if (!ret || errno != 0) - { - cast(void)fakePureSetErrno(errno); - } + fakePureErrno = errnosave; return ret; } /// ditto -void* pureCalloc(size_t nmemb, size_t size) @trusted pure @nogc nothrow +void* pureCalloc()(size_t nmemb, size_t size) @trusted pure @nogc nothrow { - const errno = fakePureGetErrno(); + const errnosave = fakePureErrno; void* ret = fakePureCalloc(nmemb, size); - if (!ret || errno != 0) - { - cast(void)fakePureSetErrno(errno); - } + fakePureErrno = errnosave; return ret; } /// ditto -void* pureRealloc(void* ptr, size_t size) @system pure @nogc nothrow +void* pureRealloc()(void* ptr, size_t size) @system pure @nogc nothrow { - const errno = fakePureGetErrno(); + const errnosave = fakePureErrno; void* ret = fakePureRealloc(ptr, size); - if (!ret || errno != 0) - { - cast(void)fakePureSetErrno(errno); - } + fakePureErrno = errnosave; return ret; } + /// ditto -void pureFree(void* ptr) @system pure @nogc nothrow +void pureFree()(void* ptr) @system pure @nogc nothrow { - const errno = fakePureGetErrno(); - fakePureFree(ptr); - cast(void)fakePureSetErrno(errno); + version (Posix) + { + // POSIX free doesn't set errno + fakePureFree(ptr); + } + else + { + const errnosave = fakePureErrno; + fakePureFree(ptr); + fakePureErrno = errnosave; + } } /// @@ -881,40 +1076,451 @@ void pureFree(void* ptr) @system pure @nogc nothrow @system pure nothrow @nogc unittest { - const int errno = fakePureGetErrno(); + const int errno = fakePureErrno(); void* x = pureMalloc(10); // normal allocation - assert(errno == fakePureGetErrno()); // errno shouldn't change + assert(errno == fakePureErrno()); // errno shouldn't change assert(x !is null); // allocation should succeed x = pureRealloc(x, 10); // normal reallocation - assert(errno == fakePureGetErrno()); // errno shouldn't change + assert(errno == fakePureErrno()); // errno shouldn't change assert(x !is null); // allocation should succeed fakePureFree(x); void* y = pureCalloc(10, 1); // normal zeroed allocation - assert(errno == fakePureGetErrno()); // errno shouldn't change + assert(errno == fakePureErrno()); // errno shouldn't change assert(y !is null); // allocation should succeed fakePureFree(y); - // subtract 2 because snn.lib adds 2 unconditionally before passing - // the size to the Windows API - void* z = pureMalloc(size_t.max - 2); // won't affect `errno` - assert(errno == fakePureGetErrno()); // errno shouldn't change + // Workaround bug in glibc 2.26 + // See also: https://issues.dlang.org/show_bug.cgi?id=17956 + void* z = pureMalloc(size_t.max & ~255); // won't affect `errno` + assert(errno == fakePureErrno()); // errno shouldn't change assert(z is null); } // locally purified for internal use here only -extern (C) private pure @system @nogc nothrow + +static import core.stdc.errno; +static if (__traits(getOverloads, core.stdc.errno, "errno").length == 1 + && __traits(getLinkage, core.stdc.errno.errno) == "C") { - pragma(mangle, "getErrno") int fakePureGetErrno(); - pragma(mangle, "setErrno") int fakePureSetErrno(int); + extern(C) pragma(mangle, __traits(identifier, core.stdc.errno.errno)) + private ref int fakePureErrno() @nogc nothrow pure @system; +} +else +{ + extern(C) private @nogc nothrow pure @system + { + pragma(mangle, __traits(identifier, core.stdc.errno.getErrno)) + @property int fakePureErrno(); + pragma(mangle, __traits(identifier, core.stdc.errno.setErrno)) + @property int fakePureErrno(int); + } +} + +version (D_BetterC) {} +else // TODO: remove this function after Phobos no longer needs it. +extern (C) private @system @nogc nothrow +{ + ref int fakePureErrnoImpl() + { + import core.stdc.errno; + return errno(); + } +} + +extern (C) private pure @system @nogc nothrow +{ pragma(mangle, "malloc") void* fakePureMalloc(size_t); pragma(mangle, "calloc") void* fakePureCalloc(size_t nmemb, size_t size); pragma(mangle, "realloc") void* fakePureRealloc(void* ptr, size_t size); pragma(mangle, "free") void fakePureFree(void* ptr); } + +/** +Destroys and then deallocates an object. + +In detail, `__delete(x)` returns with no effect if `x` is `null`. Otherwise, it +performs the following actions in sequence: +$(UL + $(LI + Calls the destructor `~this()` for the object referred to by `x` + (if `x` is a class or interface reference) or + for the object pointed to by `x` (if `x` is a pointer to a `struct`). + Arrays of structs call the destructor, if defined, for each element in the array. + If no destructor is defined, this step has no effect. + ) + $(LI + Frees the memory allocated for `x`. If `x` is a reference to a class + or interface, the memory allocated for the underlying instance is freed. If `x` is + a pointer, the memory allocated for the pointed-to object is freed. If `x` is a + built-in array, the memory allocated for the array is freed. + If `x` does not refer to memory previously allocated with `new` (or the lower-level + equivalents in the GC API), the behavior is undefined. + ) + $(LI + Lastly, `x` is set to `null`. Any attempt to read or write the freed memory via + other references will result in undefined behavior. + ) +) + +Note: Users should prefer $(REF1 destroy, object) to explicitly finalize objects, +and only resort to $(REF __delete, core,memory) when $(REF destroy, object) +wouldn't be a feasible option. + +Params: + x = aggregate object that should be destroyed + +See_Also: $(REF1 destroy, object), $(REF free, core,GC) + +History: + +The `delete` keyword allowed to free GC-allocated memory. +As this is inherently not `@safe`, it has been deprecated. +This function has been added to provide an easy transition from `delete`. +It performs the same functionality as the former `delete` keyword. +*/ +void __delete(T)(ref T x) @system +{ + static void _destructRecurse(S)(ref S s) + if (is(S == struct)) + { + static if (__traits(hasMember, S, "__xdtor") && + // Bugzilla 14746: Check that it's the exact member of S. + __traits(isSame, S, __traits(parent, s.__xdtor))) + s.__xdtor(); + } + + // See also: https://github.com/dlang/dmd/blob/v2.078.0/src/dmd/e2ir.d#L3886 + static if (is(T == interface)) + { + .object.destroy(x); + } + else static if (is(T == class)) + { + .object.destroy(x); + } + else static if (is(T == U*, U)) + { + static if (is(U == struct)) + _destructRecurse(*x); + } + else static if (is(T : E[], E)) + { + static if (is(E == struct)) + { + foreach_reverse (ref e; x) + _destructRecurse(e); + } + } + else + { + static assert(0, "It is not possible to delete: `" ~ T.stringof ~ "`"); + } + + static if (is(T == interface) || + is(T == class) || + is(T == U2*, U2)) + { + GC.free(GC.addrOf(cast(void*) x)); + x = null; + } + else static if (is(T : E2[], E2)) + { + GC.free(GC.addrOf(cast(void*) x.ptr)); + x = null; + } +} + +/// Deleting classes +unittest +{ + bool dtorCalled; + class B + { + int test; + ~this() + { + dtorCalled = true; + } + } + B b = new B(); + B a = b; + b.test = 10; + + assert(GC.addrOf(cast(void*) b) != null); + __delete(b); + assert(b is null); + assert(dtorCalled); + assert(GC.addrOf(cast(void*) b) == null); + // but be careful, a still points to it + assert(a !is null); + assert(GC.addrOf(cast(void*) a) == null); // but not a valid GC pointer +} + +/// Deleting interfaces +unittest +{ + bool dtorCalled; + interface A + { + int quack(); + } + class B : A + { + int a; + int quack() + { + a++; + return a; + } + ~this() + { + dtorCalled = true; + } + } + A a = new B(); + a.quack(); + + assert(GC.addrOf(cast(void*) a) != null); + __delete(a); + assert(a is null); + assert(dtorCalled); + assert(GC.addrOf(cast(void*) a) == null); +} + +/// Deleting structs +unittest +{ + bool dtorCalled; + struct A + { + string test; + ~this() + { + dtorCalled = true; + } + } + auto a = new A("foo"); + + assert(GC.addrOf(cast(void*) a) != null); + __delete(a); + assert(a is null); + assert(dtorCalled); + assert(GC.addrOf(cast(void*) a) == null); +} + +/// Deleting arrays +unittest +{ + int[] a = [1, 2, 3]; + auto b = a; + + assert(GC.addrOf(b.ptr) != null); + __delete(b); + assert(b is null); + assert(GC.addrOf(b.ptr) == null); + // but be careful, a still points to it + assert(a !is null); + assert(GC.addrOf(a.ptr) == null); // but not a valid GC pointer +} + +/// Deleting arrays of structs +unittest +{ + int dtorCalled; + struct A + { + int a; + ~this() + { + assert(dtorCalled == a); + dtorCalled++; + } + } + auto arr = [A(1), A(2), A(3)]; + arr[0].a = 2; + arr[1].a = 1; + arr[2].a = 0; + + assert(GC.addrOf(arr.ptr) != null); + __delete(arr); + assert(dtorCalled == 3); + assert(GC.addrOf(arr.ptr) == null); +} + +// Deleting raw memory +unittest +{ + import core.memory : GC; + auto a = GC.malloc(5); + assert(GC.addrOf(cast(void*) a) != null); + __delete(a); + assert(a is null); + assert(GC.addrOf(cast(void*) a) == null); +} + +// __delete returns with no effect if x is null +unittest +{ + Object x = null; + __delete(x); + + struct S { ~this() { } } + class C { } + interface I { } + + int[] a; __delete(a); + S[] as; __delete(as); + C c; __delete(c); + I i; __delete(i); + C* pc = &c; __delete(*pc); + I* pi = &i; __delete(*pi); + int* pint; __delete(pint); + S* ps; __delete(ps); +} + +// https://issues.dlang.org/show_bug.cgi?id=19092 +unittest +{ + const(int)[] x = [1, 2, 3]; + assert(GC.addrOf(x.ptr) != null); + __delete(x); + assert(x is null); + assert(GC.addrOf(x.ptr) == null); + + immutable(int)[] y = [1, 2, 3]; + assert(GC.addrOf(y.ptr) != null); + __delete(y); + assert(y is null); + assert(GC.addrOf(y.ptr) == null); +} + +// test realloc behaviour +unittest +{ + static void set(int* p, size_t size) + { + foreach (i; 0 .. size) + *p++ = cast(int) i; + } + static void verify(int* p, size_t size) + { + foreach (i; 0 .. size) + assert(*p++ == i); + } + static void test(size_t memsize) + { + int* p = cast(int*) GC.malloc(memsize * int.sizeof); + assert(p); + set(p, memsize); + verify(p, memsize); + + int* q = cast(int*) GC.realloc(p + 4, 2 * memsize * int.sizeof); + assert(q == null); + + q = cast(int*) GC.realloc(p + memsize / 2, 2 * memsize * int.sizeof); + assert(q == null); + + q = cast(int*) GC.realloc(p + memsize - 1, 2 * memsize * int.sizeof); + assert(q == null); + + int* r = cast(int*) GC.realloc(p, 5 * memsize * int.sizeof); + verify(r, memsize); + set(r, 5 * memsize); + + int* s = cast(int*) GC.realloc(r, 2 * memsize * int.sizeof); + verify(s, 2 * memsize); + + assert(GC.realloc(s, 0) == null); // free + assert(GC.addrOf(p) == null); + } + + test(16); + test(200); + test(800); // spans large and small pools + test(1200); + test(8000); + + void* p = GC.malloc(100); + assert(GC.realloc(&p, 50) == null); // non-GC pointer +} + +// test GC.profileStats +unittest +{ + auto stats = GC.profileStats(); + GC.collect(); + auto nstats = GC.profileStats(); + assert(nstats.numCollections > stats.numCollections); +} + +// in rt.lifetime: +private extern (C) void* _d_newitemU(scope const TypeInfo _ti) @system pure nothrow; + +/** +Moves a value to a new GC allocation. + +Params: + value = Value to be moved. If the argument is an lvalue and a struct with a + destructor or postblit, it will be reset to its `.init` value. + +Returns: + A pointer to the new GC-allocated value. +*/ +T* moveToGC(T)(auto ref T value) +{ + static T* doIt(ref T value) @trusted + { + import core.lifetime : moveEmplace; + auto mem = cast(T*) _d_newitemU(typeid(T)); // allocate but don't initialize + moveEmplace(value, *mem); + return mem; + } + + return doIt(value); // T dtor might be @system +} + +/// +@safe pure nothrow unittest +{ + struct S + { + int x; + this(this) @disable; + ~this() @safe pure nothrow @nogc {} + } + + S* p; + + // rvalue + p = moveToGC(S(123)); + assert(p.x == 123); + + // lvalue + auto lval = S(456); + p = moveToGC(lval); + assert(p.x == 456); + assert(lval.x == 0); +} + +// @system dtor +unittest +{ + struct S + { + int x; + ~this() @system {} + } + + // lvalue case is @safe, ref param isn't destructed + static assert(__traits(compiles, (ref S lval) @safe { moveToGC(lval); })); + + // rvalue case is @system, value param is destructed + static assert(!__traits(compiles, () @safe { moveToGC(S(0)); })); +} diff --git a/libphobos/libdruntime/core/runtime.d b/libphobos/libdruntime/core/runtime.d index 5fc9904..bfb72e0 100644 --- a/libphobos/libdruntime/core/runtime.d +++ b/libphobos/libdruntime/core/runtime.d @@ -4,13 +4,8 @@ * Copyright: Copyright Sean Kelly 2005 - 2009. * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Authors: Sean Kelly - * Source: $(DRUNTIMESRC core/_runtime.d) - */ - -/* Copyright Sean Kelly 2005 - 2009. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) + * Source: $(LINK2 https://github.com/dlang/druntime/blob/master/src/core/runtime.d, _runtime.d) + * Documentation: https://dlang.org/phobos/core_runtime.html */ /* NOTE: This file has been patched from the original DMD distribution to @@ -18,8 +13,6 @@ */ module core.runtime; -version (Windows) import core.stdc.wchar_ : wchar_t; - version (OSX) version = Darwin; else version (iOS) @@ -29,10 +22,30 @@ else version (TVOS) else version (WatchOS) version = Darwin; +version (GNU) +{ + import gcc.backtrace; + // This shouldn't be necessary but ensure that code doesn't get mixed + // It does however prevent the unittest SEGV handler to be installed, + // which is desireable as it uses backtrace directly. + private enum hasExecinfo = false; +} +else version (DRuntime_Use_Libunwind) +{ + import core.internal.backtrace.libunwind; + // This shouldn't be necessary but ensure that code doesn't get mixed + // It does however prevent the unittest SEGV handler to be installed, + // which is desireable as it uses backtrace directly. + private enum hasExecinfo = false; +} +else + import core.internal.execinfo; + /// C interface for Runtime.loadLibrary extern (C) void* rt_loadLibrary(const char* name); /// ditto -version (Windows) extern (C) void* rt_loadLibraryW(const wchar_t* name); +version (Windows) extern (C) void* rt_loadLibraryW(const wchar* name); + /// C interface for Runtime.unloadLibrary, returns 1/0 instead of bool extern (C) int rt_unloadLibrary(void* ptr); @@ -41,29 +54,69 @@ extern(C) int rt_init(); /// C interface for Runtime.terminate, returns 1/0 instead of bool extern(C) int rt_term(); +/** + * This type is returned by the module unit test handler to indicate testing + * results. + */ +struct UnitTestResult +{ + /** + * Number of modules which were tested + */ + size_t executed; + + /** + * Number of modules passed the unittests + */ + size_t passed; + + /** + * Should the main function be run or not? This is ignored if any tests + * failed. + */ + bool runMain; + + /** + * Should we print a summary of the results? + */ + bool summarize; + + /** + * Simple check for whether execution should continue after unit tests + * have been run. Works with legacy code that expected a bool return. + * + * Returns: + * true if execution should continue after testing is complete, false if + * not. + */ + bool opCast(T : bool)() const + { + return runMain && (executed == passed); + } + + /// Simple return code that says unit tests pass, and main should be run + enum UnitTestResult pass = UnitTestResult(0, 0, true, false); + /// Simple return code that says unit tests failed. + enum UnitTestResult fail = UnitTestResult(1, 0, false, false); +} + +/// Legacy module unit test handler +alias bool function() ModuleUnitTester; +/// Module unit test handler +alias UnitTestResult function() ExtendedModuleUnitTester; private { - alias bool function() ModuleUnitTester; alias bool function(Object) CollectHandler; alias Throwable.TraceInfo function( void* ptr ) TraceHandler; - extern (C) void rt_setCollectHandler( CollectHandler h ); - extern (C) CollectHandler rt_getCollectHandler(); - - extern (C) void rt_setTraceHandler( TraceHandler h ); - extern (C) TraceHandler rt_getTraceHandler(); - alias void delegate( Throwable ) ExceptionHandler; extern (C) void _d_print_throwable(Throwable t); extern (C) void* thread_stackBottom(); - - extern (C) string[] rt_args(); - extern (C) CArgs rt_cArgs() @nogc; } -static this() +shared static this() { // NOTE: Some module ctors will run before this handler is set, so it's // still possible the app could exit without a stack trace. If @@ -108,13 +161,6 @@ struct Runtime return !!rt_init(); } - deprecated("Please use the overload of Runtime.initialize that takes no argument.") - static bool initialize(ExceptionHandler dg = null) - { - return !!rt_init(); - } - - /** * Terminates the runtime. This call is to be used in instances where the * standard program termination process will not be not executed. This is @@ -129,23 +175,13 @@ struct Runtime return !!rt_term(); } - deprecated("Please use the overload of Runtime.terminate that takes no argument.") - static bool terminate(ExceptionHandler dg = null) - { - return !!rt_term(); - } - - /** * Returns the arguments supplied when the process was started. * * Returns: * The arguments supplied when this process was started. */ - static @property string[] args() - { - return rt_args(); - } + extern(C) pragma(mangle, "rt_args") static @property string[] args(); /** * Returns the unprocessed C arguments supplied when the process was started. @@ -168,10 +204,7 @@ struct Runtime * } * --- */ - static @property CArgs cArgs() @nogc - { - return rt_cArgs(); - } + extern(C) pragma(mangle, "rt_cArgs") static @property CArgs cArgs() @nogc; /** * Locates a dynamic library with the supplied library name and dynamically @@ -184,12 +217,13 @@ struct Runtime * Returns: * A reference to the library or null on error. */ - static void* loadLibrary()(in char[] name) + static void* loadLibrary()(const scope char[] name) { import core.stdc.stdlib : free, malloc; version (Windows) { - import core.sys.windows.windows; + import core.sys.windows.winnls : CP_UTF8, MultiByteToWideChar; + import core.sys.windows.winnt : WCHAR; if (name.length == 0) return null; // Load a DLL at runtime @@ -198,7 +232,7 @@ struct Runtime if (len == 0) return null; - auto buf = cast(wchar_t*)malloc((len+1) * wchar_t.sizeof); + auto buf = cast(WCHAR*)malloc((len+1) * WCHAR.sizeof); if (buf is null) return null; scope (exit) free(buf); @@ -253,10 +287,7 @@ struct Runtime * Params: * h = The new trace handler. Set to null to use the default handler. */ - static @property void traceHandler( TraceHandler h ) - { - rt_setTraceHandler( h ); - } + extern(C) pragma(mangle, "rt_setTraceHandler") static @property void traceHandler(TraceHandler h); /** * Gets the current trace handler. @@ -264,10 +295,7 @@ struct Runtime * Returns: * The current trace handler or null if none has been set. */ - static @property TraceHandler traceHandler() - { - return rt_getTraceHandler(); - } + extern(C) pragma(mangle, "rt_getTraceHandler") static @property TraceHandler traceHandler(); /** * Overrides the default collect hander with a user-supplied version. This @@ -280,10 +308,7 @@ struct Runtime * Params: * h = The new collect handler. Set to null to use the default handler. */ - static @property void collectHandler( CollectHandler h ) - { - rt_setCollectHandler( h ); - } + extern(C) pragma(mangle, "rt_setCollectHandler") static @property void collectHandler( CollectHandler h ); /** @@ -292,10 +317,7 @@ struct Runtime * Returns: * The current collect handler or null if none has been set. */ - static @property CollectHandler collectHandler() - { - return rt_getCollectHandler(); - } + extern(C) pragma(mangle, "rt_getCollectHandler") static @property CollectHandler collectHandler(); /** @@ -304,26 +326,39 @@ struct Runtime * value of this routine indicates to the runtime whether the tests ran * without error. * + * There are two options for handlers. The `bool` version is deprecated but + * will be kept for legacy support. Returning `true` from the handler is + * equivalent to returning `UnitTestResult.pass` from the extended version. + * Returning `false` from the handler is equivalent to returning + * `UnitTestResult.fail` from the extended version. + * + * See the documentation for `UnitTestResult` to see how you should set up + * the return structure. + * + * See the documentation for `runModuleUnitTests` for how the default + * algorithm works, or read the example below. + * * Params: - * h = The new unit tester. Set to null to use the default unit tester. + * h = The new unit tester. Set both to null to use the default unit + * tester. * * Example: * --------- - * version (unittest) shared static this() + * shared static this() * { * import core.runtime; * - * Runtime.moduleUnitTester = &customModuleUnitTester; + * Runtime.extendedModuleUnitTester = &customModuleUnitTester; * } * - * bool customModuleUnitTester() + * UnitTestResult customModuleUnitTester() * { * import std.stdio; * * writeln("Using customModuleUnitTester"); * * // Do the same thing as the default moduleUnitTester: - * size_t failed = 0; + * UnitTestResult result; * foreach (m; ModuleInfo) * { * if (m) @@ -332,45 +367,82 @@ struct Runtime * * if (fp) * { + * ++result.executed; * try * { * fp(); + * ++result.passed; * } * catch (Throwable e) * { * writeln(e); - * failed++; * } * } * } * } - * return failed == 0; + * if (result.executed != result.passed) + * { + * result.runMain = false; // don't run main + * result.summarize = true; // print failure + * } + * else + * { + * result.runMain = true; // all UT passed + * result.summarize = false; // be quiet about it. + * } + * return result; * } * --------- */ + static @property void extendedModuleUnitTester( ExtendedModuleUnitTester h ) + { + sm_extModuleUnitTester = h; + } + + /// Ditto static @property void moduleUnitTester( ModuleUnitTester h ) { sm_moduleUnitTester = h; } - /** - * Gets the current module unit tester. + * Gets the current legacy module unit tester. + * + * This property should not be used, but is supported for legacy purposes. + * + * Note that if the extended unit test handler is set, this handler will + * be ignored. * * Returns: - * The current module unit tester handler or null if none has been set. + * The current legacy module unit tester handler or null if none has been + * set. */ static @property ModuleUnitTester moduleUnitTester() { return sm_moduleUnitTester; } + /** + * Gets the current module unit tester. + * + * This handler overrides any legacy module unit tester set by the + * moduleUnitTester property. + * + * Returns: + * The current module unit tester handler or null if none has been + * set. + */ + static @property ExtendedModuleUnitTester extendedModuleUnitTester() + { + return sm_extModuleUnitTester; + } private: // NOTE: This field will only ever be set in a static ctor and should // never occur within any but the main thread, so it is safe to // make it __gshared. + __gshared ExtendedModuleUnitTester sm_extModuleUnitTester = null; __gshared ModuleUnitTester sm_moduleUnitTester = null; } @@ -444,40 +516,52 @@ extern (C) void profilegc_setlogfilename(string name); /** * This routine is called by the runtime to run module unit tests on startup. - * The user-supplied unit tester will be called if one has been supplied, + * The user-supplied unit tester will be called if one has been set, * otherwise all unit tests will be run in sequence. * + * If the extended unittest handler is registered, this function returns the + * result from that handler directly. + * + * If a legacy boolean returning custom handler is used, `false` maps to + * `UnitTestResult.fail`, and `true` maps to `UnitTestResult.pass`. This was + * the original behavior of the unit testing system. + * + * If no unittest custom handlers are registered, the following algorithm is + * executed (the behavior can be affected by the `--DRT-testmode` switch + * below): + * 1. Execute any unittests present. For each that fails, print the stack + * trace and continue. + * 2. If no unittests were present, set summarize to false, and runMain to + * true. + * 3. Otherwise, set summarize to true, and runMain to false. + * + * See the documentation for `UnitTestResult` for details on how the runtime + * treats the return value from this function. + * + * If the switch `--DRT-testmode` is passed to the executable, it can have + * one of 3 values: + * 1. "run-main": even if unit tests are run (and all pass), runMain is set + to true. + * 2. "test-or-main": any unit tests present will cause the program to + * summarize the results and exit regardless of the result. This is the + * default. + * 3. "test-only", runMain is set to false, even with no tests present. + * + * This command-line parameter does not affect custom unit test handlers. + * * Returns: - * true if execution should continue after testing is complete and false if - * not. Default behavior is to return true. + * A `UnitTestResult` struct indicating the result of running unit tests. */ -extern (C) bool runModuleUnitTests() +extern (C) UnitTestResult runModuleUnitTests() { - // backtrace - version (GNU) - import gcc.backtrace; - version (CRuntime_Glibc) - import core.sys.linux.execinfo; - else version (Darwin) - import core.sys.darwin.execinfo; - else version (FreeBSD) - import core.sys.freebsd.execinfo; - else version (NetBSD) - import core.sys.netbsd.execinfo; - else version (DragonFlyBSD) - import core.sys.dragonflybsd.execinfo; - else version (Windows) + version (Windows) import core.sys.windows.stacktrace; - else version (Solaris) - import core.sys.solaris.execinfo; - else version (CRuntime_UClibc) - import core.sys.linux.execinfo; - static if ( __traits( compiles, new LibBacktrace(0) ) ) + static if (__traits(compiles, new LibBacktrace(0))) { import core.sys.posix.signal; // segv handler - static extern (C) void unittestSegvHandler( int signum, siginfo_t* info, void* ptr ) + static extern (C) void unittestSegvHandler(int signum, siginfo_t* info, void* ptr) { import core.stdc.stdio; fprintf(stderr, "Segmentation fault while running unittests:\n"); @@ -496,18 +580,18 @@ extern (C) bool runModuleUnitTests() sigaction_t oldbus = void; (cast(byte*) &action)[0 .. action.sizeof] = 0; - sigfillset( &action.sa_mask ); // block other signals + sigfillset(&action.sa_mask); // block other signals action.sa_flags = SA_SIGINFO | SA_RESETHAND; action.sa_sigaction = &unittestSegvHandler; - sigaction( SIGSEGV, &action, &oldseg ); - sigaction( SIGBUS, &action, &oldbus ); - scope( exit ) + sigaction(SIGSEGV, &action, &oldseg); + sigaction(SIGBUS, &action, &oldbus); + scope (exit) { - sigaction( SIGSEGV, &oldseg, null ); - sigaction( SIGBUS, &oldbus, null ); + sigaction(SIGSEGV, &oldseg, null); + sigaction(SIGBUS, &oldbus, null); } } - else static if ( __traits( compiles, backtrace ) ) + else static if (hasExecinfo) { import core.sys.posix.signal; // segv handler @@ -537,385 +621,311 @@ extern (C) bool runModuleUnitTests() } } - if ( Runtime.sm_moduleUnitTester is null ) + if (Runtime.sm_extModuleUnitTester !is null) + return Runtime.sm_extModuleUnitTester(); + else if (Runtime.sm_moduleUnitTester !is null) + return Runtime.sm_moduleUnitTester() ? UnitTestResult.pass : UnitTestResult.fail; + UnitTestResult results; + foreach ( m; ModuleInfo ) { - size_t failed = 0; - foreach ( m; ModuleInfo ) + if ( !m ) + continue; + auto fp = m.unitTest; + if ( !fp ) + continue; + + import core.exception; + ++results.executed; + try { - if ( m ) + fp(); + ++results.passed; + } + catch ( Throwable e ) + { + if ( typeid(e) == typeid(AssertError) ) { - auto fp = m.unitTest; - - if ( fp ) + // Crude heuristic to figure whether the assertion originates in + // the unittested module. TODO: improve. + auto moduleName = m.name; + if (moduleName.length && e.file.length > moduleName.length + && e.file[0 .. moduleName.length] == moduleName) { - try - { - fp(); - } - catch ( Throwable e ) - { - _d_print_throwable(e); - failed++; - } + import core.stdc.stdio; + printf("%.*s(%llu): [unittest] %.*s\n", + cast(int) e.file.length, e.file.ptr, cast(ulong) e.line, + cast(int) e.message.length, e.message.ptr); + + // Exception originates in the same module, don't print + // the stack trace. + // TODO: omit stack trace only if assert was thrown + // directly by the unittest. + continue; } } + // TODO: perhaps indent all of this stuff. + _d_print_throwable(e); } - return failed == 0; } - return Runtime.sm_moduleUnitTester(); -} + import core.internal.parseoptions : rt_configOption; -/////////////////////////////////////////////////////////////////////////////// -// Default Implementations -/////////////////////////////////////////////////////////////////////////////// + if (results.passed != results.executed) + { + // by default, we always print a summary if there are failures. + results.summarize = true; + } + else switch (rt_configOption("testmode", null, false)) + { + case "run-main": + results.runMain = true; + break; + case "test-only": + // Never run main, always summarize + results.summarize = true; + break; + case "": + // By default, do not run main if tests are present. + case "test-or-main": + // only run main if there were no tests. Only summarize if we are not + // running main. + results.runMain = (results.executed == 0); + results.summarize = !results.runMain; + break; + default: + assert(0, "Unknown --DRT-testmode option: " ~ rt_configOption("testmode", null, false)); + } + return results; +} /** + * Get the default `Throwable.TraceInfo` implementation for the platform + * + * This functions returns a trace handler, allowing to inspect the + * current stack trace. + * + * Params: + * ptr = (Windows only) The context to get the stack trace from. + * When `null` (the default), start from the current frame. * + * Returns: + * A `Throwable.TraceInfo` implementation suitable to iterate over the stack, + * or `null`. If called from a finalizer (destructor), always returns `null` + * as trace handlers allocate. */ Throwable.TraceInfo defaultTraceHandler( void* ptr = null ) { - // backtrace - version (GNU) - import gcc.backtrace; - version (CRuntime_Glibc) - import core.sys.linux.execinfo; - else version (Darwin) - import core.sys.darwin.execinfo; - else version (FreeBSD) - import core.sys.freebsd.execinfo; - else version (NetBSD) - import core.sys.netbsd.execinfo; - else version (DragonFlyBSD) - import core.sys.dragonflybsd.execinfo; - else version (Windows) - import core.sys.windows.stacktrace; - else version (Solaris) - import core.sys.solaris.execinfo; - else version (CRuntime_UClibc) - import core.sys.linux.execinfo; - // avoid recursive GC calls in finalizer, trace handlers should be made @nogc instead - import core.memory : gc_inFinalizer; - if (gc_inFinalizer) + import core.memory : GC; + if (GC.inFinalizer) return null; - //printf("runtime.defaultTraceHandler()\n"); - static if ( __traits( compiles, new LibBacktrace(0) ) ) + static if (__traits(compiles, new LibBacktrace(0))) { version (Posix) - { static enum FIRSTFRAME = 4; - } else version (Win64) - { static enum FIRSTFRAME = 4; - } else - { static enum FIRSTFRAME = 0; - } return new LibBacktrace(FIRSTFRAME); } - else static if ( __traits( compiles, new UnwindBacktrace(0) ) ) + else static if (__traits(compiles, new UnwindBacktrace(0))) { version (Posix) - { static enum FIRSTFRAME = 5; - } else version (Win64) - { static enum FIRSTFRAME = 4; - } else - { static enum FIRSTFRAME = 0; - } return new UnwindBacktrace(FIRSTFRAME); } - else static if ( __traits( compiles, backtrace ) ) + else version (Windows) { - import core.demangle; - import core.stdc.stdlib : free; - import core.stdc.string : strlen, memchr, memmove; - - class DefaultTraceInfo : Throwable.TraceInfo + import core.sys.windows.stacktrace; + static if (__traits(compiles, new StackTrace(0, null))) { - this() - { - numframes = 0; //backtrace( callstack, MAXFRAMES ); - if (numframes < 2) // backtrace() failed, do it ourselves - { - static void** getBasePtr() - { - version (D_InlineAsm_X86) - asm { naked; mov EAX, EBP; ret; } - else - version (D_InlineAsm_X86_64) - asm { naked; mov RAX, RBP; ret; } - else - return null; - } - - auto stackTop = getBasePtr(); - auto stackBottom = cast(void**) thread_stackBottom(); - void* dummy; - - if ( stackTop && &dummy < stackTop && stackTop < stackBottom ) - { - auto stackPtr = stackTop; - - for ( numframes = 0; stackTop <= stackPtr && - stackPtr < stackBottom && - numframes < MAXFRAMES; ) - { - enum CALL_INSTRUCTION_SIZE = 1; // it may not be 1 but it is good enough to get - // in CALL instruction address range for backtrace - callstack[numframes++] = *(stackPtr + 1) - CALL_INSTRUCTION_SIZE; - stackPtr = cast(void**) *stackPtr; - } - } - } - } - - override int opApply( scope int delegate(ref const(char[])) dg ) const - { - return opApply( (ref size_t, ref const(char[]) buf) - { - return dg( buf ); - } ); - } + import core.sys.windows.winnt : CONTEXT; + version (Win64) + enum FIRSTFRAME = 4; + else version (Win32) + enum FIRSTFRAME = 0; + return new StackTrace(FIRSTFRAME, cast(CONTEXT*)ptr); + } + else + return null; + } + else static if (__traits(compiles, new DefaultTraceInfo())) + return new DefaultTraceInfo(); + else + return null; +} - override int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const - { - version (Posix) - { - // NOTE: The first 4 frames with the current implementation are - // inside core.runtime and the object code, so eliminate - // these for readability. The alternative would be to - // exclude the first N frames that are in a list of - // mangled function names. - enum FIRSTFRAME = 4; - } - else version (Windows) - { - // NOTE: On Windows, the number of frames to exclude is based on - // whether the exception is user or system-generated, so - // it may be necessary to exclude a list of function names - // instead. - enum FIRSTFRAME = 0; - } +/// Example of a simple program printing its stack trace +unittest +{ + import core.runtime; + import core.stdc.stdio; - version (linux) enum enableDwarf = true; - else version (FreeBSD) enum enableDwarf = true; - else version (DragonFlyBSD) enum enableDwarf = true; - else version (Darwin) enum enableDwarf = true; - else enum enableDwarf = false; + void main() + { + auto trace = defaultTraceHandler(null); + foreach (line; trace) + { + printf("%.*s\n", cast(int)line.length, line.ptr); + } + } +} - static if (enableDwarf) - { - import core.internal.traits : externDFunc; - - alias traceHandlerOpApplyImpl = externDFunc!( - "rt.backtrace.dwarf.traceHandlerOpApplyImpl", - int function(const void*[], scope int delegate(ref size_t, ref const(char[]))) - ); - - if (numframes >= FIRSTFRAME) - { - return traceHandlerOpApplyImpl( - callstack[FIRSTFRAME .. numframes], - dg - ); - } - else - { - return 0; - } - } - else - { - const framelist = backtrace_symbols( callstack.ptr, numframes ); - scope(exit) free(cast(void*) framelist); - - int ret = 0; - for ( int i = FIRSTFRAME; i < numframes; ++i ) - { - char[4096] fixbuf; - auto buf = framelist[i][0 .. strlen(framelist[i])]; - auto pos = cast(size_t)(i - FIRSTFRAME); - buf = fixline( buf, fixbuf ); - ret = dg( pos, buf ); - if ( ret ) - break; - } - return ret; - } +version (DRuntime_Use_Libunwind) +{ + import core.internal.backtrace.handler; - } + alias DefaultTraceInfo = LibunwindHandler; +} +/// Default implementation for most POSIX systems +else static if (hasExecinfo) private class DefaultTraceInfo : Throwable.TraceInfo +{ + import core.demangle; + import core.stdc.stdlib : free; + import core.stdc.string : strlen, memchr, memmove; - override string toString() const + this() + { + // it may not be 1 but it is good enough to get + // in CALL instruction address range for backtrace + enum CALL_INSTRUCTION_SIZE = 1; + + static if (__traits(compiles, backtrace((void**).init, int.init))) + numframes = backtrace(this.callstack.ptr, MAXFRAMES); + // Backtrace succeeded, adjust the frame to point to the caller + if (numframes >= 2) + foreach (ref elem; this.callstack) + elem -= CALL_INSTRUCTION_SIZE; + else // backtrace() failed, do it ourselves + { + static void** getBasePtr() { - string buf; - foreach ( i, line; this ) - buf ~= i ? "\n" ~ line : line; - return buf; + version (D_InlineAsm_X86) + asm { naked; mov EAX, EBP; ret; } + else + version (D_InlineAsm_X86_64) + asm { naked; mov RAX, RBP; ret; } + else + return null; } - private: - int numframes; - static enum MAXFRAMES = 128; - void*[MAXFRAMES] callstack = void; + auto stackTop = getBasePtr(); + auto stackBottom = cast(void**) thread_stackBottom(); + void* dummy; - private: - const(char)[] fixline( const(char)[] buf, return ref char[4096] fixbuf ) const + if ( stackTop && &dummy < stackTop && stackTop < stackBottom ) { - size_t symBeg, symEnd; - version (Darwin) - { - // format is: - // 1 module 0x00000000 D6module4funcAFZv + 0 - for ( size_t i = 0, n = 0; i < buf.length; i++ ) - { - if ( ' ' == buf[i] ) - { - n++; - while ( i < buf.length && ' ' == buf[i] ) - i++; - if ( 3 > n ) - continue; - symBeg = i; - while ( i < buf.length && ' ' != buf[i] ) - i++; - symEnd = i; - break; - } - } - } - else version (CRuntime_Glibc) - { - // format is: module(_D6module4funcAFZv) [0x00000000] - // or: module(_D6module4funcAFZv+0x78) [0x00000000] - auto bptr = cast(char*) memchr( buf.ptr, '(', buf.length ); - auto eptr = cast(char*) memchr( buf.ptr, ')', buf.length ); - auto pptr = cast(char*) memchr( buf.ptr, '+', buf.length ); - - if (pptr && pptr < eptr) - eptr = pptr; - - if ( bptr++ && eptr ) - { - symBeg = bptr - buf.ptr; - symEnd = eptr - buf.ptr; - } - } - else version (FreeBSD) - { - // format is: 0x00000000 <_D6module4funcAFZv+0x78> at module - auto bptr = cast(char*) memchr( buf.ptr, '<', buf.length ); - auto eptr = cast(char*) memchr( buf.ptr, '+', buf.length ); - - if ( bptr++ && eptr ) - { - symBeg = bptr - buf.ptr; - symEnd = eptr - buf.ptr; - } - } - else version (NetBSD) - { - // format is: 0x00000000 <_D6module4funcAFZv+0x78> at module - auto bptr = cast(char*) memchr( buf.ptr, '<', buf.length ); - auto eptr = cast(char*) memchr( buf.ptr, '+', buf.length ); - - if ( bptr++ && eptr ) - { - symBeg = bptr - buf.ptr; - symEnd = eptr - buf.ptr; - } - } - else version (DragonFlyBSD) - { - // format is: 0x00000000 <_D6module4funcAFZv+0x78> at module - auto bptr = cast(char*) memchr( buf.ptr, '<', buf.length ); - auto eptr = cast(char*) memchr( buf.ptr, '+', buf.length ); - - if ( bptr++ && eptr ) - { - symBeg = bptr - buf.ptr; - symEnd = eptr - buf.ptr; - } - } - else version (Solaris) - { - // format is object'symbol+offset [pc] - auto bptr = cast(char*) memchr( buf.ptr, '\'', buf.length ); - auto eptr = cast(char*) memchr( buf.ptr, '+', buf.length ); - - if ( bptr++ && eptr ) - { - symBeg = bptr - buf.ptr; - symEnd = eptr - buf.ptr; - } - } - else - { - // fallthrough - } + auto stackPtr = stackTop; - assert(symBeg < buf.length && symEnd < buf.length); - assert(symBeg <= symEnd); - - enum min = (size_t a, size_t b) => a <= b ? a : b; - if (symBeg == symEnd || symBeg >= fixbuf.length) + for ( numframes = 0; stackTop <= stackPtr && + stackPtr < stackBottom && + numframes < MAXFRAMES; ) { - immutable len = min(buf.length, fixbuf.length); - fixbuf[0 .. len] = buf[0 .. len]; - return fixbuf[0 .. len]; - } - else - { - fixbuf[0 .. symBeg] = buf[0 .. symBeg]; - - auto sym = demangle(buf[symBeg .. symEnd], fixbuf[symBeg .. $]); - - if (sym.ptr !is fixbuf.ptr + symBeg) - { - // demangle reallocated the buffer, copy the symbol to fixbuf - immutable len = min(fixbuf.length - symBeg, sym.length); - memmove(fixbuf.ptr + symBeg, sym.ptr, len); - if (symBeg + len == fixbuf.length) - return fixbuf[]; - } - - immutable pos = symBeg + sym.length; - assert(pos < fixbuf.length); - immutable tail = buf.length - symEnd; - immutable len = min(fixbuf.length - pos, tail); - fixbuf[pos .. pos + len] = buf[symEnd .. symEnd + len]; - return fixbuf[0 .. pos + len]; + callstack[numframes++] = *(stackPtr + 1) - CALL_INSTRUCTION_SIZE; + stackPtr = cast(void**) *stackPtr; } } } + } - return new DefaultTraceInfo; + override int opApply( scope int delegate(ref const(char[])) dg ) const + { + return opApply( (ref size_t, ref const(char[]) buf) + { + return dg( buf ); + } ); } - else static if ( __traits( compiles, new StackTrace(0, null) ) ) + + override int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const { - version (Win64) + version (linux) enum enableDwarf = true; + else version (FreeBSD) enum enableDwarf = true; + else version (DragonFlyBSD) enum enableDwarf = true; + else version (Darwin) enum enableDwarf = true; + else enum enableDwarf = false; + + const framelist = backtrace_symbols( callstack.ptr, numframes ); + scope(exit) free(cast(void*) framelist); + + static if (enableDwarf) { - static enum FIRSTFRAME = 4; + import core.internal.backtrace.dwarf; + return traceHandlerOpApplyImpl(numframes, + i => callstack[i], + (i) { auto str = framelist[i][0 .. strlen(framelist[i])]; return getMangledSymbolName(str); }, + dg); } - else version (Win32) + else { - static enum FIRSTFRAME = 0; + int ret = 0; + for (size_t pos = 0; pos < numframes; ++pos) + { + char[4096] fixbuf = void; + auto buf = framelist[pos][0 .. strlen(framelist[pos])]; + buf = fixline( buf, fixbuf ); + ret = dg( pos, buf ); + if ( ret ) + break; + } + return ret; } - import core.sys.windows.winnt : CONTEXT; - auto s = new StackTrace(FIRSTFRAME, cast(CONTEXT*)ptr); - return s; } - else + + override string toString() const { - return null; + string buf; + foreach ( i, line; this ) + buf ~= i ? "\n" ~ line : line; + return buf; + } + +private: + int numframes; + static enum MAXFRAMES = 128; + void*[MAXFRAMES] callstack = void; + +private: + const(char)[] fixline( const(char)[] buf, return ref char[4096] fixbuf ) const + { + size_t symBeg, symEnd; + + getMangledSymbolName(buf, symBeg, symEnd); + + enum min = (size_t a, size_t b) => a <= b ? a : b; + if (symBeg == symEnd || symBeg >= fixbuf.length) + { + immutable len = min(buf.length, fixbuf.length); + fixbuf[0 .. len] = buf[0 .. len]; + return fixbuf[0 .. len]; + } + else + { + fixbuf[0 .. symBeg] = buf[0 .. symBeg]; + + auto sym = demangle(buf[symBeg .. symEnd], fixbuf[symBeg .. $]); + + if (sym.ptr !is fixbuf.ptr + symBeg) + { + // demangle reallocated the buffer, copy the symbol to fixbuf + immutable len = min(fixbuf.length - symBeg, sym.length); + memmove(fixbuf.ptr + symBeg, sym.ptr, len); + if (symBeg + len == fixbuf.length) + return fixbuf[]; + } + + immutable pos = symBeg + sym.length; + assert(pos < fixbuf.length); + immutable tail = buf.length - symEnd; + immutable len = min(fixbuf.length - pos, tail); + fixbuf[pos .. pos + len] = buf[symEnd .. symEnd + len]; + return fixbuf[0 .. pos + len]; + } } } diff --git a/libphobos/libdruntime/core/stdc/math.d b/libphobos/libdruntime/core/stdc/math.d index 2de6e57..de029c4 100644 --- a/libphobos/libdruntime/core/stdc/math.d +++ b/libphobos/libdruntime/core/stdc/math.d @@ -315,17 +315,17 @@ version (CRuntime_DigitalMars) pure uint __fpclassify_d(double x); pure uint __fpclassify_ld(real x); - extern (D) - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassify_f") pure int fpclassify(float x); + pragma(mangle, "__fpclassify_f") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassify_d") pure int fpclassify(double x); + pragma(mangle, "__fpclassify_d") pure int fpclassify(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify_d" : "__fpclassify_ld") + pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify_d" : "__fpclassify_ld") pure int fpclassify(real x); + extern (D) + { //int isfinite(real-floating x); /// pure int isfinite(float x) { return fpclassify(x) >= FP_NORMAL; } @@ -452,17 +452,17 @@ else version (CRuntime_Microsoft) // fully supported since MSVCRT 12 (VS 2013) o pure int __signbit(double x); pure int __signbitl(real x); + //int fpclassify(real-floating x); + /// + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + /// + pragma(mangle, "__fpclassify") pure int fpclassify(double x); + /// + pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") + pure int fpclassify(real x); + extern (D) { - //int fpclassify(real-floating x); - /// - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); - /// - extern(C) pragma(mangle, "__fpclassify") pure int fpclassify(double x); - /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") - pure int fpclassify(real x); - //int isfinite(real-floating x); /// pure int isfinite(float x) { return (fpclassify(x) & FP_NORMAL) == 0; } @@ -478,16 +478,19 @@ else version (CRuntime_Microsoft) // fully supported since MSVCRT 12 (VS 2013) o pure int isinf(double x) { return fpclassify(x) == FP_INFINITE; } /// pure int isinf(real x) { return fpclassify(x) == FP_INFINITE; } + } - //int isnan(real-floating x); - /// - extern(C) pragma(mangle, "__isnanf") pure int isnan(float x); - /// - extern(C) pragma(mangle, "__isnan") pure int isnan(double x); - /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") - pure int isnan(real x); + //int isnan(real-floating x); + /// + pragma(mangle, "__isnanf") pure int isnan(float x); + /// + pragma(mangle, "__isnan") pure int isnan(double x); + /// + pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") + pure int isnan(real x); + extern (D) + { //int isnormal(real-floating x); /// int isnormal(float x) { return fpclassify(x) == FP_NORMAL; } @@ -495,16 +498,16 @@ else version (CRuntime_Microsoft) // fully supported since MSVCRT 12 (VS 2013) o int isnormal(double x) { return fpclassify(x) == FP_NORMAL; } /// int isnormal(real x) { return fpclassify(x) == FP_NORMAL; } - - //int signbit(real-floating x); - /// - extern(C) pragma(mangle, "__signbitf") pure int signbit(float x); - /// - extern(C) pragma(mangle, "__signbit") pure int signbit(double x); - /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") - int signbit(real x); } + + //int signbit(real-floating x); + /// + pragma(mangle, "__signbitf") pure int signbit(float x); + /// + pragma(mangle, "__signbit") pure int signbit(double x); + /// + pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") + int signbit(real x); } else { @@ -644,61 +647,58 @@ else version (CRuntime_Glibc) pure int __signbit(double x); pure int __signbitl(real x); - extern (D) - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassify") pure int fpclassify(double x); + pragma(mangle, "__fpclassify") pure int fpclassify(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") + pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") pure int fpclassify(real x); //int isfinite(real-floating x); /// - extern(C) pragma(mangle, "__finitef") pure int isfinite(float x); + pragma(mangle, "__finitef") pure int isfinite(float x); /// - extern(C) pragma(mangle, "__finite") pure int isfinite(double x); + pragma(mangle, "__finite") pure int isfinite(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__finite" : "__finitel") + pragma(mangle, real.sizeof == double.sizeof ? "__finite" : "__finitel") pure int isfinite(real x); //int isinf(real-floating x); /// - extern(C) pragma(mangle, "__isinff") pure int isinf(float x); + pragma(mangle, "__isinff") pure int isinf(float x); /// - extern(C) pragma(mangle, "__isinf") pure int isinf(double x); + pragma(mangle, "__isinf") pure int isinf(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__isinf" : "__isinfl") + pragma(mangle, real.sizeof == double.sizeof ? "__isinf" : "__isinfl") pure int isinf(real x); //int isnan(real-floating x); /// - extern(C) pragma(mangle, "__isnanf") pure int isnan(float x); + pragma(mangle, "__isnanf") pure int isnan(float x); /// - extern(C) pragma(mangle, "__isnan") pure int isnan(double x); + pragma(mangle, "__isnan") pure int isnan(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") + pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") pure int isnan(real x); //int isnormal(real-floating x); /// - pure int isnormal(float x) { return fpclassify(x) == FP_NORMAL; } + extern (D) pure int isnormal(float x) { return fpclassify(x) == FP_NORMAL; } /// - pure int isnormal(double x) { return fpclassify(x) == FP_NORMAL; } + extern (D) pure int isnormal(double x) { return fpclassify(x) == FP_NORMAL; } /// - pure int isnormal(real x) { return fpclassify(x) == FP_NORMAL; } + extern (D) pure int isnormal(real x) { return fpclassify(x) == FP_NORMAL; } //int signbit(real-floating x); /// - extern(C) pragma(mangle, "__signbitf") pure int signbit(float x); + pragma(mangle, "__signbitf") pure int signbit(float x); /// - extern(C) pragma(mangle, "__signbit") pure int signbit(double x); + pragma(mangle, "__signbit") pure int signbit(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") + pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") pure int signbit(real x); - } } else version (CRuntime_Musl) { @@ -736,16 +736,16 @@ else version (CRuntime_Musl) int __signbitl(real x); } - extern (D) pure - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassifyf") int fpclassify(float x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassify") int fpclassify(double x); + pragma(mangle, "__fpclassify") pure int fpclassify(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") - int fpclassify(real x); + pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") + pure int fpclassify(real x); + extern (D) pure + { private uint __FLOAT_BITS(float __f) { union __u_t { @@ -813,16 +813,16 @@ else version (CRuntime_Musl) int isnormal(double x) { return fpclassify(x) == FP_NORMAL; } /// int isnormal(real x) { return fpclassify(x) == FP_NORMAL; } + } //int signbit(real-floating x); /// - extern(C) pragma(mangle, "__signbitf") int signbit(float x); + pragma(mangle, "__signbitf") pure int signbit(float x); /// - extern(C) pragma(mangle, "__signbit") int signbit(double x); + pragma(mangle, "__signbit") pure int signbit(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") - int signbit(real x); - } + pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") + pure int signbit(real x); } else version (CRuntime_UClibc) { @@ -870,55 +870,55 @@ else version (CRuntime_UClibc) int __signbit(double x); int __signbitl(real x); - extern (D) - { /// - extern(C) pragma(mangle, "__fpclassifyf") int fpclassify(float x); + pragma(mangle, "__fpclassifyf") int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassify") int fpclassify(double x); + pragma(mangle, "__fpclassify") int fpclassify(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") + pragma(mangle, real.sizeof == double.sizeof ? "__fpclassify" : "__fpclassifyl") int fpclassify(real x); /// - extern(C) pragma(mangle, "__finitef") int isfinite(float x); + pragma(mangle, "__finitef") int isfinite(float x); /// - extern(C) pragma(mangle, "__finite") int isfinite(double x); + pragma(mangle, "__finite") int isfinite(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__finite" : "__finitel") + pragma(mangle, real.sizeof == double.sizeof ? "__finite" : "__finitel") int isfinite(real x); /// - extern(C) pragma(mangle, "__isinff") int isinf(float x); + pragma(mangle, "__isinff") int isinf(float x); /// - extern(C) pragma(mangle, "__isinf") int isinf(double x); + pragma(mangle, "__isinf") int isinf(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__isinf" : "__isinfl") + pragma(mangle, real.sizeof == double.sizeof ? "__isinf" : "__isinfl") int isinf(real x); /// - extern(C) pragma(mangle, "__isnanf") int isnan(float x); + pragma(mangle, "__isnanf") int isnan(float x); /// - extern(C) pragma(mangle, "__isnan") int isnan(double x); + pragma(mangle, "__isnan") int isnan(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") + pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") int isnan(real x); + extern (D) + { /// int isnormal(float x) { return fpclassify(x) == FP_NORMAL; } /// int isnormal(double x) { return fpclassify(x) == FP_NORMAL; } /// int isnormal(real x) { return fpclassify(x) == FP_NORMAL; } + } /// - extern(C) pragma(mangle, "__signbitf") int signbit(float x); + pragma(mangle, "__signbitf") int signbit(float x); /// - extern(C) pragma(mangle, "__signbit") int signbit(double x); + pragma(mangle, "__signbit") int signbit(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") + pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") int signbit(real x); - } } else version (Darwin) { @@ -999,40 +999,40 @@ else version (Darwin) pure int __isnanl(real x); } - extern (D) - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); + pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); /// - extern(C) pragma(mangle, __fpclassifyl.mangleof) pure int fpclassify(real x); + pragma(mangle, __fpclassifyl.mangleof) pure int fpclassify(real x); //int isfinite(real-floating x); /// - extern(C) pragma(mangle, "__isfinitef") pure int isfinite(float x); + pragma(mangle, "__isfinitef") pure int isfinite(float x); /// - extern(C) pragma(mangle, "__isfinited") pure int isfinite(double x); + pragma(mangle, "__isfinited") pure int isfinite(double x); /// - extern(C) pragma(mangle, __isfinitel.mangleof) pure int isfinite(real x); + pragma(mangle, __isfinitel.mangleof) pure int isfinite(real x); //int isinf(real-floating x); /// - extern(C) pragma(mangle, "__isinff") pure int isinf(float x); + pragma(mangle, "__isinff") pure int isinf(float x); /// - extern(C) pragma(mangle, "__isinfd") pure int isinf(double x); + pragma(mangle, "__isinfd") pure int isinf(double x); /// - extern(C) pragma(mangle, __isinfl.mangleof) pure int isinf(real x); + pragma(mangle, __isinfl.mangleof) pure int isinf(real x); //int isnan(real-floating x); /// - extern(C) pragma(mangle, "__isnanf") pure int isnan(float x); + pragma(mangle, "__isnanf") pure int isnan(float x); /// - extern(C) pragma(mangle, "__isnand") pure int isnan(double x); + pragma(mangle, "__isnand") pure int isnan(double x); /// - extern(C) pragma(mangle, __isnanl.mangleof) pure int isnan(real x); + pragma(mangle, __isnanl.mangleof) pure int isnan(real x); + extern (D) + { //int isnormal(real-floating x); /// pure int isnormal(float x) { return fpclassify(x) == FP_NORMAL; } @@ -1040,15 +1040,15 @@ else version (Darwin) pure int isnormal(double x) { return fpclassify(x) == FP_NORMAL; } /// pure int isnormal(real x) { return fpclassify(x) == FP_NORMAL; } + } //int signbit(real-floating x); /// - extern(C) pragma(mangle, "__signbitf") pure int signbit(float x); + pragma(mangle, "__signbitf") pure int signbit(float x); /// - extern(C) pragma(mangle, "__signbitd") pure int signbit(double x); + pragma(mangle, "__signbitd") pure int signbit(double x); /// - extern(C) pragma(mangle, "__signbitl") pure int signbit(real x); - } + pragma(mangle, "__signbitl") pure int signbit(real x); } else version (FreeBSD) { @@ -1092,56 +1092,53 @@ else version (FreeBSD) pure int __signbitf(float); pure int __signbitl(real); - extern (D) - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); + pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); /// - extern(C) pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); + pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); //int isfinite(real-floating x); /// - extern(C) pragma(mangle, "__isfinitef") pure int isfinite(float x); + pragma(mangle, "__isfinitef") pure int isfinite(float x); /// - extern(C) pragma(mangle, "__isfinite") pure int isfinite(double x); + pragma(mangle, "__isfinite") pure int isfinite(double x); /// - extern(C) pragma(mangle, "__isfinitel") pure int isfinite(real x); + pragma(mangle, "__isfinitel") pure int isfinite(real x); //int isinf(real-floating x); /// - extern(C) pragma(mangle, "__isinff") pure int isinf(float x); + pragma(mangle, "__isinff") pure int isinf(float x); /// - pure int isinf(double x) { return __isinfl(x); } + extern (D) pure int isinf(double x) { return __isinfl(x); } /// - extern(C) pragma(mangle, "__isinfl") pure int isinf(real x); + pragma(mangle, "__isinfl") pure int isinf(real x); //int isnan(real-floating x); /// - pure int isnan(float x) { return __isnanl(x); } + extern (D) pure int isnan(float x) { return __isnanl(x); } /// - pure int isnan(double x) { return __isnanl(x); } + extern (D) pure int isnan(double x) { return __isnanl(x); } /// - extern(C) pragma(mangle, "__isnanl") pure int isnan(real x); + pragma(mangle, "__isnanl") pure int isnan(real x); //int isnormal(real-floating x); /// - extern(C) pragma(mangle, "__isnormalf") pure int isnormal(float x); + pragma(mangle, "__isnormalf") pure int isnormal(float x); /// - extern(C) pragma(mangle, "__isnormal") pure int isnormal(double x); + pragma(mangle, "__isnormal") pure int isnormal(double x); /// - extern(C) pragma(mangle, "__isnormall") pure int isnormal(real x); + pragma(mangle, "__isnormall") pure int isnormal(real x); //int signbit(real-floating x); /// - extern(C) pragma(mangle, "__signbitf") pure int signbit(float x); + pragma(mangle, "__signbitf") pure int signbit(float x); /// - extern(C) pragma(mangle, "__signbit") pure int signbit(double x); + pragma(mangle, "__signbit") pure int signbit(double x); /// - pure int signbit(real x) { return __signbit(x); } - } + extern (D) pure int signbit(real x) { return __signbit(x); } } else version (OpenBSD) { @@ -1185,56 +1182,53 @@ else version (OpenBSD) pure int __signbitf(float); pure int __signbitl(real); - extern (D) - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassify") pure int fpclassify(double x); + pragma(mangle, "__fpclassify") pure int fpclassify(double x); /// - extern(C) pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); + pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); //int isfinite(real-floating x); /// - extern(C) pragma(mangle, "__isfinitef") pure int isfinite(float x); + pragma(mangle, "__isfinitef") pure int isfinite(float x); /// - extern(C) pragma(mangle, "__isfinite") pure int isfinite(double x); + pragma(mangle, "__isfinite") pure int isfinite(double x); /// - extern(C) pragma(mangle, "__isfinitel") pure int isfinite(real x); + pragma(mangle, "__isfinitel") pure int isfinite(real x); //int isinf(real-floating x); /// - extern(C) pragma(mangle, "__isinff") pure int isinf(float x); + pragma(mangle, "__isinff") pure int isinf(float x); /// - pure int isinf(double x) { return __isinfl(x); } + extern (D) pure int isinf(double x) { return __isinfl(x); } /// - extern(C) pragma(mangle, "__isinfl") pure int isinf(real x); + pragma(mangle, "__isinfl") pure int isinf(real x); //int isnan(real-floating x); /// - pure int isnan(float x) { return __isnanl(x); } + extern (D) pure int isnan(float x) { return __isnanl(x); } /// - pure int isnan(double x) { return __isnanl(x); } + extern (D) pure int isnan(double x) { return __isnanl(x); } /// - extern(C) pragma(mangle, "__isnanl") pure int isnan(real x); + pragma(mangle, "__isnanl") pure int isnan(real x); //int isnormal(real-floating x); /// - extern(C) pragma(mangle, "__isnormalf") pure int isnormal(float x); + pragma(mangle, "__isnormalf") pure int isnormal(float x); /// - extern(C) pragma(mangle, "__isnormal") pure int isnormal(double x); + pragma(mangle, "__isnormal") pure int isnormal(double x); /// - extern(C) pragma(mangle, "__isnormall") pure int isnormal(real x); + pragma(mangle, "__isnormall") pure int isnormal(real x); //int signbit(real-floating x); /// - extern(C) pragma(mangle, "__signbitf") pure int signbit(float x); + pragma(mangle, "__signbitf") pure int signbit(float x); /// - extern(C) pragma(mangle, "__signbit") pure int signbit(double x); + pragma(mangle, "__signbit") pure int signbit(double x); /// - pure int signbit(real x) { return __signbit(x); } - } + extern (D) pure int signbit(real x) { return __signbit(x); } } else version (NetBSD) { @@ -1266,17 +1260,17 @@ else version (NetBSD) pure uint __fpclassifyd(double x); pure uint __fpclassifyl(real x); - extern (D) - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); + pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__fpclassifyd" : "__fpclassifyl") + pragma(mangle, real.sizeof == double.sizeof ? "__fpclassifyd" : "__fpclassifyl") pure int fpclassify(real x); + extern (D) + { //int isfinite(real-floating x); /// pure int isfinite(float x) { return fpclassify(x) >= FP_NORMAL; } @@ -1360,32 +1354,29 @@ else version (DragonFlyBSD) pure int __signbitf(float); pure int __signbitl(real); - extern (D) - { - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); - extern(C) pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); - extern(C) pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); + pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); - extern(C) pragma(mangle, "__isfinitef") pure int isfinite(float x); - extern(C) pragma(mangle, "__isfinite") pure int isfinite(double x); - extern(C) pragma(mangle, "__isfinitel") pure int isfinite(real x); + pragma(mangle, "__isfinitef") pure int isfinite(float x); + pragma(mangle, "__isfinite") pure int isfinite(double x); + pragma(mangle, "__isfinitel") pure int isfinite(real x); - extern(C) pragma(mangle, "__isinff") pure int isinf(float x); - extern(C) pragma(mangle, "__isinf") pure int isinf(double x); - extern(C) pragma(mangle, "__isinfl") pure int isinf(real x); + pragma(mangle, "__isinff") pure int isinf(float x); + pragma(mangle, "__isinf") pure int isinf(double x); + pragma(mangle, "__isinfl") pure int isinf(real x); - extern(C) pragma(mangle, "__isnanf") pure int isnan(float x); - extern(C) pragma(mangle, "__isnan") pure int isnan(double x); - extern(C) pragma(mangle, "__isnanl") pure int isnan(real x); + pragma(mangle, "__isnanf") pure int isnan(float x); + pragma(mangle, "__isnan") pure int isnan(double x); + pragma(mangle, "__isnanl") pure int isnan(real x); - extern(C) pragma(mangle, "__isnormalf") pure int isnormal(float x); - extern(C) pragma(mangle, "__isnormal") pure int isnormal(double x); - extern(C) pragma(mangle, "__isnormall") pure int isnormal(real x); + pragma(mangle, "__isnormalf") pure int isnormal(float x); + pragma(mangle, "__isnormal") pure int isnormal(double x); + pragma(mangle, "__isnormall") pure int isnormal(real x); - extern(C) pragma(mangle, "__signbitf") pure int signbit(float x); - extern(C) pragma(mangle, "__signbit") pure int signbit(double x); - extern(C) pragma(mangle, "__signbitl") pure int signbit(real x); - } + pragma(mangle, "__signbitf") pure int signbit(float x); + pragma(mangle, "__signbit") pure int signbit(double x); + pragma(mangle, "__signbitl") pure int signbit(real x); } else version (Solaris) { @@ -1393,17 +1384,14 @@ else version (Solaris) pure int __isnan(double x); pure int __isnanl(real x); - extern (D) - { //int isnan(real-floating x); /// - extern(C) pragma(mangle, "__isnanf") pure int isnan(float x); + pragma(mangle, "__isnanf") pure int isnan(float x); /// - extern(C) pragma(mangle, "__isnan") pure int isnan(double x); + pragma(mangle, "__isnan") pure int isnan(double x); /// - extern(C) pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") + pragma(mangle, real.sizeof == double.sizeof ? "__isnan" : "__isnanl") pure int isnan(real x); - } } else version (CRuntime_Bionic) { @@ -1448,89 +1436,86 @@ else version (CRuntime_Bionic) pure int __signbitf(float); pure int __signbitl(real); - extern (D) - { //int fpclassify(real-floating x); /// - extern(C) pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); + pragma(mangle, "__fpclassifyf") pure int fpclassify(float x); /// - extern(C) pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); + pragma(mangle, "__fpclassifyd") pure int fpclassify(double x); /// - extern(C) pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); + pragma(mangle, "__fpclassifyl") pure int fpclassify(real x); //int isfinite(real-floating x); /// - extern(C) pragma(mangle, "__isfinitef") pure int isfinite(float x); + pragma(mangle, "__isfinitef") pure int isfinite(float x); /// - extern(C) pragma(mangle, "__isfinite") pure int isfinite(double x); + pragma(mangle, "__isfinite") pure int isfinite(double x); /// - extern(C) pragma(mangle, "__isfinitel") pure int isfinite(real x); + pragma(mangle, "__isfinitel") pure int isfinite(real x); //int isinf(real-floating x); /// - extern(C) pragma(mangle, "__isinff") pure int isinf(float x); + pragma(mangle, "__isinff") pure int isinf(float x); /// - extern(C) pragma(mangle, "__isinf") pure int isinf(double x); + pragma(mangle, "__isinf") pure int isinf(double x); /// - extern(C) pragma(mangle, "__isinfl") pure int isinf(real x); + pragma(mangle, "__isinfl") pure int isinf(real x); //int isnan(real-floating x); /// - extern(C) pragma(mangle, "isnanf") pure int isnan(float x); + pragma(mangle, "isnanf") pure int isnan(float x); /// - extern(C) pragma(mangle, "__isnanl") pure int isnan(real x); + pragma(mangle, "__isnanl") pure int isnan(real x); //int isnormal(real-floating x); /// - extern(C) pragma(mangle, "__isnormalf") pure int isnormal(float x); + pragma(mangle, "__isnormalf") pure int isnormal(float x); /// - extern(C) pragma(mangle, "__isnormal") pure int isnormal(double x); + pragma(mangle, "__isnormal") pure int isnormal(double x); /// - extern(C) pragma(mangle, "__isnormall") pure int isnormal(real x); + pragma(mangle, "__isnormall") pure int isnormal(real x); //int signbit(real-floating x); /// - extern(C) pragma(mangle, "__signbitf") pure int signbit(float x); + pragma(mangle, "__signbitf") pure int signbit(float x); /// - extern(C) pragma(mangle, "__signbit") pure int signbit(double x); + pragma(mangle, "__signbit") pure int signbit(double x); /// - extern(C) pragma(mangle, "__signbitl") pure int signbit(real x); - } + pragma(mangle, "__signbitl") pure int signbit(real x); } extern (D) { //int isgreater(real-floating x, real-floating y); /// - pure int isgreater(float x, float y) { return x > y && !isunordered(x, y); } + pure int isgreater(float x, float y) { return x > y; } /// - pure int isgreater(double x, double y) { return x > y && !isunordered(x, y); } + pure int isgreater(double x, double y) { return x > y; } /// - pure int isgreater(real x, real y) { return x > y && !isunordered(x, y); } + pure int isgreater(real x, real y) { return x > y; } //int isgreaterequal(real-floating x, real-floating y); /// - pure int isgreaterequal(float x, float y) { return x >= y && !isunordered(x, y); } + pure int isgreaterequal(float x, float y) { return x >= y; } /// - pure int isgreaterequal(double x, double y) { return x >= y && !isunordered(x, y); } + pure int isgreaterequal(double x, double y) { return x >= y; } /// - pure int isgreaterequal(real x, real y) { return x >= y && !isunordered(x, y); } + pure int isgreaterequal(real x, real y) { return x >= y; } //int isless(real-floating x, real-floating y); /// - pure int isless(float x, float y) { return x < y && !isunordered(x, y); } + pure int isless(float x, float y) { return x < y; } /// - pure int isless(double x, double y) { return x < y && !isunordered(x, y); } + pure int isless(double x, double y) { return x < y; } /// - pure int isless(real x, real y) { return x < y && !isunordered(x, y); } + pure int isless(real x, real y) { return x < y; } //int islessequal(real-floating x, real-floating y); /// - pure int islessequal(float x, float y) { return x <= y && !isunordered(x, y); } + pure int islessequal(float x, float y) { return x <= y; } /// - pure int islessequal(double x, double y) { return x <= y && !isunordered(x, y); } + pure int islessequal(double x, double y) { return x <= y; } /// - pure int islessequal(real x, real y) { return x <= y && !isunordered(x, y); } + pure int islessequal(real x, real y) { return x <= y; } //int islessgreater(real-floating x, real-floating y); /// diff --git a/libphobos/libdruntime/core/stdc/stdint.d b/libphobos/libdruntime/core/stdc/stdint.d index ac71b1d..9db2fda 100644 --- a/libphobos/libdruntime/core/stdc/stdint.d +++ b/libphobos/libdruntime/core/stdc/stdint.d @@ -36,7 +36,20 @@ extern (C): nothrow: @nogc: - +// These are defined the same way as D basic types, so the definition is +// platform-independant +alias int8_t = byte; /// +alias int16_t = short; /// +alias uint8_t = ubyte; /// +alias uint16_t = ushort; /// + +// 32 bit types and need to be defined on-platform basis, because +// they might have C++ binary mangling of `int` or `long`. +// 64-bit types respectively might be mangled as `long` or `long long` + +// It would seem correct to define intmax_t and uintmax_t here, but C and C++ +// compilers don't in practice always set them to the maximum supported value. +// See https://quuxplusone.github.io/blog/2019/02/28/is-int128-integral/ static if (is(ucent)) { alias int128_t = cent; /// @@ -45,10 +58,6 @@ static if (is(ucent)) version (Windows) { - alias int8_t = byte; /// - alias int16_t = short; /// - alias uint8_t = ubyte; /// - alias uint16_t = ushort; /// version (CRuntime_DigitalMars) { alias int32_t = cpp_long; /// @@ -62,23 +71,23 @@ version (Windows) alias int64_t = long; /// alias uint64_t = ulong; /// - alias int_least8_t = byte; /// - alias uint_least8_t = ubyte; /// - alias int_least16_t = short; /// - alias uint_least16_t = ushort; /// - alias int_least32_t = int32_t; /// + alias int_least8_t = byte; /// + alias uint_least8_t = ubyte; /// + alias int_least16_t = short; /// + alias uint_least16_t = ushort; /// + alias int_least32_t = int32_t; /// alias uint_least32_t = uint32_t; /// - alias int_least64_t = long; /// - alias uint_least64_t = ulong; /// - - alias int_fast8_t = byte; /// - alias uint_fast8_t = ubyte; /// - alias int_fast16_t = int; /// - alias uint_fast16_t = uint; /// - alias int_fast32_t = int32_t; /// + alias int_least64_t = long; /// + alias uint_least64_t = ulong; /// + + alias int_fast8_t = byte; /// + alias uint_fast8_t = ubyte; /// + alias int_fast16_t = int; /// + alias uint_fast16_t = uint; /// + alias int_fast32_t = int32_t; /// alias uint_fast32_t = uint32_t; /// - alias int_fast64_t = long; /// - alias uint_fast64_t = ulong; /// + alias int_fast64_t = long; /// + alias uint_fast64_t = ulong; /// alias intptr_t = ptrdiff_t; /// alias uintptr_t = size_t; /// @@ -87,10 +96,6 @@ version (Windows) } else version (Darwin) { - alias int8_t = byte; /// - alias int16_t = short; /// - alias uint8_t = ubyte; /// - alias uint16_t = ushort; /// alias int32_t = int; /// alias uint32_t = uint; /// alias int64_t = cpp_longlong; /// @@ -121,23 +126,19 @@ else version (Darwin) } else version (Posix) { - alias int8_t = byte; /// - alias int16_t = short; /// - alias uint8_t = ubyte; /// - alias uint16_t = ushort; /// - alias int32_t = int; /// - alias uint32_t = uint; /// - alias int64_t = long; /// - alias uint64_t = ulong; /// - - alias int_least8_t = byte; /// - alias uint_least8_t = ubyte; /// - alias int_least16_t = short; /// + alias int32_t = int; /// + alias uint32_t = uint; /// + alias int64_t = long; /// + alias uint64_t = ulong; /// + + alias int_least8_t = byte; /// + alias uint_least8_t = ubyte; /// + alias int_least16_t = short; /// alias uint_least16_t = ushort; /// - alias int_least32_t = int; /// - alias uint_least32_t = uint; /// - alias int_least64_t = long; /// - alias uint_least64_t = ulong; /// + alias int_least32_t = int; /// + alias uint_least32_t = uint; /// + alias int_least64_t = long; /// + alias uint_least64_t = ulong;/// version (FreeBSD) { @@ -166,13 +167,13 @@ else version (Posix) alias int_fast32_t = ptrdiff_t; /// alias uint_fast32_t = size_t; /// } - alias int_fast64_t = long; /// - alias uint_fast64_t = ulong; /// + alias int_fast64_t = long; /// + alias uint_fast64_t = ulong; /// alias intptr_t = ptrdiff_t; /// - alias uintptr_t = size_t; /// - alias intmax_t = long; /// - alias uintmax_t = ulong; /// + alias uintptr_t = size_t; /// + alias intmax_t = long; /// + alias uintmax_t = ulong; /// } else { diff --git a/libphobos/libdruntime/core/stdcpp/allocator.d b/libphobos/libdruntime/core/stdcpp/allocator.d new file mode 100644 index 0000000..abf97c4 --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/allocator.d @@ -0,0 +1,373 @@ +/** + * D binding to C++ std::allocator. + * + * Copyright: Copyright (c) 2019 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/allocator.d) + */ + +module core.stdcpp.allocator; + +import core.stdcpp.new_; +import core.stdcpp.xutility : StdNamespace, __cpp_sized_deallocation, __cpp_aligned_new; + +extern(C++, (StdNamespace)): + +/** + * Allocators are classes that define memory models to be used by some parts of + * the C++ Standard Library, and most specifically, by STL containers. + */ +extern(C++, class) +struct allocator(T) +{ + static assert(!is(T == const), "The C++ Standard forbids containers of const elements because allocator!(const T) is ill-formed."); + static assert(!is(T == immutable), "immutable is not representable in C++"); + static assert(!is(T == class), "Instantiation with `class` is not supported; D can't mangle the base (non-pointer) type of a class. Use `extern (C++, class) struct T { ... }` instead."); +extern(D): + + /// + this(U)(ref allocator!U) {} + + /// + alias size_type = size_t; + /// + alias difference_type = ptrdiff_t; + /// + alias pointer = T*; + /// + alias value_type = T; + + /// + enum propagate_on_container_move_assignment = true; + /// + enum is_always_equal = true; + + /// + alias rebind(U) = allocator!U; + + version (CppRuntime_Microsoft) + { + import core.stdcpp.xutility : _MSC_VER; + + /// + T* allocate(size_t count) @nogc + { + static if (_MSC_VER <= 1800) + { + import core.stdcpp.xutility : _Xbad_alloc; + if (count == 0) + return null; + void* mem; + if ((size_t.max / T.sizeof < count) || (mem = __cpp_new(count * T.sizeof)) is null) + _Xbad_alloc(); + return cast(T*)mem; + } + else + { + enum _Align = _New_alignof!T; + + static size_t _Get_size_of_n(T)(const size_t _Count) + { + static if (T.sizeof == 1) + return _Count; + else + { + enum size_t _Max_possible = size_t.max / T.sizeof; + return _Max_possible < _Count ? size_t.max : _Count * T.sizeof; + } + } + + const size_t _Bytes = _Get_size_of_n!T(count); + if (_Bytes == 0) + return null; + + static if (!__cpp_aligned_new || _Align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + version (INTEL_ARCH) + { + if (_Bytes >= _Big_allocation_threshold) + return cast(T*)_Allocate_manually_vector_aligned(_Bytes); + } + return cast(T*)__cpp_new(_Bytes); + } + else + { + size_t _Passed_align = _Align; + version (INTEL_ARCH) + { + if (_Bytes >= _Big_allocation_threshold) + _Passed_align = _Align < _Big_allocation_alignment ? _Big_allocation_alignment : _Align; + } + return cast(T*)__cpp_new_aligned(_Bytes, cast(align_val_t)_Passed_align); + } + } + } + /// + void deallocate(T* ptr, size_t count) @nogc + { + static if (_MSC_VER <= 1800) + { + __cpp_delete(ptr); + } + else + { + // this is observed from VS2017 + void* _Ptr = ptr; + size_t _Bytes = T.sizeof * count; + + enum _Align = _New_alignof!T; + static if (!__cpp_aligned_new || _Align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + version (INTEL_ARCH) + { + if (_Bytes >= _Big_allocation_threshold) + _Adjust_manually_vector_aligned(_Ptr, _Bytes); + } + static if (_MSC_VER <= 1900) + __cpp_delete(ptr); + else + __cpp_delete_size(_Ptr, _Bytes); + } + else + { + size_t _Passed_align = _Align; + version (INTEL_ARCH) + { + if (_Bytes >= _Big_allocation_threshold) + _Passed_align = _Align < _Big_allocation_alignment ? _Big_allocation_alignment : _Align; + } + __cpp_delete_size_aligned(_Ptr, _Bytes, cast(align_val_t)_Passed_align); + } + } + } + + /// + enum size_t max_size = size_t.max / T.sizeof; + } + else version (CppRuntime_Gcc) + { + /// + T* allocate(size_t count, const(void)* = null) @nogc + { +// if (count > max_size) +// std::__throw_bad_alloc(); + + static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + return cast(T*)__cpp_new_aligned(count * T.sizeof, cast(align_val_t)T.alignof); + else + return cast(T*)__cpp_new(count * T.sizeof); + } + /// + void deallocate(T* ptr, size_t count) @nogc + { + // NOTE: GCC doesn't seem to use the sized delete when it's available... + + static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + __cpp_delete_aligned(cast(void*)ptr, cast(align_val_t)T.alignof); + else + __cpp_delete(cast(void*)ptr); + } + + /// + enum size_t max_size = (ptrdiff_t.max < size_t.max ? cast(size_t)ptrdiff_t.max : size_t.max) / T.sizeof; + } + else version (CppRuntime_Clang) + { + /// + T* allocate(size_t count, const(void)* = null) @nogc + { +// if (count > max_size) +// __throw_length_error("allocator!T.allocate(size_t n) 'n' exceeds maximum supported size"); + + static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + return cast(T*)__cpp_new_aligned(count * T.sizeof, cast(align_val_t)T.alignof); + else + return cast(T*)__cpp_new(count * T.sizeof); + } + /// + void deallocate(T* ptr, size_t count) @nogc + { + static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + { + static if (__cpp_sized_deallocation) + return __cpp_delete_size_aligned(cast(void*)ptr, count * T.sizeof, cast(align_val_t)T.alignof); + else + return __cpp_delete_aligned(cast(void*)ptr, cast(align_val_t)T.alignof); + } + else static if (__cpp_sized_deallocation) + return __cpp_delete_size(cast(void*)ptr, count * T.sizeof); + else + return __cpp_delete(cast(void*)ptr); + } + + /// + enum size_t max_size = size_t.max / T.sizeof; + } + else + { + static assert(false, "C++ runtime not supported"); + } +} + +/// +extern(C++, (StdNamespace)) +struct allocator_traits(Alloc) +{ + import core.internal.traits : isTrue; + + /// + alias allocator_type = Alloc; + /// + alias value_type = allocator_type.value_type; + /// + alias size_type = allocator_type.size_type; + /// + alias difference_type = allocator_type.difference_type; + /// + alias pointer = allocator_type.pointer; + + /// + enum propagate_on_container_copy_assignment = isTrue!(allocator_type, "propagate_on_container_copy_assignment"); + /// + enum propagate_on_container_move_assignment = isTrue!(allocator_type, "propagate_on_container_move_assignment"); + /// + enum propagate_on_container_swap = isTrue!(allocator_type, "propagate_on_container_swap"); + /// + enum is_always_equal = isTrue!(allocator_type, "is_always_equal"); + + /// + template rebind_alloc(U) + { + static if (__traits(hasMember, allocator_type, "rebind")) + alias rebind_alloc = allocator_type.rebind!U; + else + alias rebind_alloc = allocator_type!U; + } + /// + alias rebind_traits(U) = allocator_traits!(rebind_alloc!U); + + /// + static size_type max_size()(auto ref allocator_type a) + { + static if (__traits(hasMember, allocator_type, "max_size")) + return a.max_size(); + else + return size_type.max / value_type.sizeof; + } + + /// + static allocator_type select_on_container_copy_construction()(auto ref allocator_type a) + { + static if (__traits(hasMember, allocator_type, "select_on_container_copy_construction")) + return a.select_on_container_copy_construction(); + else + return a; + } +} + +private: + +// MSVC has some bonus complexity! +version (CppRuntime_Microsoft) +{ + // some versions of VS require a `* const` pointer mangling hack + // we need a way to supply the target VS version to the compile + version = NeedsMangleHack; + + version (X86) + version = INTEL_ARCH; + version (X86_64) + version = INTEL_ARCH; + + // HACK: should we guess _DEBUG for `debug` builds? + version (_DEBUG) + enum _DEBUG = true; + else version (NDEBUG) + enum _DEBUG = false; + else + { + import core.stdcpp.xutility : __CXXLIB__; + enum _DEBUG = __CXXLIB__.length && 'd' == __CXXLIB__[$-1]; // libcmtd, msvcrtd + } + + enum _New_alignof(T) = T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__ ? T.alignof : __STDCPP_DEFAULT_NEW_ALIGNMENT__; + + version (INTEL_ARCH) + { + enum size_t _Big_allocation_threshold = 4096; + enum size_t _Big_allocation_alignment = 32; + + static assert(2 * (void*).sizeof <= _Big_allocation_alignment, "Big allocation alignment should at least match vector register alignment"); + static assert((v => v != 0 && (v & (v - 1)) == 0)(_Big_allocation_alignment), "Big allocation alignment must be a power of two"); + static assert(size_t.sizeof == (void*).sizeof, "uintptr_t is not the same size as size_t"); + + // NOTE: this must track `_DEBUG` macro used in C++... + static if (_DEBUG) + enum size_t _Non_user_size = 2 * (void*).sizeof + _Big_allocation_alignment - 1; + else + enum size_t _Non_user_size = (void*).sizeof + _Big_allocation_alignment - 1; + + version (Win64) + enum size_t _Big_allocation_sentinel = 0xFAFAFAFAFAFAFAFA; + else + enum size_t _Big_allocation_sentinel = 0xFAFAFAFA; + + extern(D) // Template so it gets compiled according to _DEBUG. + void* _Allocate_manually_vector_aligned()(const size_t _Bytes) @nogc + { + size_t _Block_size = _Non_user_size + _Bytes; + if (_Block_size <= _Bytes) + _Block_size = size_t.max; + + const size_t _Ptr_container = cast(size_t)__cpp_new(_Block_size); + if (!(_Ptr_container != 0)) + assert(false, "invalid argument"); + void* _Ptr = cast(void*)((_Ptr_container + _Non_user_size) & ~(_Big_allocation_alignment - 1)); + (cast(size_t*)_Ptr)[-1] = _Ptr_container; + + static if (_DEBUG) + (cast(size_t*)_Ptr)[-2] = _Big_allocation_sentinel; + return (_Ptr); + } + + extern(D) // Template so it gets compiled according to _DEBUG. + void _Adjust_manually_vector_aligned()(ref void* _Ptr, ref size_t _Bytes) pure nothrow @nogc + { + _Bytes += _Non_user_size; + + const size_t* _Ptr_user = cast(size_t*)_Ptr; + const size_t _Ptr_container = _Ptr_user[-1]; + + // If the following asserts, it likely means that we are performing + // an aligned delete on memory coming from an unaligned allocation. + static if (_DEBUG) + assert(_Ptr_user[-2] == _Big_allocation_sentinel, "invalid argument"); + + // Extra paranoia on aligned allocation/deallocation; ensure _Ptr_container is + // in range [_Min_back_shift, _Non_user_size] + static if (_DEBUG) + enum size_t _Min_back_shift = 2 * (void*).sizeof; + else + enum size_t _Min_back_shift = (void*).sizeof; + + const size_t _Back_shift = cast(size_t)_Ptr - _Ptr_container; + if (!(_Back_shift >= _Min_back_shift && _Back_shift <= _Non_user_size)) + assert(false, "invalid argument"); + _Ptr = cast(void*)_Ptr_container; + } + } +} +version (CppRuntime_Clang) +{ + // Helper for container swap + package(core.stdcpp) void __swap_allocator(Alloc)(ref Alloc __a1, ref Alloc __a2) + { + import core.internal.lifetime : swap; + + static if (allocator_traits!Alloc.propagate_on_container_swap) + swap(__a1, __a2); + } +} diff --git a/libphobos/libdruntime/core/stdcpp/array.d b/libphobos/libdruntime/core/stdcpp/array.d new file mode 100644 index 0000000..eb63d4c --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/array.d @@ -0,0 +1,133 @@ +/** + * D header file for interaction with C++ std::array. + * + * Copyright: Copyright (c) 2018 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/array.d) + */ + +module core.stdcpp.array; + +import core.stdcpp.xutility : StdNamespace; + +// hacks to support DMD on Win32 +version (CppRuntime_Microsoft) +{ + version = CppRuntime_Windows; // use the MS runtime ABI for win32 +} +else version (CppRuntime_DigitalMars) +{ + version = CppRuntime_Windows; // use the MS runtime ABI for win32 + pragma(msg, "std::array not supported by DMC"); +} + +extern(C++, (StdNamespace)): + +/** + * D language counterpart to C++ std::array. + * + * C++ reference: $(LINK2 https://en.cppreference.com/w/cpp/container/array) + */ +extern(C++, class) struct array(T, size_t N) +{ +extern(D): +pragma(inline, true): + + /// + alias size_type = size_t; + /// + alias difference_type = ptrdiff_t; + /// + alias value_type = T; + /// + alias pointer = T*; + /// + alias const_pointer = const(T)*; + + /// + alias as_array this; + + /// Variadic constructor + this(T[N] args ...) { this[] = args[]; } + + /// + void fill()(auto ref const(T) value) { this[] = value; } + +pure nothrow @nogc: + /// + size_type size() const @safe { return N; } + /// + alias length = size; + /// + alias opDollar = length; + /// + size_type max_size() const @safe { return N; } + /// + bool empty() const @safe { return N == 0; } + + /// + ref inout(T) front() inout @safe { static if (N > 0) { return this[0]; } else { return as_array()[][0]; /* HACK: force OOB */ } } + /// + ref inout(T) back() inout @safe { static if (N > 0) { return this[N-1]; } else { return as_array()[][0]; /* HACK: force OOB */ } } + + version (CppRuntime_Windows) + { + /// + inout(T)* data() inout @safe { return &_Elems[0]; } + /// + ref inout(T)[N] as_array() inout @safe { return _Elems[0 .. N]; } + /// + ref inout(T) at(size_type i) inout @safe { return _Elems[0 .. N][i]; } + + private: + T[N ? N : 1] _Elems; + } + else version (CppRuntime_Gcc) + { + /// + inout(T)* data() inout @safe { static if (N > 0) { return &_M_elems[0]; } else { return null; } } + /// + ref inout(T)[N] as_array() inout @trusted { return data()[0 .. N]; } + /// + ref inout(T) at(size_type i) inout @trusted { return data()[0 .. N][i]; } + + private: + static if (N > 0) + { + T[N] _M_elems; + } + else + { + struct _Placeholder {} + _Placeholder _M_placeholder; + } + } + else version (CppRuntime_Clang) + { + /// + inout(T)* data() inout @trusted { static if (N > 0) { return &__elems_[0]; } else { return cast(inout(T)*)__elems_.ptr; } } + /// + ref inout(T)[N] as_array() inout @trusted { return data()[0 .. N]; } + /// + ref inout(T) at(size_type i) inout @trusted { return data()[0 .. N][i]; } + + private: + static if (N > 0) + { + T[N] __elems_; + } + else + { + struct _ArrayInStructT { T[1] __data_; } + align(_ArrayInStructT.alignof) + byte[_ArrayInStructT.sizeof] __elems_ = void; + } + } + else + { + static assert(false, "C++ runtime not supported"); + } +} diff --git a/libphobos/libdruntime/core/stdcpp/exception.d b/libphobos/libdruntime/core/stdcpp/exception.d index 14203b0..f920057 100644 --- a/libphobos/libdruntime/core/stdcpp/exception.d +++ b/libphobos/libdruntime/core/stdcpp/exception.d @@ -6,95 +6,134 @@ * Copyright: Copyright (c) 2016 D Language Foundation * License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: $(HTTP digitalmars.com, Walter Bright) + * Manu Evans * Source: $(DRUNTIMESRC core/stdcpp/_exception.d) */ module core.stdcpp.exception; -extern (C++, "std"): +import core.stdcpp.xutility : __cplusplus, CppStdRevision; +import core.attribute : weak; -version (CppRuntime_DigitalMars) -{ - import core.stdcpp.typeinfo; +version (CppRuntime_Gcc) + version = GenericBaseException; +version (CppRuntime_Clang) + version = GenericBaseException; - alias void function() unexpected_handler; - unexpected_handler set_unexpected(unexpected_handler f) nothrow; - void unexpected(); +extern (C++, "std"): +@nogc: - alias void function() terminate_handler; - terminate_handler set_terminate(terminate_handler f) nothrow; - void terminate(); +/// +alias terminate_handler = void function() nothrow; +/// +terminate_handler set_terminate(terminate_handler f) nothrow; +/// +terminate_handler get_terminate() nothrow; +/// +void terminate() nothrow; - bool uncaught_exception(); +static if (__cplusplus < CppStdRevision.cpp17) +{ + /// + alias unexpected_handler = void function(); + /// + deprecated unexpected_handler set_unexpected(unexpected_handler f) nothrow; + /// + deprecated unexpected_handler get_unexpected() nothrow; + /// + deprecated void unexpected(); +} +static if (__cplusplus < CppStdRevision.cpp17) +{ + /// + bool uncaught_exception() nothrow; +} +else static if (__cplusplus == CppStdRevision.cpp17) +{ + /// + deprecated bool uncaught_exception() nothrow; +} +static if (__cplusplus >= CppStdRevision.cpp17) +{ + /// + int uncaught_exceptions() nothrow; +} + +version (GenericBaseException) +{ + /// class exception { - this() nothrow { } - this(const exception) nothrow { } - //exception operator=(const exception) nothrow { return this; } - //virtual ~this() nothrow; - void dtor() { } - const(char)* what() const nothrow; - } + @nogc: + /// + this() nothrow {} + /// + @weak ~this() nothrow {} // HACK: this should extern, but then we have link errors! - class bad_exception : exception - { - this() nothrow { } - this(const bad_exception) nothrow { } - //bad_exception operator=(const bad_exception) nothrow { return this; } - //virtual ~this() nothrow; - override const(char)* what() const nothrow; + /// + @weak const(char)* what() const nothrow { return "unknown"; } // HACK: this should extern, but then we have link errors! + + protected: + this(const(char)*, int = 1) nothrow { this(); } // compat with MS derived classes } } -else version (CppRuntime_Gcc) +else version (CppRuntime_DigitalMars) { - alias void function() unexpected_handler; - unexpected_handler set_unexpected(unexpected_handler f) nothrow; - void unexpected(); - - alias void function() terminate_handler; - terminate_handler set_terminate(terminate_handler f) nothrow; - void terminate(); - - pure bool uncaught_exception(); - + /// class exception { - this(); + @nogc: + /// + this() nothrow {} //virtual ~this(); - void dtor1(); - void dtor2(); - const(char)* what() const; - } + void dtor() { } // reserve slot in vtbl[] - class bad_exception : exception - { - this(); - //virtual ~this(); - override const(char)* what() const; + /// + const(char)* what() const nothrow; + + protected: + this(const(char)*, int = 1) nothrow { this(); } // compat with MS derived classes } } else version (CppRuntime_Microsoft) { + /// class exception { - this(); - this(const exception); - //exception operator=(const exception) { return this; } - //virtual ~this(); - void dtor() { } - const(char)* what() const; - - private: - const(char)* mywhat; - bool dofree; - } + @nogc: + /// + this(const(char)* message = "unknown", int = 1) nothrow { msg = message; } + /// + @weak ~this() nothrow {} - class bad_exception : exception - { - this(const(char)* msg = "bad exception"); - //virtual ~this(); + /// + @weak const(char)* what() const nothrow { return msg != null ? msg : "unknown exception"; } + + // TODO: do we want this? exceptions are classes... ref types. +// final ref exception opAssign(ref const(exception) e) nothrow { msg = e.msg; return this; } + + protected: + @weak void _Doraise() const { assert(0); } + + protected: + const(char)* msg; } + } else static assert(0, "Missing std::exception binding for this platform"); + +/// +class bad_exception : exception +{ +@nogc: + /// + this(const(char)* message = "bad exception") { super(message); } + + version (GenericBaseException) + { + /// + @weak override const(char)* what() const nothrow { return "bad exception"; } + } +} diff --git a/libphobos/libdruntime/core/stdcpp/memory.d b/libphobos/libdruntime/core/stdcpp/memory.d new file mode 100644 index 0000000..bd7976c --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/memory.d @@ -0,0 +1,163 @@ +/** +* D binding to C++ . +* +* Copyright: Copyright (c) 2019 D Language Foundation +* License: Distributed under the +* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). +* (See accompanying file LICENSE) +* Authors: Manu Evans +* Source: $(DRUNTIMESRC core/stdcpp/memory.d) +*/ + +module core.stdcpp.memory; + +public import core.stdcpp.allocator; + +import core.stdcpp.xutility : StdNamespace; + +extern(C++, (StdNamespace)): + +/// +unique_ptr!T make_unique(T, Args...)(auto ref Args args) +{ + import core.lifetime : forward; + import core.stdcpp.new_ : cpp_new; + + return unique_ptr!T(cpp_new!T(forward!args)); +} + +/// +struct default_delete(T) +{ + /// + alias pointer = ClassOrPtr!T; + + /// + void opCall()(pointer ptr) const + { + import core.stdcpp.new_ : cpp_delete; + + cpp_delete(ptr); + } +} + +/// +extern(C++, class) +struct unique_ptr(T, Deleter = default_delete!T) +{ +extern(D): + /// + this(this) @disable; + + /// + ~this() + { + reset(); + } + + /// + ref unique_ptr opAssign(typeof(null)) + { + reset(); + return this; + } + + /// + void reset(pointer p = null) + { + pointer t = __ptr(); + __ptr() = p; + if (t) + get_deleter()(t); + } + +nothrow pure @safe @nogc: + /// + alias pointer = ClassOrPtr!T; + /// + alias element_type = T; + /// + alias deleter_type = Deleter; + + /// + this(pointer ptr) + { + __ptr() = ptr; + } + + /// + inout(pointer) get() inout nothrow + { + return __ptr(); + } + + /// + bool opCast(T : bool)() const nothrow + { + return __ptr() != null; + } + + /// + pointer release() nothrow + { + pointer t = __ptr(); + __ptr() = null; + return t; + } + +// void swap(ref unique_ptr u) nothrow +// { +// __ptr_.swap(__u.__ptr_); +// } + + version (CppRuntime_Microsoft) + { + /// + ref inout(deleter_type) get_deleter() inout nothrow { return _Mypair._Myval1; } + + private: + import core.stdcpp.xutility : _Compressed_pair; + + ref pointer __ptr() nothrow { return _Mypair._Myval2; } + inout(pointer) __ptr() inout nothrow { return _Mypair._Myval2; } + + _Compressed_pair!(Deleter, pointer) _Mypair; + } + else version (CppRuntime_Gcc) + { + /// + ref inout(deleter_type) get_deleter() inout nothrow { return _M_t.get!1; } + + private: + import core.stdcpp.tuple : tuple, get; + + ref pointer __ptr() nothrow { return _M_t.get!0; } + inout(pointer) __ptr() inout nothrow { return _M_t.get!0; } + + tuple!(pointer, Deleter) _M_t; + } + else version (CppRuntime_Clang) + { + /// + ref inout(deleter_type) get_deleter() inout nothrow { return __ptr_.second; } + + private: + import core.stdcpp.xutility : __compressed_pair; + + ref pointer __ptr() nothrow { return __ptr_.first; } + inout(pointer) __ptr() inout nothrow { return __ptr_.first; } + + __compressed_pair!(pointer, deleter_type) __ptr_; + } +} + + +private: + +template ClassOrPtr(T) +{ + static if (is(T == class)) + alias ClassOrPtr = T; + else + alias ClassOrPtr = T*; +} diff --git a/libphobos/libdruntime/core/stdcpp/new_.d b/libphobos/libdruntime/core/stdcpp/new_.d new file mode 100644 index 0000000..77c179c --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/new_.d @@ -0,0 +1,186 @@ +/** + * D binding to C++ + * + * Copyright: Copyright (c) 2019 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/new_.d) + */ + +module core.stdcpp.new_; + +import core.stdcpp.xutility : __cpp_sized_deallocation, __cpp_aligned_new; +import core.stdcpp.exception : exception; + +// TODO: this really should come from __traits(getTargetInfo, "defaultNewAlignment") +version (D_LP64) + enum size_t __STDCPP_DEFAULT_NEW_ALIGNMENT__ = 16; +else + enum size_t __STDCPP_DEFAULT_NEW_ALIGNMENT__ = 8; + +extern (C++, "std") +{ + /// + struct nothrow_t {} + + /// + enum align_val_t : size_t { defaultAlignment = __STDCPP_DEFAULT_NEW_ALIGNMENT__ }; + + /// + class bad_alloc : exception + { + @nogc: + /// + this() { super("bad allocation", 1); } + } +} + + +/// +T* cpp_new(T, Args...)(auto ref Args args) if (!is(T == class)) +{ + import core.lifetime : emplace, forward; + + T* mem = cast(T*)__cpp_new(T.sizeof); + return mem.emplace(forward!args); +} + +/// +T cpp_new(T, Args...)(auto ref Args args) if (is(T == class)) +{ + import core.lifetime : emplace, forward; + + T mem = cast(T)__cpp_new(__traits(classInstanceSize, T)); + return mem.emplace(forward!args); +} + +/// +void cpp_delete(T)(T* ptr) if (!is(T == class)) +{ + destroy!false(*ptr); + __cpp_delete(ptr); +} + +/// +void cpp_delete(T)(T instance) if (is(T == class)) +{ + destroy!false(instance); + __cpp_delete(cast(void*) instance); +} + + +// raw C++ functions +extern(C++): +@nogc: + +/// Binding for ::operator new(std::size_t count) +pragma(mangle, __new_mangle) +void* __cpp_new(size_t count); + +/// Binding for ::operator new(std::size_t count, const std::nothrow_t&) +pragma(mangle, __new_nothrow_mangle) +void* __cpp_new_nothrow(size_t count, ref const(nothrow_t) = std_nothrow) nothrow; + +/// Binding for ::operator delete(void* ptr) +pragma(mangle, __delete_mangle) +void __cpp_delete(void* ptr); + +/// Binding for ::operator delete(void* ptr, const std::nothrow_t& tag) +pragma(mangle, __delete_nothrow_mangle) +void __cpp_delete_nothrow(void* ptr, ref const(nothrow_t) = std_nothrow) nothrow; + +static if (__cpp_sized_deallocation) +{ + /// Binding for ::operator delete(void* ptr, size_t size) + pragma(mangle, __delete_size_mangle) + void __cpp_delete_size(void* ptr, size_t size); +} +static if (__cpp_aligned_new) +{ + /// Binding for ::operator new(std::size_t count, std::align_val_t al) + pragma(mangle, __new_align_mangle) + void* __cpp_new_aligned(size_t count, align_val_t alignment); + + /// Binding for ::operator new(std::size_t count, std::align_val_t al, const std::nothrow_t&) + pragma(mangle, __new_aligned_nothrow_mangle) + void* __cpp_new_aligned_nothrow(size_t count, align_val_t alignment, ref const(nothrow_t) = std_nothrow) nothrow; + + /// Binding for ::operator delete(void* ptr, std::align_val_t al) + pragma(mangle, __delete_align_mangle) + void __cpp_delete_aligned(void* ptr, align_val_t alignment); + + /// Binding for ::operator delete(void* ptr, std::align_val_t al, const std::nothrow_t& tag) + pragma(mangle, __delete_align_nothrow_mangle) + void __cpp_delete_align_nothrow(void* ptr, align_val_t alignment, ref const(nothrow_t) = std_nothrow) nothrow; + + /// Binding for ::operator delete(void* ptr, size_t size, std::align_val_t al) + pragma(mangle, __delete_size_align_mangle) + void __cpp_delete_size_aligned(void* ptr, size_t size, align_val_t alignment); +} + +private: +extern (D): + +__gshared immutable nothrow_t std_nothrow; + +// we have to hard-code the mangling for the global new/delete operators +version (CppRuntime_Microsoft) +{ + version (D_LP64) + { + enum __new_mangle = "??2@YAPEAX_K@Z"; + enum __new_nothrow_mangle = "??2@YAPEAX_KAEBUnothrow_t@std@@@Z"; + enum __delete_mangle = "??3@YAXPEAX@Z"; + enum __delete_nothrow_mangle = "??3@YAXPEAXAEBUnothrow_t@std@@@Z"; + enum __delete_size_mangle = "??3@YAXPEAX_K@Z"; + enum __new_align_mangle = "??2@YAPEAX_KW4align_val_t@std@@@Z"; + enum __new_aligned_nothrow_mangle = "??2@YAPEAX_KW4align_val_t@std@@AEBUnothrow_t@1@@Z"; + enum __delete_align_mangle = "??3@YAXPEAXW4align_val_t@std@@@Z"; + enum __delete_align_nothrow_mangle = "??3@YAXPEAXW4align_val_t@std@@AEBUnothrow_t@1@@Z"; + enum __delete_size_align_mangle = "??3@YAXPEAX_KW4align_val_t@std@@@Z"; + } + else + { + enum __new_mangle = "??2@YAPAXI@Z"; + enum __new_nothrow_mangle = "??2@YAPAXIABUnothrow_t@std@@@Z"; + enum __delete_mangle = "??3@YAXPAX@Z"; + enum __delete_nothrow_mangle = "??3@YAXPAXABUnothrow_t@std@@@Z"; + enum __delete_size_mangle = "??3@YAXPAXI@Z"; + enum __new_align_mangle = "??2@YAPAXIW4align_val_t@std@@@Z"; + enum __new_aligned_nothrow_mangle = "??2@YAPAXIW4align_val_t@std@@ABUnothrow_t@1@@Z"; + enum __delete_align_mangle = "??3@YAXPAXW4align_val_t@std@@@Z"; + enum __delete_align_nothrow_mangle = "??3@YAXPAXW4align_val_t@std@@ABUnothrow_t@1@@Z"; + enum __delete_size_align_mangle = "??3@YAXPAXIW4align_val_t@std@@@Z"; + } +} +else +{ + version (D_LP64) + { + enum __new_mangle = "_Znwm"; + enum __new_nothrow_mangle = "_ZnwmRKSt9nothrow_t"; + enum __delete_mangle = "_ZdlPv"; + enum __delete_nothrow_mangle = "_ZdlPvRKSt9nothrow_t"; + enum __delete_size_mangle = "_ZdlPvm"; + enum __new_align_mangle = "_ZnwmSt11align_val_t"; + enum __new_aligned_nothrow_mangle = "_ZnwmSt11align_val_tRKSt9nothrow_t"; + enum __delete_align_mangle = "_ZdlPvSt11align_val_t"; + enum __delete_align_nothrow_mangle = "_ZdlPvSt11align_val_tRKSt9nothrow_t"; + enum __delete_size_align_mangle = "_ZdlPvmSt11align_val_t"; + } + else + { + enum __new_mangle = "_Znwj"; + enum __new_nothrow_mangle = "_ZnwjRKSt9nothrow_t"; + enum __delete_mangle = "_ZdlPv"; + enum __delete_nothrow_mangle = "_ZdlPvRKSt9nothrow_t"; + enum __delete_size_mangle = "_ZdlPvj"; + enum __new_align_mangle = "_ZnwjSt11align_val_t"; + enum __new_aligned_nothrow_mangle = "_ZnwjSt11align_val_tRKSt9nothrow_t"; + enum __delete_align_mangle = "_ZdlPvSt11align_val_t"; + enum __delete_align_nothrow_mangle = "_ZdlPvSt11align_val_tRKSt9nothrow_t"; + enum __delete_size_align_mangle = "_ZdlPvjSt11align_val_t"; + } +} diff --git a/libphobos/libdruntime/core/stdcpp/string.d b/libphobos/libdruntime/core/stdcpp/string.d new file mode 100644 index 0000000..1cdb0f4 --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/string.d @@ -0,0 +1,2593 @@ +/** + * D header file for interaction with C++ std::string. + * + * Copyright: Copyright (c) 2019 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Guillaume Chatelet + * Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/string.d) + */ + +module core.stdcpp.string; + +import core.stdcpp.allocator; +import core.stdcpp.xutility : StdNamespace; +import core.stdc.stddef : wchar_t; + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (Darwin) +{ + // Apple decided to rock a different ABI... good for them! + version = _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT; +} + +version (CppRuntime_Gcc) +{ + version (_GLIBCXX_USE_CXX98_ABI) + { + private enum StringNamespace = "std"; + version = __GTHREADS; + } + else + { + import core.internal.traits : AliasSeq; + private enum StringNamespace = AliasSeq!("std", "__cxx11"); + } +} +else + alias StringNamespace = StdNamespace; + +enum DefaultConstruct { value } + +/// Constructor argument for default construction +enum Default = DefaultConstruct(); + +@nogc: + +/** + * Character traits classes specify character properties and provide specific + * semantics for certain operations on characters and sequences of characters. + */ +extern(C++, (StdNamespace)) struct char_traits(CharT) +{ + alias char_type = CharT; + + static size_t length(const(char_type)* s) @trusted pure nothrow @nogc + { + static if (is(char_type == char) || is(char_type == ubyte)) + { + import core.stdc.string : strlen; + return strlen(s); + } + else + { + size_t len = 0; + for (; *s != char_type(0); ++s) + ++len; + return len; + } + } + + static char_type* move(char_type* s1, const char_type* s2, size_t n) @trusted pure nothrow @nogc + { + import core.stdc.string : memmove; + import core.stdc.wchar_ : wmemmove; + import core.stdc.stddef : wchar_t; + + if (n == 0) + return s1; + + version (CRuntime_Microsoft) + { + enum crt = __traits(getTargetInfo, "cppRuntimeLibrary"); + static if (crt.length >= 6 && crt[0 .. 6] == "msvcrt") + enum use_wmemmove = false; // https://issues.dlang.org/show_bug.cgi?id=20456 + else + enum use_wmemmove = true; + } + else + enum use_wmemmove = true; + + static if (use_wmemmove + && (is(char_type == wchar_t) + || is(char_type == ushort) && wchar_t.sizeof == ushort.sizeof // Windows + || is(char_type == uint) && wchar_t.sizeof == uint.sizeof)) // POSIX + return cast(char_type*) wmemmove(s1, s2, n); + else + return cast(char_type*) memmove(s1, s2, n * char_type.sizeof); + } +} + +// I don't think we can have these here, otherwise symbols are emit to druntime, and we don't want that... +//alias std_string = basic_string!char; +//alias std_u16string = basic_string!wchar; // TODO: can't mangle these yet either... +//alias std_u32string = basic_string!dchar; +//alias std_wstring = basic_string!wchar_t; // TODO: we can't mangle wchar_t properly (yet?) + +/** + * D language counterpart to C++ std::basic_string. + * + * C++ reference: $(LINK2 https://en.cppreference.com/w/cpp/string/basic_string) + */ +extern(C++, class) +extern(C++, (StringNamespace)) +struct basic_string(T, Traits = char_traits!T, Alloc = allocator!T) +{ +extern(D): +@nogc: + + /// + enum size_type npos = size_type.max; + + /// + alias size_type = size_t; + /// + alias difference_type = ptrdiff_t; + /// + alias value_type = T; + /// + alias traits_type = Traits; + /// + alias allocator_type = Alloc; + /// + alias pointer = value_type*; + /// + alias const_pointer = const(value_type)*; + + /// + alias toString = as_array; + + /// MSVC allocates on default initialisation in debug, which can't be modelled by D `struct` + @disable this(); + + /// + alias length = size; + /// + alias opDollar = length; + /// + bool empty() const nothrow @safe { return size() == 0; } + + /// + size_t[2] opSlice(size_t dim : 0)(size_t start, size_t end) const pure nothrow @safe @nogc { return [start, end]; } + + /// + ref inout(T) opIndex(size_t index) inout pure nothrow @safe @nogc { return as_array[index]; } + /// + inout(T)[] opIndex(size_t[2] slice) inout pure nothrow @safe @nogc { return as_array[slice[0] .. slice[1]]; } + /// + inout(T)[] opIndex() inout pure nothrow @safe @nogc { return as_array(); } + + /// Two `basic_string`s are equal if they represent the same sequence of code units. + bool opEquals(scope const ref basic_string s) const pure nothrow @safe { return as_array == s.as_array; } + /// ditto + bool opEquals(scope const T[] s) const pure nothrow @safe { return as_array == s; } + + /// Performs lexicographical comparison. + int opCmp(scope const ref basic_string rhs) const pure nothrow @safe { return __cmp(as_array, rhs.as_array); } + /// ditto + int opCmp(scope const T[] rhs) const pure nothrow @safe { return __cmp(as_array, rhs); } + + /// Hash to allow `basic_string`s to be used as keys for built-in associative arrays. + /// **The result will generally not be the same as C++ `std::hash>`.** + size_t toHash() const @nogc nothrow pure @safe { return .hashOf(as_array); } + + /// + void clear() { eos(0); } // TODO: bounds-check + /// + void resize(size_type n, T c = T(0)) @trusted + { + if (n <= size()) + eos(n); + else + append(n - size(), c); + } + + /// + ref inout(T) front() inout nothrow @safe { return this[0]; } + /// + ref inout(T) back() inout nothrow @safe { return this[$-1]; } + + /// + const(T)* c_str() const nothrow @safe { return data(); } + + // Modifiers + /// + ref basic_string opAssign()(auto ref basic_string str) { return assign(str); } +// ref basic_string assign(size_type n, T c); + /// + ref basic_string opAssign(const(T)[] str) { return assign(str); } + /// + ref basic_string opAssign(T c) { return assign((&c)[0 .. 1]); } + + /// + ref basic_string opIndexAssign(T c, size_t index) { as_array[index] = c; return this; } + /// + ref basic_string opIndexAssign(T c, size_t[2] slice) { as_array[slice[0] .. slice[1]] = c; return this; } + /// + ref basic_string opIndexAssign(const(T)[] str, size_t[2] slice) { as_array[slice[0] .. slice[1]] = str[]; return this; } + /// + ref basic_string opIndexAssign(T c) { as_array[] = c; return this; } + /// + ref basic_string opIndexAssign(const(T)[] str) { as_array[] = str[]; return this; } + + /// + ref basic_string opIndexOpAssign(string op)(T c, size_t index) { mixin("as_array[index] " ~ op ~ "= c;"); return this; } + /// + ref basic_string opIndexOpAssign(string op)(T c, size_t[2] slice) { mixin("as_array[slice[0] .. slice[1]] " ~ op ~ "= c;"); return this; } + /// + ref basic_string opIndexOpAssign(string op)(const(T)[] str, size_t[2] slice) { mixin("as_array[slice[0] .. slice[1]] " ~ op ~ "= str[];"); return this; } + /// + ref basic_string opIndexOpAssign(string op)(T c) { mixin("as_array[] " ~ op ~ "= c;"); return this; } + /// + ref basic_string opIndexOpAssign(string op)(const(T)[] str) { mixin("as_array[] " ~ op ~ "= str[];"); return this; } + /// + ref basic_string append(T c) { return append((&c)[0 .. 1]); } + /// + ref basic_string opOpAssign(string op : "~")(const(T)[] str) { return append(str); } + /// + ref basic_string opOpAssign(string op : "~")(T c) { return append((&c)[0 .. 1]); } + + /// + ref basic_string insert(size_type pos, ref const(basic_string) str) { return insert(pos, str.data(), str.size()); } + /// + ref basic_string insert(size_type pos, ref const(basic_string) str, size_type subpos, size_type sublen) @trusted + { + const _strsz = str.size(); + assert(subpos <= _strsz); +// if (subpos > _strsz) +// throw new RangeError("subpos exceeds length of str"); + return insert(pos, str.data() + subpos, min(sublen, _strsz - subpos)); + } + /// + ref basic_string insert(S : size_type)(S pos, const(T)* s) + { + // This overload is declared as a template to give precedence to the slice overload const(T)[] in case of conflict. + assert(s); + return insert(pos, s, traits_type.length(s)); + } + /// + ref basic_string insert(size_type pos, const(T)[] s) { insert(pos, &s[0], s.length); return this; } + + /// + ref basic_string erase(size_type pos = 0) // TODO: bounds-check + { +// _My_data._Check_offset(pos); + eos(pos); + return this; + } + /// + ref basic_string erase(size_type pos, size_type len) // TODO: bounds-check + { +// _My_data._Check_offset(pos); + T[] str = as_array(); + size_type new_len = str.length - len; + this[pos .. new_len] = this[pos + len .. str.length]; // TODO: should be memmove! + eos(new_len); + return this; + } + + /// + ref basic_string replace()(size_type pos, size_type len, auto ref basic_string str) { return replace(pos, len, str.data(), str.size()); } + /// + ref basic_string replace()(size_type pos, size_type len, auto ref basic_string str, + size_type subpos, size_type sublen=npos) + { + size_type strsz = str.size(); + assert(subpos <= strsz); +// if (subpos > strsz) +// throw new RangeError("subpos exceeds size of str"); + return replace(pos, len, str.data() + subpos, min(sublen, strsz - subpos)); + } + /// + ref basic_string replace(size_type pos, size_type len, const(value_type)[] s) { return replace(pos, len, s.ptr, s.length); } + /// + ref basic_string replace(S : size_type)(S pos, size_type len, const(value_type)* s) + { + // This overload is declared as a template to give precedence to the slice overload const(T)[] in case of conflict. + assert(s !is null, "string::replace received null"); + return replace(pos, len, s, traits_type.length(s)); + } + + /// + void push_back(T c) @trusted { append((&c)[0 .. 1]); } + /// + void pop_back() { erase(size() - 1); } + + version (CppRuntime_Microsoft) + { + //---------------------------------------------------------------------------------- + // Microsoft runtime + //---------------------------------------------------------------------------------- + + /// + this(DefaultConstruct) { _Alloc_proxy(); _Tidy_init(); } + /// + this(const(T)[] str) { _Alloc_proxy(); _Tidy_init(); assign(str); } + /// + this(const(T)[] str, ref const(allocator_type) al) { _Alloc_proxy(); _AssignAllocator(al); _Tidy_init(); assign(str); } + /// + this(this) + { + _Alloc_proxy(); + if (_Get_data()._IsAllocated()) + { + T[] _Str = _Get_data()._Mystr; + _Tidy_init(); + assign(_Str); + } + } + + /// + ~this() { _Tidy_deallocate(); } + + /// + ref inout(Alloc) get_allocator() inout { return _Getal(); } + + /// + size_type max_size() const nothrow @safe { return ((size_t.max / T.sizeof) - 1) / 2; } // HACK: clone the windows version precisely? + + /// + size_type size() const nothrow @safe { return _Get_data()._Mysize; } + /// + size_type capacity() const nothrow @safe { return _Get_data()._Myres; } + /// + inout(T)* data() inout @safe { return _Get_data()._Myptr; } + /// + inout(T)[] as_array() inout nothrow @trusted { return _Get_data()._Myptr[0 .. _Get_data()._Mysize]; } + /// + ref inout(T) at(size_type i) inout nothrow @trusted { return _Get_data()._Myptr[0 .. _Get_data()._Mysize][i]; } + + /// + ref basic_string assign(const(T)[] str) + { + size_type _Count = str.length; + auto _My_data = &_Get_data(); + if (_Count <= _My_data._Myres) + { + T* _Old_ptr = _My_data._Myptr; + _My_data._Mysize = _Count; + _Old_ptr[0 .. _Count] = str[]; // TODO: this needs to be a memmove(), does that work here? + _Old_ptr[_Count] = T(0); + return this; + } + return _Reallocate_for(_Count, (T* _New_ptr, size_type _Count, const(T)* _Ptr) nothrow { + _New_ptr[0 .. _Count] = _Ptr[0 .. _Count]; + _New_ptr[_Count] = T(0); + }, str.ptr); + } + + /// + ref basic_string assign(const ref basic_string str) + { + if (&this != &str) + assign(str.as_array); + return this; + } + + /// + ref basic_string append(const(T)[] str) + { + size_type _Count = str.length; + auto _My_data = &_Get_data(); + size_type _Old_size = _My_data._Mysize; + if (_Count <= _My_data._Myres - _Old_size) + { + pointer _Old_ptr = _My_data._Myptr; + _My_data._Mysize = _Old_size + _Count; + _Old_ptr[_Old_size .. _Old_size + _Count] = str[]; // TODO: this needs to be a memmove(), does that work here? + _Old_ptr[_Old_size + _Count] = T(0); + return this; + } + return _Reallocate_grow_by(_Count, (T* _New_ptr, const(T)[] _Old_str, const(T)[] _Str) { + _New_ptr[0 .. _Old_str.length] = _Old_str[]; + _New_ptr[_Old_str.length .. _Old_str.length + _Str.length] = _Str[]; + _New_ptr[_Old_str.length + _Str.length] = T(0); + }, str); + } + + /// + ref basic_string append(size_type n, T c) + { + alias _Count = n; + alias _Ch = c; + auto _My_data = &_Get_data(); + const size_type _Old_size = _My_data._Mysize; + if (_Count <= _My_data._Myres - _Old_size) + { + _My_data._Mysize = _Old_size + _Count; + pointer _Old_ptr = _My_data._Myptr(); + _Old_ptr[_Old_size .. _Old_size + _Count] = _Ch; + _Old_ptr[_Old_size + _Count] = T(0); + return this; + } + + return _Reallocate_grow_by(_Count, (T* _New_ptr, const(T)[] _Old_str, size_type _Count, T _Ch) { + _New_ptr[0 .. _Old_str.length] = _Old_str[]; + _New_ptr[_Old_str.length .. _Old_str.length + _Count] = _Ch; + _New_ptr[_Old_str.length + _Count] = T(0); + }, _Count, _Ch); + } + + /// + void reserve(size_type _Newcap = 0) + { + // determine new minimum length of allocated storage + + auto _My_data = &_Get_data(); + + if (_My_data._Mysize > _Newcap) + { + // requested capacity is not large enough for current size, ignore + return; // nothing to do + } + + if (_My_data._Myres == _Newcap) + { + // we're already at the requested capacity + return; // nothing to do + } + + if (_My_data._Myres < _Newcap) + { + // reallocate to grow + const size_type _Old_size = _My_data._Mysize; + _Reallocate_grow_by( + _Newcap - _Old_size, (T* _New_ptr, const(T)[] _Old_str) { + _New_ptr[0 .. _Old_str.length] = _Old_str[]; + _New_ptr[_Old_str.length] = _Old_str.ptr[_Old_str.length]; + }); + + _My_data._Mysize = _Old_size; + return; + } + + if (_My_data._BUF_SIZE > _Newcap && _My_data._Large_string_engaged()) + { + // deallocate everything; switch back to "small" mode + _Become_small(); + return; + } + + // ignore requests to reserve to [_BUF_SIZE, _Myres) + } + + /// + void shrink_to_fit() + { + // reduce capacity + + auto _My_data = &_Get_data(); + if (!_My_data._Large_string_engaged()) + { + // can't shrink from small mode + return; + } + + if (_My_data._Mysize < _My_data._BUF_SIZE) + { + _Become_small(); + return; + } + + const size_type _Target_capacity = min(_My_data._Mysize | _My_data._ALLOC_MASK, max_size()); + if (_Target_capacity < _My_data._Myres) + { + // worth shrinking, do it + auto _Al = &_Getal(); + pointer _New_ptr = _Al.allocate(_Target_capacity + 1); // throws + _Base._Orphan_all(); + _New_ptr[0 .. _My_data._Mysize + 1] = _My_data._Bx._Ptr[0 .. _My_data._Mysize + 1]; + _Al.deallocate(_My_data._Bx._Ptr, _My_data._Myres + 1); + _My_data._Bx._Ptr = _New_ptr; + _My_data._Myres = _Target_capacity; + } + } + + /// + ref basic_string insert(size_type pos, const(T)* s, size_type n) + { + // insert [_Ptr, _Ptr + _Count) at _Off + alias _Off = pos; + alias _Ptr = s; + alias _Count = n; + auto _My_data = &_Get_data(); +// _My_data._Check_offset(_Off); + const size_type _Old_size = _My_data._Mysize; + if (_Count <= _My_data._Myres - _Old_size) + { + _My_data._Mysize = _Old_size + _Count; + T* _Old_ptr = _My_data._Myptr(); + T* _Insert_at = _Old_ptr + _Off; + // the range [_Ptr, _Ptr + _Ptr_shifted_after) is left alone by moving the suffix out, + // while the range [_Ptr + _Ptr_shifted_after, _Ptr + _Count) shifts down by _Count + size_type _Ptr_shifted_after; + if (_Ptr + _Count <= _Insert_at || _Ptr > _Old_ptr + _Old_size) + { + // inserted content is before the shifted region, or does not alias + _Ptr_shifted_after = _Count; // none of _Ptr's data shifts + } + else if (_Insert_at <= _Ptr) + { + // all of [_Ptr, _Ptr + _Count) shifts + _Ptr_shifted_after = 0; + } + else + { + // [_Ptr, _Ptr + _Count) contains _Insert_at, so only the part after _Insert_at shifts + _Ptr_shifted_after = cast(size_type)(_Insert_at - _Ptr); + } + + _Traits.move(_Insert_at + _Count, _Insert_at, _Old_size - _Off + 1); // move suffix + null down + _Insert_at[0 .. _Ptr_shifted_after] = _Ptr[0 .. _Ptr_shifted_after]; + (_Insert_at + _Ptr_shifted_after)[0 .. _Count - _Ptr_shifted_after] = (_Ptr + _Count + _Ptr_shifted_after)[0 .. _Count - _Ptr_shifted_after]; + return this; + } + + return _Reallocate_grow_by( + _Count, + (T* _New_ptr, const(T)[] _Old_str, size_type _Off, const(T)* _Ptr, size_type _Count) { + _New_ptr[0 .. _Off] = _Old_str[0 .. _Off]; + _New_ptr[_Off .. _Off + _Count] = _Ptr[0 .. _Count]; + _New_ptr[_Off + _Count .. _Old_str.length + _Count + 1] = _Old_str.ptr[_Off .. _Old_str.length + 1]; + }, + _Off, _Ptr, _Count); + } + + /// + ref basic_string insert(size_type pos, size_type n, T c) + { + // insert _Count * _Ch at _Off + alias _Off = pos; + alias _Count = n; + alias _Ch = c; + auto _My_data = &_Get_data(); +// _My_data._Check_offset(_Off); + const size_type _Old_size = _My_data._Mysize; + if (_Count <= _My_data._Myres - _Old_size) + { + _My_data._Mysize = _Old_size + _Count; + T* _Old_ptr = _My_data._Myptr(); + T* _Insert_at = _Old_ptr + _Off; + _Traits.move(_Insert_at + _Count, _Insert_at, _Old_size - _Off + 1); // move suffix + null down + _Insert_at[0 .. _Count] = _Ch; // fill hole + return this; + } + + return _Reallocate_grow_by( + _Count, + (T* _New_ptr, const(T)[] _Old_str, size_type _Off, size_type _Count, T _Ch) + { + _New_ptr[0 .. _Off] = _Old_str[0 .. _Off]; + _New_ptr[_Off .. _Off + _Count] = _Ch; + _New_ptr[_Off + _Count .. _Old_str.length + 1] = _Old_str.ptr[_Off .. _Old_str.length + 1]; + }, + _Off, _Count, _Ch); + } + + /// + ref basic_string replace(size_type pos, size_type len, const(T)* s, size_type slen) + { + // replace [_Off, _Off + _N0) with [_Ptr, _Ptr + _Count) + alias _Off = pos; + alias _N0 = len; + alias _Ptr = s; + alias _Count = slen; + auto _My_data = &_Get_data(); +// _Mypair._Myval2._Check_offset(_Off); + _N0 = _My_data._Clamp_suffix_size(_Off, _N0); + if (_N0 == _Count) + { + // size doesn't change, so a single move does the trick + _Traits.move(_My_data._Myptr() + _Off, _Ptr, _Count); + return this; + } + + const size_type _Old_size = _My_data._Mysize; + const size_type _Suffix_size = _Old_size - _N0 - _Off + 1; + if (_Count < _N0) + { + // suffix shifts backwards; we don't have to move anything out of the way + _My_data._Mysize = _Old_size - (_N0 - _Count); + T* _Old_ptr = _My_data._Myptr(); + T* _Insert_at = _Old_ptr + _Off; + _Traits.move(_Insert_at, _Ptr, _Count); + _Traits.move(_Insert_at + _Count, _Insert_at + _N0, _Suffix_size); + return this; + } + + const size_type _Growth = cast(size_type)(_Count - _N0); + if (_Growth <= _My_data._Myres - _Old_size) + { + // growth fits + _My_data._Mysize = _Old_size + _Growth; + T* _Old_ptr = _My_data._Myptr(); + T* _Insert_at = _Old_ptr + _Off; + T* _Suffix_at = _Insert_at + _N0; + + size_type _Ptr_shifted_after; // see rationale in insert + if (_Ptr + _Count <= _Insert_at || _Ptr > _Old_ptr + _Old_size) + _Ptr_shifted_after = _Count; + else if (_Suffix_at <= _Ptr) + _Ptr_shifted_after = 0; + else + _Ptr_shifted_after = cast(size_type)(_Suffix_at - _Ptr); + + _Traits.move(_Suffix_at + _Growth, _Suffix_at, _Suffix_size); + // next case must be move, in case _Ptr begins before _Insert_at and contains part of the hole; + // this case doesn't occur in insert because the new content must come from outside the removed + // content there (because in insert there is no removed content) + _Traits.move(_Insert_at, _Ptr, _Ptr_shifted_after); + // the next case can be copy, because it comes from the chunk moved out of the way in the + // first move, and the hole we're filling can't alias the chunk we moved out of the way + _Insert_at[_Ptr_shifted_after .. _Count] = _Ptr[_Growth + _Ptr_shifted_after .. _Growth + _Count]; + return this; + } + + return _Reallocate_grow_by( + _Growth, + (T* _New_ptr, const(T)[] _Old_str, size_type _Off, size_type _N0, const(T)* _Ptr, size_type _Count) { + _New_ptr[0 .. _Off] = _Old_str[0 .. _Off]; + _New_ptr[_Off .. _Count] = _Ptr[0 .. _Count]; + const __n = _Old_str.length - _N0 - _Off + 1; + (_New_ptr + _Off + _Count)[0 .. __n] = (_Old_str.ptr + _Off + _N0)[0 .. __n]; + }, + _Off, _N0, _Ptr, _Count); + } + + /// + ref basic_string replace(size_type _Off, size_type _N0, size_type _Count, T _Ch) + { + // replace [_Off, _Off + _N0) with _Count * _Ch + auto _My_data = &_Get_data(); +// _My_data._Check_offset(_Off); + _N0 = _My_data._Clamp_suffix_size(_Off, _N0); + if (_Count == _N0) + { + _My_data._Myptr()[_Off .. _Off + _Count] = _Ch; + return this; + } + + const size_type _Old_size = _My_data._Mysize; + if (_Count < _N0 || _Count - _N0 <= _My_data._Myres - _Old_size) + { + // either we are shrinking, or the growth fits + _My_data._Mysize = _Old_size + _Count - _N0; // may temporarily overflow; + // OK because size_type must be unsigned + T* _Old_ptr = _My_data._Myptr(); + T* _Insert_at = _Old_ptr + _Off; + _Traits.move(_Insert_at + _Count, _Insert_at + _N0, _Old_size - _N0 - _Off + 1); + _Insert_at[0 .. _Count] = _Ch; + return this; + } + + return _Reallocate_grow_by( + _Count - _N0, + (T* _New_ptr, const(T)[] _Old_str, size_type _Off, size_type _N0, size_type _Count, T _Ch) { + _New_ptr[0 .. _Off] = _Old_str[0 .. _Off]; + _New_ptr[_Off .. _Off + _Count] = _Ch; + const __n = _Old_str.length - _N0 - _Off + 1; + (_New_ptr + _Off + _Count)[0 .. __n] = (_Old_str.ptr + _Off + _N0)[0 .. __n]; + }, + _Off, _N0, _Count, _Ch); + } + + /// + void swap(ref basic_string _Right) + { + import core.internal.lifetime : swap; + import core.stdcpp.type_traits : is_empty; + + if (&this != &_Right) + { + static if (!is_empty!allocator_type.value + && allocator_traits!allocator_type.propagate_on_container_swap) + { + swap(_Getal(), _Right._Getal()); + } + + static if (_ITERATOR_DEBUG_LEVEL != 0) + { + auto _My_data = &_Get_data(); + const bool _My_large = _My_data._Large_string_engaged(); + const bool _Right_large = _Right._Get_data()._Large_string_engaged(); + if (!_My_large) + _Base._Orphan_all(); + + if (!_Right_large) + _Right._Base._Orphan_all(); + + if (_My_large || _Right_large) + _My_data._Base._Swap_proxy_and_iterators(_Right._Get_data()._Base); + } // _ITERATOR_DEBUG_LEVEL != 0 + } + + _Swap_data!_Can_memcpy_val(_Right); + } + + private: + import core.stdcpp.xutility : MSVCLinkDirectives; + import core.stdcpp.xutility : _Container_base; + + alias _Traits = traits_type; + alias _Scary_val = _String_val!T; + + enum bool _Can_memcpy_val = is(_Traits == char_traits!E, E) && is(pointer == U*, U); + // This offset skips over the _Container_base members, if any + enum size_t _Memcpy_val_offset = _Size_after_ebco_v!_Container_base; + enum size_t _Memcpy_val_size = _Scary_val.sizeof - _Memcpy_val_offset; + + // Make sure the object files wont link against mismatching objects + mixin MSVCLinkDirectives!true; + + pragma (inline, true) + { + void eos(size_type offset) nothrow { _Get_data()._Myptr[_Get_data()._Mysize = offset] = T(0); } + + ref inout(_Base.Alloc) _Getal() inout nothrow @safe { return _Base._Mypair._Myval1; } + ref inout(_Base.ValTy) _Get_data() inout nothrow @safe { return _Base._Mypair._Myval2; } + } + + void _Alloc_proxy() nothrow + { + static if (_ITERATOR_DEBUG_LEVEL > 0) + _Base._Alloc_proxy(); + } + + void _AssignAllocator(ref const(allocator_type) al) nothrow + { + static if (_Base._Mypair._HasFirst) + _Getal() = al; + } + + void _Become_small() + { + // release any held storage and return to small string mode + // pre: *this is in large string mode + // pre: this is small enough to return to small string mode + auto _My_data = &_Get_data(); + _Base._Orphan_all(); + pointer _Ptr = _My_data._Bx._Ptr; + auto _Al = &_Getal(); + _My_data._Bx._Buf[0 .. _My_data._Mysize + 1] = _Ptr[0 .. _My_data._Mysize + 1]; + _Al.deallocate(_Ptr, _My_data._Myres + 1); + _My_data._Myres = _My_data._BUF_SIZE - 1; + } + + void _Tidy_init() nothrow + { + auto _My_data = &_Get_data(); + _My_data._Mysize = 0; + _My_data._Myres = _My_data._BUF_SIZE - 1; + _My_data._Bx._Buf[0] = T(0); + } + + size_type _Calculate_growth(size_type _Requested) const nothrow + { + auto _My_data = &_Get_data(); + size_type _Masked = _Requested | _My_data._ALLOC_MASK; + size_type _Old = _My_data._Myres; + size_type _Expanded = _Old + _Old / 2; + return _Masked > _Expanded ? _Masked : _Expanded; + } + + ref basic_string _Reallocate_for(_ArgTys...)(size_type _New_size, void function(pointer, size_type, _ArgTys) nothrow @nogc _Fn, _ArgTys _Args) + { + auto _My_data = &_Get_data(); + size_type _Old_capacity = _My_data._Myres; + size_type _New_capacity = _Calculate_growth(_New_size); + auto _Al = &_Getal(); + pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws + _Base._Orphan_all(); + _My_data._Mysize = _New_size; + _My_data._Myres = _New_capacity; + _Fn(_New_ptr, _New_size, _Args); + if (_My_data._BUF_SIZE <= _Old_capacity) + _Al.deallocate(_My_data._Bx._Ptr, _Old_capacity + 1); + _My_data._Bx._Ptr = _New_ptr; + return this; + } + + ref basic_string _Reallocate_grow_by(_ArgTys...)(size_type _Size_increase, void function(pointer, const(T)[], _ArgTys) nothrow @nogc _Fn, _ArgTys _Args) + { + auto _My_data = &_Get_data(); + size_type _Old_size = _My_data._Mysize; + size_type _New_size = _Old_size + _Size_increase; + size_type _Old_capacity = _My_data._Myres; + size_type _New_capacity = _Calculate_growth(_New_size); + auto _Al = &_Getal(); + pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws + _Base._Orphan_all(); + _My_data._Mysize = _New_size; + _My_data._Myres = _New_capacity; + if (_My_data._BUF_SIZE <= _Old_capacity) + { + pointer _Old_ptr = _My_data._Bx._Ptr; + _Fn(_New_ptr, _Old_ptr[0 .. _Old_size], _Args); + _Al.deallocate(_Old_ptr, _Old_capacity + 1); + } + else + _Fn(_New_ptr, _My_data._Bx._Buf[0 .. _Old_size], _Args); + _My_data._Bx._Ptr = _New_ptr; + return this; + } + + void _Tidy_deallocate() + { + _Base._Orphan_all(); + auto _My_data = &_Get_data(); + if (_My_data._BUF_SIZE <= _My_data._Myres) + { + pointer _Ptr = _My_data._Bx._Ptr; + auto _Al = &_Getal(); + _Al.deallocate(_Ptr, _My_data._Myres + 1); + } + _My_data._Mysize = 0; + _My_data._Myres = _My_data._BUF_SIZE - 1; + _My_data._Bx._Buf[0] = T(0); + } + + void _Swap_data(bool _memcpy : true)(ref basic_string _Right) + { + import core.stdc.string : memcpy; + + // exchange _String_val instances with _Right, memcpy optimization + auto _My_data = &_Get_data(); + auto _My_data_mem = cast(ubyte*)_My_data + _Memcpy_val_offset; + auto _Right_data_mem = cast(ubyte*)(&_Right._Get_data()) + _Memcpy_val_offset; + ubyte[_Memcpy_val_size] _Temp_mem; + memcpy(_Temp_mem.ptr, _My_data_mem, _Memcpy_val_size); + memcpy(_My_data_mem, _Right_data_mem, _Memcpy_val_size); + memcpy(_Right_data_mem, _Temp_mem.ptr, _Memcpy_val_size); + } + + void _Swap_data(bool _memcpy : false)(ref basic_string _Right) + { + import core.lifetime : swap; + + // exchange _String_val instances with _Right, general case + auto _My_data = &_Get_data(); + auto _Right_data = &_Right._Get_data(); + const bool _My_large = _My_data._Large_string_engaged(); + const bool _Right_large = _Right_data._Large_string_engaged(); + if (_My_large) + { + if (_Right_large) // swap buffers, iterators preserved + swap(_My_data._Bx._Ptr, _Right_data._Bx._Ptr); + else // swap large with small + _Swap_bx_large_with_small(*_My_data, *_Right_data); + } + else + { + if (_Right_large) // swap small with large + _Swap_bx_large_with_small(*_Right_data, *_My_data); + else + { + enum _BUF_SIZE = _My_data._BUF_SIZE; + T[_BUF_SIZE] _Temp_buf; + _Temp_buf[0 .. _BUF_SIZE] = _My_data._Bx._Buf[0 .. _BUF_SIZE]; + _My_data._Bx._Buf[0 .. _BUF_SIZE] = _Right_data._Bx._Buf[0 .. _BUF_SIZE]; + _Right_data._Bx._Buf[0 .. _BUF_SIZE] = _Temp_buf[0 .. _BUF_SIZE]; + } + } + + swap(_My_data._Mysize, _Right_data._Mysize); + swap(_My_data._Myres, _Right_data._Myres); + } + + void _Swap_bx_large_with_small(ref _Scary_val _Starts_large, ref _Scary_val _Starts_small) + { + // exchange a string in large mode with one in small mode + pointer _Ptr = _Starts_large._Bx._Ptr; + _Starts_large._Bx._Buf[] = _Starts_small._Bx._Buf[]; + _Starts_small._Bx._Ptr = _Ptr; + } + + _String_alloc!(_String_base_types!(T, Alloc)) _Base; + } + else version (CppRuntime_Gcc) + { + version (_GLIBCXX_USE_CXX98_ABI) + { + //---------------------------------------------------------------------------------- + // Old GCC/libstdc++ ref-counted implementation + //---------------------------------------------------------------------------------- + + /// + this(DefaultConstruct) + { + version (_GLIBCXX_FULLY_DYNAMIC_STRING) + static_assert(false, "DO WE NEED THIS?"); + else + _M_data = _S_empty_rep()._M_refdata(); + } + /// + this(const(T)[] str, ref const(allocator_type) al) { _M_assign_allocator(al); this(str); } + /// + this(const(T)[] str) + { + _M_data = _S_construct(str.ptr, str.ptr + str.length, _M_get_allocator); + } + /// + this(const ref basic_string str) + { + import core.stdcpp.type_traits : is_empty; + + static if (!is_empty!allocator_type.value) + _M_Alloc = str.get_allocator(); + _M_data = str._M_rep()._M_grab(get_allocator(), str.get_allocator()); + } + + /// + ~this() { _M_rep()._M_dispose(get_allocator()); } + + /// + ref inout(Alloc) get_allocator() inout { return _M_get_allocator(); } + + /// + size_type max_size() const nothrow @safe { return _Rep._S_max_size; } + + /// + size_type size() const nothrow @safe { return _M_rep()._M_length; } + /// + size_type capacity() const nothrow { return _M_rep()._M_capacity; } + /// + inout(T)* data() inout @safe { return _M_data; } + /// + inout(T)[] as_array() inout nothrow @trusted { return _M_data[0 .. _M_rep()._M_length]; } + /// + ref inout(T) at(size_type i) inout nothrow { return _M_data[0 .. _M_rep()._M_length][i]; } + + /// + ref basic_string assign(const(T)[] str) + { + const(T)* __s = str.ptr; + size_t __n = str.length; +// __glibcxx_requires_string_len(__s, __n); + _M_check_length(size(), __n, "basic_string::assign"); + if (_M_disjunct(__s) || _M_rep()._M_is_shared()) + return _M_replace_safe(size_type(0), this.size(), __s, __n); + else + { + const size_type __pos = __s - _M_data; + if (__pos >= __n) + _S_copy(_M_data, __s, __n); + else if (__pos) + _S_move(_M_data, __s, __n); + _M_rep()._M_set_length_and_sharable(__n); + return this; + } + } + + /// + ref basic_string assign(const ref basic_string str) + { + if (_M_rep() != str._M_rep()) + { + // XXX MT + allocator_type __a = this.get_allocator(); + T* __tmp = str._M_rep()._M_grab(__a, str.get_allocator()); + _M_rep()._M_dispose(__a); + _M_data = __tmp; + } + return this; + } + + /// + ref basic_string append(const(T)[] str) + { + const(T)* __s = str.ptr; + size_t __n = str.length; +// __glibcxx_requires_string_len(__s, __n); + if (__n) + { + _M_check_length(size_type(0), __n, "basic_string::append"); + const size_type __len = __n + size(); + if (__len > capacity() || _M_rep()._M_is_shared()) + { + if (_M_disjunct(__s)) + reserve(__len); + else + { + const size_type __off = __s - _M_data; + reserve(__len); + __s = _M_data + __off; + } + } + _S_copy(_M_data + size(), __s, __n); + _M_rep()._M_set_length_and_sharable(__len); + } + return this; + } + + /// + ref basic_string append(size_type __n, T __c) + { + if (__n) + { + _M_check_length(size_type(0), __n, "basic_string::append"); + const size_type __len = __n + size(); + if (__len > capacity() || _M_rep()._M_is_shared()) + reserve(__len); + const __sz = size(); + _M_data[__sz .. __sz + __n] = __c; + _M_rep()._M_set_length_and_sharable(__len); + } + return this; + } + + /// + void reserve(size_type __res = 0) + { + if (__res != capacity() || _M_rep()._M_is_shared()) + { + // Make sure we don't shrink below the current size + if (__res < size()) + __res = size(); + allocator_type __a = get_allocator(); + T* __tmp = _M_rep()._M_clone(__a, __res - size()); + _M_rep()._M_dispose(__a); + _M_data = __tmp; + } + } + + /// + void shrink_to_fit() nothrow + { + if (capacity() > size()) + { + try reserve(0); + catch (Throwable) {} + } + } + + /// + ref basic_string insert(size_type __pos, const(T)* __s, size_type __n) + { +// __glibcxx_requires_string_len(__s, __n); + cast(void) _M_check(__pos, "basic_string::insert"); + _M_check_length(size_type(0), __n, "basic_string::insert"); + if (_M_disjunct(__s) || _M_rep()._M_is_shared()) + return _M_replace_safe(__pos, size_type(0), __s, __n); + else + { + // Work in-place. + const size_type __off = __s - _M_data; + _M_mutate(__pos, 0, __n); + __s = _M_data + __off; + T* __p = _M_data + __pos; + if (__s + __n <= __p) + __p[0 .. __n] = __s[0 .. __n]; + else if (__s >= __p) + __p[0 .. __n] = (__s + __n)[0 .. __n]; + else + { + const size_type __nleft = __p - __s; + __p[0 .. __nleft] = __s[0.. __nleft]; + (__p + __nleft)[0 .. __n - __nleft] = (__p + __n)[0 .. __n - __nleft]; + } + return this; + } + } + + /// + ref basic_string insert(size_type pos, size_type n, T c) + { + return _M_replace_aux(_M_check(pos, "basic_string::insert"), size_type(0), n, c); + } + + /// + ref basic_string replace(size_type __pos, size_type __n1, const(T)* __s, size_type __n2) + { +// __glibcxx_requires_string_len(__s, __n2); + cast(void) _M_check(__pos, "basic_string::replace"); + __n1 = _M_limit(__pos, __n1); + _M_check_length(__n1, __n2, "basic_string::replace"); + bool __left; + if (_M_disjunct(__s) || _M_rep()._M_is_shared()) + return _M_replace_safe(__pos, __n1, __s, __n2); + else if ((__left = __s + __n2 <= _M_data + __pos) == true || _M_data + __pos + __n1 <= __s) + { + // Work in-place: non-overlapping case. + size_type __off = __s - _M_data; + __left ? __off : (__off += __n2 - __n1); + _M_mutate(__pos, __n1, __n2); + (_M_data + __pos)[0 .. __n2] = (_M_data + __off)[0 .. __n2]; + return this; + } + else + { + // Todo: overlapping case. + auto __tmp = basic_string(__s[0 .. __n2]); + return _M_replace_safe(__pos, __n1, __tmp._M_data, __n2); + } + } + + /// + ref basic_string replace(size_type pos, size_type n1, size_type n2, T c) + { + return _M_replace_aux(_M_check(pos, "basic_string::replace"), _M_limit(pos, n1), n2, c); + } + + /// + void swap(ref basic_string __s) + { + if (_M_rep()._M_is_leaked()) + _M_rep()._M_set_sharable(); + if (__s._M_rep()._M_is_leaked()) + __s._M_rep()._M_set_sharable(); + if (this.get_allocator() == __s.get_allocator()) + { + T* __tmp = _M_data; + _M_data = __s._M_data; + __s._M_data = __tmp; + } + // The code below can usually be optimized away. + else + { + import core.lifetime : move; + + auto __tmp1 = basic_string(this[], __s.get_allocator()); + auto __tmp2 = basic_string(__s[], this.get_allocator()); + this = move(__tmp2); + __s = move(__tmp1); + } + } + + private: + import core.stdcpp.type_traits : is_empty; + + version (__GTHREADS) + { + import core.atomic; + alias _Atomic_word = int; // should we use atomic!int? + } + else + alias _Atomic_word = int; + + struct _Rep_base + { + size_type _M_length; + size_type _M_capacity; + _Atomic_word _M_refcount; + } + + struct _Rep + { + _Rep_base base; + alias base this; + + alias _Raw_bytes_alloc = Alloc.rebind!char; + + enum size_type _S_max_size = (((npos - _Rep_base.sizeof) / T.sizeof) - 1) / 4; + enum T _S_terminal = T(0); + + __gshared size_type[(_Rep_base.sizeof + T.sizeof + size_type.sizeof - 1) / size_type.sizeof] _S_empty_rep_storage; + + static ref _Rep _S_empty_rep() nothrow @trusted { return *cast(_Rep*)_S_empty_rep_storage.ptr; } + + void _M_set_sharable() nothrow + { + _M_refcount = 0; + } + + void _M_set_length_and_sharable(size_type __n) nothrow + { + if (&this != &_S_empty_rep()) + { + _M_set_sharable(); + _M_length = __n; + _M_refdata()[__n] = _S_terminal; + } + } + + bool _M_is_leaked() const nothrow + { + import core.atomic : atomicLoad; + + version (__GTHREADS) + return atomicLoad!(MemoryOrder.raw)(this._M_refcount) < 0; + else + return _M_refcount < 0; + } +// + bool _M_is_shared() const nothrow + { + import core.atomic : atomicLoad; + + version (__GTHREADS) + return atomicLoad!(MemoryOrder.acq)(this._M_refcount) > 0; + else + return _M_refcount > 0; + } + + T* _M_refdata() nothrow @trusted { return cast(T*)(&this + 1); } + + T* _M_grab(ref allocator_type __alloc1, const ref allocator_type __alloc2) + { + return (!_M_is_leaked() && __alloc1 == __alloc2) + ? _M_refcopy() : _M_clone(__alloc1); + } + + static _Rep* _S_create(size_type __capacity, size_type __old_capacity, ref Alloc __alloc) + { + assert(__capacity <= _S_max_size); +// if (__capacity > _S_max_size) +// __throw_length_error(__N("basic_string::_S_create")); + + enum __pagesize = 4096; + enum __malloc_header_size = 4 * pointer.sizeof; + + if (__capacity > __old_capacity && __capacity < 2 * __old_capacity) + __capacity = 2 * __old_capacity; + + size_type __size = (__capacity + 1) * T.sizeof + _Rep.sizeof; + + const size_type __adj_size = __size + __malloc_header_size; + if (__adj_size > __pagesize && __capacity > __old_capacity) + { + const size_type __extra = __pagesize - __adj_size % __pagesize; + __capacity += __extra / T.sizeof; + if (__capacity > _S_max_size) + __capacity = _S_max_size; + __size = (__capacity + 1) * T.sizeof + _Rep.sizeof; + } + + _Rep* __p = cast(_Rep*)_Raw_bytes_alloc(__alloc).allocate(__size); + *__p = _Rep.init; + __p._M_capacity = __capacity; + __p._M_set_sharable(); + return __p; + } + + void _M_dispose(ref Alloc __a) + { + import core.stdcpp.xutility : __exchange_and_add_dispatch; + + if (&this != &_S_empty_rep()) + { + // Be race-detector-friendly. For more info see bits/c++config. +// _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&this._M_refcount); + // Decrement of _M_refcount is acq_rel, because: + // - all but last decrements need to release to synchronize with + // the last decrement that will delete the object. + // - the last decrement needs to acquire to synchronize with + // all the previous decrements. + // - last but one decrement needs to release to synchronize with + // the acquire load in _M_is_shared that will conclude that + // the object is not shared anymore. + if (__exchange_and_add_dispatch(&this._M_refcount, -1) <= 0) + { +// _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&this._M_refcount); + _M_destroy(__a); + } + } + } + + void _M_destroy(ref Alloc __a) + { + const size_type __size = _Rep_base.sizeof + (_M_capacity + 1) * T.sizeof; + _Raw_bytes_alloc(__a).deallocate(cast(char*)&this, __size); + } + + T* _M_refcopy() nothrow @trusted + { + import core.stdcpp.xutility : __atomic_add_dispatch; + + if (&this != &_S_empty_rep()) + __atomic_add_dispatch(&this._M_refcount, 1); + return _M_refdata(); + // XXX MT + } + + T* _M_clone(ref Alloc __alloc, size_type __res = 0) + { + const size_type __requested_cap = _M_length + __res; + _Rep* __r = _S_create(__requested_cap, _M_capacity, __alloc); + if (_M_length) + _S_copy(__r._M_refdata(), _M_refdata(), _M_length); + + __r._M_set_length_and_sharable(_M_length); + return __r._M_refdata(); + } + } + + static if (!is_empty!allocator_type.value) + allocator_type _M_Alloc; + T* _M_p; // The actual data. + + alias _M_data = _M_p; + + pragma (inline, true) + { + void eos(size_type offset) + { + _M_mutate(offset, size() - offset, size_type(0)); + } + + ref inout(allocator_type) _M_get_allocator() inout + { + static if (!is_empty!allocator_type.value) + return _M_Alloc; + else + return *cast(inout(allocator_type)*)&this; + } + + _Rep* _M_rep() const nothrow @trusted { return &(cast(_Rep*)_M_data)[-1]; } + + size_type _M_limit(size_type __pos, size_type __off) const @safe nothrow @nogc pure + { + const bool __testoff = __off < size() - __pos; + return __testoff ? __off : size() - __pos; + } + } + + size_type _M_check(size_type __pos, const char* __s) const + { + assert(__pos <= size()); +// if (__pos > size()) +// __throw_out_of_range_fmt(__N("%s: __pos (which is %zu) > " +// "this->size() (which is %zu)"), +// __s, __pos, this->size()); + return __pos; + } + + static ref _Rep _S_empty_rep() nothrow + { + return _Rep._S_empty_rep(); + } + + static T* _S_construct(const(T)* __beg, const(T)* __end, ref Alloc __a) + { + version (_GLIBCXX_FULLY_DYNAMIC_STRING) {} else + { + if (__beg == __end && __a == Alloc()) + return _S_empty_rep()._M_refdata(); + } + + const size_type __dnew = __end - __beg; + + _Rep* __r = _Rep._S_create(__dnew, size_type(0), __a); + _S_copy(__r._M_refdata(), __beg, __end - __beg); + __r._M_set_length_and_sharable(__dnew); + return __r._M_refdata(); + } + + ref basic_string _M_replace_safe(size_type __pos1, size_type __n1, const(T)* __s, size_type __n2) + { + _M_mutate(__pos1, __n1, __n2); + if (__n2) + _S_copy(_M_data + __pos1, __s, __n2); + return this; + } + + ref basic_string _M_replace_aux(size_type __pos1, size_type __n1, size_type __n2, T __c) + { + _M_check_length(__n1, __n2, "basic_string::_M_replace_aux"); + _M_mutate(__pos1, __n1, __n2); + if (__n2) + _M_data[__pos1 .. __pos1 + __n2] = __c; + return this; + } + + void _M_mutate(size_type __pos, size_type __len1, size_type __len2) + { + const size_type __old_size = size(); + const size_type __new_size = __old_size + __len2 - __len1; + const size_type __how_much = __old_size - __pos - __len1; + + if (__new_size > capacity() || _M_rep()._M_is_shared()) + { + allocator_type __a = get_allocator(); + _Rep* __r = _Rep._S_create(__new_size, capacity(), __a); + + if (__pos) + _S_copy(__r._M_refdata(), _M_data, __pos); + if (__how_much) + _S_copy(__r._M_refdata() + __pos + __len2, _M_data + __pos + __len1, __how_much); + + allocator_type* __al = cast() &__a; + _M_rep()._M_dispose(*__al); + _M_data = __r._M_refdata(); + } + else if (__how_much && __len1 != __len2) + _S_move(_M_data + __pos + __len2, _M_data + __pos + __len1, __how_much); + _M_rep()._M_set_length_and_sharable(__new_size); + } + } + else + { + pragma(msg, "libstdc++ std::__cxx11::basic_string is not yet supported; the struct contains an interior pointer which breaks D move semantics!"); + + //---------------------------------------------------------------------------------- + // GCC/libstdc++ modern implementation + //---------------------------------------------------------------------------------- + + /// + this(DefaultConstruct) { _M_p = _M_local_data(); _M_set_length(0); } + /// + this(const(T)[] str, ref const(allocator_type) al) { _M_assign_allocator(al); this(str); } + /// + this(const(T)[] str) + { + _M_p = _M_local_data(); + _M_construct(str.ptr, str.length); + } + /// + this(this) + { + assert(false); + // TODO: how do I know if it was local before?! + } + + /// + ~this() { _M_dispose(); } + + /// + ref inout(Alloc) get_allocator() inout { return _M_get_allocator(); } + + /// + size_type max_size() const nothrow @safe { return ((size_t.max / T.sizeof) - 1) / 2; } + + /// + size_type size() const nothrow @safe { return _M_string_length; } + /// + size_type capacity() const nothrow { return _M_is_local ? _S_local_capacity : _M_allocated_capacity; } + /// + inout(T)* data() inout @safe { return _M_data; } + /// + inout(T)[] as_array() inout nothrow @trusted { return _M_data[0 .. _M_string_length]; } + /// + ref inout(T) at(size_type i) inout nothrow { return _M_data[0 .. _M_string_length][i]; } + + /// + ref basic_string assign(const(T)[] str) + { +// __glibcxx_requires_string_len(str.ptr, str.length); + return _M_replace(size_type(0), size(), str.ptr, str.length); + } + + /// + ref basic_string assign(const ref basic_string str) + { + if (&this != &str) + assign(str.as_array); + return this; + } + + /// + ref basic_string append(const(T)[] str) + { +// __glibcxx_requires_string_len(str.ptr, str.length); + _M_check_length(size_type(0), str.length, "basic_string::append"); + return _M_append(str.ptr, str.length); + } + + /// + ref basic_string append(size_type n, T c) + { + return _M_replace_aux(size(), size_type(0), n, c); + } + + /// + void reserve(size_type __res = 0) + { + // Make sure we don't shrink below the current size. + if (__res < length()) + __res = length(); + + const size_type __capacity = capacity(); + if (__res != __capacity) + { + if (__res > __capacity || __res > size_type(_S_local_capacity)) + { + pointer __tmp = _M_create(__res, __capacity); + _S_copy(__tmp, _M_data, length() + 1); + _M_dispose(); + _M_data = __tmp; + _M_capacity = __res; + } + else if (!_M_is_local()) + { + _S_copy(_M_local_data(), _M_data, length() + 1); + _M_destroy(__capacity); + _M_data = _M_local_data(); + } + } + } + + /// + void shrink_to_fit() nothrow + { + if (capacity() > size()) + { + try reserve(0); + catch (Throwable) {} + } + } + + /// + ref basic_string insert(size_type pos, const(T)* s, size_type n) + { + return replace(pos, size_type(0), s, n); + } + + /// + ref basic_string insert(size_type pos, size_type n, T c) + { + return _M_replace_aux(_M_check(pos, "basic_string::insert"), size_type(0), n, c); + } + + /// + ref basic_string replace(size_type pos, size_type n1, const(T)* s, size_type n2) + { +// __glibcxx_requires_string_len(s, n2); + return _M_replace(_M_check(pos, "basic_string::replace"), _M_limit(pos, n1), s, n2); + } + + /// + ref basic_string replace(size_type pos, size_type n1, size_type n2, T c) + { + return _M_replace_aux(_M_check(pos, "basic_string::replace"), _M_limit(pos, n1), n2, c); + } + + /// + void swap(ref basic_string __s) + { + if (&this == &__s) + return; + + __alloc_on_swap(__s._M_get_allocator()); + + if (_M_is_local()) + { + if (__s._M_is_local()) + { + if (length() && __s.length()) + { + T[_S_local_capacity + 1] __tmp_data; + __tmp_data[] = __s._M_local_buf[]; + __s._M_local_buf[] = _M_local_buf[]; + _M_local_buf[] = __tmp_data[]; + } + else if (__s.length()) + { + _M_local_buf[] = __s._M_local_buf[]; + _M_length = __s.length(); + __s._M_set_length(0); + return; + } + else if (length()) + { + __s._M_local_buf[] = _M_local_buf[]; + __s._M_length = length(); + _M_set_length(0); + return; + } + } + else + { + const size_type __tmp_capacity = __s._M_allocated_capacity; + __s._M_local_buf[] = _M_local_buf[]; + _M_data = __s._M_data; + __s._M_data = __s._M_local_buf.ptr; + _M_capacity = __tmp_capacity; + } + } + else + { + const size_type __tmp_capacity = _M_allocated_capacity; + if (__s._M_is_local()) + { + _M_local_buf[] = __s._M_local_buf[]; + __s._M_data = _M_data; + _M_data = _M_local_buf.ptr; + } + else + { + pointer __tmp_ptr = _M_data; + _M_data = __s._M_data; + __s._M_data = __tmp_ptr; + _M_capacity = __s._M_allocated_capacity; + } + __s._M_capacity = __tmp_capacity; + } + + const size_type __tmp_length = length(); + _M_length = __s.length(); + __s._M_length = __tmp_length; + } + + private: +// import core.exception : RangeError; + import core.stdcpp.type_traits : is_empty; + + static if (!is_empty!allocator_type.value) + allocator_type _M_Alloc; + pointer _M_p; // The actual data. + size_type _M_string_length; + + enum size_type _S_local_capacity = 15 / T.sizeof; + union + { + T[_S_local_capacity + 1] _M_local_buf; + size_type _M_allocated_capacity; + } + + alias _M_length = _M_string_length; + alias _M_capacity = _M_allocated_capacity; + alias _M_data = _M_p; + + pragma (inline, true) + { + void eos(size_type offset) nothrow { _M_set_length(offset); } + + inout(pointer) _M_local_data() inout { return _M_local_buf.ptr; } + bool _M_is_local() const { return _M_data == _M_local_data; } + + ref inout(allocator_type) _M_get_allocator() inout + { + static if (!is_empty!allocator_type.value) + return _M_Alloc; + else + return *cast(inout(allocator_type)*)&this; + } + + void _M_set_length(size_type __n) + { + _M_length = __n; + _M_data[__n] = T(0); + } + + size_type _M_check(size_type __pos, const char* __s) const + { + assert(__pos <= size()); +// if (__pos > size()) +// __throw_out_of_range_fmt(__N("%s: __pos (which is %zu) > " +// "this->size() (which is %zu)"), +// __s, __pos, this->size()); + return __pos; + } + + // NB: _M_limit doesn't check for a bad __pos value. + size_type _M_limit(size_type __pos, size_type __off) const nothrow pure @nogc @safe + { + const bool __testoff = __off < size() - __pos; + return __testoff ? __off : size() - __pos; + } + + void __alloc_on_swap()(ref allocator_type __a) + if (!is_empty!allocator_type.value) + { + import core.internal.lifetime : swap; + + static if (allocator_traits!allocator_type.propagate_on_container_swap) + swap(_M_get_allocator(), __a); + } + + void __alloc_on_swap()(ref allocator_type __a) + if (is_empty!allocator_type.value) + { + import core.internal.lifetime : swap; + import core.lifetime : move; + + static if (allocator_traits!allocator_type.propagate_on_container_swap) + { + static if (is(typeof(_M_get_allocator().opAssign(move(__a))))) + swap(_M_get_allocator(), __a); + } + } + } + + void _M_construct(const(T)* __beg, size_type __dnew) + { + if (__dnew > _S_local_capacity) + { + _M_data = _M_create(__dnew, size_type(0)); + _M_capacity = __dnew; + } + _M_data[0 .. __dnew] = __beg[0 .. __dnew]; + _M_set_length(__dnew); + } + + pointer _M_create(ref size_type __capacity, size_type __old_capacity) + { + assert(__capacity <= max_size()); +// if (__capacity > max_size()) +// throw new RangeError("Length exceeds `max_size()`"); // std::__throw_length_error(__N("basic_string::_M_create")); + if (__capacity > __old_capacity && __capacity < 2 * __old_capacity) + { + __capacity = 2 * __old_capacity; + if (__capacity > max_size()) + __capacity = max_size(); + } + return _M_get_allocator().allocate(__capacity + 1); + } + + ref basic_string _M_replace(size_type __pos, size_type __len1, const(T)* __s, const size_type __len2) + { + _M_check_length(__len1, __len2, "basic_string::_M_replace"); + + const size_type __old_size = size(); + const size_type __new_size = __old_size + __len2 - __len1; + + if (__new_size <= capacity()) + { + pointer __p = _M_data + __pos; + + const size_type __how_much = __old_size - __pos - __len1; + if (_M_disjunct(__s)) + { + if (__how_much && __len1 != __len2) + _S_move(__p + __len2, __p + __len1, __how_much); + if (__len2) + _S_copy(__p, __s, __len2); + } + else + { + // Work in-place. + if (__len2 && __len2 <= __len1) + _S_move(__p, __s, __len2); + if (__how_much && __len1 != __len2) + _S_move(__p + __len2, __p + __len1, __how_much); + if (__len2 > __len1) + { + if (__s + __len2 <= __p + __len1) + _S_move(__p, __s, __len2); + else if (__s >= __p + __len1) + _S_copy(__p, __s + __len2 - __len1, __len2); + else + { + const size_type __nleft = (__p + __len1) - __s; + _S_move(__p, __s, __nleft); + _S_copy(__p + __nleft, __p + __len2, + __len2 - __nleft); + } + } + } + } + else + _M_mutate(__pos, __len1, __s, __len2); + + _M_set_length(__new_size); + return this; + } + + ref basic_string _M_replace_aux(size_type __pos1, size_type __n1, size_type __n2, T __c) + { + _M_check_length(__n1, __n2, "basic_string::_M_replace_aux"); + + const size_type __old_size = size(); + const size_type __new_size = __old_size + __n2 - __n1; + + if (__new_size <= capacity()) + { + pointer __p = _M_data + __pos1; + + const size_type __how_much = __old_size - __pos1 - __n1; + if (__how_much && __n1 != __n2) + _S_move(__p + __n2, __p + __n1, __how_much); + } + else + _M_mutate(__pos1, __n1, null, __n2); + + if (__n2) + _M_data[__pos1 .. __pos1 + __n2] = __c; + + _M_set_length(__new_size); + return this; + } + + ref basic_string _M_append(const(T)* __s, size_type __n) + { + const size_type __len = __n + size(); + if (__len <= capacity()) + { + if (__n) + _S_copy(_M_data + size(), __s, __n); + } + else + _M_mutate(size(), size_type(0), __s, __n); + _M_set_length(__len); + return this; + } + + void _M_mutate(size_type __pos, size_type __len1, const(T)* __s, size_type __len2) + { + const size_type __how_much = length() - __pos - __len1; + + size_type __new_capacity = length() + __len2 - __len1; + pointer __r = _M_create(__new_capacity, capacity()); + + if (__pos) + _S_copy(__r, _M_data, __pos); + if (__s && __len2) + _S_copy(__r + __pos, __s, __len2); + if (__how_much) + _S_copy(__r + __pos + __len2, + _M_data + __pos + __len1, __how_much); + + _M_dispose(); + _M_data = __r; + _M_capacity = __new_capacity; + } + + void _M_dispose() + { + if (!_M_is_local) + _M_destroy(_M_allocated_capacity); + } + + void _M_destroy(size_type __size) + { + _M_get_allocator().deallocate(_M_data, __size + 1); + } + } + + // common GCC/stdlibc++ code + + void _M_check_length(size_type __n1, size_type __n2, const char* __s) const + { + assert (!(max_size() - (size() - __n1) < __n2)); +// if (max_size() - (size() - __n1) < __n2) +// __throw_length_error(__N(__s)); + } + + void _M_assign_allocator(ref const(allocator_type) al) nothrow + { + static if (!is_empty!allocator_type.value) + _M_Alloc = al; + } + + bool _M_disjunct(const(T)* __s) const nothrow + { + return __s < _M_data || _M_data + size() < __s; + } + + static void _S_move(T* __d, const(T)* __s, size_type __n) + { + if (__d == __s) + return; + if (__d < __s) + { + for (size_t i = 0; i < __n; ++i) + __d[i] = __s[i]; + } + else + { + for (ptrdiff_t i = __n - 1; i >= 0; --i) + __d[i] = __s[i]; + } + } + static void _S_copy(T* __d, const(T)* __s, size_type __n) + { + __d[0 .. __n] = __s[0 .. __n]; + } + } + else version (CppRuntime_Clang) + { + //---------------------------------------------------------------------------------- + // Clang/libc++ implementation + //---------------------------------------------------------------------------------- + + /// + this(DefaultConstruct) { __zero(); } + /// + this(const(T)[] str, ref const(allocator_type) al) { __assign_allocator(al); this(str); } + /// + this(const(T)[] str) { __init(str.ptr, str.length); } + /// + this(this) + { + if (__is_long()) + __init(__get_long_pointer(), __get_long_size()); + } + + /// + ~this() + { +// __get_db()->__erase_c(this); // TODO: support `_LIBCPP_DEBUG_LEVEL >= 2` ?? + if (__is_long()) + __alloc().deallocate(__get_long_pointer(), __get_long_cap()); + } + + /// + ref inout(Alloc) get_allocator() inout { return __alloc(); } + + /// + size_type max_size() const nothrow @safe + { + size_type __m = size_t.max; // TODO: __alloc_traits::max_size(__alloc()); + version (BigEndian) + return (__m <= ~__long_mask ? __m : __m/2) - __alignment; + else + return __m - __alignment; + } + + /// + size_type size() const nothrow { return __is_long() ? __get_long_size() : __get_short_size(); } + /// + size_type capacity() const nothrow { return (__is_long() ? __get_long_cap() : __min_cap) - 1; } + /// + inout(T)* data() inout @safe { return __get_pointer(); } + /// + inout(T)[] as_array() inout nothrow @trusted { return __get_pointer()[0 .. size()]; } + /// + ref inout(T) at(size_type i) inout nothrow @trusted { return __get_pointer()[0 .. size()][i]; } + + /// + ref basic_string assign(const(T)[] str) + { + const(value_type)* __s = str.ptr; + size_type __n = str.length; + size_type __cap = capacity(); + if (__cap >= __n) + { + value_type* __p = __get_pointer(); + __p[0 .. __n] = __s[0 .. __n]; // TODO: is memmove? + __p[__n] = value_type(0); + __set_size(__n); +// __invalidate_iterators_past(__n); // TODO: support `_LIBCPP_DEBUG_LEVEL >= 2` ?? + } + else + { + size_type __sz = size(); + __grow_by_and_replace(__cap, __n - __cap, __sz, 0, __sz, __n, __s); + } + return this; + } + + /// + ref basic_string assign(const ref basic_string str) + { + if (&this != &str) + assign(str.as_array); + return this; + } + + /// + ref basic_string append(const(T)[] str) + { + const(value_type)* __s = str.ptr; + size_type __n = str.length; + size_type __cap = capacity(); + size_type __sz = size(); + if (__cap - __sz >= __n) + { + if (__n) + { + value_type* __p = __get_pointer(); + (__p + __sz)[0 .. __n] = __s[0 .. __n]; + __sz += __n; + __set_size(__sz); + __p[__sz] = value_type(0); + } + } + else + __grow_by_and_replace(__cap, __sz + __n - __cap, __sz, __sz, 0, __n, __s); + return this; + } + + /// + ref basic_string append(size_type __n, value_type __c) + { + if (__n) + { + size_type __cap = capacity(); + size_type __sz = size(); + if (__cap - __sz < __n) + __grow_by(__cap, __sz + __n - __cap, __sz, __sz, 0); + pointer __p = __get_pointer(); + __p[__sz .. __sz + __n] = __c; + __sz += __n; + __set_size(__sz); + __p[__sz] = value_type(0); + } + return this; + } + + /// + void reserve(size_type __res_arg = 0) + { + assert(__res_arg <= max_size()); +// if (__res_arg > max_size()) +// __throw_length_error(); + size_type __cap = capacity(); + size_type __sz = size(); + __res_arg = max(__res_arg, __sz); + __res_arg = __recommend(__res_arg); + if (__res_arg != __cap) + { + pointer __new_data, __p; + bool __was_long, __now_long; + if (__res_arg == __min_cap - 1) + { + __was_long = true; + __now_long = false; + __new_data = __get_short_pointer(); + __p = __get_long_pointer(); + } + else + { + if (__res_arg > __cap) + __new_data = __alloc().allocate(__res_arg+1); + else + { + try + __new_data = __alloc().allocate(__res_arg+1); + catch (Throwable) + return; + } + __now_long = true; + __was_long = __is_long(); + __p = __get_pointer(); + } + __new_data[0 .. size()+1] = __p[0 .. size()+1]; + if (__was_long) + __alloc().deallocate(__p, __cap+1); + if (__now_long) + { + __set_long_cap(__res_arg+1); + __set_long_size(__sz); + __set_long_pointer(__new_data); + } + else + __set_short_size(__sz); +// __invalidate_all_iterators(); // TODO: + } + } + + /// + void shrink_to_fit() + { + reserve(); + } + + /// + ref basic_string insert(size_type __pos, const(value_type)* __s, size_type __n) + { + assert(__n == 0 || __s != null, "string::insert received null"); + size_type __sz = size(); + assert(__pos <= __sz); +// if (__pos > __sz) +// this->__throw_out_of_range(); + size_type __cap = capacity(); + if (__cap - __sz >= __n) + { + if (__n) + { + value_type* __p = __get_pointer(); + size_type __n_move = __sz - __pos; + if (__n_move != 0) + { + if (__p + __pos <= __s && __s < __p + __sz) + __s += __n; + traits_type.move(__p + __pos + __n, __p + __pos, __n_move); + } + traits_type.move(__p + __pos, __s, __n); + __sz += __n; + __set_size(__sz); + __p[__sz] = value_type(0); + } + } + else + __grow_by_and_replace(__cap, __sz + __n - __cap, __sz, __pos, 0, __n, __s); + return this; + } + + /// + ref basic_string insert(size_type pos, size_type n, value_type c) + { + alias __pos = pos; + alias __n = n; + alias __c = c; + size_type __sz = size(); + assert(__pos <= __sz); +// if (__pos > __sz) +// __throw_out_of_range(); + if (__n) + { + size_type __cap = capacity(); + value_type* __p; + if (__cap - __sz >= __n) + { + __p = __get_pointer(); + size_type __n_move = __sz - __pos; + if (__n_move != 0) + traits_type.move(__p + __pos + __n, __p + __pos, __n_move); + } + else + { + __grow_by(__cap, __sz + __n - __cap, __sz, __pos, 0, __n); + __p = __get_long_pointer(); + } + __p[__pos .. __pos + __n] = __c; + __sz += __n; + __set_size(__sz); + __p[__sz] = value_type(0); + } + return this; + } + + /// + ref basic_string replace(size_type __pos, size_type __n1, const(T)* __s, size_type __n2) + { + assert(__n2 == 0 || __s != null, "string::replace received null"); + size_type __sz = size(); + assert(__pos <= __sz); +// if (__pos > __sz) +// __throw_out_of_range(); + __n1 = min(__n1, __sz - __pos); + size_type __cap = capacity(); + if (__cap - __sz + __n1 >= __n2) + { + value_type* __p = __get_pointer(); + if (__n1 != __n2) + { + size_type __n_move = __sz - __pos - __n1; + if (__n_move != 0) + { + if (__n1 > __n2) + { + traits_type.move(__p + __pos, __s, __n2); + traits_type.move(__p + __pos + __n2, __p + __pos + __n1, __n_move); + goto __finish; + } + if (__p + __pos < __s && __s < __p + __sz) + { + if (__p + __pos + __n1 <= __s) + __s += __n2 - __n1; + else // __p + __pos < __s < __p + __pos + __n1 + { + traits_type.move(__p + __pos, __s, __n1); + __pos += __n1; + __s += __n2; + __n2 -= __n1; + __n1 = 0; + } + } + traits_type.move(__p + __pos + __n2, __p + __pos + __n1, __n_move); + } + } + traits_type.move(__p + __pos, __s, __n2); + __finish: + // __sz += __n2 - __n1; in this and the below function below can cause unsigned integer overflow, + // but this is a safe operation, so we disable the check. + __sz += __n2 - __n1; + __set_size(__sz); +// __invalidate_iterators_past(__sz); // TODO + __p[__sz] = value_type(0); + } + else + __grow_by_and_replace(__cap, __sz - __n1 + __n2 - __cap, __sz, __pos, __n1, __n2, __s); + return this; + } + + /// + ref basic_string replace(size_type __pos, size_type __n1, size_type __n2, value_type __c) + { + size_type __sz = size(); + assert(__pos <= __sz); +// if (__pos > __sz) +// __throw_out_of_range(); + __n1 = min(__n1, __sz - __pos); + size_type __cap = capacity(); + value_type* __p; + if (__cap - __sz + __n1 >= __n2) + { + __p = __get_pointer(); + if (__n1 != __n2) + { + size_type __n_move = __sz - __pos - __n1; + if (__n_move != 0) + traits_type.move(__p + __pos + __n2, __p + __pos + __n1, __n_move); + } + } + else + { + __grow_by(__cap, __sz - __n1 + __n2 - __cap, __sz, __pos, __n1, __n2); + __p = __get_long_pointer(); + } + __p[__pos .. __pos + __n2] = __c; + __sz += __n2 - __n1; + __set_size(__sz); +// __invalidate_iterators_past(__sz); // TODO + __p[__sz] = value_type(0); + return this; + } + + /// + void swap(ref basic_string __str) + { + import core.internal.lifetime : swap; +// static if (_LIBCPP_DEBUG_LEVEL >= 2) +// { +// if (!__is_long()) +// __get_db().__invalidate_all(&this); +// if (!__str.__is_long()) +// __get_db().__invalidate_all(&__str); +// __get_db().swap(&this, &__str); +// } + assert( + __alloc_traits.propagate_on_container_swap || + __alloc_traits.is_always_equal || + __alloc() == __str.__alloc(), "swapping non-equal allocators"); + swap(__r_.first(), __str.__r_.first()); + __swap_allocator(__alloc(), __str.__alloc()); + } + + private: +// import core.exception : RangeError; + import core.stdcpp.xutility : __compressed_pair; + + alias __alloc_traits = allocator_traits!allocator_type; + + enum __alignment = 16; + + version (_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT) + { + struct __long + { + pointer __data_; + size_type __size_; + size_type __cap_; + } + + version (BigEndian) + { + enum size_type __short_mask = 0x01; + enum size_type __long_mask = 0x1; + } + else + { + enum size_type __short_mask = 0x80; + enum size_type __long_mask = ~(size_type(~0) >> 1); + } + + enum size_type __min_cap = (__long.sizeof - 1)/value_type.sizeof > 2 ? (__long.sizeof - 1)/value_type.sizeof : 2; + + struct __short + { + value_type[__min_cap] __data_; + struct + { + static if (value_type.sizeof > 1) + ubyte[value_type.sizeof-1] __xx; // __padding + ubyte __size_; + } + } + } + else + { + struct __long + { + size_type __cap_; + size_type __size_; + pointer __data_; + } + + version (BigEndian) + { + enum size_type __short_mask = 0x80; + enum size_type __long_mask = ~(size_type(~0) >> 1); + } + else + { + enum size_type __short_mask = 0x01; + enum size_type __long_mask = 0x1; + } + + enum size_type __min_cap = (__long.sizeof - 1)/value_type.sizeof > 2 ? (__long.sizeof - 1)/value_type.sizeof : 2; + + struct __short + { + union + { + ubyte __size_; + value_type __lx; + } + value_type[__min_cap] __data_; + } + } + + union __ulx { __long __lx; __short __lxx; } + enum __n_words = __ulx.sizeof / size_type.sizeof; + + struct __raw + { + size_type[__n_words] __words; + } + + struct __rep + { + union + { + __long __l; + __short __s; + __raw __r; + } + } + + __compressed_pair!(__rep, allocator_type) __r_; + + pragma (inline, true) + { + void eos(size_type offset) nothrow + { + __set_size(offset); +// __invalidate_iterators_past(__sz); // TODO: support `_LIBCPP_DEBUG_LEVEL >= 2` ?? + __get_pointer()[offset] = value_type(0); + } + + version (_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT) + { + version (BigEndian) + { + void __set_short_size(size_type __s) nothrow @safe { __r_.first().__s.__size_ = cast(ubyte)(__s << 1); } + size_type __get_short_size() const nothrow @safe { return __r_.first().__s.__size_ >> 1; } + } + else + { + void __set_short_size(size_type __s) nothrow @safe { __r_.first().__s.__size_ = cast(ubyte)(__s);} + size_type __get_short_size() const nothrow @safe { return __r_.first().__s.__size_;} + } + } + else + { + version (BigEndian) + { + void __set_short_size(size_type __s) nothrow @safe { __r_.first().__s.__size_ = cast(ubyte)(__s); } + size_type __get_short_size() const nothrow @safe { return __r_.first().__s.__size_; } + } + else + { + void __set_short_size(size_type __s) nothrow @safe { __r_.first().__s.__size_ = cast(ubyte)(__s << 1); } + size_type __get_short_size() const nothrow @safe { return __r_.first().__s.__size_ >> 1; } + } + } + void __set_long_size(size_type __s) nothrow { __r_.first().__l.__size_ = __s; } + size_type __get_long_size() const nothrow { return __r_.first().__l.__size_; } + void __set_size(size_type __s) nothrow { if (__is_long()) __set_long_size(__s); else __set_short_size(__s); } + + void __set_long_cap(size_type __s) nothrow { __r_.first().__l.__cap_ = __long_mask | __s; } + size_type __get_long_cap() const nothrow { return __r_.first().__l.__cap_ & size_type(~__long_mask); } + + void __set_long_pointer(pointer __p) nothrow { __r_.first().__l.__data_ = __p; } + inout(T)* __get_long_pointer() inout nothrow { return __r_.first().__l.__data_; } + inout(T)* __get_short_pointer() inout nothrow @safe { return &__r_.first().__s.__data_[0]; } + inout(T)* __get_pointer() inout nothrow { return __is_long() ? __get_long_pointer() : __get_short_pointer(); } + + bool __is_long() const nothrow @safe { return (__r_.first().__s.__size_ & __short_mask) != 0; } + + void __zero() nothrow @safe { __r_.first().__r.__words[] = 0; } + + ref inout(allocator_type) __alloc() inout nothrow @safe { return __r_.second(); } + + void __init(const(value_type)* __s, size_type __sz) { return __init(__s, __sz, __sz); } + } + + void __assign_allocator(ref const(allocator_type) al) nothrow + { + static if (!__r_.Ty2Empty) + __alloc() = al; + } + + void __init(const(value_type)* __s, size_type __sz, size_type __reserve) + { + assert(__reserve <= max_size()); +// if (__reserve > max_size()) +// throw new RangeError("Length exceeds `max_size()`"); // this->__throw_length_error(); + pointer __p; + if (__reserve < __min_cap) + { + __set_short_size(__sz); + __p = __get_short_pointer(); + } + else + { + size_type __cap = __recommend(__reserve); + __p = __alloc().allocate(__cap+1, null); + __set_long_pointer(__p); + __set_long_cap(__cap+1); + __set_long_size(__sz); + } + __p[0 .. __sz] = __s[0 .. __sz]; + __p[__sz] = value_type(0); + } + + static size_type __recommend(size_type __s) nothrow @safe + { + static size_type __align_it(size_type __a)(size_type __s) nothrow @safe { return (__s + (__a-1)) & ~(__a-1); } + if (__s < __min_cap) return __min_cap - 1; + size_type __guess = __align_it!(value_type.sizeof < __alignment ? __alignment/value_type.sizeof : 1)(__s+1) - 1; + if (__guess == __min_cap) ++__guess; + return __guess; + } + + void __grow_by_and_replace(size_type __old_cap, size_type __delta_cap, size_type __old_sz, size_type __n_copy, + size_type __n_del, size_type __n_add, const(value_type)* __p_new_stuff) + { + size_type __ms = max_size(); + assert(__delta_cap <= __ms - __old_cap - 1); +// if (__delta_cap > __ms - __old_cap - 1) +// throw new RangeError("Length exceeds `max_size()`"); // this->__throw_length_error(); + pointer __old_p = __get_pointer(); + size_type __cap = __old_cap < __ms / 2 - __alignment ? + __recommend(max(__old_cap + __delta_cap, 2 * __old_cap)) : + __ms - 1; + pointer __p = __alloc().allocate(__cap+1); +// __invalidate_all_iterators(); // TODO: support `_LIBCPP_DEBUG_LEVEL >= 2` ?? + if (__n_copy != 0) + __p[0 .. __n_copy] = __old_p[0 .. __n_copy]; + if (__n_add != 0) + (__p + __n_copy)[0 .. __n_add] = __p_new_stuff[0 .. __n_add]; + size_type __sec_cp_sz = __old_sz - __n_del - __n_copy; + if (__sec_cp_sz != 0) + (__p + __n_copy + __n_add)[0 .. __sec_cp_sz] = (__old_p + __n_copy + __n_del)[0 .. __sec_cp_sz]; + if (__old_cap+1 != __min_cap) + __alloc().deallocate(__old_p, __old_cap+1); + __set_long_pointer(__p); + __set_long_cap(__cap+1); + __old_sz = __n_copy + __n_add + __sec_cp_sz; + __set_long_size(__old_sz); + __p[__old_sz] = value_type(0); + } + + void __grow_by(size_type __old_cap, size_type __delta_cap, size_type __old_sz, + size_type __n_copy, size_type __n_del, size_type __n_add = 0) + { + size_type __ms = max_size(); + assert(__delta_cap <= __ms - __old_cap); +// if (__delta_cap > __ms - __old_cap) +// __throw_length_error(); + pointer __old_p = __get_pointer(); + size_type __cap = __old_cap < __ms / 2 - __alignment ? + __recommend(max(__old_cap + __delta_cap, 2 * __old_cap)) : + __ms - 1; + pointer __p = __alloc().allocate(__cap+1); +// __invalidate_all_iterators(); // TODO: + if (__n_copy != 0) + __p[0 .. __n_copy] = __old_p[0 .. __n_copy]; + size_type __sec_cp_sz = __old_sz - __n_del - __n_copy; + if (__sec_cp_sz != 0) + (__p + __n_copy + __n_add)[0 .. __sec_cp_sz] = (__old_p + __n_copy + __n_del)[0 .. __sec_cp_sz]; + if (__old_cap+1 != __min_cap) + __alloc().deallocate(__old_p, __old_cap+1); + __set_long_pointer(__p); + __set_long_cap(__cap+1); + } + } + else + { + static assert(false, "C++ runtime not supported"); + } +} + + +// platform detail +private: +version (CppRuntime_Microsoft) +{ + import core.stdcpp.xutility : _ITERATOR_DEBUG_LEVEL; + +extern(C++, (StdNamespace)): + extern (C++) struct _String_base_types(_Elem, _Alloc) + { + alias Ty = _Elem; + alias Alloc = _Alloc; + } + + extern (C++, class) struct _String_alloc(_Alloc_types) + { + import core.stdcpp.xutility : _Compressed_pair; + + alias Ty = _Alloc_types.Ty; + alias Alloc = _Alloc_types.Alloc; + alias ValTy = _String_val!Ty; + + extern(D) @safe @nogc: + pragma(inline, true) + { + ref inout(Alloc) _Getal() inout pure nothrow { return _Mypair._Myval1; } + ref inout(ValTy) _Get_data() inout pure nothrow { return _Mypair._Myval2; } + } + + void _Orphan_all() nothrow { _Get_data._Base._Orphan_all(); } + + static if (_ITERATOR_DEBUG_LEVEL > 0) + { + import core.stdcpp.xutility : _Container_proxy; + + ~this() + { + _Free_proxy(); + } + + pragma(inline, true) + ref inout(_Container_proxy*) _Myproxy() inout pure nothrow { return _Get_data._Base._Myproxy; } + + void _Alloc_proxy() nothrow @trusted + { + import core.lifetime : emplace; + + alias _Alproxy = Alloc.rebind!_Container_proxy; + try // TODO: or should we make allocator::allocate() `nothrow`? + _Myproxy() = _Alproxy(_Getal()).allocate(1); + catch (Throwable) + assert(false, "Failed to allocate iterator debug container proxy"); + emplace!_Container_proxy(_Myproxy()); + _Myproxy()._Mycont = &_Get_data()._Base; + } + void _Free_proxy() nothrow @trusted + { + alias _Alproxy = Alloc.rebind!_Container_proxy; + _Orphan_all(); + destroy!false(*_Myproxy()); + try // TODO: or should we make allocator::deallocate() `nothrow`? + _Alproxy(_Getal()).deallocate(_Myproxy(), 1); + catch (Throwable) + assert(false, "Failed to deallocate iterator debug container proxy"); + _Myproxy() = null; + } + } + + _Compressed_pair!(Alloc, ValTy) _Mypair; + } + + extern (C++, class) struct _String_val(T) + { + import core.stdcpp.xutility : _Container_base; + import core.stdcpp.type_traits : is_empty; + + enum _BUF_SIZE = 16 / T.sizeof < 1 ? 1 : 16 / T.sizeof; + enum _ALLOC_MASK = T.sizeof <= 1 ? 15 : T.sizeof <= 2 ? 7 : T.sizeof <= 4 ? 3 : T.sizeof <= 8 ? 1 : 0; + + static if (!is_empty!_Container_base.value) + _Container_base _Base; + else + ref inout(_Container_base) _Base() inout { return *cast(inout(_Container_base)*)&this; } + + union _Bxty + { + T[_BUF_SIZE] _Buf; + T* _Ptr; + } + + _Bxty _Bx; + size_t _Mysize = 0; // current length of string + size_t _Myres = _BUF_SIZE - 1; // current storage reserved for string + + pragma (inline, true): + extern (D): + pure nothrow @nogc: + bool _IsAllocated() const @safe { return _BUF_SIZE <= _Myres; } + alias _Large_string_engaged = _IsAllocated; + @property inout(T)* _Myptr() inout @trusted { return _BUF_SIZE <= _Myres ? _Bx._Ptr : _Bx._Buf.ptr; } + @property inout(T)[] _Mystr() inout @trusted { return _BUF_SIZE <= _Myres ? _Bx._Ptr[0 .. _Mysize] : _Bx._Buf[0 .. _Mysize]; } + + auto _Clamp_suffix_size(T)(const T _Off, const T _Size) const + { + // trims _Size to the longest it can be assuming a string at/after _Off + return min(_Size, _Mysize - _Off); + } + } + + template _Size_after_ebco_v(_Ty) + { + import core.stdcpp.type_traits : is_empty; + + enum size_t _Size_after_ebco_v = is_empty!_Ty.value ? 0 : _Ty.sizeof; // get _Ty's size after being EBCO'd + } +} + +auto ref T max(T)(auto ref T a, auto ref T b) { return b > a ? b : a; } +auto ref T min(T)(auto ref T a, auto ref T b) { return b < a ? b : a; } diff --git a/libphobos/libdruntime/core/stdcpp/string_view.d b/libphobos/libdruntime/core/stdcpp/string_view.d new file mode 100644 index 0000000..172c170 --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/string_view.d @@ -0,0 +1,130 @@ +/** + * D header file for interaction with C++ std::string_view. + * + * Copyright: Copyright (c) 2018 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/string_view.d) + */ + +module core.stdcpp.string_view; + +import core.stdc.stddef : wchar_t; +import core.stdcpp.xutility : StdNamespace; + +// hacks to support DMD on Win32 +version (CppRuntime_Microsoft) +{ + version = CppRuntime_Windows; // use the MS runtime ABI for win32 +} +else version (CppRuntime_DigitalMars) +{ + version = CppRuntime_Windows; // use the MS runtime ABI for win32 + pragma(msg, "std::basic_string_view not supported by DMC"); +} + +extern(C++, (StdNamespace)): +@nogc: + +/// +alias string_view = basic_string_view!char; +/// +alias u16string_view = basic_string_view!wchar; +/// +alias u32string_view = basic_string_view!dchar; +/// +alias wstring_view = basic_string_view!wchar_t; + + +/** + * Character traits classes specify character properties and provide specific + * semantics for certain operations on characters and sequences of characters. + */ +extern(C++, struct) struct char_traits(CharT) {} + + +/** +* D language counterpart to C++ std::basic_string_view. +* +* C++ reference: $(LINK2 hhttps://en.cppreference.com/w/cpp/string/basic_string_view) +*/ +extern(C++, class) struct basic_string_view(T, Traits = char_traits!T) +{ +extern(D): +pragma(inline, true): +pure nothrow @nogc: + + /// + enum size_type npos = size_type.max; + + /// + alias size_type = size_t; + /// + alias difference_type = ptrdiff_t; + /// + alias value_type = T; + /// + alias pointer = T*; + /// + alias const_pointer = const(T)*; + + /// + alias as_array this; + /// + alias toString = as_array; + + /// + this(const(T)[] str) @trusted { __data = str.ptr; __size = str.length; } + + /// + alias length = size; + /// + alias opDollar = length; + /// + size_type size() const @safe { return __size; } + /// + bool empty() const @safe { return __size == 0; } + + /// + const(T)* data() const @safe { return __data; } + /// + const(T)[] as_array() const @trusted { return __data[0 .. __size]; } + + /// + ref const(T) at(size_type i) const @trusted { return __data[0 .. __size][i]; } + + /// + ref const(T) front() const @safe { return this[0]; } + /// + ref const(T) back() const @safe { return this[$-1]; } + +private: + // use the proper field names from C++ so debugging doesn't get weird + version (CppRuntime_Windows) + { + const_pointer _Mydata; + size_type _Mysize; + + alias __data = _Mydata; + alias __size = _Mysize; + } + else version (CppRuntime_Gcc) + { + size_t _M_len; + const(T)* _M_str; + + alias __data = _M_str; + alias __size = _M_len; + } + else version (CppRuntime_Clang) + { + const value_type* __data; + size_type __size; + } + else + { + static assert(false, "C++ runtime not supported"); + } +} diff --git a/libphobos/libdruntime/core/stdcpp/type_traits.d b/libphobos/libdruntime/core/stdcpp/type_traits.d new file mode 100644 index 0000000..e4bf41c --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/type_traits.d @@ -0,0 +1,50 @@ +/** + * D header file for interaction with C++ std::type_traits. + * + * Copyright: Copyright Digital Mars 2018. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/type_traits.d) + */ + +module core.stdcpp.type_traits; + +extern(C++, "std"): + +/// +struct integral_constant(T, T Val) +{ + /// + enum T value = Val; + /// + alias value_type = T; + /// + alias type = typeof(this); +} + +/// +alias bool_constant(bool b) = integral_constant!(bool, b); + +// Useful for dealing with enable_if constraints. +/// +alias true_type = bool_constant!true; +/// +alias false_type = bool_constant!false; + +/// +struct is_empty(T) +{ + static if (is(T == struct)) + private enum __is_empty = T.tupleof.length == 0; + else + private enum __is_empty = false; + + /// + enum bool value = __is_empty; + /// + alias value_type = bool; + /// + alias type = integral_constant!(bool, value); +} diff --git a/libphobos/libdruntime/core/stdcpp/typeinfo.d b/libphobos/libdruntime/core/stdcpp/typeinfo.d index 693b8cd..53b25c5 100644 --- a/libphobos/libdruntime/core/stdcpp/typeinfo.d +++ b/libphobos/libdruntime/core/stdcpp/typeinfo.d @@ -11,11 +11,14 @@ module core.stdcpp.typeinfo; +import core.attribute : weak; + version (CppRuntime_DigitalMars) { import core.stdcpp.exception; extern (C++, "std"): + @nogc: class type_info { @@ -27,8 +30,8 @@ version (CppRuntime_DigitalMars) //bool operator==(const type_info rhs) const; //bool operator!=(const type_info rhs) const; - final bool before(const type_info rhs) const; - final const(char)* name() const; + final bool before(const type_info rhs) const nothrow; + final const(char)* name() const nothrow; protected: //type_info(); private: @@ -59,6 +62,7 @@ else version (CppRuntime_Microsoft) import core.stdcpp.exception; extern (C++, "std"): + @nogc: struct __type_info_node { @@ -70,12 +74,11 @@ else version (CppRuntime_Microsoft) class type_info { - //virtual ~this(); - void dtor() { } // reserve slot in vtbl[] + @weak ~this() nothrow {} //bool operator==(const type_info rhs) const; //bool operator!=(const type_info rhs) const; - final bool before(const type_info rhs) const; - final const(char)* name(__type_info_node* p = &__type_info_root_node) const; + final bool before(const type_info rhs) const nothrow; + final const(char)* name(__type_info_node* p = &__type_info_root_node) const nothrow; private: void* pdata; @@ -85,13 +88,13 @@ else version (CppRuntime_Microsoft) class bad_cast : exception { - this(const(char)* msg = "bad cast"); + this(const(char)* msg = "bad cast") @nogc nothrow { super(msg); } //virtual ~this(); } class bad_typeid : exception { - this(const(char)* msg = "bad typeid"); + this(const(char)* msg = "bad typeid") @nogc nothrow { super(msg); } //virtual ~this(); } } @@ -101,19 +104,21 @@ else version (CppRuntime_Gcc) extern (C++, "__cxxabiv1") { - class __class_type_info; + extern(C++, class) struct __class_type_info; } extern (C++, "std"): + @nogc: - class type_info + abstract class type_info { - void dtor1(); // consume destructor slot in vtbl[] - void dtor2(); // consume destructor slot in vtbl[] - final const(char)* name()() const nothrow { + @weak ~this() {} + @weak final const(char)* name() const nothrow + { return _name[0] == '*' ? _name + 1 : _name; } - final bool before()(const type_info _arg) const { + @weak final bool before(const type_info _arg) const nothrow + { import core.stdc.string : strcmp; return (_name[0] == '*' && _arg._name[0] == '*') ? _name < _arg._name @@ -123,24 +128,66 @@ else version (CppRuntime_Gcc) bool __is_pointer_p() const; bool __is_function_p() const; bool __do_catch(const type_info, void**, uint) const; - bool __do_upcast(const __class_type_info, void**) const; + bool __do_upcast(const __class_type_info*, void**) const; + protected: const(char)* _name; - this(const(char)*); + + this(const(char)* name) { _name = name; } + } + + class bad_cast : exception + { + this() nothrow {} + //~this(); + @weak override const(char)* what() const nothrow { return "bad cast"; } + } + + class bad_typeid : exception + { + this() nothrow {} + //~this(); + @weak override const(char)* what() const nothrow { return "bad typeid"; } + } +} +else version (CppRuntime_Clang) +{ + import core.stdcpp.exception; + + extern (C++, "std"): + @nogc: + + abstract class type_info + { + @weak ~this() {} + @weak final const(char)* name() const nothrow + { + return __type_name; + } + @weak final bool before(const type_info __arg) const nothrow + { + return __type_name < __arg.__type_name; + } + //bool operator==(const type_info) const; + + protected: + const(char)* __type_name; + + this(const(char)* __n) { __type_name = __n; } } class bad_cast : exception { - this(); + this() nothrow {} //~this(); - override const(char)* what() const; + @weak override const(char)* what() const nothrow { return "bad cast"; } } class bad_typeid : exception { - this(); + this() nothrow {} //~this(); - override const(char)* what() const; + @weak override const(char)* what() const nothrow { return "bad typeid"; } } } else diff --git a/libphobos/libdruntime/core/stdcpp/utility.d b/libphobos/libdruntime/core/stdcpp/utility.d new file mode 100644 index 0000000..a34e356 --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/utility.d @@ -0,0 +1,50 @@ +/** + * D header file for interaction with Microsoft C++ + * + * Copyright: Copyright (c) 2018 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/utility.d) + */ + +module core.stdcpp.utility; + +import core.stdcpp.xutility : StdNamespace; + +extern(C++, (StdNamespace)): +@nogc: + +/** +* D language counterpart to C++ std::pair. +* +* C++ reference: $(LINK2 https://en.cppreference.com/w/cpp/utility/pair) +*/ +struct pair(T1, T2) +{ + /// + alias first_type = T1; + /// + alias second_type = T2; + + /// + T1 first; + /// + T2 second; + + // FreeBSD has pair as non-POD so add a contructor + version (FreeBSD) + { + this(T1 t1, T2 t2) inout + { + first = t1; + second = t2; + } + this(ref return scope inout pair!(T1, T2) src) inout + { + first = src.first; + second = src.second; + } + } +} diff --git a/libphobos/libdruntime/core/stdcpp/vector.d b/libphobos/libdruntime/core/stdcpp/vector.d new file mode 100644 index 0000000..c64dbf6 --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/vector.d @@ -0,0 +1,850 @@ +/** + * D header file for interaction with C++ std::vector. + * + * Copyright: Copyright (c) 2018 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Guillaume Chatelet + * Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/vector.d) + */ + +module core.stdcpp.vector; + +/////////////////////////////////////////////////////////////////////////////// +// std::vector declaration. +// +// Current caveats : +// - missing noexcept +// - nothrow @trusted @nogc for most functions depend on knowledge +// of T's construction/destruction/assignment semantics +/////////////////////////////////////////////////////////////////////////////// + +import core.stdcpp.allocator; + +enum DefaultConstruct { value } + +/// Constructor argument for default construction +enum Default = DefaultConstruct(); + +extern(C++, "std"): + +alias vector(T) = vector!(T, allocator!T); +extern(C++, class) struct vector(T, Alloc) +{ + import core.lifetime : forward, move, core_emplace = emplace; + + static assert(!is(T == bool), "vector!bool not supported!"); +extern(D): + + /// + alias size_type = size_t; + /// + alias difference_type = ptrdiff_t; + /// + alias value_type = T; + /// + alias allocator_type = Alloc; + /// + alias pointer = T*; + /// + alias const_pointer = const(T)*; + + /// MSVC allocates on default initialisation in debug, which can't be modelled by D `struct` + @disable this(); + + /// + alias length = size; + /// + alias opDollar = length; + + /// + size_t[2] opSlice(size_t dim : 0)(size_t start, size_t end) const pure nothrow @safe @nogc { return [start, end]; } + + /// + ref inout(T) opIndex(size_t index) inout pure nothrow @safe @nogc { return as_array[index]; } + /// + inout(T)[] opIndex(size_t[2] slice) inout pure nothrow @safe @nogc { return as_array[slice[0] .. slice[1]]; } + /// + inout(T)[] opIndex() inout pure nothrow @safe @nogc { return as_array(); } + + /// + ref vector opAssign(U)(auto ref vector!(U, Alloc) s) { opAssign(s.as_array); return this; } + /// + ref vector opAssign(T[] array) + { + clear(); + reserve(array.length); + insert(0, array); + return this; + } + + /// + void opIndexAssign()(auto ref T val, size_t index) { as_array[index] = val; } + /// + void opIndexAssign()(auto ref T val, size_t[2] slice) { as_array[slice[0] .. slice[1]] = val; } + /// + void opIndexAssign(T[] val, size_t[2] slice) { as_array[slice[0] .. slice[1]] = val[]; } + /// + void opIndexAssign()(auto ref T val) { as_array[] = val; } + /// + void opIndexAssign(T[] val) { as_array[] = val[]; } + + /// + void opIndexOpAssign(string op)(auto ref T val, size_t index) { mixin("as_array[index] " ~ op ~ "= val;"); } + /// + void opIndexOpAssign(string op)(auto ref T val, size_t[2] slice) { mixin("as_array[slice[0] .. slice[1]] " ~ op ~ "= val;"); } + /// + void opIndexOpAssign(string op)(T[] val, size_t[2] slice) { mixin("as_array[slice[0] .. slice[1]] " ~ op ~ "= val[];"); } + /// + void opIndexOpAssign(string op)(auto ref T val) { mixin("as_array[] " ~ op ~ "= val;"); } + /// + void opIndexOpAssign(string op)(T[] val) { mixin("as_array[] " ~ op ~ "= val[];"); } + + /// + ref inout(T) front() inout pure nothrow @safe @nogc { return as_array[0]; } + /// + ref inout(T) back() inout pure nothrow @safe @nogc { return as_array[$-1]; } + + /// + ref vector opOpAssign(string op : "~")(auto ref T item) { push_back(forward!item); return this; } + /// + ref vector opOpAssign(string op : "~")(T[] array) { insert(length, array); return this; } + + /// + void append(T[] array) { insert(length, array); } + + /// Performs elementwise equality check. + bool opEquals(this This, That)(auto ref That rhs) + if (is(immutable That == immutable vector)) { return as_array == rhs.as_array; } + + /// Performs lexicographical comparison. + static if (is(typeof((ref T a, ref T b) => a < b))) + int opCmp(this This, That)(auto ref That rhs) + if (is(immutable That == immutable vector)) { return __cmp(as_array, rhs.as_array); } + + /// Hash to allow `vector`s to be used as keys for built-in associative arrays. + /// **The result will generally not be the same as C++ `std::hash>`.** + size_t toHash() const { return .hashOf(as_array); } + + // Modifiers + /// + void push_back(U)(auto ref U element) + { + emplace_back(forward!element); + } + + version (CppRuntime_Microsoft) + { + //---------------------------------------------------------------------------------- + // Microsoft runtime + //---------------------------------------------------------------------------------- + + /// + this(DefaultConstruct) @nogc { _Alloc_proxy(); } + /// + this()(size_t count) + { + _Alloc_proxy(); + _Buy(count); + scope(failure) _Tidy(); + _Get_data()._Mylast = _Udefault(_Get_data()._Myfirst, count); + } + /// + this()(size_t count, auto ref T val) + { + _Alloc_proxy(); + _Buy(count); + scope(failure) _Tidy(); + _Get_data()._Mylast = _Ufill(_Get_data()._Myfirst, count, val); + } + /// + this()(T[] array) + { + _Alloc_proxy(); + _Buy(array.length); + scope(failure) _Tidy(); + _Get_data()._Mylast = _Utransfer!false(array.ptr, array.ptr + array.length, _Get_data()._Myfirst); + } + /// + this(this) + { + _Alloc_proxy(); + pointer _First = _Get_data()._Myfirst; + pointer _Last = _Get_data()._Mylast; + _Buy(_Last - _First); + scope(failure) _Tidy(); + _Get_data()._Mylast = _Utransfer!false(_First, _Last, _Get_data()._Myfirst); + } + + /// + ~this() { _Tidy(); } + + /// + ref inout(Alloc) get_allocator() inout pure nothrow @safe @nogc { return _Getal(); } + + /// + size_type max_size() const pure nothrow @safe @nogc { return ((size_t.max / T.sizeof) - 1) / 2; } // HACK: clone the windows version precisely? + + /// + size_type size() const pure nothrow @safe @nogc { return _Get_data()._Mylast - _Get_data()._Myfirst; } + /// + size_type capacity() const pure nothrow @safe @nogc { return _Get_data()._Myend - _Get_data()._Myfirst; } + /// + bool empty() const pure nothrow @safe @nogc { return _Get_data()._Myfirst == _Get_data()._Mylast; } + /// + inout(T)* data() inout pure nothrow @safe @nogc { return _Get_data()._Myfirst; } + /// + inout(T)[] as_array() inout pure nothrow @trusted @nogc { return _Get_data()._Myfirst[0 .. size()]; } + /// + ref inout(T) at(size_type i) inout pure nothrow @trusted @nogc { return _Get_data()._Myfirst[0 .. size()][i]; } + + /// + ref T emplace_back(Args...)(auto ref Args args) + { + if (_Has_unused_capacity()) + return _Emplace_back_with_unused_capacity(forward!args); + return *_Emplace_reallocate(_Get_data()._Mylast, forward!args); + } + + /// + void reserve(const size_type newCapacity) + { + if (newCapacity > capacity()) + { +// if (newCapacity > max_size()) +// _Xlength(); + _Reallocate_exactly(newCapacity); + } + } + + /// + void shrink_to_fit() + { + if (_Has_unused_capacity()) + { + if (empty()) + _Tidy(); + else + _Reallocate_exactly(size()); + } + } + + /// + void pop_back() + { + static if (_ITERATOR_DEBUG_LEVEL == 2) + { + assert(!empty(), "vector empty before pop"); + _Orphan_range(_Get_data()._Mylast - 1, _Get_data()._Mylast); + } + destroy!false(_Get_data()._Mylast[-1]); + --_Get_data()._Mylast; + } + + /// + void clear() + { + _Base._Orphan_all(); + _Destroy(_Get_data()._Myfirst, _Get_data()._Mylast); + _Get_data()._Mylast = _Get_data()._Myfirst; + } + + /// + void resize()(const size_type newsize) + { + static assert(is(typeof({static T i;})), T.stringof ~ ".this() is annotated with @disable."); + _Resize(newsize, (pointer _Dest, size_type _Count) => _Udefault(_Dest, _Count)); + } + + /// + void resize()(const size_type newsize, auto ref T val) + { + _Resize(newsize, (pointer _Dest, size_type _Count) => _Ufill(_Dest, _Count, forward!val)); + } + + void emplace(Args...)(size_t offset, auto ref Args args) + { + pointer _Whereptr = _Get_data()._Myfirst + offset; + pointer _Oldlast = _Get_data()._Mylast; + if (_Has_unused_capacity()) + { + if (_Whereptr == _Oldlast) + _Emplace_back_with_unused_capacity(forward!args); + else + { + T _Obj = T(forward!args); + static if (_ITERATOR_DEBUG_LEVEL == 2) + _Orphan_range(_Whereptr, _Oldlast); + move(_Oldlast[-1], *_Oldlast); + ++_Get_data()._Mylast; + _Move_backward_unchecked(_Whereptr, _Oldlast - 1, _Oldlast); + move(_Obj, *_Whereptr); + } + return; + } + _Emplace_reallocate(_Whereptr, forward!args); + } + + /// + void insert(size_t offset, T[] array) + { + pointer _Where = _Get_data()._Myfirst + offset; + pointer _First = array.ptr; + pointer _Last = _First + array.length; + + const size_type _Count = array.length; + const size_type _Whereoff = offset; + const bool _One_at_back = _Count == 1 && _Get_data()._Myfirst + _Whereoff == _Get_data()._Mylast; + + if (_Count == 0) + { + // nothing to do, avoid invalidating iterators + } + else if (_Count > _Unused_capacity()) + { // reallocate + const size_type _Oldsize = size(); + +// if (_Count > max_size() - _Oldsize) +// _Xlength(); + + const size_type _Newsize = _Oldsize + _Count; + const size_type _Newcapacity = _Calculate_growth(_Newsize); + + pointer _Newvec = _Getal().allocate(_Newcapacity); + pointer _Constructed_last = _Newvec + _Whereoff + _Count; + pointer _Constructed_first = _Constructed_last; + + try + { + _Utransfer!false(_First, _Last, _Newvec + _Whereoff); + _Constructed_first = _Newvec + _Whereoff; + + if (_One_at_back) + { + _Utransfer!(true, true)(_Get_data()._Myfirst, _Get_data()._Mylast, _Newvec); + } + else + { + _Utransfer!true(_Get_data()._Myfirst, _Where, _Newvec); + _Constructed_first = _Newvec; + _Utransfer!true(_Where, _Get_data()._Mylast, _Newvec + _Whereoff + _Count); + } + } + catch (Throwable e) + { + _Destroy(_Constructed_first, _Constructed_last); + _Getal().deallocate(_Newvec, _Newcapacity); + throw e; + } + + _Change_array(_Newvec, _Newsize, _Newcapacity); + } + else + { // Attempt to provide the strong guarantee for EmplaceConstructible failure. + // If we encounter copy/move construction/assignment failure, provide the basic guarantee. + // (For one-at-back, this provides the strong guarantee.) + + pointer _Oldlast = _Get_data()._Mylast; + const size_type _Affected_elements = cast(size_type)(_Oldlast - _Where); + + if (_Count < _Affected_elements) + { // some affected elements must be assigned + _Get_data()._Mylast = _Utransfer!true(_Oldlast - _Count, _Oldlast, _Oldlast); + _Move_backward_unchecked(_Where, _Oldlast - _Count, _Oldlast); + _Destroy(_Where, _Where + _Count); + + try + { + _Utransfer!false(_First, _Last, _Where); + } + catch (Throwable e) + { + // glue the broken pieces back together + try + { + _Utransfer!true(_Where + _Count, _Where + 2 * _Count, _Where); + } + catch (Throwable e) + { + // vaporize the detached piece + static if (_ITERATOR_DEBUG_LEVEL == 2) + _Orphan_range(_Where, _Oldlast); + _Destroy(_Where + _Count, _Get_data()._Mylast); + _Get_data()._Mylast = _Where; + throw e; + } + + _Move_unchecked(_Where + 2 * _Count, _Get_data()._Mylast, _Where + _Count); + _Destroy(_Oldlast, _Get_data()._Mylast); + _Get_data()._Mylast = _Oldlast; + throw e; + } + } + else + { // affected elements don't overlap before/after + pointer _Relocated = _Where + _Count; + _Get_data()._Mylast = _Utransfer!true(_Where, _Oldlast, _Relocated); + _Destroy(_Where, _Oldlast); + + try + { + _Utransfer!false(_First, _Last, _Where); + } + catch (Throwable e) + { + // glue the broken pieces back together + try + { + _Utransfer!true(_Relocated, _Get_data()._Mylast, _Where); + } + catch (Throwable e) + { + // vaporize the detached piece + static if (_ITERATOR_DEBUG_LEVEL == 2) + _Orphan_range(_Where, _Oldlast); + _Destroy(_Relocated, _Get_data()._Mylast); + _Get_data()._Mylast = _Where; + throw e; + } + + _Destroy(_Relocated, _Get_data()._Mylast); + _Get_data()._Mylast = _Oldlast; + throw e; + } + } + static if (_ITERATOR_DEBUG_LEVEL == 2) + _Orphan_range(_Where, _Oldlast); + } + } + + private: + import core.stdcpp.xutility : MSVCLinkDirectives; + + // Make sure the object files wont link against mismatching objects + mixin MSVCLinkDirectives!true; + + pragma(inline, true) + { + ref inout(_Base.Alloc) _Getal() inout pure nothrow @safe @nogc { return _Base._Mypair._Myval1; } + ref inout(_Base.ValTy) _Get_data() inout pure nothrow @safe @nogc { return _Base._Mypair._Myval2; } + } + + void _Alloc_proxy() @nogc + { + static if (_ITERATOR_DEBUG_LEVEL > 0) + _Base._Alloc_proxy(); + } + + void _AssignAllocator(ref const(allocator_type) al) nothrow @nogc + { + static if (_Base._Mypair._HasFirst) + _Getal() = al; + } + + bool _Buy(size_type _Newcapacity) @trusted @nogc + { + _Get_data()._Myfirst = null; + _Get_data()._Mylast = null; + _Get_data()._Myend = null; + + if (_Newcapacity == 0) + return false; + + // TODO: how to handle this in D? kinda like a range exception... +// if (_Newcapacity > max_size()) +// _Xlength(); + + _Get_data()._Myfirst = _Getal().allocate(_Newcapacity); + _Get_data()._Mylast = _Get_data()._Myfirst; + _Get_data()._Myend = _Get_data()._Myfirst + _Newcapacity; + + return true; + } + + static void _Destroy(pointer _First, pointer _Last) + { + for (; _First != _Last; ++_First) + destroy!false(*_First); + } + + void _Tidy() + { + _Base._Orphan_all(); + if (_Get_data()._Myfirst) + { + _Destroy(_Get_data()._Myfirst, _Get_data()._Mylast); + _Getal().deallocate(_Get_data()._Myfirst, capacity()); + _Get_data()._Myfirst = null; + _Get_data()._Mylast = null; + _Get_data()._Myend = null; + } + } + + size_type _Unused_capacity() const pure nothrow @safe @nogc + { + return _Get_data()._Myend - _Get_data()._Mylast; + } + + bool _Has_unused_capacity() const pure nothrow @safe @nogc + { + return _Get_data()._Myend != _Get_data()._Mylast; + } + + ref T _Emplace_back_with_unused_capacity(Args...)(auto ref Args args) + { + core_emplace(_Get_data()._Mylast, forward!args); + static if (_ITERATOR_DEBUG_LEVEL == 2) + _Orphan_range(_Get_data()._Mylast, _Get_data()._Mylast); + return *_Get_data()._Mylast++; + } + + pointer _Emplace_reallocate(_Valty...)(pointer _Whereptr, auto ref _Valty _Val) + { + const size_type _Whereoff = _Whereptr - _Get_data()._Myfirst; + const size_type _Oldsize = size(); + + // TODO: what should we do in D? kinda like a range overflow? +// if (_Oldsize == max_size()) +// _Xlength(); + + const size_type _Newsize = _Oldsize + 1; + const size_type _Newcapacity = _Calculate_growth(_Newsize); + + pointer _Newvec = _Getal().allocate(_Newcapacity); + pointer _Constructed_last = _Newvec + _Whereoff + 1; + pointer _Constructed_first = _Constructed_last; + + try + { + core_emplace(_Newvec + _Whereoff, forward!_Val); + _Constructed_first = _Newvec + _Whereoff; + if (_Whereptr == _Get_data()._Mylast) + _Utransfer!(true, true)(_Get_data()._Myfirst, _Get_data()._Mylast, _Newvec); + else + { + _Utransfer!true(_Get_data()._Myfirst, _Whereptr, _Newvec); + _Constructed_first = _Newvec; + _Utransfer!true(_Whereptr, _Get_data()._Mylast, _Newvec + _Whereoff + 1); + } + } + catch (Throwable e) + { + _Destroy(_Constructed_first, _Constructed_last); + _Getal().deallocate(_Newvec, _Newcapacity); + throw e; + } + + _Change_array(_Newvec, _Newsize, _Newcapacity); + return _Get_data()._Myfirst + _Whereoff; + } + + void _Resize(_Lambda)(const size_type _Newsize, _Lambda _Udefault_or_fill) + { + const size_type _Oldsize = size(); + const size_type _Oldcapacity = capacity(); + + if (_Newsize > _Oldcapacity) + { +// if (_Newsize > max_size()) +// _Xlength(); + + const size_type _Newcapacity = _Calculate_growth(_Newsize); + + pointer _Newvec = _Getal().allocate(_Newcapacity); + pointer _Appended_first = _Newvec + _Oldsize; + pointer _Appended_last = _Appended_first; + + try + { + _Appended_last = _Udefault_or_fill(_Appended_first, _Newsize - _Oldsize); + _Utransfer!(true, true)(_Get_data()._Myfirst, _Get_data()._Mylast, _Newvec); + } + catch (Throwable e) + { + _Destroy(_Appended_first, _Appended_last); + _Getal().deallocate(_Newvec, _Newcapacity); + throw e; + } + _Change_array(_Newvec, _Newsize, _Newcapacity); + } + else if (_Newsize > _Oldsize) + { + pointer _Oldlast = _Get_data()._Mylast; + _Get_data()._Mylast = _Udefault_or_fill(_Oldlast, _Newsize - _Oldsize); + static if (_ITERATOR_DEBUG_LEVEL == 2) + _Orphan_range(_Oldlast, _Oldlast); + } + else if (_Newsize == _Oldsize) + { + // nothing to do, avoid invalidating iterators + } + else + { + pointer _Newlast = _Get_data()._Myfirst + _Newsize; + static if (_ITERATOR_DEBUG_LEVEL == 2) + _Orphan_range(_Newlast, _Get_data()._Mylast); + _Destroy(_Newlast, _Get_data()._Mylast); + _Get_data()._Mylast = _Newlast; + } + } + + void _Reallocate_exactly(const size_type _Newcapacity) + { + import core.lifetime : moveEmplace; + + const size_type _Size = size(); + pointer _Newvec = _Getal().allocate(_Newcapacity); + + try + { + for (size_t i = _Size; i > 0; ) + { + --i; + moveEmplace(_Get_data()._Myfirst[i], _Newvec[i]); + } + } + catch (Throwable e) + { + _Getal().deallocate(_Newvec, _Newcapacity); + throw e; + } + + _Change_array(_Newvec, _Size, _Newcapacity); + } + + void _Change_array(pointer _Newvec, const size_type _Newsize, const size_type _Newcapacity) + { + _Base._Orphan_all(); + + if (_Get_data()._Myfirst != null) + { + _Destroy(_Get_data()._Myfirst, _Get_data()._Mylast); + _Getal().deallocate(_Get_data()._Myfirst, capacity()); + } + + _Get_data()._Myfirst = _Newvec; + _Get_data()._Mylast = _Newvec + _Newsize; + _Get_data()._Myend = _Newvec + _Newcapacity; + } + + size_type _Calculate_growth(const size_type _Newsize) const pure nothrow @nogc @safe + { + const size_type _Oldcapacity = capacity(); + if (_Oldcapacity > max_size() - _Oldcapacity/2) + return _Newsize; + const size_type _Geometric = _Oldcapacity + _Oldcapacity/2; + if (_Geometric < _Newsize) + return _Newsize; + return _Geometric; + } + + struct _Uninitialized_backout + { + this() @disable; + this(pointer _Dest) + { + _First = _Dest; + _Last = _Dest; + } + ~this() + { + _Destroy(_First, _Last); + } + void _Emplace_back(Args...)(auto ref Args args) + { + core_emplace(_Last, forward!args); + ++_Last; + } + pointer _Release() + { + _First = _Last; + return _Last; + } + private: + pointer _First; + pointer _Last; + } + pointer _Utransfer(bool _move, bool _ifNothrow = false)(pointer _First, pointer _Last, pointer _Dest) + { + // TODO: if copy/move are trivial, then we can memcpy/memmove + auto _Backout = _Uninitialized_backout(_Dest); + for (; _First != _Last; ++_First) + { + static if (_move && (!_ifNothrow || true)) // isNothrow!T (move in D is always nothrow! ...until opPostMove) + _Backout._Emplace_back(move(*_First)); + else + _Backout._Emplace_back(*_First); + } + return _Backout._Release(); + } + pointer _Ufill()(pointer _Dest, size_t _Count, auto ref T val) + { + // TODO: if T.sizeof == 1 and no elaborate constructor, fast-path to memset + // TODO: if copy ctor/postblit are nothrow, just range assign + auto _Backout = _Uninitialized_backout(_Dest); + for (; 0 < _Count; --_Count) + _Backout._Emplace_back(val); + return _Backout._Release(); + } + pointer _Udefault()(pointer _Dest, size_t _Count) + { + // TODO: if zero init, then fast-path to zeromem + auto _Backout = _Uninitialized_backout(_Dest); + for (; 0 < _Count; --_Count) + _Backout._Emplace_back(); + return _Backout._Release(); + } + pointer _Move_unchecked(pointer _First, pointer _Last, pointer _Dest) + { + // TODO: can `memmove` if conditions are right... + for (; _First != _Last; ++_Dest, ++_First) + move(*_First, *_Dest); + return _Dest; + } + pointer _Move_backward_unchecked(pointer _First, pointer _Last, pointer _Dest) + { + while (_First != _Last) + move(*--_Last, *--_Dest); + return _Dest; + } + + static if (_ITERATOR_DEBUG_LEVEL == 2) + { + void _Orphan_range(pointer _First, pointer _Last) const @nogc + { + import core.stdcpp.xutility : _Lockit, _LOCK_DEBUG; + + alias const_iterator = _Base.const_iterator; + auto _Lock = _Lockit(_LOCK_DEBUG); + + const_iterator** _Pnext = cast(const_iterator**)_Get_data()._Base._Getpfirst(); + if (!_Pnext) + return; + + while (*_Pnext) + { + if ((*_Pnext)._Ptr < _First || _Last < (*_Pnext)._Ptr) + { + _Pnext = cast(const_iterator**)(*_Pnext)._Base._Getpnext(); + } + else + { + (*_Pnext)._Base._Clrcont(); + *_Pnext = *cast(const_iterator**)(*_Pnext)._Base._Getpnext(); + } + } + } + } + + _Vector_alloc!(_Vec_base_types!(T, Alloc)) _Base; + } + else version (None) + { + size_type size() const pure nothrow @safe @nogc { return 0; } + size_type capacity() const pure nothrow @safe @nogc { return 0; } + bool empty() const pure nothrow @safe @nogc { return true; } + + inout(T)* data() inout pure nothrow @safe @nogc { return null; } + inout(T)[] as_array() inout pure nothrow @trusted @nogc { return null; } + ref inout(T) at(size_type i) inout pure nothrow @trusted @nogc { data()[0]; } + } + else + { + static assert(false, "C++ runtime not supported"); + } +} + + +// platform detail +private: +version (CppRuntime_Microsoft) +{ + import core.stdcpp.xutility : _ITERATOR_DEBUG_LEVEL; + + extern (C++, struct) struct _Vec_base_types(_Ty, _Alloc0) + { + alias Ty = _Ty; + alias Alloc = _Alloc0; + } + + extern (C++, class) struct _Vector_alloc(_Alloc_types) + { + import core.stdcpp.xutility : _Compressed_pair; + extern(D): + @nogc: + + alias Ty = _Alloc_types.Ty; + alias Alloc = _Alloc_types.Alloc; + alias ValTy = _Vector_val!Ty; + + void _Orphan_all() nothrow @safe + { + static if (is(typeof(ValTy._Base))) + _Mypair._Myval2._Base._Orphan_all(); + } + + static if (_ITERATOR_DEBUG_LEVEL != 0) + { + import core.stdcpp.xutility : _Container_proxy; + + alias const_iterator = _Vector_const_iterator!(ValTy); + + ~this() + { + _Free_proxy(); + } + + void _Alloc_proxy() @trusted + { + import core.lifetime : emplace; + + alias _Alproxy = Alloc.rebind!_Container_proxy; + _Alproxy _Proxy_allocator = _Alproxy(_Mypair._Myval1); + _Mypair._Myval2._Base._Myproxy = _Proxy_allocator.allocate(1); + emplace(_Mypair._Myval2._Base._Myproxy); + _Mypair._Myval2._Base._Myproxy._Mycont = &_Mypair._Myval2._Base; + } + void _Free_proxy() + { + alias _Alproxy = Alloc.rebind!_Container_proxy; + _Alproxy _Proxy_allocator = _Alproxy(_Mypair._Myval1); + _Orphan_all(); + destroy!false(_Mypair._Myval2._Base._Myproxy); + _Proxy_allocator.deallocate(_Mypair._Myval2._Base._Myproxy, 1); + _Mypair._Myval2._Base._Myproxy = null; + } + } + + _Compressed_pair!(Alloc, ValTy) _Mypair; + } + + extern (C++, class) struct _Vector_val(T) + { + import core.stdcpp.xutility : _Container_base; + import core.stdcpp.type_traits : is_empty; + + alias pointer = T*; + + static if (!is_empty!_Container_base.value) + _Container_base _Base; + + pointer _Myfirst; // pointer to beginning of array + pointer _Mylast; // pointer to current end of sequence + pointer _Myend; // pointer to end of array + } + + static if (_ITERATOR_DEBUG_LEVEL > 0) + { + extern (C++, class) struct _Vector_const_iterator(_Myvec) + { + import core.stdcpp.xutility : _Iterator_base; + import core.stdcpp.type_traits : is_empty; + + static if (!is_empty!_Iterator_base.value) + _Iterator_base _Base; + _Myvec.pointer _Ptr; + } + } +} diff --git a/libphobos/libdruntime/core/stdcpp/xutility.d b/libphobos/libdruntime/core/stdcpp/xutility.d new file mode 100644 index 0000000..fa61701 --- /dev/null +++ b/libphobos/libdruntime/core/stdcpp/xutility.d @@ -0,0 +1,427 @@ +/** + * D header file for interaction with Microsoft C++ + * + * Copyright: Copyright (c) 2018 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/xutility.d) + */ + +module core.stdcpp.xutility; + +@nogc: + +version (CppRuntime_Clang) +{ + import core.internal.traits : AliasSeq; + enum StdNamespace = AliasSeq!("std", "__1"); +} +else +{ + enum StdNamespace = "std"; +} + +enum CppStdRevision : uint +{ + cpp98 = 199711, + cpp11 = 201103, + cpp14 = 201402, + cpp17 = 201703 +} + +enum __cplusplus = __traits(getTargetInfo, "cppStd"); + +// wrangle C++ features +enum __cpp_sized_deallocation = __cplusplus >= CppStdRevision.cpp14 || is(typeof(_MSC_VER)) ? 201309 : 0; +enum __cpp_aligned_new = __cplusplus >= CppStdRevision.cpp17 ? 201606 : 0; + + +version (CppRuntime_Microsoft) +{ + import core.stdcpp.type_traits : is_empty; + + version (_MSC_VER_1200) + enum _MSC_VER = 1200; + else version (_MSC_VER_1300) + enum _MSC_VER = 1300; + else version (_MSC_VER_1310) + enum _MSC_VER = 1310; + else version (_MSC_VER_1400) + enum _MSC_VER = 1400; + else version (_MSC_VER_1500) + enum _MSC_VER = 1500; + else version (_MSC_VER_1600) + enum _MSC_VER = 1600; + else version (_MSC_VER_1700) + enum _MSC_VER = 1700; + else version (_MSC_VER_1800) + enum _MSC_VER = 1800; + else version (_MSC_VER_1900) + enum _MSC_VER = 1900; + else version (_MSC_VER_1910) + enum _MSC_VER = 1910; + else version (_MSC_VER_1911) + enum _MSC_VER = 1911; + else version (_MSC_VER_1912) + enum _MSC_VER = 1912; + else version (_MSC_VER_1913) + enum _MSC_VER = 1913; + else version (_MSC_VER_1914) + enum _MSC_VER = 1914; + else version (_MSC_VER_1915) + enum _MSC_VER = 1915; + else version (_MSC_VER_1916) + enum _MSC_VER = 1916; + else version (_MSC_VER_1920) + enum _MSC_VER = 1920; + else version (_MSC_VER_1921) + enum _MSC_VER = 1921; + else version (_MSC_VER_1922) + enum _MSC_VER = 1922; + else version (_MSC_VER_1923) + enum _MSC_VER = 1923; + else + enum _MSC_VER = 1923; // assume most recent compiler version + + // Client code can mixin the set of MSVC linker directives + mixin template MSVCLinkDirectives(bool failMismatch = false) + { + import core.stdcpp.xutility : __CXXLIB__, _ITERATOR_DEBUG_LEVEL; + + static if (__CXXLIB__ == "libcmtd") + { + pragma(lib, "libcpmtd"); + static if (failMismatch) + pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MTd_StaticDebug"); + } + else static if (__CXXLIB__ == "msvcrtd") + { + pragma(lib, "msvcprtd"); + static if (failMismatch) + pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MDd_DynamicDebug"); + } + else static if (__CXXLIB__ == "libcmt") + { + pragma(lib, "libcpmt"); + static if (failMismatch) + pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MT_StaticRelease"); + } + else static if (__CXXLIB__ == "msvcrt") + { + pragma(lib, "msvcprt"); + static if (failMismatch) + pragma(linkerDirective, "/FAILIFMISMATCH:RuntimeLibrary=MD_DynamicRelease"); + } + static if (failMismatch) + pragma(linkerDirective, "/FAILIFMISMATCH:_ITERATOR_DEBUG_LEVEL=" ~ ('0' + _ITERATOR_DEBUG_LEVEL)); + } + + // HACK: should we guess _DEBUG for `debug` builds? + version (NDEBUG) {} + else debug version = _DEBUG; + + // By specific user request + version (_ITERATOR_DEBUG_LEVEL_0) + enum _ITERATOR_DEBUG_LEVEL = 0; + else version (_ITERATOR_DEBUG_LEVEL_1) + enum _ITERATOR_DEBUG_LEVEL = 1; + else version (_ITERATOR_DEBUG_LEVEL_2) + enum _ITERATOR_DEBUG_LEVEL = 2; + else + { + // Match the C Runtime + static if (__CXXLIB__ == "libcmtd" || __CXXLIB__ == "msvcrtd") + enum _ITERATOR_DEBUG_LEVEL = 2; + else static if (__CXXLIB__ == "libcmt" || __CXXLIB__ == "msvcrt" || + __CXXLIB__ == "msvcrt100" || __CXXLIB__ == "msvcrt110" || __CXXLIB__ == "msvcrt120") + enum _ITERATOR_DEBUG_LEVEL = 0; + else + { + static if (__CXXLIB__.length > 0) + pragma(msg, "Unrecognised C++ runtime library '" ~ __CXXLIB__ ~ "'"); + + // No runtime specified; as a best-guess, -release will produce code that matches the MSVC release CRT + version (_DEBUG) + enum _ITERATOR_DEBUG_LEVEL = 2; + else + enum _ITERATOR_DEBUG_LEVEL = 0; + } + } + + // convenient alias for the C++ std library name + enum __CXXLIB__ = __traits(getTargetInfo, "cppRuntimeLibrary"); + +extern(C++, "std"): +package: + enum _LOCK_DEBUG = 3; + + extern(C++, class) struct _Lockit + { + this(int) nothrow @nogc @safe; + ~this() nothrow @nogc @safe; + + private: + int _Locktype; + } + void dummyDtor() { assert(false); } + pragma(linkerDirective, "/ALTERNATENAME:" ~ _Lockit.__dtor.mangleof ~ "=" ~ dummyDtor.mangleof); + + struct _Container_base0 + { + extern(D): + void _Orphan_all()() nothrow @nogc @safe {} + void _Swap_all()(ref _Container_base0) nothrow @nogc @safe {} + void _Swap_proxy_and_iterators()(ref _Container_base0) nothrow {} + } + struct _Iterator_base0 + { + extern(D): + void _Adopt()(const(void)*) nothrow @nogc @safe {} + const(_Container_base0)* _Getcont()() const nothrow @nogc @safe { return null; } + + enum bool _Unwrap_when_unverified = true; + } + + struct _Container_proxy + { + const(_Container_base12)* _Mycont; + _Iterator_base12* _Myfirstiter; + } + + struct _Container_base12 + { + extern(D): + inout(_Iterator_base12*)*_Getpfirst()() inout nothrow @nogc @safe + { + return _Myproxy == null ? null : &_Myproxy._Myfirstiter; + } + void _Orphan_all()() nothrow @nogc @safe + { + static if (_ITERATOR_DEBUG_LEVEL == 2) + { + if (_Myproxy != null) + { + auto _Lock = _Lockit(_LOCK_DEBUG); + for (_Iterator_base12 **_Pnext = &_Myproxy._Myfirstiter; *_Pnext != null; *_Pnext = (*_Pnext)._Mynextiter) + (*_Pnext)._Myproxy = null; + _Myproxy._Myfirstiter = null; + } + } + } +// void _Swap_all()(ref _Container_base12) nothrow @nogc; + + void _Swap_proxy_and_iterators()(ref _Container_base12 _Right) nothrow + { + static if (_ITERATOR_DEBUG_LEVEL == 2) + auto _Lock = _Lockit(_LOCK_DEBUG); + + _Container_proxy* _Temp = _Myproxy; + _Myproxy = _Right._Myproxy; + _Right._Myproxy = _Temp; + + if (_Myproxy) + _Myproxy._Mycont = &this; + + if (_Right._Myproxy) + _Right._Myproxy._Mycont = &_Right; + } + + _Container_proxy* _Myproxy; + } + + struct _Iterator_base12 + { + extern(D): + void _Adopt()(_Container_base12 *_Parent) nothrow @nogc @safe + { + if (_Parent == null) + { + static if (_ITERATOR_DEBUG_LEVEL == 2) + { + auto _Lock = _Lockit(_LOCK_DEBUG); + _Orphan_me(); + } + } + else + { + _Container_proxy *_Parent_proxy = _Parent._Myproxy; + + static if (_ITERATOR_DEBUG_LEVEL == 2) + { + if (_Myproxy != _Parent_proxy) + { + auto _Lock = _Lockit(_LOCK_DEBUG); + _Orphan_me(); + _Mynextiter = _Parent_proxy._Myfirstiter; + _Parent_proxy._Myfirstiter = &this; + _Myproxy = _Parent_proxy; + } + } + else + _Myproxy = _Parent_proxy; + } + } + void _Clrcont()() nothrow @nogc @safe + { + _Myproxy = null; + } + const(_Container_base12)* _Getcont()() const nothrow @nogc @safe + { + return _Myproxy == null ? null : _Myproxy._Mycont; + } + inout(_Iterator_base12*)*_Getpnext()() inout nothrow @nogc @safe + { + return &_Mynextiter; + } + void _Orphan_me()() nothrow @nogc @safe + { + static if (_ITERATOR_DEBUG_LEVEL == 2) + { + if (_Myproxy != null) + { + _Iterator_base12 **_Pnext = &_Myproxy._Myfirstiter; + while (*_Pnext != null && *_Pnext != &this) + _Pnext = &(*_Pnext)._Mynextiter; + assert(*_Pnext, "ITERATOR LIST CORRUPTED!"); + *_Pnext = _Mynextiter; + _Myproxy = null; + } + } + } + + enum bool _Unwrap_when_unverified = _ITERATOR_DEBUG_LEVEL == 0; + + _Container_proxy *_Myproxy; + _Iterator_base12 *_Mynextiter; + } + + static if (_ITERATOR_DEBUG_LEVEL == 0) + { + alias _Container_base = _Container_base0; + alias _Iterator_base = _Iterator_base0; + } + else + { + alias _Container_base = _Container_base12; + alias _Iterator_base = _Iterator_base12; + } + + extern (C++, class) struct _Compressed_pair(_Ty1, _Ty2, bool Ty1Empty = is_empty!_Ty1.value) + { + pragma (inline, true): + extern(D): + pure nothrow @nogc: + enum _HasFirst = !Ty1Empty; + + ref inout(_Ty1) first() inout @safe { return _Myval1; } + ref inout(_Ty2) second() inout @safe { return _Myval2; } + + static if (!Ty1Empty) + _Ty1 _Myval1; + else + { + @property ref inout(_Ty1) _Myval1() inout @trusted { return *_GetBase(); } + private inout(_Ty1)* _GetBase() inout @trusted { return cast(inout(_Ty1)*)&this; } + } + _Ty2 _Myval2; + } + + // these are all [[noreturn]] + void _Xbad_alloc() nothrow; + void _Xinvalid_argument(const(char)* message) nothrow; + void _Xlength_error(const(char)* message) nothrow; + void _Xout_of_range(const(char)* message) nothrow; + void _Xoverflow_error(const(char)* message) nothrow; + void _Xruntime_error(const(char)* message) nothrow; +} +else version (CppRuntime_Clang) +{ + import core.stdcpp.type_traits : is_empty; + +extern(C++, "std"): + + extern (C++, class) struct __compressed_pair(_T1, _T2) + { + pragma (inline, true): + extern(D): + enum Ty1Empty = is_empty!_T1.value; + enum Ty2Empty = is_empty!_T2.value; + + ref inout(_T1) first() inout nothrow @safe @nogc { return __value1_; } + ref inout(_T2) second() inout nothrow @safe @nogc { return __value2_; } + + private: + private inout(_T1)* __get_base1() inout { return cast(inout(_T1)*)&this; } + private inout(_T2)* __get_base2() inout { return cast(inout(_T2)*)&__get_base1()[Ty1Empty ? 0 : 1]; } + + static if (!Ty1Empty) + _T1 __value1_; + else + @property ref inout(_T1) __value1_() inout nothrow @trusted @nogc { return *__get_base1(); } + static if (!Ty2Empty) + _T2 __value2_; + else + @property ref inout(_T2) __value2_() inout nothrow @trusted @nogc { return *__get_base2(); } + } +} +version (CppRuntime_Gcc) +{ + import core.atomic; + + alias _Atomic_word = int; + + void __atomic_add_dispatch()(_Atomic_word* __mem, int __val) nothrow @nogc @safe + { + version (__GTHREADS) + { + // TODO: check __gthread_active_p() +// if (__gthread_active_p()) + __atomic_add(__mem, __val); +// } +// else +// __atomic_add_single(__mem, __val); + } + else + __atomic_add_single(__mem, __val); + } + + void __atomic_add()(_Atomic_word* __mem, int __val) nothrow @nogc @safe + { + atomicFetchAdd!(MemoryOrder.acq_rel)(*__mem, __val); + } + + void __atomic_add_single()(_Atomic_word* __mem, int __val) nothrow @nogc @safe + { + *__mem += __val; + } + + _Atomic_word __exchange_and_add_dispatch()(_Atomic_word* __mem, int __val) nothrow @nogc @safe + { + version (__GTHREADS) + { + // TODO: check __gthread_active_p() + return __exchange_and_add(__mem, __val); + +// if (__gthread_active_p()) +// return __exchange_and_add(__mem, __val); +// else +// return __exchange_and_add_single(__mem, __val); + } + else + return __exchange_and_add_single(__mem, __val); + } + + _Atomic_word __exchange_and_add()(_Atomic_word* __mem, int __val) nothrow @nogc @safe + { + return atomicFetchAdd!(MemoryOrder.acq_rel)(*__mem, __val); + } + + _Atomic_word __exchange_and_add_single()(_Atomic_word* __mem, int __val) nothrow @nogc @safe + { + _Atomic_word __result = *__mem; + *__mem += __val; + return __result; + } +} diff --git a/libphobos/libdruntime/core/sync/barrier.d b/libphobos/libdruntime/core/sync/barrier.d index dd54d5c..1d04421 100644 --- a/libphobos/libdruntime/core/sync/barrier.d +++ b/libphobos/libdruntime/core/sync/barrier.d @@ -17,14 +17,8 @@ module core.sync.barrier; public import core.sync.exception; -private import core.sync.condition; -private import core.sync.mutex; - -version (Posix) -{ - private import core.stdc.errno; - private import core.sys.posix.pthread; -} +import core.sync.condition; +import core.sync.mutex; //////////////////////////////////////////////////////////////////////////////// @@ -60,7 +54,7 @@ class Barrier { assert( limit > 0 ); } - body + do { m_lock = new Mutex; m_cond = new Condition( m_lock ); @@ -112,40 +106,35 @@ private: // Unit Tests //////////////////////////////////////////////////////////////////////////////// - -version (unittest) +unittest { - private import core.thread; + import core.thread; + int numThreads = 10; + auto barrier = new Barrier( numThreads ); + auto synInfo = new Object; + int numReady = 0; + int numPassed = 0; - unittest + void threadFn() { - int numThreads = 10; - auto barrier = new Barrier( numThreads ); - auto synInfo = new Object; - int numReady = 0; - int numPassed = 0; - - void threadFn() + synchronized( synInfo ) { - synchronized( synInfo ) - { - ++numReady; - } - barrier.wait(); - synchronized( synInfo ) - { - ++numPassed; - } + ++numReady; } - - auto group = new ThreadGroup; - - for ( int i = 0; i < numThreads; ++i ) + barrier.wait(); + synchronized( synInfo ) { - group.create( &threadFn ); + ++numPassed; } - group.joinAll(); - assert( numReady == numThreads && numPassed == numThreads ); } + + auto group = new ThreadGroup; + + for ( int i = 0; i < numThreads; ++i ) + { + group.create( &threadFn ); + } + group.joinAll(); + assert( numReady == numThreads && numPassed == numThreads ); } diff --git a/libphobos/libdruntime/core/sync/condition.d b/libphobos/libdruntime/core/sync/condition.d index 8afa8f7..674d78d 100644 --- a/libphobos/libdruntime/core/sync/condition.d +++ b/libphobos/libdruntime/core/sync/condition.d @@ -22,20 +22,20 @@ public import core.time; version (Windows) { - private import core.sync.semaphore; - private import core.sys.windows.basetsd /+: HANDLE+/; - private import core.sys.windows.winbase /+: CloseHandle, CreateSemaphoreA, CRITICAL_SECTION, + import core.sync.semaphore; + import core.sys.windows.basetsd /+: HANDLE+/; + import core.sys.windows.winbase /+: CloseHandle, CreateSemaphoreA, CRITICAL_SECTION, DeleteCriticalSection, EnterCriticalSection, INFINITE, InitializeCriticalSection, LeaveCriticalSection, ReleaseSemaphore, WAIT_OBJECT_0, WaitForSingleObject+/; - private import core.sys.windows.windef /+: BOOL, DWORD+/; - private import core.sys.windows.winerror /+: WAIT_TIMEOUT+/; + import core.sys.windows.windef /+: BOOL, DWORD+/; + import core.sys.windows.winerror /+: WAIT_TIMEOUT+/; } else version (Posix) { - private import core.sync.config; - private import core.stdc.errno; - private import core.sys.posix.pthread; - private import core.sys.posix.time; + import core.sync.config; + import core.stdc.errno; + import core.sys.posix.pthread; + import core.sys.posix.time; } else { @@ -76,27 +76,71 @@ class Condition */ this( Mutex m ) nothrow @safe { + this(m, true); + } + + /// ditto + this( shared Mutex m ) shared nothrow @safe + { + this(m, true); + } + + // + private this(this Q, M)( M m, bool _unused_ ) nothrow @trusted + if ((is(Q == Condition) && is(M == Mutex)) || + (is(Q == shared Condition) && is(M == shared Mutex))) + { version (Windows) { - m_blockLock = CreateSemaphoreA( null, 1, 1, null ); + static if (is(Q == Condition)) + { + alias HANDLE_TYPE = void*; + } + else + { + alias HANDLE_TYPE = shared(void*); + } + m_blockLock = cast(HANDLE_TYPE) CreateSemaphoreA( null, 1, 1, null ); if ( m_blockLock == m_blockLock.init ) throw new SyncError( "Unable to initialize condition" ); - scope(failure) CloseHandle( m_blockLock ); + scope(failure) CloseHandle( cast(void*) m_blockLock ); - m_blockQueue = CreateSemaphoreA( null, 0, int.max, null ); + m_blockQueue = cast(HANDLE_TYPE) CreateSemaphoreA( null, 0, int.max, null ); if ( m_blockQueue == m_blockQueue.init ) throw new SyncError( "Unable to initialize condition" ); - scope(failure) CloseHandle( m_blockQueue ); + scope(failure) CloseHandle( cast(void*) m_blockQueue ); - InitializeCriticalSection( &m_unblockLock ); + InitializeCriticalSection( cast(RTL_CRITICAL_SECTION*) &m_unblockLock ); m_assocMutex = m; } else version (Posix) { m_assocMutex = m; - int rc = pthread_cond_init( &m_hndl, null ); - if ( rc ) - throw new SyncError( "Unable to initialize condition" ); + static if ( is( typeof( pthread_condattr_setclock ) ) ) + { + () @trusted + { + pthread_condattr_t attr = void; + int rc = pthread_condattr_init( &attr ); + if ( rc ) + throw new SyncError( "Unable to initialize condition" ); + rc = pthread_condattr_setclock( &attr, CLOCK_MONOTONIC ); + if ( rc ) + throw new SyncError( "Unable to initialize condition" ); + rc = pthread_cond_init( cast(pthread_cond_t*) &m_hndl, &attr ); + if ( rc ) + throw new SyncError( "Unable to initialize condition" ); + rc = pthread_condattr_destroy( &attr ); + if ( rc ) + throw new SyncError( "Unable to initialize condition" ); + } (); + } + else + { + int rc = pthread_cond_init( cast(pthread_cond_t*) &m_hndl, null ); + if ( rc ) + throw new SyncError( "Unable to initialize condition" ); + } } } @@ -135,12 +179,23 @@ class Condition return m_assocMutex; } + /// ditto + @property shared(Mutex) mutex() shared + { + return m_assocMutex; + } + // undocumented function for internal use final @property Mutex mutex_nothrow() pure nothrow @safe @nogc { return m_assocMutex; } + // ditto + final @property shared(Mutex) mutex_nothrow() shared pure nothrow @safe @nogc + { + return m_assocMutex; + } //////////////////////////////////////////////////////////////////////////// // General Actions @@ -155,19 +210,31 @@ class Condition */ void wait() { + wait!(typeof(this))(true); + } + + /// ditto + void wait() shared + { + wait!(typeof(this))(true); + } + + /// ditto + void wait(this Q)( bool _unused_ ) + if (is(Q == Condition) || is(Q == shared Condition)) + { version (Windows) { timedWait( INFINITE ); } else version (Posix) { - int rc = pthread_cond_wait( &m_hndl, m_assocMutex.handleAddr() ); + int rc = pthread_cond_wait( cast(pthread_cond_t*) &m_hndl, (cast(Mutex) m_assocMutex).handleAddr() ); if ( rc ) throw new SyncError( "Unable to wait for condition" ); } } - /** * Suspends the calling thread until a notification occurs or until the * supplied time period has elapsed. @@ -185,11 +252,24 @@ class Condition * true if notified before the timeout and false if not. */ bool wait( Duration val ) + { + return wait!(typeof(this))(val, true); + } + + /// ditto + bool wait( Duration val ) shared + { + return wait!(typeof(this))(val, true); + } + + /// ditto + bool wait(this Q)( Duration val, bool _unused_ ) + if (is(Q == Condition) || is(Q == shared Condition)) in { assert( !val.isNegative ); } - body + do { version (Windows) { @@ -209,8 +289,8 @@ class Condition timespec t = void; mktspec( t, val ); - int rc = pthread_cond_timedwait( &m_hndl, - m_assocMutex.handleAddr(), + int rc = pthread_cond_timedwait( cast(pthread_cond_t*) &m_hndl, + (cast(Mutex) m_assocMutex).handleAddr(), &t ); if ( !rc ) return true; @@ -220,7 +300,6 @@ class Condition } } - /** * Notifies one waiter. * @@ -229,19 +308,46 @@ class Condition */ void notify() { + notify!(typeof(this))(true); + } + + /// ditto + void notify() shared + { + notify!(typeof(this))(true); + } + + /// ditto + void notify(this Q)( bool _unused_ ) + if (is(Q == Condition) || is(Q == shared Condition)) + { version (Windows) { - notify( false ); + notify_( false ); } else version (Posix) { - int rc = pthread_cond_signal( &m_hndl ); + // Since OS X 10.7 (Lion), pthread_cond_signal returns EAGAIN after retrying 8192 times, + // so need to retrying while it returns EAGAIN. + // + // 10.7.0 (Lion): http://www.opensource.apple.com/source/Libc/Libc-763.11/pthreads/pthread_cond.c + // 10.8.0 (Mountain Lion): http://www.opensource.apple.com/source/Libc/Libc-825.24/pthreads/pthread_cond.c + // 10.10.0 (Yosemite): http://www.opensource.apple.com/source/libpthread/libpthread-105.1.4/src/pthread_cond.c + // 10.11.0 (El Capitan): http://www.opensource.apple.com/source/libpthread/libpthread-137.1.1/src/pthread_cond.c + // 10.12.0 (Sierra): http://www.opensource.apple.com/source/libpthread/libpthread-218.1.3/src/pthread_cond.c + // 10.13.0 (High Sierra): http://www.opensource.apple.com/source/libpthread/libpthread-301.1.6/src/pthread_cond.c + // 10.14.0 (Mojave): http://www.opensource.apple.com/source/libpthread/libpthread-330.201.1/src/pthread_cond.c + // 10.14.1 (Mojave): http://www.opensource.apple.com/source/libpthread/libpthread-330.220.2/src/pthread_cond.c + + int rc; + do { + rc = pthread_cond_signal( cast(pthread_cond_t*) &m_hndl ); + } while ( rc == EAGAIN ); if ( rc ) throw new SyncError( "Unable to notify condition" ); } } - /** * Notifies all waiters. * @@ -250,40 +356,84 @@ class Condition */ void notifyAll() { + notifyAll!(typeof(this))(true); + } + + /// ditto + void notifyAll() shared + { + notifyAll!(typeof(this))(true); + } + + /// ditto + void notifyAll(this Q)( bool _unused_ ) + if (is(Q == Condition) || is(Q == shared Condition)) + { version (Windows) { - notify( true ); + notify_( true ); } else version (Posix) { - int rc = pthread_cond_broadcast( &m_hndl ); + // Since OS X 10.7 (Lion), pthread_cond_broadcast returns EAGAIN after retrying 8192 times, + // so need to retrying while it returns EAGAIN. + // + // 10.7.0 (Lion): http://www.opensource.apple.com/source/Libc/Libc-763.11/pthreads/pthread_cond.c + // 10.8.0 (Mountain Lion): http://www.opensource.apple.com/source/Libc/Libc-825.24/pthreads/pthread_cond.c + // 10.10.0 (Yosemite): http://www.opensource.apple.com/source/libpthread/libpthread-105.1.4/src/pthread_cond.c + // 10.11.0 (El Capitan): http://www.opensource.apple.com/source/libpthread/libpthread-137.1.1/src/pthread_cond.c + // 10.12.0 (Sierra): http://www.opensource.apple.com/source/libpthread/libpthread-218.1.3/src/pthread_cond.c + // 10.13.0 (High Sierra): http://www.opensource.apple.com/source/libpthread/libpthread-301.1.6/src/pthread_cond.c + // 10.14.0 (Mojave): http://www.opensource.apple.com/source/libpthread/libpthread-330.201.1/src/pthread_cond.c + // 10.14.1 (Mojave): http://www.opensource.apple.com/source/libpthread/libpthread-330.220.2/src/pthread_cond.c + + int rc; + do { + rc = pthread_cond_broadcast( cast(pthread_cond_t*) &m_hndl ); + } while ( rc == EAGAIN ); if ( rc ) throw new SyncError( "Unable to notify condition" ); } } - private: version (Windows) { - bool timedWait( DWORD timeout ) + bool timedWait(this Q)( DWORD timeout ) + if (is(Q == Condition) || is(Q == shared Condition)) { + static if (is(Q == Condition)) + { + auto op(string o, T, V1)(ref T val, V1 mod) + { + return mixin("val " ~ o ~ "mod"); + } + } + else + { + auto op(string o, T, V1)(ref shared T val, V1 mod) + { + import core.atomic: atomicOp; + return atomicOp!o(val, mod); + } + } + int numSignalsLeft; int numWaitersGone; DWORD rc; - rc = WaitForSingleObject( m_blockLock, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) m_blockLock, INFINITE ); assert( rc == WAIT_OBJECT_0 ); - m_numWaitersBlocked++; + op!"+="(m_numWaitersBlocked, 1); - rc = ReleaseSemaphore( m_blockLock, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); assert( rc ); m_assocMutex.unlock(); scope(failure) m_assocMutex.lock(); - rc = WaitForSingleObject( m_blockQueue, timeout ); + rc = WaitForSingleObject( cast(HANDLE) m_blockQueue, timeout ); assert( rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT ); bool timedOut = (rc == WAIT_TIMEOUT); @@ -297,7 +447,7 @@ private: // timeout (or canceled) if ( m_numWaitersBlocked != 0 ) { - m_numWaitersBlocked--; + op!"-="(m_numWaitersBlocked, 1); // do not unblock next waiter below (already unblocked) numSignalsLeft = 0; } @@ -307,12 +457,12 @@ private: m_numWaitersGone = 1; } } - if ( --m_numWaitersToUnblock == 0 ) + if ( op!"-="(m_numWaitersToUnblock, 1) == 0 ) { if ( m_numWaitersBlocked != 0 ) { // open the gate - rc = ReleaseSemaphore( m_blockLock, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); assert( rc ); // do not open the gate below again numSignalsLeft = 0; @@ -323,14 +473,14 @@ private: } } } - else if ( ++m_numWaitersGone == int.max / 2 ) + else if ( op!"+="(m_numWaitersGone, 1) == int.max / 2 ) { // timeout/canceled or spurious event :-) - rc = WaitForSingleObject( m_blockLock, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) m_blockLock, INFINITE ); assert( rc == WAIT_OBJECT_0 ); // something is going on here - test of timeouts? - m_numWaitersBlocked -= m_numWaitersGone; - rc = ReleaseSemaphore( m_blockLock, 1, null ); + op!"-="(m_numWaitersBlocked, m_numWaitersGone); + rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); assert( rc == WAIT_OBJECT_0 ); m_numWaitersGone = 0; } @@ -342,17 +492,17 @@ private: // better now than spurious later (same as ResetEvent) for ( ; numWaitersGone > 0; --numWaitersGone ) { - rc = WaitForSingleObject( m_blockQueue, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) m_blockQueue, INFINITE ); assert( rc == WAIT_OBJECT_0 ); } // open the gate - rc = ReleaseSemaphore( m_blockLock, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); assert( rc ); } else if ( numSignalsLeft != 0 ) { // unblock next waiter - rc = ReleaseSemaphore( m_blockQueue, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) m_blockQueue, 1, null ); assert( rc ); } m_assocMutex.lock(); @@ -360,8 +510,25 @@ private: } - void notify( bool all ) + void notify_(this Q)( bool all ) + if (is(Q == Condition) || is(Q == shared Condition)) { + static if (is(Q == Condition)) + { + auto op(string o, T, V1)(ref T val, V1 mod) + { + return mixin("val " ~ o ~ "mod"); + } + } + else + { + auto op(string o, T, V1)(ref shared T val, V1 mod) + { + import core.atomic: atomicOp; + return atomicOp!o(val, mod); + } + } + DWORD rc; EnterCriticalSection( &m_unblockLock ); @@ -376,23 +543,23 @@ private: } if ( all ) { - m_numWaitersToUnblock += m_numWaitersBlocked; + op!"+="(m_numWaitersToUnblock, m_numWaitersBlocked); m_numWaitersBlocked = 0; } else { - m_numWaitersToUnblock++; - m_numWaitersBlocked--; + op!"+="(m_numWaitersToUnblock, 1); + op!"-="(m_numWaitersBlocked, 1); } LeaveCriticalSection( &m_unblockLock ); } else if ( m_numWaitersBlocked > m_numWaitersGone ) { - rc = WaitForSingleObject( m_blockLock, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) m_blockLock, INFINITE ); assert( rc == WAIT_OBJECT_0 ); if ( 0 != m_numWaitersGone ) { - m_numWaitersBlocked -= m_numWaitersGone; + op!"-="(m_numWaitersBlocked, m_numWaitersGone); m_numWaitersGone = 0; } if ( all ) @@ -403,10 +570,10 @@ private: else { m_numWaitersToUnblock = 1; - m_numWaitersBlocked--; + op!"-="(m_numWaitersBlocked, 1); } LeaveCriticalSection( &m_unblockLock ); - rc = ReleaseSemaphore( m_blockQueue, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) m_blockQueue, 1, null ); assert( rc ); } else @@ -439,12 +606,11 @@ private: // Unit Tests //////////////////////////////////////////////////////////////////////////////// - -version (unittest) +unittest { - private import core.thread; - private import core.sync.mutex; - private import core.sync.semaphore; + import core.thread; + import core.sync.mutex; + import core.sync.semaphore; void testNotify() @@ -601,11 +767,173 @@ version (unittest) assert( !alertedTwo ); } + testNotify(); + testNotifyAll(); + testWaitTimeout(); +} + +unittest +{ + import core.thread; + import core.sync.mutex; + import core.sync.semaphore; + - unittest + void testNotify() { - testNotify(); - testNotifyAll(); - testWaitTimeout(); + auto mutex = new shared Mutex; + auto condReady = new shared Condition( mutex ); + auto semDone = new Semaphore; + auto synLoop = new Object; + int numWaiters = 10; + int numTries = 10; + int numReady = 0; + int numTotal = 0; + int numDone = 0; + int numPost = 0; + + void waiter() + { + for ( int i = 0; i < numTries; ++i ) + { + synchronized( mutex ) + { + while ( numReady < 1 ) + { + condReady.wait(); + } + --numReady; + ++numTotal; + } + + synchronized( synLoop ) + { + ++numDone; + } + semDone.wait(); + } + } + + auto group = new ThreadGroup; + + for ( int i = 0; i < numWaiters; ++i ) + group.create( &waiter ); + + for ( int i = 0; i < numTries; ++i ) + { + for ( int j = 0; j < numWaiters; ++j ) + { + synchronized( mutex ) + { + ++numReady; + condReady.notify(); + } + } + while ( true ) + { + synchronized( synLoop ) + { + if ( numDone >= numWaiters ) + break; + } + Thread.yield(); + } + for ( int j = 0; j < numWaiters; ++j ) + { + semDone.notify(); + } + } + + group.joinAll(); + assert( numTotal == numWaiters * numTries ); + } + + + void testNotifyAll() + { + auto mutex = new shared Mutex; + auto condReady = new shared Condition( mutex ); + int numWaiters = 10; + int numReady = 0; + int numDone = 0; + bool alert = false; + + void waiter() + { + synchronized( mutex ) + { + ++numReady; + while ( !alert ) + condReady.wait(); + ++numDone; + } + } + + auto group = new ThreadGroup; + + for ( int i = 0; i < numWaiters; ++i ) + group.create( &waiter ); + + while ( true ) + { + synchronized( mutex ) + { + if ( numReady >= numWaiters ) + { + alert = true; + condReady.notifyAll(); + break; + } + } + Thread.yield(); + } + group.joinAll(); + assert( numReady == numWaiters && numDone == numWaiters ); + } + + + void testWaitTimeout() + { + auto mutex = new shared Mutex; + auto condReady = new shared Condition( mutex ); + bool waiting = false; + bool alertedOne = true; + bool alertedTwo = true; + + void waiter() + { + synchronized( mutex ) + { + waiting = true; + // we never want to miss the notification (30s) + alertedOne = condReady.wait( dur!"seconds"(30) ); + // but we don't want to wait long for the timeout (10ms) + alertedTwo = condReady.wait( dur!"msecs"(10) ); + } + } + + auto thread = new Thread( &waiter ); + thread.start(); + + while ( true ) + { + synchronized( mutex ) + { + if ( waiting ) + { + condReady.notify(); + break; + } + } + Thread.yield(); + } + thread.join(); + assert( waiting ); + assert( alertedOne ); + assert( !alertedTwo ); } + + testNotify(); + testNotifyAll(); + testWaitTimeout(); } diff --git a/libphobos/libdruntime/core/sync/config.d b/libphobos/libdruntime/core/sync/config.d index b2de225..39f7a8c 100644 --- a/libphobos/libdruntime/core/sync/config.d +++ b/libphobos/libdruntime/core/sync/config.d @@ -3,7 +3,7 @@ * specific to this package. * * Copyright: Copyright Sean Kelly 2005 - 2009. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Sean Kelly * Source: $(DRUNTIMESRC core/sync/_config.d) */ @@ -18,16 +18,17 @@ module core.sync.config; version (Posix) { - private import core.sys.posix.time; - private import core.sys.posix.sys.time; - private import core.time; + import core.sys.posix.pthread; + import core.sys.posix.time; + import core.sys.posix.sys.time; + import core.time; - void mktspec( ref timespec t ) nothrow + void mktspec( ref timespec t ) nothrow @nogc { - static if ( false && is( typeof( clock_gettime ) ) ) + static if ( is (typeof ( pthread_condattr_setclock ) ) ) { - clock_gettime( CLOCK_REALTIME, &t ); + clock_gettime( CLOCK_MONOTONIC, &t ); } else { @@ -41,14 +42,14 @@ version (Posix) } - void mktspec( ref timespec t, Duration delta ) nothrow + void mktspec( ref timespec t, Duration delta ) nothrow @nogc { mktspec( t ); mvtspec( t, delta ); } - void mvtspec( ref timespec t, Duration delta ) nothrow + void mvtspec( ref timespec t, Duration delta ) nothrow @nogc { auto val = delta; val += dur!"seconds"( t.tv_sec ); diff --git a/libphobos/libdruntime/core/sync/event.d b/libphobos/libdruntime/core/sync/event.d new file mode 100644 index 0000000..3795106 --- /dev/null +++ b/libphobos/libdruntime/core/sync/event.d @@ -0,0 +1,345 @@ +/** + * The event module provides a primitive for lightweight signaling of other threads + * (emulating Windows events on Posix) + * + * Copyright: Copyright (c) 2019 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Rainer Schuetze + * Source: $(DRUNTIMESRC core/sync/event.d) + */ +module core.sync.event; + +version (Windows) +{ + import core.sys.windows.basetsd /+: HANDLE +/; + import core.sys.windows.winerror /+: WAIT_TIMEOUT +/; + import core.sys.windows.winbase /+: CreateEvent, CloseHandle, SetEvent, ResetEvent, + WaitForSingleObject, INFINITE, WAIT_OBJECT_0+/; +} +else version (Posix) +{ + import core.sys.posix.pthread; + import core.sys.posix.sys.types; + import core.sys.posix.time; +} +else +{ + static assert(false, "Platform not supported"); +} + +import core.time; +import core.internal.abort : abort; + +/** + * represents an event. Clients of an event are suspended while waiting + * for the event to be "signaled". + * + * Implemented using `pthread_mutex` and `pthread_condition` on Posix and + * `CreateEvent` and `SetEvent` on Windows. +--- +import core.sync.event, core.thread, std.file; + +struct ProcessFile +{ + ThreadGroup group; + Event event; + void[] buffer; + + void doProcess() + { + event.wait(); + // process buffer + } + + void process(string filename) + { + event.initialize(true, false); + group = new ThreadGroup; + for (int i = 0; i < 10; ++i) + group.create(&doProcess); + + buffer = std.file.read(filename); + event.set(); + group.joinAll(); + event.terminate(); + } +} +--- + */ +struct Event +{ +nothrow @nogc: + /** + * Creates an event object. + * + * Params: + * manualReset = the state of the event is not reset automatically after resuming waiting clients + * initialState = initial state of the signal + */ + this(bool manualReset, bool initialState) + { + initialize(manualReset, initialState); + } + + /** + * Initializes an event object. Does nothing if the event is already initialized. + * + * Params: + * manualReset = the state of the event is not reset automatically after resuming waiting clients + * initialState = initial state of the signal + */ + void initialize(bool manualReset, bool initialState) + { + version (Windows) + { + if (m_event) + return; + m_event = CreateEvent(null, manualReset, initialState, null); + m_event || abort("Error: CreateEvent failed."); + } + else version (Posix) + { + if (m_initalized) + return; + pthread_mutex_init(cast(pthread_mutex_t*) &m_mutex, null) == 0 || + abort("Error: pthread_mutex_init failed."); + static if ( is( typeof( pthread_condattr_setclock ) ) ) + { + pthread_condattr_t attr = void; + pthread_condattr_init(&attr) == 0 || + abort("Error: pthread_condattr_init failed."); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC) == 0 || + abort("Error: pthread_condattr_setclock failed."); + pthread_cond_init(&m_cond, &attr) == 0 || + abort("Error: pthread_cond_init failed."); + pthread_condattr_destroy(&attr) == 0 || + abort("Error: pthread_condattr_destroy failed."); + } + else + { + pthread_cond_init(&m_cond, null) == 0 || + abort("Error: pthread_cond_init failed."); + } + m_state = initialState; + m_manualReset = manualReset; + m_initalized = true; + } + } + + // copying not allowed, can produce resource leaks + @disable this(this); + @disable void opAssign(Event); + + ~this() + { + terminate(); + } + + /** + * deinitialize event. Does nothing if the event is not initialized. There must not be + * threads currently waiting for the event to be signaled. + */ + void terminate() + { + version (Windows) + { + if (m_event) + CloseHandle(m_event); + m_event = null; + } + else version (Posix) + { + if (m_initalized) + { + pthread_mutex_destroy(&m_mutex) == 0 || + abort("Error: pthread_mutex_destroy failed."); + pthread_cond_destroy(&m_cond) == 0 || + abort("Error: pthread_cond_destroy failed."); + m_initalized = false; + } + } + } + + + /// Set the event to "signaled", so that waiting clients are resumed + void set() + { + version (Windows) + { + if (m_event) + SetEvent(m_event); + } + else version (Posix) + { + if (m_initalized) + { + pthread_mutex_lock(&m_mutex); + m_state = true; + pthread_cond_broadcast(&m_cond); + pthread_mutex_unlock(&m_mutex); + } + } + } + + /// Reset the event manually + void reset() + { + version (Windows) + { + if (m_event) + ResetEvent(m_event); + } + else version (Posix) + { + if (m_initalized) + { + pthread_mutex_lock(&m_mutex); + m_state = false; + pthread_mutex_unlock(&m_mutex); + } + } + } + + /** + * Wait for the event to be signaled without timeout. + * + * Returns: + * `true` if the event is in signaled state, `false` if the event is uninitialized or another error occured + */ + bool wait() + { + version (Windows) + { + return m_event && WaitForSingleObject(m_event, INFINITE) == WAIT_OBJECT_0; + } + else version (Posix) + { + return wait(Duration.max); + } + } + + /** + * Wait for the event to be signaled with timeout. + * + * Params: + * tmout = the maximum time to wait + * Returns: + * `true` if the event is in signaled state, `false` if the event was nonsignaled for the given time or + * the event is uninitialized or another error occured + */ + bool wait(Duration tmout) + { + version (Windows) + { + if (!m_event) + return false; + + auto maxWaitMillis = dur!("msecs")(uint.max - 1); + + while (tmout > maxWaitMillis) + { + auto res = WaitForSingleObject(m_event, uint.max - 1); + if (res != WAIT_TIMEOUT) + return res == WAIT_OBJECT_0; + tmout -= maxWaitMillis; + } + auto ms = cast(uint)(tmout.total!"msecs"); + return WaitForSingleObject(m_event, ms) == WAIT_OBJECT_0; + } + else version (Posix) + { + if (!m_initalized) + return false; + + pthread_mutex_lock(&m_mutex); + + int result = 0; + if (!m_state) + { + if (tmout == Duration.max) + { + result = pthread_cond_wait(&m_cond, &m_mutex); + } + else + { + import core.sync.config; + + timespec t = void; + mktspec(t, tmout); + + result = pthread_cond_timedwait(&m_cond, &m_mutex, &t); + } + } + if (result == 0 && !m_manualReset) + m_state = false; + + pthread_mutex_unlock(&m_mutex); + + return result == 0; + } + } + +private: + version (Windows) + { + HANDLE m_event; + } + else version (Posix) + { + pthread_mutex_t m_mutex; + pthread_cond_t m_cond; + bool m_initalized; + bool m_state; + bool m_manualReset; + } +} + +// Test single-thread (non-shared) use. +@nogc nothrow unittest +{ + // auto-reset, initial state false + Event ev1 = Event(false, false); + assert(!ev1.wait(1.dur!"msecs")); + ev1.set(); + assert(ev1.wait()); + assert(!ev1.wait(1.dur!"msecs")); + + // manual-reset, initial state true + Event ev2 = Event(true, true); + assert(ev2.wait()); + assert(ev2.wait()); + ev2.reset(); + assert(!ev2.wait(1.dur!"msecs")); +} + +unittest +{ + import core.thread, core.atomic; + + scope event = new Event(true, false); + int numThreads = 10; + shared int numRunning = 0; + + void testFn() + { + event.wait(8.dur!"seconds"); // timeout below limit for druntime test_runner + numRunning.atomicOp!"+="(1); + } + + auto group = new ThreadGroup; + + for (int i = 0; i < numThreads; ++i) + group.create(&testFn); + + auto start = MonoTime.currTime; + assert(numRunning == 0); + + event.set(); + group.joinAll(); + + assert(numRunning == numThreads); + + assert(MonoTime.currTime - start < 5.dur!"seconds"); +} diff --git a/libphobos/libdruntime/core/sync/mutex.d b/libphobos/libdruntime/core/sync/mutex.d index 024009f..b153ab9 100644 --- a/libphobos/libdruntime/core/sync/mutex.d +++ b/libphobos/libdruntime/core/sync/mutex.d @@ -20,13 +20,13 @@ public import core.sync.exception; version (Windows) { - private import core.sys.windows.winbase /+: CRITICAL_SECTION, DeleteCriticalSection, + import core.sys.windows.winbase /+: CRITICAL_SECTION, DeleteCriticalSection, EnterCriticalSection, InitializeCriticalSection, LeaveCriticalSection, TryEnterCriticalSection+/; } else version (Posix) { - private import core.sys.posix.pthread; + import core.sys.posix.pthread; } else { @@ -129,7 +129,7 @@ class Mutex : assert(obj.__monitor is null, "The provided object has a monitor already set!"); } - body + do { this(); obj.__monitor = cast(void*) &m_proxy; @@ -345,14 +345,10 @@ unittest @system @nogc nothrow unittest { import core.stdc.stdlib : malloc, free; + import core.lifetime : emplace; - void* p = malloc(__traits(classInstanceSize, Mutex)); - - auto ti = typeid(Mutex); - p[0 .. ti.initializer.length] = ti.initializer[]; - - shared Mutex mtx = cast(shared(Mutex)) p; - mtx.__ctor(); + auto mtx = cast(shared Mutex) malloc(__traits(classInstanceSize, Mutex)); + emplace(mtx); mtx.lock_nothrow(); diff --git a/libphobos/libdruntime/core/sync/rwmutex.d b/libphobos/libdruntime/core/sync/rwmutex.d index ba94a9e..89ef667 100644 --- a/libphobos/libdruntime/core/sync/rwmutex.d +++ b/libphobos/libdruntime/core/sync/rwmutex.d @@ -17,13 +17,13 @@ module core.sync.rwmutex; public import core.sync.exception; -private import core.sync.condition; -private import core.sync.mutex; -private import core.memory; +import core.sync.condition; +import core.sync.mutex; +import core.memory; version (Posix) { - private import core.sys.posix.pthread; + import core.sys.posix.pthread; } @@ -225,6 +225,51 @@ class ReadWriteMutex } } + /** + * Attempts to acquire a read lock on the enclosing mutex. If one can + * be obtained without blocking, the lock is acquired and true is + * returned. If not, the function blocks until either the lock can be + * obtained or the time elapsed exceeds $(D_PARAM timeout), returning + * true if the lock was acquired and false if the function timed out. + * + * Params: + * timeout = maximum amount of time to wait for the lock + * Returns: + * true if the lock was acquired and false if not. + */ + bool tryLock(Duration timeout) + { + synchronized( m_commonMutex ) + { + if (!shouldQueueReader) + { + ++m_numActiveReaders; + return true; + } + + enum zero = Duration.zero(); + if (timeout <= zero) + return false; + + ++m_numQueuedReaders; + scope(exit) --m_numQueuedReaders; + + enum maxWaitPerCall = dur!"hours"(24 * 365); // Avoid problems calling wait with huge Duration. + const initialTime = MonoTime.currTime; + m_readerQueue.wait(timeout < maxWaitPerCall ? timeout : maxWaitPerCall); + while (shouldQueueReader) + { + const timeElapsed = MonoTime.currTime - initialTime; + if (timeElapsed >= timeout) + return false; + auto nextWait = timeout - timeElapsed; + m_readerQueue.wait(nextWait < maxWaitPerCall ? nextWait : maxWaitPerCall); + } + ++m_numActiveReaders; + return true; + } + } + private: @property bool shouldQueueReader() @@ -341,6 +386,50 @@ class ReadWriteMutex } } + /** + * Attempts to acquire a write lock on the enclosing mutex. If one can + * be obtained without blocking, the lock is acquired and true is + * returned. If not, the function blocks until either the lock can be + * obtained or the time elapsed exceeds $(D_PARAM timeout), returning + * true if the lock was acquired and false if the function timed out. + * + * Params: + * timeout = maximum amount of time to wait for the lock + * Returns: + * true if the lock was acquired and false if not. + */ + bool tryLock(Duration timeout) + { + synchronized( m_commonMutex ) + { + if (!shouldQueueWriter) + { + ++m_numActiveWriters; + return true; + } + + enum zero = Duration.zero(); + if (timeout <= zero) + return false; + + ++m_numQueuedWriters; + scope(exit) --m_numQueuedWriters; + + enum maxWaitPerCall = dur!"hours"(24 * 365); // Avoid problems calling wait with huge Duration. + const initialTime = MonoTime.currTime; + m_writerQueue.wait(timeout < maxWaitPerCall ? timeout : maxWaitPerCall); + while (shouldQueueWriter) + { + const timeElapsed = MonoTime.currTime - initialTime; + if (timeElapsed >= timeout) + return false; + auto nextWait = timeout - timeElapsed; + m_writerQueue.wait(nextWait < maxWaitPerCall ? nextWait : maxWaitPerCall); + } + ++m_numActiveWriters; + return true; + } + } private: @property bool shouldQueueWriter() @@ -526,3 +615,79 @@ unittest runTest(ReadWriteMutex.Policy.PREFER_READERS); runTest(ReadWriteMutex.Policy.PREFER_WRITERS); } + +unittest +{ + import core.atomic, core.thread; + __gshared ReadWriteMutex rwmutex; + shared static bool threadTriedOnceToGetLock; + shared static bool threadFinallyGotLock; + + rwmutex = new ReadWriteMutex(); + atomicFence; + const maxTimeAllowedForTest = dur!"seconds"(20); + // Test ReadWriteMutex.Reader.tryLock(Duration). + { + static void testReaderTryLock() + { + assert(!rwmutex.reader.tryLock(Duration.min)); + threadTriedOnceToGetLock.atomicStore(true); + assert(rwmutex.reader.tryLock(Duration.max)); + threadFinallyGotLock.atomicStore(true); + rwmutex.reader.unlock; + } + assert(rwmutex.writer.tryLock(Duration.zero), "should have been able to obtain lock without blocking"); + auto otherThread = new Thread(&testReaderTryLock).start; + const failIfThisTimeisReached = MonoTime.currTime + maxTimeAllowedForTest; + Thread.yield; + // We started otherThread with the writer lock held so otherThread's + // first rwlock.reader.tryLock with timeout Duration.min should fail. + while (!threadTriedOnceToGetLock.atomicLoad) + { + assert(MonoTime.currTime < failIfThisTimeisReached, "timed out"); + Thread.yield; + } + rwmutex.writer.unlock; + // Soon after we release the writer lock otherThread's second + // rwlock.reader.tryLock with timeout Duration.max should succeed. + while (!threadFinallyGotLock.atomicLoad) + { + assert(MonoTime.currTime < failIfThisTimeisReached, "timed out"); + Thread.yield; + } + otherThread.join; + } + threadTriedOnceToGetLock.atomicStore(false); // Reset. + threadFinallyGotLock.atomicStore(false); // Reset. + // Test ReadWriteMutex.Writer.tryLock(Duration). + { + static void testWriterTryLock() + { + assert(!rwmutex.writer.tryLock(Duration.min)); + threadTriedOnceToGetLock.atomicStore(true); + assert(rwmutex.writer.tryLock(Duration.max)); + threadFinallyGotLock.atomicStore(true); + rwmutex.writer.unlock; + } + assert(rwmutex.reader.tryLock(Duration.zero), "should have been able to obtain lock without blocking"); + auto otherThread = new Thread(&testWriterTryLock).start; + const failIfThisTimeisReached = MonoTime.currTime + maxTimeAllowedForTest; + Thread.yield; + // We started otherThread with the reader lock held so otherThread's + // first rwlock.writer.tryLock with timeout Duration.min should fail. + while (!threadTriedOnceToGetLock.atomicLoad) + { + assert(MonoTime.currTime < failIfThisTimeisReached, "timed out"); + Thread.yield; + } + rwmutex.reader.unlock; + // Soon after we release the reader lock otherThread's second + // rwlock.writer.tryLock with timeout Duration.max should succeed. + while (!threadFinallyGotLock.atomicLoad) + { + assert(MonoTime.currTime < failIfThisTimeisReached, "timed out"); + Thread.yield; + } + otherThread.join; + } +} diff --git a/libphobos/libdruntime/core/sync/semaphore.d b/libphobos/libdruntime/core/sync/semaphore.d index 56ac7dc..cf2bddb 100644 --- a/libphobos/libdruntime/core/sync/semaphore.d +++ b/libphobos/libdruntime/core/sync/semaphore.d @@ -29,25 +29,25 @@ else version (WatchOS) version (Windows) { - private import core.sys.windows.basetsd /+: HANDLE+/; - private import core.sys.windows.winbase /+: CloseHandle, CreateSemaphoreA, INFINITE, + import core.sys.windows.basetsd /+: HANDLE+/; + import core.sys.windows.winbase /+: CloseHandle, CreateSemaphoreA, INFINITE, ReleaseSemaphore, WAIT_OBJECT_0, WaitForSingleObject+/; - private import core.sys.windows.windef /+: BOOL, DWORD+/; - private import core.sys.windows.winerror /+: WAIT_TIMEOUT+/; + import core.sys.windows.windef /+: BOOL, DWORD+/; + import core.sys.windows.winerror /+: WAIT_TIMEOUT+/; } else version (Darwin) { - private import core.sync.config; - private import core.stdc.errno; - private import core.sys.posix.time; - private import core.sys.darwin.mach.semaphore; + import core.sync.config; + import core.stdc.errno; + import core.sys.posix.time; + import core.sys.darwin.mach.semaphore; } else version (Posix) { - private import core.sync.config; - private import core.stdc.errno; - private import core.sys.posix.pthread; - private import core.sys.posix.semaphore; + import core.sync.config; + import core.stdc.errno; + import core.sys.posix.pthread; + import core.sys.posix.semaphore; } else { @@ -197,7 +197,7 @@ class Semaphore { assert( !period.isNegative ); } - body + do { version (Windows) { @@ -253,8 +253,11 @@ class Semaphore } else version (Posix) { + import core.sys.posix.time : clock_gettime, CLOCK_REALTIME; + timespec t = void; - mktspec( t, period ); + clock_gettime( CLOCK_REALTIME, &t ); + mvtspec( t, period ); while ( true ) { @@ -359,8 +362,7 @@ protected: // Unit Tests //////////////////////////////////////////////////////////////////////////////// - -version (unittest) +unittest { import core.thread, core.atomic; @@ -447,10 +449,6 @@ version (unittest) assert(alertedOne && !alertedTwo); } - - unittest - { - testWait(); - testWaitTimeout(); - } + testWait(); + testWaitTimeout(); } diff --git a/libphobos/libdruntime/core/sys/darwin/dlfcn.d b/libphobos/libdruntime/core/sys/darwin/dlfcn.d index a38d900..406d588 100644 --- a/libphobos/libdruntime/core/sys/darwin/dlfcn.d +++ b/libphobos/libdruntime/core/sys/darwin/dlfcn.d @@ -38,3 +38,8 @@ int dladdr(const scope void* addr, Dl_info* info); enum RTLD_NOLOAD = 0x10; enum RTLD_NODELETE = 0x80; enum RTLD_FIRST = 0x100; + +enum RTLD_NEXT = cast(void*) -1; +enum RTLD_DEFAULT = cast(void*) -2; +enum RTLD_SELF = cast(void*) -3; +enum RTLD_MAIN_ONLY = cast(void*) -5; diff --git a/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf32.d b/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf32.d index 2c35d0b..035bba5 100644 --- a/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf32.d +++ b/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf32.d @@ -112,7 +112,7 @@ extern (D) pure { auto ELF32_M_SYM(I)(I info) @safe { return info >> 8; } auto ELF32_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubye)size; } + auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubyte)size; } } struct Elf32_Cap diff --git a/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf64.d b/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf64.d index 94b7e42..f7d9247 100644 --- a/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf64.d +++ b/libphobos/libdruntime/core/sys/dragonflybsd/sys/elf64.d @@ -118,7 +118,7 @@ extern (D) pure { auto ELF64_M_SYM(I)(I info) @safe { return info >> 8; } auto ELF64_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF64_M_INFO(S, SZ)(S sym, SZ size) @safe { return (sym << 8) + cast(ubye)size; } + auto ELF64_M_INFO(S, SZ)(S sym, SZ size) @safe { return (sym << 8) + cast(ubyte)size; } } struct Elf64_Cap diff --git a/libphobos/libdruntime/core/sys/freebsd/sys/elf32.d b/libphobos/libdruntime/core/sys/freebsd/sys/elf32.d index 6145522..63cc4f9 100644 --- a/libphobos/libdruntime/core/sys/freebsd/sys/elf32.d +++ b/libphobos/libdruntime/core/sys/freebsd/sys/elf32.d @@ -112,7 +112,7 @@ extern (D) { auto ELF32_M_SYM(I)(I info) { return info >> 8; } auto ELF32_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubye)size; } + auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubyte)size; } } struct Elf32_Cap diff --git a/libphobos/libdruntime/core/sys/freebsd/sys/elf64.d b/libphobos/libdruntime/core/sys/freebsd/sys/elf64.d index f208b01..8c63e04 100644 --- a/libphobos/libdruntime/core/sys/freebsd/sys/elf64.d +++ b/libphobos/libdruntime/core/sys/freebsd/sys/elf64.d @@ -127,7 +127,7 @@ extern (D) { auto ELF64_M_SYM(I)(I info) { return info >> 8; } auto ELF64_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF64_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubye)size; } + auto ELF64_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubyte)size; } } struct Elf64_Cap diff --git a/libphobos/libdruntime/core/sys/linux/fs.d b/libphobos/libdruntime/core/sys/linux/fs.d new file mode 100644 index 0000000..5faa756 --- /dev/null +++ b/libphobos/libdruntime/core/sys/linux/fs.d @@ -0,0 +1,265 @@ +/** + * D header file for the linux/fs.h interface. + * + * This file has definitions for some important file table structures + * and constants and structures used by various generic file system + * ioctl's. + * + * Copyright: The D Language Foundation 2021. + * License : $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Authors : Luís Ferreira + */ +module core.sys.linux.fs; + +version (linux): + +public import core.sys.posix.sys.ioctl; + +import core.stdc.config : c_ulong, c_long; + +extern (C): +@system: +@nogc: +nothrow: + +enum INR_OPEN_CUR = 1024; /// Initial setting for nfile rlimits +enum INR_OPEN_MAX = 4096; /// Hard limit for nfile rlimits + +enum BLOCK_SIZE_BITS = 10; /// +enum BLOCK_SIZE = 1 << BLOCK_SIZE_BITS; /// + +enum +{ + SEEK_SET = 0, /// seek relative to beginning of file + SEEK_CUR = 1, /// seek relative to current file position + SEEK_END = 2, /// seek relative to end of file + SEEK_DATA = 3, /// seek to the next data + SEEK_HOLE = 4, /// seek to the next hole + SEEK_MAX = SEEK_HOLE, /// +} + +enum +{ + RENAME_NOREPLACE = 1 << 0, /// Don't overwrite target + RENAME_EXCHANGE = 1 << 1, /// Exchange source and dest + RENAME_WHITEOUT = 1 << 2, /// Whiteout source +} + +struct file_clone_range +{ + long src_fd; + ulong src_offset; + ulong src_length; + ulong dest_offset; +} + +struct fstrim_range +{ + ulong start; + ulong len; + ulong minlen; +} + +/** + * extent-same (dedupe) ioctls; these MUST match the btrfs ioctl definitions + */ +enum +{ + FILE_DEDUPE_RANGE_SAME = 0, + FILE_DEDUPE_RANGE_DIFFERS = 1, +} + +/** + * from struct btrfs_ioctl_file_extent_same_info + */ +struct file_dedupe_range_info +{ + long dest_fd; /// in - destination file + ulong dest_offset; /// in - start of extent in destination + ulong bytes_deduped; /// out - total # of bytes we were able to dedupe from this file. + /** status of this dedupe operation: + * < 0 for error + * == FILE_DEDUPE_RANGE_SAME if dedupe succeeds + * == FILE_DEDUPE_RANGE_DIFFERS if data differs + */ + int status; + uint reserved; /// must be zero +} + +/** + * from struct btrfs_ioctl_file_extent_same_args + */ +struct file_dedupe_range +{ + ulong src_offset; /// in - start of extent in source + ulong src_length; /// in - length of extent + ushort dest_count; /// in - total elements in info array + ushort reserved1; /// must be zero + uint reserved2; /// must be zero + file_dedupe_range_info[0] info; +} + +/** + * And dynamically-tunable limits and defaults: + */ +struct files_stat_struct +{ + c_ulong nr_files; /// read only + c_ulong nr_free_files; /// read only + c_ulong max_files; /// tunable +} + +struct inodes_stat_t +{ + c_long nr_inodes; + c_long nr_unused; + c_long[5] dummy; /// padding for sysctl ABI compatibility +} + +enum NR_FILE = 8192; + +/** + * Structure for FS_IOC_FSGETXATTR[A] and FS_IOC_FSSETXATTR. + */ +struct fsxattr +{ + uint fsx_xflags; + uint fsx_extsize; + uint fsx_nextents; + uint fsx_projid; /// project identifier + uint fsx_cowextsize; /// CoW extsize + ubyte[8] fsx_pad; +} + +/* + * Flags for the fsx_xflags field + */ +enum { + S_XFLAG_REALTIME = 0x00000001, /// data in realtime volume + S_XFLAG_PREALLOC = 0x00000002, /// preallocated file extents + S_XFLAG_IMMUTABLE = 0x00000008, /// file cannot be modified + S_XFLAG_APPEND = 0x00000010, /// all writes append + S_XFLAG_SYNC = 0x00000020, /// all writes synchronous + S_XFLAG_NOATIME = 0x00000040, /// do not update access time + S_XFLAG_NODUMP = 0x00000080, /// do not include in backups + S_XFLAG_RTINHERIT = 0x00000100, /// create with rt bit set + S_XFLAG_PROJINHERIT = 0x00000200, /// create with parents projid + S_XFLAG_NOSYMLINKS = 0x00000400, /// disallow symlink creation + S_XFLAG_EXTSIZE = 0x00000800, /// extent size allocator hint + S_XFLAG_EXTSZINHERIT = 0x00001000, /// inherit inode extent size + S_XFLAG_NODEFRAG = 0x00002000, /// do not defragment + S_XFLAG_FILESTREAM = 0x00004000, /// use filestream allocator + S_XFLAG_DAX = 0x00008000, /// use DAX for IO + S_XFLAG_COWEXTSIZE = 0x00010000, /// CoW extent size allocator hint + S_XFLAG_HASATTR = 0x80000000, /// no DIFLAG for this +} + +enum BLKROSET = _IO(0x12, 93); /// set device read-only +enum BLKROGET = _IO(0x12, 94); /// get read-only status +enum BLKRRPART = _IO(0x12, 95); /// re-read partition table +enum BLKGETSIZE = _IO(0x12, 96); /// return device size +enum BLKFLSBUF = _IO(0x12, 97); /// flush buffer cache +enum BLKRASET = _IO(0x12, 98); /// set read ahead for block device +enum BLKRAGET = _IO(0x12, 99); /// get current read ahead setting +enum BLKFRASET = _IO(0x12, 100); /// set filesystem +enum BLKFRAGET = _IO(0x12, 101); /// get filesystem +enum BLKSECTSET = _IO(0x12, 102); /// set max sectors per request +enum BLKSECTGET = _IO(0x12, 103); /// get max sectors per request +enum BLKSSZGET = _IO(0x12, 104); /// get block device sector size + + +enum BLKBSZGET = _IOR!size_t(0x12, 112); +enum BLKBSZSET = _IOW!size_t(0x12, 113); +enum BLKGETSIZE64 = _IOR!size_t(0x12, 114); +enum BLKTRACESTART = _IO(0x12, 116); +enum BLKTRACESTOP = _IO(0x12, 117); +enum BLKTRACETEARDOWN = _IO(0x12, 118); +enum BLKDISCARD = _IO(0x12, 119); +enum BLKIOMIN = _IO(0x12, 120); +enum BLKIOOPT = _IO(0x12, 121); +enum BLKALIGNOFF = _IO(0x12, 122); +enum BLKPBSZGET = _IO(0x12, 123); +enum BLKDISCARDZEROES = _IO(0x12, 124); +enum BLKSECDISCARD = _IO(0x12, 125); +enum BLKROTATIONAL = _IO(0x12, 126); +enum BLKZEROOUT = _IO(0x12, 127); + +enum BMAP_IOCTL = 1; /// obsolete - kept for compatibility +enum FIBMAP = _IO(0x00, 1); /// bmap access +enum FIGETBSZ = _IO(0x00, 2); /// get the block size used for bmap + +enum FSLABEL_MAX = 256; /// Max chars for the interface; each fs may differ + +/** + * Inode flags (FS_IOC_GETFLAGS / FS_IOC_SETFLAGS) + * + * Note: for historical reasons, these flags were originally used and + * defined for use by ext2/ext3, and then other file systems started + * using these flags so they wouldn't need to write their own version + * of chattr/lsattr (which was shipped as part of e2fsprogs). You + * should think twice before trying to use these flags in new + * contexts, or trying to assign these flags, since they are used both + * as the UAPI and the on-disk encoding for ext2/3/4. Also, we are + * almost out of 32-bit flags. :-) + * + * We have recently hoisted FS_IOC_FSGETXATTR / FS_IOC_FSSETXATTR from + * XFS to the generic FS level interface. This uses a structure that + * has padding and hence has more room to grow, so it may be more + * appropriate for many new use cases. + */ +enum { + FS_SECRM_FL = 0x00000001, /// Secure deletion + FS_UNRM_FL = 0x00000002, /// Undelete + FS_COMPR_FL = 0x00000004, /// Compress file + FS_SYNC_FL = 0x00000008, /// Synchronous updates + FS_IMMUTABLE_FL = 0x00000010, /// Immutable file + FS_APPEND_FL = 0x00000020, /// writes to file may only append + FS_NODUMP_FL = 0x00000040, /// do not dump file + FS_NOATIME_FL = 0x00000080, /// do not update atime + FS_DIRTY_FL = 0x00000100, /// Reserved for compression usage + FS_COMPRBLK_FL = 0x00000200, /// One or more compressed clusters + FS_NOCOMP_FL = 0x00000400, /// Don't compress + FS_ENCRYPT_FL = 0x00000800, /// Encrypted file + FS_BTREE_FL = 0x00001000, /// btree format dir + FS_INDEX_FL = 0x00001000, /// hash-indexed directory + FS_IMAGIC_FL = 0x00002000, /// AFS directory + FS_JOURNAL_DATA_FL = 0x00004000, /// Reserved for ext3 + FS_NOTAIL_FL = 0x00008000, /// file tail should not be merged + FS_DIRSYNC_FL = 0x00010000, /// dirsync behaviour (directories only) + FS_TOPDIR_FL = 0x00020000, /// Top of directory hierarchie + FS_HUGE_FILE_FL = 0x00040000, /// Reserved for ext4 + FS_EXTENT_FL = 0x00080000, /// Extents + FS_VERITY_FL = 0x00100000, /// Verity protected inode + FS_EA_INODE_FL = 0x00200000, /// Inode used for large EA + FS_EOFBLOCKS_FL = 0x00400000, /// Reserved for ext4 + FS_NOCOW_FL = 0x00800000, /// Do not cow file + FS_DAX_FL = 0x02000000, /// Inode is DAX + FS_INLINE_DATA_FL = 0x10000000, /// Reserved for ext4 + FS_PROJINHERIT_FL = 0x20000000, /// Create with parents projid + FS_CASEFOLD_FL = 0x40000000, /// Folder is case insensitive + FS_RESERVED_FL = 0x80000000, /// reserved for ext2 lib +} + +enum FS_FL_USER_VISIBLE = 0x0003DFFF; /// User visible flags +enum FS_FL_USER_MODIFIABLE = 0x000380FF; /// User modifiable flags + +enum SYNC_FILE_RANGE_WAIT_BEFORE = 1; +enum SYNC_FILE_RANGE_WRITE = 2; +enum SYNC_FILE_RANGE_WAIT_AFTER = 4; +enum SYNC_FILE_RANGE_WRITE_AND_WAIT = SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WAIT_AFTER; + +alias __kernel_rwf_t = int; + +/** + * Flags for preadv2/pwritev2: + */ +enum : __kernel_rwf_t { + RWF_HIPRI = 0x00000001, /// high priority request, poll if possible + RWF_DSYNC = 0x00000002, /// per-IO O_DSYNC + RWF_SYNC = 0x00000004, /// per-IO O_SYNC + RWF_NOWAIT = 0x00000008, /// per-IO, return -EAGAIN if operation would block + RWF_APPEND = 0x00000010, /// per-IO O_APPEND +} + +/// mask of flags supported by the kernel +enum RWF_SUPPORTED = RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT | RWF_APPEND; diff --git a/libphobos/libdruntime/core/sys/linux/io_uring.d b/libphobos/libdruntime/core/sys/linux/io_uring.d new file mode 100644 index 0000000..5e1a20c --- /dev/null +++ b/libphobos/libdruntime/core/sys/linux/io_uring.d @@ -0,0 +1,414 @@ +/** + * D header file for the io_uring interface. + * Available since Linux 5.1 + * + * Copyright: Copyright Jens Axboe 2019, + * Copyright Christoph Hellwig 2019. + * License : $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Authors : Luís Ferreira + */ +module core.sys.linux.io_uring; + +version (linux): + +import core.sys.linux.fs : __kernel_rwf_t; + +extern (C): +@system: +@nogc: +nothrow: +@system: + +/** + * IO submission data structure (Submission Queue Entry) + */ +struct io_uring_sqe +{ + /// type of operation for this sqe + ubyte opcode; + /// IOSQE_* flags + ubyte flags; + /// ioprio for the request + ushort ioprio; + /// file descriptor to do IO on + int fd; + union + { + /// offset into file + ulong off; + ulong addr2; + } + + union + { + /// pointer to buffer or iovecs + ulong addr; + ulong splice_off_in; + } + + /// buffer size or number of iovecs + uint len; + union + { + __kernel_rwf_t rw_flags; + uint fsync_flags; + + /// compatibility + ushort poll_events; + /// word-reversed for BE + uint poll32_events; + + uint sync_range_flags; + uint msg_flags; + uint timeout_flags; + uint accept_flags; + uint cancel_flags; + uint open_flags; + uint statx_flags; + uint fadvise_advice; + uint splice_flags; + uint rename_flags; + uint unlink_flags; + } + + /// data to be passed back at completion time + ulong user_data; + union + { + struct + { + /** + * pack this to avoid bogus arm OABI complaints + */ + union + { + align (1): + + /// index into fixed buffers, if used + ushort buf_index; + /// for grouped buffer selection + ushort buf_group; + } + + /// personality to use, if used + ushort personality; + int splice_fd_in; + } + + ulong[3] __pad2; + } +} + +enum +{ + IOSQE_FIXED_FILE_BIT = 0, + IOSQE_IO_DRAIN_BIT = 1, + IOSQE_IO_LINK_BIT = 2, + IOSQE_IO_HARDLINK_BIT = 3, + IOSQE_ASYNC_BIT = 4, + IOSQE_BUFFER_SELECT_BIT = 5 +} + +enum +{ + /// use fixed fileset + IOSQE_FIXED_FILE = 1U << IOSQE_FIXED_FILE_BIT, + /// issue after inflight IO + IOSQE_IO_DRAIN = 1U << IOSQE_IO_DRAIN_BIT, + /// links next sqe + IOSQE_IO_LINK = 1U << IOSQE_IO_LINK_BIT, + /// like LINK, but stronger + IOSQE_IO_HARDLINK = 1U << IOSQE_IO_HARDLINK_BIT, + /// always go async + IOSQE_ASYNC = 1U << IOSQE_ASYNC_BIT, + /// select buffer from sqe.buf_group + IOSQE_BUFFER_SELECT = 1U << IOSQE_BUFFER_SELECT_BIT, +} + +/** + * io_uring_setup() flags + */ +enum +{ + /// io_context is polled + IORING_SETUP_IOPOLL = 1U << 0, + /// SQ poll thread + IORING_SETUP_SQPOLL = 1U << 1, + /// sq_thread_cpu is valid + IORING_SETUP_SQ_AFF = 1U << 2, + /// app defines CQ size + IORING_SETUP_CQSIZE = 1U << 3, + /// clamp SQ/CQ ring sizes + IORING_SETUP_CLAMP = 1U << 4, + /// attach to existing wq + IORING_SETUP_ATTACH_WQ = 1U << 5, + /// start with ring disabled + IORING_SETUP_R_DISABLED = 1U << 6, +} + +enum +{ + IORING_OP_NOP = 0, + IORING_OP_READV = 1, + IORING_OP_WRITEV = 2, + IORING_OP_FSYNC = 3, + IORING_OP_READ_FIXED = 4, + IORING_OP_WRITE_FIXED = 5, + IORING_OP_POLL_ADD = 6, + IORING_OP_POLL_REMOVE = 7, + IORING_OP_SYNC_FILE_RANGE = 8, + IORING_OP_SENDMSG = 9, + IORING_OP_RECVMSG = 10, + IORING_OP_TIMEOUT = 11, + IORING_OP_TIMEOUT_REMOVE = 12, + IORING_OP_ACCEPT = 13, + IORING_OP_ASYNC_CANCEL = 14, + IORING_OP_LINK_TIMEOUT = 15, + IORING_OP_CONNECT = 16, + IORING_OP_FALLOCATE = 17, + IORING_OP_OPENAT = 18, + IORING_OP_CLOSE = 19, + IORING_OP_FILES_UPDATE = 20, + IORING_OP_STATX = 21, + IORING_OP_READ = 22, + IORING_OP_WRITE = 23, + IORING_OP_FADVISE = 24, + IORING_OP_MADVISE = 25, + IORING_OP_SEND = 26, + IORING_OP_RECV = 27, + IORING_OP_OPENAT2 = 28, + IORING_OP_EPOLL_CTL = 29, + IORING_OP_SPLICE = 30, + IORING_OP_PROVIDE_BUFFERS = 31, + IORING_OP_REMOVE_BUFFERS = 32, + IORING_OP_TEE = 33, + IORING_OP_SHUTDOWN = 34, + IORING_OP_RENAMEAT = 35, + IORING_OP_UNLINKAT = 36, + + IORING_OP_LAST = 37 +} + +enum +{ + IORING_FSYNC_DATASYNC = 1U << 0, +} + +enum +{ + IORING_TIMEOUT_ABS = 1U << 0, + IORING_TIMEOUT_UPDATE = 1U << 1, +} + +enum SPLICE_F_FD_IN_FIXED = 1U << 31; + +/** + * IO completion data structure (Completion Queue Entry) + */ +struct io_uring_cqe +{ + /// submission passed back + ulong user_data; + /// result code for this event + int res; + + uint flags; +} + +/** + * If set, the upper 16 bits are the buffer ID + */ +enum IORING_CQE_F_BUFFER = 1U << 0; + +enum +{ + IORING_CQE_BUFFER_SHIFT = 16, +} + +/** + * Magic offsets for the application to mmap the data it needs + */ +enum +{ + IORING_OFF_SQ_RING = 0UL, + IORING_OFF_CQ_RING = 0x8000000UL, + IORING_OFF_SQES = 0x10000000UL, +} + +/** + * Filled with the offset for mmap(2) + */ +struct io_sqring_offsets +{ + uint head; + uint tail; + uint ring_mask; + uint ring_entries; + uint flags; + uint dropped; + uint array; + uint resv1; + ulong resv2; +} + +enum +{ + /// needs io_uring_enter wakeup + IORING_SQ_NEED_WAKEUP = 1U << 0, + /// CQ ring is overflown + IORING_SQ_CQ_OVERFLOW = 1U << 1, +} + +struct io_cqring_offsets +{ + uint head; + uint tail; + uint ring_mask; + uint ring_entries; + uint overflow; + uint cqes; + uint flags; + uint resv1; + ulong resv2; +} + +enum +{ + /// disable eventfd notifications + IORING_CQ_EVENTFD_DISABLED = 1U << 0, +} + +/** + * io_uring_enter(2) flags + */ +enum +{ + IORING_ENTER_GETEVENTS = 1U << 0, + IORING_ENTER_SQ_WAKEUP = 1U << 1, + IORING_ENTER_SQ_WAIT = 1U << 2, + IORING_ENTER_EXT_ARG = 1U << 3, +} + +/** + * Passed in for io_uring_setup(2) + */ +struct io_uring_params +{ + uint sq_entries; + uint cq_entries; + uint flags; + uint sq_thread_cpu; + uint sq_thread_idle; + uint features; + uint wq_fd; + uint[3] resv; + io_sqring_offsets sq_off; + io_cqring_offsets cq_off; +} + +enum +{ + IORING_FEAT_SINGLE_MMAP = 1U << 0, + IORING_FEAT_NODROP = 1U << 1, + IORING_FEAT_SUBMIT_STABLE = 1U << 2, + IORING_FEAT_RW_CUR_POS = 1U << 3, + IORING_FEAT_CUR_PERSONALITY = 1U << 4, + IORING_FEAT_FAST_POLL = 1U << 5, + IORING_FEAT_POLL_32BITS = 1U << 6, + IORING_FEAT_SQPOLL_NONFIXED = 1U << 7, + IORING_FEAT_EXT_ARG = 1U << 8, +} + +/** + * io_uring_register(2) opcodes and arguments + */ +enum +{ + IORING_REGISTER_BUFFERS = 0, + IORING_UNREGISTER_BUFFERS = 1, + IORING_REGISTER_FILES = 2, + IORING_UNREGISTER_FILES = 3, + IORING_REGISTER_EVENTFD = 4, + IORING_UNREGISTER_EVENTFD = 5, + IORING_REGISTER_FILES_UPDATE = 6, + IORING_REGISTER_EVENTFD_ASYNC = 7, + IORING_REGISTER_PROBE = 8, + IORING_REGISTER_PERSONALITY = 9, + IORING_UNREGISTER_PERSONALITY = 10, + IORING_REGISTER_RESTRICTIONS = 11, + IORING_REGISTER_ENABLE_RINGS = 12, + + IORING_REGISTER_LAST = 13 +} + +struct io_uring_files_update +{ + uint offset; + uint resv; + ulong fds; +} + +enum IO_URING_OP_SUPPORTED = 1U << 0; + +struct io_uring_probe_op +{ + ubyte op; + ubyte resv; + + /// IO_URING_OP_* flags + ushort flags; + uint resv2; +} + +struct io_uring_probe +{ + /// last opcode supported + ubyte last_op; + + /// length of ops[] array below + ubyte ops_len; + + ushort resv; + uint[3] resv2; + io_uring_probe_op[0] ops; +} + +struct io_uring_restriction +{ + ushort opcode; + + union + { + ubyte register_op; + ubyte sqe_op; + ubyte sqe_flags; + } + + ubyte resv; + uint[3] resv2; +} + +enum +{ + /// Allow an io_uring_register(2) opcode + IORING_RESTRICTION_REGISTER_OP = 0, + + /// Allow an sqe opcode + IORING_RESTRICTION_SQE_OP = 1, + + /// Allow sqe flags + IORING_RESTRICTION_SQE_FLAGS_ALLOWED = 2, + + /// Require sqe flags (these flags must be set on each submission) + IORING_RESTRICTION_SQE_FLAGS_REQUIRED = 3, + + IORING_RESTRICTION_LAST = 4 +} + +struct io_uring_getevents_arg +{ + ulong sigmask; + uint sigmask_sz; + uint pad; + ulong ts; +} diff --git a/libphobos/libdruntime/core/sys/linux/perf_event.d b/libphobos/libdruntime/core/sys/linux/perf_event.d new file mode 100644 index 0000000..805b47e --- /dev/null +++ b/libphobos/libdruntime/core/sys/linux/perf_event.d @@ -0,0 +1,2515 @@ +/** + * D header file for perf_event_open system call. + * + * Converted from linux userspace header, comments included. + * + * Authors: Max Haughton + */ +module core.sys.linux.perf_event; +version (linux) : extern (C): +@nogc: +nothrow: +@system: + +import core.sys.posix.sys.ioctl; +import core.sys.posix.unistd; + +version (HPPA) version = HPPA_Any; +version (HPPA64) version = HPPA_Any; +version (PPC) version = PPC_Any; +version (PPC64) version = PPC_Any; +version (RISCV32) version = RISCV_Any; +version (RISCV64) version = RISCV_Any; +version (S390) version = IBMZ_Any; +version (SPARC) version = SPARC_Any; +version (SPARC64) version = SPARC_Any; +version (SystemZ) version = IBMZ_Any; + +version (X86_64) +{ + version (D_X32) + enum __NR_perf_event_open = 0x40000000 + 298; + else + enum __NR_perf_event_open = 298; +} +else version (X86) +{ + enum __NR_perf_event_open = 336; +} +else version (ARM) +{ + enum __NR_perf_event_open = 364; +} +else version (AArch64) +{ + enum __NR_perf_event_open = 241; +} +else version (HPPA_Any) +{ + enum __NR_perf_event_open = 318; +} +else version (IBMZ_Any) +{ + enum __NR_perf_event_open = 331; +} +else version (MIPS32) +{ + enum __NR_perf_event_open = 4333; +} +else version (MIPS64) +{ + version (MIPS_N32) + enum __NR_perf_event_open = 6296; + else version (MIPS_N64) + enum __NR_perf_event_open = 5292; + else + static assert(0, "Architecture not supported"); +} +else version (PPC_Any) +{ + enum __NR_perf_event_open = 319; +} +else version (RISCV_Any) +{ + enum __NR_perf_event_open = 241; +} +else version (SPARC_Any) +{ + enum __NR_perf_event_open = 327; +} +else +{ + static assert(0, "Architecture not supported"); +} +extern (C) extern long syscall(long __sysno, ...); +static long perf_event_open(perf_event_attr* hw_event, pid_t pid, int cpu, int group_fd, ulong flags) +{ + return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags); +} +/* + * User-space ABI bits: + */ + +/** + * attr.type + */ +enum perf_type_id +{ + PERF_TYPE_HARDWARE = 0, + PERF_TYPE_SOFTWARE = 1, + PERF_TYPE_TRACEPOINT = 2, + PERF_TYPE_HW_CACHE = 3, + PERF_TYPE_RAW = 4, + PERF_TYPE_BREAKPOINT = 5, + + PERF_TYPE_MAX = 6 /* non-ABI */ +} +/** + * Generalized performance event event_id types, used by the + * attr.event_id parameter of the sys_perf_event_open() + * syscall: + */ +enum perf_hw_id +{ + /// + PERF_COUNT_HW_CPU_CYCLES = 0, + /// + PERF_COUNT_HW_INSTRUCTIONS = 1, + /// + PERF_COUNT_HW_CACHE_REFERENCES = 2, + /// + PERF_COUNT_HW_CACHE_MISSES = 3, + /// + PERF_COUNT_HW_BRANCH_INSTRUCTIONS = 4, + /// + PERF_COUNT_HW_BRANCH_MISSES = 5, + /// + PERF_COUNT_HW_BUS_CYCLES = 6, + /// + PERF_COUNT_HW_STALLED_CYCLES_FRONTEND = 7, + /// + PERF_COUNT_HW_STALLED_CYCLES_BACKEND = 8, + /// + PERF_COUNT_HW_REF_CPU_CYCLES = 9, + /// + PERF_COUNT_HW_MAX = 10 /* non-ABI */ +} + +/** + * Generalized hardware cache events: + * + * { L1-D, L1-I, LLC, ITLB, DTLB, BPU, NODE } x + * { read, write, prefetch } x + * { accesses, misses } + */ +enum perf_hw_cache_id +{ + /// + PERF_COUNT_HW_CACHE_L1D = 0, + /// + PERF_COUNT_HW_CACHE_L1I = 1, + /// + PERF_COUNT_HW_CACHE_LL = 2, + /// + PERF_COUNT_HW_CACHE_DTLB = 3, + /// + PERF_COUNT_HW_CACHE_ITLB = 4, + /// + PERF_COUNT_HW_CACHE_BPU = 5, + /// + PERF_COUNT_HW_CACHE_NODE = 6, + /// + PERF_COUNT_HW_CACHE_MAX = 7 /* non-ABI */ +} +/// +enum perf_hw_cache_op_id +{ + /// + PERF_COUNT_HW_CACHE_OP_READ = 0, + /// + PERF_COUNT_HW_CACHE_OP_WRITE = 1, + /// + PERF_COUNT_HW_CACHE_OP_PREFETCH = 2, + /// + PERF_COUNT_HW_CACHE_OP_MAX = 3 /* non-ABI */ +} +/// +enum perf_hw_cache_op_result_id +{ + /// + PERF_COUNT_HW_CACHE_RESULT_ACCESS = 0, + /// + PERF_COUNT_HW_CACHE_RESULT_MISS = 1, + /// + PERF_COUNT_HW_CACHE_RESULT_MAX = 2 /* non-ABI */ +} + +/** + * Special "software" events provided by the kernel, even if the hardware + * does not support performance events. These events measure various + * physical and sw events of the kernel (and allow the profiling of them as + * well): + */ +enum perf_sw_ids +{ + /// + PERF_COUNT_SW_CPU_CLOCK = 0, + /// + PERF_COUNT_SW_TASK_CLOCK = 1, + /// + PERF_COUNT_SW_PAGE_FAULTS = 2, + /// + PERF_COUNT_SW_CONTEXT_SWITCHES = 3, + /// + PERF_COUNT_SW_CPU_MIGRATIONS = 4, + /// + PERF_COUNT_SW_PAGE_FAULTS_MIN = 5, + /// + PERF_COUNT_SW_PAGE_FAULTS_MAJ = 6, + /// + PERF_COUNT_SW_ALIGNMENT_FAULTS = 7, + /// + PERF_COUNT_SW_EMULATION_FAULTS = 8, + /// + PERF_COUNT_SW_DUMMY = 9, + /// + PERF_COUNT_SW_BPF_OUTPUT = 10, + /// + PERF_COUNT_SW_MAX = 11 /* non-ABI */ +} + +/** + * Bits that can be set in attr.sample_type to request information + * in the overflow packets. + */ +enum perf_event_sample_format +{ + /// + PERF_SAMPLE_IP = 1U << 0, + /// + PERF_SAMPLE_TID = 1U << 1, + /// + PERF_SAMPLE_TIME = 1U << 2, + /// + PERF_SAMPLE_ADDR = 1U << 3, + /// + PERF_SAMPLE_READ = 1U << 4, + /// + PERF_SAMPLE_CALLCHAIN = 1U << 5, + /// + PERF_SAMPLE_ID = 1U << 6, + /// + PERF_SAMPLE_CPU = 1U << 7, + /// + PERF_SAMPLE_PERIOD = 1U << 8, + /// + PERF_SAMPLE_STREAM_ID = 1U << 9, + /// + PERF_SAMPLE_RAW = 1U << 10, + /// + PERF_SAMPLE_BRANCH_STACK = 1U << 11, + /// + PERF_SAMPLE_REGS_USER = 1U << 12, + /// + PERF_SAMPLE_STACK_USER = 1U << 13, + /// + PERF_SAMPLE_WEIGHT = 1U << 14, + /// + PERF_SAMPLE_DATA_SRC = 1U << 15, + /// + PERF_SAMPLE_IDENTIFIER = 1U << 16, + /// + PERF_SAMPLE_TRANSACTION = 1U << 17, + /// + PERF_SAMPLE_REGS_INTR = 1U << 18, + /// + PERF_SAMPLE_PHYS_ADDR = 1U << 19, + /// + PERF_SAMPLE_MAX = 1U << 20 /* non-ABI */ +} + +/** + * values to program into branch_sample_type when PERF_SAMPLE_BRANCH is set + * + * If the user does not pass priv level information via branch_sample_type, + * the kernel uses the event's priv level. Branch and event priv levels do + * not have to match. Branch priv level is checked for permissions. + * + * The branch types can be combined, however BRANCH_ANY covers all types + * of branches and therefore it supersedes all the other types. + */ +enum perf_branch_sample_type_shift +{ + PERF_SAMPLE_BRANCH_USER_SHIFT = 0, /** user branches */ + PERF_SAMPLE_BRANCH_KERNEL_SHIFT = 1, /** kernel branches */ + PERF_SAMPLE_BRANCH_HV_SHIFT = 2, /** hypervisor branches */ + + PERF_SAMPLE_BRANCH_ANY_SHIFT = 3, /** any branch types */ + PERF_SAMPLE_BRANCH_ANY_CALL_SHIFT = 4, /** any call branch */ + PERF_SAMPLE_BRANCH_ANY_RETURN_SHIFT = 5, /** any return branch */ + PERF_SAMPLE_BRANCH_IND_CALL_SHIFT = 6, /** indirect calls */ + PERF_SAMPLE_BRANCH_ABORT_TX_SHIFT = 7, /** transaction aborts */ + PERF_SAMPLE_BRANCH_IN_TX_SHIFT = 8, /** in transaction */ + PERF_SAMPLE_BRANCH_NO_TX_SHIFT = 9, /** not in transaction */ + PERF_SAMPLE_BRANCH_COND_SHIFT = 10, /** conditional branches */ + + PERF_SAMPLE_BRANCH_CALL_STACK_SHIFT = 11, /** call/ret stack */ + PERF_SAMPLE_BRANCH_IND_JUMP_SHIFT = 12, /** indirect jumps */ + PERF_SAMPLE_BRANCH_CALL_SHIFT = 13, /** direct call */ + + PERF_SAMPLE_BRANCH_NO_FLAGS_SHIFT = 14, /** no flags */ + PERF_SAMPLE_BRANCH_NO_CYCLES_SHIFT = 15, /** no cycles */ + + PERF_SAMPLE_BRANCH_TYPE_SAVE_SHIFT = 16, /** save branch type */ + + PERF_SAMPLE_BRANCH_MAX_SHIFT = 17 /** non-ABI */ +} +/// +enum perf_branch_sample_type +{ + PERF_SAMPLE_BRANCH_USER = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_USER_SHIFT, + PERF_SAMPLE_BRANCH_KERNEL = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_KERNEL_SHIFT, + PERF_SAMPLE_BRANCH_HV = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_HV_SHIFT, + PERF_SAMPLE_BRANCH_ANY = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_ANY_SHIFT, + PERF_SAMPLE_BRANCH_ANY_CALL = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_ANY_CALL_SHIFT, + PERF_SAMPLE_BRANCH_ANY_RETURN = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_ANY_RETURN_SHIFT, + PERF_SAMPLE_BRANCH_IND_CALL = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_IND_CALL_SHIFT, + PERF_SAMPLE_BRANCH_ABORT_TX = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_ABORT_TX_SHIFT, + PERF_SAMPLE_BRANCH_IN_TX = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_IN_TX_SHIFT, + PERF_SAMPLE_BRANCH_NO_TX = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_NO_TX_SHIFT, + PERF_SAMPLE_BRANCH_COND = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_COND_SHIFT, + PERF_SAMPLE_BRANCH_CALL_STACK = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_CALL_STACK_SHIFT, + PERF_SAMPLE_BRANCH_IND_JUMP = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_IND_JUMP_SHIFT, + PERF_SAMPLE_BRANCH_CALL = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_CALL_SHIFT, + PERF_SAMPLE_BRANCH_NO_FLAGS = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_NO_FLAGS_SHIFT, + PERF_SAMPLE_BRANCH_NO_CYCLES = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_NO_CYCLES_SHIFT, + PERF_SAMPLE_BRANCH_TYPE_SAVE = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_TYPE_SAVE_SHIFT, + PERF_SAMPLE_BRANCH_MAX = 1U << perf_branch_sample_type_shift.PERF_SAMPLE_BRANCH_MAX_SHIFT +} + +/** + * Common flow change classification + */ +enum +{ + PERF_BR_UNKNOWN = 0, /** unknown */ + PERF_BR_COND = 1, /** conditional */ + PERF_BR_UNCOND = 2, /** unconditional */ + PERF_BR_IND = 3, /** indirect */ + PERF_BR_CALL = 4, /** function call */ + PERF_BR_IND_CALL = 5, /** indirect function call */ + PERF_BR_RET = 6, /** function return */ + PERF_BR_SYSCALL = 7, /** syscall */ + PERF_BR_SYSRET = 8, /** syscall return */ + PERF_BR_COND_CALL = 9, /** conditional function call */ + PERF_BR_COND_RET = 10, /** conditional function return */ + PERF_BR_MAX = 11 +} + +/// +enum PERF_SAMPLE_BRANCH_PLM_ALL = perf_branch_sample_type.PERF_SAMPLE_BRANCH_USER + | perf_branch_sample_type.PERF_SAMPLE_BRANCH_KERNEL + | perf_branch_sample_type.PERF_SAMPLE_BRANCH_HV; + +/** + * Values to determine ABI of the registers dump. + */ +enum perf_sample_regs_abi +{ + /// + PERF_SAMPLE_REGS_ABI_NONE = 0, + /// + PERF_SAMPLE_REGS_ABI_32 = 1, + /// + PERF_SAMPLE_REGS_ABI_64 = 2 +} + +/** + * Values for the memory transaction event qualifier, mostly for + * abort events. Multiple bits can be set. + */ +enum +{ + PERF_TXN_ELISION = 1 << 0, /** From elision */ + PERF_TXN_TRANSACTION = 1 << 1, /** From transaction */ + PERF_TXN_SYNC = 1 << 2, /** Instruction is related */ + PERF_TXN_ASYNC = 1 << 3, /** Instruction not related */ + PERF_TXN_RETRY = 1 << 4, /** Retry possible */ + PERF_TXN_CONFLICT = 1 << 5, /** Conflict abort */ + PERF_TXN_CAPACITY_WRITE = 1 << 6, /** Capacity write abort */ + PERF_TXN_CAPACITY_READ = 1 << 7, /** Capacity read abort */ + + PERF_TXN_MAX = 1 << 8, /** non-ABI */ + + /** bits 32..63 are reserved for the abort code */ + + ///PERF_TXN_ABORT_MASK = 0xffffffff << 32, + PERF_TXN_ABORT_SHIFT = 32 +} + +/** + * The format of the data returned by read() on a perf event fd, + * as specified by attr.read_format: + * --- + * struct read_format { + * { u64 value; + * { u64 time_enabled; } && PERF_FORMAT_TOTAL_TIME_ENABLED + * { u64 time_running; } && PERF_FORMAT_TOTAL_TIME_RUNNING + * { u64 id; } && PERF_FORMAT_ID + * } && !PERF_FORMAT_GROUP + * + * { u64 nr; + * { u64 time_enabled; } && PERF_FORMAT_TOTAL_TIME_ENABLED + * { u64 time_running; } && PERF_FORMAT_TOTAL_TIME_RUNNING + * { u64 value; + * { u64 id; } && PERF_FORMAT_ID + * } cntr[nr]; + * } && PERF_FORMAT_GROUP + * }; + * --- + */ +enum perf_event_read_format +{ + /// + PERF_FORMAT_TOTAL_TIME_ENABLED = 1U << 0, + /// + PERF_FORMAT_TOTAL_TIME_RUNNING = 1U << 1, + /// + PERF_FORMAT_ID = 1U << 2, + /// + PERF_FORMAT_GROUP = 1U << 3, + PERF_FORMAT_MAX = 1U << 4 /** non-ABI */ +} + +enum PERF_ATTR_SIZE_VER0 = 64; /** sizeof first published struct */ +enum PERF_ATTR_SIZE_VER1 = 72; /** add: config2 */ +enum PERF_ATTR_SIZE_VER2 = 80; /** add: branch_sample_type */ +enum PERF_ATTR_SIZE_VER3 = 96; /** add: sample_regs_user */ +/* add: sample_stack_user */ +enum PERF_ATTR_SIZE_VER4 = 104; /** add: sample_regs_intr */ +enum PERF_ATTR_SIZE_VER5 = 112; /** add: aux_watermark */ + +/** + * Hardware event_id to monitor via a performance monitoring event: + * + * @sample_max_stack: Max number of frame pointers in a callchain, + * should be < /proc/sys/kernel/perf_event_max_stack + */ +struct perf_event_attr +{ + /** + *Major type: hardware/software/tracepoint/etc. + */ + uint type; + + /** + * Size of the attr structure, for fwd/bwd compat. + */ + uint size; + + /** + * Type specific configuration information. + */ + ulong config; + /// + union + { + /// + ulong sample_period; + /// + ulong sample_freq; + } + /// + ulong sample_type; + /// + ulong read_format; + + // mixin(bitfields!( + // ulong, "disabled", 1, + // ulong, "inherit", 1, + // ulong, "pinned", 1, + // ulong, "exclusive", 1, + // ulong, "exclude_user", 1, + // ulong, "exclude_kernel", 1, + // ulong, "exclude_hv", 1, + // ulong, "exclude_idle", 1, + // ulong, "mmap", 1, + // ulong, "comm", 1, + // ulong, "freq", 1, + // ulong, "inherit_stat", 1, + // ulong, "enable_on_exec", 1, + // ulong, "task", 1, + // ulong, "watermark", 1, + // ulong, "precise_ip", 2, + // ulong, "mmap_data", 1, + // ulong, "sample_id_all", 1, + // ulong, "exclude_host", 1, + // ulong, "exclude_guest", 1, + // ulong, "exclude_callchain_kernel", 1, + // ulong, "exclude_callchain_user", 1, + // ulong, "mmap2", 1, + // ulong, "comm_exec", 1, + // ulong, "use_clockid", 1, + // ulong, "context_switch", 1, + // ulong, "write_backward", 1, + // ulong, "namespaces", 1, + // ulong, "__reserved_1", 35)); + private ulong perf_event_attr_bitmanip; + /// + @property ulong disabled() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 1U) >> 0U; + return cast(ulong) result; + } + /// + @property void disabled(ulong v) @safe pure nothrow @nogc + { + assert(v >= disabled_min, + "Value is smaller than the minimum value of bitfield 'disabled'"); + assert(v <= disabled_max, + "Value is greater than the maximum value of bitfield 'disabled'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 1U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 0U) & 1U)); + } + + enum ulong disabled_min = cast(ulong) 0U; + enum ulong disabled_max = cast(ulong) 1U; + /// + @property ulong inherit() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 2U) >> 1U; + return cast(ulong) result; + } + /// + @property void inherit(ulong v) @safe pure nothrow @nogc + { + assert(v >= inherit_min, + "Value is smaller than the minimum value of bitfield 'inherit'"); + assert(v <= inherit_max, + "Value is greater than the maximum value of bitfield 'inherit'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 2U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 1U) & 2U)); + } + + enum ulong inherit_min = cast(ulong) 0U; + enum ulong inherit_max = cast(ulong) 1U; + /// + @property ulong pinned() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 4U) >> 2U; + return cast(ulong) result; + } + /// + @property void pinned(ulong v) @safe pure nothrow @nogc + { + assert(v >= pinned_min, + "Value is smaller than the minimum value of bitfield 'pinned'"); + assert(v <= pinned_max, + "Value is greater than the maximum value of bitfield 'pinned'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 4U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 2U) & 4U)); + } + + enum ulong pinned_min = cast(ulong) 0U; + enum ulong pinned_max = cast(ulong) 1U; + /// + @property ulong exclusive() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 8U) >> 3U; + return cast(ulong) result; + } + /// + @property void exclusive(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclusive_min, + "Value is smaller than the minimum value of bitfield 'exclusive'"); + assert(v <= exclusive_max, + "Value is greater than the maximum value of bitfield 'exclusive'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 8U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 3U) & 8U)); + } + + enum ulong exclusive_min = cast(ulong) 0U; + enum ulong exclusive_max = cast(ulong) 1U; + /// + @property ulong exclude_user() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 16U) >> 4U; + return cast(ulong) result; + } + /// + @property void exclude_user(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_user_min, + "Value is smaller than the minimum value of bitfield 'exclude_user'"); + assert(v <= exclude_user_max, + "Value is greater than the maximum value of bitfield 'exclude_user'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 16U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 4U) & 16U)); + } + + enum ulong exclude_user_min = cast(ulong) 0U; + enum ulong exclude_user_max = cast(ulong) 1U; + /// + @property ulong exclude_kernel() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 32U) >> 5U; + return cast(ulong) result; + } + /// + @property void exclude_kernel(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_kernel_min, + "Value is smaller than the minimum value of bitfield 'exclude_kernel'"); + assert(v <= exclude_kernel_max, + "Value is greater than the maximum value of bitfield 'exclude_kernel'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 32U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 5U) & 32U)); + } + + enum ulong exclude_kernel_min = cast(ulong) 0U; + enum ulong exclude_kernel_max = cast(ulong) 1U; + /// + @property ulong exclude_hv() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 64U) >> 6U; + return cast(ulong) result; + } + /// + @property void exclude_hv(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_hv_min, + "Value is smaller than the minimum value of bitfield 'exclude_hv'"); + assert(v <= exclude_hv_max, + "Value is greater than the maximum value of bitfield 'exclude_hv'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 64U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 6U) & 64U)); + } + + enum ulong exclude_hv_min = cast(ulong) 0U; + enum ulong exclude_hv_max = cast(ulong) 1U; + /// + @property ulong exclude_idle() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 128U) >> 7U; + return cast(ulong) result; + } + /// + @property void exclude_idle(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_idle_min, + "Value is smaller than the minimum value of bitfield 'exclude_idle'"); + assert(v <= exclude_idle_max, + "Value is greater than the maximum value of bitfield 'exclude_idle'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 128U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 7U) & 128U)); + } + + enum ulong exclude_idle_min = cast(ulong) 0U; + enum ulong exclude_idle_max = cast(ulong) 1U; + /// + @property ulong mmap() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 256U) >> 8U; + return cast(ulong) result; + } + /// + @property void mmap(ulong v) @safe pure nothrow @nogc + { + assert(v >= mmap_min, "Value is smaller than the minimum value of bitfield 'mmap'"); + assert(v <= mmap_max, "Value is greater than the maximum value of bitfield 'mmap'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 256U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 8U) & 256U)); + } + + enum ulong mmap_min = cast(ulong) 0U; + enum ulong mmap_max = cast(ulong) 1U; + /// + @property ulong comm() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 512U) >> 9U; + return cast(ulong) result; + } + /// + @property void comm(ulong v) @safe pure nothrow @nogc + { + assert(v >= comm_min, "Value is smaller than the minimum value of bitfield 'comm'"); + assert(v <= comm_max, "Value is greater than the maximum value of bitfield 'comm'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 512U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 9U) & 512U)); + } + + enum ulong comm_min = cast(ulong) 0U; + enum ulong comm_max = cast(ulong) 1U; + /// + @property ulong freq() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 1024U) >> 10U; + return cast(ulong) result; + } + /// + @property void freq(ulong v) @safe pure nothrow @nogc + { + assert(v >= freq_min, "Value is smaller than the minimum value of bitfield 'freq'"); + assert(v <= freq_max, "Value is greater than the maximum value of bitfield 'freq'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 1024U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 10U) & 1024U)); + } + + enum ulong freq_min = cast(ulong) 0U; + enum ulong freq_max = cast(ulong) 1U; + /// + @property ulong inherit_stat() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 2048U) >> 11U; + return cast(ulong) result; + } + /// + @property void inherit_stat(ulong v) @safe pure nothrow @nogc + { + assert(v >= inherit_stat_min, + "Value is smaller than the minimum value of bitfield 'inherit_stat'"); + assert(v <= inherit_stat_max, + "Value is greater than the maximum value of bitfield 'inherit_stat'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 2048U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 11U) & 2048U)); + } + + enum ulong inherit_stat_min = cast(ulong) 0U; + enum ulong inherit_stat_max = cast(ulong) 1U; + /// + @property ulong enable_on_exec() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 4096U) >> 12U; + return cast(ulong) result; + } + /// + @property void enable_on_exec(ulong v) @safe pure nothrow @nogc + { + assert(v >= enable_on_exec_min, + "Value is smaller than the minimum value of bitfield 'enable_on_exec'"); + assert(v <= enable_on_exec_max, + "Value is greater than the maximum value of bitfield 'enable_on_exec'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 4096U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 12U) & 4096U)); + } + + enum ulong enable_on_exec_min = cast(ulong) 0U; + enum ulong enable_on_exec_max = cast(ulong) 1U; + /// + @property ulong task() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 8192U) >> 13U; + return cast(ulong) result; + } + /// + @property void task(ulong v) @safe pure nothrow @nogc + { + assert(v >= task_min, "Value is smaller than the minimum value of bitfield 'task'"); + assert(v <= task_max, "Value is greater than the maximum value of bitfield 'task'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 8192U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 13U) & 8192U)); + } + + enum ulong task_min = cast(ulong) 0U; + enum ulong task_max = cast(ulong) 1U; + /// + @property ulong watermark() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 16384U) >> 14U; + return cast(ulong) result; + } + /// + @property void watermark(ulong v) @safe pure nothrow @nogc + { + assert(v >= watermark_min, + "Value is smaller than the minimum value of bitfield 'watermark'"); + assert(v <= watermark_max, + "Value is greater than the maximum value of bitfield 'watermark'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 16384U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 14U) & 16384U)); + } + + enum ulong watermark_min = cast(ulong) 0U; + enum ulong watermark_max = cast(ulong) 1U; + /// + @property ulong precise_ip() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 98304U) >> 15U; + return cast(ulong) result; + } + /// + @property void precise_ip(ulong v) @safe pure nothrow @nogc + { + assert(v >= precise_ip_min, + "Value is smaller than the minimum value of bitfield 'precise_ip'"); + assert(v <= precise_ip_max, + "Value is greater than the maximum value of bitfield 'precise_ip'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 98304U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 15U) & 98304U)); + } + + enum ulong precise_ip_min = cast(ulong) 0U; + enum ulong precise_ip_max = cast(ulong) 3U; + /// + @property ulong mmap_data() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 131072U) >> 17U; + return cast(ulong) result; + } + /// + @property void mmap_data(ulong v) @safe pure nothrow @nogc + { + assert(v >= mmap_data_min, + "Value is smaller than the minimum value of bitfield 'mmap_data'"); + assert(v <= mmap_data_max, + "Value is greater than the maximum value of bitfield 'mmap_data'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 131072U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 17U) & 131072U)); + } + + enum ulong mmap_data_min = cast(ulong) 0U; + enum ulong mmap_data_max = cast(ulong) 1U; + /// + @property ulong sample_id_all() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 262144U) >> 18U; + return cast(ulong) result; + } + /// + @property void sample_id_all(ulong v) @safe pure nothrow @nogc + { + assert(v >= sample_id_all_min, + "Value is smaller than the minimum value of bitfield 'sample_id_all'"); + assert(v <= sample_id_all_max, + "Value is greater than the maximum value of bitfield 'sample_id_all'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 262144U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 18U) & 262144U)); + } + + enum ulong sample_id_all_min = cast(ulong) 0U; + enum ulong sample_id_all_max = cast(ulong) 1U; + /// + @property ulong exclude_host() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 524288U) >> 19U; + return cast(ulong) result; + } + /// + @property void exclude_host(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_host_min, + "Value is smaller than the minimum value of bitfield 'exclude_host'"); + assert(v <= exclude_host_max, + "Value is greater than the maximum value of bitfield 'exclude_host'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 524288U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 19U) & 524288U)); + } + + enum ulong exclude_host_min = cast(ulong) 0U; + enum ulong exclude_host_max = cast(ulong) 1U; + /// + @property ulong exclude_guest() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 1048576U) >> 20U; + return cast(ulong) result; + } + /// + @property void exclude_guest(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_guest_min, + "Value is smaller than the minimum value of bitfield 'exclude_guest'"); + assert(v <= exclude_guest_max, + "Value is greater than the maximum value of bitfield 'exclude_guest'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 1048576U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 20U) & 1048576U)); + } + + enum ulong exclude_guest_min = cast(ulong) 0U; + enum ulong exclude_guest_max = cast(ulong) 1U; + /// + @property ulong exclude_callchain_kernel() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 2097152U) >> 21U; + return cast(ulong) result; + } + /// + @property void exclude_callchain_kernel(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_callchain_kernel_min, + "Value is smaller than the minimum value of bitfield 'exclude_callchain_kernel'"); + assert(v <= exclude_callchain_kernel_max, + "Value is greater than the maximum value of bitfield 'exclude_callchain_kernel'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 2097152U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 21U) & 2097152U)); + } + + enum ulong exclude_callchain_kernel_min = cast(ulong) 0U; + enum ulong exclude_callchain_kernel_max = cast(ulong) 1U; + /// + @property ulong exclude_callchain_user() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 4194304U) >> 22U; + return cast(ulong) result; + } + /// + @property void exclude_callchain_user(ulong v) @safe pure nothrow @nogc + { + assert(v >= exclude_callchain_user_min, + "Value is smaller than the minimum value of bitfield 'exclude_callchain_user'"); + assert(v <= exclude_callchain_user_max, + "Value is greater than the maximum value of bitfield 'exclude_callchain_user'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 4194304U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 22U) & 4194304U)); + } + + enum ulong exclude_callchain_user_min = cast(ulong) 0U; + enum ulong exclude_callchain_user_max = cast(ulong) 1U; + /// + @property ulong mmap2() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 8388608U) >> 23U; + return cast(ulong) result; + } + /// + @property void mmap2(ulong v) @safe pure nothrow @nogc + { + assert(v >= mmap2_min, + "Value is smaller than the minimum value of bitfield 'mmap2'"); + assert(v <= mmap2_max, + "Value is greater than the maximum value of bitfield 'mmap2'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 8388608U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 23U) & 8388608U)); + } + + enum ulong mmap2_min = cast(ulong) 0U; + enum ulong mmap2_max = cast(ulong) 1U; + /// + @property ulong comm_exec() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 16777216U) >> 24U; + return cast(ulong) result; + } + /// + @property void comm_exec(ulong v) @safe pure nothrow @nogc + { + assert(v >= comm_exec_min, + "Value is smaller than the minimum value of bitfield 'comm_exec'"); + assert(v <= comm_exec_max, + "Value is greater than the maximum value of bitfield 'comm_exec'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 16777216U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 24U) & 16777216U)); + } + + enum ulong comm_exec_min = cast(ulong) 0U; + enum ulong comm_exec_max = cast(ulong) 1U; + /// + @property ulong use_clockid() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 33554432U) >> 25U; + return cast(ulong) result; + } + /// + @property void use_clockid(ulong v) @safe pure nothrow @nogc + { + assert(v >= use_clockid_min, + "Value is smaller than the minimum value of bitfield 'use_clockid'"); + assert(v <= use_clockid_max, + "Value is greater than the maximum value of bitfield 'use_clockid'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 33554432U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 25U) & 33554432U)); + } + + enum ulong use_clockid_min = cast(ulong) 0U; + enum ulong use_clockid_max = cast(ulong) 1U; + /// + @property ulong context_switch() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 67108864U) >> 26U; + return cast(ulong) result; + } + /// + @property void context_switch(ulong v) @safe pure nothrow @nogc + { + assert(v >= context_switch_min, + "Value is smaller than the minimum value of bitfield 'context_switch'"); + assert(v <= context_switch_max, + "Value is greater than the maximum value of bitfield 'context_switch'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 67108864U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 26U) & 67108864U)); + } + + enum ulong context_switch_min = cast(ulong) 0U; + enum ulong context_switch_max = cast(ulong) 1U; + /// + @property ulong write_backward() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 134217728U) >> 27U; + return cast(ulong) result; + } + /// + @property void write_backward(ulong v) @safe pure nothrow @nogc + { + assert(v >= write_backward_min, + "Value is smaller than the minimum value of bitfield 'write_backward'"); + assert(v <= write_backward_max, + "Value is greater than the maximum value of bitfield 'write_backward'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 134217728U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 27U) & 134217728U)); + } + + enum ulong write_backward_min = cast(ulong) 0U; + enum ulong write_backward_max = cast(ulong) 1U; + /// + @property ulong namespaces() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 268435456U) >> 28U; + return cast(ulong) result; + } + /// + @property void namespaces(ulong v) @safe pure nothrow @nogc + { + assert(v >= namespaces_min, + "Value is smaller than the minimum value of bitfield 'namespaces'"); + assert(v <= namespaces_max, + "Value is greater than the maximum value of bitfield 'namespaces'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast(typeof(perf_event_attr_bitmanip)) 268435456U)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 28U) & 268435456U)); + } + + enum ulong namespaces_min = cast(ulong) 0U; + enum ulong namespaces_max = cast(ulong) 1U; + /// + @property ulong __reserved_1() @safe pure nothrow @nogc const + { + auto result = (perf_event_attr_bitmanip & 18446744073172680704UL) >> 29U; + return cast(ulong) result; + } + /// + @property void __reserved_1(ulong v) @safe pure nothrow @nogc + { + assert(v >= __reserved_1_min, + "Value is smaller than the minimum value of bitfield '__reserved_1'"); + assert(v <= __reserved_1_max, + "Value is greater than the maximum value of bitfield '__reserved_1'"); + perf_event_attr_bitmanip = cast(typeof(perf_event_attr_bitmanip))( + (perf_event_attr_bitmanip & (-1 - cast( + typeof(perf_event_attr_bitmanip)) 18446744073172680704UL)) | ( + (cast(typeof(perf_event_attr_bitmanip)) v << 29U) & 18446744073172680704UL)); + } + + enum ulong __reserved_1_min = cast(ulong) 0U; + enum ulong __reserved_1_max = cast(ulong) 34359738367UL; + /// + union + { + uint wakeup_events; /** wakeup every n events */ + uint wakeup_watermark; /** bytes before wakeup */ + } + /// + uint bp_type; + + union + { + /// + ulong bp_addr; + ulong config1; /** extension of config */ + } + + union + { + /// + ulong bp_len; + ulong config2; /** extension of config1 */ + } + + ulong branch_sample_type; /** enum perf_branch_sample_type */ + + /** + * Defines set of user regs to dump on samples. + * See asm/perf_regs.h for details. + */ + ulong sample_regs_user; + + /** + * Defines size of the user stack to dump on samples. + */ + uint sample_stack_user; + /// + int clockid; + + /** + * Defines set of regs to dump for each sample + * state captured on: + * - precise = 0: PMU interrupt + * - precise > 0: sampled instruction + * + * See asm/perf_regs.h for details. + */ + ulong sample_regs_intr; + + /** + * Wakeup watermark for AUX area + */ + uint aux_watermark; + /// + ushort sample_max_stack; + /** align to __u64 */ + ushort __reserved_2; +} +/// +extern (D) auto perf_flags(T)(auto ref T attr) +{ + return *(&attr.read_format + 1); +} + +/** + * Ioctls that can be done on a perf event fd: + */ +enum PERF_EVENT_IOC_ENABLE = _IO('$', 0); +/// +enum PERF_EVENT_IOC_DISABLE = _IO('$', 1); +/// +enum PERF_EVENT_IOC_REFRESH = _IO('$', 2); +/// +enum PERF_EVENT_IOC_RESET = _IO('$', 3); +/// +enum PERF_EVENT_IOC_PERIOD = _IOW!ulong('$', 4); +/// +enum PERF_EVENT_IOC_SET_OUTPUT = _IO('$', 5); +/// +enum PERF_EVENT_IOC_SET_FILTER = _IOW!(char*)('$', 6); +/// +enum PERF_EVENT_IOC_ID = _IOR!(ulong*)('$', 7); +/// +enum PERF_EVENT_IOC_SET_BPF = _IOW!uint('$', 8); +/// +enum PERF_EVENT_IOC_PAUSE_OUTPUT = _IOW!uint('$', 9); + +/// +enum perf_event_ioc_flags +{ + PERF_IOC_FLAG_GROUP = 1U << 0 +} + +/** + * Structure of the page that can be mapped via mmap + */ +struct perf_event_mmap_page +{ + uint version_; /** version number of this structure */ + uint compat_version; /** lowest version this is compat with */ + + /** + * Bits needed to read the hw events in user-space. + * --- + * u32 seq, time_mult, time_shift, index, width; + * u64 count, enabled, running; + * u64 cyc, time_offset; + * s64 pmc = 0; + * + * do { + * seq = pc->lock; + * barrier() + * + * enabled = pc->time_enabled; + * running = pc->time_running; + * + * if (pc->cap_usr_time && enabled != running) { + * cyc = rdtsc(); + * time_offset = pc->time_offset; + * time_mult = pc->time_mult; + * time_shift = pc->time_shift; + * } + * + * index = pc->index; + * count = pc->offset; + * if (pc->cap_user_rdpmc && index) { + * width = pc->pmc_width; + * pmc = rdpmc(index - 1); + * } + * + * barrier(); + * } while (pc->lock != seq); + * --- + * NOTE: for obvious reason this only works on self-monitoring + * processes. + */ + uint lock; /** seqlock for synchronization */ + uint index; /** hardware event identifier */ + long offset; /** add to hardware event value */ + ulong time_enabled; /** time event active */ + ulong time_running; /** time event on cpu */ + /// + union + { + /// + ulong capabilities; + + struct + { + /* mixin(bitfields!(ulong, "cap_bit0", 1, ulong, "cap_bit0_is_deprecated", 1, ulong, + "cap_user_rdpmc", 1, ulong, "cap_user_time", 1, ulong, + "cap_user_time_zero", 1, ulong, "cap_____res", 59)); */ + + private ulong mmap_page_bitmanip; + /// + @property ulong cap_bit0() @safe pure nothrow @nogc const + { + auto result = (mmap_page_bitmanip & 1U) >> 0U; + return cast(ulong) result; + } + /// + @property void cap_bit0(ulong v) @safe pure nothrow @nogc + { + assert(v >= cap_bit0_min, + "Value is smaller than the minimum value of bitfield 'cap_bit0'"); + assert(v <= cap_bit0_max, + "Value is greater than the maximum value of bitfield 'cap_bit0'"); + mmap_page_bitmanip = cast(typeof(mmap_page_bitmanip))( + (mmap_page_bitmanip & (-1 - cast(typeof(mmap_page_bitmanip)) 1U)) | ( + (cast(typeof(mmap_page_bitmanip)) v << 0U) & 1U)); + } + + enum ulong cap_bit0_min = cast(ulong) 0U; + enum ulong cap_bit0_max = cast(ulong) 1U; + /// + @property ulong cap_bit0_is_deprecated() @safe pure nothrow @nogc const + { + auto result = (mmap_page_bitmanip & 2U) >> 1U; + return cast(ulong) result; + } + /// + @property void cap_bit0_is_deprecated(ulong v) @safe pure nothrow @nogc + { + assert(v >= cap_bit0_is_deprecated_min, + "Value is smaller than the minimum value of bitfield 'cap_bit0_is_deprecated'"); + assert(v <= cap_bit0_is_deprecated_max, + "Value is greater than the maximum value of bitfield 'cap_bit0_is_deprecated'"); + mmap_page_bitmanip = cast(typeof(mmap_page_bitmanip))( + (mmap_page_bitmanip & (-1 - cast(typeof(mmap_page_bitmanip)) 2U)) | ( + (cast(typeof(mmap_page_bitmanip)) v << 1U) & 2U)); + } + + enum ulong cap_bit0_is_deprecated_min = cast(ulong) 0U; + enum ulong cap_bit0_is_deprecated_max = cast(ulong) 1U; + /// + @property ulong cap_user_rdpmc() @safe pure nothrow @nogc const + { + auto result = (mmap_page_bitmanip & 4U) >> 2U; + return cast(ulong) result; + } + /// + @property void cap_user_rdpmc(ulong v) @safe pure nothrow @nogc + { + assert(v >= cap_user_rdpmc_min, + "Value is smaller than the minimum value of bitfield 'cap_user_rdpmc'"); + assert(v <= cap_user_rdpmc_max, + "Value is greater than the maximum value of bitfield 'cap_user_rdpmc'"); + mmap_page_bitmanip = cast(typeof(mmap_page_bitmanip))( + (mmap_page_bitmanip & (-1 - cast(typeof(mmap_page_bitmanip)) 4U)) | ( + (cast(typeof(mmap_page_bitmanip)) v << 2U) & 4U)); + } + + enum ulong cap_user_rdpmc_min = cast(ulong) 0U; + enum ulong cap_user_rdpmc_max = cast(ulong) 1U; + /// + @property ulong cap_user_time() @safe pure nothrow @nogc const + { + auto result = (mmap_page_bitmanip & 8U) >> 3U; + return cast(ulong) result; + } + /// + @property void cap_user_time(ulong v) @safe pure nothrow @nogc + { + assert(v >= cap_user_time_min, + "Value is smaller than the minimum value of bitfield 'cap_user_time'"); + assert(v <= cap_user_time_max, + "Value is greater than the maximum value of bitfield 'cap_user_time'"); + mmap_page_bitmanip = cast(typeof(mmap_page_bitmanip))( + (mmap_page_bitmanip & (-1 - cast(typeof(mmap_page_bitmanip)) 8U)) | ( + (cast(typeof(mmap_page_bitmanip)) v << 3U) & 8U)); + } + + enum ulong cap_user_time_min = cast(ulong) 0U; + enum ulong cap_user_time_max = cast(ulong) 1U; + /// + @property ulong cap_user_time_zero() @safe pure nothrow @nogc const + { + auto result = (mmap_page_bitmanip & 16U) >> 4U; + return cast(ulong) result; + } + /// + @property void cap_user_time_zero(ulong v) @safe pure nothrow @nogc + { + assert(v >= cap_user_time_zero_min, + "Value is smaller than the minimum value of bitfield 'cap_user_time_zero'"); + assert(v <= cap_user_time_zero_max, + "Value is greater than the maximum value of bitfield 'cap_user_time_zero'"); + mmap_page_bitmanip = cast(typeof(mmap_page_bitmanip))( + (mmap_page_bitmanip & (-1 - cast(typeof(mmap_page_bitmanip)) 16U)) | ( + (cast(typeof(mmap_page_bitmanip)) v << 4U) & 16U)); + } + + enum ulong cap_user_time_zero_min = cast(ulong) 0U; + enum ulong cap_user_time_zero_max = cast(ulong) 1U; + /// + @property ulong cap_____res() @safe pure nothrow @nogc const + { + auto result = (mmap_page_bitmanip & 18446744073709551584UL) >> 5U; + return cast(ulong) result; + } + /// + @property void cap_____res(ulong v) @safe pure nothrow @nogc + { + assert(v >= cap_____res_min, + "Value is smaller than the minimum value of bitfield 'cap_____res'"); + assert(v <= cap_____res_max, + "Value is greater than the maximum value of bitfield 'cap_____res'"); + mmap_page_bitmanip = cast(typeof(mmap_page_bitmanip))((mmap_page_bitmanip & ( + -1 - cast(typeof(mmap_page_bitmanip)) 18446744073709551584UL)) | ( + (cast(typeof(mmap_page_bitmanip)) v << 5U) & 18446744073709551584UL)); + } + + enum ulong cap_____res_min = cast(ulong) 0U; + enum ulong cap_____res_max = cast(ulong) 576460752303423487UL; + } + } + + /** + * If cap_user_rdpmc this field provides the bit-width of the value + * read using the rdpmc() or equivalent instruction. This can be used + * to sign extend the result like: + * + * pmc <<= 64 - width; + * pmc >>= 64 - width; // signed shift right + * count += pmc; + */ + ushort pmc_width; + + /** + * If cap_usr_time the below fields can be used to compute the time + * delta since time_enabled (in ns) using rdtsc or similar. + * + * u64 quot, rem; + * u64 delta; + * + * quot = (cyc >> time_shift); + * rem = cyc & (((u64)1 << time_shift) - 1); + * delta = time_offset + quot * time_mult + + * ((rem * time_mult) >> time_shift); + * + * Where time_offset,time_mult,time_shift and cyc are read in the + * seqcount loop described above. This delta can then be added to + * enabled and possible running (if index), improving the scaling: + * + * enabled += delta; + * if (index) + * running += delta; + * + * quot = count / running; + * rem = count % running; + * count = quot * enabled + (rem * enabled) / running; + */ + ushort time_shift; + /// + uint time_mult; + /// + ulong time_offset; + /** + * If cap_usr_time_zero, the hardware clock (e.g. TSC) can be calculated + * from sample timestamps. + * + * time = timestamp - time_zero; + * quot = time / time_mult; + * rem = time % time_mult; + * cyc = (quot << time_shift) + (rem << time_shift) / time_mult; + * + * And vice versa: + * + * quot = cyc >> time_shift; + * rem = cyc & (((u64)1 << time_shift) - 1); + * timestamp = time_zero + quot * time_mult + + * ((rem * time_mult) >> time_shift); + */ + ulong time_zero; + uint size; /** Header size up to __reserved[] fields. */ + + /** + * Hole for extension of the self monitor capabilities + */ + + ubyte[948] __reserved; /** align to 1k. */ + + /** + * Control data for the mmap() data buffer. + * + * User-space reading the @data_head value should issue an smp_rmb(), + * after reading this value. + * + * When the mapping is PROT_WRITE the @data_tail value should be + * written by userspace to reflect the last read data, after issueing + * an smp_mb() to separate the data read from the ->data_tail store. + * In this case the kernel will not over-write unread data. + * + * See perf_output_put_handle() for the data ordering. + * + * data_{offset,size} indicate the location and size of the perf record + * buffer within the mmapped area. + */ + ulong data_head; /** head in the data section */ + ulong data_tail; /** user-space written tail */ + ulong data_offset; /** where the buffer starts */ + ulong data_size; /** data buffer size */ + + /** + * AUX area is defined by aux_{offset,size} fields that should be set + * by the userspace, so that + * --- + * aux_offset >= data_offset + data_size + * --- + * prior to mmap()ing it. Size of the mmap()ed area should be aux_size. + * + * Ring buffer pointers aux_{head,tail} have the same semantics as + * data_{head,tail} and same ordering rules apply. + */ + ulong aux_head; + /// + ulong aux_tail; + /// + ulong aux_offset; + /// + ulong aux_size; +} +/// +enum PERF_RECORD_MISC_CPUMODE_MASK = 7 << 0; +/// +enum PERF_RECORD_MISC_CPUMODE_UNKNOWN = 0 << 0; +/// +enum PERF_RECORD_MISC_KERNEL = 1 << 0; +/// +enum PERF_RECORD_MISC_USER = 2 << 0; +/// +enum PERF_RECORD_MISC_HYPERVISOR = 3 << 0; +/// +enum PERF_RECORD_MISC_GUEST_KERNEL = 4 << 0; +/// +enum PERF_RECORD_MISC_GUEST_USER = 5 << 0; + +/** + * Indicates that /proc/PID/maps parsing are truncated by time out. + */ +enum PERF_RECORD_MISC_PROC_MAP_PARSE_TIMEOUT = 1 << 12; +/** + * PERF_RECORD_MISC_MMAP_DATA and PERF_RECORD_MISC_COMM_EXEC are used on + * different events so can reuse the same bit position. + * Ditto PERF_RECORD_MISC_SWITCH_OUT. + */ +enum PERF_RECORD_MISC_MMAP_DATA = 1 << 13; +/// +enum PERF_RECORD_MISC_COMM_EXEC = 1 << 13; +/// +enum PERF_RECORD_MISC_SWITCH_OUT = 1 << 13; +/** + * Indicates that the content of PERF_SAMPLE_IP points to + * the actual instruction that triggered the event. See also + * perf_event_attr::precise_ip. + */ +enum PERF_RECORD_MISC_EXACT_IP = 1 << 14; +/** + * Reserve the last bit to indicate some extended misc field + */ +enum PERF_RECORD_MISC_EXT_RESERVED = 1 << 15; +/// +struct perf_event_header +{ + /// + uint type; + /// + ushort misc; + /// + ushort size; +} +/// +struct perf_ns_link_info +{ + /// + ulong dev; + /// + ulong ino; +} + +enum +{ + /// + NET_NS_INDEX = 0, + /// + UTS_NS_INDEX = 1, + /// + IPC_NS_INDEX = 2, + /// + PID_NS_INDEX = 3, + /// + USER_NS_INDEX = 4, + /// + MNT_NS_INDEX = 5, + /// + CGROUP_NS_INDEX = 6, + NR_NAMESPACES = 7 /** number of available namespaces */ +} +/// +enum perf_event_type +{ + /** + * If perf_event_attr.sample_id_all is set then all event types will + * have the sample_type selected fields related to where/when + * (identity) an event took place (TID, TIME, ID, STREAM_ID, CPU, + * IDENTIFIER) described in PERF_RECORD_SAMPLE below, it will be stashed + * just after the perf_event_header and the fields already present for + * the existing fields, i.e. at the end of the payload. That way a newer + * perf.data file will be supported by older perf tools, with these new + * optional fields being ignored. + * --- + * struct sample_id { + * { u32 pid, tid; } && PERF_SAMPLE_TID + * { u64 time; } && PERF_SAMPLE_TIME + * { u64 id; } && PERF_SAMPLE_ID + * { u64 stream_id;} && PERF_SAMPLE_STREAM_ID + * { u32 cpu, res; } && PERF_SAMPLE_CPU + * { u64 id; } && PERF_SAMPLE_IDENTIFIER + * } && perf_event_attr::sample_id_all + * --- + * Note that PERF_SAMPLE_IDENTIFIER duplicates PERF_SAMPLE_ID. The + * advantage of PERF_SAMPLE_IDENTIFIER is that its position is fixed + * relative to header.size. + */ + + /* + * The MMAP events record the PROT_EXEC mappings so that we can + * correlate userspace IPs to code. They have the following structure: + * --- + * struct { + * struct perf_event_header header; + * + * u32 pid, tid; + * u64 addr; + * u64 len; + * u64 pgoff; + * char filename[]; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_MMAP = 1, + + /** + * --- + * struct { + * struct perf_event_header header; + * u64 id; + * u64 lost; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_LOST = 2, + + /** + * --- + * struct { + * struct perf_event_header header; + * + * u32 pid, tid; + * char comm[]; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_COMM = 3, + + /** + * --- + * struct { + * struct perf_event_header header; + * u32 pid, ppid; + * u32 tid, ptid; + * u64 time; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_EXIT = 4, + + /** + * --- + * struct { + * struct perf_event_header header; + * u64 time; + * u64 id; + * u64 stream_id; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_THROTTLE = 5, + PERF_RECORD_UNTHROTTLE = 6, + /** + * --- + * struct { + * struct perf_event_header header; + * u32 pid, ppid; + * u32 tid, ptid; + * u64 time; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_FORK = 7, + /** + * --- + * struct { + * struct perf_event_header header; + * u32 pid, tid; + * + * struct read_format values; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_READ = 8, + /** + * --- + * struct { + * struct perf_event_header header; + * + * # + * # Note that PERF_SAMPLE_IDENTIFIER duplicates PERF_SAMPLE_ID. + * # The advantage of PERF_SAMPLE_IDENTIFIER is that its position + * # is fixed relative to header. + * # + * + * { u64 id; } && PERF_SAMPLE_IDENTIFIER + * { u64 ip; } && PERF_SAMPLE_IP + * { u32 pid, tid; } && PERF_SAMPLE_TID + * { u64 time; } && PERF_SAMPLE_TIME + * { u64 addr; } && PERF_SAMPLE_ADDR + * { u64 id; } && PERF_SAMPLE_ID + * { u64 stream_id;} && PERF_SAMPLE_STREAM_ID + * { u32 cpu, res; } && PERF_SAMPLE_CPU + * { u64 period; } && PERF_SAMPLE_PERIOD + * + * { struct read_format values; } && PERF_SAMPLE_READ + * + * { u64 nr, + * u64 ips[nr]; } && PERF_SAMPLE_CALLCHAIN + * + * # + * # The RAW record below is opaque data wrt the ABI + * # + * # That is, the ABI doesn't make any promises wrt to + * # the stability of its content, it may vary depending + * # on event, hardware, kernel version and phase of + * # the moon. + * # + * # In other words, PERF_SAMPLE_RAW contents are not an ABI. + * # + * + * { u32 size; + * char data[size];}&& PERF_SAMPLE_RAW + * + * { u64 nr; + * { u64 from, to, flags } lbr[nr];} && PERF_SAMPLE_BRANCH_STACK + * + * { u64 abi; # enum perf_sample_regs_abi + * u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_USER + * + * { u64 size; + * char data[size]; + * u64 dyn_size; } && PERF_SAMPLE_STACK_USER + * + * { u64 weight; } && PERF_SAMPLE_WEIGHT + * { u64 data_src; } && PERF_SAMPLE_DATA_SRC + * { u64 transaction; } && PERF_SAMPLE_TRANSACTION + * { u64 abi; # enum perf_sample_regs_abi + * u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_INTR + * { u64 phys_addr;} && PERF_SAMPLE_PHYS_ADDR + * }; + * --- + */ + PERF_RECORD_SAMPLE = 9, + + /** + * --- + * The MMAP2 records are an augmented version of MMAP, they add + * maj, min, ino numbers to be used to uniquely identify each mapping + * + * struct { + * struct perf_event_header header; + * + * u32 pid, tid; + * u64 addr; + * u64 len; + * u64 pgoff; + * u32 maj; + * u32 min; + * u64 ino; + * u64 ino_generation; + * u32 prot, flags; + * char filename[]; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_MMAP2 = 10, + + /** + * Records that new data landed in the AUX buffer part. + * --- + * struct { + * struct perf_event_header header; + * + * u64 aux_offset; + * u64 aux_size; + * u64 flags; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_AUX = 11, + + /** + * --- + * Indicates that instruction trace has started + * + * struct { + * struct perf_event_header header; + * u32 pid; + * u32 tid; + * }; + * --- + */ + PERF_RECORD_ITRACE_START = 12, + + /** + * Records the dropped/lost sample number. + * --- + * struct { + * struct perf_event_header header; + * + * u64 lost; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_LOST_SAMPLES = 13, + + /** + * + * Records a context switch in or out (flagged by + * PERF_RECORD_MISC_SWITCH_OUT). See also + * PERF_RECORD_SWITCH_CPU_WIDE. + * --- + * struct { + * struct perf_event_header header; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_SWITCH = 14, + + /** + * CPU-wide version of PERF_RECORD_SWITCH with next_prev_pid and + * next_prev_tid that are the next (switching out) or previous + * (switching in) pid/tid. + * --- + * struct { + * struct perf_event_header header; + * u32 next_prev_pid; + * u32 next_prev_tid; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_SWITCH_CPU_WIDE = 15, + + /** + * --- + * struct { + * struct perf_event_header header; + * u32 pid; + * u32 tid; + * u64 nr_namespaces; + * { u64 dev, inode; } [nr_namespaces]; + * struct sample_id sample_id; + * }; + * --- + */ + PERF_RECORD_NAMESPACES = 16, + + PERF_RECORD_MAX = 17 /* non-ABI */ +} +/// +enum PERF_MAX_STACK_DEPTH = 127; +/// +enum PERF_MAX_CONTEXTS_PER_STACK = 8; +/// +enum perf_callchain_context +{ + /// + PERF_CONTEXT_HV = cast(ulong)-32, + /// + PERF_CONTEXT_KERNEL = cast(ulong)-128, + /// + PERF_CONTEXT_USER = cast(ulong)-512, + /// + PERF_CONTEXT_GUEST = cast(ulong)-2048, + /// + PERF_CONTEXT_GUEST_KERNEL = cast(ulong)-2176, + /// + PERF_CONTEXT_GUEST_USER = cast(ulong)-2560, + /// + PERF_CONTEXT_MAX = cast(ulong)-4095 +} + +/** + * PERF_RECORD_AUX::flags bits + */ +enum PERF_AUX_FLAG_TRUNCATED = 0x01; /** record was truncated to fit */ +enum PERF_AUX_FLAG_OVERWRITE = 0x02; /** snapshot from overwrite mode */ +enum PERF_AUX_FLAG_PARTIAL = 0x04; /** record contains gaps */ +enum PERF_AUX_FLAG_COLLISION = 0x08; /** sample collided with another */ +/// +enum PERF_FLAG_FD_NO_GROUP = 1UL << 0; +/// +enum PERF_FLAG_FD_OUTPUT = 1UL << 1; +enum PERF_FLAG_PID_CGROUP = 1UL << 2; /** pid=cgroup id, per-cpu mode only */ +enum PERF_FLAG_FD_CLOEXEC = 1UL << 3; /** O_CLOEXEC */ +///perm_mem_data_src is endian specific. +version (LittleEndian) +{ + /// + union perf_mem_data_src + { + /// + ulong val; + + struct + { + /* mixin(bitfields!(ulong, "mem_op", 5, ulong, "mem_lvl", 14, ulong, + "mem_snoop", 5, ulong, "mem_lock", 2, ulong, "mem_dtlb", 7, ulong, + "mem_lvl_num", 4, ulong, "mem_remote", 1, ulong, + "mem_snoopx", 2, ulong, "mem_rsvd", 24)); */ + + private ulong perf_mem_data_src_bitmanip; + /// + @property ulong mem_op() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 31U) >> 0U; + return cast(ulong) result; + } + /// + @property void mem_op(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_op_min, + "Value is smaller than the minimum value of bitfield 'mem_op'"); + assert(v <= mem_op_max, + "Value is greater than the maximum value of bitfield 'mem_op'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 31U)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 0U) & 31U)); + } + + enum ulong mem_op_min = cast(ulong) 0U; + enum ulong mem_op_max = cast(ulong) 31U; + /// + @property ulong mem_lvl() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 524256U) >> 5U; + return cast(ulong) result; + } + /// + @property void mem_lvl(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_lvl_min, + "Value is smaller than the minimum value of bitfield 'mem_lvl'"); + assert(v <= mem_lvl_max, + "Value is greater than the maximum value of bitfield 'mem_lvl'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 524256U)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 5U) & 524256U)); + } + + enum ulong mem_lvl_min = cast(ulong) 0U; + enum ulong mem_lvl_max = cast(ulong) 16383U; + /// + @property ulong mem_snoop() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 16252928U) >> 19U; + return cast(ulong) result; + } + /// + @property void mem_snoop(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_snoop_min, + "Value is smaller than the minimum value of bitfield 'mem_snoop'"); + assert(v <= mem_snoop_max, + "Value is greater than the maximum value of bitfield 'mem_snoop'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 16252928U)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 19U) & 16252928U)); + } + + enum ulong mem_snoop_min = cast(ulong) 0U; + enum ulong mem_snoop_max = cast(ulong) 31U; + /// + @property ulong mem_lock() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 50331648U) >> 24U; + return cast(ulong) result; + } + /// + @property void mem_lock(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_lock_min, + "Value is smaller than the minimum value of bitfield 'mem_lock'"); + assert(v <= mem_lock_max, + "Value is greater than the maximum value of bitfield 'mem_lock'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 50331648U)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 24U) & 50331648U)); + } + + enum ulong mem_lock_min = cast(ulong) 0U; + enum ulong mem_lock_max = cast(ulong) 3U; + /// + @property ulong mem_dtlb() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 8522825728UL) >> 26U; + return cast(ulong) result; + } + /// + @property void mem_dtlb(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_dtlb_min, + "Value is smaller than the minimum value of bitfield 'mem_dtlb'"); + assert(v <= mem_dtlb_max, + "Value is greater than the maximum value of bitfield 'mem_dtlb'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 8522825728UL)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 26U) & 8522825728UL)); + } + + enum ulong mem_dtlb_min = cast(ulong) 0U; + enum ulong mem_dtlb_max = cast(ulong) 127U; + /// + @property ulong mem_lvl_num() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 128849018880UL) >> 33U; + return cast(ulong) result; + } + /// + @property void mem_lvl_num(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_lvl_num_min, + "Value is smaller than the minimum value of bitfield 'mem_lvl_num'"); + assert(v <= mem_lvl_num_max, + "Value is greater than the maximum value of bitfield 'mem_lvl_num'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 128849018880UL)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 33U) & 128849018880UL)); + } + + enum ulong mem_lvl_num_min = cast(ulong) 0U; + enum ulong mem_lvl_num_max = cast(ulong) 15U; + /// + @property ulong mem_remote() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 137438953472UL) >> 37U; + return cast(ulong) result; + } + /// + @property void mem_remote(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_remote_min, + "Value is smaller than the minimum value of bitfield 'mem_remote'"); + assert(v <= mem_remote_max, + "Value is greater than the maximum value of bitfield 'mem_remote'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 137438953472UL)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 37U) & 137438953472UL)); + } + + enum ulong mem_remote_min = cast(ulong) 0U; + enum ulong mem_remote_max = cast(ulong) 1U; + /// + @property ulong mem_snoopx() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 824633720832UL) >> 38U; + return cast(ulong) result; + } + /// + @property void mem_snoopx(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_snoopx_min, + "Value is smaller than the minimum value of bitfield 'mem_snoopx'"); + assert(v <= mem_snoopx_max, + "Value is greater than the maximum value of bitfield 'mem_snoopx'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))((perf_mem_data_src_bitmanip & ( + -1 - cast(typeof(perf_mem_data_src_bitmanip)) 824633720832UL)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 38U) & 824633720832UL)); + } + + enum ulong mem_snoopx_min = cast(ulong) 0U; + enum ulong mem_snoopx_max = cast(ulong) 3U; + /// + @property ulong mem_rsvd() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src_bitmanip & 18446742974197923840UL) >> 40U; + return cast(ulong) result; + } + /// + @property void mem_rsvd(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_rsvd_min, + "Value is smaller than the minimum value of bitfield 'mem_rsvd'"); + assert(v <= mem_rsvd_max, + "Value is greater than the maximum value of bitfield 'mem_rsvd'"); + perf_mem_data_src_bitmanip = cast( + typeof(perf_mem_data_src_bitmanip))( + (perf_mem_data_src_bitmanip & (-1 - cast( + typeof(perf_mem_data_src_bitmanip)) 18446742974197923840UL)) | ( + (cast(typeof(perf_mem_data_src_bitmanip)) v << 40U) & 18446742974197923840UL)); + } + + enum ulong mem_rsvd_min = cast(ulong) 0U; + enum ulong mem_rsvd_max = cast(ulong) 16777215U; + + } + } +} +else +{ + /// + union perf_mem_data_src + { + /// + ulong val; + + struct + { + import std.bitmanip : bitfields; + + /* mixin(bitfields!(ulong, "mem_rsvd", 24, ulong, "mem_snoopx", 2, ulong, + "mem_remote", 1, ulong, "mem_lvl_num", 4, ulong, "mem_dtlb", 7, ulong, + "mem_lock", 2, ulong, "mem_snoop", 5, ulong, "mem_lvl", + 14, ulong, "mem_op", 5)); */ + private ulong perf_mem_data_src; + /// + @property ulong mem_rsvd() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 16777215U) >> 0U; + return cast(ulong) result; + } + /// + @property void mem_rsvd(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_rsvd_min, + "Value is smaller than the minimum value of bitfield 'mem_rsvd'"); + assert(v <= mem_rsvd_max, + "Value is greater than the maximum value of bitfield 'mem_rsvd'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))( + (perf_mem_data_src & (-1 - cast(typeof(perf_mem_data_src)) 16777215U)) | ( + (cast(typeof(perf_mem_data_src)) v << 0U) & 16777215U)); + } + + enum ulong mem_rsvd_min = cast(ulong) 0U; + enum ulong mem_rsvd_max = cast(ulong) 16777215U; + /// + @property ulong mem_snoopx() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 50331648U) >> 24U; + return cast(ulong) result; + } + /// + @property void mem_snoopx(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_snoopx_min, + "Value is smaller than the minimum value of bitfield 'mem_snoopx'"); + assert(v <= mem_snoopx_max, + "Value is greater than the maximum value of bitfield 'mem_snoopx'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))( + (perf_mem_data_src & (-1 - cast(typeof(perf_mem_data_src)) 50331648U)) | ( + (cast(typeof(perf_mem_data_src)) v << 24U) & 50331648U)); + } + + enum ulong mem_snoopx_min = cast(ulong) 0U; + enum ulong mem_snoopx_max = cast(ulong) 3U; + /// + @property ulong mem_remote() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 67108864U) >> 26U; + return cast(ulong) result; + } + /// + @property void mem_remote(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_remote_min, + "Value is smaller than the minimum value of bitfield 'mem_remote'"); + assert(v <= mem_remote_max, + "Value is greater than the maximum value of bitfield 'mem_remote'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))( + (perf_mem_data_src & (-1 - cast(typeof(perf_mem_data_src)) 67108864U)) | ( + (cast(typeof(perf_mem_data_src)) v << 26U) & 67108864U)); + } + + enum ulong mem_remote_min = cast(ulong) 0U; + enum ulong mem_remote_max = cast(ulong) 1U; + /// + @property ulong mem_lvl_num() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 2013265920U) >> 27U; + return cast(ulong) result; + } + /// + @property void mem_lvl_num(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_lvl_num_min, + "Value is smaller than the minimum value of bitfield 'mem_lvl_num'"); + assert(v <= mem_lvl_num_max, + "Value is greater than the maximum value of bitfield 'mem_lvl_num'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))( + (perf_mem_data_src & (-1 - cast(typeof(perf_mem_data_src)) 2013265920U)) | ( + (cast(typeof(perf_mem_data_src)) v << 27U) & 2013265920U)); + } + + enum ulong mem_lvl_num_min = cast(ulong) 0U; + enum ulong mem_lvl_num_max = cast(ulong) 15U; + /// + @property ulong mem_dtlb() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 272730423296UL) >> 31U; + return cast(ulong) result; + } + /// + @property void mem_dtlb(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_dtlb_min, + "Value is smaller than the minimum value of bitfield 'mem_dtlb'"); + assert(v <= mem_dtlb_max, + "Value is greater than the maximum value of bitfield 'mem_dtlb'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))( + (perf_mem_data_src & (-1 - cast(typeof(perf_mem_data_src)) 272730423296UL)) | ( + (cast(typeof(perf_mem_data_src)) v << 31U) & 272730423296UL)); + } + + enum ulong mem_dtlb_min = cast(ulong) 0U; + enum ulong mem_dtlb_max = cast(ulong) 127U; + /// + @property ulong mem_lock() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 824633720832UL) >> 38U; + return cast(ulong) result; + } + /// + @property void mem_lock(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_lock_min, + "Value is smaller than the minimum value of bitfield 'mem_lock'"); + assert(v <= mem_lock_max, + "Value is greater than the maximum value of bitfield 'mem_lock'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))( + (perf_mem_data_src & (-1 - cast(typeof(perf_mem_data_src)) 824633720832UL)) | ( + (cast(typeof(perf_mem_data_src)) v << 38U) & 824633720832UL)); + } + + enum ulong mem_lock_min = cast(ulong) 0U; + enum ulong mem_lock_max = cast(ulong) 3U; + /// + @property ulong mem_snoop() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 34084860461056UL) >> 40U; + return cast(ulong) result; + } + /// + @property void mem_snoop(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_snoop_min, + "Value is smaller than the minimum value of bitfield 'mem_snoop'"); + assert(v <= mem_snoop_max, + "Value is greater than the maximum value of bitfield 'mem_snoop'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))( + (perf_mem_data_src & (-1 - cast(typeof(perf_mem_data_src)) 34084860461056UL)) | ( + (cast(typeof(perf_mem_data_src)) v << 40U) & 34084860461056UL)); + } + + enum ulong mem_snoop_min = cast(ulong) 0U; + enum ulong mem_snoop_max = cast(ulong) 31U; + /// + @property ulong mem_lvl() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 576425567931334656UL) >> 45U; + return cast(ulong) result; + } + /// + @property void mem_lvl(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_lvl_min, + "Value is smaller than the minimum value of bitfield 'mem_lvl'"); + assert(v <= mem_lvl_max, + "Value is greater than the maximum value of bitfield 'mem_lvl'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))((perf_mem_data_src & ( + -1 - cast(typeof(perf_mem_data_src)) 576425567931334656UL)) | ( + (cast(typeof(perf_mem_data_src)) v << 45U) & 576425567931334656UL)); + } + + enum ulong mem_lvl_min = cast(ulong) 0U; + enum ulong mem_lvl_max = cast(ulong) 16383U; + /// + @property ulong mem_op() @safe pure nothrow @nogc const + { + auto result = (perf_mem_data_src & 17870283321406128128UL) >> 59U; + return cast(ulong) result; + } + /// + @property void mem_op(ulong v) @safe pure nothrow @nogc + { + assert(v >= mem_op_min, + "Value is smaller than the minimum value of bitfield 'mem_op'"); + assert(v <= mem_op_max, + "Value is greater than the maximum value of bitfield 'mem_op'"); + perf_mem_data_src = cast(typeof(perf_mem_data_src))((perf_mem_data_src & ( + -1 - cast(typeof(perf_mem_data_src)) 17870283321406128128UL)) | ( + (cast(typeof(perf_mem_data_src)) v << 59U) & 17870283321406128128UL)); + } + + enum ulong mem_op_min = cast(ulong) 0U; + enum ulong mem_op_max = cast(ulong) 31U; + } + } +} + +/* snoop mode, ext */ +/* remote */ +/* memory hierarchy level number */ +/* tlb access */ +/* lock instr */ +/* snoop mode */ +/* memory hierarchy level */ +/* type of opcode */ + +/** type of opcode (load/store/prefetch,code) */ +enum PERF_MEM_OP_NA = 0x01; /** not available */ +enum PERF_MEM_OP_LOAD = 0x02; /** load instruction */ +enum PERF_MEM_OP_STORE = 0x04; /** store instruction */ +enum PERF_MEM_OP_PFETCH = 0x08; /** prefetch */ +enum PERF_MEM_OP_EXEC = 0x10; /** code (execution) */ +enum PERF_MEM_OP_SHIFT = 0; + +/* memory hierarchy (memory level, hit or miss) */ +enum PERF_MEM_LVL_NA = 0x01; /** not available */ +enum PERF_MEM_LVL_HIT = 0x02; /** hit level */ +enum PERF_MEM_LVL_MISS = 0x04; /** miss level */ +enum PERF_MEM_LVL_L1 = 0x08; /** L1 */ +enum PERF_MEM_LVL_LFB = 0x10; /** Line Fill Buffer */ +enum PERF_MEM_LVL_L2 = 0x20; /** L2 */ +enum PERF_MEM_LVL_L3 = 0x40; /** L3 */ +enum PERF_MEM_LVL_LOC_RAM = 0x80; /** Local DRAM */ +enum PERF_MEM_LVL_REM_RAM1 = 0x100; /** Remote DRAM (1 hop) */ +enum PERF_MEM_LVL_REM_RAM2 = 0x200; /** Remote DRAM (2 hops) */ +enum PERF_MEM_LVL_REM_CCE1 = 0x400; /** Remote Cache (1 hop) */ +enum PERF_MEM_LVL_REM_CCE2 = 0x800; /** Remote Cache (2 hops) */ +enum PERF_MEM_LVL_IO = 0x1000; /** I/O memory */ +enum PERF_MEM_LVL_UNC = 0x2000; /** Uncached memory */ +/// +enum PERF_MEM_LVL_SHIFT = 5; + +enum PERF_MEM_REMOTE_REMOTE = 0x01; /** Remote */ +/// +enum PERF_MEM_REMOTE_SHIFT = 37; + +enum PERF_MEM_LVLNUM_L1 = 0x01; /** L1 */ +enum PERF_MEM_LVLNUM_L2 = 0x02; /** L2 */ +enum PERF_MEM_LVLNUM_L3 = 0x03; /** L3 */ +enum PERF_MEM_LVLNUM_L4 = 0x04; /** L4 */ +/* 5-0xa available */ +enum PERF_MEM_LVLNUM_ANY_CACHE = 0x0b; /** Any cache */ +enum PERF_MEM_LVLNUM_LFB = 0x0c; /** LFB */ +enum PERF_MEM_LVLNUM_RAM = 0x0d; /** RAM */ +enum PERF_MEM_LVLNUM_PMEM = 0x0e; /** PMEM */ +enum PERF_MEM_LVLNUM_NA = 0x0f; /** N/A */ +/// +enum PERF_MEM_LVLNUM_SHIFT = 33; + +/* snoop mode */ +enum PERF_MEM_SNOOP_NA = 0x01; /** not available */ +enum PERF_MEM_SNOOP_NONE = 0x02; /** no snoop */ +enum PERF_MEM_SNOOP_HIT = 0x04; /** snoop hit */ +enum PERF_MEM_SNOOP_MISS = 0x08; /** snoop miss */ +enum PERF_MEM_SNOOP_HITM = 0x10; /** snoop hit modified */ +/// +enum PERF_MEM_SNOOP_SHIFT = 19; + +enum PERF_MEM_SNOOPX_FWD = 0x01; /** forward */ +/** 1 free */ +enum PERF_MEM_SNOOPX_SHIFT = 37; + +/** locked instruction */ +enum PERF_MEM_LOCK_NA = 0x01; /** not available */ +enum PERF_MEM_LOCK_LOCKED = 0x02; /** locked transaction */ +/// +enum PERF_MEM_LOCK_SHIFT = 24; + +/* TLB access */ +enum PERF_MEM_TLB_NA = 0x01; /** not available */ +enum PERF_MEM_TLB_HIT = 0x02; /** hit level */ +enum PERF_MEM_TLB_MISS = 0x04; /** miss level */ +enum PERF_MEM_TLB_L1 = 0x08; /** L1 */ +enum PERF_MEM_TLB_L2 = 0x10; /** L2 */ +enum PERF_MEM_TLB_WK = 0x20; /** Hardware Walker*/ +enum PERF_MEM_TLB_OS = 0x40; /** OS fault handler */ +/// +enum PERF_MEM_TLB_SHIFT = 26; + +/** + * single taken branch record layout: + * + * from: source instruction (may not always be a branch insn) + * to: branch target + * mispred: branch target was mispredicted + * predicted: branch target was predicted + * + * support for mispred, predicted is optional. In case it + * is not supported mispred = predicted = 0. + * + * in_tx: running in a hardware transaction + * abort: aborting a hardware transaction + * cycles: cycles from last branch (or 0 if not supported) + * type: branch type + */ +struct perf_branch_entry +{ + /// + ulong from; + /// + ulong to; + + /* mixin(bitfields!(ulong, "mispred", 1, ulong, "predicted", 1, ulong, + "in_tx", 1, ulong, "abort", 1, ulong, "cycles", 16, ulong, "type", + 4, ulong, "reserved", 40)); */ + private ulong perf_branch_entry_bitmanip; + /// + @property ulong mispred() @safe pure nothrow @nogc const + { + auto result = (perf_branch_entry_bitmanip & 1U) >> 0U; + return cast(ulong) result; + } + /// + @property void mispred(ulong v) @safe pure nothrow @nogc + { + assert(v >= mispred_min, + "Value is smaller than the minimum value of bitfield 'mispred'"); + assert(v <= mispred_max, + "Value is greater than the maximum value of bitfield 'mispred'"); + perf_branch_entry_bitmanip = cast(typeof(perf_branch_entry_bitmanip))( + (perf_branch_entry_bitmanip & (-1 - cast(typeof(perf_branch_entry_bitmanip)) 1U)) | ( + (cast(typeof(perf_branch_entry_bitmanip)) v << 0U) & 1U)); + } + + enum ulong mispred_min = cast(ulong) 0U; + enum ulong mispred_max = cast(ulong) 1U; + /// + @property ulong predicted() @safe pure nothrow @nogc const + { + auto result = (perf_branch_entry_bitmanip & 2U) >> 1U; + return cast(ulong) result; + } + /// + @property void predicted(ulong v) @safe pure nothrow @nogc + { + assert(v >= predicted_min, + "Value is smaller than the minimum value of bitfield 'predicted'"); + assert(v <= predicted_max, + "Value is greater than the maximum value of bitfield 'predicted'"); + perf_branch_entry_bitmanip = cast(typeof(perf_branch_entry_bitmanip))( + (perf_branch_entry_bitmanip & (-1 - cast(typeof(perf_branch_entry_bitmanip)) 2U)) | ( + (cast(typeof(perf_branch_entry_bitmanip)) v << 1U) & 2U)); + } + + enum ulong predicted_min = cast(ulong) 0U; + enum ulong predicted_max = cast(ulong) 1U; + /// + @property ulong in_tx() @safe pure nothrow @nogc const + { + auto result = (perf_branch_entry_bitmanip & 4U) >> 2U; + return cast(ulong) result; + } + /// + @property void in_tx(ulong v) @safe pure nothrow @nogc + { + assert(v >= in_tx_min, + "Value is smaller than the minimum value of bitfield 'in_tx'"); + assert(v <= in_tx_max, + "Value is greater than the maximum value of bitfield 'in_tx'"); + perf_branch_entry_bitmanip = cast(typeof(perf_branch_entry_bitmanip))( + (perf_branch_entry_bitmanip & (-1 - cast(typeof(perf_branch_entry_bitmanip)) 4U)) | ( + (cast(typeof(perf_branch_entry_bitmanip)) v << 2U) & 4U)); + } + + enum ulong in_tx_min = cast(ulong) 0U; + enum ulong in_tx_max = cast(ulong) 1U; + /// + @property ulong abort() @safe pure nothrow @nogc const + { + auto result = (perf_branch_entry_bitmanip & 8U) >> 3U; + return cast(ulong) result; + } + /// + @property void abort(ulong v) @safe pure nothrow @nogc + { + assert(v >= abort_min, + "Value is smaller than the minimum value of bitfield 'abort'"); + assert(v <= abort_max, + "Value is greater than the maximum value of bitfield 'abort'"); + perf_branch_entry_bitmanip = cast(typeof(perf_branch_entry_bitmanip))( + (perf_branch_entry_bitmanip & (-1 - cast(typeof(perf_branch_entry_bitmanip)) 8U)) | ( + (cast(typeof(perf_branch_entry_bitmanip)) v << 3U) & 8U)); + } + + enum ulong abort_min = cast(ulong) 0U; + enum ulong abort_max = cast(ulong) 1U; + /// + @property ulong cycles() @safe pure nothrow @nogc const + { + auto result = (perf_branch_entry_bitmanip & 1048560U) >> 4U; + return cast(ulong) result; + } + /// + @property void cycles(ulong v) @safe pure nothrow @nogc + { + assert(v >= cycles_min, + "Value is smaller than the minimum value of bitfield 'cycles'"); + assert(v <= cycles_max, + "Value is greater than the maximum value of bitfield 'cycles'"); + perf_branch_entry_bitmanip = cast(typeof(perf_branch_entry_bitmanip))( + (perf_branch_entry_bitmanip & (-1 - cast(typeof(perf_branch_entry_bitmanip)) 1048560U)) | ( + (cast(typeof(perf_branch_entry_bitmanip)) v << 4U) & 1048560U)); + } + + enum ulong cycles_min = cast(ulong) 0U; + enum ulong cycles_max = cast(ulong) 65535U; + /// + @property ulong type() @safe pure nothrow @nogc const + { + auto result = (perf_branch_entry_bitmanip & 15728640U) >> 20U; + return cast(ulong) result; + } + /// + @property void type(ulong v) @safe pure nothrow @nogc + { + assert(v >= type_min, "Value is smaller than the minimum value of bitfield 'type'"); + assert(v <= type_max, "Value is greater than the maximum value of bitfield 'type'"); + perf_branch_entry_bitmanip = cast(typeof(perf_branch_entry_bitmanip))( + (perf_branch_entry_bitmanip & (-1 - cast(typeof(perf_branch_entry_bitmanip)) 15728640U)) | ( + (cast(typeof(perf_branch_entry_bitmanip)) v << 20U) & 15728640U)); + } + + enum ulong type_min = cast(ulong) 0U; + enum ulong type_max = cast(ulong) 15U; + /// + @property ulong reserved() @safe pure nothrow @nogc const + { + auto result = (perf_branch_entry_bitmanip & 18446744073692774400UL) >> 24U; + return cast(ulong) result; + } + /// + @property void reserved(ulong v) @safe pure nothrow @nogc + { + assert(v >= reserved_min, + "Value is smaller than the minimum value of bitfield 'reserved'"); + assert(v <= reserved_max, + "Value is greater than the maximum value of bitfield 'reserved'"); + perf_branch_entry_bitmanip = cast(typeof(perf_branch_entry_bitmanip))( + (perf_branch_entry_bitmanip & (-1 - cast( + typeof(perf_branch_entry_bitmanip)) 18446744073692774400UL)) | ( + (cast(typeof(perf_branch_entry_bitmanip)) v << 24U) & 18446744073692774400UL)); + } + + enum ulong reserved_min = cast(ulong) 0U; + enum ulong reserved_max = cast(ulong) 1099511627775UL; +} diff --git a/libphobos/libdruntime/core/sys/linux/sys/procfs.d b/libphobos/libdruntime/core/sys/linux/sys/procfs.d new file mode 100644 index 0000000..6a113e1 --- /dev/null +++ b/libphobos/libdruntime/core/sys/linux/sys/procfs.d @@ -0,0 +1,15 @@ +/** + * D header file for GNU/Linux. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Teodor Dutu + */ + +module core.sys.linux.sys.procfs; + +import core.sys.posix.sys.types : pid_t; + +version (linux) +{ + alias lwpid_t = pid_t; +} diff --git a/libphobos/libdruntime/core/sys/netbsd/sys/elf32.d b/libphobos/libdruntime/core/sys/netbsd/sys/elf32.d index b22b97f..ac623d6 100644 --- a/libphobos/libdruntime/core/sys/netbsd/sys/elf32.d +++ b/libphobos/libdruntime/core/sys/netbsd/sys/elf32.d @@ -112,7 +112,7 @@ extern (D) { auto ELF32_M_SYM(I)(I info) { return info >> 8; } auto ELF32_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubye)size; } + auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubyte)size; } } struct Elf32_Cap diff --git a/libphobos/libdruntime/core/sys/netbsd/sys/elf64.d b/libphobos/libdruntime/core/sys/netbsd/sys/elf64.d index f78d066..659ac40 100644 --- a/libphobos/libdruntime/core/sys/netbsd/sys/elf64.d +++ b/libphobos/libdruntime/core/sys/netbsd/sys/elf64.d @@ -118,7 +118,7 @@ extern (D) { auto ELF64_M_SYM(I)(I info) { return info >> 8; } auto ELF64_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF64_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubye)size; } + auto ELF64_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubyte)size; } } struct Elf64_Cap diff --git a/libphobos/libdruntime/core/sys/openbsd/execinfo.d b/libphobos/libdruntime/core/sys/openbsd/execinfo.d new file mode 100644 index 0000000..f5b317f --- /dev/null +++ b/libphobos/libdruntime/core/sys/openbsd/execinfo.d @@ -0,0 +1,147 @@ +/** + * OpenBSD implementation of glibc's $(LINK2 http://www.gnu.org/software/libc/manual/html_node/Backtraces.html backtrace) facility. + * + * Copyright: Copyright Martin Nowak 2012. + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Authors: Martin Nowak + * Source: $(DRUNTIMESRC core/sys/openbsd/_execinfo.d) + */ +module core.sys.openbsd.execinfo; + +version (OpenBSD): +extern (C): +nothrow: + +version (GNU) + version = BacktraceExternal; + +version (BacktraceExternal) +{ + size_t backtrace(void**, size_t); + char** backtrace_symbols(const(void*)*, size_t); + void backtrace_symbols_fd(const(void*)*, size_t, int); + char** backtrace_symbols_fmt(const(void*)*, size_t, const char*); + int backtrace_symbols_fd_fmt(const(void*)*, size_t, int, const char*); +} +else +{ + import core.sys.openbsd.dlfcn; + + // Use extern (D) so that these functions don't collide with libexecinfo. + + extern (D) int backtrace(void** buffer, int size) + { + import core.thread : thread_stackBottom; + + void** p, pend=cast(void**)thread_stackBottom(); + version (D_InlineAsm_X86) + asm nothrow @trusted { mov p[EBP], EBP; } + else version (D_InlineAsm_X86_64) + asm nothrow @trusted { mov p[RBP], RBP; } + else + static assert(false, "Architecture not supported."); + + int i; + for (; i < size && p < pend; ++i) + { + buffer[i] = *(p + 1); + auto pnext = cast(void**)*p; + if (pnext <= p) break; + p = pnext; + } + return i; + } + + + extern (D) char** backtrace_symbols(const(void*)* buffer, int size) + { + static void* realloc(void* p, size_t len) nothrow + { + static import cstdlib=core.stdc.stdlib; + auto res = cstdlib.realloc(p, len); + if (res is null) cstdlib.free(p); + return res; + } + + if (size <= 0) return null; + + size_t pos = size * (char*).sizeof; + char** p = cast(char**)realloc(null, pos); + if (p is null) return null; + + Dl_info info; + foreach (i, addr; buffer[0 .. size]) + { + if (dladdr(addr, &info) == 0) + (cast(ubyte*)&info)[0 .. info.sizeof] = 0; + fixupDLInfo(addr, info); + + immutable len = formatStackFrame(null, 0, addr, info); + assert(len > 0); + + p = cast(char**)realloc(p, pos + len); + if (p is null) return null; + + formatStackFrame(cast(char*)p + pos, len, addr, info) == len || assert(0); + + p[i] = cast(char*)pos; + pos += len; + } + foreach (i; 0 .. size) + { + pos = cast(size_t)p[i]; + p[i] = cast(char*)p + pos; + } + return p; + } + + + extern (D) void backtrace_symbols_fd(const(void*)* buffer, int size, int fd) + { + import core.sys.posix.unistd : write; + import core.stdc.stdlib : alloca; + + if (size <= 0) return; + + Dl_info info; + foreach (i, addr; buffer[0 .. size]) + { + if (dladdr(addr, &info) == 0) + (cast(ubyte*)&info)[0 .. info.sizeof] = 0; + fixupDLInfo(addr, info); + + enum maxAlloca = 1024; + enum min = (size_t a, size_t b) => a <= b ? a : b; + immutable len = min(formatStackFrame(null, 0, addr, info), maxAlloca); + assert(len > 0); + + auto p = cast(char*)alloca(len); + if (p is null) return; + + formatStackFrame(p, len, addr, info) >= len || assert(0); + p[len - 1] = '\n'; + write(fd, p, len); + } + } + + + private void fixupDLInfo(const(void)* addr, ref Dl_info info) + { + if (info.dli_fname is null) info.dli_fname = "???"; + if (info.dli_fbase is null) info.dli_fbase = null; + if (info.dli_sname is null) info.dli_sname = "???"; + if (info.dli_saddr is null) info.dli_saddr = cast(void*)addr; + } + + + private size_t formatStackFrame(char* p, size_t plen, const(void)* addr, const ref Dl_info info) + { + import core.stdc.stdio : snprintf; + + immutable off = addr - info.dli_saddr; + immutable len = snprintf(p, plen, "%p <%s+%zd> at %s", + addr, info.dli_sname, off, info.dli_fname); + assert(len > 0); + return cast(size_t)len + 1; // + '\0' + } +} diff --git a/libphobos/libdruntime/core/sys/openbsd/sys/elf32.d b/libphobos/libdruntime/core/sys/openbsd/sys/elf32.d index cefee38..dae977a 100644 --- a/libphobos/libdruntime/core/sys/openbsd/sys/elf32.d +++ b/libphobos/libdruntime/core/sys/openbsd/sys/elf32.d @@ -112,7 +112,7 @@ extern (D) pure { auto ELF32_M_SYM(I)(I info) @safe { return info >> 8; } auto ELF32_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubye)size; } + auto ELF32_M_INFO(S, SZ)(S sym, SZ size) { return (sym << 8) + cast(ubyte)size; } } struct Elf32_Cap diff --git a/libphobos/libdruntime/core/sys/openbsd/sys/elf64.d b/libphobos/libdruntime/core/sys/openbsd/sys/elf64.d index d5e15fc..e26a5fc 100644 --- a/libphobos/libdruntime/core/sys/openbsd/sys/elf64.d +++ b/libphobos/libdruntime/core/sys/openbsd/sys/elf64.d @@ -118,7 +118,7 @@ extern (D) pure { auto ELF64_M_SYM(I)(I info) @safe { return info >> 8; } auto ELF64_M_SIZE(I)(I info) { return cast(ubyte)info; } - auto ELF64_M_INFO(S, SZ)(S sym, SZ size) @safe { return (sym << 8) + cast(ubye)size; } + auto ELF64_M_INFO(S, SZ)(S sym, SZ size) @safe { return (sym << 8) + cast(ubyte)size; } } struct Elf64_Cap diff --git a/libphobos/libdruntime/core/sys/posix/arpa/inet.d b/libphobos/libdruntime/core/sys/posix/arpa/inet.d index 6881142..c602e17 100644 --- a/libphobos/libdruntime/core/sys/posix/arpa/inet.d +++ b/libphobos/libdruntime/core/sys/posix/arpa/inet.d @@ -68,8 +68,6 @@ version (CRuntime_Glibc) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @trusted pure { uint32_t htonl(uint32_t); @@ -93,8 +91,6 @@ else version (Darwin) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @trusted pure { uint32_t htonl(uint32_t); @@ -118,8 +114,6 @@ else version (FreeBSD) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @trusted pure { uint32_t htonl(uint32_t); @@ -143,8 +137,6 @@ else version (NetBSD) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @trusted pure { uint32_t htonl(uint32_t); @@ -168,30 +160,22 @@ else version (OpenBSD) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @safe pure extern (D) { - private + version (BigEndian) { - uint32_t __swap32( uint32_t x ) - { - uint32_t byte32_swap = (x & 0xff) << 24 | (x &0xff00) << 8 | - (x & 0xff0000) >> 8 | (x & 0xff000000) >> 24; - return byte32_swap; - } - - uint16_t __swap16( uint16_t x ) - { - uint16_t byte16_swap = (x & 0xff) << 8 | (x & 0xff00) >> 8; - return byte16_swap; - } + uint32_t htonl(uint32_t x) { return x; } + uint16_t htons(uint16_t x) { return x; } } + else + { + import core.bitop : bswap, byteswap; - uint32_t htonl(uint32_t x) { return __swap32(x); } - uint16_t htons(uint16_t x) { return __swap16(x); } - uint32_t ntohl(uint32_t x) { return __swap32(x); } - uint16_t ntohs(uint16_t x) { return __swap16(x); } + uint32_t htonl(uint32_t x) { return bswap(x); } + uint16_t htons(uint16_t x) { return byteswap(x); } + } + alias ntohl = htonl; + alias ntohs = htons; } in_addr_t inet_addr(const scope char*); @@ -209,8 +193,6 @@ else version (DragonFlyBSD) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @trusted pure { uint32_t htonl(uint32_t); @@ -233,7 +215,6 @@ else version (Solaris) { in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; @trusted pure { @@ -257,30 +238,22 @@ else version (CRuntime_Bionic) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @safe pure extern (D) { - private + version (BigEndian) { - uint32_t __swap32( uint32_t x ) - { - uint32_t byte32_swap = (x & 0xff) << 24 | (x &0xff00) << 8 | - (x & 0xff0000) >> 8 | (x & 0xff000000) >> 24; - return byte32_swap; - } - - uint16_t __swap16( uint16_t x ) - { - uint16_t byte16_swap = (x & 0xff) << 8 | (x & 0xff00) >> 8; - return byte16_swap; - } + uint32_t htonl(uint32_t x) { return x; } + uint16_t htons(uint16_t x) { return x; } } + else + { + import core.bitop : bswap, byteswap; - uint32_t htonl(uint32_t x) { return __swap32(x); } - uint16_t htons(uint16_t x) { return __swap16(x); } - uint32_t ntohl(uint32_t x) { return __swap32(x); } - uint16_t ntohs(uint16_t x) { return __swap16(x); } + uint32_t htonl(uint32_t x) { return bswap(x); } + uint16_t htons(uint16_t x) { return byteswap(x); } + } + alias ntohl = htonl; + alias ntohs = htons; } in_addr_t inet_addr(const scope char*); @@ -298,8 +271,6 @@ else version (CRuntime_Musl) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @trusted pure { uint32_t htonl(uint32_t); @@ -323,8 +294,6 @@ else version (CRuntime_UClibc) in_addr_t s_addr; } - enum INET_ADDRSTRLEN = 16; - @trusted pure { uint32_t htonl(uint32_t); @@ -339,9 +308,6 @@ else version (CRuntime_UClibc) int inet_pton(int, const scope char*, void*); } -// -// IPV6 (IP6) -// /* NOTE: The following must must be defined in core.sys.posix.arpa.inet to break a circular import: INET6_ADDRSTRLEN. @@ -349,39 +315,5 @@ NOTE: The following must must be defined in core.sys.posix.arpa.inet to break INET6_ADDRSTRLEN // from core.sys.posix.netinet.in_ */ -version (CRuntime_Glibc) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (Darwin) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (FreeBSD) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (NetBSD) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (OpenBSD) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (DragonFlyBSD) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (Solaris) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (CRuntime_Bionic) -{ - enum INET6_ADDRSTRLEN = 46; -} -else version (CRuntime_UClibc) -{ - enum INET6_ADDRSTRLEN = 46; -} +enum INET_ADDRSTRLEN = 16; +enum INET6_ADDRSTRLEN = 46; diff --git a/libphobos/libdruntime/core/sys/posix/fcntl.d b/libphobos/libdruntime/core/sys/posix/fcntl.d index 59df921..6833f3b 100644 --- a/libphobos/libdruntime/core/sys/posix/fcntl.d +++ b/libphobos/libdruntime/core/sys/posix/fcntl.d @@ -159,6 +159,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x400; // octal 02000 enum O_NONBLOCK = 0x800; // octal 04000 + enum O_CLOEXEC = 0x80000; // octal 02000000 enum O_SYNC = 0x101000; // octal 04010000 enum O_DSYNC = 0x1000; // octal 010000 enum O_RSYNC = O_SYNC; @@ -172,6 +173,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x00008; // octal 010 enum O_NONBLOCK = 0x10004; // octal 0200004 + enum O_CLOEXEC = 0x200000; // octal 02000000 enum O_SYNC = 0x48000; // octal 01100000 enum O_DSYNC = 0x40000; // octal 01000000 enum O_RSYNC = 0x80000; // octal 02000000 @@ -186,6 +188,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x0008; enum O_DSYNC = 0x0010; enum O_NONBLOCK = 0x0080; + enum O_CLOEXEC = 0x80000; enum O_RSYNC = O_SYNC; enum O_SYNC = 0x4010; } @@ -198,6 +201,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x400; // octal 02000 enum O_NONBLOCK = 0x800; // octal 04000 + enum O_CLOEXEC = 0x80000; // octal 02000000 enum O_SYNC = 0x101000; // octal 04010000 enum O_DSYNC = 0x1000; // octal 010000 enum O_RSYNC = O_SYNC; @@ -211,6 +215,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x400; // octal 02000 enum O_NONBLOCK = 0x800; // octal 04000 + enum O_CLOEXEC = 0x80000; // octal 02000000 enum O_SYNC = 0x101000; // octal 04010000 enum O_DSYNC = 0x1000; // octal 010000 enum O_RSYNC = O_SYNC; @@ -224,6 +229,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x400; // octal 02000 enum O_NONBLOCK = 0x800; // octal 04000 + enum O_CLOEXEC = 0x80000; // octal 02000000 enum O_SYNC = 0x101000; // octal 04010000 enum O_DSYNC = 0x1000; // octal 010000 enum O_RSYNC = O_SYNC; @@ -237,6 +243,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x8; enum O_NONBLOCK = 0x4000; + enum O_CLOEXEC = 0x400000; enum O_SYNC = 0x802000; enum O_DSYNC = 0x2000; enum O_RSYNC = O_SYNC; @@ -250,6 +257,7 @@ version (CRuntime_Glibc) enum O_APPEND = 0x400; // octal 02000 enum O_NONBLOCK = 0x800; // octal 04000 + enum O_CLOEXEC = 0x80000; // octal 02000000 enum O_SYNC = 0x101000; // octal 04010000 enum O_DSYNC = 0x1000; // octal 010000 enum O_RSYNC = O_SYNC; @@ -895,10 +903,10 @@ else version (CRuntime_Musl) O_SEARCH = O_PATH, O_EXEC = O_PATH, - O_ACCMODE = (03|O_SEARCH), - O_RDONLY = 00, - O_WRONLY = 01, - O_RDWR = 02, + O_ACCMODE = (3|O_SEARCH), + O_RDONLY = 0, + O_WRONLY = 1, + O_RDWR = 2, } enum { diff --git a/libphobos/libdruntime/core/sys/posix/net/if_.d b/libphobos/libdruntime/core/sys/posix/net/if_.d index 3713673..e63af4f 100644 --- a/libphobos/libdruntime/core/sys/posix/net/if_.d +++ b/libphobos/libdruntime/core/sys/posix/net/if_.d @@ -157,4 +157,4 @@ else version (CRuntime_UClibc) char* if_indextoname(uint, char*); if_nameindex_t* if_nameindex(); void if_freenameindex(if_nameindex_t*); -} +} \ No newline at end of file diff --git a/libphobos/libdruntime/core/sys/posix/semaphore.d b/libphobos/libdruntime/core/sys/posix/semaphore.d index 4f6f639..a163e59 100644 --- a/libphobos/libdruntime/core/sys/posix/semaphore.d +++ b/libphobos/libdruntime/core/sys/posix/semaphore.d @@ -99,7 +99,7 @@ else version (NetBSD) } else version (OpenBSD) { - struct __sem { } + struct __sem; alias sem_t = __sem*; enum SEM_FAILED = cast(sem_t*) null; diff --git a/libphobos/libdruntime/core/sys/posix/setjmp.d b/libphobos/libdruntime/core/sys/posix/setjmp.d index b98d321..91e3a19 100644 --- a/libphobos/libdruntime/core/sys/posix/setjmp.d +++ b/libphobos/libdruntime/core/sys/posix/setjmp.d @@ -261,6 +261,10 @@ else version (OpenBSD) { enum _JBLEN = 64; } + else version (AArch64) + { + enum _JBLEN = 64; + } else version (PPC) { enum _JBLEN = 100; diff --git a/libphobos/libdruntime/core/sys/posix/stdio.d b/libphobos/libdruntime/core/sys/posix/stdio.d index 41b52da..c8f92ec 100644 --- a/libphobos/libdruntime/core/sys/posix/stdio.d +++ b/libphobos/libdruntime/core/sys/posix/stdio.d @@ -526,6 +526,16 @@ else version (CRuntime_Musl) int putc_unlocked(int, FILE*); int putchar_unlocked(int); } +else version (CRuntime_Bionic) +{ + void flockfile(FILE*); + int ftrylockfile(FILE*); + void funlockfile(FILE*); + int getc_unlocked(FILE*); + int getchar_unlocked(); + int putc_unlocked(int, FILE*); + int putchar_unlocked(int); +} else version (Darwin) { void flockfile(FILE*); diff --git a/libphobos/libdruntime/core/sys/posix/string.d b/libphobos/libdruntime/core/sys/posix/string.d index e17dfc6..b9e1c1c 100644 --- a/libphobos/libdruntime/core/sys/posix/string.d +++ b/libphobos/libdruntime/core/sys/posix/string.d @@ -31,11 +31,11 @@ public import core.sys.posix.locale : locale_t; public import core.stdc.string; /// Copy string until character found -void* memccpy(return void* dst, scope const void* src, int c, size_t n); +void* memccpy(return void* dst, scope const void* src, int c, size_t n) pure; /// Copy string (including terminating '\0') -char* stpcpy(return char* dst, scope const char* src); +char* stpcpy(return char* dst, scope const char* src) pure; /// Ditto -char* stpncpy(return char* dst, const char* src, size_t len); +char* stpncpy(return char* dst, const char* src, size_t len) pure; /// Compare strings according to current collation int strcoll_l(scope const char* s1, scope const char* s2, locale_t locale); /// @@ -43,7 +43,7 @@ char* strerror_l(int, locale_t); /// Save a copy of a string char* strndup(scope const char* str, size_t len); /// Find length of string up to `maxlen` -size_t strnlen(scope const char* str, size_t maxlen); +size_t strnlen(scope const char* str, size_t maxlen) pure; /// System signal messages const(char)* strsignal(int); /// Isolate sequential tokens in a null-terminated string diff --git a/libphobos/libdruntime/core/sys/windows/basetsd.d b/libphobos/libdruntime/core/sys/windows/basetsd.d index 97983c6..3bcac12 100644 --- a/libphobos/libdruntime/core/sys/windows/basetsd.d +++ b/libphobos/libdruntime/core/sys/windows/basetsd.d @@ -59,7 +59,7 @@ package mixin template AlignedStr(int alignVal, string name, string memberlist, mixin( _alignSpec ~ " struct " ~ name ~" { " ~ _alignSpec ~":"~ memberlist~" }" ); } -version (unittest) { +version (CoreUnittest) { private mixin AlignedStr!(16, "_Test_Aligned_Str", q{char a; char b;}); private mixin AlignedStr!(0, "_Test_NoAligned_Str", q{char a; char b;}); } diff --git a/libphobos/libdruntime/core/sys/windows/dll.d b/libphobos/libdruntime/core/sys/windows/dll.d index cc2422b..8e9d7a0 100644 --- a/libphobos/libdruntime/core/sys/windows/dll.d +++ b/libphobos/libdruntime/core/sys/windows/dll.d @@ -425,7 +425,6 @@ int dll_getRefCount( HINSTANCE hInstance ) nothrow @nogc mov peb, RAX; } } - } else version (Win32) { diff --git a/libphobos/libdruntime/core/sys/windows/sqlext.d b/libphobos/libdruntime/core/sys/windows/sqlext.d index 3acfc5a..1f89105 100644 --- a/libphobos/libdruntime/core/sys/windows/sqlext.d +++ b/libphobos/libdruntime/core/sys/windows/sqlext.d @@ -535,7 +535,7 @@ enum SQL_U_UNION_ALL = 2; enum SQL_UB_OFF = 0UL; enum SQL_UB_DEFAULT = SQL_UB_OFF; -enum SQL_UB_ON = 01UL; +enum SQL_UB_ON = 1UL; enum SQL_UNION = 96; enum SQL_UNSEARCHABLE = 0; diff --git a/libphobos/libdruntime/core/thread/fiber.d b/libphobos/libdruntime/core/thread/fiber.d index 2f90f17..56f6d67 100644 --- a/libphobos/libdruntime/core/thread/fiber.d +++ b/libphobos/libdruntime/core/thread/fiber.d @@ -1710,7 +1710,7 @@ unittest { assert( composed.state == Fiber.State.TERM ); } -version (unittest) +version (CoreUnittest) { class TestFiber : Fiber { diff --git a/libphobos/libdruntime/core/thread/osthread.d b/libphobos/libdruntime/core/thread/osthread.d index 9fcd30e..653adb9 100644 --- a/libphobos/libdruntime/core/thread/osthread.d +++ b/libphobos/libdruntime/core/thread/osthread.d @@ -1049,7 +1049,7 @@ class Thread : ThreadBase } } -private Thread toThread(ThreadBase t) @trusted nothrow @nogc pure +private Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure { return cast(Thread) cast(void*) t; } @@ -1209,6 +1209,18 @@ unittest thr.join(); } +// https://issues.dlang.org/show_bug.cgi?id=22124 +unittest +{ + Thread thread = new Thread({}); + auto fun(Thread t, int x) + { + t.__ctor({x = 3;}); + return t; + } + static assert(!__traits(compiles, () @nogc => fun(thread, 3) )); +} + unittest { import core.sync.semaphore; @@ -2212,15 +2224,7 @@ version (Windows) void append( Throwable t ) { - if ( obj.m_unhandled is null ) - obj.m_unhandled = t; - else - { - Throwable last = obj.m_unhandled; - while ( last.next !is null ) - last = last.next; - last.next = t; - } + obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); } version (D_InlineAsm_X86) @@ -2367,15 +2371,7 @@ else version (Posix) void append( Throwable t ) { - if ( obj.m_unhandled is null ) - obj.m_unhandled = t; - else - { - Throwable last = obj.m_unhandled; - while ( last.next !is null ) - last = last.next; - last.next = t; - } + obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); } try { diff --git a/libphobos/libdruntime/core/thread/threadbase.d b/libphobos/libdruntime/core/thread/threadbase.d index 0a8de10..4592bf1 100644 --- a/libphobos/libdruntime/core/thread/threadbase.d +++ b/libphobos/libdruntime/core/thread/threadbase.d @@ -573,11 +573,9 @@ package(core.thread): static void initLocks() @nogc { - _slock[] = typeid(Mutex).initializer[]; - (cast(Mutex)_slock.ptr).__ctor(); - - _criticalRegionLock[] = typeid(Mutex).initializer[]; - (cast(Mutex)_criticalRegionLock.ptr).__ctor(); + import core.lifetime : emplace; + emplace!Mutex(_slock[]); + emplace!Mutex(_criticalRegionLock[]); } static void termLocks() @nogc @@ -1338,8 +1336,8 @@ package void initLowlevelThreads() @nogc { - ll_lock[] = typeid(Mutex).initializer[]; - lowlevelLock.__ctor(); + import core.lifetime : emplace; + emplace(lowlevelLock()); } void termLowlevelThreads() @nogc diff --git a/libphobos/libdruntime/core/time.d b/libphobos/libdruntime/core/time.d index e7744c8..26e515b 100644 --- a/libphobos/libdruntime/core/time.d +++ b/libphobos/libdruntime/core/time.d @@ -22,8 +22,6 @@ system clock ticks, using the highest precision that the system provides.)) $(TR $(TDNW $(LREF MonoTime)) $(TD Represents a monotonic timestamp in system clock ticks, using the highest precision that the system provides.)) - $(TR $(TDNW $(LREF FracSec)) $(TD Represents fractional seconds - (portions of time smaller than a second).)) $(LEADINGROW Functions) $(TR $(TDNW $(LREF convert)) $(TD Generic way of converting between two time units.)) @@ -40,37 +38,27 @@ $(TR $(TH ) $(TH From $(LREF Duration)) $(TH From $(LREF TickDuration)) - $(TH From $(LREF FracSec)) $(TH From units) ) $(TR $(TD $(B To $(LREF Duration))) $(TD -) $(TD $(D tickDuration.)$(REF_SHORT to, std,conv)$(D !Duration())) - $(TD -) $(TD $(D dur!"msecs"(5)) or $(D 5.msecs())) ) $(TR $(TD $(B To $(LREF TickDuration))) $(TD $(D duration.)$(REF_SHORT to, std,conv)$(D !TickDuration())) $(TD -) - $(TD -) $(TD $(D TickDuration.from!"msecs"(msecs))) ) - $(TR $(TD $(B To $(LREF FracSec))) - $(TD $(D duration.fracSec)) - $(TD -) - $(TD -) - $(TD $(D FracSec.from!"msecs"(msecs))) - ) $(TR $(TD $(B To units)) $(TD $(D duration.total!"days")) $(TD $(D tickDuration.msecs)) - $(TD $(D fracSec.msecs)) $(TD $(D convert!("days", "msecs")(msecs))) )) Copyright: Copyright 2010 - 2012 - License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - Authors: Jonathan M Davis and Kato Shoichi + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: $(HTTP jmdavisprog.com, Jonathan M Davis) and Kato Shoichi Source: $(DRUNTIMESRC core/_time.d) Macros: NBSP=  @@ -80,7 +68,6 @@ module core.time; import core.exception; import core.stdc.time; import core.stdc.stdio; -import core.internal.traits : _Unqual = Unqual; import core.internal.string; version (Windows) @@ -128,13 +115,6 @@ ulong mach_absolute_time(); } -//To verify that an lvalue isn't required. -version (unittest) private T copy(T)(T t) -{ - return t; -} - - /++ What type of clock to use with $(LREF MonoTime) / $(LREF MonoTimeImpl) or $(D std.datetime.Clock.currTime). They default to $(D ClockType.normal), @@ -181,7 +161,7 @@ version (CoreDdoc) enum ClockType On systems which do not support a coarser clock, $(D MonoTimeImpl!(ClockType.coarse)) will internally use the same clock - as $(D Monotime) does, and $(D Clock.currTime!(ClockType.coarse)) will + as $(D MonoTime) does, and $(D Clock.currTime!(ClockType.coarse)) will use the same clock as $(D Clock.currTime). This is because the coarse clock is doing the same thing as the normal clock (just at lower precision), whereas some of the other clock types @@ -536,7 +516,7 @@ public: +/ static @property nothrow @nogc Duration min() { return Duration(long.min); } - unittest + version (CoreUnittest) unittest { assert(zero == dur!"seconds"(0)); assert(Duration.max == Duration(long.max)); @@ -561,30 +541,31 @@ public: +/ int opCmp(Duration rhs) const nothrow @nogc { - if (_hnsecs < rhs._hnsecs) - return -1; - if (_hnsecs > rhs._hnsecs) - return 1; - return 0; + return (_hnsecs > rhs._hnsecs) - (_hnsecs < rhs._hnsecs); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(Duration, const Duration, immutable Duration)) + import core.internal.traits : rvalueOf; + foreach (T; AliasSeq!(Duration, const Duration, immutable Duration)) { - foreach (U; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (U; AliasSeq!(Duration, const Duration, immutable Duration)) { T t = 42; - U u = t; + // workaround https://issues.dlang.org/show_bug.cgi?id=18296 + version (D_Coverage) + U u = T(t._hnsecs); + else + U u = t; assert(t == u); - assert(copy(t) == u); - assert(t == copy(u)); + assert(rvalueOf(t) == u); + assert(t == rvalueOf(u)); } } - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { - foreach (E; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (E; AliasSeq!(Duration, const Duration, immutable Duration)) { assert((cast(D)Duration(12)).opCmp(cast(E)Duration(12)) == 0); assert((cast(D)Duration(-12)).opCmp(cast(E)Duration(-12)) == 0); @@ -595,23 +576,23 @@ public: assert((cast(D)Duration(12)).opCmp(cast(E)Duration(10)) > 0); assert((cast(D)Duration(12)).opCmp(cast(E)Duration(-12)) > 0); - assert(copy(cast(D)Duration(12)).opCmp(cast(E)Duration(12)) == 0); - assert(copy(cast(D)Duration(-12)).opCmp(cast(E)Duration(-12)) == 0); + assert(rvalueOf(cast(D)Duration(12)).opCmp(cast(E)Duration(12)) == 0); + assert(rvalueOf(cast(D)Duration(-12)).opCmp(cast(E)Duration(-12)) == 0); - assert(copy(cast(D)Duration(10)).opCmp(cast(E)Duration(12)) < 0); - assert(copy(cast(D)Duration(-12)).opCmp(cast(E)Duration(12)) < 0); + assert(rvalueOf(cast(D)Duration(10)).opCmp(cast(E)Duration(12)) < 0); + assert(rvalueOf(cast(D)Duration(-12)).opCmp(cast(E)Duration(12)) < 0); - assert(copy(cast(D)Duration(12)).opCmp(cast(E)Duration(10)) > 0); - assert(copy(cast(D)Duration(12)).opCmp(cast(E)Duration(-12)) > 0); + assert(rvalueOf(cast(D)Duration(12)).opCmp(cast(E)Duration(10)) > 0); + assert(rvalueOf(cast(D)Duration(12)).opCmp(cast(E)Duration(-12)) > 0); - assert((cast(D)Duration(12)).opCmp(copy(cast(E)Duration(12))) == 0); - assert((cast(D)Duration(-12)).opCmp(copy(cast(E)Duration(-12))) == 0); + assert((cast(D)Duration(12)).opCmp(rvalueOf(cast(E)Duration(12))) == 0); + assert((cast(D)Duration(-12)).opCmp(rvalueOf(cast(E)Duration(-12))) == 0); - assert((cast(D)Duration(10)).opCmp(copy(cast(E)Duration(12))) < 0); - assert((cast(D)Duration(-12)).opCmp(copy(cast(E)Duration(12))) < 0); + assert((cast(D)Duration(10)).opCmp(rvalueOf(cast(E)Duration(12))) < 0); + assert((cast(D)Duration(-12)).opCmp(rvalueOf(cast(E)Duration(12))) < 0); - assert((cast(D)Duration(12)).opCmp(copy(cast(E)Duration(10))) > 0); - assert((cast(D)Duration(12)).opCmp(copy(cast(E)Duration(-12))) > 0); + assert((cast(D)Duration(12)).opCmp(rvalueOf(cast(E)Duration(10))) > 0); + assert((cast(D)Duration(12)).opCmp(rvalueOf(cast(E)Duration(-12))) > 0); } } } @@ -634,20 +615,20 @@ public: rhs = The duration to add to or subtract from this $(D Duration). +/ Duration opBinary(string op, D)(D rhs) const nothrow @nogc - if (((op == "+" || op == "-" || op == "%") && is(_Unqual!D == Duration)) || - ((op == "+" || op == "-") && is(_Unqual!D == TickDuration))) + if (((op == "+" || op == "-" || op == "%") && is(immutable D == immutable Duration)) || + ((op == "+" || op == "-") && is(immutable D == immutable TickDuration))) { - static if (is(_Unqual!D == Duration)) + static if (is(immutable D == immutable Duration)) return Duration(mixin("_hnsecs " ~ op ~ " rhs._hnsecs")); - else if (is(_Unqual!D == TickDuration)) + else return Duration(mixin("_hnsecs " ~ op ~ " rhs.hnsecs")); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { - foreach (E; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (E; AliasSeq!(Duration, const Duration, immutable Duration)) { assert((cast(D)Duration(5)) + (cast(E)Duration(7)) == Duration(12)); assert((cast(D)Duration(5)) - (cast(E)Duration(7)) == Duration(-2)); @@ -678,7 +659,7 @@ public: assert((cast(D)Duration(-7)) % (cast(E)Duration(5)) == Duration(-2)); } - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { assertApprox((cast(D)Duration(5)) + cast(T)TickDuration.from!"usecs"(7), Duration(70), Duration(80)); assertApprox((cast(D)Duration(5)) - cast(T)TickDuration.from!"usecs"(7), Duration(-70), Duration(-60)); @@ -720,16 +701,16 @@ public: +/ Duration opBinaryRight(string op, D)(D lhs) const nothrow @nogc if ((op == "+" || op == "-") && - is(_Unqual!D == TickDuration)) + is(immutable D == immutable TickDuration)) { return Duration(mixin("lhs.hnsecs " ~ op ~ " _hnsecs")); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { assertApprox((cast(T)TickDuration.from!"usecs"(7)) + cast(D)Duration(5), Duration(70), Duration(80)); assertApprox((cast(T)TickDuration.from!"usecs"(7)) - cast(D)Duration(5), Duration(60), Duration(70)); @@ -772,18 +753,18 @@ public: Params: rhs = The duration to add to or subtract from this $(D Duration). +/ - ref Duration opOpAssign(string op, D)(in D rhs) nothrow @nogc - if (((op == "+" || op == "-" || op == "%") && is(_Unqual!D == Duration)) || - ((op == "+" || op == "-") && is(_Unqual!D == TickDuration))) + ref Duration opOpAssign(string op, D)(const scope D rhs) nothrow @nogc + if (((op == "+" || op == "-" || op == "%") && is(immutable D == immutable Duration)) || + ((op == "+" || op == "-") && is(immutable D == immutable TickDuration))) { - static if (is(_Unqual!D == Duration)) + static if (is(immutable D == immutable Duration)) mixin("_hnsecs " ~ op ~ "= rhs._hnsecs;"); - else if (is(_Unqual!D == TickDuration)) + else mixin("_hnsecs " ~ op ~ "= rhs.hnsecs;"); return this; } - unittest + version (CoreUnittest) unittest { static void test1(string op, E)(Duration actual, in E rhs, Duration expected, size_t line = __LINE__) { @@ -801,7 +782,7 @@ public: assertApprox(actual, lower, upper, "op assign failed", line); } - foreach (E; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (E; AliasSeq!(Duration, const Duration, immutable Duration)) { test1!"+="(Duration(5), (cast(E)Duration(7)), Duration(12)); test1!"-="(Duration(5), (cast(E)Duration(7)), Duration(-2)); @@ -832,7 +813,7 @@ public: test1!"%="(Duration(-7), (cast(E)Duration(-5)), Duration(-2)); } - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { test2!"+="(Duration(5), cast(T)TickDuration.from!"usecs"(7), Duration(70), Duration(80)); test2!"-="(Duration(5), cast(T)TickDuration.from!"usecs"(7), Duration(-70), Duration(-60)); @@ -855,9 +836,9 @@ public: test2!"-="(Duration(-7), cast(T)TickDuration.from!"usecs"(-5), Duration(38), Duration(48)); } - foreach (D; _TypeTuple!(const Duration, immutable Duration)) + foreach (D; AliasSeq!(const Duration, immutable Duration)) { - foreach (E; _TypeTuple!(Duration, const Duration, immutable Duration, + foreach (E; AliasSeq!(Duration, const Duration, immutable Duration, TickDuration, const TickDuration, immutable TickDuration)) { D lhs = D(120); @@ -888,9 +869,9 @@ public: mixin("return Duration(_hnsecs " ~ op ~ " value);"); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { assert((cast(D)Duration(5)) * 7 == Duration(35)); assert((cast(D)Duration(7)) * 5 == Duration(35)); @@ -909,9 +890,9 @@ public: } } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { assert((cast(D)Duration(5)) / 7 == Duration(0)); assert((cast(D)Duration(7)) / 5 == Duration(1)); @@ -950,7 +931,7 @@ public: return this; } - unittest + version (CoreUnittest) unittest { static void test(D)(D actual, long value, Duration expected, size_t line = __LINE__) { @@ -982,7 +963,7 @@ public: static assert(!__traits(compiles, idur *= 12)); } - unittest + version (CoreUnittest) unittest { static void test(Duration actual, long value, Duration expected, size_t line = __LINE__) { @@ -1030,7 +1011,7 @@ public: return _hnsecs / rhs._hnsecs; } - unittest + version (CoreUnittest) unittest { assert(Duration(5) / Duration(7) == 0); assert(Duration(7) / Duration(5) == 1); @@ -1069,9 +1050,9 @@ public: return opBinary!op(value); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { assert(5 * cast(D)Duration(7) == Duration(35)); assert(7 * cast(D)Duration(5) == Duration(35)); @@ -1100,9 +1081,9 @@ public: return Duration(-_hnsecs); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { assert(-(cast(D)Duration(7)) == Duration(-7)); assert(-(cast(D)Duration(5)) == Duration(-5)); @@ -1121,22 +1102,22 @@ public: $(D duration.to!TickDuration()) +/ TickDuration opCast(T)() const nothrow @nogc - if (is(_Unqual!T == TickDuration)) + if (is(immutable T == immutable TickDuration)) { return TickDuration.from!"hnsecs"(_hnsecs); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { - foreach (units; _TypeTuple!("seconds", "msecs", "usecs", "hnsecs")) + foreach (units; AliasSeq!("seconds", "msecs", "usecs", "hnsecs")) { enum unitsPerSec = convert!("seconds", units)(1); if (TickDuration.ticksPerSec >= unitsPerSec) { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { auto t = TickDuration.from!units(1); assertApprox(cast(T)cast(D)dur!units(1), t - TickDuration(1), t + TickDuration(1), units); @@ -1165,7 +1146,7 @@ public: return _hnsecs != 0; } - unittest + version (CoreUnittest) unittest { auto d = 10.minutes; assert(d); @@ -1175,7 +1156,7 @@ public: //Temporary hack until bug http://d.puremagic.com/issues/show_bug.cgi?id=5747 is fixed. Duration opCast(T)() const nothrow @nogc - if (is(_Unqual!T == Duration)) + if (is(immutable T == immutable Duration)) { return this; } @@ -1299,13 +1280,13 @@ public: enum allAreMutableIntegralTypes = allAreMutableIntegralTypes!(Args[1 .. $]); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(long, int, short, byte, ulong, uint, ushort, ubyte)) + foreach (T; AliasSeq!(long, int, short, byte, ulong, uint, ushort, ubyte)) static assert(allAreMutableIntegralTypes!T); - foreach (T; _TypeTuple!(long, int, short, byte, ulong, uint, ushort, ubyte)) + foreach (T; AliasSeq!(long, int, short, byte, ulong, uint, ushort, ubyte)) static assert(!allAreMutableIntegralTypes!(const T)); - foreach (T; _TypeTuple!(char, wchar, dchar, float, double, real, string)) + foreach (T; AliasSeq!(char, wchar, dchar, float, double, real, string)) static assert(!allAreMutableIntegralTypes!T); static assert(allAreMutableIntegralTypes!(long, int, short, byte)); static assert(!allAreMutableIntegralTypes!(long, int, short, char, byte)); @@ -1366,9 +1347,9 @@ public: } } - pure nothrow unittest + version (CoreUnittest) pure nothrow unittest { - foreach (D; _TypeTuple!(const Duration, immutable Duration)) + foreach (D; AliasSeq!(const Duration, immutable Duration)) { D d = dur!"weeks"(3) + dur!"days"(5) + dur!"hours"(19) + dur!"minutes"(7) + dur!"seconds"(2) + dur!"hnsecs"(1234567); @@ -1474,7 +1455,7 @@ public: static assert(!is(typeof(d.split("hnsecs", "seconds", "msecs")()))); static assert(!is(typeof(d.split("seconds", "hnecs", "msecs")()))); static assert(!is(typeof(d.split("seconds", "msecs", "msecs")()))); - alias _TypeTuple!("nsecs", "hnsecs", "usecs", "msecs", "seconds", + alias AliasSeq!("nsecs", "hnsecs", "usecs", "msecs", "seconds", "minutes", "hours", "days", "weeks") timeStrs; foreach (i, str; timeStrs[1 .. $]) static assert(!is(typeof(d.split!(timeStrs[i - 1], str)()))); @@ -1521,10 +1502,7 @@ public: units == "hnsecs" || units == "nsecs") { - static if (units == "nsecs") - return convert!("hnsecs", "nsecs")(_hnsecs); - else - return getUnitsFromHNSecs!units(_hnsecs); + return convert!("hnsecs", units)(_hnsecs); } /// @@ -1543,9 +1521,9 @@ public: assert(dur!"nsecs"(2007).total!"nsecs" == 2000); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(const Duration, immutable Duration)) + foreach (D; AliasSeq!(const Duration, immutable Duration)) { assert((cast(D)dur!"weeks"(12)).total!"weeks" == 12); assert((cast(D)dur!"weeks"(12)).total!"days" == 84); @@ -1598,7 +1576,7 @@ public: unit = "μs"; else unit = plural ? units : units[0 .. $-1]; - res ~= signedToTempString(val, 10); + res ~= signedToTempString(val); res ~= " "; res ~= unit; } @@ -1649,9 +1627,9 @@ public: assert(usecs(-5239492).toString() == "-5 secs, -239 ms, and -492 μs"); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { assert((cast(D)Duration(0)).toString() == "0 hnsecs"); assert((cast(D)Duration(1)).toString() == "1 hnsec"); @@ -1707,9 +1685,9 @@ public: return _hnsecs < 0; } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { assert(!(cast(D)Duration(100)).isNegative); assert(!(cast(D)Duration(1)).isNegative); @@ -1766,7 +1744,7 @@ unittest td = The TickDuration to convert +/ T to(string units, T, D)(D td) @safe pure nothrow @nogc - if (is(_Unqual!D == TickDuration) && + if (is(immutable D == immutable TickDuration) && (units == "seconds" || units == "msecs" || units == "usecs" || @@ -1803,8 +1781,9 @@ unittest long tl = to!("seconds",long)(t); assert(tl == 1000); + import core.stdc.math : fabs; double td = to!("seconds",double)(t); - assert(_abs(td - 1000) < 0.001); + assert(fabs(td - 1000) < 0.001); } unittest @@ -1819,12 +1798,12 @@ unittest auto _str(F)(F val) { static if (is(F == int) || is(F == long)) - return signedToTempString(val, 10); + return signedToTempString(val); else - return unsignedToTempString(val, 10); + return unsignedToTempString(val); } - foreach (F; _TypeTuple!(int,uint,long,ulong,float,double,real)) + foreach (F; AliasSeq!(int,uint,long,ulong,float,double,real)) { F t1f = to!(U,F)(t1); F t2f = to!(U,F)(t2); @@ -1950,7 +1929,7 @@ unittest unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { assert(dur!"weeks"(7).total!"weeks" == 7); assert(dur!"days"(7).total!"days" == 7); @@ -2093,7 +2072,7 @@ struct MonoTimeImpl(ClockType clockType) static assert(0, "Unsupported platform"); // POD value, test mutable/const/immutable conversion - unittest + version (CoreUnittest) unittest { MonoTimeImpl m; const MonoTimeImpl cm = m; @@ -2130,26 +2109,26 @@ struct MonoTimeImpl(ClockType clockType) version (Windows) { - long ticks; - if (QueryPerformanceCounter(&ticks) == 0) - { - // This probably cannot happen on Windows 95 or later - import core.internal.abort : abort; - abort("Call to QueryPerformanceCounter failed."); - } + long ticks = void; + QueryPerformanceCounter(&ticks); return MonoTimeImpl(ticks); } else version (Darwin) return MonoTimeImpl(mach_absolute_time()); else version (Posix) { - timespec ts; - if (clock_gettime(clockArg, &ts) != 0) + timespec ts = void; + immutable error = clock_gettime(clockArg, &ts); + // clockArg is supported and if tv_sec is long or larger + // overflow won't happen before 292 billion years A.D. + static if (ts.tv_sec.max < long.max) { - import core.internal.abort : abort; - abort("Call to clock_gettime failed."); + if (error) + { + import core.internal.abort : abort; + abort("Call to clock_gettime failed."); + } } - return MonoTimeImpl(convClockFreq(ts.tv_sec * 1_000_000_000L + ts.tv_nsec, 1_000_000_000L, ticksPerSecond)); @@ -2176,7 +2155,7 @@ struct MonoTimeImpl(ClockType clockType) MonoTimeImpl min() { return MonoTimeImpl(long.min); } } - unittest + version (CoreUnittest) unittest { assert(MonoTimeImpl.zero == MonoTimeImpl(0)); assert(MonoTimeImpl.max == MonoTimeImpl(long.max)); @@ -2199,28 +2178,28 @@ struct MonoTimeImpl(ClockType clockType) +/ int opCmp(MonoTimeImpl rhs) const pure nothrow @nogc { - if (_ticks < rhs._ticks) - return -1; - return _ticks > rhs._ticks ? 1 : 0; + return (_ticks > rhs._ticks) - (_ticks < rhs._ticks); } - unittest + version (CoreUnittest) unittest { + import core.internal.traits : rvalueOf; const t = MonoTimeImpl.currTime; - assert(t == copy(t)); + assert(t == rvalueOf(t)); } - unittest + version (CoreUnittest) unittest { + import core.internal.traits : rvalueOf; const before = MonoTimeImpl.currTime; auto after = MonoTimeImpl(before._ticks + 42); assert(before < after); - assert(copy(before) <= before); - assert(copy(after) > before); - assert(after >= copy(after)); + assert(rvalueOf(before) <= before); + assert(rvalueOf(after) > before); + assert(after >= rvalueOf(after)); } - unittest + version (CoreUnittest) unittest { const currTime = MonoTimeImpl.currTime; assert(MonoTimeImpl(long.max) > MonoTimeImpl(0)); @@ -2274,16 +2253,17 @@ assert(before + timeElapsed == after); return Duration(convClockFreq(diff , ticksPerSecond, hnsecsPer!"seconds")); } - unittest + version (CoreUnittest) unittest { + import core.internal.traits : rvalueOf; const t = MonoTimeImpl.currTime; - assert(t - copy(t) == Duration.zero); + assert(t - rvalueOf(t) == Duration.zero); static assert(!__traits(compiles, t + t)); } - unittest + version (CoreUnittest) unittest { - static void test(in MonoTimeImpl before, in MonoTimeImpl after, in Duration min) + static void test(const scope MonoTimeImpl before, const scope MonoTimeImpl after, const scope Duration min) { immutable diff = after - before; assert(diff >= min); @@ -2312,14 +2292,14 @@ assert(before + timeElapsed == after); mixin("return MonoTimeImpl(_ticks " ~ op ~ " rhsConverted);"); } - unittest + version (CoreUnittest) unittest { const t = MonoTimeImpl.currTime; assert(t + Duration(0) == t); assert(t - Duration(0) == t); } - unittest + version (CoreUnittest) unittest { const t = MonoTimeImpl.currTime; @@ -2345,7 +2325,7 @@ assert(before + timeElapsed == after); return this; } - unittest + version (CoreUnittest) unittest { auto mt = MonoTimeImpl.currTime; const initial = mt; @@ -2386,7 +2366,7 @@ assert(before + timeElapsed == after); return _ticks; } - unittest + version (CoreUnittest) unittest { const mt = MonoTimeImpl.currTime; assert(mt.ticks == mt._ticks); @@ -2405,7 +2385,7 @@ assert(before + timeElapsed == after); return _ticksPerSecond[_clockIdx]; } - unittest + version (CoreUnittest) unittest { assert(MonoTimeImpl.ticksPerSecond == _ticksPerSecond[_clockIdx]); } @@ -2415,15 +2395,15 @@ assert(before + timeElapsed == after); string toString() const pure nothrow { static if (clockType == ClockType.normal) - return "MonoTime(" ~ signedToTempString(_ticks, 10) ~ " ticks, " ~ signedToTempString(ticksPerSecond, 10) ~ " ticks per second)"; + return "MonoTime(" ~ signedToTempString(_ticks) ~ " ticks, " ~ signedToTempString(ticksPerSecond) ~ " ticks per second)"; else - return "MonoTimeImpl!(ClockType." ~ _clockName ~ ")(" ~ signedToTempString(_ticks, 10) ~ " ticks, " ~ - signedToTempString(ticksPerSecond, 10) ~ " ticks per second)"; + return "MonoTimeImpl!(ClockType." ~ _clockName ~ ")(" ~ signedToTempString(_ticks) ~ " ticks, " ~ + signedToTempString(ticksPerSecond) ~ " ticks per second)"; } - unittest + version (CoreUnittest) unittest { - static min(T)(T a, T b) { return a < b ? a : b; } + import core.internal.util.math : min; static void eat(ref string s, const(char)[] exp) { @@ -2438,9 +2418,9 @@ assert(before + timeElapsed == after); else eat(str, "MonoTimeImpl!(ClockType."~_clockName~")("); - eat(str, signedToTempString(mt._ticks, 10)); + eat(str, signedToTempString(mt._ticks)); eat(str, " ticks, "); - eat(str, signedToTempString(ticksPerSecond, 10)); + eat(str, signedToTempString(ticksPerSecond)); eat(str, " ticks per second)"); } @@ -2448,7 +2428,7 @@ private: // static immutable long _ticksPerSecond; - unittest + version (CoreUnittest) unittest { assert(_ticksPerSecond[_clockIdx]); } @@ -2784,8 +2764,7 @@ struct TickDuration { /++ It's the same as $(D TickDuration(0)), but it's provided to be - consistent with $(D Duration) and $(D FracSec), which provide $(D zero) - properties. + consistent with $(D Duration), which provides a $(D zero) property. +/ TickDuration zero() { return TickDuration(0); } @@ -2800,7 +2779,7 @@ struct TickDuration TickDuration min() { return TickDuration(long.min); } } - unittest + version (CoreUnittest) unittest { assert(zero == TickDuration(0)); assert(TickDuration.max == TickDuration(long.max)); @@ -2851,7 +2830,7 @@ struct TickDuration appOrigin = TickDuration.currSystemTick; } - unittest + version (CoreUnittest) unittest { assert(ticksPerSec); } @@ -2874,9 +2853,9 @@ struct TickDuration return this.to!("seconds", long)(); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { assert((cast(T)TickDuration(ticksPerSec)).seconds == 1); assert((cast(T)TickDuration(ticksPerSec - 1)).seconds == 0); @@ -2945,11 +2924,11 @@ struct TickDuration return TickDuration(cast(long)(length * (ticksPerSec / cast(real)unitsPerSec))); } - unittest + version (CoreUnittest) unittest { - foreach (units; _TypeTuple!("seconds", "msecs", "usecs", "nsecs")) + foreach (units; AliasSeq!("seconds", "msecs", "usecs", "nsecs")) { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { assertApprox((cast(T)TickDuration.from!units(1000)).to!(units, long)(), 500, 1500, units); @@ -2970,21 +2949,21 @@ struct TickDuration $(D tickDuration.to!Duration()) +/ Duration opCast(T)() @safe const pure nothrow @nogc - if (is(_Unqual!T == Duration)) + if (is(immutable T == immutable Duration)) { return Duration(hnsecs); } - unittest + version (CoreUnittest) unittest { - foreach (D; _TypeTuple!(Duration, const Duration, immutable Duration)) + foreach (D; AliasSeq!(Duration, const Duration, immutable Duration)) { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { auto expected = dur!"seconds"(1); assert(cast(D)cast(T)TickDuration.from!"seconds"(1) == expected); - foreach (units; _TypeTuple!("msecs", "usecs", "hnsecs")) + foreach (units; AliasSeq!("msecs", "usecs", "hnsecs")) { D actual = cast(D)cast(T)TickDuration.from!units(1_000_000); assertApprox(actual, dur!units(900_000), dur!units(1_100_000)); @@ -2996,7 +2975,7 @@ struct TickDuration //Temporary hack until bug http://d.puremagic.com/issues/show_bug.cgi?id=5747 is fixed. TickDuration opCast(T)() @safe const pure nothrow @nogc - if (is(_Unqual!T == TickDuration)) + if (is(immutable T == immutable TickDuration)) { return this; } @@ -3025,9 +3004,9 @@ struct TickDuration return this; } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { auto a = TickDuration.currSystemTick; auto result = a += cast(T)TickDuration.currSystemTick; @@ -3039,7 +3018,7 @@ struct TickDuration assert(b == result); assert(b.to!("seconds", real)() <= 0); - foreach (U; _TypeTuple!(const TickDuration, immutable TickDuration)) + foreach (U; AliasSeq!(const TickDuration, immutable TickDuration)) { U u = TickDuration(12); static assert(!__traits(compiles, u += cast(T)TickDuration.currSystemTick)); @@ -3070,13 +3049,13 @@ struct TickDuration return TickDuration(mixin("length " ~ op ~ " rhs.length")); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { T a = TickDuration.currSystemTick; T b = TickDuration.currSystemTick; - assert((a + b).seconds > 0); + assert((a + b).usecs > 0); assert((a - b).seconds <= 0); } } @@ -3091,9 +3070,9 @@ struct TickDuration return TickDuration(-length); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { assert(-(cast(T)TickDuration(7)) == TickDuration(-7)); assert(-(cast(T)TickDuration(5)) == TickDuration(-5)); @@ -3109,26 +3088,27 @@ struct TickDuration +/ int opCmp(TickDuration rhs) @safe const pure nothrow @nogc { - return length < rhs.length ? -1 : (length == rhs.length ? 0 : 1); + return (length > rhs.length) - (length < rhs.length); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + import core.internal.traits : rvalueOf; + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { - foreach (U; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (U; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { T t = TickDuration.currSystemTick; U u = t; assert(t == u); - assert(copy(t) == u); - assert(t == copy(u)); + assert(rvalueOf(t) == u); + assert(t == rvalueOf(u)); } } - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { - foreach (U; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (U; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { T t = TickDuration.currSystemTick; U u = t + t; @@ -3137,15 +3117,15 @@ struct TickDuration assert(u > t); assert(u >= u); - assert(copy(t) < u); - assert(copy(t) <= t); - assert(copy(u) > t); - assert(copy(u) >= u); + assert(rvalueOf(t) < u); + assert(rvalueOf(t) <= t); + assert(rvalueOf(u) > t); + assert(rvalueOf(u) >= u); - assert(t < copy(u)); - assert(t <= copy(t)); - assert(u > copy(t)); - assert(u >= copy(u)); + assert(t < rvalueOf(u)); + assert(t <= rvalueOf(t)); + assert(u > rvalueOf(t)); + assert(u >= rvalueOf(u)); } } } @@ -3170,7 +3150,7 @@ struct TickDuration length = cast(long)(length * value); } - unittest + version (CoreUnittest) unittest { immutable curr = TickDuration.currSystemTick; TickDuration t1 = curr; @@ -3187,7 +3167,7 @@ struct TickDuration t1 *= 2.1; assert(t1 > t2); - foreach (T; _TypeTuple!(const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(const TickDuration, immutable TickDuration)) { T t = TickDuration.currSystemTick; assert(!__traits(compiles, t *= 12)); @@ -3221,7 +3201,7 @@ struct TickDuration length = cast(long)(length / value); } - unittest + version (CoreUnittest) unittest { immutable curr = TickDuration.currSystemTick; immutable t1 = curr; @@ -3240,7 +3220,7 @@ struct TickDuration _assertThrown!TimeException(t2 /= 0); - foreach (T; _TypeTuple!(const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(const TickDuration, immutable TickDuration)) { T t = TickDuration.currSystemTick; assert(!__traits(compiles, t /= 12)); @@ -3268,9 +3248,9 @@ struct TickDuration return TickDuration(cast(long)(length * value)); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { T t1 = TickDuration.currSystemTick; T t2 = t1 + t1; @@ -3307,9 +3287,9 @@ struct TickDuration return TickDuration(cast(long)(length / value)); } - unittest + version (CoreUnittest) unittest { - foreach (T; _TypeTuple!(TickDuration, const TickDuration, immutable TickDuration)) + foreach (T; AliasSeq!(TickDuration, const TickDuration, immutable TickDuration)) { T t1 = TickDuration.currSystemTick; T t2 = t1 + t1; @@ -3332,7 +3312,7 @@ struct TickDuration this.length = ticks; } - unittest + version (CoreUnittest) unittest { foreach (i; [-42, 0, 42]) assert(TickDuration(i).length == i); @@ -3369,10 +3349,8 @@ struct TickDuration import core.internal.abort : abort; version (Windows) { - ulong ticks; - if (QueryPerformanceCounter(cast(long*)&ticks) == 0) - abort("Failed in QueryPerformanceCounter()."); - + ulong ticks = void; + QueryPerformanceCounter(cast(long*)&ticks); return TickDuration(ticks); } else version (Darwin) @@ -3381,10 +3359,8 @@ struct TickDuration return TickDuration(cast(long)mach_absolute_time()); else { - timeval tv; - if (gettimeofday(&tv, null) != 0) - abort("Failed in gettimeofday()."); - + timeval tv = void; + gettimeofday(&tv, null); return TickDuration(tv.tv_sec * TickDuration.ticksPerSec + tv.tv_usec * TickDuration.ticksPerSec / 1000 / 1000); } @@ -3393,26 +3369,32 @@ struct TickDuration { static if (is(typeof(clock_gettime))) { - timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) - abort("Failed in clock_gettime()."); - + timespec ts = void; + immutable error = clock_gettime(CLOCK_MONOTONIC, &ts); + // CLOCK_MONOTONIC is supported and if tv_sec is long or larger + // overflow won't happen before 292 billion years A.D. + static if (ts.tv_sec.max < long.max) + { + if (error) + { + import core.internal.abort : abort; + abort("Call to clock_gettime failed."); + } + } return TickDuration(ts.tv_sec * TickDuration.ticksPerSec + ts.tv_nsec * TickDuration.ticksPerSec / 1000 / 1000 / 1000); } else { - timeval tv; - if (gettimeofday(&tv, null) != 0) - abort("Failed in gettimeofday()."); - + timeval tv = void; + gettimeofday(&tv, null); return TickDuration(tv.tv_sec * TickDuration.ticksPerSec + tv.tv_usec * TickDuration.ticksPerSec / 1000 / 1000); } } } - @safe nothrow unittest + version (CoreUnittest) @safe nothrow unittest { assert(TickDuration.currSystemTick.length > 0); } @@ -3500,13 +3482,13 @@ unittest unittest { - foreach (units; _TypeTuple!("weeks", "days", "hours", "seconds", "msecs", "usecs", "hnsecs", "nsecs")) + foreach (units; AliasSeq!("weeks", "days", "hours", "seconds", "msecs", "usecs", "hnsecs", "nsecs")) { static assert(!__traits(compiles, convert!("years", units)(12)), units); static assert(!__traits(compiles, convert!(units, "years")(12)), units); } - foreach (units; _TypeTuple!("years", "months", "weeks", "days", + foreach (units; AliasSeq!("years", "months", "weeks", "days", "hours", "seconds", "msecs", "usecs", "hnsecs", "nsecs")) { assert(convert!(units, units)(12) == 12); @@ -3568,612 +3550,6 @@ unittest assert(convert!("nsecs", "hnsecs")(100) == 1); } - -/++ - Represents fractional seconds. - - This is the portion of the time which is smaller than a second and it cannot - hold values which would be greater than or equal to a second (or less than - or equal to a negative second). - - It holds hnsecs internally, but you can create it using either milliseconds, - microseconds, or hnsecs. What it does is allow for a simple way to set or - adjust the fractional seconds portion of a $(D Duration) or a - $(REF SysTime, std,datetime) without having to worry about whether you're - dealing with milliseconds, microseconds, or hnsecs. - - $(D FracSec)'s functions which take time unit strings do accept - $(D "nsecs"), but because the resolution of $(D Duration) and - $(REF SysTime, std,datetime) is hnsecs, you don't actually get precision higher - than hnsecs. $(D "nsecs") is accepted merely for convenience. Any values - given as nsecs will be converted to hnsecs using $(D convert) (which uses - truncating division when converting to smaller units). - +/ -struct FracSec -{ -@safe pure: - -public: - - /++ - A $(D FracSec) of $(D 0). It's shorter than doing something like - $(D FracSec.from!"msecs"(0)) and more explicit than $(D FracSec.init). - +/ - static @property nothrow @nogc FracSec zero() { return FracSec(0); } - - unittest - { - assert(zero == FracSec.from!"msecs"(0)); - } - - - /++ - Create a $(D FracSec) from the given units ($(D "msecs"), $(D "usecs"), - or $(D "hnsecs")). - - Params: - units = The units to create a FracSec from. - value = The number of the given units passed the second. - - Throws: - $(D TimeException) if the given value would result in a $(D FracSec) - greater than or equal to $(D 1) second or less than or equal to - $(D -1) seconds. - +/ - static FracSec from(string units)(long value) - if (units == "msecs" || - units == "usecs" || - units == "hnsecs" || - units == "nsecs") - { - immutable hnsecs = cast(int)convert!(units, "hnsecs")(value); - _enforceValid(hnsecs); - return FracSec(hnsecs); - } - - unittest - { - assert(FracSec.from!"msecs"(0) == FracSec(0)); - assert(FracSec.from!"usecs"(0) == FracSec(0)); - assert(FracSec.from!"hnsecs"(0) == FracSec(0)); - - foreach (sign; [1, -1]) - { - _assertThrown!TimeException(from!"msecs"(1000 * sign)); - - assert(FracSec.from!"msecs"(1 * sign) == FracSec(10_000 * sign)); - assert(FracSec.from!"msecs"(999 * sign) == FracSec(9_990_000 * sign)); - - _assertThrown!TimeException(from!"usecs"(1_000_000 * sign)); - - assert(FracSec.from!"usecs"(1 * sign) == FracSec(10 * sign)); - assert(FracSec.from!"usecs"(999 * sign) == FracSec(9990 * sign)); - assert(FracSec.from!"usecs"(999_999 * sign) == FracSec(9999_990 * sign)); - - _assertThrown!TimeException(from!"hnsecs"(10_000_000 * sign)); - - assert(FracSec.from!"hnsecs"(1 * sign) == FracSec(1 * sign)); - assert(FracSec.from!"hnsecs"(999 * sign) == FracSec(999 * sign)); - assert(FracSec.from!"hnsecs"(999_999 * sign) == FracSec(999_999 * sign)); - assert(FracSec.from!"hnsecs"(9_999_999 * sign) == FracSec(9_999_999 * sign)); - - assert(FracSec.from!"nsecs"(1 * sign) == FracSec(0)); - assert(FracSec.from!"nsecs"(10 * sign) == FracSec(0)); - assert(FracSec.from!"nsecs"(99 * sign) == FracSec(0)); - assert(FracSec.from!"nsecs"(100 * sign) == FracSec(1 * sign)); - assert(FracSec.from!"nsecs"(99_999 * sign) == FracSec(999 * sign)); - assert(FracSec.from!"nsecs"(99_999_999 * sign) == FracSec(999_999 * sign)); - assert(FracSec.from!"nsecs"(999_999_999 * sign) == FracSec(9_999_999 * sign)); - } - } - - - /++ - Returns the negation of this $(D FracSec). - +/ - FracSec opUnary(string op)() const nothrow @nogc - if (op == "-") - { - return FracSec(-_hnsecs); - } - - unittest - { - foreach (val; [-7, -5, 0, 5, 7]) - { - foreach (F; _TypeTuple!(FracSec, const FracSec, immutable FracSec)) - { - F fs = FracSec(val); - assert(-fs == FracSec(-val)); - } - } - } - - - /++ - The value of this $(D FracSec) as milliseconds. - +/ - @property int msecs() const nothrow @nogc - { - return cast(int)convert!("hnsecs", "msecs")(_hnsecs); - } - - unittest - { - foreach (F; _TypeTuple!(FracSec, const FracSec, immutable FracSec)) - { - assert(FracSec(0).msecs == 0); - - foreach (sign; [1, -1]) - { - assert((cast(F)FracSec(1 * sign)).msecs == 0); - assert((cast(F)FracSec(999 * sign)).msecs == 0); - assert((cast(F)FracSec(999_999 * sign)).msecs == 99 * sign); - assert((cast(F)FracSec(9_999_999 * sign)).msecs == 999 * sign); - } - } - } - - - /++ - The value of this $(D FracSec) as milliseconds. - - Params: - milliseconds = The number of milliseconds passed the second. - - Throws: - $(D TimeException) if the given value is not less than $(D 1) second - and greater than a $(D -1) seconds. - +/ - @property void msecs(int milliseconds) - { - immutable hnsecs = cast(int)convert!("msecs", "hnsecs")(milliseconds); - _enforceValid(hnsecs); - _hnsecs = hnsecs; - } - - unittest - { - static void test(int msecs, FracSec expected = FracSec.init, size_t line = __LINE__) - { - FracSec fs; - fs.msecs = msecs; - - if (fs != expected) - throw new AssertError("unittest failure", __FILE__, line); - } - - _assertThrown!TimeException(test(-1000)); - _assertThrown!TimeException(test(1000)); - - test(0, FracSec(0)); - - foreach (sign; [1, -1]) - { - test(1 * sign, FracSec(10_000 * sign)); - test(999 * sign, FracSec(9_990_000 * sign)); - } - - foreach (F; _TypeTuple!(const FracSec, immutable FracSec)) - { - F fs = FracSec(1234567); - static assert(!__traits(compiles, fs.msecs = 12), F.stringof); - } - } - - - /++ - The value of this $(D FracSec) as microseconds. - +/ - @property int usecs() const nothrow @nogc - { - return cast(int)convert!("hnsecs", "usecs")(_hnsecs); - } - - unittest - { - foreach (F; _TypeTuple!(FracSec, const FracSec, immutable FracSec)) - { - assert(FracSec(0).usecs == 0); - - foreach (sign; [1, -1]) - { - assert((cast(F)FracSec(1 * sign)).usecs == 0); - assert((cast(F)FracSec(999 * sign)).usecs == 99 * sign); - assert((cast(F)FracSec(999_999 * sign)).usecs == 99_999 * sign); - assert((cast(F)FracSec(9_999_999 * sign)).usecs == 999_999 * sign); - } - } - } - - - /++ - The value of this $(D FracSec) as microseconds. - - Params: - microseconds = The number of microseconds passed the second. - - Throws: - $(D TimeException) if the given value is not less than $(D 1) second - and greater than a $(D -1) seconds. - +/ - @property void usecs(int microseconds) - { - immutable hnsecs = cast(int)convert!("usecs", "hnsecs")(microseconds); - _enforceValid(hnsecs); - _hnsecs = hnsecs; - } - - unittest - { - static void test(int usecs, FracSec expected = FracSec.init, size_t line = __LINE__) - { - FracSec fs; - fs.usecs = usecs; - - if (fs != expected) - throw new AssertError("unittest failure", __FILE__, line); - } - - _assertThrown!TimeException(test(-1_000_000)); - _assertThrown!TimeException(test(1_000_000)); - - test(0, FracSec(0)); - - foreach (sign; [1, -1]) - { - test(1 * sign, FracSec(10 * sign)); - test(999 * sign, FracSec(9990 * sign)); - test(999_999 * sign, FracSec(9_999_990 * sign)); - } - - foreach (F; _TypeTuple!(const FracSec, immutable FracSec)) - { - F fs = FracSec(1234567); - static assert(!__traits(compiles, fs.usecs = 12), F.stringof); - } - } - - - /++ - The value of this $(D FracSec) as hnsecs. - +/ - @property int hnsecs() const nothrow @nogc - { - return _hnsecs; - } - - unittest - { - foreach (F; _TypeTuple!(FracSec, const FracSec, immutable FracSec)) - { - assert(FracSec(0).hnsecs == 0); - - foreach (sign; [1, -1]) - { - assert((cast(F)FracSec(1 * sign)).hnsecs == 1 * sign); - assert((cast(F)FracSec(999 * sign)).hnsecs == 999 * sign); - assert((cast(F)FracSec(999_999 * sign)).hnsecs == 999_999 * sign); - assert((cast(F)FracSec(9_999_999 * sign)).hnsecs == 9_999_999 * sign); - } - } - } - - - /++ - The value of this $(D FracSec) as hnsecs. - - Params: - hnsecs = The number of hnsecs passed the second. - - Throws: - $(D TimeException) if the given value is not less than $(D 1) second - and greater than a $(D -1) seconds. - +/ - @property void hnsecs(int hnsecs) - { - _enforceValid(hnsecs); - _hnsecs = hnsecs; - } - - unittest - { - static void test(int hnsecs, FracSec expected = FracSec.init, size_t line = __LINE__) - { - FracSec fs; - fs.hnsecs = hnsecs; - - if (fs != expected) - throw new AssertError("unittest failure", __FILE__, line); - } - - _assertThrown!TimeException(test(-10_000_000)); - _assertThrown!TimeException(test(10_000_000)); - - test(0, FracSec(0)); - - foreach (sign; [1, -1]) - { - test(1 * sign, FracSec(1 * sign)); - test(999 * sign, FracSec(999 * sign)); - test(999_999 * sign, FracSec(999_999 * sign)); - test(9_999_999 * sign, FracSec(9_999_999 * sign)); - } - - foreach (F; _TypeTuple!(const FracSec, immutable FracSec)) - { - F fs = FracSec(1234567); - static assert(!__traits(compiles, fs.hnsecs = 12), F.stringof); - } - } - - - /++ - The value of this $(D FracSec) as nsecs. - - Note that this does not give you any greater precision - than getting the value of this $(D FracSec) as hnsecs. - +/ - @property int nsecs() const nothrow @nogc - { - return cast(int)convert!("hnsecs", "nsecs")(_hnsecs); - } - - unittest - { - foreach (F; _TypeTuple!(FracSec, const FracSec, immutable FracSec)) - { - assert(FracSec(0).nsecs == 0); - - foreach (sign; [1, -1]) - { - assert((cast(F)FracSec(1 * sign)).nsecs == 100 * sign); - assert((cast(F)FracSec(999 * sign)).nsecs == 99_900 * sign); - assert((cast(F)FracSec(999_999 * sign)).nsecs == 99_999_900 * sign); - assert((cast(F)FracSec(9_999_999 * sign)).nsecs == 999_999_900 * sign); - } - } - } - - - /++ - The value of this $(D FracSec) as nsecs. - - Note that this does not give you any greater precision - than setting the value of this $(D FracSec) as hnsecs. - - Params: - nsecs = The number of nsecs passed the second. - - Throws: - $(D TimeException) if the given value is not less than $(D 1) second - and greater than a $(D -1) seconds. - +/ - @property void nsecs(long nsecs) - { - immutable hnsecs = cast(int)convert!("nsecs", "hnsecs")(nsecs); - _enforceValid(hnsecs); - _hnsecs = hnsecs; - } - - unittest - { - static void test(int nsecs, FracSec expected = FracSec.init, size_t line = __LINE__) - { - FracSec fs; - fs.nsecs = nsecs; - - if (fs != expected) - throw new AssertError("unittest failure", __FILE__, line); - } - - _assertThrown!TimeException(test(-1_000_000_000)); - _assertThrown!TimeException(test(1_000_000_000)); - - test(0, FracSec(0)); - - foreach (sign; [1, -1]) - { - test(1 * sign, FracSec(0)); - test(10 * sign, FracSec(0)); - test(100 * sign, FracSec(1 * sign)); - test(999 * sign, FracSec(9 * sign)); - test(999_999 * sign, FracSec(9999 * sign)); - test(9_999_999 * sign, FracSec(99_999 * sign)); - } - - foreach (F; _TypeTuple!(const FracSec, immutable FracSec)) - { - F fs = FracSec(1234567); - static assert(!__traits(compiles, fs.nsecs = 12), F.stringof); - } - } - - - /+ - Converts this $(D TickDuration) 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 $(D TickDuration) 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(); - } - - unittest - { - auto fs = FracSec(12); - const cfs = FracSec(12); - immutable ifs = FracSec(12); - assert(fs.toString() == "12 hnsecs"); - assert(cfs.toString() == "12 hnsecs"); - assert(ifs.toString() == "12 hnsecs"); - } - - -private: - - /+ - Since we have two versions of $(D toString), we have $(D _toStringImpl) - so that they can share implementations. - +/ - string _toStringImpl() const nothrow - { - long hnsecs = _hnsecs; - - immutable milliseconds = splitUnitsFromHNSecs!"msecs"(hnsecs); - immutable microseconds = splitUnitsFromHNSecs!"usecs"(hnsecs); - - if (hnsecs == 0) - { - if (microseconds == 0) - { - if (milliseconds == 0) - return "0 hnsecs"; - else - { - if (milliseconds == 1) - return "1 ms"; - else - { - auto r = signedToTempString(milliseconds, 10).idup; - r ~= " ms"; - return r; - } - } - } - else - { - immutable fullMicroseconds = getUnitsFromHNSecs!"usecs"(_hnsecs); - - if (fullMicroseconds == 1) - return "1 μs"; - else - { - auto r = signedToTempString(fullMicroseconds, 10).idup; - r ~= " μs"; - return r; - } - } - } - else - { - if (_hnsecs == 1) - return "1 hnsec"; - else - { - auto r = signedToTempString(_hnsecs, 10).idup; - r ~= " hnsecs"; - return r; - } - } - } - - unittest - { - foreach (sign; [1 , -1]) - { - immutable signStr = sign == 1 ? "" : "-"; - - assert(FracSec.from!"msecs"(0 * sign).toString() == "0 hnsecs"); - assert(FracSec.from!"msecs"(1 * sign).toString() == signStr ~ "1 ms"); - assert(FracSec.from!"msecs"(2 * sign).toString() == signStr ~ "2 ms"); - assert(FracSec.from!"msecs"(100 * sign).toString() == signStr ~ "100 ms"); - assert(FracSec.from!"msecs"(999 * sign).toString() == signStr ~ "999 ms"); - - assert(FracSec.from!"usecs"(0* sign).toString() == "0 hnsecs"); - assert(FracSec.from!"usecs"(1* sign).toString() == signStr ~ "1 μs"); - assert(FracSec.from!"usecs"(2* sign).toString() == signStr ~ "2 μs"); - assert(FracSec.from!"usecs"(100* sign).toString() == signStr ~ "100 μs"); - assert(FracSec.from!"usecs"(999* sign).toString() == signStr ~ "999 μs"); - assert(FracSec.from!"usecs"(1000* sign).toString() == signStr ~ "1 ms"); - assert(FracSec.from!"usecs"(2000* sign).toString() == signStr ~ "2 ms"); - assert(FracSec.from!"usecs"(9999* sign).toString() == signStr ~ "9999 μs"); - assert(FracSec.from!"usecs"(10_000* sign).toString() == signStr ~ "10 ms"); - assert(FracSec.from!"usecs"(20_000* sign).toString() == signStr ~ "20 ms"); - assert(FracSec.from!"usecs"(100_000* sign).toString() == signStr ~ "100 ms"); - assert(FracSec.from!"usecs"(100_001* sign).toString() == signStr ~ "100001 μs"); - assert(FracSec.from!"usecs"(999_999* sign).toString() == signStr ~ "999999 μs"); - - assert(FracSec.from!"hnsecs"(0* sign).toString() == "0 hnsecs"); - assert(FracSec.from!"hnsecs"(1* sign).toString() == (sign == 1 ? "1 hnsec" : "-1 hnsecs")); - assert(FracSec.from!"hnsecs"(2* sign).toString() == signStr ~ "2 hnsecs"); - assert(FracSec.from!"hnsecs"(100* sign).toString() == signStr ~ "10 μs"); - assert(FracSec.from!"hnsecs"(999* sign).toString() == signStr ~ "999 hnsecs"); - assert(FracSec.from!"hnsecs"(1000* sign).toString() == signStr ~ "100 μs"); - assert(FracSec.from!"hnsecs"(2000* sign).toString() == signStr ~ "200 μs"); - assert(FracSec.from!"hnsecs"(9999* sign).toString() == signStr ~ "9999 hnsecs"); - assert(FracSec.from!"hnsecs"(10_000* sign).toString() == signStr ~ "1 ms"); - assert(FracSec.from!"hnsecs"(20_000* sign).toString() == signStr ~ "2 ms"); - assert(FracSec.from!"hnsecs"(100_000* sign).toString() == signStr ~ "10 ms"); - assert(FracSec.from!"hnsecs"(100_001* sign).toString() == signStr ~ "100001 hnsecs"); - assert(FracSec.from!"hnsecs"(200_000* sign).toString() == signStr ~ "20 ms"); - assert(FracSec.from!"hnsecs"(999_999* sign).toString() == signStr ~ "999999 hnsecs"); - assert(FracSec.from!"hnsecs"(1_000_001* sign).toString() == signStr ~ "1000001 hnsecs"); - assert(FracSec.from!"hnsecs"(9_999_999* sign).toString() == signStr ~ "9999999 hnsecs"); - } - } - - - /+ - Returns whether the given number of hnsecs fits within the range of - $(D FracSec). - - Params: - hnsecs = The number of hnsecs. - +/ - static bool _valid(int hnsecs) nothrow @nogc - { - immutable second = convert!("seconds", "hnsecs")(1); - return hnsecs > -second && hnsecs < second; - } - - - /+ - Throws: - $(D TimeException) if $(D valid(hnsecs)) is $(D false). - +/ - static void _enforceValid(int hnsecs) - { - if (!_valid(hnsecs)) - throw new TimeException("FracSec must be greater than equal to 0 and less than 1 second."); - } - - - /+ - Params: - hnsecs = The number of hnsecs passed the second. - +/ - this(int hnsecs) nothrow @nogc - { - _hnsecs = hnsecs; - } - - - invariant() - { - if (!_valid(_hnsecs)) - throw new AssertError("Invariant Failure: hnsecs [" ~ signedToTempString(_hnsecs, 10).idup ~ "]", __FILE__, __LINE__); - } - - - int _hnsecs; -} - - /++ Exception type used by core.time. +/ @@ -4318,7 +3694,6 @@ long splitUnitsFromHNSecs(string units)(ref long hnsecs) @safe pure nothrow @nog return value; } -/// unittest { auto hnsecs = 2595000000007L; @@ -4331,83 +3706,6 @@ unittest assert(hnsecs == 7); } - -/+ - This function is used to split out the units without getting the remaining - hnsecs. - - See_Also: - $(LREF splitUnitsFromHNSecs) - - 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 @nogc - if (units == "weeks" || - units == "days" || - units == "hours" || - units == "minutes" || - units == "seconds" || - units == "msecs" || - units == "usecs" || - units == "hnsecs") -{ - return convert!("hnsecs", units)(hnsecs); -} - -/// -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. - - See_Also: - $(LREF splitUnitsFromHNSecs) - - 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 @nogc - if (units == "weeks" || - units == "days" || - units == "hours" || - units == "minutes" || - units == "seconds" || - units == "msecs" || - units == "usecs" || - units == "hnsecs") -{ - immutable value = convert!("hnsecs", units)(hnsecs); - - return hnsecs - convert!(units, "hnsecs")(value); -} - -/// -unittest -{ - auto hnsecs = 2595000000007L; - auto returned = removeUnitsFromHNSecs!"days"(hnsecs); - assert(returned == 3000000007); - assert(hnsecs == 2595000000007L); -} - - /+ Whether all of the given strings are among the accepted strings. +/ @@ -4494,63 +3792,6 @@ unittest assert(!unitsAreInDescendingOrder("days", "hours", "days")); } - -/+ - The time units which are one step larger than the given units. - +/ -template nextLargerTimeUnits(string units) - if (units == "days" || - units == "hours" || - units == "minutes" || - units == "seconds" || - units == "msecs" || - units == "usecs" || - units == "hnsecs" || - units == "nsecs") -{ - static if (units == "days") - enum nextLargerTimeUnits = "weeks"; - else static if (units == "hours") - enum nextLargerTimeUnits = "days"; - else static if (units == "minutes") - enum nextLargerTimeUnits = "hours"; - else static if (units == "seconds") - enum nextLargerTimeUnits = "minutes"; - else static if (units == "msecs") - enum nextLargerTimeUnits = "seconds"; - else static if (units == "usecs") - enum nextLargerTimeUnits = "msecs"; - else static if (units == "hnsecs") - enum nextLargerTimeUnits = "usecs"; - else static if (units == "nsecs") - enum nextLargerTimeUnits = "hnsecs"; - else - static assert(0, "Broken template constraint"); -} - -/// -unittest -{ - assert(nextLargerTimeUnits!"minutes" == "hours"); - assert(nextLargerTimeUnits!"hnsecs" == "usecs"); -} - -unittest -{ - assert(nextLargerTimeUnits!"nsecs" == "hnsecs"); - assert(nextLargerTimeUnits!"hnsecs" == "usecs"); - assert(nextLargerTimeUnits!"usecs" == "msecs"); - assert(nextLargerTimeUnits!"msecs" == "seconds"); - assert(nextLargerTimeUnits!"seconds" == "minutes"); - assert(nextLargerTimeUnits!"minutes" == "hours"); - assert(nextLargerTimeUnits!"hours" == "days"); - assert(nextLargerTimeUnits!"days" == "weeks"); - - static assert(!__traits(compiles, nextLargerTimeUnits!"weeks")); - static assert(!__traits(compiles, nextLargerTimeUnits!"months")); - static assert(!__traits(compiles, nextLargerTimeUnits!"years")); -} - version (Darwin) long machTicksPerSecond() { @@ -4581,16 +3822,16 @@ double _abs(double val) @safe pure nothrow @nogc } -version (unittest) +version (CoreUnittest) string doubleToString(double value) @safe pure nothrow { string result; if (value < 0 && cast(long)value == 0) result = "-0"; else - result = signedToTempString(cast(long)value, 10).idup; + result = signedToTempString(cast(long)value).idup; result ~= '.'; - result ~= unsignedToTempString(cast(ulong)(_abs((value - cast(long)value) * 1_000_000) + .5), 10); + result ~= unsignedToTempString(cast(ulong)(_abs((value - cast(long)value) * 1_000_000) + .5)); while (result[$-1] == '0') result = result[0 .. $-1]; @@ -4612,21 +3853,17 @@ unittest assert(aStr == "-0.337", aStr); } -version (unittest) const(char)* numToStringz()(long value) @trusted pure nothrow +version (CoreUnittest) const(char)* numToStringz()(long value) @trusted pure nothrow { - return (signedToTempString(value, 10) ~ "\0").ptr; + return (signedToTempString(value) ~ "\0").ptr; } -/+ A copy of std.typecons.TypeTuple. +/ -template _TypeTuple(TList...) -{ - alias TList _TypeTuple; -} +import core.internal.traits : AliasSeq; /+ An adjusted copy of std.exception.assertThrown. +/ -version (unittest) void _assertThrown(T : Throwable = Exception, E) +version (CoreUnittest) void _assertThrown(T : Throwable = Exception, E) (lazy E expression, string msg = null, string file = __FILE__, @@ -4721,7 +3958,7 @@ unittest } -version (unittest) void assertApprox(D, E)(D actual, +version (CoreUnittest) void assertApprox(D, E)(D actual, E lower, E upper, string msg = "unittest failure", @@ -4734,7 +3971,7 @@ version (unittest) void assertApprox(D, E)(D actual, throw new AssertError(msg ~ ": upper: " ~ actual.toString(), __FILE__, line); } -version (unittest) void assertApprox(D, E)(D actual, +version (CoreUnittest) void assertApprox(D, E)(D actual, E lower, E upper, string msg = "unittest failure", @@ -4743,14 +3980,14 @@ version (unittest) void assertApprox(D, E)(D actual, { if (actual.length < lower.length || actual.length > upper.length) { - throw new AssertError(msg ~ (": [" ~ signedToTempString(lower.length, 10) ~ "] [" ~ - signedToTempString(actual.length, 10) ~ "] [" ~ - signedToTempString(upper.length, 10) ~ "]").idup, + throw new AssertError(msg ~ (": [" ~ signedToTempString(lower.length) ~ "] [" ~ + signedToTempString(actual.length) ~ "] [" ~ + signedToTempString(upper.length) ~ "]").idup, __FILE__, line); } } -version (unittest) void assertApprox(MT)(MT actual, +version (CoreUnittest) void assertApprox(MT)(MT actual, MT lower, MT upper, string msg = "unittest failure", @@ -4760,14 +3997,14 @@ version (unittest) void assertApprox(MT)(MT actual, assertApprox(actual._ticks, lower._ticks, upper._ticks, msg, line); } -version (unittest) void assertApprox()(long actual, +version (CoreUnittest) void assertApprox()(long actual, long lower, long upper, string msg = "unittest failure", size_t line = __LINE__) { if (actual < lower) - throw new AssertError(msg ~ ": lower: " ~ signedToTempString(actual, 10).idup, __FILE__, line); + throw new AssertError(msg ~ ": lower: " ~ signedToTempString(actual).idup, __FILE__, line); if (actual > upper) - throw new AssertError(msg ~ ": upper: " ~ signedToTempString(actual, 10).idup, __FILE__, line); + throw new AssertError(msg ~ ": upper: " ~ signedToTempString(actual).idup, __FILE__, line); } diff --git a/libphobos/libdruntime/gc/bits.d b/libphobos/libdruntime/gc/bits.d deleted file mode 100644 index 60ba4f4..0000000 --- a/libphobos/libdruntime/gc/bits.d +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Contains a bitfield used by the GC. - * - * Copyright: Copyright Digital Mars 2005 - 2013. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, David Friedman, Sean Kelly - */ - -/* Copyright Digital Mars 2005 - 2013. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module gc.bits; - - -import core.bitop; -import core.stdc.string; -import core.stdc.stdlib; -import core.exception : onOutOfMemoryError; - -struct GCBits -{ - alias size_t wordtype; - - enum BITS_PER_WORD = (wordtype.sizeof * 8); - enum BITS_SHIFT = (wordtype.sizeof == 8 ? 6 : 5); - enum BITS_MASK = (BITS_PER_WORD - 1); - enum BITS_1 = cast(wordtype)1; - - wordtype* data; - size_t nbits; - - void Dtor() nothrow - { - if (data) - { - free(data); - data = null; - } - } - - void alloc(size_t nbits) nothrow - { - this.nbits = nbits; - data = cast(typeof(data[0])*)calloc(nwords, data[0].sizeof); - if (!data) - onOutOfMemoryError(); - } - - wordtype test(size_t i) const nothrow - in - { - assert(i < nbits); - } - body - { - return core.bitop.bt(data, i); - } - - int set(size_t i) nothrow - in - { - assert(i < nbits); - } - body - { - return core.bitop.bts(data, i); - } - - int clear(size_t i) nothrow - in - { - assert(i <= nbits); - } - body - { - return core.bitop.btr(data, i); - } - - void zero() nothrow - { - memset(data, 0, nwords * wordtype.sizeof); - } - - void copy(GCBits *f) nothrow - in - { - assert(nwords == f.nwords); - } - body - { - memcpy(data, f.data, nwords * wordtype.sizeof); - } - - @property size_t nwords() const pure nothrow - { - return (nbits + (BITS_PER_WORD - 1)) >> BITS_SHIFT; - } -} - -unittest -{ - GCBits b; - - b.alloc(786); - assert(!b.test(123)); - assert(!b.clear(123)); - assert(!b.set(123)); - assert(b.test(123)); - assert(b.clear(123)); - assert(!b.test(123)); - - b.set(785); - b.set(0); - assert(b.test(785)); - assert(b.test(0)); - b.zero(); - assert(!b.test(785)); - assert(!b.test(0)); - - GCBits b2; - b2.alloc(786); - b2.set(38); - b.copy(&b2); - assert(b.test(38)); - b2.Dtor(); - b.Dtor(); -} diff --git a/libphobos/libdruntime/gc/config.d b/libphobos/libdruntime/gc/config.d deleted file mode 100644 index b0789cd..0000000 --- a/libphobos/libdruntime/gc/config.d +++ /dev/null @@ -1,291 +0,0 @@ -/** -* Contains the garbage collector configuration. -* -* Copyright: Copyright Digital Mars 2016 -* License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). -*/ - -module gc.config; - -import core.stdc.stdlib; -import core.stdc.stdio; -import core.stdc.ctype; -import core.stdc.string; -import core.vararg; - -nothrow @nogc: -extern extern(C) string[] rt_args(); - -extern extern(C) __gshared bool rt_envvars_enabled; -extern extern(C) __gshared bool rt_cmdline_enabled; -extern extern(C) __gshared string[] rt_options; - -__gshared Config config; - -struct Config -{ - bool disable; // start disabled - ubyte profile; // enable profiling with summary when terminating program - string gc = "conservative"; // select gc implementation conservative|manual - - size_t initReserve; // initial reserve (MB) - size_t minPoolSize = 1; // initial and minimum pool size (MB) - size_t maxPoolSize = 64; // maximum pool size (MB) - size_t incPoolSize = 3; // pool size increment (MB) - float heapSizeFactor = 2.0; // heap size to used memory ratio - -@nogc nothrow: - - bool initialize() - { - import core.internal.traits : externDFunc; - - alias rt_configCallBack = string delegate(string) @nogc nothrow; - alias fn_configOption = string function(string opt, scope rt_configCallBack dg, bool reverse) @nogc nothrow; - - alias rt_configOption = externDFunc!("rt.config.rt_configOption", fn_configOption); - - string parse(string opt) @nogc nothrow - { - if (!parseOptions(opt)) - return "err"; - return null; // continue processing - } - string s = rt_configOption("gcopt", &parse, true); - return s is null; - } - - void help() - { - version (unittest) if (inUnittest) return; - - string s = "GC options are specified as white space separated assignments: - disable:0|1 - start disabled (%d) - profile:0|1|2 - enable profiling with summary when terminating program (%d) - gc:conservative|manual - select gc implementation (default = conservative) - - initReserve:N - initial memory to reserve in MB (%lld) - minPoolSize:N - initial and minimum pool size in MB (%lld) - maxPoolSize:N - maximum pool size in MB (%lld) - incPoolSize:N - pool size increment MB (%lld) - heapSizeFactor:N - targeted heap size to used memory ratio (%g) -"; - printf(s.ptr, disable, profile, cast(long)initReserve, cast(long)minPoolSize, - cast(long)maxPoolSize, cast(long)incPoolSize, heapSizeFactor); - } - - bool parseOptions(string opt) - { - opt = skip!isspace(opt); - while (opt.length) - { - auto tail = find!(c => c == ':' || c == '=' || c == ' ')(opt); - auto name = opt[0 .. $ - tail.length]; - if (name == "help") - { - help(); - opt = skip!isspace(tail); - continue; - } - if (tail.length <= 1 || tail[0] == ' ') - return optError("Missing argument for", name); - tail = tail[1 .. $]; - - switch (name) - { - foreach (field; __traits(allMembers, Config)) - { - static if (!is(typeof(__traits(getMember, this, field)) == function)) - { - case field: - if (!parse(name, tail, __traits(getMember, this, field))) - return false; - break; - } - } - break; - - default: - return optError("Unknown", name); - } - opt = skip!isspace(tail); - } - return true; - } -} - -private: - -bool optError(in char[] msg, in char[] name) -{ - version (unittest) if (inUnittest) return false; - - fprintf(stderr, "%.*s GC option '%.*s'.\n", - cast(int)msg.length, msg.ptr, - cast(int)name.length, name.ptr); - return false; -} - -inout(char)[] skip(alias pred)(inout(char)[] str) -{ - return find!(c => !pred(c))(str); -} - -inout(char)[] find(alias pred)(inout(char)[] str) -{ - foreach (i; 0 .. str.length) - if (pred(str[i])) return str[i .. $]; - return null; -} - -bool parse(T:size_t)(const(char)[] optname, ref inout(char)[] str, ref T res) -in { assert(str.length); } -body -{ - size_t i, v; - for (; i < str.length && isdigit(str[i]); ++i) - v = 10 * v + str[i] - '0'; - - if (!i) - return parseError("a number", optname, str); - if (v > res.max) - return parseError("a number " ~ T.max.stringof ~ " or below", optname, str[0 .. i]); - str = str[i .. $]; - res = cast(T) v; - return true; -} - -bool parse(const(char)[] optname, ref inout(char)[] str, ref bool res) -in { assert(str.length); } -body -{ - if (str[0] == '1' || str[0] == 'y' || str[0] == 'Y') - res = true; - else if (str[0] == '0' || str[0] == 'n' || str[0] == 'N') - res = false; - else - return parseError("'0/n/N' or '1/y/Y'", optname, str); - str = str[1 .. $]; - return true; -} - -bool parse(const(char)[] optname, ref inout(char)[] str, ref float res) -in { assert(str.length); } -body -{ - // % uint f %n \0 - char[1 + 10 + 1 + 2 + 1] fmt=void; - // specify max-width - immutable n = snprintf(fmt.ptr, fmt.length, "%%%uf%%n", cast(uint)str.length); - assert(n > 4 && n < fmt.length); - - int nscanned; - version (CRuntime_DigitalMars) - { - /* Older sscanf's in snn.lib can write to its first argument, causing a crash - * if the string is in readonly memory. Recent updates to DMD - * https://github.com/dlang/dmd/pull/6546 - * put string literals in readonly memory. - * Although sscanf has been fixed, - * http://ftp.digitalmars.com/snn.lib - * this workaround is here so it still works with the older snn.lib. - */ - // Create mutable copy of str - const length = str.length; - char* mptr = cast(char*)malloc(length + 1); - assert(mptr); - memcpy(mptr, str.ptr, length); - mptr[length] = 0; - const result = sscanf(mptr, fmt.ptr, &res, &nscanned); - free(mptr); - if (result < 1) - return parseError("a float", optname, str); - } - else - { - if (sscanf(str.ptr, fmt.ptr, &res, &nscanned) < 1) - return parseError("a float", optname, str); - } - str = str[nscanned .. $]; - return true; -} - -bool parse(const(char)[] optname, ref inout(char)[] str, ref inout(char)[] res) -in { assert(str.length); } -body -{ - auto tail = str.find!(c => c == ':' || c == '=' || c == ' '); - res = str[0 .. $ - tail.length]; - if (!res.length) - return parseError("an identifier", optname, str); - str = tail; - return true; -} - -bool parseError(in char[] exp, in char[] opt, in char[] got) -{ - version (unittest) if (inUnittest) return false; - - fprintf(stderr, "Expecting %.*s as argument for GC option '%.*s', got '%.*s' instead.\n", - cast(int)exp.length, exp.ptr, - cast(int)opt.length, opt.ptr, - cast(int)got.length, got.ptr); - return false; -} - -size_t min(size_t a, size_t b) { return a <= b ? a : b; } - -version (unittest) __gshared bool inUnittest; - -unittest -{ - inUnittest = true; - scope (exit) inUnittest = false; - - Config conf; - assert(!conf.parseOptions("disable")); - assert(!conf.parseOptions("disable:")); - assert(!conf.parseOptions("disable:5")); - assert(conf.parseOptions("disable:y") && conf.disable); - assert(conf.parseOptions("disable:n") && !conf.disable); - assert(conf.parseOptions("disable:Y") && conf.disable); - assert(conf.parseOptions("disable:N") && !conf.disable); - assert(conf.parseOptions("disable:1") && conf.disable); - assert(conf.parseOptions("disable:0") && !conf.disable); - - assert(conf.parseOptions("disable=y") && conf.disable); - assert(conf.parseOptions("disable=n") && !conf.disable); - - assert(conf.parseOptions("profile=0") && conf.profile == 0); - assert(conf.parseOptions("profile=1") && conf.profile == 1); - assert(conf.parseOptions("profile=2") && conf.profile == 2); - assert(!conf.parseOptions("profile=256")); - - assert(conf.parseOptions("disable:1 minPoolSize:16")); - assert(conf.disable); - assert(conf.minPoolSize == 16); - - assert(conf.parseOptions("heapSizeFactor:3.1")); - assert(conf.heapSizeFactor == 3.1f); - assert(conf.parseOptions("heapSizeFactor:3.1234567890 disable:0")); - assert(conf.heapSizeFactor > 3.123f); - assert(!conf.disable); - assert(!conf.parseOptions("heapSizeFactor:3.0.2.5")); - assert(conf.parseOptions("heapSizeFactor:2")); - assert(conf.heapSizeFactor == 2.0f); - - assert(!conf.parseOptions("initReserve:foo")); - assert(!conf.parseOptions("initReserve:y")); - assert(!conf.parseOptions("initReserve:20.5")); - - assert(conf.parseOptions("help")); - assert(conf.parseOptions("help profile:1")); - assert(conf.parseOptions("help profile:1 help")); - - assert(conf.parseOptions("gc:manual") && conf.gc == "manual"); - assert(conf.parseOptions("gc:my-gc~modified") && conf.gc == "my-gc~modified"); - assert(conf.parseOptions("gc:conservative help profile:1") && conf.gc == "conservative" && conf.profile == 1); - - // the config parse doesn't know all available GC names, so should accept unknown ones - assert(conf.parseOptions("gc:whatever")); -} diff --git a/libphobos/libdruntime/gc/gcinterface.d b/libphobos/libdruntime/gc/gcinterface.d deleted file mode 100644 index abe88f1..0000000 --- a/libphobos/libdruntime/gc/gcinterface.d +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Contains the internal GC interface. - * - * Copyright: Copyright Digital Mars 2016. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, Sean Kelly, Jeremy DeHaan - */ - - /* Copyright Digital Mars 2016. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module gc.gcinterface; - -static import core.memory; -alias BlkAttr = core.memory.GC.BlkAttr; -alias BlkInfo = core.memory.GC.BlkInfo; - -alias RootIterator = int delegate(scope int delegate(ref Root) nothrow dg); -alias RangeIterator = int delegate(scope int delegate(ref Range) nothrow dg); - - -struct Root -{ - void* proot; - alias proot this; -} - -struct Range -{ - void* pbot; - void* ptop; - TypeInfo ti; // should be tail const, but doesn't exist for references - alias pbot this; // only consider pbot for relative ordering (opCmp) -} - -interface GC -{ - - /* - * - */ - void Dtor(); - - /** - * - */ - void enable(); - - /** - * - */ - void disable(); - - /** - * - */ - void collect() nothrow; - - /** - * - */ - void collectNoStack() nothrow; - - /** - * minimize free space usage - */ - void minimize() nothrow; - - /** - * - */ - uint getAttr(void* p) nothrow; - - /** - * - */ - uint setAttr(void* p, uint mask) nothrow; - - /** - * - */ - uint clrAttr(void* p, uint mask) nothrow; - - /** - * - */ - void* malloc(size_t size, uint bits, const TypeInfo ti) nothrow; - - /* - * - */ - BlkInfo qalloc(size_t size, uint bits, const TypeInfo ti) nothrow; - - /* - * - */ - void* calloc(size_t size, uint bits, const TypeInfo ti) nothrow; - - /* - * - */ - void* realloc(void* p, size_t size, uint bits, const TypeInfo ti) nothrow; - - /** - * Attempt to in-place enlarge the memory block pointed to by p by at least - * minsize bytes, up to a maximum of maxsize additional bytes. - * This does not attempt to move the memory block (like realloc() does). - * - * Returns: - * 0 if could not extend p, - * total size of entire memory block if successful. - */ - size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow; - - /** - * - */ - size_t reserve(size_t size) nothrow; - - /** - * - */ - void free(void* p) nothrow; - - /** - * Determine the base address of the block containing p. If p is not a gc - * allocated pointer, return null. - */ - void* addrOf(void* p) nothrow; - - /** - * Determine the allocated size of pointer p. If p is an interior pointer - * or not a gc allocated pointer, return 0. - */ - size_t sizeOf(void* p) nothrow; - - /** - * Determine the base address of the block containing p. If p is not a gc - * allocated pointer, return null. - */ - BlkInfo query(void* p) nothrow; - - /** - * Retrieve statistics about garbage collection. - * Useful for debugging and tuning. - */ - core.memory.GC.Stats stats() nothrow; - - /** - * add p to list of roots - */ - void addRoot(void* p) nothrow @nogc; - - /** - * remove p from list of roots - */ - void removeRoot(void* p) nothrow @nogc; - - /** - * - */ - @property RootIterator rootIter() @nogc; - - /** - * add range to scan for roots - */ - void addRange(void* p, size_t sz, const TypeInfo ti) nothrow @nogc; - - /** - * remove range - */ - void removeRange(void* p) nothrow @nogc; - - /** - * - */ - @property RangeIterator rangeIter() @nogc; - - /** - * run finalizers - */ - void runFinalizers(in void[] segment) nothrow; - - /* - * - */ - bool inFinalizer() nothrow; -} diff --git a/libphobos/libdruntime/gc/impl/conservative/gc.d b/libphobos/libdruntime/gc/impl/conservative/gc.d deleted file mode 100644 index 300a32a..0000000 --- a/libphobos/libdruntime/gc/impl/conservative/gc.d +++ /dev/null @@ -1,3413 +0,0 @@ -/** - * Contains the garbage collector implementation. - * - * Copyright: Copyright Digital Mars 2001 - 2016. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, David Friedman, Sean Kelly - */ - -/* Copyright Digital Mars 2005 - 2016. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module gc.impl.conservative.gc; - -// D Programming Language Garbage Collector implementation - -/************** Debugging ***************************/ - -//debug = PRINTF; // turn on printf's -//debug = COLLECT_PRINTF; // turn on printf's -//debug = PRINTF_TO_FILE; // redirect printf's ouptut to file "gcx.log" -//debug = LOGGING; // log allocations / frees -//debug = MEMSTOMP; // stomp on memory -//debug = SENTINEL; // add underrun/overrrun protection - // NOTE: this needs to be enabled globally in the makefiles - // (-debug=SENTINEL) to pass druntime's unittests. -//debug = PTRCHECK; // more pointer checking -//debug = PTRCHECK2; // thorough but slow pointer checking -//debug = INVARIANT; // enable invariants -//debug = PROFILE_API; // profile API calls for config.profile > 1 - -/***************************************************/ - -import gc.bits; -import gc.os; -import gc.config; -import gc.gcinterface; - -import rt.util.container.treap; - -import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc; -import core.stdc.string : memcpy, memset, memmove; -import core.bitop; -import core.thread; -static import core.memory; - -version (GNU) import gcc.builtins; - -debug (PRINTF_TO_FILE) import core.stdc.stdio : sprintf, fprintf, fopen, fflush, FILE; -else import core.stdc.stdio : sprintf, printf; // needed to output profiling results - -import core.time; -alias currTime = MonoTime.currTime; - -debug(PRINTF_TO_FILE) -{ - private __gshared MonoTime gcStartTick; - private __gshared FILE* gcx_fh; - - private int printf(ARGS...)(const char* fmt, ARGS args) nothrow - { - if (!gcx_fh) - gcx_fh = fopen("gcx.log", "w"); - if (!gcx_fh) - return 0; - - int len; - if (MonoTime.ticksPerSecond == 0) - { - len = fprintf(gcx_fh, "before init: "); - } - else - { - if (gcStartTick == MonoTime.init) - gcStartTick = MonoTime.currTime; - immutable timeElapsed = MonoTime.currTime - gcStartTick; - immutable secondsAsDouble = timeElapsed.total!"hnsecs" / cast(double)convert!("seconds", "hnsecs")(1); - len = fprintf(gcx_fh, "%10.6f: ", secondsAsDouble); - } - len += fprintf(gcx_fh, fmt, args); - fflush(gcx_fh); - return len; - } -} - -debug(PRINTF) void printFreeInfo(Pool* pool) nothrow -{ - uint nReallyFree; - foreach (i; 0..pool.npages) { - if (pool.pagetable[i] >= B_FREE) nReallyFree++; - } - - printf("Pool %p: %d really free, %d supposedly free\n", pool, nReallyFree, pool.freepages); -} - -// Track total time spent preparing for GC, -// marking, sweeping and recovering pages. -__gshared Duration prepTime; -__gshared Duration markTime; -__gshared Duration sweepTime; -__gshared Duration recoverTime; -__gshared Duration maxPauseTime; -__gshared size_t numCollections; -__gshared size_t maxPoolMemory; - -__gshared long numMallocs; -__gshared long numFrees; -__gshared long numReallocs; -__gshared long numExtends; -__gshared long numOthers; -__gshared long mallocTime; // using ticks instead of MonoTime for better performance -__gshared long freeTime; -__gshared long reallocTime; -__gshared long extendTime; -__gshared long otherTime; -__gshared long lockTime; - -private -{ - extern (C) - { - // to allow compilation of this module without access to the rt package, - // make these functions available from rt.lifetime - void rt_finalizeFromGC(void* p, size_t size, uint attr) nothrow; - int rt_hasFinalizerInSegment(void* p, size_t size, uint attr, in void[] segment) nothrow; - - // Declared as an extern instead of importing core.exception - // to avoid inlining - see issue 13725. - void onInvalidMemoryOperationError() @nogc nothrow; - void onOutOfMemoryErrorNoGC() @nogc nothrow; - } - - enum - { - OPFAIL = ~cast(size_t)0 - } -} - - -alias GC gc_t; - - -/* ======================= Leak Detector =========================== */ - - -debug (LOGGING) -{ - struct Log - { - void* p; - size_t size; - size_t line; - char* file; - void* parent; - - void print() nothrow - { - printf(" p = %p, size = %zd, parent = %p ", p, size, parent); - if (file) - { - printf("%s(%u)", file, cast(uint)line); - } - printf("\n"); - } - } - - - struct LogArray - { - size_t dim; - size_t allocdim; - Log *data; - - void Dtor() nothrow - { - if (data) - cstdlib.free(data); - data = null; - } - - void reserve(size_t nentries) nothrow - { - assert(dim <= allocdim); - if (allocdim - dim < nentries) - { - allocdim = (dim + nentries) * 2; - assert(dim + nentries <= allocdim); - if (!data) - { - data = cast(Log*)cstdlib.malloc(allocdim * Log.sizeof); - if (!data && allocdim) - onOutOfMemoryErrorNoGC(); - } - else - { Log *newdata; - - newdata = cast(Log*)cstdlib.malloc(allocdim * Log.sizeof); - if (!newdata && allocdim) - onOutOfMemoryErrorNoGC(); - memcpy(newdata, data, dim * Log.sizeof); - cstdlib.free(data); - data = newdata; - } - } - } - - - void push(Log log) nothrow - { - reserve(1); - data[dim++] = log; - } - - void remove(size_t i) nothrow - { - memmove(data + i, data + i + 1, (dim - i) * Log.sizeof); - dim--; - } - - - size_t find(void *p) nothrow - { - for (size_t i = 0; i < dim; i++) - { - if (data[i].p == p) - return i; - } - return OPFAIL; // not found - } - - - void copy(LogArray *from) nothrow - { - reserve(from.dim - dim); - assert(from.dim <= allocdim); - memcpy(data, from.data, from.dim * Log.sizeof); - dim = from.dim; - } - } -} - - -/* ============================ GC =============================== */ - -class ConservativeGC : GC -{ - // For passing to debug code (not thread safe) - __gshared size_t line; - __gshared char* file; - - Gcx *gcx; // implementation - - import core.internal.spinlock; - static gcLock = shared(AlignedSpinLock)(SpinLock.Contention.lengthy); - static bool _inFinalizer; - - // lock GC, throw InvalidMemoryOperationError on recursive locking during finalization - static void lockNR() @nogc nothrow - { - if (_inFinalizer) - onInvalidMemoryOperationError(); - gcLock.lock(); - } - - - static void initialize(ref GC gc) - { - import core.stdc.string: memcpy; - - if (config.gc != "conservative") - return; - - auto p = cstdlib.malloc(__traits(classInstanceSize,ConservativeGC)); - - if (!p) - onOutOfMemoryErrorNoGC(); - - auto init = typeid(ConservativeGC).initializer(); - assert(init.length == __traits(classInstanceSize, ConservativeGC)); - auto instance = cast(ConservativeGC) memcpy(p, init.ptr, init.length); - instance.__ctor(); - - gc = instance; - } - - - static void finalize(ref GC gc) - { - if (config.gc != "conservative") - return; - - auto instance = cast(ConservativeGC) gc; - instance.Dtor(); - cstdlib.free(cast(void*)instance); - } - - - this() - { - //config is assumed to have already been initialized - - gcx = cast(Gcx*)cstdlib.calloc(1, Gcx.sizeof); - if (!gcx) - onOutOfMemoryErrorNoGC(); - gcx.initialize(); - - if (config.initReserve) - gcx.reserve(config.initReserve << 20); - if (config.disable) - gcx.disabled++; - } - - - void Dtor() - { - version (linux) - { - //debug(PRINTF) printf("Thread %x ", pthread_self()); - //debug(PRINTF) printf("GC.Dtor()\n"); - } - - if (gcx) - { - gcx.Dtor(); - cstdlib.free(gcx); - gcx = null; - } - } - - - void enable() - { - static void go(Gcx* gcx) nothrow - { - assert(gcx.disabled > 0); - gcx.disabled--; - } - runLocked!(go, otherTime, numOthers)(gcx); - } - - - void disable() - { - static void go(Gcx* gcx) nothrow - { - gcx.disabled++; - } - runLocked!(go, otherTime, numOthers)(gcx); - } - - - auto runLocked(alias func, Args...)(auto ref Args args) - { - debug(PROFILE_API) immutable tm = (config.profile > 1 ? currTime.ticks : 0); - lockNR(); - scope (failure) gcLock.unlock(); - debug(PROFILE_API) immutable tm2 = (config.profile > 1 ? currTime.ticks : 0); - - static if (is(typeof(func(args)) == void)) - func(args); - else - auto res = func(args); - - debug(PROFILE_API) if (config.profile > 1) - lockTime += tm2 - tm; - gcLock.unlock(); - - static if (!is(typeof(func(args)) == void)) - return res; - } - - - auto runLocked(alias func, alias time, alias count, Args...)(auto ref Args args) - { - debug(PROFILE_API) immutable tm = (config.profile > 1 ? currTime.ticks : 0); - lockNR(); - scope (failure) gcLock.unlock(); - debug(PROFILE_API) immutable tm2 = (config.profile > 1 ? currTime.ticks : 0); - - static if (is(typeof(func(args)) == void)) - func(args); - else - auto res = func(args); - - debug(PROFILE_API) if (config.profile > 1) - { - count++; - immutable now = currTime.ticks; - lockTime += tm2 - tm; - time += now - tm2; - } - gcLock.unlock(); - - static if (!is(typeof(func(args)) == void)) - return res; - } - - - uint getAttr(void* p) nothrow - { - if (!p) - { - return 0; - } - - static uint go(Gcx* gcx, void* p) nothrow - { - Pool* pool = gcx.findPool(p); - uint oldb = 0; - - if (pool) - { - p = sentinel_sub(p); - auto biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; - - oldb = pool.getBits(biti); - } - return oldb; - } - - return runLocked!(go, otherTime, numOthers)(gcx, p); - } - - - uint setAttr(void* p, uint mask) nothrow - { - if (!p) - { - return 0; - } - - static uint go(Gcx* gcx, void* p, uint mask) nothrow - { - Pool* pool = gcx.findPool(p); - uint oldb = 0; - - if (pool) - { - p = sentinel_sub(p); - auto biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; - - oldb = pool.getBits(biti); - pool.setBits(biti, mask); - } - return oldb; - } - - return runLocked!(go, otherTime, numOthers)(gcx, p, mask); - } - - - uint clrAttr(void* p, uint mask) nothrow - { - if (!p) - { - return 0; - } - - static uint go(Gcx* gcx, void* p, uint mask) nothrow - { - Pool* pool = gcx.findPool(p); - uint oldb = 0; - - if (pool) - { - p = sentinel_sub(p); - auto biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; - - oldb = pool.getBits(biti); - pool.clrBits(biti, mask); - } - return oldb; - } - - return runLocked!(go, otherTime, numOthers)(gcx, p, mask); - } - - - void *malloc(size_t size, uint bits, const TypeInfo ti) nothrow - { - if (!size) - { - return null; - } - - size_t localAllocSize = void; - - auto p = runLocked!(mallocNoSync, mallocTime, numMallocs)(size, bits, localAllocSize, ti); - - if (!(bits & BlkAttr.NO_SCAN)) - { - memset(p + size, 0, localAllocSize - size); - } - - return p; - } - - - // - // - // - private void *mallocNoSync(size_t size, uint bits, ref size_t alloc_size, const TypeInfo ti = null) nothrow - { - assert(size != 0); - - //debug(PRINTF) printf("GC::malloc(size = %d, gcx = %p)\n", size, gcx); - assert(gcx); - //debug(PRINTF) printf("gcx.self = %x, pthread_self() = %x\n", gcx.self, pthread_self()); - - auto p = gcx.alloc(size + SENTINEL_EXTRA, alloc_size, bits); - if (!p) - onOutOfMemoryErrorNoGC(); - - debug (SENTINEL) - { - p = sentinel_add(p); - sentinel_init(p, size); - alloc_size = size; - } - gcx.log_malloc(p, size); - - return p; - } - - - BlkInfo qalloc( size_t size, uint bits, const TypeInfo ti) nothrow - { - - if (!size) - { - return BlkInfo.init; - } - - BlkInfo retval; - - retval.base = runLocked!(mallocNoSync, mallocTime, numMallocs)(size, bits, retval.size, ti); - - if (!(bits & BlkAttr.NO_SCAN)) - { - memset(retval.base + size, 0, retval.size - size); - } - - retval.attr = bits; - return retval; - } - - - void *calloc(size_t size, uint bits, const TypeInfo ti) nothrow - { - if (!size) - { - return null; - } - - size_t localAllocSize = void; - - auto p = runLocked!(mallocNoSync, mallocTime, numMallocs)(size, bits, localAllocSize, ti); - - memset(p, 0, size); - if (!(bits & BlkAttr.NO_SCAN)) - { - memset(p + size, 0, localAllocSize - size); - } - - return p; - } - - - void *realloc(void *p, size_t size, uint bits, const TypeInfo ti) nothrow - { - size_t localAllocSize = void; - auto oldp = p; - - p = runLocked!(reallocNoSync, mallocTime, numMallocs)(p, size, bits, localAllocSize, ti); - - if (p !is oldp && !(bits & BlkAttr.NO_SCAN)) - { - memset(p + size, 0, localAllocSize - size); - } - - return p; - } - - - // - // bits will be set to the resulting bits of the new block - // - private void *reallocNoSync(void *p, size_t size, ref uint bits, ref size_t alloc_size, const TypeInfo ti = null) nothrow - { - if (!size) - { if (p) - { freeNoSync(p); - p = null; - } - alloc_size = 0; - } - else if (!p) - { - p = mallocNoSync(size, bits, alloc_size, ti); - } - else - { void *p2; - size_t psize; - - //debug(PRINTF) printf("GC::realloc(p = %p, size = %zu)\n", p, size); - debug (SENTINEL) - { - sentinel_Invariant(p); - psize = *sentinel_size(p); - if (psize != size) - { - if (psize) - { - Pool *pool = gcx.findPool(p); - - if (pool) - { - auto biti = cast(size_t)(sentinel_sub(p) - pool.baseAddr) >> pool.shiftBy; - - if (bits) - { - pool.clrBits(biti, ~BlkAttr.NONE); - pool.setBits(biti, bits); - } - else - { - bits = pool.getBits(biti); - } - } - } - p2 = mallocNoSync(size, bits, alloc_size, ti); - if (psize < size) - size = psize; - //debug(PRINTF) printf("\tcopying %d bytes\n",size); - memcpy(p2, p, size); - p = p2; - } - } - else - { - auto pool = gcx.findPool(p); - if (pool.isLargeObject) - { - auto lpool = cast(LargeObjectPool*) pool; - psize = lpool.getSize(p); // get allocated size - - if (size <= PAGESIZE / 2) - goto Lmalloc; // switching from large object pool to small object pool - - auto psz = psize / PAGESIZE; - auto newsz = (size + PAGESIZE - 1) / PAGESIZE; - if (newsz == psz) - { - alloc_size = psize; - return p; - } - - auto pagenum = lpool.pagenumOf(p); - - if (newsz < psz) - { // Shrink in place - debug (MEMSTOMP) memset(p + size, 0xF2, psize - size); - lpool.freePages(pagenum + newsz, psz - newsz); - } - else if (pagenum + newsz <= pool.npages) - { // Attempt to expand in place - foreach (binsz; lpool.pagetable[pagenum + psz .. pagenum + newsz]) - if (binsz != B_FREE) - goto Lmalloc; - - debug (MEMSTOMP) memset(p + psize, 0xF0, size - psize); - debug(PRINTF) printFreeInfo(pool); - memset(&lpool.pagetable[pagenum + psz], B_PAGEPLUS, newsz - psz); - gcx.usedLargePages += newsz - psz; - lpool.freepages -= (newsz - psz); - debug(PRINTF) printFreeInfo(pool); - } - else - goto Lmalloc; // does not fit into current pool - - lpool.updateOffsets(pagenum); - if (bits) - { - immutable biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; - pool.clrBits(biti, ~BlkAttr.NONE); - pool.setBits(biti, bits); - } - alloc_size = newsz * PAGESIZE; - return p; - } - - psize = (cast(SmallObjectPool*) pool).getSize(p); // get allocated size - if (psize < size || // if new size is bigger - psize > size * 2) // or less than half - { - Lmalloc: - if (psize && pool) - { - auto biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; - - if (bits) - { - pool.clrBits(biti, ~BlkAttr.NONE); - pool.setBits(biti, bits); - } - else - { - bits = pool.getBits(biti); - } - } - p2 = mallocNoSync(size, bits, alloc_size, ti); - if (psize < size) - size = psize; - //debug(PRINTF) printf("\tcopying %d bytes\n",size); - memcpy(p2, p, size); - p = p2; - } - else - alloc_size = psize; - } - } - return p; - } - - - size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow - { - return runLocked!(extendNoSync, extendTime, numExtends)(p, minsize, maxsize, ti); - } - - - // - // - // - private size_t extendNoSync(void* p, size_t minsize, size_t maxsize, const TypeInfo ti = null) nothrow - in - { - assert(minsize <= maxsize); - } - body - { - //debug(PRINTF) printf("GC::extend(p = %p, minsize = %zu, maxsize = %zu)\n", p, minsize, maxsize); - debug (SENTINEL) - { - return 0; - } - else - { - auto pool = gcx.findPool(p); - if (!pool || !pool.isLargeObject) - return 0; - - auto lpool = cast(LargeObjectPool*) pool; - auto psize = lpool.getSize(p); // get allocated size - if (psize < PAGESIZE) - return 0; // cannot extend buckets - - auto psz = psize / PAGESIZE; - auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE; - auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE; - - auto pagenum = lpool.pagenumOf(p); - - size_t sz; - for (sz = 0; sz < maxsz; sz++) - { - auto i = pagenum + psz + sz; - if (i == lpool.npages) - break; - if (lpool.pagetable[i] != B_FREE) - { if (sz < minsz) - return 0; - break; - } - } - if (sz < minsz) - return 0; - debug (MEMSTOMP) memset(pool.baseAddr + (pagenum + psz) * PAGESIZE, 0xF0, sz * PAGESIZE); - memset(lpool.pagetable + pagenum + psz, B_PAGEPLUS, sz); - lpool.updateOffsets(pagenum); - lpool.freepages -= sz; - gcx.usedLargePages += sz; - return (psz + sz) * PAGESIZE; - } - } - - - size_t reserve(size_t size) nothrow - { - if (!size) - { - return 0; - } - - return runLocked!(reserveNoSync, otherTime, numOthers)(size); - } - - - // - // - // - private size_t reserveNoSync(size_t size) nothrow - { - assert(size != 0); - assert(gcx); - - return gcx.reserve(size); - } - - - void free(void *p) nothrow - { - if (!p || _inFinalizer) - { - return; - } - - return runLocked!(freeNoSync, freeTime, numFrees)(p); - } - - - // - // - // - private void freeNoSync(void *p) nothrow - { - debug(PRINTF) printf("Freeing %p\n", cast(size_t) p); - assert (p); - - Pool* pool; - size_t pagenum; - Bins bin; - size_t biti; - - // Find which page it is in - pool = gcx.findPool(p); - if (!pool) // if not one of ours - return; // ignore - - pagenum = pool.pagenumOf(p); - - debug(PRINTF) printf("pool base = %p, PAGENUM = %d of %d, bin = %d\n", pool.baseAddr, pagenum, pool.npages, pool.pagetable[pagenum]); - debug(PRINTF) if (pool.isLargeObject) printf("Block size = %d\n", pool.bPageOffsets[pagenum]); - - bin = cast(Bins)pool.pagetable[pagenum]; - - // Verify that the pointer is at the beginning of a block, - // no action should be taken if p is an interior pointer - if (bin > B_PAGE) // B_PAGEPLUS or B_FREE - return; - if ((sentinel_sub(p) - pool.baseAddr) & (binsize[bin] - 1)) - return; - - sentinel_Invariant(p); - p = sentinel_sub(p); - biti = cast(size_t)(p - pool.baseAddr) >> pool.shiftBy; - - pool.clrBits(biti, ~BlkAttr.NONE); - - if (pool.isLargeObject) // if large alloc - { - assert(bin == B_PAGE); - auto lpool = cast(LargeObjectPool*) pool; - - // Free pages - size_t npages = lpool.bPageOffsets[pagenum]; - debug (MEMSTOMP) memset(p, 0xF2, npages * PAGESIZE); - lpool.freePages(pagenum, npages); - } - else - { // Add to free list - List *list = cast(List*)p; - - debug (MEMSTOMP) memset(p, 0xF2, binsize[bin]); - - list.next = gcx.bucket[bin]; - list.pool = pool; - gcx.bucket[bin] = list; - } - - gcx.log_free(sentinel_add(p)); - } - - - void* addrOf(void *p) nothrow - { - if (!p) - { - return null; - } - - return runLocked!(addrOfNoSync, otherTime, numOthers)(p); - } - - - // - // - // - void* addrOfNoSync(void *p) nothrow - { - if (!p) - { - return null; - } - - auto q = gcx.findBase(p); - if (q) - q = sentinel_add(q); - return q; - } - - - size_t sizeOf(void *p) nothrow - { - if (!p) - { - return 0; - } - - return runLocked!(sizeOfNoSync, otherTime, numOthers)(p); - } - - - // - // - // - private size_t sizeOfNoSync(void *p) nothrow - { - assert (p); - - debug (SENTINEL) - { - p = sentinel_sub(p); - size_t size = gcx.findSize(p); - - // Check for interior pointer - // This depends on: - // 1) size is a power of 2 for less than PAGESIZE values - // 2) base of memory pool is aligned on PAGESIZE boundary - if (cast(size_t)p & (size - 1) & (PAGESIZE - 1)) - size = 0; - return size ? size - SENTINEL_EXTRA : 0; - } - else - { - size_t size = gcx.findSize(p); - - // Check for interior pointer - // This depends on: - // 1) size is a power of 2 for less than PAGESIZE values - // 2) base of memory pool is aligned on PAGESIZE boundary - if (cast(size_t)p & (size - 1) & (PAGESIZE - 1)) - return 0; - return size; - } - } - - - BlkInfo query(void *p) nothrow - { - if (!p) - { - BlkInfo i; - return i; - } - - return runLocked!(queryNoSync, otherTime, numOthers)(p); - } - - // - // - // - BlkInfo queryNoSync(void *p) nothrow - { - assert(p); - - BlkInfo info = gcx.getInfo(p); - debug(SENTINEL) - { - if (info.base) - { - info.base = sentinel_add(info.base); - info.size = *sentinel_size(info.base); - } - } - return info; - } - - - /** - * Verify that pointer p: - * 1) belongs to this memory pool - * 2) points to the start of an allocated piece of memory - * 3) is not on a free list - */ - void check(void *p) nothrow - { - if (!p) - { - return; - } - - return runLocked!(checkNoSync, otherTime, numOthers)(p); - } - - - // - // - // - private void checkNoSync(void *p) nothrow - { - assert(p); - - sentinel_Invariant(p); - debug (PTRCHECK) - { - Pool* pool; - size_t pagenum; - Bins bin; - size_t size; - - p = sentinel_sub(p); - pool = gcx.findPool(p); - assert(pool); - pagenum = pool.pagenumOf(p); - bin = cast(Bins)pool.pagetable[pagenum]; - assert(bin <= B_PAGE); - size = binsize[bin]; - assert((cast(size_t)p & (size - 1)) == 0); - - debug (PTRCHECK2) - { - if (bin < B_PAGE) - { - // Check that p is not on a free list - List *list; - - for (list = gcx.bucket[bin]; list; list = list.next) - { - assert(cast(void*)list != p); - } - } - } - } - } - - - void addRoot(void *p) nothrow @nogc - { - if (!p) - { - return; - } - - gcx.addRoot(p); - } - - - void removeRoot(void *p) nothrow @nogc - { - if (!p) - { - return; - } - - gcx.removeRoot(p); - } - - - @property RootIterator rootIter() @nogc - { - return &gcx.rootsApply; - } - - - void addRange(void *p, size_t sz, const TypeInfo ti = null) nothrow @nogc - { - if (!p || !sz) - { - return; - } - - gcx.addRange(p, p + sz, ti); - } - - - void removeRange(void *p) nothrow @nogc - { - if (!p) - { - return; - } - - gcx.removeRange(p); - } - - - @property RangeIterator rangeIter() @nogc - { - return &gcx.rangesApply; - } - - - void runFinalizers(in void[] segment) nothrow - { - static void go(Gcx* gcx, in void[] segment) nothrow - { - gcx.runFinalizers(segment); - } - return runLocked!(go, otherTime, numOthers)(gcx, segment); - } - - - bool inFinalizer() nothrow - { - return _inFinalizer; - } - - - void collect() nothrow - { - fullCollect(); - } - - - void collectNoStack() nothrow - { - fullCollectNoStack(); - } - - - /** - * Do full garbage collection. - * Return number of pages free'd. - */ - size_t fullCollect() nothrow - { - debug(PRINTF) printf("GC.fullCollect()\n"); - - // Since a finalizer could launch a new thread, we always need to lock - // when collecting. - static size_t go(Gcx* gcx) nothrow - { - return gcx.fullcollect(); - } - immutable result = runLocked!go(gcx); - - version (none) - { - GCStats stats; - - getStats(stats); - debug(PRINTF) printf("heapSize = %zx, freeSize = %zx\n", - stats.heapSize, stats.freeSize); - } - - gcx.log_collect(); - return result; - } - - - /** - * do full garbage collection ignoring roots - */ - void fullCollectNoStack() nothrow - { - // Since a finalizer could launch a new thread, we always need to lock - // when collecting. - static size_t go(Gcx* gcx) nothrow - { - return gcx.fullcollect(true); - } - runLocked!go(gcx); - } - - - void minimize() nothrow - { - static void go(Gcx* gcx) nothrow - { - gcx.minimize(); - } - runLocked!(go, otherTime, numOthers)(gcx); - } - - - core.memory.GC.Stats stats() nothrow - { - typeof(return) ret; - - runLocked!(getStatsNoSync, otherTime, numOthers)(ret); - - return ret; - } - - - // - // - // - private void getStatsNoSync(out core.memory.GC.Stats stats) nothrow - { - foreach (pool; gcx.pooltable[0 .. gcx.npools]) - { - foreach (bin; pool.pagetable[0 .. pool.npages]) - { - if (bin == B_FREE) - stats.freeSize += PAGESIZE; - else - stats.usedSize += PAGESIZE; - } - } - - size_t freeListSize; - foreach (n; 0 .. B_PAGE) - { - immutable sz = binsize[n]; - for (List *list = gcx.bucket[n]; list; list = list.next) - freeListSize += sz; - } - - stats.usedSize -= freeListSize; - stats.freeSize += freeListSize; - } -} - - -/* ============================ Gcx =============================== */ - -enum -{ PAGESIZE = 4096, - POOLSIZE = (4096*256), -} - - -enum -{ - B_16, - B_32, - B_64, - B_128, - B_256, - B_512, - B_1024, - B_2048, - B_PAGE, // start of large alloc - B_PAGEPLUS, // continuation of large alloc - B_FREE, // free page - B_MAX -} - - -alias ubyte Bins; - - -struct List -{ - List *next; - Pool *pool; -} - - -immutable uint[B_MAX] binsize = [ 16,32,64,128,256,512,1024,2048,4096 ]; -immutable size_t[B_MAX] notbinsize = [ ~(16-1),~(32-1),~(64-1),~(128-1),~(256-1), - ~(512-1),~(1024-1),~(2048-1),~(4096-1) ]; - -alias PageBits = GCBits.wordtype[PAGESIZE / 16 / GCBits.BITS_PER_WORD]; -static assert(PAGESIZE % (GCBits.BITS_PER_WORD * 16) == 0); - -private void set(ref PageBits bits, size_t i) @nogc pure nothrow -{ - assert(i < PageBits.sizeof * 8); - bts(bits.ptr, i); -} - -/* ============================ Gcx =============================== */ - -struct Gcx -{ - import core.internal.spinlock; - auto rootsLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); - auto rangesLock = shared(AlignedSpinLock)(SpinLock.Contention.brief); - Treap!Root roots; - Treap!Range ranges; - - bool log; // turn on logging - debug(INVARIANT) bool initialized; - uint disabled; // turn off collections if >0 - - import gc.pooltable; - @property size_t npools() pure const nothrow { return pooltable.length; } - PoolTable!Pool pooltable; - - List*[B_PAGE] bucket; // free list for each small size - - // run a collection when reaching those thresholds (number of used pages) - float smallCollectThreshold, largeCollectThreshold; - uint usedSmallPages, usedLargePages; - // total number of mapped pages - uint mappedPages; - - void initialize() - { - (cast(byte*)&this)[0 .. Gcx.sizeof] = 0; - log_init(); - roots.initialize(); - ranges.initialize(); - smallCollectThreshold = largeCollectThreshold = 0.0f; - usedSmallPages = usedLargePages = 0; - mappedPages = 0; - //printf("gcx = %p, self = %x\n", &this, self); - debug(INVARIANT) initialized = true; - } - - - void Dtor() - { - if (config.profile) - { - printf("\tNumber of collections: %llu\n", cast(ulong)numCollections); - printf("\tTotal GC prep time: %lld milliseconds\n", - prepTime.total!("msecs")); - printf("\tTotal mark time: %lld milliseconds\n", - markTime.total!("msecs")); - printf("\tTotal sweep time: %lld milliseconds\n", - sweepTime.total!("msecs")); - printf("\tTotal page recovery time: %lld milliseconds\n", - recoverTime.total!("msecs")); - long maxPause = maxPauseTime.total!("msecs"); - printf("\tMax Pause Time: %lld milliseconds\n", maxPause); - long gcTime = (recoverTime + sweepTime + markTime + prepTime).total!("msecs"); - printf("\tGrand total GC time: %lld milliseconds\n", gcTime); - long pauseTime = (markTime + prepTime).total!("msecs"); - - char[30] apitxt; - apitxt[0] = 0; - debug(PROFILE_API) if (config.profile > 1) - { - static Duration toDuration(long dur) - { - return MonoTime(dur) - MonoTime(0); - } - - printf("\n"); - printf("\tmalloc: %llu calls, %lld ms\n", cast(ulong)numMallocs, toDuration(mallocTime).total!"msecs"); - printf("\trealloc: %llu calls, %lld ms\n", cast(ulong)numReallocs, toDuration(reallocTime).total!"msecs"); - printf("\tfree: %llu calls, %lld ms\n", cast(ulong)numFrees, toDuration(freeTime).total!"msecs"); - printf("\textend: %llu calls, %lld ms\n", cast(ulong)numExtends, toDuration(extendTime).total!"msecs"); - printf("\tother: %llu calls, %lld ms\n", cast(ulong)numOthers, toDuration(otherTime).total!"msecs"); - printf("\tlock time: %lld ms\n", toDuration(lockTime).total!"msecs"); - - long apiTime = mallocTime + reallocTime + freeTime + extendTime + otherTime + lockTime; - printf("\tGC API: %lld ms\n", toDuration(apiTime).total!"msecs"); - sprintf(apitxt.ptr, " API%5ld ms", toDuration(apiTime).total!"msecs"); - } - - printf("GC summary:%5lld MB,%5lld GC%5lld ms, Pauses%5lld ms <%5lld ms%s\n", - cast(long) maxPoolMemory >> 20, cast(ulong)numCollections, gcTime, - pauseTime, maxPause, apitxt.ptr); - } - - debug(INVARIANT) initialized = false; - - for (size_t i = 0; i < npools; i++) - { - Pool *pool = pooltable[i]; - mappedPages -= pool.npages; - pool.Dtor(); - cstdlib.free(pool); - } - assert(!mappedPages); - pooltable.Dtor(); - - roots.removeAll(); - ranges.removeAll(); - toscan.reset(); - } - - - void Invariant() const { } - - debug(INVARIANT) - invariant() - { - if (initialized) - { - //printf("Gcx.invariant(): this = %p\n", &this); - pooltable.Invariant(); - - rangesLock.lock(); - foreach (range; ranges) - { - assert(range.pbot); - assert(range.ptop); - assert(range.pbot <= range.ptop); - } - rangesLock.unlock(); - - for (size_t i = 0; i < B_PAGE; i++) - { - for (auto list = cast(List*)bucket[i]; list; list = list.next) - { - } - } - } - } - - - /** - * - */ - void addRoot(void *p) nothrow @nogc - { - rootsLock.lock(); - scope (failure) rootsLock.unlock(); - roots.insert(Root(p)); - rootsLock.unlock(); - } - - - /** - * - */ - void removeRoot(void *p) nothrow @nogc - { - rootsLock.lock(); - scope (failure) rootsLock.unlock(); - roots.remove(Root(p)); - rootsLock.unlock(); - } - - - /** - * - */ - int rootsApply(scope int delegate(ref Root) nothrow dg) nothrow - { - rootsLock.lock(); - scope (failure) rootsLock.unlock(); - auto ret = roots.opApply(dg); - rootsLock.unlock(); - return ret; - } - - - /** - * - */ - void addRange(void *pbot, void *ptop, const TypeInfo ti) nothrow @nogc - { - //debug(PRINTF) printf("Thread %x ", pthread_self()); - debug(PRINTF) printf("%p.Gcx::addRange(%p, %p)\n", &this, pbot, ptop); - rangesLock.lock(); - scope (failure) rangesLock.unlock(); - ranges.insert(Range(pbot, ptop)); - rangesLock.unlock(); - } - - - /** - * - */ - void removeRange(void *pbot) nothrow @nogc - { - //debug(PRINTF) printf("Thread %x ", pthread_self()); - debug(PRINTF) printf("Gcx.removeRange(%p)\n", pbot); - rangesLock.lock(); - scope (failure) rangesLock.unlock(); - ranges.remove(Range(pbot, pbot)); // only pbot is used, see Range.opCmp - rangesLock.unlock(); - - // debug(PRINTF) printf("Wrong thread\n"); - // This is a fatal error, but ignore it. - // The problem is that we can get a Close() call on a thread - // other than the one the range was allocated on. - //assert(zero); - } - - /** - * - */ - int rangesApply(scope int delegate(ref Range) nothrow dg) nothrow - { - rangesLock.lock(); - scope (failure) rangesLock.unlock(); - auto ret = ranges.opApply(dg); - rangesLock.unlock(); - return ret; - } - - - /** - * - */ - void runFinalizers(in void[] segment) nothrow - { - ConservativeGC._inFinalizer = true; - scope (failure) ConservativeGC._inFinalizer = false; - - foreach (pool; pooltable[0 .. npools]) - { - if (!pool.finals.nbits) continue; - - if (pool.isLargeObject) - { - auto lpool = cast(LargeObjectPool*) pool; - lpool.runFinalizers(segment); - } - else - { - auto spool = cast(SmallObjectPool*) pool; - spool.runFinalizers(segment); - } - } - ConservativeGC._inFinalizer = false; - } - - Pool* findPool(void* p) pure nothrow - { - return pooltable.findPool(p); - } - - /** - * Find base address of block containing pointer p. - * Returns null if not a gc'd pointer - */ - void* findBase(void *p) nothrow - { - Pool *pool; - - pool = findPool(p); - if (pool) - { - size_t offset = cast(size_t)(p - pool.baseAddr); - size_t pn = offset / PAGESIZE; - Bins bin = cast(Bins)pool.pagetable[pn]; - - // Adjust bit to be at start of allocated memory block - if (bin <= B_PAGE) - { - return pool.baseAddr + (offset & notbinsize[bin]); - } - else if (bin == B_PAGEPLUS) - { - auto pageOffset = pool.bPageOffsets[pn]; - offset -= pageOffset * PAGESIZE; - pn -= pageOffset; - - return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1))); - } - else - { - // we are in a B_FREE page - assert(bin == B_FREE); - return null; - } - } - return null; - } - - - /** - * Find size of pointer p. - * Returns 0 if not a gc'd pointer - */ - size_t findSize(void *p) nothrow - { - Pool* pool = findPool(p); - if (pool) - return pool.slGetSize(p); - return 0; - } - - /** - * - */ - BlkInfo getInfo(void* p) nothrow - { - Pool* pool = findPool(p); - if (pool) - return pool.slGetInfo(p); - return BlkInfo(); - } - - /** - * Computes the bin table using CTFE. - */ - static byte[2049] ctfeBins() nothrow - { - byte[2049] ret; - size_t p = 0; - for (Bins b = B_16; b <= B_2048; b++) - for ( ; p <= binsize[b]; p++) - ret[p] = b; - - return ret; - } - - static const byte[2049] binTable = ctfeBins(); - - /** - * Allocate a new pool of at least size bytes. - * Sort it into pooltable[]. - * Mark all memory in the pool as B_FREE. - * Return the actual number of bytes reserved or 0 on error. - */ - size_t reserve(size_t size) nothrow - { - size_t npages = (size + PAGESIZE - 1) / PAGESIZE; - - // Assume reserve() is for small objects. - Pool* pool = newPool(npages, false); - - if (!pool) - return 0; - return pool.npages * PAGESIZE; - } - - /** - * Update the thresholds for when to collect the next time - */ - void updateCollectThresholds() nothrow - { - static float max(float a, float b) nothrow - { - return a >= b ? a : b; - } - - // instantly increases, slowly decreases - static float smoothDecay(float oldVal, float newVal) nothrow - { - // decay to 63.2% of newVal over 5 collections - // http://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter - enum alpha = 1.0 / (5 + 1); - immutable decay = (newVal - oldVal) * alpha + oldVal; - return max(newVal, decay); - } - - immutable smTarget = usedSmallPages * config.heapSizeFactor; - smallCollectThreshold = smoothDecay(smallCollectThreshold, smTarget); - immutable lgTarget = usedLargePages * config.heapSizeFactor; - largeCollectThreshold = smoothDecay(largeCollectThreshold, lgTarget); - } - - /** - * Minimizes physical memory usage by returning free pools to the OS. - */ - void minimize() nothrow - { - debug(PRINTF) printf("Minimizing.\n"); - - foreach (pool; pooltable.minimize()) - { - debug(PRINTF) printFreeInfo(pool); - mappedPages -= pool.npages; - pool.Dtor(); - cstdlib.free(pool); - } - - debug(PRINTF) printf("Done minimizing.\n"); - } - - private @property bool lowMem() const nothrow - { - return isLowOnMem(mappedPages * PAGESIZE); - } - - void* alloc(size_t size, ref size_t alloc_size, uint bits) nothrow - { - return size <= 2048 ? smallAlloc(binTable[size], alloc_size, bits) - : bigAlloc(size, alloc_size, bits); - } - - void* smallAlloc(Bins bin, ref size_t alloc_size, uint bits) nothrow - { - alloc_size = binsize[bin]; - - void* p; - bool tryAlloc() nothrow - { - if (!bucket[bin]) - { - bucket[bin] = allocPage(bin); - if (!bucket[bin]) - return false; - } - p = bucket[bin]; - return true; - } - - if (!tryAlloc()) - { - if (!lowMem && (disabled || usedSmallPages < smallCollectThreshold)) - { - // disabled or threshold not reached => allocate a new pool instead of collecting - if (!newPool(1, false)) - { - // out of memory => try to free some memory - fullcollect(); - if (lowMem) minimize(); - } - } - else - { - fullcollect(); - if (lowMem) minimize(); - } - // tryAlloc will succeed if a new pool was allocated above, if it fails allocate a new pool now - if (!tryAlloc() && (!newPool(1, false) || !tryAlloc())) - // out of luck or memory - onOutOfMemoryErrorNoGC(); - } - assert(p !is null); - - // Return next item from free list - bucket[bin] = (cast(List*)p).next; - auto pool = (cast(List*)p).pool; - if (bits) - pool.setBits((p - pool.baseAddr) >> pool.shiftBy, bits); - //debug(PRINTF) printf("\tmalloc => %p\n", p); - debug (MEMSTOMP) memset(p, 0xF0, alloc_size); - return p; - } - - /** - * Allocate a chunk of memory that is larger than a page. - * Return null if out of memory. - */ - void* bigAlloc(size_t size, ref size_t alloc_size, uint bits, const TypeInfo ti = null) nothrow - { - debug(PRINTF) printf("In bigAlloc. Size: %d\n", size); - - LargeObjectPool* pool; - size_t pn; - immutable npages = (size + PAGESIZE - 1) / PAGESIZE; - if (npages == 0) - onOutOfMemoryErrorNoGC(); // size just below size_t.max requested - - bool tryAlloc() nothrow - { - foreach (p; pooltable[0 .. npools]) - { - if (!p.isLargeObject || p.freepages < npages) - continue; - auto lpool = cast(LargeObjectPool*) p; - if ((pn = lpool.allocPages(npages)) == OPFAIL) - continue; - pool = lpool; - return true; - } - return false; - } - - bool tryAllocNewPool() nothrow - { - pool = cast(LargeObjectPool*) newPool(npages, true); - if (!pool) return false; - pn = pool.allocPages(npages); - assert(pn != OPFAIL); - return true; - } - - if (!tryAlloc()) - { - if (!lowMem && (disabled || usedLargePages < largeCollectThreshold)) - { - // disabled or threshold not reached => allocate a new pool instead of collecting - if (!tryAllocNewPool()) - { - // disabled but out of memory => try to free some memory - fullcollect(); - minimize(); - } - } - else - { - fullcollect(); - minimize(); - } - // If alloc didn't yet succeed retry now that we collected/minimized - if (!pool && !tryAlloc() && !tryAllocNewPool()) - // out of luck or memory - return null; - } - assert(pool); - - debug(PRINTF) printFreeInfo(&pool.base); - pool.pagetable[pn] = B_PAGE; - if (npages > 1) - memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); - pool.updateOffsets(pn); - usedLargePages += npages; - pool.freepages -= npages; - - debug(PRINTF) printFreeInfo(&pool.base); - - auto p = pool.baseAddr + pn * PAGESIZE; - debug(PRINTF) printf("Got large alloc: %p, pt = %d, np = %d\n", p, pool.pagetable[pn], npages); - debug (MEMSTOMP) memset(p, 0xF1, size); - alloc_size = npages * PAGESIZE; - //debug(PRINTF) printf("\tp = %p\n", p); - - if (bits) - pool.setBits(pn, bits); - return p; - } - - - /** - * Allocate a new pool with at least npages in it. - * Sort it into pooltable[]. - * Return null if failed. - */ - Pool *newPool(size_t npages, bool isLargeObject) nothrow - { - //debug(PRINTF) printf("************Gcx::newPool(npages = %d)****************\n", npages); - - // Minimum of POOLSIZE - size_t minPages = (config.minPoolSize << 20) / PAGESIZE; - if (npages < minPages) - npages = minPages; - else if (npages > minPages) - { // Give us 150% of requested size, so there's room to extend - auto n = npages + (npages >> 1); - if (n < size_t.max/PAGESIZE) - npages = n; - } - - // Allocate successively larger pools up to 8 megs - if (npools) - { size_t n; - - n = config.minPoolSize + config.incPoolSize * npools; - if (n > config.maxPoolSize) - n = config.maxPoolSize; // cap pool size - n *= (1 << 20) / PAGESIZE; // convert MB to pages - if (npages < n) - npages = n; - } - - //printf("npages = %d\n", npages); - - auto pool = cast(Pool *)cstdlib.calloc(1, isLargeObject ? LargeObjectPool.sizeof : SmallObjectPool.sizeof); - if (pool) - { - pool.initialize(npages, isLargeObject); - if (!pool.baseAddr || !pooltable.insert(pool)) - { - pool.Dtor(); - cstdlib.free(pool); - return null; - } - } - - mappedPages += npages; - - if (config.profile) - { - if (mappedPages * PAGESIZE > maxPoolMemory) - maxPoolMemory = mappedPages * PAGESIZE; - } - return pool; - } - - /** - * Allocate a page of bin's. - * Returns: - * head of a single linked list of new entries - */ - List* allocPage(Bins bin) nothrow - { - //debug(PRINTF) printf("Gcx::allocPage(bin = %d)\n", bin); - for (size_t n = 0; n < npools; n++) - { - Pool* pool = pooltable[n]; - if (pool.isLargeObject) - continue; - if (List* p = (cast(SmallObjectPool*)pool).allocPage(bin)) - { - ++usedSmallPages; - return p; - } - } - return null; - } - - static struct ToScanStack - { - nothrow: - @disable this(this); - - void reset() - { - _length = 0; - os_mem_unmap(_p, _cap * Range.sizeof); - _p = null; - _cap = 0; - } - - void push(Range rng) - { - if (_length == _cap) grow(); - _p[_length++] = rng; - } - - Range pop() - in { assert(!empty); } - body - { - return _p[--_length]; - } - - ref inout(Range) opIndex(size_t idx) inout - in { assert(idx < _length); } - body - { - return _p[idx]; - } - - @property size_t length() const { return _length; } - @property bool empty() const { return !length; } - - private: - void grow() - { - enum initSize = 64 * 1024; // Windows VirtualAlloc granularity - immutable ncap = _cap ? 2 * _cap : initSize / Range.sizeof; - auto p = cast(Range*)os_mem_map(ncap * Range.sizeof); - if (p is null) onOutOfMemoryErrorNoGC(); - if (_p !is null) - { - p[0 .. _length] = _p[0 .. _length]; - os_mem_unmap(_p, _cap * Range.sizeof); - } - _p = p; - _cap = ncap; - } - - size_t _length; - Range* _p; - size_t _cap; - } - - ToScanStack toscan; - - /** - * Search a range of memory values and mark any pointers into the GC pool. - */ - void mark(void *pbot, void *ptop) scope nothrow - { - void **p1 = cast(void **)pbot; - void **p2 = cast(void **)ptop; - - // limit the amount of ranges added to the toscan stack - enum FANOUT_LIMIT = 32; - size_t stackPos; - Range[FANOUT_LIMIT] stack = void; - - Lagain: - size_t pcache = 0; - - // let dmd allocate a register for this.pools - auto pools = pooltable.pools; - const highpool = pooltable.npools - 1; - const minAddr = pooltable.minAddr; - const maxAddr = pooltable.maxAddr; - - //printf("marking range: [%p..%p] (%#zx)\n", p1, p2, cast(size_t)p2 - cast(size_t)p1); - Lnext: for (; p1 < p2; p1++) - { - auto p = *p1; - - //if (log) debug(PRINTF) printf("\tmark %p\n", p); - if (p >= minAddr && p < maxAddr) - { - if ((cast(size_t)p & ~cast(size_t)(PAGESIZE-1)) == pcache) - continue; - - Pool* pool = void; - size_t low = 0; - size_t high = highpool; - while (true) - { - size_t mid = (low + high) >> 1; - pool = pools[mid]; - if (p < pool.baseAddr) - high = mid - 1; - else if (p >= pool.topAddr) - low = mid + 1; - else break; - - if (low > high) - continue Lnext; - } - size_t offset = cast(size_t)(p - pool.baseAddr); - size_t biti = void; - size_t pn = offset / PAGESIZE; - Bins bin = cast(Bins)pool.pagetable[pn]; - void* base = void; - - //debug(PRINTF) printf("\t\tfound pool %p, base=%p, pn = %zd, bin = %d, biti = x%x\n", pool, pool.baseAddr, pn, bin, biti); - - // Adjust bit to be at start of allocated memory block - if (bin < B_PAGE) - { - // We don't care abou setting pointsToBase correctly - // because it's ignored for small object pools anyhow. - auto offsetBase = offset & notbinsize[bin]; - biti = offsetBase >> pool.shiftBy; - base = pool.baseAddr + offsetBase; - //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); - - if (!pool.mark.set(biti) && !pool.noscan.test(biti)) { - stack[stackPos++] = Range(base, base + binsize[bin]); - if (stackPos == stack.length) - break; - } - } - else if (bin == B_PAGE) - { - auto offsetBase = offset & notbinsize[bin]; - base = pool.baseAddr + offsetBase; - biti = offsetBase >> pool.shiftBy; - //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); - - pcache = cast(size_t)p & ~cast(size_t)(PAGESIZE-1); - - // For the NO_INTERIOR attribute. This tracks whether - // the pointer is an interior pointer or points to the - // base address of a block. - bool pointsToBase = (base == sentinel_sub(p)); - if (!pointsToBase && pool.nointerior.nbits && pool.nointerior.test(biti)) - continue; - - if (!pool.mark.set(biti) && !pool.noscan.test(biti)) { - stack[stackPos++] = Range(base, base + pool.bPageOffsets[pn] * PAGESIZE); - if (stackPos == stack.length) - break; - } - } - else if (bin == B_PAGEPLUS) - { - pn -= pool.bPageOffsets[pn]; - base = pool.baseAddr + (pn * PAGESIZE); - biti = pn * (PAGESIZE >> pool.shiftBy); - - pcache = cast(size_t)p & ~cast(size_t)(PAGESIZE-1); - if (pool.nointerior.nbits && pool.nointerior.test(biti)) - continue; - - if (!pool.mark.set(biti) && !pool.noscan.test(biti)) { - stack[stackPos++] = Range(base, base + pool.bPageOffsets[pn] * PAGESIZE); - if (stackPos == stack.length) - break; - } - } - else - { - // Don't mark bits in B_FREE pages - assert(bin == B_FREE); - continue; - } - } - } - - Range next=void; - if (p1 < p2) - { - // local stack is full, push it to the global stack - assert(stackPos == stack.length); - toscan.push(Range(p1, p2)); - // reverse order for depth-first-order traversal - foreach_reverse (ref rng; stack[0 .. $ - 1]) - toscan.push(rng); - stackPos = 0; - next = stack[$-1]; - } - else if (stackPos) - { - // pop range from local stack and recurse - next = stack[--stackPos]; - } - else if (!toscan.empty) - { - // pop range from global stack and recurse - next = toscan.pop(); - } - else - { - // nothing more to do - return; - } - p1 = cast(void**)next.pbot; - p2 = cast(void**)next.ptop; - // printf(" pop [%p..%p] (%#zx)\n", p1, p2, cast(size_t)p2 - cast(size_t)p1); - goto Lagain; - } - - // collection step 1: prepare freebits and mark bits - void prepare() nothrow - { - size_t n; - Pool* pool; - - for (n = 0; n < npools; n++) - { - pool = pooltable[n]; - pool.mark.zero(); - if (!pool.isLargeObject) pool.freebits.zero(); - } - - debug(COLLECT_PRINTF) printf("Set bits\n"); - - // Mark each free entry, so it doesn't get scanned - for (n = 0; n < B_PAGE; n++) - { - for (List *list = bucket[n]; list; list = list.next) - { - pool = list.pool; - assert(pool); - pool.freebits.set(cast(size_t)(cast(void*)list - pool.baseAddr) / 16); - } - } - - debug(COLLECT_PRINTF) printf("Marked free entries.\n"); - - for (n = 0; n < npools; n++) - { - pool = pooltable[n]; - if (!pool.isLargeObject) - { - pool.mark.copy(&pool.freebits); - } - } - } - - // collection step 2: mark roots and heap - void markAll(bool nostack) nothrow - { - if (!nostack) - { - debug(COLLECT_PRINTF) printf("\tscan stacks.\n"); - // Scan stacks and registers for each paused thread - thread_scanAll(&mark); - } - - // Scan roots[] - debug(COLLECT_PRINTF) printf("\tscan roots[]\n"); - foreach (root; roots) - { - mark(cast(void*)&root.proot, cast(void*)(&root.proot + 1)); - } - - // Scan ranges[] - debug(COLLECT_PRINTF) printf("\tscan ranges[]\n"); - //log++; - foreach (range; ranges) - { - debug(COLLECT_PRINTF) printf("\t\t%p .. %p\n", range.pbot, range.ptop); - mark(range.pbot, range.ptop); - } - //log--; - } - - // collection step 3: free all unreferenced objects - size_t sweep() nothrow - { - // Free up everything not marked - debug(COLLECT_PRINTF) printf("\tfree'ing\n"); - size_t freedLargePages; - size_t freed; - for (size_t n = 0; n < npools; n++) - { - size_t pn; - Pool* pool = pooltable[n]; - - if (pool.isLargeObject) - { - for (pn = 0; pn < pool.npages; pn++) - { - Bins bin = cast(Bins)pool.pagetable[pn]; - if (bin > B_PAGE) continue; - size_t biti = pn; - - if (!pool.mark.test(biti)) - { - void *p = pool.baseAddr + pn * PAGESIZE; - void* q = sentinel_add(p); - sentinel_Invariant(q); - - if (pool.finals.nbits && pool.finals.clear(biti)) - { - size_t size = pool.bPageOffsets[pn] * PAGESIZE - SENTINEL_EXTRA; - uint attr = pool.getBits(biti); - rt_finalizeFromGC(q, size, attr); - } - - pool.clrBits(biti, ~BlkAttr.NONE ^ BlkAttr.FINALIZE); - - debug(COLLECT_PRINTF) printf("\tcollecting big %p\n", p); - log_free(q); - pool.pagetable[pn] = B_FREE; - if (pn < pool.searchStart) pool.searchStart = pn; - freedLargePages++; - pool.freepages++; - - debug (MEMSTOMP) memset(p, 0xF3, PAGESIZE); - while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS) - { - pn++; - pool.pagetable[pn] = B_FREE; - - // Don't need to update searchStart here because - // pn is guaranteed to be greater than last time - // we updated it. - - pool.freepages++; - freedLargePages++; - - debug (MEMSTOMP) - { p += PAGESIZE; - memset(p, 0xF3, PAGESIZE); - } - } - pool.largestFree = pool.freepages; // invalidate - } - } - } - else - { - - for (pn = 0; pn < pool.npages; pn++) - { - Bins bin = cast(Bins)pool.pagetable[pn]; - - if (bin < B_PAGE) - { - immutable size = binsize[bin]; - void *p = pool.baseAddr + pn * PAGESIZE; - void *ptop = p + PAGESIZE; - immutable base = pn * (PAGESIZE/16); - immutable bitstride = size / 16; - - bool freeBits; - PageBits toFree; - - for (size_t i; p < ptop; p += size, i += bitstride) - { - immutable biti = base + i; - - if (!pool.mark.test(biti)) - { - void* q = sentinel_add(p); - sentinel_Invariant(q); - - if (pool.finals.nbits && pool.finals.test(biti)) - rt_finalizeFromGC(q, size - SENTINEL_EXTRA, pool.getBits(biti)); - - freeBits = true; - toFree.set(i); - - debug(COLLECT_PRINTF) printf("\tcollecting %p\n", p); - log_free(sentinel_add(p)); - - debug (MEMSTOMP) memset(p, 0xF3, size); - - freed += size; - } - } - - if (freeBits) - pool.freePageBits(pn, toFree); - } - } - } - } - - assert(freedLargePages <= usedLargePages); - usedLargePages -= freedLargePages; - debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedLargePages, npools); - return freedLargePages; - } - - // collection step 4: recover pages with no live objects, rebuild free lists - size_t recover() nothrow - { - // init tail list - List**[B_PAGE] tail = void; - foreach (i, ref next; tail) - next = &bucket[i]; - - // Free complete pages, rebuild free list - debug(COLLECT_PRINTF) printf("\tfree complete pages\n"); - size_t freedSmallPages; - for (size_t n = 0; n < npools; n++) - { - size_t pn; - Pool* pool = pooltable[n]; - - if (pool.isLargeObject) - continue; - - for (pn = 0; pn < pool.npages; pn++) - { - Bins bin = cast(Bins)pool.pagetable[pn]; - size_t biti; - size_t u; - - if (bin < B_PAGE) - { - size_t size = binsize[bin]; - size_t bitstride = size / 16; - size_t bitbase = pn * (PAGESIZE / 16); - size_t bittop = bitbase + (PAGESIZE / 16); - void* p; - - biti = bitbase; - for (biti = bitbase; biti < bittop; biti += bitstride) - { - if (!pool.freebits.test(biti)) - goto Lnotfree; - } - pool.pagetable[pn] = B_FREE; - if (pn < pool.searchStart) pool.searchStart = pn; - pool.freepages++; - freedSmallPages++; - continue; - - Lnotfree: - p = pool.baseAddr + pn * PAGESIZE; - for (u = 0; u < PAGESIZE; u += size) - { - biti = bitbase + u / 16; - if (!pool.freebits.test(biti)) - continue; - auto elem = cast(List *)(p + u); - elem.pool = pool; - *tail[bin] = elem; - tail[bin] = &elem.next; - } - } - } - } - // terminate tail list - foreach (ref next; tail) - *next = null; - - assert(freedSmallPages <= usedSmallPages); - usedSmallPages -= freedSmallPages; - debug(COLLECT_PRINTF) printf("\trecovered pages = %d\n", freedSmallPages); - return freedSmallPages; - } - - /** - * Return number of full pages free'd. - */ - size_t fullcollect(bool nostack = false) nothrow - { - MonoTime start, stop, begin; - - if (config.profile) - { - begin = start = currTime; - } - - debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n"); - //printf("\tpool address range = %p .. %p\n", minAddr, maxAddr); - - { - // lock roots and ranges around suspending threads b/c they're not reentrant safe - rangesLock.lock(); - rootsLock.lock(); - scope (exit) - { - rangesLock.unlock(); - rootsLock.unlock(); - } - thread_suspendAll(); - - prepare(); - - if (config.profile) - { - stop = currTime; - prepTime += (stop - start); - start = stop; - } - - markAll(nostack); - - thread_processGCMarks(&isMarked); - thread_resumeAll(); - } - - if (config.profile) - { - stop = currTime; - markTime += (stop - start); - Duration pause = stop - begin; - if (pause > maxPauseTime) - maxPauseTime = pause; - start = stop; - } - - ConservativeGC._inFinalizer = true; - size_t freedLargePages=void; - { - scope (failure) ConservativeGC._inFinalizer = false; - freedLargePages = sweep(); - ConservativeGC._inFinalizer = false; - } - - if (config.profile) - { - stop = currTime; - sweepTime += (stop - start); - start = stop; - } - - immutable freedSmallPages = recover(); - - if (config.profile) - { - stop = currTime; - recoverTime += (stop - start); - ++numCollections; - } - - updateCollectThresholds(); - - return freedLargePages + freedSmallPages; - } - - /** - * Returns true if the addr lies within a marked block. - * - * Warning! This should only be called while the world is stopped inside - * the fullcollect function. - */ - int isMarked(void *addr) scope nothrow - { - // first, we find the Pool this block is in, then check to see if the - // mark bit is clear. - auto pool = findPool(addr); - if (pool) - { - auto offset = cast(size_t)(addr - pool.baseAddr); - auto pn = offset / PAGESIZE; - auto bins = cast(Bins)pool.pagetable[pn]; - size_t biti = void; - if (bins <= B_PAGE) - { - biti = (offset & notbinsize[bins]) >> pool.shiftBy; - } - else if (bins == B_PAGEPLUS) - { - pn -= pool.bPageOffsets[pn]; - biti = pn * (PAGESIZE >> pool.shiftBy); - } - else // bins == B_FREE - { - assert(bins == B_FREE); - return IsMarked.no; - } - return pool.mark.test(biti) ? IsMarked.yes : IsMarked.no; - } - return IsMarked.unknown; - } - - - /***** Leak Detector ******/ - - - debug (LOGGING) - { - LogArray current; - LogArray prev; - - - void log_init() - { - //debug(PRINTF) printf("+log_init()\n"); - current.reserve(1000); - prev.reserve(1000); - //debug(PRINTF) printf("-log_init()\n"); - } - - - void log_malloc(void *p, size_t size) nothrow - { - //debug(PRINTF) printf("+log_malloc(p = %p, size = %zd)\n", p, size); - Log log; - - log.p = p; - log.size = size; - log.line = GC.line; - log.file = GC.file; - log.parent = null; - - GC.line = 0; - GC.file = null; - - current.push(log); - //debug(PRINTF) printf("-log_malloc()\n"); - } - - - void log_free(void *p) nothrow - { - //debug(PRINTF) printf("+log_free(%p)\n", p); - auto i = current.find(p); - if (i == OPFAIL) - { - debug(PRINTF) printf("free'ing unallocated memory %p\n", p); - } - else - current.remove(i); - //debug(PRINTF) printf("-log_free()\n"); - } - - - void log_collect() nothrow - { - //debug(PRINTF) printf("+log_collect()\n"); - // Print everything in current that is not in prev - - debug(PRINTF) printf("New pointers this cycle: --------------------------------\n"); - size_t used = 0; - for (size_t i = 0; i < current.dim; i++) - { - auto j = prev.find(current.data[i].p); - if (j == OPFAIL) - current.data[i].print(); - else - used++; - } - - debug(PRINTF) printf("All roots this cycle: --------------------------------\n"); - for (size_t i = 0; i < current.dim; i++) - { - void* p = current.data[i].p; - if (!findPool(current.data[i].parent)) - { - auto j = prev.find(current.data[i].p); - debug(PRINTF) printf(j == OPFAIL ? "N" : " "); - current.data[i].print(); - } - } - - debug(PRINTF) printf("Used = %d-------------------------------------------------\n", used); - prev.copy(¤t); - - debug(PRINTF) printf("-log_collect()\n"); - } - - - void log_parent(void *p, void *parent) nothrow - { - //debug(PRINTF) printf("+log_parent()\n"); - auto i = current.find(p); - if (i == OPFAIL) - { - debug(PRINTF) printf("parent'ing unallocated memory %p, parent = %p\n", p, parent); - Pool *pool; - pool = findPool(p); - assert(pool); - size_t offset = cast(size_t)(p - pool.baseAddr); - size_t biti; - size_t pn = offset / PAGESIZE; - Bins bin = cast(Bins)pool.pagetable[pn]; - biti = (offset & notbinsize[bin]); - debug(PRINTF) printf("\tbin = %d, offset = x%x, biti = x%x\n", bin, offset, biti); - } - else - { - current.data[i].parent = parent; - } - //debug(PRINTF) printf("-log_parent()\n"); - } - - } - else - { - void log_init() nothrow { } - void log_malloc(void *p, size_t size) nothrow { } - void log_free(void *p) nothrow { } - void log_collect() nothrow { } - void log_parent(void *p, void *parent) nothrow { } - } -} - -/* ============================ Pool =============================== */ - -struct Pool -{ - void* baseAddr; - void* topAddr; - GCBits mark; // entries already scanned, or should not be scanned - GCBits freebits; // entries that are on the free list - GCBits finals; // entries that need finalizer run on them - GCBits structFinals;// struct entries that need a finalzier run on them - GCBits noscan; // entries that should not be scanned - GCBits appendable; // entries that are appendable - GCBits nointerior; // interior pointers should be ignored. - // Only implemented for large object pools. - size_t npages; - size_t freepages; // The number of pages not in use. - ubyte* pagetable; - - bool isLargeObject; - - uint shiftBy; // shift count for the divisor used for determining bit indices. - - // This tracks how far back we have to go to find the nearest B_PAGE at - // a smaller address than a B_PAGEPLUS. To save space, we use a uint. - // This limits individual allocations to 16 terabytes, assuming a 4k - // pagesize. - uint* bPageOffsets; - - // This variable tracks a conservative estimate of where the first free - // page in this pool is, so that if a lot of pages towards the beginning - // are occupied, we can bypass them in O(1). - size_t searchStart; - size_t largestFree; // upper limit for largest free chunk in large object pool - - void initialize(size_t npages, bool isLargeObject) nothrow - { - this.isLargeObject = isLargeObject; - size_t poolsize; - - shiftBy = isLargeObject ? 12 : 4; - - //debug(PRINTF) printf("Pool::Pool(%u)\n", npages); - poolsize = npages * PAGESIZE; - assert(poolsize >= POOLSIZE); - baseAddr = cast(byte *)os_mem_map(poolsize); - - // Some of the code depends on page alignment of memory pools - assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0); - - if (!baseAddr) - { - //debug(PRINTF) printf("GC fail: poolsize = x%zx, errno = %d\n", poolsize, errno); - //debug(PRINTF) printf("message = '%s'\n", sys_errlist[errno]); - - npages = 0; - poolsize = 0; - } - //assert(baseAddr); - topAddr = baseAddr + poolsize; - auto nbits = cast(size_t)poolsize >> shiftBy; - - mark.alloc(nbits); - - // pagetable already keeps track of what's free for the large object - // pool. - if (!isLargeObject) - { - freebits.alloc(nbits); - } - - noscan.alloc(nbits); - appendable.alloc(nbits); - - pagetable = cast(ubyte*)cstdlib.malloc(npages); - if (!pagetable) - onOutOfMemoryErrorNoGC(); - - if (isLargeObject) - { - bPageOffsets = cast(uint*)cstdlib.malloc(npages * uint.sizeof); - if (!bPageOffsets) - onOutOfMemoryErrorNoGC(); - } - - memset(pagetable, B_FREE, npages); - - this.npages = npages; - this.freepages = npages; - this.searchStart = 0; - this.largestFree = npages; - } - - - void Dtor() nothrow - { - if (baseAddr) - { - int result; - - if (npages) - { - result = os_mem_unmap(baseAddr, npages * PAGESIZE); - assert(result == 0); - npages = 0; - } - - baseAddr = null; - topAddr = null; - } - if (pagetable) - { - cstdlib.free(pagetable); - pagetable = null; - } - - if (bPageOffsets) - cstdlib.free(bPageOffsets); - - mark.Dtor(); - if (isLargeObject) - { - nointerior.Dtor(); - } - else - { - freebits.Dtor(); - } - finals.Dtor(); - structFinals.Dtor(); - noscan.Dtor(); - appendable.Dtor(); - } - - /** - * - */ - uint getBits(size_t biti) nothrow - { - uint bits; - - if (finals.nbits && finals.test(biti)) - bits |= BlkAttr.FINALIZE; - if (structFinals.nbits && structFinals.test(biti)) - bits |= BlkAttr.STRUCTFINAL; - if (noscan.test(biti)) - bits |= BlkAttr.NO_SCAN; - if (nointerior.nbits && nointerior.test(biti)) - bits |= BlkAttr.NO_INTERIOR; - if (appendable.test(biti)) - bits |= BlkAttr.APPENDABLE; - return bits; - } - - /** - * - */ - void clrBits(size_t biti, uint mask) nothrow - { - immutable dataIndex = biti >> GCBits.BITS_SHIFT; - immutable bitOffset = biti & GCBits.BITS_MASK; - immutable keep = ~(GCBits.BITS_1 << bitOffset); - - if (mask & BlkAttr.FINALIZE && finals.nbits) - finals.data[dataIndex] &= keep; - - if (structFinals.nbits && (mask & BlkAttr.STRUCTFINAL)) - structFinals.data[dataIndex] &= keep; - - if (mask & BlkAttr.NO_SCAN) - noscan.data[dataIndex] &= keep; - if (mask & BlkAttr.APPENDABLE) - appendable.data[dataIndex] &= keep; - if (nointerior.nbits && (mask & BlkAttr.NO_INTERIOR)) - nointerior.data[dataIndex] &= keep; - } - - /** - * - */ - void setBits(size_t biti, uint mask) nothrow - { - // Calculate the mask and bit offset once and then use it to - // set all of the bits we need to set. - immutable dataIndex = biti >> GCBits.BITS_SHIFT; - immutable bitOffset = biti & GCBits.BITS_MASK; - immutable orWith = GCBits.BITS_1 << bitOffset; - - if (mask & BlkAttr.STRUCTFINAL) - { - if (!structFinals.nbits) - structFinals.alloc(mark.nbits); - structFinals.data[dataIndex] |= orWith; - } - - if (mask & BlkAttr.FINALIZE) - { - if (!finals.nbits) - finals.alloc(mark.nbits); - finals.data[dataIndex] |= orWith; - } - - if (mask & BlkAttr.NO_SCAN) - { - noscan.data[dataIndex] |= orWith; - } -// if (mask & BlkAttr.NO_MOVE) -// { -// if (!nomove.nbits) -// nomove.alloc(mark.nbits); -// nomove.data[dataIndex] |= orWith; -// } - if (mask & BlkAttr.APPENDABLE) - { - appendable.data[dataIndex] |= orWith; - } - - if (isLargeObject && (mask & BlkAttr.NO_INTERIOR)) - { - if (!nointerior.nbits) - nointerior.alloc(mark.nbits); - nointerior.data[dataIndex] |= orWith; - } - } - - void freePageBits(size_t pagenum, in ref PageBits toFree) nothrow - { - assert(!isLargeObject); - assert(!nointerior.nbits); // only for large objects - - import core.internal.traits : staticIota; - immutable beg = pagenum * (PAGESIZE / 16 / GCBits.BITS_PER_WORD); - foreach (i; staticIota!(0, PageBits.length)) - { - immutable w = toFree[i]; - if (!w) continue; - - immutable wi = beg + i; - freebits.data[wi] |= w; - noscan.data[wi] &= ~w; - appendable.data[wi] &= ~w; - } - - if (finals.nbits) - { - foreach (i; staticIota!(0, PageBits.length)) - if (toFree[i]) - finals.data[beg + i] &= ~toFree[i]; - } - - if (structFinals.nbits) - { - foreach (i; staticIota!(0, PageBits.length)) - if (toFree[i]) - structFinals.data[beg + i] &= ~toFree[i]; - } - } - - /** - * Given a pointer p in the p, return the pagenum. - */ - size_t pagenumOf(void *p) const nothrow - in - { - assert(p >= baseAddr); - assert(p < topAddr); - } - body - { - return cast(size_t)(p - baseAddr) / PAGESIZE; - } - - @property bool isFree() const pure nothrow - { - return npages == freepages; - } - - size_t slGetSize(void* p) nothrow - { - if (isLargeObject) - return (cast(LargeObjectPool*)&this).getSize(p); - else - return (cast(SmallObjectPool*)&this).getSize(p); - } - - BlkInfo slGetInfo(void* p) nothrow - { - if (isLargeObject) - return (cast(LargeObjectPool*)&this).getInfo(p); - else - return (cast(SmallObjectPool*)&this).getInfo(p); - } - - - void Invariant() const {} - - debug(INVARIANT) - invariant() - { - //mark.Invariant(); - //scan.Invariant(); - //freebits.Invariant(); - //finals.Invariant(); - //structFinals.Invariant(); - //noscan.Invariant(); - //appendable.Invariant(); - //nointerior.Invariant(); - - if (baseAddr) - { - //if (baseAddr + npages * PAGESIZE != topAddr) - //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr); - assert(baseAddr + npages * PAGESIZE == topAddr); - } - - if (pagetable !is null) - { - for (size_t i = 0; i < npages; i++) - { - Bins bin = cast(Bins)pagetable[i]; - assert(bin < B_MAX); - } - } - } -} - -struct LargeObjectPool -{ - Pool base; - alias base this; - - void updateOffsets(size_t fromWhere) nothrow - { - assert(pagetable[fromWhere] == B_PAGE); - size_t pn = fromWhere + 1; - for (uint offset = 1; pn < npages; pn++, offset++) - { - if (pagetable[pn] != B_PAGEPLUS) break; - bPageOffsets[pn] = offset; - } - - // Store the size of the block in bPageOffsets[fromWhere]. - bPageOffsets[fromWhere] = cast(uint) (pn - fromWhere); - } - - /** - * Allocate n pages from Pool. - * Returns OPFAIL on failure. - */ - size_t allocPages(size_t n) nothrow - { - if (largestFree < n || searchStart + n > npages) - return OPFAIL; - - //debug(PRINTF) printf("Pool::allocPages(n = %d)\n", n); - size_t largest = 0; - if (pagetable[searchStart] == B_PAGEPLUS) - { - searchStart -= bPageOffsets[searchStart]; // jump to B_PAGE - searchStart += bPageOffsets[searchStart]; - } - while (searchStart < npages && pagetable[searchStart] == B_PAGE) - searchStart += bPageOffsets[searchStart]; - - for (size_t i = searchStart; i < npages; ) - { - assert(pagetable[i] == B_FREE); - size_t p = 1; - while (p < n && i + p < npages && pagetable[i + p] == B_FREE) - p++; - - if (p == n) - return i; - - if (p > largest) - largest = p; - - i += p; - while (i < npages && pagetable[i] == B_PAGE) - { - // we have the size information, so we skip a whole bunch of pages. - i += bPageOffsets[i]; - } - } - - // not enough free pages found, remember largest free chunk - largestFree = largest; - return OPFAIL; - } - - /** - * Free npages pages starting with pagenum. - */ - void freePages(size_t pagenum, size_t npages) nothrow - { - //memset(&pagetable[pagenum], B_FREE, npages); - if (pagenum < searchStart) - searchStart = pagenum; - - for (size_t i = pagenum; i < npages + pagenum; i++) - { - if (pagetable[i] < B_FREE) - { - freepages++; - } - - pagetable[i] = B_FREE; - } - largestFree = freepages; // invalidate - } - - /** - * Get size of pointer p in pool. - */ - size_t getSize(void *p) const nothrow - in - { - assert(p >= baseAddr); - assert(p < topAddr); - } - body - { - size_t pagenum = pagenumOf(p); - Bins bin = cast(Bins)pagetable[pagenum]; - assert(bin == B_PAGE); - return bPageOffsets[pagenum] * PAGESIZE; - } - - /** - * - */ - BlkInfo getInfo(void* p) nothrow - { - BlkInfo info; - - size_t offset = cast(size_t)(p - baseAddr); - size_t pn = offset / PAGESIZE; - Bins bin = cast(Bins)pagetable[pn]; - - if (bin == B_PAGEPLUS) - pn -= bPageOffsets[pn]; - else if (bin != B_PAGE) - return info; // no info for free pages - - info.base = baseAddr + pn * PAGESIZE; - info.size = bPageOffsets[pn] * PAGESIZE; - - info.attr = getBits(pn); - return info; - } - - void runFinalizers(in void[] segment) nothrow - { - foreach (pn; 0 .. npages) - { - Bins bin = cast(Bins)pagetable[pn]; - if (bin > B_PAGE) - continue; - size_t biti = pn; - - if (!finals.test(biti)) - continue; - - auto p = sentinel_add(baseAddr + pn * PAGESIZE); - size_t size = bPageOffsets[pn] * PAGESIZE - SENTINEL_EXTRA; - uint attr = getBits(biti); - - if (!rt_hasFinalizerInSegment(p, size, attr, segment)) - continue; - - rt_finalizeFromGC(p, size, attr); - - clrBits(biti, ~BlkAttr.NONE); - - if (pn < searchStart) - searchStart = pn; - - debug(COLLECT_PRINTF) printf("\tcollecting big %p\n", p); - //log_free(sentinel_add(p)); - - size_t n = 1; - for (; pn + n < npages; ++n) - if (pagetable[pn + n] != B_PAGEPLUS) - break; - debug (MEMSTOMP) memset(baseAddr + pn * PAGESIZE, 0xF3, n * PAGESIZE); - freePages(pn, n); - } - } -} - - -struct SmallObjectPool -{ - Pool base; - alias base this; - - /** - * Get size of pointer p in pool. - */ - size_t getSize(void *p) const nothrow - in - { - assert(p >= baseAddr); - assert(p < topAddr); - } - body - { - size_t pagenum = pagenumOf(p); - Bins bin = cast(Bins)pagetable[pagenum]; - assert(bin < B_PAGE); - return binsize[bin]; - } - - BlkInfo getInfo(void* p) nothrow - { - BlkInfo info; - size_t offset = cast(size_t)(p - baseAddr); - size_t pn = offset / PAGESIZE; - Bins bin = cast(Bins)pagetable[pn]; - - if (bin >= B_PAGE) - return info; - - info.base = cast(void*)((cast(size_t)p) & notbinsize[bin]); - info.size = binsize[bin]; - offset = info.base - baseAddr; - info.attr = getBits(cast(size_t)(offset >> shiftBy)); - - return info; - } - - void runFinalizers(in void[] segment) nothrow - { - foreach (pn; 0 .. npages) - { - Bins bin = cast(Bins)pagetable[pn]; - if (bin >= B_PAGE) - continue; - - immutable size = binsize[bin]; - auto p = baseAddr + pn * PAGESIZE; - const ptop = p + PAGESIZE; - immutable base = pn * (PAGESIZE/16); - immutable bitstride = size / 16; - - bool freeBits; - PageBits toFree; - - for (size_t i; p < ptop; p += size, i += bitstride) - { - immutable biti = base + i; - - if (!finals.test(biti)) - continue; - - auto q = sentinel_add(p); - uint attr = getBits(biti); - - if (!rt_hasFinalizerInSegment(q, size, attr, segment)) - continue; - - rt_finalizeFromGC(q, size, attr); - - freeBits = true; - toFree.set(i); - - debug(COLLECT_PRINTF) printf("\tcollecting %p\n", p); - //log_free(sentinel_add(p)); - - debug (MEMSTOMP) memset(p, 0xF3, size); - } - - if (freeBits) - freePageBits(pn, toFree); - } - } - - /** - * Allocate a page of bin's. - * Returns: - * head of a single linked list of new entries - */ - List* allocPage(Bins bin) nothrow - { - size_t pn; - for (pn = searchStart; pn < npages; pn++) - if (pagetable[pn] == B_FREE) - goto L1; - - return null; - - L1: - searchStart = pn + 1; - pagetable[pn] = cast(ubyte)bin; - freepages--; - - // Convert page to free list - size_t size = binsize[bin]; - void* p = baseAddr + pn * PAGESIZE; - void* ptop = p + PAGESIZE - size; - auto first = cast(List*) p; - - for (; p < ptop; p += size) - { - (cast(List *)p).next = cast(List *)(p + size); - (cast(List *)p).pool = &base; - } - (cast(List *)p).next = null; - (cast(List *)p).pool = &base; - return first; - } -} - -unittest // bugzilla 14467 -{ - int[] arr = new int[10]; - assert(arr.capacity); - arr = arr[$..$]; - assert(arr.capacity); -} - -unittest // bugzilla 15353 -{ - import core.memory : GC; - - static struct Foo - { - ~this() - { - GC.free(buf); // ignored in finalizer - } - - void* buf; - } - new Foo(GC.malloc(10)); - GC.collect(); -} - -unittest // bugzilla 15822 -{ - import core.memory : GC; - - ubyte[16] buf; - static struct Foo - { - ~this() - { - GC.removeRange(ptr); - GC.removeRoot(ptr); - } - - ubyte* ptr; - } - GC.addRoot(buf.ptr); - GC.addRange(buf.ptr, buf.length); - new Foo(buf.ptr); - GC.collect(); -} - -unittest // bugzilla 1180 -{ - import core.exception; - try - { - size_t x = size_t.max - 100; - byte[] big_buf = new byte[x]; - } - catch (OutOfMemoryError) - { - } -} - -/* ============================ SENTINEL =============================== */ - - -debug (SENTINEL) -{ - const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits - const ubyte SENTINEL_POST = 0xF5; // 8 bits - const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1; - - - inout(size_t*) sentinel_size(inout void *p) nothrow { return &(cast(inout size_t *)p)[-2]; } - inout(size_t*) sentinel_pre(inout void *p) nothrow { return &(cast(inout size_t *)p)[-1]; } - inout(ubyte*) sentinel_post(inout void *p) nothrow { return &(cast(inout ubyte *)p)[*sentinel_size(p)]; } - - - void sentinel_init(void *p, size_t size) nothrow - { - *sentinel_size(p) = size; - *sentinel_pre(p) = SENTINEL_PRE; - *sentinel_post(p) = SENTINEL_POST; - } - - - void sentinel_Invariant(const void *p) nothrow - { - debug - { - assert(*sentinel_pre(p) == SENTINEL_PRE); - assert(*sentinel_post(p) == SENTINEL_POST); - } - else if (*sentinel_pre(p) != SENTINEL_PRE || *sentinel_post(p) != SENTINEL_POST) - onInvalidMemoryOperationError(); // also trigger in release build - } - - - void *sentinel_add(void *p) nothrow - { - return p + 2 * size_t.sizeof; - } - - - void *sentinel_sub(void *p) nothrow - { - return p - 2 * size_t.sizeof; - } -} -else -{ - const uint SENTINEL_EXTRA = 0; - - - void sentinel_init(void *p, size_t size) nothrow - { - } - - - void sentinel_Invariant(const void *p) nothrow - { - } - - - void *sentinel_add(void *p) nothrow - { - return p; - } - - - void *sentinel_sub(void *p) nothrow - { - return p; - } -} - -debug (MEMSTOMP) -unittest -{ - import core.memory; - auto p = cast(uint*)GC.malloc(uint.sizeof*3); - assert(*p == 0xF0F0F0F0); - p[2] = 0; // First two will be used for free list - GC.free(p); - assert(p[2] == 0xF2F2F2F2); -} - -debug (SENTINEL) -unittest -{ - import core.memory; - auto p = cast(ubyte*)GC.malloc(1); - assert(p[-1] == 0xF4); - assert(p[ 1] == 0xF5); -/* - p[1] = 0; - bool thrown; - try - GC.free(p); - catch (Error e) - thrown = true; - p[1] = 0xF5; - assert(thrown); -*/ -} - -unittest -{ - import core.memory; - - // https://issues.dlang.org/show_bug.cgi?id=9275 - GC.removeRoot(null); - GC.removeRoot(cast(void*)13); -} - -// improve predictability of coverage of code that is eventually not hit by other tests -unittest -{ - import core.memory; - auto p = GC.malloc(260 << 20); // new pool has 390 MB - auto q = GC.malloc(65 << 20); // next chunk (larger than 64MB to ensure the same pool is used) - auto r = GC.malloc(65 << 20); // another chunk in same pool - assert(p + (260 << 20) == q); - assert(q + (65 << 20) == r); - GC.free(q); - // should trigger "assert(bin == B_FREE);" in mark due to dangling pointer q: - GC.collect(); - // should trigger "break;" in extendNoSync: - size_t sz = GC.extend(p, 64 << 20, 66 << 20); // trigger size after p large enough (but limited) - assert(sz == 325 << 20); - GC.free(p); - GC.free(r); - r = q; // ensure q is not trashed before collection above - - p = GC.malloc(70 << 20); // from the same pool - q = GC.malloc(70 << 20); - r = GC.malloc(70 << 20); - auto s = GC.malloc(70 << 20); - auto t = GC.malloc(70 << 20); // 350 MB of 390 MB used - assert(p + (70 << 20) == q); - assert(q + (70 << 20) == r); - assert(r + (70 << 20) == s); - assert(s + (70 << 20) == t); - GC.free(r); // ensure recalculation of largestFree in nxxt allocPages - auto z = GC.malloc(75 << 20); // needs new pool - - GC.free(p); - GC.free(q); - GC.free(s); - GC.free(t); - GC.free(z); - GC.minimize(); // release huge pool -} - diff --git a/libphobos/libdruntime/gc/impl/manual/gc.d b/libphobos/libdruntime/gc/impl/manual/gc.d deleted file mode 100644 index f5c6b77..0000000 --- a/libphobos/libdruntime/gc/impl/manual/gc.d +++ /dev/null @@ -1,274 +0,0 @@ -/** - * This module contains a minimal garbage collector implementation according to - * published requirements. This library is mostly intended to serve as an - * example, but it is usable in applications which do not rely on a garbage - * collector to clean up memory (ie. when dynamic array resizing is not used, - * and all memory allocated with 'new' is freed deterministically with - * 'delete'). - * - * Please note that block attribute data must be tracked, or at a minimum, the - * FINALIZE bit must be tracked for any allocated memory block because calling - * rt_finalize on a non-object block can result in an access violation. In the - * allocator below, this tracking is done via a leading uint bitmask. A real - * allocator may do better to store this data separately, similar to the basic - * GC. - * - * Copyright: Copyright Sean Kelly 2005 - 2016. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Sean Kelly - */ - -/* Copyright Sean Kelly 2005 - 2016. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module gc.impl.manual.gc; - -import gc.config; -import gc.gcinterface; - -import rt.util.container.array; - -import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc; -static import core.memory; - -extern (C) void onOutOfMemoryError(void* pretend_sideffect = null) @trusted pure nothrow @nogc; /* dmd @@@BUG11461@@@ */ - -class ManualGC : GC -{ - __gshared Array!Root roots; - __gshared Array!Range ranges; - - static void initialize(ref GC gc) - { - import core.stdc.string; - - if (config.gc != "manual") - return; - - auto p = cstdlib.malloc(__traits(classInstanceSize, ManualGC)); - if (!p) - onOutOfMemoryError(); - - auto init = typeid(ManualGC).initializer(); - assert(init.length == __traits(classInstanceSize, ManualGC)); - auto instance = cast(ManualGC) memcpy(p, init.ptr, init.length); - instance.__ctor(); - - gc = instance; - } - - static void finalize(ref GC gc) - { - if (config.gc != "manual") - return; - - auto instance = cast(ManualGC) gc; - instance.Dtor(); - cstdlib.free(cast(void*) instance); - } - - this() - { - } - - void Dtor() - { - } - - void enable() - { - } - - void disable() - { - } - - void collect() nothrow - { - } - - void collectNoStack() nothrow - { - } - - void minimize() nothrow - { - } - - uint getAttr(void* p) nothrow - { - return 0; - } - - uint setAttr(void* p, uint mask) nothrow - { - return 0; - } - - uint clrAttr(void* p, uint mask) nothrow - { - return 0; - } - - void* malloc(size_t size, uint bits, const TypeInfo ti) nothrow - { - void* p = cstdlib.malloc(size); - - if (size && p is null) - onOutOfMemoryError(); - return p; - } - - BlkInfo qalloc(size_t size, uint bits, const TypeInfo ti) nothrow - { - BlkInfo retval; - retval.base = malloc(size, bits, ti); - retval.size = size; - retval.attr = bits; - return retval; - } - - void* calloc(size_t size, uint bits, const TypeInfo ti) nothrow - { - void* p = cstdlib.calloc(1, size); - - if (size && p is null) - onOutOfMemoryError(); - return p; - } - - void* realloc(void* p, size_t size, uint bits, const TypeInfo ti) nothrow - { - p = cstdlib.realloc(p, size); - - if (size && p is null) - onOutOfMemoryError(); - return p; - } - - size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow - { - return 0; - } - - size_t reserve(size_t size) nothrow - { - return 0; - } - - void free(void* p) nothrow - { - cstdlib.free(p); - } - - /** - * Determine the base address of the block containing p. If p is not a gc - * allocated pointer, return null. - */ - void* addrOf(void* p) nothrow - { - return null; - } - - /** - * Determine the allocated size of pointer p. If p is an interior pointer - * or not a gc allocated pointer, return 0. - */ - size_t sizeOf(void* p) nothrow - { - return 0; - } - - /** - * Determine the base address of the block containing p. If p is not a gc - * allocated pointer, return null. - */ - BlkInfo query(void* p) nothrow - { - return BlkInfo.init; - } - - core.memory.GC.Stats stats() nothrow - { - return typeof(return).init; - } - - void addRoot(void* p) nothrow @nogc - { - roots.insertBack(Root(p)); - } - - void removeRoot(void* p) nothrow @nogc - { - foreach (ref r; roots) - { - if (r is p) - { - r = roots.back; - roots.popBack(); - return; - } - } - assert(false); - } - - @property RootIterator rootIter() return @nogc - { - return &rootsApply; - } - - private int rootsApply(scope int delegate(ref Root) nothrow dg) - { - foreach (ref r; roots) - { - if (auto result = dg(r)) - return result; - } - return 0; - } - - void addRange(void* p, size_t sz, const TypeInfo ti = null) nothrow @nogc - { - ranges.insertBack(Range(p, p + sz, cast() ti)); - } - - void removeRange(void* p) nothrow @nogc - { - foreach (ref r; ranges) - { - if (r.pbot is p) - { - r = ranges.back; - ranges.popBack(); - return; - } - } - assert(false); - } - - @property RangeIterator rangeIter() return @nogc - { - return &rangesApply; - } - - private int rangesApply(scope int delegate(ref Range) nothrow dg) - { - foreach (ref r; ranges) - { - if (auto result = dg(r)) - return result; - } - return 0; - } - - void runFinalizers(in void[] segment) nothrow - { - } - - bool inFinalizer() nothrow - { - return false; - } -} diff --git a/libphobos/libdruntime/gc/os.d b/libphobos/libdruntime/gc/os.d deleted file mode 100644 index 337f134..0000000 --- a/libphobos/libdruntime/gc/os.d +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Contains OS-level routines needed by the garbage collector. - * - * Copyright: Copyright Digital Mars 2005 - 2013. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, David Friedman, Sean Kelly, Leandro Lucarella - */ - -/* Copyright Digital Mars 2005 - 2013. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module gc.os; - - -version (Windows) -{ - import core.sys.windows.winbase : GetCurrentThreadId, VirtualAlloc, VirtualFree; - import core.sys.windows.winnt : MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE; - - alias int pthread_t; - - pthread_t pthread_self() nothrow - { - return cast(pthread_t) GetCurrentThreadId(); - } - - //version = GC_Use_Alloc_Win32; -} -else version (Posix) -{ - version (OSX) - version = Darwin; - else version (iOS) - version = Darwin; - else version (TVOS) - version = Darwin; - else version (WatchOS) - version = Darwin; - - import core.sys.posix.sys.mman; - version (FreeBSD) import core.sys.freebsd.sys.mman : MAP_ANON; - version (DragonFlyBSD) import core.sys.dragonflybsd.sys.mman : MAP_ANON; - version (NetBSD) import core.sys.netbsd.sys.mman : MAP_ANON; - version (OpenBSD) import core.sys.openbsd.sys.mman : MAP_ANON; - version (CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON; - version (Darwin) import core.sys.darwin.sys.mman : MAP_ANON; - version (CRuntime_UClibc) import core.sys.linux.sys.mman : MAP_ANON; - import core.stdc.stdlib; - - //version = GC_Use_Alloc_MMap; -} -else -{ - import core.stdc.stdlib; - - //version = GC_Use_Alloc_Malloc; -} - -/+ -static if (is(typeof(VirtualAlloc))) - version = GC_Use_Alloc_Win32; -else static if (is(typeof(mmap))) - version = GC_Use_Alloc_MMap; -else static if (is(typeof(valloc))) - version = GC_Use_Alloc_Valloc; -else static if (is(typeof(malloc))) - version = GC_Use_Alloc_Malloc; -else static assert(false, "No supported allocation methods available."); -+/ - -static if (is(typeof(VirtualAlloc))) // version (GC_Use_Alloc_Win32) -{ - /** - * Map memory. - */ - void *os_mem_map(size_t nbytes) nothrow - { - return VirtualAlloc(null, nbytes, MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE); - } - - - /** - * Unmap memory allocated with os_mem_map(). - * Returns: - * 0 success - * !=0 failure - */ - int os_mem_unmap(void *base, size_t nbytes) nothrow - { - return cast(int)(VirtualFree(base, 0, MEM_RELEASE) == 0); - } -} -else static if (is(typeof(mmap))) // else version (GC_Use_Alloc_MMap) -{ - void *os_mem_map(size_t nbytes) nothrow - { void *p; - - p = mmap(null, nbytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - return (p == MAP_FAILED) ? null : p; - } - - - int os_mem_unmap(void *base, size_t nbytes) nothrow - { - return munmap(base, nbytes); - } -} -else static if (is(typeof(valloc))) // else version (GC_Use_Alloc_Valloc) -{ - void *os_mem_map(size_t nbytes) nothrow - { - return valloc(nbytes); - } - - - int os_mem_unmap(void *base, size_t nbytes) nothrow - { - free(base); - return 0; - } -} -else static if (is(typeof(malloc))) // else version (GC_Use_Alloc_Malloc) -{ - // NOTE: This assumes malloc granularity is at least (void*).sizeof. If - // (req_size + PAGESIZE) is allocated, and the pointer is rounded up - // to PAGESIZE alignment, there will be space for a void* at the end - // after PAGESIZE bytes used by the GC. - - - import gc.gc; - - - const size_t PAGE_MASK = PAGESIZE - 1; - - - void *os_mem_map(size_t nbytes) nothrow - { byte *p, q; - p = cast(byte *) malloc(nbytes + PAGESIZE); - q = p + ((PAGESIZE - ((cast(size_t) p & PAGE_MASK))) & PAGE_MASK); - * cast(void**)(q + nbytes) = p; - return q; - } - - - int os_mem_unmap(void *base, size_t nbytes) nothrow - { - free( *cast(void**)( cast(byte*) base + nbytes ) ); - return 0; - } -} -else -{ - static assert(false, "No supported allocation methods available."); -} - -/** - Check for any kind of memory pressure. - - Params: - mapped = the amount of memory mapped by the GC in bytes - Returns: - true if memory is scarce -*/ -// TOOD: get virtual mem sizes and current usage from OS -// TODO: compare current RSS and avail. physical memory -version (Windows) -{ - bool isLowOnMem(size_t mapped) nothrow @nogc - { - version (D_LP64) - return false; - else - { - import core.sys.windows.winbase : GlobalMemoryStatus, MEMORYSTATUS; - MEMORYSTATUS stat; - GlobalMemoryStatus(&stat); - // Less than 5 % of virtual address space available - return stat.dwAvailVirtual < stat.dwTotalVirtual / 20; - } - } -} -else version (Darwin) -{ - bool isLowOnMem(size_t mapped) nothrow @nogc - { - enum GB = 2 ^^ 30; - version (D_LP64) - return false; - else - { - // 80 % of available 4GB is used for GC (excluding malloc and mmap) - enum size_t limit = 4UL * GB * 8 / 10; - return mapped > limit; - } - } -} -else -{ - bool isLowOnMem(size_t mapped) nothrow @nogc - { - enum GB = 2 ^^ 30; - version (D_LP64) - return false; - else - { - // be conservative and assume 3GB - enum size_t limit = 3UL * GB * 8 / 10; - return mapped > limit; - } - } -} diff --git a/libphobos/libdruntime/gc/pooltable.d b/libphobos/libdruntime/gc/pooltable.d deleted file mode 100644 index cfbe465..0000000 --- a/libphobos/libdruntime/gc/pooltable.d +++ /dev/null @@ -1,285 +0,0 @@ -/** - * A sorted array to quickly lookup pools. - * - * Copyright: Copyright Digital Mars 2001 -. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, David Friedman, Sean Kelly, Martin Nowak - */ -module gc.pooltable; - -static import cstdlib=core.stdc.stdlib; - -struct PoolTable(Pool) -{ - import core.stdc.string : memmove; - -nothrow: - void Dtor() - { - cstdlib.free(pools); - pools = null; - npools = 0; - } - - bool insert(Pool* pool) - { - auto newpools = cast(Pool **)cstdlib.realloc(pools, (npools + 1) * pools[0].sizeof); - if (!newpools) - return false; - - pools = newpools; - - // Sort pool into newpooltable[] - size_t i; - for (; i < npools; ++i) - { - if (pool.baseAddr < pools[i].baseAddr) - break; - } - if (i != npools) - memmove(pools + i + 1, pools + i, (npools - i) * pools[0].sizeof); - pools[i] = pool; - - ++npools; - - _minAddr = pools[0].baseAddr; - _maxAddr = pools[npools - 1].topAddr; - - return true; - } - - @property size_t length() pure const - { - return npools; - } - - ref inout(Pool*) opIndex(size_t idx) inout pure - in { assert(idx < length); } - body - { - return pools[idx]; - } - - inout(Pool*)[] opSlice(size_t a, size_t b) inout pure - in { assert(a <= length && b <= length); } - body - { - return pools[a .. b]; - } - - alias opDollar = length; - - /** - * Find Pool that pointer is in. - * Return null if not in a Pool. - * Assume pooltable[] is sorted. - */ - Pool *findPool(void *p) nothrow - { - if (p >= minAddr && p < maxAddr) - { - assert(npools); - - // let dmd allocate a register for this.pools - auto pools = this.pools; - - if (npools == 1) - return pools[0]; - - /* The pooltable[] is sorted by address, so do a binary search - */ - size_t low = 0; - size_t high = npools - 1; - while (low <= high) - { - size_t mid = (low + high) >> 1; - auto pool = pools[mid]; - if (p < pool.baseAddr) - high = mid - 1; - else if (p >= pool.topAddr) - low = mid + 1; - else - return pool; - } - } - return null; - } - - // semi-stable partition, returns right half for which pred is false - Pool*[] minimize() pure - { - static void swap(ref Pool* a, ref Pool* b) - { - auto c = a; a = b; b = c; - } - - size_t i; - // find first bad entry - for (; i < npools; ++i) - if (pools[i].isFree) break; - - // move good in front of bad entries - size_t j = i + 1; - for (; j < npools; ++j) - { - if (!pools[j].isFree) // keep - swap(pools[i++], pools[j]); - } - // npooltable[0 .. i] => used pools - // npooltable[i .. npools] => free pools - - if (i) - { - _minAddr = pools[0].baseAddr; - _maxAddr = pools[i - 1].topAddr; - } - else - { - _minAddr = _maxAddr = null; - } - - immutable len = npools; - npools = i; - // return freed pools to the caller - return pools[npools .. len]; - } - - void Invariant() const - { - if (!npools) return; - - foreach (i, pool; pools[0 .. npools - 1]) - assert(pool.baseAddr < pools[i + 1].baseAddr); - - assert(_minAddr == pools[0].baseAddr); - assert(_maxAddr == pools[npools - 1].topAddr); - } - - @property const(void)* minAddr() pure const { return _minAddr; } - @property const(void)* maxAddr() pure const { return _maxAddr; } - -package: - Pool** pools; - size_t npools; - void* _minAddr, _maxAddr; -} - -unittest -{ - enum NPOOLS = 6; - enum NPAGES = 10; - enum PAGESIZE = 4096; - - static struct MockPool - { - byte* baseAddr, topAddr; - size_t freepages, npages; - @property bool isFree() const pure nothrow { return freepages == npages; } - } - PoolTable!MockPool pooltable; - - void reset() - { - foreach (ref pool; pooltable[0 .. $]) - pool.freepages = pool.npages; - pooltable.minimize(); - assert(pooltable.length == 0); - - foreach (i; 0 .. NPOOLS) - { - auto pool = cast(MockPool*)cstdlib.malloc(MockPool.sizeof); - *pool = MockPool.init; - assert(pooltable.insert(pool)); - } - } - - void usePools() - { - foreach (pool; pooltable[0 .. $]) - { - pool.npages = NPAGES; - pool.freepages = NPAGES / 2; - } - } - - // all pools are free - reset(); - assert(pooltable.length == NPOOLS); - auto freed = pooltable.minimize(); - assert(freed.length == NPOOLS); - assert(pooltable.length == 0); - - // all pools used - reset(); - usePools(); - assert(pooltable.length == NPOOLS); - freed = pooltable.minimize(); - assert(freed.length == 0); - assert(pooltable.length == NPOOLS); - - // preserves order of used pools - reset(); - usePools(); - - { - MockPool*[NPOOLS] opools = pooltable[0 .. NPOOLS]; - // make the 2nd pool free - pooltable[2].freepages = NPAGES; - - pooltable.minimize(); - assert(pooltable.length == NPOOLS - 1); - assert(pooltable[0] == opools[0]); - assert(pooltable[1] == opools[1]); - assert(pooltable[2] == opools[3]); - } - - // test that PoolTable reduces min/max address span - reset(); - usePools(); - - byte* base, top; - - { - // fill with fake addresses - size_t i; - foreach (pool; pooltable[0 .. NPOOLS]) - { - pool.baseAddr = cast(byte*)(i++ * NPAGES * PAGESIZE); - pool.topAddr = pool.baseAddr + NPAGES * PAGESIZE; - } - base = pooltable[0].baseAddr; - top = pooltable[NPOOLS - 1].topAddr; - } - - freed = pooltable.minimize(); - assert(freed.length == 0); - assert(pooltable.length == NPOOLS); - assert(pooltable.minAddr == base); - assert(pooltable.maxAddr == top); - - pooltable[NPOOLS - 1].freepages = NPAGES; - pooltable[NPOOLS - 2].freepages = NPAGES; - - freed = pooltable.minimize(); - assert(freed.length == 2); - assert(pooltable.length == NPOOLS - 2); - assert(pooltable.minAddr == base); - assert(pooltable.maxAddr == pooltable[NPOOLS - 3].topAddr); - - pooltable[0].freepages = NPAGES; - - freed = pooltable.minimize(); - assert(freed.length == 1); - assert(pooltable.length == NPOOLS - 3); - assert(pooltable.minAddr != base); - assert(pooltable.minAddr == pooltable[0].baseAddr); - assert(pooltable.maxAddr == pooltable[NPOOLS - 4].topAddr); - - // free all - foreach (pool; pooltable[0 .. $]) - pool.freepages = NPAGES; - freed = pooltable.minimize(); - assert(freed.length == NPOOLS - 3); - assert(pooltable.length == 0); - pooltable.Dtor(); -} diff --git a/libphobos/libdruntime/gc/proxy.d b/libphobos/libdruntime/gc/proxy.d deleted file mode 100644 index 794da90..0000000 --- a/libphobos/libdruntime/gc/proxy.d +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Contains the external GC interface. - * - * Copyright: Copyright Digital Mars 2005 - 2016. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, Sean Kelly - */ - -/* Copyright Digital Mars 2005 - 2016. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module gc.proxy; - -import gc.impl.conservative.gc; -import gc.impl.manual.gc; -import gc.config; -import gc.gcinterface; - -static import core.memory; - -private -{ - static import core.memory; - alias BlkInfo = core.memory.GC.BlkInfo; - - extern (C) void thread_init(); - extern (C) void thread_term(); - - __gshared GC instance; - __gshared GC proxiedGC; // used to iterate roots of Windows DLLs - -} - - -extern (C) -{ - - void gc_init() - { - config.initialize(); - ManualGC.initialize(instance); - ConservativeGC.initialize(instance); - if (instance is null) - { - import core.stdc.stdio : fprintf, stderr; - import core.stdc.stdlib : exit; - - fprintf(stderr, "No GC was initialized, please recheck the name of the selected GC ('%.*s').\n", cast(int)config.gc.length, config.gc.ptr); - exit(1); - } - - // NOTE: The GC must initialize the thread library - // before its first collection. - thread_init(); - } - - void gc_term() - { - // NOTE: There may be daemons threads still running when this routine is - // called. If so, cleaning memory out from under then is a good - // way to make them crash horribly. This probably doesn't matter - // much since the app is supposed to be shutting down anyway, but - // I'm disabling cleanup for now until I can think about it some - // more. - // - // NOTE: Due to popular demand, this has been re-enabled. It still has - // the problems mentioned above though, so I guess we'll see. - - instance.collectNoStack(); // not really a 'collect all' -- still scans - // static data area, roots, and ranges. - - thread_term(); - - ManualGC.finalize(instance); - ConservativeGC.finalize(instance); - } - - void gc_enable() - { - instance.enable(); - } - - void gc_disable() - { - instance.disable(); - } - - void gc_collect() nothrow - { - instance.collect(); - } - - void gc_minimize() nothrow - { - instance.minimize(); - } - - uint gc_getAttr( void* p ) nothrow - { - return instance.getAttr(p); - } - - uint gc_setAttr( void* p, uint a ) nothrow - { - return instance.setAttr(p, a); - } - - uint gc_clrAttr( void* p, uint a ) nothrow - { - return instance.clrAttr(p, a); - } - - void* gc_malloc( size_t sz, uint ba = 0, const TypeInfo ti = null ) nothrow - { - return instance.malloc(sz, ba, ti); - } - - BlkInfo gc_qalloc( size_t sz, uint ba = 0, const TypeInfo ti = null ) nothrow - { - return instance.qalloc( sz, ba, ti ); - } - - void* gc_calloc( size_t sz, uint ba = 0, const TypeInfo ti = null ) nothrow - { - return instance.calloc( sz, ba, ti ); - } - - void* gc_realloc( void* p, size_t sz, uint ba = 0, const TypeInfo ti = null ) nothrow - { - return instance.realloc( p, sz, ba, ti ); - } - - size_t gc_extend( void* p, size_t mx, size_t sz, const TypeInfo ti = null ) nothrow - { - return instance.extend( p, mx, sz,ti ); - } - - size_t gc_reserve( size_t sz ) nothrow - { - return instance.reserve( sz ); - } - - void gc_free( void* p ) nothrow - { - return instance.free( p ); - } - - void* gc_addrOf( void* p ) nothrow - { - return instance.addrOf( p ); - } - - size_t gc_sizeOf( void* p ) nothrow - { - return instance.sizeOf( p ); - } - - BlkInfo gc_query( void* p ) nothrow - { - return instance.query( p ); - } - - core.memory.GC.Stats gc_stats() nothrow - { - return instance.stats(); - } - - void gc_addRoot( void* p ) nothrow - { - return instance.addRoot( p ); - } - - void gc_addRange( void* p, size_t sz, const TypeInfo ti = null ) nothrow - { - return instance.addRange( p, sz, ti ); - } - - void gc_removeRoot( void* p ) nothrow - { - return instance.removeRoot( p ); - } - - void gc_removeRange( void* p ) nothrow - { - return instance.removeRange( p ); - } - - void gc_runFinalizers( in void[] segment ) nothrow - { - return instance.runFinalizers( segment ); - } - - bool gc_inFinalizer() nothrow - { - return instance.inFinalizer(); - } - - GC gc_getProxy() nothrow - { - return instance; - } - - export - { - void gc_setProxy( GC proxy ) - { - foreach (root; instance.rootIter) - { - proxy.addRoot(root); - } - - foreach (range; instance.rangeIter) - { - proxy.addRange(range.pbot, range.ptop - range.pbot, range.ti); - } - - proxiedGC = instance; // remember initial GC to later remove roots - instance = proxy; - } - - void gc_clrProxy() - { - foreach (root; proxiedGC.rootIter) - { - instance.removeRoot(root); - } - - foreach (range; proxiedGC.rangeIter) - { - instance.removeRange(range); - } - - instance = proxiedGC; - proxiedGC = null; - } - } -} diff --git a/libphobos/libdruntime/gcc/deh.d b/libphobos/libdruntime/gcc/deh.d index bbc351c..85322db 100644 --- a/libphobos/libdruntime/gcc/deh.d +++ b/libphobos/libdruntime/gcc/deh.d @@ -32,8 +32,8 @@ import gcc.attributes; extern(C) { - int _d_isbaseof(ClassInfo, ClassInfo); - void _d_createTrace(Object, void*); + int _d_isbaseof(ClassInfo, ClassInfo) @nogc nothrow pure @safe; + void _d_createTrace(Throwable, void*); void _d_print_throwable(Throwable t); } @@ -432,6 +432,9 @@ extern(C) void* __gdc_begin_catch(_Unwind_Exception* unwindHeader) ExceptionHeader* header = ExceptionHeader.toExceptionHeader(unwindHeader); void* objectp = cast(void*)header.object; + // Remove our reference to the exception. We should not decrease its refcount, + // because we pass the object on to the caller. + header.object = null; // Something went wrong when stacking up chained headers... if (header != ExceptionHeader.pop()) @@ -455,6 +458,11 @@ extern(C) void _d_throw(Throwable object) // Add to thrown exception stack. eh.push(); + // Increment reference count if object is a refcounted Throwable. + auto refcount = object.refcount(); + if (refcount) + object.refcount() = refcount + 1; + // Called by unwinder when exception object needs destruction by other than our code. extern(C) void exception_cleanup(_Unwind_Reason_Code code, _Unwind_Exception* exc) { @@ -976,14 +984,10 @@ private _Unwind_Reason_Code __gdc_personality(_Unwind_Action actions, if (currentLsd != nextLsd) break; - // Add our object onto the end of the existing chain. - Throwable n = ehn.object; - while (n.next) - n = n.next; - n.next = eh.object; + // Add our object onto the end of the existing chain and replace + // our exception object with in-flight one. + eh.object = Throwable.chainTogether(ehn.object, eh.object); - // Replace our exception object with in-flight one - eh.object = ehn.object; if (nextHandler != handler && !bypassed) { handler = nextHandler; diff --git a/libphobos/libdruntime/gcc/emutls.d b/libphobos/libdruntime/gcc/emutls.d index 4622305..e0eab8c 100644 --- a/libphobos/libdruntime/gcc/emutls.d +++ b/libphobos/libdruntime/gcc/emutls.d @@ -25,7 +25,8 @@ module gcc.emutls; import core.atomic, core.stdc.stdlib, core.stdc.string, core.sync.mutex; -import rt.util.container.array, rt.util.container.hashtab; +import core.internal.container.array; +import core.internal.container.hashtab; import core.internal.traits : classInstanceAlignment; import gcc.builtins, gcc.gthread; diff --git a/libphobos/libdruntime/gcc/sections/elf.d b/libphobos/libdruntime/gcc/sections/elf.d index 3480fb9..9662cdd 100644 --- a/libphobos/libdruntime/gcc/sections/elf.d +++ b/libphobos/libdruntime/gcc/sections/elf.d @@ -90,8 +90,8 @@ import core.sys.posix.pthread; import rt.deh; import rt.dmain2; import rt.minfo; -import rt.util.container.array; -import rt.util.container.hashtab; +import core.internal.container.array; +import core.internal.container.hashtab; import gcc.builtins; import gcc.config; import gcc.sections.common; @@ -124,7 +124,7 @@ struct DSO return _moduleGroup.modules; } - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc + @property ref inout(ModuleGroup) moduleGroup() inout return nothrow @nogc { return _moduleGroup; } diff --git a/libphobos/libdruntime/gcc/sections/macho.d b/libphobos/libdruntime/gcc/sections/macho.d index 3ce58a53..e6e7966 100644 --- a/libphobos/libdruntime/gcc/sections/macho.d +++ b/libphobos/libdruntime/gcc/sections/macho.d @@ -31,8 +31,8 @@ import core.sys.darwin.mach.dyld; import core.sys.darwin.mach.getsect; import core.sys.posix.pthread; import rt.minfo; -import rt.util.container.array; -import rt.util.container.hashtab; +import core.internal.container.array; +import core.internal.container.hashtab; import gcc.sections.common; version (GNU_EMUTLS) @@ -66,7 +66,7 @@ struct DSO return _moduleGroup.modules; } - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc + @property ref inout(ModuleGroup) moduleGroup() inout return nothrow @nogc { return _moduleGroup; } diff --git a/libphobos/libdruntime/gcc/sections/pecoff.d b/libphobos/libdruntime/gcc/sections/pecoff.d index ed0340e..7361349 100644 --- a/libphobos/libdruntime/gcc/sections/pecoff.d +++ b/libphobos/libdruntime/gcc/sections/pecoff.d @@ -30,8 +30,8 @@ import core.sys.windows.winbase; import core.sys.windows.windef; import core.sys.windows.winnt; import rt.minfo; -import rt.util.container.array; -import rt.util.container.hashtab; +import core.internal.container.array; +import core.internal.container.hashtab; import gcc.sections.common; version (GNU_EMUTLS) @@ -65,7 +65,7 @@ struct DSO return _moduleGroup.modules; } - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc + @property ref inout(ModuleGroup) moduleGroup() inout return nothrow @nogc { return _moduleGroup; } diff --git a/libphobos/libdruntime/object.d b/libphobos/libdruntime/object.d index e96d1c4..151755f 100644 --- a/libphobos/libdruntime/object.d +++ b/libphobos/libdruntime/object.d @@ -1,21 +1,62 @@ /** + * $(SCRIPT inhibitQuickIndex = 1;) + * $(DIVC quickindex, + * $(BOOKTABLE, + * $(TR $(TH Category) $(TH Symbols)) + * $(TR $(TD Arrays) $(TD + * $(MYREF assumeSafeAppend) + * $(MYREF capacity) + * $(MYREF idup) + * $(MYREF reserve) + * )) + * $(TR $(TD Associative arrays) $(TD + * $(MYREF byKey) + * $(MYREF byKeyValue) + * $(MYREF byValue) + * $(MYREF clear) + * $(MYREF get) + * $(MYREF keys) + * $(MYREF rehash) + * $(MYREF require) + * $(MYREF update) + * $(MYREF values) + * )) + * $(TR $(TD General) $(TD + * $(MYREF destroy) + * $(MYREF dup) + * $(MYREF hashOf) + * $(MYREF opEquals) + * )) + * $(TR $(TD Types) $(TD + * $(MYREF Error) + * $(MYREF Exception) + * $(MYREF noreturn) + * $(MYREF Object) + * $(MYREF Throwable) + * )) + * $(TR $(TD Type info) $(TD + * $(MYREF Interface) + * $(MYREF ModuleInfo) + * $(MYREF OffsetTypeInfo) + * $(MYREF RTInfoImpl) + * $(MYREF rtinfoNoPointers) + * $(MYREF TypeInfo) + * $(MYREF TypeInfo_Class) + * )) + * )) + * * Forms the symbols available to all D programs. Includes Object, which is * the root of the class object hierarchy. This module is implicitly * imported. * * Copyright: Copyright Digital Mars 2000 - 2011. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright, Sean Kelly + * Source: $(DRUNTIMESRC object.d) */ module object; -private -{ - extern (C) Object _d_newclass(const TypeInfo_Class ci); - extern (C) void rt_finalize(void *data, bool det=true); -} - alias size_t = typeof(int.sizeof); alias ptrdiff_t = typeof(cast(void*)0 - cast(void*)0); @@ -29,7 +70,12 @@ alias string = immutable(char)[]; alias wstring = immutable(wchar)[]; alias dstring = immutable(dchar)[]; -version (D_ObjectiveC) public import core.attribute : selector; +version (D_ObjectiveC) +{ + deprecated("explicitly import `selector` instead using: `import core.attribute : selector;`") + public import core.attribute : selector; +} +version (Posix) public import core.attribute : gnuAbiTag; // Some ABIs use a complex varargs implementation requiring TypeInfo.argTypes(). version (GNU) @@ -65,6 +111,20 @@ class Object return typeid(this).name; } + @system unittest + { + enum unittest_sym_name = __traits(identifier, __traits(parent, (){})); + enum fqn_unittest = "object.Object." ~ unittest_sym_name; // object.__unittest_LX_CY + + class C {} + + Object obj = new Object; + C c = new C; + + assert(obj.toString() == "object.Object"); + assert(c.toString() == fqn_unittest ~ ".C"); + } + /** * Compute hash function for Object. */ @@ -100,6 +160,23 @@ class Object //return this !is o; } + @system unittest + { + Object obj = new Object; + + bool gotCaught; + try + { + obj.opCmp(new Object); + } + catch (Exception e) + { + gotCaught = true; + assert(e.msg == "need opCmp for class object.Object"); + } + assert(gotCaught); + } + /** * Test whether $(D this) is equal to $(D o). * The default implementation only compares by identity (using the $(D is) operator). @@ -149,9 +226,18 @@ class Object } return null; } + + @system unittest + { + Object valid_obj = Object.factory("object.Object"); + Object invalid_obj = Object.factory("object.__this_class_doesnt_exist__"); + + assert(valid_obj !is null); + assert(invalid_obj is null); + } } -auto opEquals(Object lhs, Object rhs) +bool opEquals(Object lhs, Object rhs) { // If aliased to the same object or both null => equal if (lhs is rhs) return true; @@ -159,6 +245,8 @@ auto opEquals(Object lhs, Object rhs) // If either is null => non-equal if (lhs is null || rhs is null) return false; + if (!lhs.opEquals(rhs)) return false; + // If same exact type => one call to method opEquals if (typeid(lhs) is typeid(rhs) || !__ctfe && typeid(lhs).opEquals(typeid(rhs))) @@ -166,22 +254,116 @@ auto opEquals(Object lhs, Object rhs) (issue 7147). But CTFE also guarantees that equal TypeInfos are always identical. So, no opEquals needed during CTFE. */ { - return lhs.opEquals(rhs); + return true; } // General case => symmetric calls to method opEquals - return lhs.opEquals(rhs) && rhs.opEquals(lhs); + return rhs.opEquals(lhs); } /************************ * Returns true if lhs and rhs are equal. */ -auto opEquals(const Object lhs, const Object rhs) +bool opEquals(const Object lhs, const Object rhs) { // A hack for the moment. return opEquals(cast()lhs, cast()rhs); } +/// If aliased to the same object or both null => equal +@system unittest +{ + class F { int flag; this(int flag) { this.flag = flag; } } + + F f; + assert(f == f); // both null + f = new F(1); + assert(f == f); // both aliased to the same object +} + +/// If either is null => non-equal +@system unittest +{ + class F { int flag; this(int flag) { this.flag = flag; } } + F f; + assert(!(new F(0) == f)); + assert(!(f == new F(0))); +} + +/// If same exact type => one call to method opEquals +@system unittest +{ + class F + { + int flag; + + this(int flag) + { + this.flag = flag; + } + + override bool opEquals(const Object o) + { + return flag == (cast(F) o).flag; + } + } + + F f; + assert(new F(0) == new F(0)); + assert(!(new F(0) == new F(1))); +} + +/// General case => symmetric calls to method opEquals +@system unittest +{ + int fEquals, gEquals; + + class Base + { + int flag; + this(int flag) + { + this.flag = flag; + } + } + + class F : Base + { + this(int flag) { super(flag); } + + override bool opEquals(const Object o) + { + fEquals++; + return flag == (cast(Base) o).flag; + } + } + + class G : Base + { + this(int flag) { super(flag); } + + override bool opEquals(const Object o) + { + gEquals++; + return flag == (cast(Base) o).flag; + } + } + + assert(new F(1) == new G(1)); + assert(fEquals == 1); + assert(gEquals == 1); +} + +// To cover const Object opEquals +@system unittest +{ + const Object obj1 = new Object; + const Object obj2 = new Object; + + assert(obj1 == obj1); + assert(obj1 != obj2); +} + private extern(C) void _d_setSameMutex(shared Object ownee, shared Object owner) nothrow; void setSameMutex(shared Object ownee, shared Object owner) @@ -189,6 +371,24 @@ void setSameMutex(shared Object ownee, shared Object owner) _d_setSameMutex(ownee, owner); } +@system unittest +{ + shared Object obj1 = new Object; + synchronized class C + { + void bar() {} + } + shared C obj2 = new shared(C); + obj2.bar(); + + assert(obj1.__monitor != obj2.__monitor); + assert(obj1.__monitor is null); + + setSameMutex(obj1, obj2); + assert(obj1.__monitor == obj2.__monitor); + assert(obj1.__monitor !is null); +} + /** * Information about an interface. * When an object is accessed via an interface, an Interface* appears as the @@ -218,7 +418,7 @@ struct OffsetTypeInfo */ class TypeInfo { - override string toString() const pure @safe nothrow + override string toString() const @safe nothrow { return typeid(this).name; } @@ -228,18 +428,21 @@ class TypeInfo return hashOf(this.toString()); } - override int opCmp(Object o) + override int opCmp(Object rhs) { - import core.internal.traits : externDFunc; - alias dstrcmp = externDFunc!("core.internal.string.dstrcmp", - int function(scope const char[] s1, scope const char[] s2) @trusted pure nothrow @nogc); - - if (this is o) + if (this is rhs) return 0; - TypeInfo ti = cast(TypeInfo)o; + auto ti = cast(TypeInfo) rhs; if (ti is null) return 1; - return dstrcmp(this.toString(), ti.toString()); + return __cmp(this.toString(), ti.toString()); + } + + @system unittest + { + assert(typeid(void) <= typeid(void)); + assert(typeid(void).opCmp(null)); + assert(!typeid(void).opCmp(typeid(void))); } override bool opEquals(Object o) @@ -254,6 +457,14 @@ class TypeInfo return ti && this.toString() == ti.toString(); } + @system unittest + { + auto anotherObj = new Object(); + + assert(typeid(void).opEquals(typeid(void))); + assert(!typeid(void).opEquals(anotherObj)); + } + /** * Computes a hash of the instance of a type. * Params: @@ -280,8 +491,22 @@ class TypeInfo /// Swaps two instances of the type. void swap(void* p1, void* p2) const { - immutable size_t n = tsize; - for (size_t i = 0; i < n; i++) + size_t remaining = tsize; + // If the type might contain pointers perform the swap in pointer-sized + // chunks in case a garbage collection pass interrupts this function. + if ((cast(size_t) p1 | cast(size_t) p2) % (void*).alignof == 0) + { + while (remaining >= (void*).sizeof) + { + void* tmp = *cast(void**) p1; + *cast(void**) p1 = *cast(void**) p2; + *cast(void**) p2 = tmp; + p1 += (void*).sizeof; + p2 += (void*).sizeof; + remaining -= (void*).sizeof; + } + } + for (size_t i = 0; i < remaining; i++) { byte t = (cast(byte *)p1)[i]; (cast(byte*)p1)[i] = (cast(byte*)p2)[i]; @@ -289,6 +514,36 @@ class TypeInfo } } + @system unittest + { + class _TypeInfo_Dummy : TypeInfo + { + override const(void)[] initializer() const { return []; } + @property override size_t tsize() nothrow pure const @safe @nogc { return tsize_val; } + + size_t tsize_val; + } + auto dummy = new _TypeInfo_Dummy(); + cast(void)dummy.initializer(); // For coverage completeness + + int a = 2, b = -2; + dummy.swap(&a, &b); + // does nothing because tsize is 0 + assert(a == 2); + assert(b == -2); + + dummy.tsize_val = int.sizeof; + dummy.swap(&a, &b); + assert(a == -2); + assert(b == 2); + + void* ptr_a = null, ptr_b = cast(void*)1; + dummy.tsize_val = (void*).sizeof; + dummy.swap(&ptr_a, &ptr_b); + assert(ptr_a is cast(void*)1); + assert(ptr_b is null); + } + /** Get TypeInfo for 'next' type, as defined by what kind of type this is, null if none. */ @property inout(TypeInfo) next() nothrow pure inout @nogc { return null; } @@ -302,7 +557,7 @@ class TypeInfo abstract const(void)[] initializer() nothrow pure const @safe @nogc; /** Get flags for type: 1 means GC should scan for pointers, - 2 means arg of this type is passed in XMM register */ + 2 means arg of this type is passed in SIMD register(s) if available */ @property uint flags() nothrow pure const @safe @nogc { return 0; } /// Get type information on the contents of the type; null if not available @@ -330,9 +585,87 @@ class TypeInfo @property immutable(void)* rtInfo() nothrow pure const @safe @nogc { return rtinfoHasPointers; } // better safe than sorry } +@system unittest +{ + class _TypeInfo_Dummy : TypeInfo + { + override const(void)[] initializer() const { return []; } + } + auto dummy = new _TypeInfo_Dummy(); + cast(void)dummy.initializer(); // For coverage completeness + + assert(dummy.rtInfo() is rtinfoHasPointers); + assert(typeid(void).rtInfo() is rtinfoNoPointers); + + assert(dummy.tsize() == 0); + + bool gotCaught; + try + { + dummy.compare(null, null); + } catch (Error e) + { + gotCaught = true; + assert(e.msg == "TypeInfo.compare is not implemented"); + } + assert(gotCaught); + + assert(dummy.equals(null, null)); + assert(!dummy.equals(cast(void*)1, null)); +} + +@system unittest +{ + assert(typeid(void).next() is null); + assert(typeid(void).offTi() is null); + assert(typeid(void).tsize() == 1); + + version (WithArgTypes) + { + TypeInfo ti1; + TypeInfo ti2; + assert(typeid(void).argTypes(ti1, ti2) == 0); + assert(typeid(void) is ti1); + + assert(ti1 !is null); + assert(ti2 is null); + } +} + +@system unittest +{ + class _ZypeInfo_Dummy : TypeInfo + { + override const(void)[] initializer() const { return []; } + } + auto dummy2 = new _ZypeInfo_Dummy(); + cast(void)dummy2.initializer(); // For coverage completeness + + assert(typeid(void) > dummy2); + assert(dummy2 < typeid(void)); +} + +@safe unittest +{ + enum unittest_sym_name = __traits(identifier, __traits(parent, (){})); + enum fqn_unittest = "object." ~ unittest_sym_name; // object.__unittest_LX_CY + + class _TypeInfo_Dummy : TypeInfo + { + override const(void)[] initializer() const { return []; } + } + + auto dummy = new _TypeInfo_Dummy(); + cast(void)dummy.initializer(); // For coverage completeness + + assert(dummy.toString() == fqn_unittest ~ "._TypeInfo_Dummy"); + assert(dummy.toHash() == hashOf(dummy.toString())); + assert(dummy.getHash(null) == 0); +} + class TypeInfo_Enum : TypeInfo { - override string toString() const { return name; } + override string toString() const pure { return name; } override bool opEquals(Object o) { @@ -343,15 +676,117 @@ class TypeInfo_Enum : TypeInfo this.base == c.base; } + @system unittest + { + enum E { A, B, C } + enum EE { A, B, C } + + assert(typeid(E).opEquals(typeid(E))); + assert(!typeid(E).opEquals(typeid(EE))); + } + override size_t getHash(scope const void* p) const { return base.getHash(p); } + + @system unittest + { + enum E { A, B, C } + E e1 = E.A; + E e2 = E.B; + + assert(typeid(E).getHash(&e1) == hashOf(E.A)); + assert(typeid(E).getHash(&e2) == hashOf(E.B)); + + enum ES : string { A = "foo", B = "bar" } + ES es1 = ES.A; + ES es2 = ES.B; + + assert(typeid(ES).getHash(&es1) == hashOf("foo")); + assert(typeid(ES).getHash(&es2) == hashOf("bar")); + } + override bool equals(in void* p1, in void* p2) const { return base.equals(p1, p2); } + + @system unittest + { + enum E { A, B, C } + + E e1 = E.A; + E e2 = E.B; + + assert(typeid(E).equals(&e1, &e1)); + assert(!typeid(E).equals(&e1, &e2)); + } + override int compare(in void* p1, in void* p2) const { return base.compare(p1, p2); } + + @system unittest + { + enum E { A, B, C } + + E e1 = E.A; + E e2 = E.B; + + assert(typeid(E).compare(&e1, &e1) == 0); + assert(typeid(E).compare(&e1, &e2) < 0); + assert(typeid(E).compare(&e2, &e1) > 0); + } + override @property size_t tsize() nothrow pure const { return base.tsize; } + + @safe unittest + { + enum E { A, B, C } + enum ES : string { A = "a", B = "b", C = "c"} + + assert(typeid(E).tsize == E.sizeof); + assert(typeid(ES).tsize == ES.sizeof); + assert(typeid(E).tsize != ES.sizeof); + } + override void swap(void* p1, void* p2) const { return base.swap(p1, p2); } + @system unittest + { + enum E { A, B, C } + + E e1 = E.A; + E e2 = E.B; + + typeid(E).swap(&e1, &e2); + assert(e1 == E.B); + assert(e2 == E.A); + } + override @property inout(TypeInfo) next() nothrow pure inout { return base.next; } + + @system unittest + { + enum E { A, B, C } + + assert(typeid(E).next is null); + } + override @property uint flags() nothrow pure const { return base.flags; } + @safe unittest + { + enum E { A, B, C } + + assert(typeid(E).flags == 0); + } + + override const(OffsetTypeInfo)[] offTi() const { return base.offTi; } + + @system unittest + { + enum E { A, B, C } + + assert(typeid(E).offTi is null); + } + + override void destroy(void* p) const { return base.destroy(p); } + override void postblit(void* p) const { return base.postblit(p); } + override const(void)[] initializer() const { return m_init.length ? m_init : base.initializer(); @@ -371,7 +806,19 @@ class TypeInfo_Enum : TypeInfo void[] m_init; } -unittest // issue 12233 +@safe unittest +{ + enum unittest_sym_name = __traits(identifier, __traits(parent, (){})); + enum fqn_unittest = "object." ~ unittest_sym_name; // object.__unittest_LX_CY + + enum E { A, B, C } + enum EE { A, B, C } + + assert(typeid(E).toString() == fqn_unittest ~ ".E"); +} + + +@safe unittest // issue 12233 { static assert(is(typeof(TypeInfo.init) == TypeInfo)); assert(TypeInfo.init is null); @@ -404,12 +851,8 @@ class TypeInfo_Pointer : TypeInfo override int compare(in void* p1, in void* p2) const { - if (*cast(void**)p1 < *cast(void**)p2) - return -1; - else if (*cast(void**)p1 > *cast(void**)p2) - return 1; - else - return 0; + const v1 = *cast(void**) p1, v2 = *cast(void**) p2; + return (v1 > v2) - (v1 < v2); } override @property size_t tsize() nothrow pure const @@ -483,7 +926,7 @@ class TypeInfo_Array : TypeInfo if (result) return result; } - return cast(int)a1.length - cast(int)a2.length; + return (a1.length > a2.length) - (a1.length < a2.length); } override @property size_t tsize() nothrow pure const @@ -531,12 +974,12 @@ class TypeInfo_StaticArray : TypeInfo { override string toString() const { - import core.internal.traits : externDFunc; - alias sizeToTempString = externDFunc!("core.internal.string.unsignedToTempString", - char[] function(ulong, return char[], uint) @safe pure nothrow @nogc); + import core.internal.string : unsignedToTempString; char[20] tmpBuff = void; - return value.toString() ~ "[" ~ sizeToTempString(len, tmpBuff, 10) ~ "]"; + const lenString = unsignedToTempString(len, tmpBuff); + + return (() @trusted => cast(string) (value.toString() ~ "[" ~ lenString ~ "]"))(); } override bool opEquals(Object o) @@ -585,28 +1028,22 @@ class TypeInfo_StaticArray : TypeInfo override void swap(void* p1, void* p2) const { - import core.memory; import core.stdc.string : memcpy; - void* tmp; - size_t sz = value.tsize; - ubyte[16] buffer; - void* pbuffer; - - if (sz < buffer.sizeof) - tmp = buffer.ptr; - else - tmp = pbuffer = (new void[sz]).ptr; - - for (size_t u = 0; u < len; u += sz) + size_t remaining = value.tsize * len; + void[size_t.sizeof * 4] buffer = void; + while (remaining > buffer.length) { - size_t o = u * sz; - memcpy(tmp, p1 + o, sz); - memcpy(p1 + o, p2 + o, sz); - memcpy(p2 + o, tmp, sz); + memcpy(buffer.ptr, p1, buffer.length); + memcpy(p1, p2, buffer.length); + memcpy(p2, buffer.ptr, buffer.length); + p1 += buffer.length; + p2 += buffer.length; + remaining -= buffer.length; } - if (pbuffer) - GC.free(pbuffer); + memcpy(buffer.ptr, p1, remaining); + memcpy(p1, p2, remaining); + memcpy(p2, buffer.ptr, remaining); } override const(void)[] initializer() nothrow pure const @@ -656,6 +1093,23 @@ class TypeInfo_StaticArray : TypeInfo override @property immutable(void)* rtInfo() nothrow pure const @safe { return value.rtInfo(); } } +// https://issues.dlang.org/show_bug.cgi?id=21315 +@system unittest +{ + int[16] a, b; + foreach (int i; 0 .. 16) + { + a[i] = i; + b[i] = ~i; + } + typeid(int[16]).swap(&a, &b); + foreach (int i; 0 .. 16) + { + assert(a[i] == ~i); + assert(b[i] == i); + } +} + class TypeInfo_AssociativeArray : TypeInfo { override string toString() const @@ -731,7 +1185,7 @@ class TypeInfo_Vector : TypeInfo override void swap(void* p1, void* p2) const { return base.swap(p1, p2); } override @property inout(TypeInfo) next() nothrow pure inout { return base.next; } - override @property uint flags() nothrow pure const { return base.flags; } + override @property uint flags() nothrow pure const { return 2; /* passed in SIMD register */ } override const(void)[] initializer() nothrow pure const { @@ -750,14 +1204,14 @@ class TypeInfo_Vector : TypeInfo class TypeInfo_Function : TypeInfo { - override string toString() const + override string toString() const pure @trusted { import core.demangle : demangleType; alias SafeDemangleFunctionType = char[] function (const(char)[] buf, char[] dst = null) @safe nothrow pure; - SafeDemangleFunctionType demangle = ( () @trusted => cast(SafeDemangleFunctionType)(&demangleType) ) (); + SafeDemangleFunctionType demangle = cast(SafeDemangleFunctionType) &demangleType; - return (() @trusted => cast(string)(demangle(deco))) (); + return cast(string) demangle(deco); } override bool opEquals(Object o) @@ -790,7 +1244,7 @@ class TypeInfo_Function : TypeInfo string deco; } -unittest +@safe unittest { abstract class C { @@ -805,11 +1259,57 @@ unittest assert(typeid(functionTypes[2]).toString() == "int function(int, int)"); } +@system unittest +{ + abstract class C + { + void func(); + void func(int a); + } + + alias functionTypes = typeof(__traits(getVirtualFunctions, C, "func")); + + Object obj = typeid(functionTypes[0]); + assert(obj.opEquals(typeid(functionTypes[0]))); + assert(typeid(functionTypes[0]) == typeid(functionTypes[0])); + assert(typeid(functionTypes[0]) != typeid(functionTypes[1])); + + assert(typeid(functionTypes[0]).tsize() == 0); + assert(typeid(functionTypes[0]).initializer() is null); + assert(typeid(functionTypes[0]).rtInfo() is null); +} + class TypeInfo_Delegate : TypeInfo { - override string toString() const + override string toString() const pure @trusted + { + import core.demangle : demangleType; + + alias SafeDemangleFunctionType = char[] function (const(char)[] buf, char[] dst = null) @safe nothrow pure; + SafeDemangleFunctionType demangle = cast(SafeDemangleFunctionType) &demangleType; + + return cast(string) demangle(deco); + } + + @safe unittest { - return cast(string)(next.toString() ~ " delegate()"); + double sqr(double x) { return x * x; } + sqr(double.init); // for coverage completeness + + auto delegate_str = "double delegate(double) pure nothrow @nogc @safe"; + + assert(typeid(typeof(&sqr)).toString() == delegate_str); + assert(delegate_str.hashOf() == typeid(typeof(&sqr)).hashOf()); + assert(typeid(typeof(&sqr)).toHash() == typeid(typeof(&sqr)).hashOf()); + + int g; + + alias delegate_type = typeof((int a, int b) => a + b + g); + delegate_str = "int delegate(int, int) pure nothrow @nogc @safe"; + + assert(typeid(delegate_type).toString() == delegate_str); + assert(delegate_str.hashOf() == typeid(delegate_type).hashOf()); + assert(typeid(delegate_type).toHash() == typeid(delegate_type).hashOf()); } override bool opEquals(Object o) @@ -820,6 +1320,19 @@ class TypeInfo_Delegate : TypeInfo return c && this.deco == c.deco; } + @system unittest + { + double sqr(double x) { return x * x; } + int dbl(int x) { return x + x; } + sqr(double.init); // for coverage completeness + dbl(int.init); // for coverage completeness + + Object obj = typeid(typeof(&sqr)); + assert(obj.opEquals(typeid(typeof(&sqr)))); + assert(typeid(typeof(&sqr)) == typeid(typeof(&sqr))); + assert(typeid(typeof(&dbl)) != typeid(typeof(&sqr))); + } + override size_t getHash(scope const void* p) @trusted const { return hashOf(*cast(void delegate()*)p); @@ -877,6 +1390,10 @@ class TypeInfo_Delegate : TypeInfo override @property immutable(void)* rtInfo() nothrow pure const @safe { return RTInfo!(int delegate()); } } +private extern (C) Object _d_newclass(const TypeInfo_Class ci); +private extern (C) int _d_isbaseof(scope TypeInfo_Class child, + scope const TypeInfo_Class parent) @nogc nothrow pure @safe; // rt.cast_ + /** * Runtime type information about a class. * Can be retrieved from an object instance by using the @@ -884,14 +1401,14 @@ class TypeInfo_Delegate : TypeInfo */ class TypeInfo_Class : TypeInfo { - override string toString() const { return info.name; } + override string toString() const pure { return name; } override bool opEquals(Object o) { if (this is o) return true; auto c = cast(const TypeInfo_Class)o; - return c && this.info.name == c.info.name; + return c && this.name == c.name; } override size_t getHash(scope const void* p) @trusted const @@ -947,8 +1464,8 @@ class TypeInfo_Class : TypeInfo return m_offTi; } - @property auto info() @safe nothrow pure const { return this; } - @property auto typeinfo() @safe nothrow pure const { return this; } + final @property auto info() @safe @nogc nothrow pure const return { return this; } + final @property auto typeinfo() @safe @nogc nothrow pure const return { return this; } byte[] m_init; /** class static initializer * (init.length gives size in bytes of class) @@ -983,7 +1500,7 @@ class TypeInfo_Class : TypeInfo * Search all modules for TypeInfo_Class corresponding to classname. * Returns: null if not found */ - static const(TypeInfo_Class) find(in char[] classname) + static const(TypeInfo_Class) find(const scope char[] classname) { foreach (m; ModuleInfo) { @@ -1019,12 +1536,42 @@ class TypeInfo_Class : TypeInfo } return o; } -} - -alias ClassInfo = TypeInfo_Class; -unittest -{ + /** + * Returns true if the class described by `child` derives from or is + * the class described by this `TypeInfo_Class`. Always returns false + * if the argument is null. + * + * Params: + * child = TypeInfo for some class + * Returns: + * true if the class described by `child` derives from or is the + * class described by this `TypeInfo_Class`. + */ + final bool isBaseOf(scope const TypeInfo_Class child) const @nogc nothrow pure @trusted + { + if (m_init.length) + { + // If this TypeInfo_Class represents an actual class we only need + // to check the child and its direct ancestors. + for (auto ti = cast() child; ti !is null; ti = ti.base) + if (ti is this) + return true; + return false; + } + else + { + // If this TypeInfo_Class is the .info field of a TypeInfo_Interface + // we also need to recursively check the child's interfaces. + return child !is null && _d_isbaseof(cast() child, this); + } + } +} + +alias ClassInfo = TypeInfo_Class; + +@safe unittest +{ // Bugzilla 14401 static class X { @@ -1039,7 +1586,7 @@ unittest class TypeInfo_Interface : TypeInfo { - override string toString() const { return info.name; } + override string toString() const pure { return info.name; } override bool opEquals(Object o) { @@ -1108,19 +1655,67 @@ class TypeInfo_Interface : TypeInfo override @property uint flags() nothrow pure const { return 1; } TypeInfo_Class info; + + /** + * Returns true if the class described by `child` derives from the + * interface described by this `TypeInfo_Interface`. Always returns + * false if the argument is null. + * + * Params: + * child = TypeInfo for some class + * Returns: + * true if the class described by `child` derives from the + * interface described by this `TypeInfo_Interface`. + */ + final bool isBaseOf(scope const TypeInfo_Class child) const @nogc nothrow pure @trusted + { + return child !is null && _d_isbaseof(cast() child, this.info); + } + + /** + * Returns true if the interface described by `child` derives from + * or is the interface described by this `TypeInfo_Interface`. + * Always returns false if the argument is null. + * + * Params: + * child = TypeInfo for some interface + * Returns: + * true if the interface described by `child` derives from or is + * the interface described by this `TypeInfo_Interface`. + */ + final bool isBaseOf(scope const TypeInfo_Interface child) const @nogc nothrow pure @trusted + { + return child !is null && _d_isbaseof(cast() child.info, this.info); + } +} + +@safe unittest +{ + enum unittest_sym_name = __traits(identifier, __traits(parent, (){})); + enum fqn_unittest = "object." ~ unittest_sym_name; // object.__unittest_LX_CY + + interface I {} + + assert(fqn_unittest ~ ".I" == typeid(I).info.name); + assert((fqn_unittest ~ ".I").hashOf() == typeid(I).hashOf()); + assert(typeid(I).toHash() == typeid(I).hashOf()); } class TypeInfo_Struct : TypeInfo { override string toString() const { return name; } + override size_t toHash() const + { + return hashOf(this.mangledName); + } + override bool opEquals(Object o) { if (this is o) return true; auto s = cast(const TypeInfo_Struct)o; - return s && this.name == s.name && - this.initializer().length == s.initializer().length; + return s && this.mangledName == s.mangledName; } override size_t getHash(scope const void* p) @trusted pure nothrow const @@ -1219,23 +1814,45 @@ class TypeInfo_Struct : TypeInfo (*xpostblit)(p); } - string name; - void[] m_init; // initializer; m_init.ptr == null if 0 initialize + string mangledName; + + final @property string name() nothrow const @trusted + { + import core.demangle : demangleType; + + if (mangledName is null) // e.g., opaque structs + return null; + + const key = cast(const void*) this; // faster lookup than TypeInfo_Struct, at the cost of potential duplicates per binary + static string[typeof(key)] demangledNamesCache; // per thread - @safe pure nothrow - { - size_t function(in void*) xtoHash; - bool function(in void*, in void*) xopEquals; - int function(in void*, in void*) xopCmp; - string function(in void*) xtoString; + // not nothrow: + //return demangledNamesCache.require(key, cast(string) demangleType(mangledName)); - enum StructFlags : uint + if (auto pDemangled = key in demangledNamesCache) + return *pDemangled; + + const demangled = cast(string) demangleType(mangledName); + demangledNamesCache[key] = demangled; + return demangled; + } + + void[] m_init; // initializer; m_init.ptr == null if 0 initialize + + @safe pure nothrow { - hasPointers = 0x1, - isDynamicType = 0x2, // built at runtime, needs type info in xdtor + size_t function(in void*) xtoHash; + bool function(in void*, in void*) xopEquals; + int function(in void*, in void*) xopCmp; + string function(in void*) xtoString; + + enum StructFlags : uint + { + hasPointers = 0x1, + isDynamicType = 0x2, // built at runtime, needs type info in xdtor + } + StructFlags m_flags; } - StructFlags m_flags; - } union { void function(void*) xdtor; @@ -1261,7 +1878,7 @@ class TypeInfo_Struct : TypeInfo immutable(void)* m_RTInfo; // data for precise GC } -unittest +@system unittest { struct S { @@ -1428,12 +2045,7 @@ class TypeInfo_Inout : TypeInfo_Const } } - -/////////////////////////////////////////////////////////////////////////////// -// ModuleInfo -/////////////////////////////////////////////////////////////////////////////// - - +// Contents of Moduleinfo._flags enum { MIctorstart = 0x1, // we've started constructing it @@ -1452,31 +2064,35 @@ enum MIname = 0x1000, } - +/***************************************** + * An instance of ModuleInfo is generated into the object file for each compiled module. + * + * It provides access to various aspects of the module. + * It is not generated for betterC. + */ struct ModuleInfo { - uint _flags; + uint _flags; // MIxxxx uint _index; // index into _moduleinfo_array[] version (all) { deprecated("ModuleInfo cannot be copy-assigned because it is a variable-sized struct.") - void opAssign(in ModuleInfo m) { _flags = m._flags; _index = m._index; } + void opAssign(const scope ModuleInfo m) { _flags = m._flags; _index = m._index; } } else { @disable this(); - @disable this(this) const; } const: - private void* addrOf(int flag) nothrow pure @nogc + private void* addrOf(int flag) return nothrow pure @nogc in { assert(flag >= MItlsctor && flag <= MIname); assert(!(flag & (flag - 1)) && !(flag & ~(flag - 1) << 1)); } - body + do { import core.stdc.string : strlen; @@ -1539,42 +2155,74 @@ const: @property uint flags() nothrow pure @nogc { return _flags; } + /************************ + * Returns: + * module constructor for thread locals, `null` if there isn't one + */ @property void function() tlsctor() nothrow pure @nogc { return flags & MItlsctor ? *cast(typeof(return)*)addrOf(MItlsctor) : null; } + /************************ + * Returns: + * module destructor for thread locals, `null` if there isn't one + */ @property void function() tlsdtor() nothrow pure @nogc { return flags & MItlsdtor ? *cast(typeof(return)*)addrOf(MItlsdtor) : null; } + /***************************** + * Returns: + * address of a module's `const(MemberInfo)[] getMembers(string)` function, `null` if there isn't one + */ @property void* xgetMembers() nothrow pure @nogc { return flags & MIxgetMembers ? *cast(typeof(return)*)addrOf(MIxgetMembers) : null; } + /************************ + * Returns: + * module constructor, `null` if there isn't one + */ @property void function() ctor() nothrow pure @nogc { return flags & MIctor ? *cast(typeof(return)*)addrOf(MIctor) : null; } + /************************ + * Returns: + * module destructor, `null` if there isn't one + */ @property void function() dtor() nothrow pure @nogc { return flags & MIdtor ? *cast(typeof(return)*)addrOf(MIdtor) : null; } + /************************ + * Returns: + * module order independent constructor, `null` if there isn't one + */ @property void function() ictor() nothrow pure @nogc { return flags & MIictor ? *cast(typeof(return)*)addrOf(MIictor) : null; } + /************* + * Returns: + * address of function that runs the module's unittests, `null` if there isn't one + */ @property void function() unitTest() nothrow pure @nogc { return flags & MIunitTest ? *cast(typeof(return)*)addrOf(MIunitTest) : null; } - @property immutable(ModuleInfo*)[] importedModules() nothrow pure @nogc + /**************** + * Returns: + * array of pointers to the ModuleInfo's of modules imported by this one + */ + @property immutable(ModuleInfo*)[] importedModules() return nothrow pure @nogc { if (flags & MIimportedModules) { @@ -1584,7 +2232,11 @@ const: return null; } - @property TypeInfo_Class[] localClasses() nothrow pure @nogc + /**************** + * Returns: + * array of TypeInfo_Class references for classes defined in this module + */ + @property TypeInfo_Class[] localClasses() return nothrow pure @nogc { if (flags & MIlocalClasses) { @@ -1594,16 +2246,16 @@ const: return null; } - @property string name() nothrow pure @nogc + /******************** + * Returns: + * name of module, `null` if no name + */ + @property string name() return nothrow pure @nogc { - if (true || flags & MIname) // always available for now - { - import core.stdc.string : strlen; + import core.stdc.string : strlen; - auto p = cast(immutable char*)addrOf(MIname); - return p[0 .. strlen(p)]; - } - // return null; + auto p = cast(immutable char*) addrOf(MIname); + return p[0 .. strlen(p)]; } static int opApply(scope int delegate(ModuleInfo*) dg) @@ -1617,7 +2269,7 @@ const: } } -unittest +@system unittest { ModuleInfo* m1; foreach (m; ModuleInfo) @@ -1677,23 +2329,115 @@ class Throwable : Object * caught $(D Exception) will be chained to the new $(D Throwable) via this * field. */ - Throwable next; + private Throwable nextInChain; + + private uint _refcount; // 0 : allocated by GC + // 1 : allocated by _d_newThrowable() + // 2.. : reference count + 1 + + /** + * Returns: + * A reference to the _next error in the list. This is used when a new + * $(D Throwable) is thrown from inside a $(D catch) block. The originally + * caught $(D Exception) will be chained to the new $(D Throwable) via this + * field. + */ + @property inout(Throwable) next() @safe inout return scope pure nothrow @nogc { return nextInChain; } + + /** + * Replace next in chain with `tail`. + * Use `chainTogether` instead if at all possible. + */ + @property void next(Throwable tail) @safe scope pure nothrow @nogc + { + if (tail && tail._refcount) + ++tail._refcount; // increment the replacement *first* + + auto n = nextInChain; + nextInChain = null; // sever the tail before deleting it + + if (n && n._refcount) + _d_delThrowable(n); // now delete the old tail + + nextInChain = tail; // and set the new tail + } + + /** + * Returns: + * mutable reference to the reference count, which is + * 0 - allocated by the GC, 1 - allocated by _d_newThrowable(), + * and >=2 which is the reference count + 1 + * Note: + * Marked as `@system` to discourage casual use of it. + */ + @system @nogc final pure nothrow ref uint refcount() return { return _refcount; } + + /** + * Loop over the chain of Throwables. + */ + int opApply(scope int delegate(Throwable) dg) + { + int result = 0; + for (Throwable t = this; t; t = t.nextInChain) + { + result = dg(t); + if (result) + break; + } + return result; + } + + /** + * Append `e2` to chain of exceptions that starts with `e1`. + * Params: + * e1 = start of chain (can be null) + * e2 = second part of chain (can be null) + * Returns: + * Throwable that is at the start of the chain; null if both `e1` and `e2` are null + */ + static @__future @system @nogc pure nothrow Throwable chainTogether(return scope Throwable e1, return scope Throwable e2) + { + if (!e1) + return e2; + if (!e2) + return e1; + if (e2.refcount()) + ++e2.refcount(); + + for (auto e = e1; 1; e = e.nextInChain) + { + if (!e.nextInChain) + { + e.nextInChain = e2; + break; + } + } + return e1; + } - @nogc @safe pure nothrow this(string msg, Throwable next = null) + @nogc @safe pure nothrow this(string msg, Throwable nextInChain = null) { this.msg = msg; - this.next = next; + this.nextInChain = nextInChain; + if (nextInChain && nextInChain._refcount) + ++nextInChain._refcount; //this.info = _d_traceContext(); } - @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable next = null) + @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable nextInChain = null) { - this(msg, next); + this(msg, nextInChain); this.file = file; this.line = line; //this.info = _d_traceContext(); } + @trusted nothrow ~this() + { + if (nextInChain && nextInChain._refcount) + _d_delThrowable(nextInChain); + } + /** * Overrides $(D Object.toString) and returns the error message. * Internally this forwards to the $(D toString) overload that @@ -1714,15 +2458,13 @@ class Throwable : Object */ void toString(scope void delegate(in char[]) sink) const { - import core.internal.traits : externDFunc; - alias sizeToTempString = externDFunc!("core.internal.string.unsignedToTempString", - char[] function(ulong, return char[], uint) @safe pure nothrow @nogc); + import core.internal.string : unsignedToTempString; char[20] tmpBuff = void; sink(typeid(this).name); sink("@"); sink(file); - sink("("); sink(sizeToTempString(line, tmpBuff, 10)); sink(")"); + sink("("); sink(unsignedToTempString(line, tmpBuff)); sink(")"); if (msg.length) { @@ -1744,6 +2486,19 @@ class Throwable : Object } } } + + /** + * Get the message describing the error. + * Base behavior is to return the `Throwable.msg` field. + * Override to return some other error message. + * + * Returns: + * Error message + */ + @__future const(char)[] message() const + { + return this.msg; + } } @@ -1759,29 +2514,45 @@ class Exception : Throwable { /** - * Creates a new instance of Exception. The next parameter is used + * Creates a new instance of Exception. The nextInChain parameter is used * internally and should always be $(D null) when passed by user code. * This constructor does not automatically throw the newly-created * Exception; the $(D throw) statement should be used for that purpose. */ - @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) { - super(msg, file, line, next); + super(msg, file, line, nextInChain); } +} - @nogc @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) +/// +@safe unittest +{ + bool gotCaught; + try + { + throw new Exception("msg"); + } + catch (Exception e) { - super(msg, file, line, next); + gotCaught = true; + assert(e.msg == "msg"); } + assert(gotCaught); } -unittest +@system unittest { { auto e = new Exception("msg"); assert(e.file == __FILE__); assert(e.line == __LINE__ - 2); - assert(e.next is null); + assert(e.nextInChain is null); assert(e.msg == "msg"); } @@ -1789,7 +2560,7 @@ unittest auto e = new Exception("msg", new Exception("It's an Exception!"), "hello", 42); assert(e.file == "hello"); assert(e.line == 42); - assert(e.next !is null); + assert(e.nextInChain !is null); assert(e.msg == "msg"); } @@ -1797,9 +2568,14 @@ unittest auto e = new Exception("msg", "hello", 42, new Exception("It's an Exception!")); assert(e.file == "hello"); assert(e.line == 42); - assert(e.next !is null); + assert(e.nextInChain !is null); assert(e.msg == "msg"); } + + { + auto e = new Exception("message"); + assert(e.message == "message"); + } } @@ -1815,20 +2591,20 @@ unittest class Error : Throwable { /** - * Creates a new instance of Error. The next parameter is used + * Creates a new instance of Error. The nextInChain parameter is used * internally and should always be $(D null) when passed by user code. * This constructor does not automatically throw the newly-created * Error; the $(D throw) statement should be used for that purpose. */ - @nogc @safe pure nothrow this(string msg, Throwable next = null) + @nogc @safe pure nothrow this(string msg, Throwable nextInChain = null) { - super(msg, next); + super(msg, nextInChain); bypassedException = null; } - @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable next = null) + @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable nextInChain = null) { - super(msg, file, line, next); + super(msg, file, line, nextInChain); bypassedException = null; } @@ -1837,13 +2613,29 @@ class Error : Throwable Throwable bypassedException; } -unittest +/// +@system unittest +{ + bool gotCaught; + try + { + throw new Error("msg"); + } + catch (Error e) + { + gotCaught = true; + assert(e.msg == "msg"); + } + assert(gotCaught); +} + +@safe unittest { { auto e = new Error("msg"); assert(e.file is null); assert(e.line == 0); - assert(e.next is null); + assert(e.nextInChain is null); assert(e.msg == "msg"); assert(e.bypassedException is null); } @@ -1852,7 +2644,7 @@ unittest auto e = new Error("msg", new Exception("It's an Exception!")); assert(e.file is null); assert(e.line == 0); - assert(e.next !is null); + assert(e.nextInChain !is null); assert(e.msg == "msg"); assert(e.bypassedException is null); } @@ -1861,32 +2653,24 @@ unittest auto e = new Error("msg", "hello", 42, new Exception("It's an Exception!")); assert(e.file == "hello"); assert(e.line == 42); - assert(e.next !is null); + assert(e.nextInChain !is null); assert(e.msg == "msg"); assert(e.bypassedException is null); } } -/* Used in Exception Handling LSDA tables to 'wrap' C++ type info - * so it can be distinguished from D TypeInfo - */ -class __cpp_type_info_ptr -{ - void* ptr; // opaque pointer to C++ RTTI type info -} - extern (C) { // from druntime/src/rt/aaA.d private struct AA { void* impl; } // size_t _aaLen(in AA aa) pure nothrow @nogc; - private void* _aaGetY(AA* paa, const TypeInfo_AssociativeArray ti, in size_t valsz, in void* pkey) pure nothrow; - private void* _aaGetX(AA* paa, const TypeInfo_AssociativeArray ti, in size_t valsz, in void* pkey, out bool found) pure nothrow; + private void* _aaGetY(AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey) pure nothrow; + private void* _aaGetX(AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey, out bool found) pure nothrow; // inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t valsz, in void* pkey); - inout(void[]) _aaValues(inout AA aa, in size_t keysz, in size_t valsz, const TypeInfo tiValueArray) pure nothrow; - inout(void[]) _aaKeys(inout AA aa, in size_t keysz, const TypeInfo tiKeyArray) pure nothrow; - void* _aaRehash(AA* paa, in TypeInfo keyti) pure nothrow; + inout(void[]) _aaValues(inout AA aa, const size_t keysz, const size_t valsz, const TypeInfo tiValueArray) pure nothrow; + inout(void[]) _aaKeys(inout AA aa, const size_t keysz, const TypeInfo tiKeyArray) pure nothrow; + void* _aaRehash(AA* paa, const scope TypeInfo keyti) pure nothrow; void _aaClear(AA aa) pure nothrow; // alias _dg_t = extern(D) int delegate(void*); @@ -1902,8 +2686,8 @@ extern (C) void* _aaRangeFrontValue(AARange r) pure nothrow @nogc @safe; void _aaRangePopFront(ref AARange r) pure nothrow @nogc @safe; - int _aaEqual(in TypeInfo tiRaw, in AA aa1, in AA aa2); - hash_t _aaGetHash(in AA* aa, in TypeInfo tiRaw) nothrow; + int _aaEqual(scope const TypeInfo tiRaw, scope const AA aa1, scope const AA aa2); + hash_t _aaGetHash(scope const AA* aa, scope const TypeInfo tiRaw) nothrow; /* _d_assocarrayliteralTX marked as pure, because aaLiteral can be called from pure code. @@ -1926,17 +2710,44 @@ alias AssociativeArray(Key, Value) = Value[Key]; * Params: * aa = The associative array. */ -void clear(T : Value[Key], Value, Key)(T aa) +void clear(Value, Key)(Value[Key] aa) { _aaClear(*cast(AA *) &aa); } /* ditto */ -void clear(T : Value[Key], Value, Key)(T* aa) +void clear(Value, Key)(Value[Key]* aa) { _aaClear(*cast(AA *) aa); } +/// +@system unittest +{ + auto aa = ["k1": 2]; + aa.clear; + assert("k1" !in aa); +} + +// Issue 20559 +@system unittest +{ + static class Foo + { + int[string] aa; + alias aa this; + } + + auto v = new Foo(); + v["Hello World"] = 42; + v.clear; + assert("Hello World" !in v); + + // Test for T* + static assert(!__traits(compiles, (&v).clear)); + static assert( __traits(compiles, (*(&v)).clear)); +} + /*********************************** * Reorganizes the associative array in place so that lookups are more * efficient. @@ -2000,15 +2811,16 @@ V[K] dup(T : V[K], K, V)(T aa) return *cast(V*)pv; } - if (auto postblit = _getPostblit!V()) - { - foreach (k, ref v; aa) - postblit(duplicateElem(k, v)); - } - else + foreach (k, ref v; aa) { - foreach (k, ref v; aa) + static if (!__traits(hasPostblit, V)) duplicateElem(k, v); + else static if (__traits(isStaticArray, V)) + _doPostblit(duplicateElem(k, v)[]); + else static if (!is(typeof(v.__xpostblit())) && is(immutable V == immutable UV, UV)) + (() @trusted => *cast(UV*) &duplicateElem(k, v))().__xpostblit(); + else + duplicateElem(k, v).__xpostblit(); } return result; @@ -2020,6 +2832,15 @@ V[K] dup(T : V[K], K, V)(T* aa) return (*aa).dup; } +/// +@safe unittest +{ + auto aa = ["k1": 2]; + auto a2 = aa.dup; + aa["k2"] = 3; + assert("k2" !in a2); +} + // this should never be made public. private AARange _aaToRange(T: V[K], K, V)(ref T aa) pure nothrow @nogc @safe { @@ -2048,10 +2869,9 @@ auto byKey(T : V[K], K, V)(T aa) pure nothrow @nogc @safe pure nothrow @nogc: @property bool empty() @safe { return _aaRangeEmpty(r); } - @property ref front() + @property ref front() @trusted { - auto p = (() @trusted => cast(substInout!K*) _aaRangeFrontKey(r)) (); - return *p; + return *cast(substInout!K*) _aaRangeFrontKey(r); } void popFront() @safe { _aaRangePopFront(r); } @property Result save() { return this; } @@ -2066,6 +2886,17 @@ auto byKey(T : V[K], K, V)(T* aa) pure nothrow @nogc return (*aa).byKey(); } +/// +@safe unittest +{ + auto dict = [1: 0, 2: 0]; + int sum; + foreach (v; dict.byKey) + sum += v; + + assert(sum == 3); +} + /*********************************** * Returns a forward range over the values of the associative array. * Params: @@ -2083,10 +2914,9 @@ auto byValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe pure nothrow @nogc: @property bool empty() @safe { return _aaRangeEmpty(r); } - @property ref front() + @property ref front() @trusted { - auto p = (() @trusted => cast(substInout!V*) _aaRangeFrontValue(r)) (); - return *p; + return *cast(substInout!V*) _aaRangeFrontValue(r); } void popFront() @safe { _aaRangePopFront(r); } @property Result save() { return this; } @@ -2101,6 +2931,17 @@ auto byValue(T : V[K], K, V)(T* aa) pure nothrow @nogc return (*aa).byValue(); } +/// +@safe unittest +{ + auto dict = ["k1": 1, "k2": 2]; + int sum; + foreach (v; dict.byValue) + sum += v; + + assert(sum == 3); +} + /*********************************** * Returns a forward range over the key value pairs of the associative array. * Params: @@ -2127,15 +2968,13 @@ auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe private void* keyp; private void* valp; - @property ref key() inout + @property ref key() inout @trusted { - auto p = (() @trusted => cast(substInout!K*) keyp) (); - return *p; + return *cast(substInout!K*) keyp; } - @property ref value() inout + @property ref value() inout @trusted { - auto p = (() @trusted => cast(substInout!V*) valp) (); - return *p; + return *cast(substInout!V*) valp; } } return Pair(_aaRangeFrontKey(r), @@ -2154,6 +2993,17 @@ auto byKeyValue(T : V[K], K, V)(T* aa) pure nothrow @nogc return (*aa).byKeyValue(); } +/// +@safe unittest +{ + auto dict = ["k1": 1, "k2": 2]; + int sum; + foreach (e; dict.byKeyValue) + sum += e.value; + + assert(sum == 3); +} + /*********************************** * Returns a dynamic array, the elements of which are the keys in the * associative array. @@ -2169,9 +3019,12 @@ Key[] keys(T : Value[Key], Value, Key)(T aa) @property alias realAA = aa; else const(Value[Key]) realAA = aa; - auto a = cast(void[])_aaKeys(*cast(inout(AA)*)&realAA, Key.sizeof, typeid(Key[])); - auto res = *cast(Key[]*)&a; - _doPostblit(res); + auto res = () @trusted { + auto a = cast(void[])_aaKeys(*cast(inout(AA)*)&realAA, Key.sizeof, typeid(Key[])); + return *cast(Key[]*)&a; + }(); + static if (__traits(hasPostblit, Key)) + _doPostblit(res); return res; } @@ -2181,7 +3034,18 @@ Key[] keys(T : Value[Key], Value, Key)(T *aa) @property return (*aa).keys; } -@system unittest +/// +@safe unittest +{ + auto aa = [1: "v1", 2: "v2"]; + int sum; + foreach (k; aa.keys) + sum += k; + + assert(sum == 3); +} + +@safe unittest { static struct S { @@ -2194,6 +3058,36 @@ Key[] keys(T : Value[Key], Value, Key)(T *aa) @property assert(s.keys.length == 0); } +@safe unittest +{ + @safe static struct Key + { + string str; + this(this) @safe {} + } + string[Key] aa; + static assert(__traits(compiles, { + void test() @safe { + const _ = aa.keys; + } + })); +} + +@safe unittest +{ + static struct Key + { + string str; + this(this) @system {} + } + string[Key] aa; + static assert(!__traits(compiles, { + void test() @safe { + const _ = aa.keys; + } + })); +} + /*********************************** * Returns a dynamic array, the elements of which are the values in the * associative array. @@ -2209,9 +3103,12 @@ Value[] values(T : Value[Key], Value, Key)(T aa) @property alias realAA = aa; else const(Value[Key]) realAA = aa; - auto a = cast(void[])_aaValues(*cast(inout(AA)*)&realAA, Key.sizeof, Value.sizeof, typeid(Value[])); - auto res = *cast(Value[]*)&a; - _doPostblit(res); + auto res = () @trusted { + auto a = cast(void[])_aaValues(*cast(inout(AA)*)&realAA, Key.sizeof, Value.sizeof, typeid(Value[])); + return *cast(Value[]*)&a; + }(); + static if (__traits(hasPostblit, Value)) + _doPostblit(res); return res; } @@ -2221,7 +3118,18 @@ Value[] values(T : Value[Key], Value, Key)(T *aa) @property return (*aa).values; } -@system unittest +/// +@safe unittest +{ + auto aa = ["k1": 1, "k2": 2]; + int sum; + foreach (e; aa.values) + sum += e; + + assert(sum == 3); +} + +@safe unittest { static struct S { @@ -2234,6 +3142,36 @@ Value[] values(T : Value[Key], Value, Key)(T *aa) @property assert(s.values.length == 0); } +@safe unittest +{ + @safe static struct Value + { + string str; + this(this) @safe {} + } + Value[string] aa; + static assert(__traits(compiles, { + void test() @safe { + const _ = aa.values; + } + })); +} + +@safe unittest +{ + static struct Value + { + string str; + this(this) @system {} + } + Value[string] aa; + static assert(!__traits(compiles, { + void test() @safe { + const _ = aa.values; + } + })); +} + /*********************************** * Looks up key; if it exists returns corresponding value else evaluates and * returns defaultValue. @@ -2256,6 +3194,13 @@ inout(V) get(K, V)(inout(V[K])* aa, K key, lazy inout(V) defaultValue) return (*aa).get(key, defaultValue); } +@safe unittest +{ + auto aa = ["k1": 1]; + assert(aa.get("k1", 0) == 1); + assert(aa.get("k2", 0) == 0); +} + /*********************************** * Looks up key; if it exists returns corresponding value else evaluates * value, adds it to the associative array and returns it. @@ -2281,48 +3226,38 @@ ref V require(K, V)(ref V[K] aa, K key, lazy V value = V.init) { auto p = cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); } - return found ? *p : (*p = value); -} - -// Constraints for aa update. Delegates, Functions or Functors (classes that -// provide opCall) are allowed. See unittest for an example. -private -{ - template isCreateOperation(C, V) + if (found) + return *p; + else { - static if (is(C : V delegate()) || is(C : V function())) - enum bool isCreateOperation = true; - else static if (isCreateOperation!(typeof(&C.opCall), V)) - enum bool isCreateOperation = true; - else - enum bool isCreateOperation = false; + *p = value; // Not `return (*p = value)` since if `=` is overloaded + return *p; // this might not return a ref to the left-hand side. } +} - template isUpdateOperation(U, V) - { - static if (is(U : V delegate(ref V)) || is(U : V function(ref V))) - enum bool isUpdateOperation = true; - else static if (isUpdateOperation!(typeof(&U.opCall), V)) - enum bool isUpdateOperation = true; - else - enum bool isUpdateOperation = false; - } +/// +@safe unittest +{ + auto aa = ["k1": 1]; + assert(aa.require("k1", 0) == 1); + assert(aa.require("k2", 0) == 0); + assert(aa["k2"] == 0); } // Tests whether T can be @safe-ly copied. Use a union to exclude destructor from the test. private enum bool isSafeCopyable(T) = is(typeof(() @safe { union U { T x; } T *x; auto u = U(*x); })); /*********************************** - * Looks up key; if it exists applies the update delegate else evaluates the - * create delegate and adds it to the associative array + * Looks up key; if it exists applies the update callable else evaluates the + * create callable and adds it to the associative array * Params: * aa = The associative array. * key = The key. - * create = The delegate to apply on create. - * update = The delegate to apply on update. + * create = The callable to apply on create. + * update = The callable to apply on update. */ void update(K, V, C, U)(ref V[K] aa, K key, scope C create, scope U update) -if (isCreateOperation!(C, V) && isUpdateOperation!(U, V)) +if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof(update(aa[K.init])) == void))) { bool found; // if key is @safe-ly copyable, `update` may infer @safe @@ -2340,10 +3275,35 @@ if (isCreateOperation!(C, V) && isUpdateOperation!(U, V)) if (!found) *p = create(); else - *p = update(*p); + { + static if (is(typeof(update(*p)) == void)) + update(*p); + else + *p = update(*p); + } +} + +/// +@system unittest +{ + auto aa = ["k1": 1]; + + aa.update("k1", { + return -1; // create (won't be executed) + }, (ref int v) { + v += 1; // update + }); + assert(aa["k1"] == 2); + + aa.update("k2", { + return 0; // create + }, (ref int v) { + v = -1; // update (won't be executed) + }); + assert(aa["k2"] == 0); } -unittest +@safe unittest { static struct S { @@ -2373,557 +3333,348 @@ unittest static assert(!is(typeof(() @safe { aais.update(S(1234), { return 1234; }, (ref int x) { x++; return x; }); }))); } -private void _destructRecurse(S)(ref S s) - if (is(S == struct)) -{ - static if (__traits(hasMember, S, "__xdtor") && - // Bugzilla 14746: Check that it's the exact member of S. - __traits(isSame, S, __traits(parent, s.__xdtor))) - s.__xdtor(); -} - -private void _destructRecurse(E, size_t n)(ref E[n] arr) -{ - import core.internal.traits : hasElaborateDestructor; - - static if (hasElaborateDestructor!E) - { - foreach_reverse (ref elem; arr) - _destructRecurse(elem); - } -} - -// Public and explicitly undocumented -void _postblitRecurse(S)(ref S s) - if (is(S == struct)) -{ - static if (__traits(hasMember, S, "__xpostblit") && - // Bugzilla 14746: Check that it's the exact member of S. - __traits(isSame, S, __traits(parent, s.__xpostblit))) - s.__xpostblit(); -} - -// Ditto -void _postblitRecurse(E, size_t n)(ref E[n] arr) +@safe unittest { - import core.internal.traits : hasElaborateCopyConstructor; - - static if (hasElaborateCopyConstructor!E) + struct S0 { - size_t i; - scope(failure) + int opCall(ref int v) { - for (; i != 0; --i) - { - _destructRecurse(arr[i - 1]); // What to do if this throws? - } + return v + 1; } - - for (i = 0; i < arr.length; ++i) - _postblitRecurse(arr[i]); } -} - -// Test destruction/postblit order -@safe nothrow pure unittest -{ - string[] order; - struct InnerTop + struct S1 { - ~this() @safe nothrow pure + int opCall()() { - order ~= "destroy inner top"; + return -2; } - this(this) @safe nothrow pure + T opCall(T)(ref T v) { - order ~= "copy inner top"; + return v + 1; } } - struct InnerMiddle {} + int[string] a = ["2" : 1]; + a.update("2", () => -1, S0.init); + assert(a["2"] == 2); + a.update("0", () => -1, S0.init); + assert(a["0"] == -1); + a.update("2", S1.init, S1.init); + assert(a["2"] == 3); + a.update("1", S1.init, S1.init); + assert(a["1"] == -2); +} - version (none) // https://issues.dlang.org/show_bug.cgi?id=14242 - struct InnerElement - { - static char counter = '1'; +@system unittest +{ + int[string] aa; - ~this() @safe nothrow pure - { - order ~= "destroy inner element #" ~ counter++; - } + foreach (n; 0 .. 2) + aa.update("k1", { + return 7; + }, (ref int v) { + return v + 3; + }); + assert(aa["k1"] == 10); +} - this(this) @safe nothrow pure - { - order ~= "copy inner element #" ~ counter++; - } - } +version (CoreDdoc) +{ + // This lets DDoc produce better documentation. - struct InnerBottom - { - ~this() @safe nothrow pure - { - order ~= "destroy inner bottom"; - } + /** + Calculates the hash value of `arg` with an optional `seed` initial value. + The result might not be equal to `typeid(T).getHash(&arg)`. - this(this) @safe nothrow pure - { - order ~= "copy inner bottom"; - } - } + Params: + arg = argument to calculate the hash value of + seed = optional `seed` value (may be used for hash chaining) - struct S + Return: calculated hash value of `arg` + */ + size_t hashOf(T)(auto ref T arg, size_t seed) { - char[] s; - InnerTop top; - InnerMiddle middle; - version (none) InnerElement[3] array; // https://issues.dlang.org/show_bug.cgi?id=14242 - int a; - InnerBottom bottom; - ~this() @safe nothrow pure { order ~= "destroy outer"; } - this(this) @safe nothrow pure { order ~= "copy outer"; } + static import core.internal.hash; + return core.internal.hash.hashOf(arg, seed); } - - string[] destructRecurseOrder; + /// ditto + size_t hashOf(T)(auto ref T arg) { - S s; - _destructRecurse(s); - destructRecurseOrder = order; - order = null; + static import core.internal.hash; + return core.internal.hash.hashOf(arg); } - assert(order.length); - assert(destructRecurseOrder == order); - order = null; - - S s; - _postblitRecurse(s); - assert(order.length); - auto postblitRecurseOrder = order; - order = null; - S s2 = s; - assert(order.length); - assert(postblitRecurseOrder == order); + @safe unittest + { + auto h1 = "my.string".hashOf; + assert(h1 == "my.string".hashOf); + } } - -// Test static struct -nothrow @safe @nogc unittest +else { - static int i = 0; - static struct S { ~this() nothrow @safe @nogc { i = 42; } } - S s; - _destructRecurse(s); - assert(i == 42); + public import core.internal.hash : hashOf; } -unittest +/// +@system unittest { - // Bugzilla 14746 - static struct HasDtor + class MyObject { - ~this() { assert(0); } + size_t myMegaHash() const @safe pure nothrow + { + return 42; + } } - static struct Owner + struct Test { - HasDtor* ptr; - alias ptr this; + int a; + string b; + MyObject c; + size_t toHash() const pure nothrow + { + size_t hash = a.hashOf(); + hash = b.hashOf(hash); + size_t h1 = c.myMegaHash(); + hash = h1.hashOf(hash); //Mix two hash values + return hash; + } } +} - Owner o; - assert(o.ptr is null); - destroy(o); // must not reach in HasDtor.__dtor() +bool _xopEquals(in void*, in void*) +{ + throw new Error("TypeInfo.equals is not implemented"); } -unittest +bool _xopCmp(in void*, in void*) { - // Bugzilla 14746 - static struct HasPostblit - { - this(this) { assert(0); } - } - static struct Owner - { - HasPostblit* ptr; - alias ptr this; - } + throw new Error("TypeInfo.compare is not implemented"); +} - Owner o; - assert(o.ptr is null); - _postblitRecurse(o); // must not reach in HasPostblit.__postblit() +/****************************************** + * Create RTInfo for type T + */ + +template RTInfoImpl(size_t[] pointerBitmap) +{ + immutable size_t[pointerBitmap.length] RTInfoImpl = pointerBitmap[]; } -// Test handling of fixed-length arrays -// Separate from first test because of https://issues.dlang.org/show_bug.cgi?id=14242 -unittest +template NoPointersBitmapPayload(size_t N) { - string[] order; + enum size_t[N] NoPointersBitmapPayload = 0; +} - struct S - { - char id; +template RTInfo(T) +{ + enum pointerBitmap = __traits(getPointerBitmap, T); + static if (pointerBitmap[1 .. $] == NoPointersBitmapPayload!(pointerBitmap.length - 1)) + enum RTInfo = rtinfoNoPointers; + else + enum RTInfo = RTInfoImpl!(pointerBitmap).ptr; +} - this(this) - { - order ~= "copy #" ~ id; - } +/** +* shortcuts for the precise GC, also generated by the compiler +* used instead of the actual pointer bitmap +*/ +enum immutable(void)* rtinfoNoPointers = null; +enum immutable(void)* rtinfoHasPointers = cast(void*)1; - ~this() - { - order ~= "destroy #" ~ id; - } - } +// Helper functions - string[] destructRecurseOrder; +private inout(TypeInfo) getElement(return inout TypeInfo value) @trusted pure nothrow +{ + TypeInfo element = cast() value; + for (;;) { - S[3] arr = [S('1'), S('2'), S('3')]; - _destructRecurse(arr); - destructRecurseOrder = order; - order = null; + if (auto qualified = cast(TypeInfo_Const) element) + element = qualified.base; + else if (auto redefined = cast(TypeInfo_Enum) element) + element = redefined.base; + else if (auto staticArray = cast(TypeInfo_StaticArray) element) + element = staticArray.value; + else if (auto vector = cast(TypeInfo_Vector) element) + element = vector.base; + else + break; } - assert(order.length); - assert(destructRecurseOrder == order); - order = null; - - S[3] arr = [S('1'), S('2'), S('3')]; - _postblitRecurse(arr); - assert(order.length); - auto postblitRecurseOrder = order; - order = null; - - auto arrCopy = arr; - assert(order.length); - assert(postblitRecurseOrder == order); + return cast(inout) element; } -// Test handling of failed postblit -// Not nothrow or @safe because of https://issues.dlang.org/show_bug.cgi?id=14242 -/+ nothrow @safe +/ unittest +private size_t getArrayHash(const scope TypeInfo element, const scope void* ptr, const size_t count) @trusted nothrow { - static class FailedPostblitException : Exception { this() nothrow @safe { super(null); } } - static string[] order; - static struct Inner - { - char id; - - @safe: - this(this) - { - order ~= "copy inner #" ~ id; - if (id == '2') - throw new FailedPostblitException(); - } + if (!count) + return 0; - ~this() nothrow - { - order ~= "destroy inner #" ~ id; - } - } + const size_t elementSize = element.tsize; + if (!elementSize) + return 0; - static struct Outer + static bool hasCustomToHash(const scope TypeInfo value) @trusted pure nothrow { - Inner inner1, inner2, inner3; - - nothrow @safe: - this(char first, char second, char third) - { - inner1 = Inner(first); - inner2 = Inner(second); - inner3 = Inner(third); - } + const element = getElement(value); - this(this) - { - order ~= "copy outer"; - } + if (const struct_ = cast(const TypeInfo_Struct) element) + return !!struct_.xtoHash; - ~this() - { - order ~= "destroy outer"; - } + return cast(const TypeInfo_Array) element + || cast(const TypeInfo_AssociativeArray) element + || cast(const ClassInfo) element + || cast(const TypeInfo_Interface) element; } - auto outer = Outer('1', '2', '3'); - - try _postblitRecurse(outer); - catch (FailedPostblitException) {} - catch (Exception) assert(false); - - auto postblitRecurseOrder = order; - order = null; - - try auto copy = outer; - catch (FailedPostblitException) {} - catch (Exception) assert(false); - - assert(postblitRecurseOrder == order); - order = null; - - Outer[3] arr = [Outer('1', '1', '1'), Outer('1', '2', '3'), Outer('3', '3', '3')]; - - try _postblitRecurse(arr); - catch (FailedPostblitException) {} - catch (Exception) assert(false); + if (!hasCustomToHash(element)) + return hashOf(ptr[0 .. elementSize * count]); - postblitRecurseOrder = order; - order = null; + size_t hash = 0; + foreach (size_t i; 0 .. count) + hash = hashOf(element.getHash(ptr + i * elementSize), hash); + return hash; +} - try auto arrCopy = arr; - catch (FailedPostblitException) {} - catch (Exception) assert(false); +/// Provide the .dup array property. +@property auto dup(T)(T[] a) + if (!is(const(T) : T)) +{ + import core.internal.traits : Unconst; + static assert(is(T : Unconst!T), "Cannot implicitly convert type "~T.stringof~ + " to "~Unconst!T.stringof~" in dup."); - assert(postblitRecurseOrder == order); + return _dup!(T, Unconst!T)(a); } -/++ - Destroys the given object and puts it in an invalid state. It's used to - _destroy an object so that any cleanup which its destructor or finalizer - does is done and so that it no longer references any other objects. It does - $(I not) initiate a GC cycle or free any GC memory. - +/ -void destroy(T)(T obj) if (is(T == class)) +/// +@safe unittest { - rt_finalize(cast(void*)obj); + auto arr = [1, 2]; + auto arr2 = arr.dup; + arr[0] = 0; + assert(arr == [0, 2]); + assert(arr2 == [1, 2]); } /// ditto -void destroy(T)(T obj) if (is(T == interface)) -{ - destroy(cast(Object)obj); -} - -version (unittest) unittest -{ - interface I { } - { - class A: I { string s = "A"; this() {} } - auto a = new A, b = new A; - a.s = b.s = "asd"; - destroy(a); - assert(a.s == "A"); - - I i = b; - destroy(i); - assert(b.s == "A"); - } - { - static bool destroyed = false; - class B: I - { - string s = "B"; - this() {} - ~this() - { - destroyed = true; - } - } - auto a = new B, b = new B; - a.s = b.s = "asd"; - destroy(a); - assert(destroyed); - assert(a.s == "B"); - - destroyed = false; - I i = b; - destroy(i); - assert(destroyed); - assert(b.s == "B"); - } - // this test is invalid now that the default ctor is not run after clearing - version (none) - { - class C - { - string s; - this() - { - s = "C"; - } - } - auto a = new C; - a.s = "asd"; - destroy(a); - assert(a.s == "C"); - } +// const overload to support implicit conversion to immutable (unique result, see DIP29) +@property T[] dup(T)(const(T)[] a) + if (is(const(T) : T)) +{ + return _dup!(const(T), T)(a); } -/// ditto -void destroy(T)(ref T obj) if (is(T == struct)) -{ - _destructRecurse(obj); - () @trusted { - auto buf = (cast(ubyte*) &obj)[0 .. T.sizeof]; - auto init = cast(ubyte[])typeid(T).initializer(); - if (init.ptr is null) // null ptr means initialize to 0s - buf[] = 0; - else - buf[] = init[]; - } (); -} -version (unittest) nothrow @safe @nogc unittest -{ - { - struct A { string s = "A"; } - A a; - a.s = "asd"; - destroy(a); - assert(a.s == "A"); - } - { - static int destroyed = 0; - struct C - { - string s = "C"; - ~this() nothrow @safe @nogc - { - destroyed ++; - } - } - - struct B - { - C c; - string s = "B"; - ~this() nothrow @safe @nogc - { - destroyed ++; - } - } - B a; - a.s = "asd"; - a.c.s = "jkl"; - destroy(a); - assert(destroyed == 2); - assert(a.s == "B"); - assert(a.c.s == "C" ); - } +/// Provide the .idup array property. +@property immutable(T)[] idup(T)(T[] a) +{ + static assert(is(T : immutable(T)), "Cannot implicitly convert type "~T.stringof~ + " to immutable in idup."); + return _dup!(T, immutable(T))(a); } /// ditto -void destroy(T : U[n], U, size_t n)(ref T obj) if (!is(T == struct)) +@property immutable(T)[] idup(T:void)(const(T)[] a) { - foreach_reverse (ref e; obj[]) - destroy(e); + return a.dup; } -version (unittest) unittest +/// +@safe unittest { - int[2] a; - a[0] = 1; - a[1] = 2; - destroy(a); - assert(a == [ 0, 0 ]); + char[] arr = ['a', 'b', 'c']; + string s = arr.idup; + arr[0] = '.'; + assert(s == "abc"); } -unittest +private U[] _dup(T, U)(scope T[] a) pure nothrow @trusted if (__traits(isPOD, T)) { - static struct vec2f { - float[2] values; - alias values this; - } + if (__ctfe) + return _dupCtfe!(T, U)(a); - vec2f v; - destroy!vec2f(v); + import core.stdc.string : memcpy; + auto arr = _d_newarrayU(typeid(T[]), a.length); + memcpy(arr.ptr, cast(const(void)*) a.ptr, T.sizeof * a.length); + return *cast(U[]*) &arr; } -unittest +private U[] _dupCtfe(T, U)(scope T[] a) { - // Bugzilla 15009 - static string op; - static struct S - { - int x; - this(int x) { op ~= "C" ~ cast(char)('0'+x); this.x = x; } - this(this) { op ~= "P" ~ cast(char)('0'+x); } - ~this() { op ~= "D" ~ cast(char)('0'+x); } - } - - { - S[2] a1 = [S(1), S(2)]; - op = ""; - } - assert(op == "D2D1"); // built-in scope destruction - { - S[2] a1 = [S(1), S(2)]; - op = ""; - destroy(a1); - assert(op == "D2D1"); // consistent with built-in behavior - } - - { - S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; - op = ""; - } - assert(op == "D4D3D2D1"); + static if (is(T : void)) + assert(0, "Cannot dup a void[] array at compile time."); + else { - S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; - op = ""; - destroy(a2); - assert(op == "D4D3D2D1", op); + U[] res; + foreach (ref e; a) + res ~= e; + return res; } } -/// ditto -void destroy(T)(ref T obj) - if (!is(T == struct) && !is(T == interface) && !is(T == class) && !_isStaticArray!T) +private U[] _dup(T, U)(T[] a) if (!__traits(isPOD, T)) { - obj = T.init; -} + // note: copyEmplace is `@system` inside a `@trusted` block, so the __ctfe branch + // has the extra duty to infer _dup `@system` when the copy-constructor is `@system`. + if (__ctfe) + return _dupCtfe!(T, U)(a); -template _isStaticArray(T : U[N], U, size_t N) -{ - enum bool _isStaticArray = true; -} + import core.lifetime: copyEmplace; + U[] res = () @trusted { + auto arr = cast(U*) _d_newarrayU(typeid(T[]), a.length); + size_t i; + scope (failure) + { + import core.internal.lifetime: emplaceInitializer; + // Initialize all remaining elements to not destruct garbage + foreach (j; i .. a.length) + emplaceInitializer(cast() arr[j]); + } + for (; i < a.length; i++) + { + copyEmplace(a.ptr[i], arr[i]); + } + return cast(U[])(arr[0..a.length]); + } (); -template _isStaticArray(T) -{ - enum bool _isStaticArray = false; + return res; } -version (unittest) unittest +// https://issues.dlang.org/show_bug.cgi?id=22107 +@safe unittest { - { - int a = 42; - destroy(a); - assert(a == 0); - } - { - float a = 42; - destroy(a); - assert(isnan(a)); - } -} + static int i; + @safe struct S + { + this(this) { i++; } + } -version (unittest) -{ - private bool isnan(float x) + void fun(scope S[] values...) @safe { - return x != x; + values.dup; } } -private -{ - extern (C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) nothrow; - extern (C) size_t _d_arraysetcapacity(const TypeInfo ti, size_t newcapacity, void *arrptr) pure nothrow; -} +// HACK: This is a lie. `_d_arraysetcapacity` is neither `nothrow` nor `pure`, but this lie is +// necessary for now to prevent breaking code. +private extern (C) size_t _d_arraysetcapacity(const TypeInfo ti, size_t newcapacity, void[]* arrptr) pure nothrow; /** - * (Property) Gets the current _capacity of a slice. The _capacity is the size - * that the slice can grow to before the underlying array must be - * reallocated or extended. - * - * If an append must reallocate a slice with no possibility of extension, then - * `0` is returned. This happens when the slice references a static array, or - * if another slice references elements past the end of the current slice. - * - * Note: The _capacity of a slice may be impacted by operations on other slices. - */ +(Property) Gets the current _capacity of a slice. The _capacity is the size +that the slice can grow to before the underlying array must be +reallocated or extended. + +If an append must reallocate a slice with no possibility of extension, then +`0` is returned. This happens when the slice references a static array, or +if another slice references elements past the end of the current slice. + +Note: The _capacity of a slice may be impacted by operations on other slices. +*/ @property size_t capacity(T)(T[] arr) pure nothrow @trusted { - return _d_arraysetcapacity(typeid(T[]), 0, cast(void *)&arr); + return _d_arraysetcapacity(typeid(T[]), 0, cast(void[]*)&arr); } + /// @safe unittest { @@ -2948,38 +3699,54 @@ private } /** - * Reserves capacity for a slice. The capacity is the size - * that the slice can grow to before the underlying array must be - * reallocated or extended. - * - * Returns: The new capacity of the array (which may be larger than - * the requested capacity). - */ +Reserves capacity for a slice. The capacity is the size +that the slice can grow to before the underlying array must be +reallocated or extended. + +Returns: The new capacity of the array (which may be larger than +the requested capacity). +*/ size_t reserve(T)(ref T[] arr, size_t newcapacity) pure nothrow @trusted { - return _d_arraysetcapacity(typeid(T[]), newcapacity, cast(void *)&arr); + if (__ctfe) + return newcapacity; + else + return _d_arraysetcapacity(typeid(T[]), newcapacity, cast(void[]*)&arr); } + /// -unittest +@safe unittest { //Static array slice: no capacity. Reserve relocates. int[4] sarray = [1, 2, 3, 4]; int[] slice = sarray[]; auto u = slice.reserve(8); assert(u >= 8); - assert(sarray.ptr !is slice.ptr); + assert(&sarray[0] !is &slice[0]); assert(slice.capacity == u); //Dynamic array slices int[] a = [1, 2, 3, 4]; a.reserve(8); //prepare a for appending 4 more items - auto p = a.ptr; + auto p = &a[0]; u = a.capacity; a ~= [5, 6, 7, 8]; - assert(p == a.ptr); //a should not have been reallocated + assert(p == &a[0]); //a should not have been reallocated assert(u == a.capacity); //a should not have been extended } +// https://issues.dlang.org/show_bug.cgi?id=12330, reserve() at CTFE time +@safe unittest +{ + int[] foo() { + int[] result; + auto a = result.reserve = 5; + assert(a == 5); + return result; + } + enum r = foo(); +} + // Issue 6646: should be possible to use array.reserve from SafeD. @safe unittest { @@ -2987,28 +3754,33 @@ unittest a.reserve(10); } +// HACK: This is a lie. `_d_arrayshrinkfit` is not `nothrow`, but this lie is necessary +// for now to prevent breaking code. +private extern (C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) nothrow; + /** - * Assume that it is safe to append to this array. Appends made to this array - * after calling this function may append in place, even if the array was a - * slice of a larger array to begin with. - * - * Use this only when it is certain there are no elements in use beyond the - * array in the memory block. If there are, those elements will be - * overwritten by appending to this array. - * - * Warning: Calling this function, and then using references to data located after the - * given array results in undefined behavior. - * - * Returns: - * The input is returned. - */ -auto ref inout(T[]) assumeSafeAppend(T)(auto ref inout(T[]) arr) nothrow +Assume that it is safe to append to this array. Appends made to this array +after calling this function may append in place, even if the array was a +slice of a larger array to begin with. + +Use this only when it is certain there are no elements in use beyond the +array in the memory block. If there are, those elements will be +overwritten by appending to this array. + +Warning: Calling this function, and then using references to data located after the +given array results in undefined behavior. + +Returns: + The input is returned. +*/ +auto ref inout(T[]) assumeSafeAppend(T)(auto ref inout(T[]) arr) nothrow @system { _d_arrayshrinkfit(typeid(T[]), *(cast(void[]*)&arr)); return arr; } + /// -unittest +@system unittest { int[] a = [1, 2, 3, 4]; @@ -3026,7 +3798,7 @@ unittest } } -unittest +@system unittest { int[] arr; auto newcap = arr.reserve(2000); @@ -3042,7 +3814,7 @@ unittest assert(ptr == arr.ptr); } -unittest +@system unittest { int[] arr = [1, 2, 3]; void foo(ref int[] i) @@ -3056,7 +3828,7 @@ unittest } // https://issues.dlang.org/show_bug.cgi?id=10574 -unittest +@system unittest { int[] a; immutable(int[]) b; @@ -3070,914 +3842,913 @@ unittest assert(is(typeof(b3) == immutable(int[]))); } -version (none) -{ - // enforce() copied from Phobos std.contracts for destroy(), left out until - // we decide whether to use it. - +private extern (C) void[] _d_newarrayU(const scope TypeInfo ti, size_t length) pure nothrow; - T _enforce(T, string file = __FILE__, int line = __LINE__) - (T value, lazy const(char)[] msg = null) +private void _doPostblit(T)(T[] arr) +{ + // infer static postblit type, run postblit if any + static if (__traits(hasPostblit, T)) { - if (!value) bailOut(file, line, msg); - return value; + static if (__traits(isStaticArray, T) && is(T : E[], E)) + _doPostblit(cast(E[]) arr); + else static if (!is(typeof(arr[0].__xpostblit())) && is(immutable T == immutable U, U)) + foreach (ref elem; (() @trusted => cast(U[]) arr)()) + elem.__xpostblit(); + else + foreach (ref elem; arr) + elem.__xpostblit(); } +} - T _enforce(T, string file = __FILE__, int line = __LINE__) - (T value, scope void delegate() dg) - { - if (!value) dg(); - return value; - } +@safe unittest +{ + static struct S1 { int* p; } + static struct S2 { @disable this(); } + static struct S3 { @disable this(this); } - T _enforce(T)(T value, lazy Exception ex) + int dg1() pure nothrow @safe { - if (!value) throw ex(); - return value; + { + char[] m; + string i; + m = m.dup; + i = i.idup; + m = i.dup; + i = m.idup; + } + { + S1[] m; + immutable(S1)[] i; + m = m.dup; + i = i.idup; + static assert(!is(typeof(m.idup))); + static assert(!is(typeof(i.dup))); + } + { + S3[] m; + immutable(S3)[] i; + static assert(!is(typeof(m.dup))); + static assert(!is(typeof(i.idup))); + } + { + shared(S1)[] m; + m = m.dup; + static assert(!is(typeof(m.idup))); + } + { + int[] a = (inout(int)) { inout(const(int))[] a; return a.dup; }(0); + } + return 1; } - private void _bailOut(string file, int line, in char[] msg) + int dg2() pure nothrow @safe { - char[21] buf; - throw new Exception(cast(string)(file ~ "(" ~ ulongToString(buf[], line) ~ "): " ~ (msg ? msg : "Enforcement failed"))); + { + S2[] m = [S2.init, S2.init]; + immutable(S2)[] i = [S2.init, S2.init]; + m = m.dup; + m = i.dup; + i = m.idup; + i = i.idup; + } + return 2; } -} + enum a = dg1(); + enum b = dg2(); + assert(dg1() == a); + assert(dg2() == b); +} -/*************************************** - * Helper function used to see if two containers of different - * types have the same contents in the same sequence. - */ - -bool _ArrayEq(T1, T2)(T1[] a1, T2[] a2) +@system unittest { - if (a1.length != a2.length) - return false; + static struct Sunpure { this(this) @safe nothrow {} } + static struct Sthrow { this(this) @safe pure {} } + static struct Sunsafe { this(this) @system pure nothrow {} } + static struct Snocopy { @disable this(this); } - // This is function is used as a compiler intrinsic and explicitly written - // in a lowered flavor to use as few CTFE instructions as possible. - size_t idx = 0; - immutable length = a1.length; + [].dup!Sunpure; + [].dup!Sthrow; + cast(void) [].dup!Sunsafe; + static assert(!__traits(compiles, () pure { [].dup!Sunpure; })); + static assert(!__traits(compiles, () nothrow { [].dup!Sthrow; })); + static assert(!__traits(compiles, () @safe { [].dup!Sunsafe; })); + static assert(!__traits(compiles, () { [].dup!Snocopy; })); - for (;idx < length;++idx) - { - if (a1[idx] != a2[idx]) - return false; - } - return true; + [].idup!Sunpure; + [].idup!Sthrow; + [].idup!Sunsafe; + static assert(!__traits(compiles, () pure { [].idup!Sunpure; })); + static assert(!__traits(compiles, () nothrow { [].idup!Sthrow; })); + static assert(!__traits(compiles, () @safe { [].idup!Sunsafe; })); + static assert(!__traits(compiles, () { [].idup!Snocopy; })); } -version (D_Ddoc) +@safe unittest { - // This lets DDoc produce better documentation. + // test that the copy-constructor is called with .dup + static struct ArrElem + { + int a; + this(int a) + { + this.a = a; + } + this(ref const ArrElem) + { + a = 2; + } + this(ref ArrElem) immutable + { + a = 3; + } + } - /** - Calculates the hash value of `arg` with an optional `seed` initial value. - The result might not be equal to `typeid(T).getHash(&arg)`. + auto arr = [ArrElem(1), ArrElem(1)]; - Params: - arg = argument to calculate the hash value of - seed = optional `seed` value (may be used for hash chaining) + ArrElem[] b = arr.dup; + assert(b[0].a == 2 && b[1].a == 2); - Return: calculated hash value of `arg` - */ - size_t hashOf(T)(auto ref T arg, size_t seed) - { - static import core.internal.hash; - return core.internal.hash.hashOf(arg, seed); - } - /// ditto - size_t hashOf(T)(auto ref T arg) - { - static import core.internal.hash; - return core.internal.hash.hashOf(arg); - } -} -else -{ - public import core.internal.hash : hashOf; + immutable ArrElem[] c = arr.idup; + assert(c[0].a == 3 && c[1].a == 3); } -unittest +@system unittest { - // Issue # 16654 / 16764 - auto a = [1]; - auto b = a.dup; - assert(hashOf(a) == hashOf(b)); + static struct Sunpure { this(ref const typeof(this)) @safe nothrow {} } + static struct Sthrow { this(ref const typeof(this)) @safe pure {} } + static struct Sunsafe { this(ref const typeof(this)) @system pure nothrow {} } + [].dup!Sunpure; + [].dup!Sthrow; + cast(void) [].dup!Sunsafe; + static assert(!__traits(compiles, () pure { [].dup!Sunpure; })); + static assert(!__traits(compiles, () nothrow { [].dup!Sthrow; })); + static assert(!__traits(compiles, () @safe { [].dup!Sunsafe; })); + + // for idup to work on structs that have copy constructors, it is necessary + // that the struct defines a copy constructor that creates immutable objects + static struct ISunpure { this(ref const typeof(this)) immutable @safe nothrow {} } + static struct ISthrow { this(ref const typeof(this)) immutable @safe pure {} } + static struct ISunsafe { this(ref const typeof(this)) immutable @system pure nothrow {} } + [].idup!ISunpure; + [].idup!ISthrow; + [].idup!ISunsafe; + static assert(!__traits(compiles, () pure { [].idup!ISunpure; })); + static assert(!__traits(compiles, () nothrow { [].idup!ISthrow; })); + static assert(!__traits(compiles, () @safe { [].idup!ISunsafe; })); } -bool _xopEquals(in void*, in void*) +@safe unittest { - throw new Error("TypeInfo.equals is not implemented"); + static int*[] pureFoo() pure { return null; } + { char[] s; immutable x = s.dup; } + { immutable x = (cast(int*[])null).dup; } + { immutable x = pureFoo(); } + { immutable x = pureFoo().dup; } } -bool _xopCmp(in void*, in void*) +@safe unittest { - throw new Error("TypeInfo.compare is not implemented"); + auto a = [1, 2, 3]; + auto b = a.dup; + debug(SENTINEL) {} else + assert(b.capacity >= 3); } -void __ctfeWrite(const string s) @nogc @safe pure nothrow {} +@system unittest +{ + // Bugzilla 12580 + void[] m = [0]; + shared(void)[] s = [cast(shared)1]; + immutable(void)[] i = [cast(immutable)2]; -/****************************************** - * Create RTInfo for type T - */ + s = s.dup; + static assert(is(typeof(s.dup) == shared(void)[])); -template RTInfoImpl(size_t[] pointerBitmap) -{ - immutable size_t[pointerBitmap.length] RTInfoImpl = pointerBitmap[]; + m = i.dup; + i = m.dup; + i = i.idup; + i = m.idup; + i = s.idup; + i = s.dup; + static assert(!__traits(compiles, m = s.dup)); } -template NoPointersBitmapPayload(size_t N) +@safe unittest { - enum size_t[N] NoPointersBitmapPayload = 0; + // Bugzilla 13809 + static struct S + { + this(this) {} + ~this() {} + } + + S[] arr; + auto a = arr.dup; } -template RTInfo(T) +@system unittest { - enum pointerBitmap = __traits(getPointerBitmap, T); - static if (pointerBitmap[1 .. $] == NoPointersBitmapPayload!(pointerBitmap.length - 1)) - enum RTInfo = rtinfoNoPointers; - else - enum RTInfo = RTInfoImpl!(pointerBitmap).ptr; -} + // Bugzilla 16504 + static struct S + { + __gshared int* gp; + int* p; + // postblit and hence .dup could escape + this(this) { gp = p; } + } -/** -* shortcuts for the precise GC, also generated by the compiler -* used instead of the actual pointer bitmap -*/ -enum immutable(void)* rtinfoNoPointers = null; -enum immutable(void)* rtinfoHasPointers = cast(void*)1; + int p; + scope S[1] arr = [S(&p)]; + auto a = arr.dup; // dup does escape +} -// lhs == rhs lowers to __equals(lhs, rhs) for dynamic arrays -bool __equals(T1, T2)(T1[] lhs, T2[] rhs) +// https://issues.dlang.org/show_bug.cgi?id=21983 +// dup/idup destroys partially constructed arrays on failure +@safe unittest { - import core.internal.traits : Unqual; - alias U1 = Unqual!T1; - alias U2 = Unqual!T2; - - static @trusted ref R at(R)(R[] r, size_t i) { return r.ptr[i]; } - static @trusted R trustedCast(R, S)(S[] r) { return cast(R) r; } - - if (lhs.length != rhs.length) - return false; - - if (lhs.length == 0 && rhs.length == 0) - return true; - - static if (is(U1 == void) && is(U2 == void)) - { - return __equals(trustedCast!(ubyte[])(lhs), trustedCast!(ubyte[])(rhs)); - } - else static if (is(U1 == void)) - { - return __equals(trustedCast!(ubyte[])(lhs), rhs); - } - else static if (is(U2 == void)) - { - return __equals(lhs, trustedCast!(ubyte[])(rhs)); - } - else static if (!is(U1 == U2)) - { - // This should replace src/object.d _ArrayEq which - // compares arrays of different types such as long & int, - // char & wchar. - // Compiler lowers to __ArrayEq in dmd/src/opover.d - foreach (const u; 0 .. lhs.length) - { - if (at(lhs, u) != at(rhs, u)) - return false; - } - return true; - } - else static if (__traits(isIntegral, U1)) + static struct SImpl(bool postblit) { + int num; + long l = 0xDEADBEEF; - if (!__ctfe) + static if (postblit) { - import core.stdc.string : memcmp; - return () @trusted { return memcmp(cast(void*)lhs.ptr, cast(void*)rhs.ptr, lhs.length * U1.sizeof) == 0; }(); + this(this) + { + if (this.num == 3) + throw new Exception(""); + } } else { - foreach (const u; 0 .. lhs.length) + this(scope ref const SImpl other) { - if (at(lhs, u) != at(rhs, u)) - return false; + if (other.num == 3) + throw new Exception(""); + + this.num = other.num; + this.l = other.l; } - return true; } - } - else - { - foreach (const u; 0 .. lhs.length) + + ~this() @trusted { - static if (__traits(compiles, __equals(at(lhs, u), at(rhs, u)))) - { - if (!__equals(at(lhs, u), at(rhs, u))) - return false; - } - else static if (__traits(isFloating, U1)) - { - if (at(lhs, u) != at(rhs, u)) - return false; - } - else static if (is(U1 : Object) && is(U2 : Object)) - { - if (!(cast(Object)at(lhs, u) is cast(Object)at(rhs, u) - || at(lhs, u) && (cast(Object)at(lhs, u)).opEquals(cast(Object)at(rhs, u)))) - return false; - } - else static if (__traits(hasMember, U1, "opEquals")) - { - if (!at(lhs, u).opEquals(at(rhs, u))) - return false; - } - else static if (is(U1 == delegate)) - { - if (at(lhs, u) != at(rhs, u)) - return false; - } - else static if (is(U1 == U11*, U11)) - { - if (at(lhs, u) != at(rhs, u)) - return false; - } - else + if (l != 0xDEADBEEF) { - if (at(lhs, u).tupleof != at(rhs, u).tupleof) - return false; + import core.stdc.stdio; + printf("Unexpected value: %lld\n", l); + fflush(stdout); + assert(false); } } - - return true; } -} -unittest { - assert(__equals([], [])); - assert(!__equals([1, 2], [1, 2, 3])); -} + alias Postblit = SImpl!true; + alias Copy = SImpl!false; -unittest -{ - struct A + static int test(S)() { - int a; + S[4] arr = [ S(1), S(2), S(3), S(4) ]; + try + { + arr.dup(); + assert(false); + } + catch (Exception) + { + return 1; + } } - auto arr1 = [A(0), A(2)]; - auto arr2 = [A(0), A(1)]; - auto arr3 = [A(0), A(1)]; + static assert(test!Postblit()); + assert(test!Postblit()); - assert(arr1 != arr2); - assert(arr2 == arr3); + static assert(test!Copy()); + assert(test!Copy()); } -unittest +/** +Destroys the given object and optionally resets to initial state. It's used to +_destroy an object, calling its destructor or finalizer so it no longer +references any other objects. It does $(I not) initiate a GC cycle or free +any GC memory. +If `initialize` is supplied `false`, the object is considered invalid after +destruction, and should not be referenced. +*/ +void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct)) { - struct A - { - int a; - int b; - - bool opEquals(const A other) - { - return this.a == other.b && this.b == other.a; - } - } + import core.internal.destruction : destructRecurse; - auto arr1 = [A(1, 0), A(0, 1)]; - auto arr2 = [A(1, 0), A(0, 1)]; - auto arr3 = [A(0, 1), A(1, 0)]; + destructRecurse(obj); - assert(arr1 != arr2); - assert(arr2 == arr3); + static if (initialize) + { + import core.internal.lifetime : emplaceInitializer; + emplaceInitializer(obj); // emplace T.init + } } -// Compare class and interface objects for ordering. -private int __cmp(Obj)(Obj lhs, Obj rhs) -if (is(Obj : Object)) +@safe unittest { - if (lhs is rhs) - return 0; - // Regard null references as always being "less than" - if (!lhs) - return -1; - if (!rhs) - return 1; - return lhs.opCmp(rhs); + struct A { string s = "A"; } + A a = {s: "B"}; + assert(a.s == "B"); + a.destroy; + assert(a.s == "A"); } -int __cmp(T)(const T[] lhs, const T[] rhs) @trusted -if (__traits(isScalar, T)) -{ - // Compute U as the implementation type for T - static if (is(T == ubyte) || is(T == void) || is(T == bool)) - alias U = char; - else static if (is(T == wchar)) - alias U = ushort; - else static if (is(T == dchar)) - alias U = uint; - else static if (is(T == ifloat)) - alias U = float; - else static if (is(T == idouble)) - alias U = double; - else static if (is(T == ireal)) - alias U = real; - else - alias U = T; - - static if (is(U == char)) - { - import core.internal.string : dstrcmp; - return dstrcmp(cast(char[]) lhs, cast(char[]) rhs); - } - else static if (!is(U == T)) +nothrow @safe @nogc unittest +{ { - // Reuse another implementation - return __cmp(cast(U[]) lhs, cast(U[]) rhs); + struct A { string s = "A"; } + A a; + a.s = "asd"; + destroy!false(a); + assert(a.s == "asd"); + destroy(a); + assert(a.s == "A"); } - else { - immutable len = lhs.length <= rhs.length ? lhs.length : rhs.length; - foreach (const u; 0 .. len) + static int destroyed = 0; + struct C { - static if (__traits(isFloating, T)) + string s = "C"; + ~this() nothrow @safe @nogc { - immutable a = lhs.ptr[u], b = rhs.ptr[u]; - static if (is(T == cfloat) || is(T == cdouble) - || is(T == creal)) - { - // Use rt.cmath2._Ccmp instead ? - auto r = (a.re > b.re) - (a.re < b.re); - if (!r) r = (a.im > b.im) - (a.im < b.im); - } - else - { - const r = (a > b) - (a < b); - } - if (r) return r; + destroyed ++; + } + } + + struct B + { + C c; + string s = "B"; + ~this() nothrow @safe @nogc + { + destroyed ++; } - else if (lhs.ptr[u] != rhs.ptr[u]) - return lhs.ptr[u] < rhs.ptr[u] ? -1 : 1; } - return lhs.length < rhs.length ? -1 : (lhs.length > rhs.length); + B a; + a.s = "asd"; + a.c.s = "jkl"; + destroy!false(a); + assert(destroyed == 2); + assert(a.s == "asd"); + assert(a.c.s == "jkl" ); + destroy(a); + assert(destroyed == 4); + assert(a.s == "B"); + assert(a.c.s == "C" ); } } -// This function is called by the compiler when dealing with array -// comparisons in the semantic analysis phase of CmpExp. The ordering -// comparison is lowered to a call to this template. -int __cmp(T1, T2)(T1[] s1, T2[] s2) -if (!__traits(isScalar, T1) && !__traits(isScalar, T2)) -{ - import core.internal.traits : Unqual; - alias U1 = Unqual!T1; - alias U2 = Unqual!T2; - - static if (is(U1 == void) && is(U2 == void)) - static @trusted ref inout(ubyte) at(inout(void)[] r, size_t i) { return (cast(inout(ubyte)*) r.ptr)[i]; } - else - static @trusted ref R at(R)(R[] r, size_t i) { return r.ptr[i]; } - - // All unsigned byte-wide types = > dstrcmp - immutable len = s1.length <= s2.length ? s1.length : s2.length; +private extern (C) void rt_finalize(void *data, bool det=true) nothrow; - foreach (const u; 0 .. len) +/// ditto +void destroy(bool initialize = true, T)(T obj) if (is(T == class)) +{ + static if (__traits(getLinkage, T) == "C++") { - static if (__traits(compiles, __cmp(at(s1, u), at(s2, u)))) - { - auto c = __cmp(at(s1, u), at(s2, u)); - if (c != 0) - return c; - } - else static if (__traits(compiles, at(s1, u).opCmp(at(s2, u)))) - { - auto c = at(s1, u).opCmp(at(s2, u)); - if (c != 0) - return c; - } - else static if (__traits(compiles, at(s1, u) < at(s2, u))) - { - if (at(s1, u) != at(s2, u)) - return at(s1, u) < at(s2, u) ? -1 : 1; - } - else + static if (__traits(hasMember, T, "__xdtor")) + obj.__xdtor(); + + static if (initialize) { - // TODO: fix this legacy bad behavior, see - // https://issues.dlang.org/show_bug.cgi?id=17244 - static assert(is(U1 == U2), "Internal error."); - import core.stdc.string : memcmp; - auto c = (() @trusted => memcmp(&at(s1, u), &at(s2, u), U1.sizeof))(); - if (c != 0) - return c; + enum classSize = __traits(classInstanceSize, T); + (cast(void*)obj)[0 .. classSize] = typeid(T).initializer[]; } } - return s1.length < s2.length ? -1 : (s1.length > s2.length); + else + rt_finalize(cast(void*)obj); } -// integral types -@safe unittest +/// ditto +void destroy(bool initialize = true, T)(T obj) if (is(T == interface)) { - void compareMinMax(T)() - { - T[2] a = [T.max, T.max]; - T[2] b = [T.min, T.min]; - - assert(__cmp(a, b) > 0); - assert(__cmp(b, a) < 0); - } + static assert(__traits(getLinkage, T) == "D", "Invalid call to destroy() on extern(" ~ __traits(getLinkage, T) ~ ") interface"); - compareMinMax!int; - compareMinMax!uint; - compareMinMax!long; - compareMinMax!ulong; - compareMinMax!short; - compareMinMax!ushort; - compareMinMax!byte; - compareMinMax!dchar; - compareMinMax!wchar; + destroy!initialize(cast(Object)obj); } -// char types (dstrcmp) -@safe unittest +/// Reference type demonstration +@system unittest { - void compareMinMax(T)() + class C { - T[2] a = [T.max, T.max]; - T[2] b = [T.min, T.min]; + struct Agg + { + static int dtorCount; - assert(__cmp(a, b) > 0); - assert(__cmp(b, a) < 0); + int x = 10; + ~this() { dtorCount++; } + } + + static int dtorCount; + + string s = "S"; + Agg a; + ~this() { dtorCount++; } } - compareMinMax!ubyte; - compareMinMax!bool; - compareMinMax!char; - compareMinMax!(const char); + C c = new C(); + assert(c.dtorCount == 0); // destructor not yet called + assert(c.s == "S"); // initial state `c.s` is `"S"` + assert(c.a.dtorCount == 0); // destructor not yet called + assert(c.a.x == 10); // initial state `c.a.x` is `10` + c.s = "T"; + c.a.x = 30; + assert(c.s == "T"); // `c.s` is `"T"` + destroy(c); + assert(c.dtorCount == 1); // `c`'s destructor was called + assert(c.s == "S"); // `c.s` is back to its inital state, `"S"` + assert(c.a.dtorCount == 1); // `c.a`'s destructor was called + assert(c.a.x == 10); // `c.a.x` is back to its inital state, `10` - string s1 = "aaaa"; - string s2 = "bbbb"; - assert(__cmp(s2, s1) > 0); - assert(__cmp(s1, s2) < 0); -} + // check C++ classes work too! + extern (C++) class CPP + { + struct Agg + { + __gshared int dtorCount; + + int x = 10; + ~this() { dtorCount++; } + } -// fp types + __gshared int dtorCount; + + string s = "S"; + Agg a; + ~this() { dtorCount++; } + } + + CPP cpp = new CPP(); + assert(cpp.dtorCount == 0); // destructor not yet called + assert(cpp.s == "S"); // initial state `cpp.s` is `"S"` + assert(cpp.a.dtorCount == 0); // destructor not yet called + assert(cpp.a.x == 10); // initial state `cpp.a.x` is `10` + cpp.s = "T"; + cpp.a.x = 30; + assert(cpp.s == "T"); // `cpp.s` is `"T"` + destroy!false(cpp); // destroy without initialization + assert(cpp.dtorCount == 1); // `cpp`'s destructor was called + assert(cpp.s == "T"); // `cpp.s` is not initialized + assert(cpp.a.dtorCount == 1); // `cpp.a`'s destructor was called + assert(cpp.a.x == 30); // `cpp.a.x` is not initialized + destroy(cpp); + assert(cpp.dtorCount == 2); // `cpp`'s destructor was called again + assert(cpp.s == "S"); // `cpp.s` is back to its inital state, `"S"` + assert(cpp.a.dtorCount == 2); // `cpp.a`'s destructor was called again + assert(cpp.a.x == 10); // `cpp.a.x` is back to its inital state, `10` +} + +/// Value type demonstration @safe unittest { - void compareMinMax(T)() - { - T[2] a = [T.max, T.max]; - T[2] b = [T.min_normal, T.min_normal]; - T[2] c = [T.max, T.min_normal]; - T[1] d = [T.max]; + int i; + assert(i == 0); // `i`'s initial state is `0` + i = 1; + assert(i == 1); // `i` changed to `1` + destroy!false(i); + assert(i == 1); // `i` was not initialized + destroy(i); + assert(i == 0); // `i` is back to its initial state `0` +} - assert(__cmp(a, b) > 0); - assert(__cmp(b, a) < 0); - assert(__cmp(a, c) > 0); - assert(__cmp(a, d) > 0); - assert(__cmp(d, c) < 0); - assert(__cmp(c, c) == 0); +@system unittest +{ + extern(C++) + static class C + { + void* ptr; + this() {} } - compareMinMax!real; - compareMinMax!float; - compareMinMax!double; - compareMinMax!ireal; - compareMinMax!ifloat; - compareMinMax!idouble; - compareMinMax!creal; - //compareMinMax!cfloat; - compareMinMax!cdouble; - - // qualifiers - compareMinMax!(const real); - compareMinMax!(immutable real); + destroy!false(new C()); + destroy!true(new C()); } -// void[] -@safe unittest +@system unittest { - void[] a; - const(void)[] b; + // class with an `alias this` + class A + { + static int dtorCount; + ~this() + { + dtorCount++; + } + } - (() @trusted + class B { - a = cast(void[]) "bb"; - b = cast(const(void)[]) "aa"; - })(); + A a; + alias a this; + this() + { + a = new A; + } + static int dtorCount; + ~this() + { + dtorCount++; + } + } + auto b = new B; + assert(A.dtorCount == 0); + assert(B.dtorCount == 0); + destroy(b); + assert(A.dtorCount == 0); + assert(B.dtorCount == 1); - assert(__cmp(a, b) > 0); - assert(__cmp(b, a) < 0); + auto a = new A; + destroy(a); + assert(A.dtorCount == 1); } -// arrays of arrays with mixed modifiers -@safe unittest +@system unittest { - // https://issues.dlang.org/show_bug.cgi?id=17876 - bool less1(immutable size_t[][] a, size_t[][] b) { return a < b; } - bool less2(const void[][] a, void[][] b) { return a < b; } - bool less3(inout size_t[][] a, size_t[][] b) { return a < b; } - - immutable size_t[][] a = [[1, 2], [3, 4]]; - size_t[][] b = [[1, 2], [3, 5]]; - assert(less1(a, b)); - assert(less3(a, b)); + interface I { } + { + class A: I { string s = "A"; this() {} } + auto a = new A, b = new A; + a.s = b.s = "asd"; + destroy(a); + assert(a.s == "A"); - auto va = [cast(immutable void[])a[0], a[1]]; - auto vb = [cast(void[])b[0], b[1]]; - assert(less2(va, vb)); + I i = b; + destroy(i); + assert(b.s == "A"); + } + { + static bool destroyed = false; + class B: I + { + string s = "B"; + this() {} + ~this() + { + destroyed = true; + } + } + auto a = new B, b = new B; + a.s = b.s = "asd"; + destroy(a); + assert(destroyed); + assert(a.s == "B"); + + destroyed = false; + I i = b; + destroy(i); + assert(destroyed); + assert(b.s == "B"); + } + // this test is invalid now that the default ctor is not run after clearing + version (none) + { + class C + { + string s; + this() + { + s = "C"; + } + } + auto a = new C; + a.s = "asd"; + destroy(a); + assert(a.s == "C"); + } } -// objects -@safe unittest +nothrow @safe @nogc unittest { - class C { - int i; - this(int i) { this.i = i; } + struct A { string s = "A"; } + A a; + a.s = "asd"; + destroy!false(a); + assert(a.s == "asd"); + destroy(a); + assert(a.s == "A"); + } + { + static int destroyed = 0; + struct C + { + string s = "C"; + ~this() nothrow @safe @nogc + { + destroyed ++; + } + } - override int opCmp(Object c) const @safe + struct B { - return i - (cast(C)c).i; + C c; + string s = "B"; + ~this() nothrow @safe @nogc + { + destroyed ++; + } } + B a; + a.s = "asd"; + a.c.s = "jkl"; + destroy!false(a); + assert(destroyed == 2); + assert(a.s == "asd"); + assert(a.c.s == "jkl" ); + destroy(a); + assert(destroyed == 4); + assert(a.s == "B"); + assert(a.c.s == "C" ); } - - auto c1 = new C(1); - auto c2 = new C(2); - assert(__cmp(c1, null) > 0); - assert(__cmp(null, c1) < 0); - assert(__cmp(c1, c1) == 0); - assert(__cmp(c1, c2) < 0); - assert(__cmp(c2, c1) > 0); - - assert(__cmp([c1, c1][], [c2, c2][]) < 0); - assert(__cmp([c2, c2], [c1, c1]) > 0); } -// structs -@safe unittest +nothrow unittest { - struct C + // Bugzilla 20049: Test to ensure proper behavior of `nothrow` destructors + class C { - ubyte i; - this(ubyte i) { this.i = i; } + static int dtorCount = 0; + this() nothrow {} + ~this() nothrow { dtorCount++; } } - auto c1 = C(1); - auto c2 = C(2); - - assert(__cmp([c1, c1][], [c2, c2][]) < 0); - assert(__cmp([c2, c2], [c1, c1]) > 0); - assert(__cmp([c2, c2], [c2, c1]) > 0); + auto c = new C; + destroy(c); + assert(C.dtorCount == 1); } -// Compiler hook into the runtime implementation of array (vector) operations. -template _arrayOp(Args...) +/// ditto +void destroy(bool initialize = true, T)(ref T obj) +if (__traits(isStaticArray, T)) { - import core.internal.arrayop; - alias _arrayOp = arrayOp!Args; + foreach_reverse (ref e; obj[]) + destroy!initialize(e); } -// Helper functions - -private inout(TypeInfo) getElement(inout TypeInfo value) @trusted pure nothrow +@safe unittest { - TypeInfo element = cast() value; - for (;;) - { - if (auto qualified = cast(TypeInfo_Const) element) - element = qualified.base; - else if (auto redefined = cast(TypeInfo_Enum) element) - element = redefined.base; - else if (auto staticArray = cast(TypeInfo_StaticArray) element) - element = staticArray.value; - else if (auto vector = cast(TypeInfo_Vector) element) - element = vector.base; - else - break; - } - return cast(inout) element; + int[2] a; + a[0] = 1; + a[1] = 2; + destroy!false(a); + assert(a == [ 1, 2 ]); + destroy(a); + assert(a == [ 0, 0 ]); } -private size_t getArrayHash(in TypeInfo element, in void* ptr, in size_t count) @trusted nothrow +@safe unittest { - if (!count) - return 0; + static struct vec2f { + float[2] values; + alias values this; + } - const size_t elementSize = element.tsize; - if (!elementSize) - return 0; + vec2f v; + destroy!(true, vec2f)(v); +} - static bool hasCustomToHash(in TypeInfo value) @trusted pure nothrow +@system unittest +{ + // Bugzilla 15009 + static string op; + static struct S { - const element = getElement(value); - - if (const struct_ = cast(const TypeInfo_Struct) element) - return !!struct_.xtoHash; - - return cast(const TypeInfo_Array) element - || cast(const TypeInfo_AssociativeArray) element - || cast(const ClassInfo) element - || cast(const TypeInfo_Interface) element; + int x; + this(int x) { op ~= "C" ~ cast(char)('0'+x); this.x = x; } + this(this) { op ~= "P" ~ cast(char)('0'+x); } + ~this() { op ~= "D" ~ cast(char)('0'+x); } } - import core.internal.traits : externDFunc; - if (!hasCustomToHash(element)) - return hashOf(ptr[0 .. elementSize * count]); + { + S[2] a1 = [S(1), S(2)]; + op = ""; + } + assert(op == "D2D1"); // built-in scope destruction + { + S[2] a1 = [S(1), S(2)]; + op = ""; + destroy(a1); + assert(op == "D2D1"); // consistent with built-in behavior + } - size_t hash = 0; - foreach (size_t i; 0 .. count) - hash = hashOf(element.getHash(ptr + i * elementSize), hash); - return hash; + { + S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; + op = ""; + } + assert(op == "D4D3D2D1"); + { + S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; + op = ""; + destroy(a2); + assert(op == "D4D3D2D1", op); + } } -/// Provide the .dup array property. -@property auto dup(T)(T[] a) - if (!is(const(T) : T)) +// https://issues.dlang.org/show_bug.cgi?id=19218 +@system unittest { - import core.internal.traits : Unconst; - static assert(is(T : Unconst!T), "Cannot implicitly convert type "~T.stringof~ - " to "~Unconst!T.stringof~" in dup."); + static struct S + { + static dtorCount = 0; + ~this() { ++dtorCount; } + } - // wrap unsafe _dup in @trusted to preserve @safe postblit - static if (__traits(compiles, (T b) @safe { T a = b; })) - return _trustedDup!(T, Unconst!T)(a); - else - return _dup!(T, Unconst!T)(a); -} + static interface I + { + ref S[3] getArray(); + alias getArray this; + } -/// ditto -// const overload to support implicit conversion to immutable (unique result, see DIP29) -@property T[] dup(T)(const(T)[] a) - if (is(const(T) : T)) -{ - // wrap unsafe _dup in @trusted to preserve @safe postblit - static if (__traits(compiles, (T b) @safe { T a = b; })) - return _trustedDup!(const(T), T)(a); - else - return _dup!(const(T), T)(a); -} + static class C : I + { + static dtorCount = 0; + ~this() { ++dtorCount; } + S[3] a; + alias a this; -/// Provide the .idup array property. -@property immutable(T)[] idup(T)(T[] a) -{ - static assert(is(T : immutable(T)), "Cannot implicitly convert type "~T.stringof~ - " to immutable in idup."); + ref S[3] getArray() { return a; } + } - // wrap unsafe _dup in @trusted to preserve @safe postblit - static if (__traits(compiles, (T b) @safe { T a = b; })) - return _trustedDup!(T, immutable(T))(a); - else - return _dup!(T, immutable(T))(a); -} + C c = new C(); + destroy(c); + assert(S.dtorCount == 3); + assert(C.dtorCount == 1); -/// ditto -@property immutable(T)[] idup(T:void)(const(T)[] a) -{ - return a.dup; + I i = new C(); + destroy(i); + assert(S.dtorCount == 6); + assert(C.dtorCount == 2); } -private U[] _trustedDup(T, U)(T[] a) @trusted +/// ditto +void destroy(bool initialize = true, T)(ref T obj) + if (!is(T == struct) && !is(T == interface) && !is(T == class) && !__traits(isStaticArray, T)) { - return _dup!(T, U)(a); + static if (initialize) + obj = T.init; } -private U[] _dup(T, U)(T[] a) // pure nothrow depends on postblit +@safe unittest { - if (__ctfe) { - static if (is(T : void)) - assert(0, "Cannot dup a void[] array at compile time."); - else - { - U[] res; - foreach (ref e; a) - res ~= e; - return res; - } + int a = 42; + destroy!false(a); + assert(a == 42); + destroy(a); + assert(a == 0); + } + { + float a = 42; + destroy!false(a); + assert(a == 42); + destroy(a); + assert(a != a); // isnan } - - import core.stdc.string : memcpy; - - void[] arr = _d_newarrayU(typeid(T[]), a.length); - memcpy(arr.ptr, cast(const(void)*)a.ptr, T.sizeof * a.length); - auto res = *cast(U[]*)&arr; - - static if (!is(T : void)) - _doPostblit(res); - return res; } -private extern (C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow; - - -/************** - * Get the postblit for type T. - * Returns: - * null if no postblit is necessary - * function pointer for struct postblits - * delegate for class postblits - */ -private auto _getPostblit(T)() @trusted pure nothrow @nogc +@safe unittest { - // infer static postblit type, run postblit if any - static if (is(T == struct)) + // Bugzilla 14746 + static struct HasDtor { - import core.internal.traits : Unqual; - // use typeid(Unqual!T) here to skip TypeInfo_Const/Shared/... - alias _PostBlitType = typeof(function (ref T t){ T a = t; }); - return cast(_PostBlitType)typeid(Unqual!T).xpostblit; + ~this() { assert(0); } } - else if ((&typeid(T).postblit).funcptr !is &TypeInfo.postblit) + static struct Owner { - alias _PostBlitType = typeof(delegate (ref T t){ T a = t; }); - return cast(_PostBlitType)&typeid(T).postblit; + HasDtor* ptr; + alias ptr this; } - else - return null; -} -private void _doPostblit(T)(T[] arr) -{ - // infer static postblit type, run postblit if any - if (auto postblit = _getPostblit!T()) - { - foreach (ref elem; arr) - postblit(elem); - } + Owner o; + assert(o.ptr is null); + destroy(o); // must not reach in HasDtor.__dtor() } -unittest -{ - static struct S1 { int* p; } - static struct S2 { @disable this(); } - static struct S3 { @disable this(this); } +/* ************************************************************************ + COMPILER SUPPORT +The compiler lowers certain expressions to instantiations of the following +templates. They must be implicitly imported, which is why they are here +in this file. They must also be `public` as they must be visible from the +scope in which they are instantiated. They are explicitly undocumented as +they are only intended to be instantiated by the compiler, not the user. +**************************************************************************/ - int dg1() pure nothrow @safe - { - { - char[] m; - string i; - m = m.dup; - i = i.idup; - m = i.dup; - i = m.idup; - } - { - S1[] m; - immutable(S1)[] i; - m = m.dup; - i = i.idup; - static assert(!is(typeof(m.idup))); - static assert(!is(typeof(i.dup))); - } - { - S3[] m; - immutable(S3)[] i; - static assert(!is(typeof(m.dup))); - static assert(!is(typeof(i.idup))); - } - { - shared(S1)[] m; - m = m.dup; - static assert(!is(typeof(m.idup))); - } - { - int[] a = (inout(int)) { inout(const(int))[] a; return a.dup; }(0); - } - return 1; - } +public import core.internal.entrypoint : _d_cmain; - int dg2() pure nothrow @safe - { - { - S2[] m = [S2.init, S2.init]; - immutable(S2)[] i = [S2.init, S2.init]; - m = m.dup; - m = i.dup; - i = m.idup; - i = i.idup; - } - return 2; - } +public import core.internal.array.appending : _d_arrayappendTImpl; +public import core.internal.array.appending : _d_arrayappendcTXImpl; +public import core.internal.array.comparison : __cmp; +public import core.internal.array.equality : __equals; +public import core.internal.array.casting: __ArrayCast; +public import core.internal.array.concatenation : _d_arraycatnTXImpl; +public import core.internal.array.construction : _d_arrayctor; +public import core.internal.array.construction : _d_arraysetctor; +public import core.internal.array.capacity: _d_arraysetlengthTImpl; - enum a = dg1(); - enum b = dg2(); - assert(dg1() == a); - assert(dg2() == b); -} +public import core.internal.dassert: _d_assert_fail; -unittest -{ - static struct Sunpure { this(this) @safe nothrow {} } - static struct Sthrow { this(this) @safe pure {} } - static struct Sunsafe { this(this) @system pure nothrow {} } +public import core.internal.destruction: __ArrayDtor; - static assert( __traits(compiles, () { [].dup!Sunpure; })); - static assert(!__traits(compiles, () pure { [].dup!Sunpure; })); - static assert( __traits(compiles, () { [].dup!Sthrow; })); - static assert(!__traits(compiles, () nothrow { [].dup!Sthrow; })); - static assert( __traits(compiles, () { [].dup!Sunsafe; })); - static assert(!__traits(compiles, () @safe { [].dup!Sunsafe; })); +public import core.internal.moving: __move_post_blt; - static assert( __traits(compiles, () { [].idup!Sunpure; })); - static assert(!__traits(compiles, () pure { [].idup!Sunpure; })); - static assert( __traits(compiles, () { [].idup!Sthrow; })); - static assert(!__traits(compiles, () nothrow { [].idup!Sthrow; })); - static assert( __traits(compiles, () { [].idup!Sunsafe; })); - static assert(!__traits(compiles, () @safe { [].idup!Sunsafe; })); -} +public import core.internal.postblit: __ArrayPostblit; -unittest -{ - static int*[] pureFoo() pure { return null; } - { char[] s; immutable x = s.dup; } - { immutable x = (cast(int*[])null).dup; } - { immutable x = pureFoo(); } - { immutable x = pureFoo().dup; } -} +public import core.internal.switch_: __switch; +public import core.internal.switch_: __switch_error; -unittest -{ - auto a = [1, 2, 3]; - auto b = a.dup; - debug(SENTINEL) {} else - assert(b.capacity >= 3); -} +public @trusted @nogc nothrow pure extern (C) void _d_delThrowable(scope Throwable); -unittest +// Compare class and interface objects for ordering. +private int __cmp(Obj)(Obj lhs, Obj rhs) +if (is(Obj : Object)) { - // Bugzilla 12580 - void[] m = [0]; - shared(void)[] s = [cast(shared)1]; - immutable(void)[] i = [cast(immutable)2]; - - s = s.dup; - static assert(is(typeof(s.dup) == shared(void)[])); - - m = i.dup; - i = m.dup; - i = i.idup; - i = m.idup; - i = s.idup; - i = s.dup; - static assert(!__traits(compiles, m = s.dup)); + if (lhs is rhs) + return 0; + // Regard null references as always being "less than" + if (!lhs) + return -1; + if (!rhs) + return 1; + return lhs.opCmp(rhs); } -unittest +// objects +@safe unittest { - // Bugzilla 13809 - static struct S + class C { - this(this) {} - ~this() {} + int i; + this(int i) { this.i = i; } + + override int opCmp(Object c) const @safe + { + return i - (cast(C)c).i; + } } - S[] arr; - auto a = arr.dup; + auto c1 = new C(1); + auto c2 = new C(2); + assert(__cmp(c1, null) > 0); + assert(__cmp(null, c1) < 0); + assert(__cmp(c1, c1) == 0); + assert(__cmp(c1, c2) < 0); + assert(__cmp(c2, c1) > 0); + + assert(__cmp([c1, c1][], [c2, c2][]) < 0); + assert(__cmp([c2, c2], [c1, c1]) > 0); } -unittest +// structs +@safe unittest { - // Bugzilla 16504 - static struct S + struct C { - __gshared int* gp; - int* p; - // postblit and hence .dup could escape - this(this) { gp = p; } + ubyte i; + this(ubyte i) { this.i = i; } } - int p; - scope arr = [S(&p)]; - auto a = arr.dup; // dup does escape + auto c1 = C(1); + auto c2 = C(2); + + assert(__cmp([c1, c1][], [c2, c2][]) < 0); + assert(__cmp([c2, c2], [c1, c1]) > 0); + assert(__cmp([c2, c2], [c2, c1]) > 0); } -// compiler frontend lowers dynamic array comparison to this -bool __ArrayEq(T1, T2)(T1[] a, T2[] b) +@safe unittest { - if (a.length != b.length) - return false; - foreach (size_t i; 0 .. a.length) - { - if (a[i] != b[i]) - return false; - } - return true; + auto a = "hello"c; + + assert(a > "hel"); + assert(a >= "hel"); + assert(a < "helloo"); + assert(a <= "helloo"); + assert(a > "betty"); + assert(a >= "betty"); + assert(a == "hello"); + assert(a <= "hello"); + assert(a >= "hello"); + assert(a < "я"); } -// compiler frontend lowers struct array postblitting to this -void __ArrayPostblit(T)(T[] a) +// Used in Exception Handling LSDA tables to 'wrap' C++ type info +// so it can be distinguished from D TypeInfo +class __cpp_type_info_ptr { - foreach (ref T e; a) - e.__xpostblit(); + void* ptr; // opaque pointer to C++ RTTI type info } -// compiler frontend lowers dynamic array deconstruction to this -void __ArrayDtor(T)(T[] a) +// Compiler hook into the runtime implementation of array (vector) operations. +template _arrayOp(Args...) { - foreach_reverse (ref T e; a) - e.__xdtor(); + import core.internal.array.operations; + alias _arrayOp = arrayOp!Args; } + +public import core.builtins : __ctfeWrite; diff --git a/libphobos/libdruntime/rt/aApply.d b/libphobos/libdruntime/rt/aApply.d index f665702..bea441f 100644 --- a/libphobos/libdruntime/rt/aApply.d +++ b/libphobos/libdruntime/rt/aApply.d @@ -4,13 +4,13 @@ * of those. * * Copyright: Copyright Digital Mars 2004 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright - * Source: $(DRUNTIMESRC src/rt/_aApply.d) + * Source: $(DRUNTIMESRC rt/_aApply.d) */ module rt.aApply; -private import rt.util.utf : decode, toUTF8; +import core.internal.utf : decode, toUTF8; /**********************************************/ /* 1 argument versions */ diff --git a/libphobos/libdruntime/rt/aApplyR.d b/libphobos/libdruntime/rt/aApplyR.d index b29d370..6db6530 100644 --- a/libphobos/libdruntime/rt/aApplyR.d +++ b/libphobos/libdruntime/rt/aApplyR.d @@ -4,8 +4,9 @@ * of those. * * Copyright: Copyright Digital Mars 2004 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright, Sean Kelly + * Source: $(DRUNTIMESRC rt/_aApplyR.d) */ /* Copyright Digital Mars 2004 - 2010. @@ -20,7 +21,7 @@ module rt.aApplyR; * and dchar, and 2 of each of those. */ -private import rt.util.utf; +import core.internal.utf; /**********************************************/ /* 1 argument versions */ diff --git a/libphobos/libdruntime/rt/aaA.d b/libphobos/libdruntime/rt/aaA.d index 0ccf902..0181053 100644 --- a/libphobos/libdruntime/rt/aaA.d +++ b/libphobos/libdruntime/rt/aaA.d @@ -2,8 +2,9 @@ * Implementation of associative arrays. * * Copyright: Copyright Digital Mars 2000 - 2015. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Nowak + * Source: $(DRUNTIMESRC rt/_aaA.d) */ module rt.aaA; @@ -11,6 +12,7 @@ module rt.aaA; extern (C) immutable int _aaVersion = 1; import core.memory : GC; +import core.internal.util.math : min, max; // grow threshold private enum GROW_NUM = 4; @@ -48,13 +50,12 @@ struct AA private struct Impl { private: - this(in TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) + this(scope const TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) { keysz = cast(uint) ti.key.tsize; valsz = cast(uint) ti.value.tsize; buckets = allocBuckets(sz); firstUsed = cast(uint) buckets.length; - entryTI = fakeEntryTI(ti.key, ti.value); valoff = cast(uint) talign(keysz, ti.value.talign); import rt.lifetime : hasPostblit, unqualify; @@ -63,6 +64,8 @@ private: flags |= Flags.keyHasPostblit; if ((ti.key.flags | ti.value.flags) & 1) flags |= Flags.hasPointers; + + entryTI = fakeEntryTI(this, ti.key, ti.value); } Bucket[] buckets; @@ -110,7 +113,7 @@ private: } // lookup a key - inout(Bucket)* findSlotLookup(size_t hash, in void* pkey, in TypeInfo keyti) inout + inout(Bucket)* findSlotLookup(size_t hash, scope const void* pkey, scope const TypeInfo keyti) inout { for (size_t i = hash & mask, j = 1;; ++j) { @@ -122,7 +125,7 @@ private: } } - void grow(in TypeInfo keyti) + void grow(scope const TypeInfo keyti) { // If there are so many deleted entries, that growing would push us // below the shrink threshold, we just purge deleted entries instead. @@ -132,7 +135,7 @@ private: resize(GROW_FAC * dim); } - void shrink(in TypeInfo keyti) + void shrink(scope const TypeInfo keyti) { if (dim > INIT_NUM_BUCKETS) resize(dim / GROW_FAC); @@ -200,7 +203,7 @@ Bucket[] allocBuckets(size_t dim) @trusted pure nothrow // Entry //------------------------------------------------------------------------------ -private void* allocEntry(in Impl* aa, in void* pkey) +private void* allocEntry(scope const Impl* aa, scope const void* pkey) { import rt.lifetime : _d_newitemU; import core.stdc.string : memcpy, memset; @@ -243,19 +246,45 @@ private bool hasDtor(const TypeInfo ti) return false; } +private immutable(void)* getRTInfo(const TypeInfo ti) +{ + // classes are references + const isNoClass = ti && typeid(ti) !is typeid(TypeInfo_Class); + return isNoClass ? ti.rtInfo() : rtinfoHasPointers; +} + // build type info for Entry with additional key and value fields -TypeInfo_Struct fakeEntryTI(const TypeInfo keyti, const TypeInfo valti) +TypeInfo_Struct fakeEntryTI(ref Impl aa, const TypeInfo keyti, const TypeInfo valti) { import rt.lifetime : unqualify; auto kti = unqualify(keyti); auto vti = unqualify(valti); - if (!hasDtor(kti) && !hasDtor(vti)) + + // figure out whether RTInfo has to be generated (indicated by rtisize > 0) + enum pointersPerWord = 8 * (void*).sizeof * (void*).sizeof; + auto rtinfo = rtinfoNoPointers; + size_t rtisize = 0; + immutable(size_t)* keyinfo = void; + immutable(size_t)* valinfo = void; + if (aa.flags & Impl.Flags.hasPointers) + { + // classes are references + keyinfo = cast(immutable(size_t)*) getRTInfo(keyti); + valinfo = cast(immutable(size_t)*) getRTInfo(valti); + + if (keyinfo is rtinfoHasPointers && valinfo is rtinfoHasPointers) + rtinfo = rtinfoHasPointers; + else + rtisize = 1 + (aa.valoff + aa.valsz + pointersPerWord - 1) / pointersPerWord; + } + bool entryHasDtor = hasDtor(kti) || hasDtor(vti); + if (rtisize == 0 && !entryHasDtor) return null; // save kti and vti after type info for struct enum sizeti = __traits(classInstanceSize, TypeInfo_Struct); - void* p = GC.malloc(sizeti + 2 * (void*).sizeof); + void* p = GC.malloc(sizeti + (2 + rtisize) * (void*).sizeof); import core.stdc.string : memcpy; memcpy(p, typeid(TypeInfo_Struct).initializer().ptr, sizeti); @@ -265,26 +294,146 @@ TypeInfo_Struct fakeEntryTI(const TypeInfo keyti, const TypeInfo valti) extra[0] = cast() kti; extra[1] = cast() vti; - static immutable tiName = __MODULE__ ~ ".Entry!(...)"; - ti.name = tiName; + static immutable tiMangledName = "S2rt3aaA__T5EntryZ"; + ti.mangledName = tiMangledName; + + ti.m_RTInfo = rtisize > 0 ? rtinfoEntry(aa, keyinfo, valinfo, cast(size_t*)(extra + 2), rtisize) : rtinfo; + ti.m_flags = ti.m_RTInfo is rtinfoNoPointers ? cast(TypeInfo_Struct.StructFlags)0 : TypeInfo_Struct.StructFlags.hasPointers; // we don't expect the Entry objects to be used outside of this module, so we have control // over the non-usage of the callback methods and other entries and can keep these null // xtoHash, xopEquals, xopCmp, xtoString and xpostblit - ti.m_RTInfo = rtinfoNoPointers; - immutable entrySize = talign(kti.tsize, vti.talign) + vti.tsize; + immutable entrySize = aa.valoff + aa.valsz; ti.m_init = (cast(ubyte*) null)[0 .. entrySize]; // init length, but not ptr - // xdtor needs to be built from the dtors of key and value for the GC - ti.xdtorti = &entryDtor; + if (entryHasDtor) + { + // xdtor needs to be built from the dtors of key and value for the GC + ti.xdtorti = &entryDtor; + ti.m_flags |= TypeInfo_Struct.StructFlags.isDynamicType; + } - ti.m_flags = TypeInfo_Struct.StructFlags.isDynamicType; - ti.m_flags |= (keyti.flags | valti.flags) & TypeInfo_Struct.StructFlags.hasPointers; ti.m_align = cast(uint) max(kti.talign, vti.talign); return ti; } +// build appropriate RTInfo at runtime +immutable(void)* rtinfoEntry(ref Impl aa, immutable(size_t)* keyinfo, immutable(size_t)* valinfo, size_t* rtinfoData, size_t rtinfoSize) +{ + enum bitsPerWord = 8 * size_t.sizeof; + + rtinfoData[0] = aa.valoff + aa.valsz; + rtinfoData[1..rtinfoSize] = 0; + + void copyKeyInfo(string src)() + { + size_t pos = 1; + size_t keybits = aa.keysz / (void*).sizeof; + while (keybits >= bitsPerWord) + { + rtinfoData[pos] = mixin(src); + keybits -= bitsPerWord; + pos++; + } + if (keybits > 0) + rtinfoData[pos] = mixin(src) & ((cast(size_t) 1 << keybits) - 1); + } + + if (keyinfo is rtinfoHasPointers) + copyKeyInfo!"~cast(size_t) 0"(); + else if (keyinfo !is rtinfoNoPointers) + copyKeyInfo!"keyinfo[pos]"(); + + void copyValInfo(string src)() + { + size_t bitpos = aa.valoff / (void*).sizeof; + size_t pos = 1; + size_t dstpos = 1 + bitpos / bitsPerWord; + size_t begoff = bitpos % bitsPerWord; + size_t valbits = aa.valsz / (void*).sizeof; + size_t endoff = (bitpos + valbits) % bitsPerWord; + for (;;) + { + const bits = bitsPerWord - begoff; + size_t s = mixin(src); + rtinfoData[dstpos] |= s << begoff; + if (begoff > 0 && valbits > bits) + rtinfoData[dstpos+1] |= s >> bits; + if (valbits < bitsPerWord) + break; + valbits -= bitsPerWord; + dstpos++; + pos++; + } + if (endoff > 0) + rtinfoData[dstpos] &= ((cast(size_t) 1 << endoff) - 1); + } + + if (valinfo is rtinfoHasPointers) + copyValInfo!"~cast(size_t) 0"(); + else if (valinfo !is rtinfoNoPointers) + copyValInfo!"valinfo[pos]"(); + + return cast(immutable(void)*) rtinfoData; +} + +unittest +{ + void test(K, V)() + { + static struct Entry + { + K key; + V val; + } + auto keyti = typeid(K); + auto valti = typeid(V); + auto valrti = getRTInfo(valti); + auto keyrti = getRTInfo(keyti); + + auto impl = new Impl(typeid(V[K])); + if (valrti is rtinfoNoPointers && keyrti is rtinfoNoPointers) + { + assert(!(impl.flags & Impl.Flags.hasPointers)); + assert(impl.entryTI is null); + } + else if (valrti is rtinfoHasPointers && keyrti is rtinfoHasPointers) + { + assert(impl.flags & Impl.Flags.hasPointers); + assert(impl.entryTI is null); + } + else + { + auto rtInfo = cast(size_t*) impl.entryTI.rtInfo(); + auto refInfo = cast(size_t*) typeid(Entry).rtInfo(); + assert(rtInfo[0] == refInfo[0]); // size + enum bytesPerWord = 8 * size_t.sizeof * (void*).sizeof; + size_t words = (rtInfo[0] + bytesPerWord - 1) / bytesPerWord; + foreach (i; 0 .. words) + assert(rtInfo[1 + i] == refInfo[i + 1]); + } + } + test!(long, int)(); + test!(string, string); + test!(ubyte[16], Object); + + static struct Small + { + ubyte[16] guid; + string name; + } + test!(string, Small); + + static struct Large + { + ubyte[1024] data; + string[412] names; + ubyte[1024] moredata; + } + test!(Large, Large); +} + //============================================================================== // Helper functions //------------------------------------------------------------------------------ @@ -307,14 +456,14 @@ private size_t mix(size_t h) @safe pure nothrow @nogc return h; } -private size_t calcHash(in void* pkey, in TypeInfo keyti) +private size_t calcHash(scope const void* pkey, scope const TypeInfo keyti) { immutable hash = keyti.getHash(pkey); // highest bit is set to distinguish empty/deleted from filled buckets return mix(hash) | HASH_FILLED_MARK; } -private size_t nextpow2(in size_t n) pure nothrow @nogc +private size_t nextpow2(const size_t n) pure nothrow @nogc { import core.bitop : bsr; @@ -332,22 +481,12 @@ pure nothrow @nogc unittest assert(nextpow2(n) == pow2); } -private T min(T)(T a, T b) pure nothrow @nogc -{ - return a < b ? a : b; -} - -private T max(T)(T a, T b) pure nothrow @nogc -{ - return b < a ? a : b; -} - //============================================================================== // API Implementation //------------------------------------------------------------------------------ /// Determine number of entries in associative array. -extern (C) size_t _aaLen(in AA aa) pure nothrow @nogc +extern (C) size_t _aaLen(scope const AA aa) pure nothrow @nogc { return aa ? aa.length : 0; } @@ -356,7 +495,7 @@ extern (C) size_t _aaLen(in AA aa) pure nothrow @nogc * Lookup *pkey in aa. * Called only from implementation of (aa[key]) expressions when value is mutable. * Params: - * aa = associative array opaque pointer + * paa = associative array opaque pointer * ti = TypeInfo for the associative array * valsz = ignored * pkey = pointer to the key value @@ -365,18 +504,18 @@ extern (C) size_t _aaLen(in AA aa) pure nothrow @nogc * If key was not in the aa, a mutable pointer to newly inserted value which * is set to all zeros */ -extern (C) void* _aaGetY(AA* aa, const TypeInfo_AssociativeArray ti, - in size_t valsz, in void* pkey) +extern (C) void* _aaGetY(AA* paa, const TypeInfo_AssociativeArray ti, + const size_t valsz, scope const void* pkey) { bool found; - return _aaGetX(aa, ti, valsz, pkey, found); + return _aaGetX(paa, ti, valsz, pkey, found); } /****************************** * Lookup *pkey in aa. * Called only from implementation of require * Params: - * aa = associative array opaque pointer + * paa = associative array opaque pointer * ti = TypeInfo for the associative array * valsz = ignored * pkey = pointer to the key value @@ -386,12 +525,16 @@ extern (C) void* _aaGetY(AA* aa, const TypeInfo_AssociativeArray ti, * If key was not in the aa, a mutable pointer to newly inserted value which * is set to all zeros */ -extern (C) void* _aaGetX(AA* aa, const TypeInfo_AssociativeArray ti, - in size_t valsz, in void* pkey, out bool found) +extern (C) void* _aaGetX(AA* paa, const TypeInfo_AssociativeArray ti, + const size_t valsz, scope const void* pkey, out bool found) { // lazily alloc implementation - if (aa.impl is null) - aa.impl = new Impl(ti); + AA aa = *paa; + if (aa is null) + { + aa = new Impl(ti); + *paa = aa; + } // get hash and bucket for key immutable hash = calcHash(pkey, ti.key); @@ -417,7 +560,7 @@ extern (C) void* _aaGetX(AA* aa, const TypeInfo_AssociativeArray ti, // update search cache and allocate entry aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); p.hash = hash; - p.entry = allocEntry(aa.impl, pkey); + p.entry = allocEntry(aa, pkey); // postblit for key if (aa.flags & Impl.Flags.keyHasPostblit) { @@ -440,8 +583,8 @@ extern (C) void* _aaGetX(AA* aa, const TypeInfo_AssociativeArray ti, * Returns: * pointer to value if present, null otherwise */ -extern (C) inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t valsz, - in void* pkey) +extern (C) inout(void)* _aaGetRvalueX(inout AA aa, scope const TypeInfo keyti, const size_t valsz, + scope const void* pkey) { return _aaInX(aa, keyti, pkey); } @@ -456,7 +599,7 @@ extern (C) inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t * Returns: * pointer to value if present, null otherwise */ -extern (C) inout(void)* _aaInX(inout AA aa, in TypeInfo keyti, in void* pkey) +extern (C) inout(void)* _aaInX(inout AA aa, scope const TypeInfo keyti, scope const void* pkey) { if (aa.empty) return null; @@ -467,8 +610,8 @@ extern (C) inout(void)* _aaInX(inout AA aa, in TypeInfo keyti, in void* pkey) return null; } -/// Delete entry in AA, return true if it was present -extern (C) bool _aaDelX(AA aa, in TypeInfo keyti, in void* pkey) +/// Delete entry scope const AA, return true if it was present +extern (C) bool _aaDelX(AA aa, scope const TypeInfo keyti, scope const void* pkey) { if (aa.empty) return false; @@ -481,7 +624,9 @@ extern (C) bool _aaDelX(AA aa, in TypeInfo keyti, in void* pkey) p.entry = null; ++aa.deleted; - if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM) + // `shrink` reallocates, and allocating from a finalizer leads to + // InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442 + if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM && !GC.inFinalizer()) aa.shrink(keyti); return true; @@ -494,20 +639,21 @@ extern (C) void _aaClear(AA aa) pure nothrow { if (!aa.empty) { - aa.impl.clear(); + aa.clear(); } } /// Rehash AA -extern (C) void* _aaRehash(AA* paa, in TypeInfo keyti) pure nothrow +extern (C) void* _aaRehash(AA* paa, scope const TypeInfo keyti) pure nothrow { - if (!paa.empty) - paa.resize(nextpow2(INIT_DEN * paa.length / INIT_NUM)); - return *paa; + AA aa = *paa; + if (!aa.empty) + aa.resize(nextpow2(INIT_DEN * aa.length / INIT_NUM)); + return aa; } /// Return a GC allocated array of all values -extern (C) inout(void[]) _aaValues(inout AA aa, in size_t keysz, in size_t valsz, +extern (C) inout(void[]) _aaValues(inout AA aa, const size_t keysz, const size_t valsz, const TypeInfo tiValueArray) pure nothrow { if (aa.empty) @@ -531,7 +677,7 @@ extern (C) inout(void[]) _aaValues(inout AA aa, in size_t keysz, in size_t valsz } /// Return a GC allocated array of all keys -extern (C) inout(void[]) _aaKeys(inout AA aa, in size_t keysz, const TypeInfo tiKeyArray) pure nothrow +extern (C) inout(void[]) _aaKeys(inout AA aa, const size_t keysz, const TypeInfo tiKeyArray) pure nothrow { if (aa.empty) return null; @@ -557,7 +703,7 @@ extern (D) alias dg_t = int delegate(void*); extern (D) alias dg2_t = int delegate(void*, void*); /// foreach opApply over all values -extern (C) int _aaApply(AA aa, in size_t keysz, dg_t dg) +extern (C) int _aaApply(AA aa, const size_t keysz, dg_t dg) { if (aa.empty) return 0; @@ -574,7 +720,7 @@ extern (C) int _aaApply(AA aa, in size_t keysz, dg_t dg) } /// foreach opApply over all key/value pairs -extern (C) int _aaApply2(AA aa, in size_t keysz, dg2_t dg) +extern (C) int _aaApply2(AA aa, const size_t keysz, dg2_t dg) { if (aa.empty) return 0; @@ -639,9 +785,9 @@ extern (C) Impl* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void } /// compares 2 AAs for equality -extern (C) int _aaEqual(in TypeInfo tiRaw, in AA aa1, in AA aa2) +extern (C) int _aaEqual(scope const TypeInfo tiRaw, scope const AA aa1, scope const AA aa2) { - if (aa1.impl is aa2.impl) + if (aa1 is aa2) return true; immutable len = _aaLen(aa1); @@ -669,8 +815,10 @@ extern (C) int _aaEqual(in TypeInfo tiRaw, in AA aa1, in AA aa2) } /// compute a hash -extern (C) hash_t _aaGetHash(in AA* aa, in TypeInfo tiRaw) nothrow +extern (C) hash_t _aaGetHash(scope const AA* paa, scope const TypeInfo tiRaw) nothrow { + const AA aa = *paa; + if (aa.empty) return 0; @@ -707,7 +855,7 @@ struct Range extern (C) pure nothrow @nogc @safe { - Range _aaRange(AA aa) + Range _aaRange(return AA aa) { if (!aa) return Range(); @@ -715,7 +863,7 @@ extern (C) pure nothrow @nogc @safe foreach (i; aa.firstUsed .. aa.dim) { if (aa.buckets[i].filled) - return Range(aa.impl, i); + return Range(aa, i); } return Range(aa, aa.dim); } @@ -756,7 +904,7 @@ extern (C) pure nothrow @nogc @safe } } -// Most tests are now in in test_aa.d +// Most tests are now in test_aa.d // test postblit for AA literals unittest diff --git a/libphobos/libdruntime/rt/adi.d b/libphobos/libdruntime/rt/adi.d index 44f0e15..ea5a78f 100644 --- a/libphobos/libdruntime/rt/adi.d +++ b/libphobos/libdruntime/rt/adi.d @@ -6,7 +6,7 @@ * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright - * Source: $(DRUNTIMESRC src/rt/_adi.d) + * Source: $(DRUNTIMESRC rt/_adi.d) */ module rt.adi; @@ -16,66 +16,6 @@ module rt.adi; private { debug(adi) import core.stdc.stdio; - import core.stdc.string; - import core.stdc.stdlib; - import core.memory; - import rt.util.utf; - - extern (C) void[] _adSort(void[] a, TypeInfo ti); -} - -private dchar[] mallocUTF32(C)(in C[] s) -{ - size_t j = 0; - auto p = cast(dchar*)malloc(dchar.sizeof * s.length); - auto r = p[0..s.length]; // r[] will never be longer than s[] - foreach (dchar c; s) - r[j++] = c; - return r[0 .. j]; -} - -/********************************************** - * Sort array of chars. - */ - -extern (C) char[] _adSortChar(char[] a) -{ - if (a.length > 1) - { - auto da = mallocUTF32(a); - _adSort(*cast(void[]*)&da, typeid(da[0])); - size_t i = 0; - foreach (dchar d; da) - { char[4] buf; - auto t = toUTF8(buf, d); - a[i .. i + t.length] = t[]; - i += t.length; - } - free(da.ptr); - } - return a; -} - -/********************************************** - * Sort array of wchars. - */ - -extern (C) wchar[] _adSortWchar(wchar[] a) -{ - if (a.length > 1) - { - auto da = mallocUTF32(a); - _adSort(*cast(void[]*)&da, typeid(da[0])); - size_t i = 0; - foreach (dchar d; da) - { wchar[2] buf; - auto t = toUTF16(buf, d); - a[i .. i + t.length] = t[]; - i += t.length; - } - free(da.ptr); - } - return a; } /*************************************** @@ -85,27 +25,6 @@ extern (C) wchar[] _adSortWchar(wchar[] a) * 0 not equal */ -extern (C) int _adEq(void[] a1, void[] a2, TypeInfo ti) -{ - debug(adi) printf("_adEq(a1.length = %d, a2.length = %d)\n", a1.length, a2.length); - if (a1.length != a2.length) - return 0; // not equal - auto sz = ti.tsize; - auto p1 = a1.ptr; - auto p2 = a2.ptr; - - if (sz == 1) - // We should really have a ti.isPOD() check for this - return (memcmp(p1, p2, a1.length) == 0); - - for (size_t i = 0; i < a1.length; i++) - { - if (!ti.equals(p1 + i * sz, p2 + i * sz)) - return 0; // not equal - } - return 1; // equal -} - extern (C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) { debug(adi) printf("_adEq2(a1.length = %d, a2.length = %d)\n", a1.length, a2.length); @@ -115,218 +34,43 @@ extern (C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) return 0; return 1; } -unittest + +@safe unittest { debug(adi) printf("array.Eq unittest\n"); - auto a = "hello"c; + struct S(T) { T val; } + alias String = S!string; + alias Float = S!float; + + String[1] a = [String("hello"c)]; - assert(a != "hel"); - assert(a != "helloo"); - assert(a != "betty"); - assert(a == "hello"); - assert(a != "hxxxx"); + assert(a != [String("hel")]); + assert(a != [String("helloo")]); + assert(a != [String("betty")]); + assert(a == [String("hello")]); + assert(a != [String("hxxxx")]); - float[] fa = [float.nan]; + Float[1] fa = [Float(float.nan)]; assert(fa != fa); } -/*************************************** - * Support for array compare test. - */ - -extern (C) int _adCmp(void[] a1, void[] a2, TypeInfo ti) +unittest { - debug(adi) printf("adCmp()\n"); - auto len = a1.length; - if (a2.length < len) - len = a2.length; - auto sz = ti.tsize; - void *p1 = a1.ptr; - void *p2 = a2.ptr; + debug(adi) printf("struct.Eq unittest\n"); - if (sz == 1) - { // We should really have a ti.isPOD() check for this - auto c = memcmp(p1, p2, len); - if (c) - return c; - } - else + static struct TestStruct { - for (size_t i = 0; i < len; i++) + int value; + + bool opEquals(const TestStruct rhs) const { - auto c = ti.compare(p1 + i * sz, p2 + i * sz); - if (c) - return c; + return value == rhs.value; } } - if (a1.length == a2.length) - return 0; - return (a1.length > a2.length) ? 1 : -1; -} - -extern (C) int _adCmp2(void[] a1, void[] a2, TypeInfo ti) -{ - debug(adi) printf("_adCmp2(a1.length = %d, a2.length = %d)\n", a1.length, a2.length); - return ti.compare(&a1, &a2); -} -unittest -{ - debug(adi) printf("array.Cmp unittest\n"); - - auto a = "hello"c; - - assert(a > "hel"); - assert(a >= "hel"); - assert(a < "helloo"); - assert(a <= "helloo"); - assert(a > "betty"); - assert(a >= "betty"); - assert(a == "hello"); - assert(a <= "hello"); - assert(a >= "hello"); - assert(a < "я"); -} - -/*************************************** - * Support for array compare test. - */ - -extern (C) int _adCmpChar(void[] a1, void[] a2) -{ - version (D_InlineAsm_X86) - { - asm - { naked ; - - push EDI ; - push ESI ; - - mov ESI,a1+4[4+ESP] ; - mov EDI,a2+4[4+ESP] ; - - mov ECX,a1[4+ESP] ; - mov EDX,a2[4+ESP] ; - - cmp ECX,EDX ; - jb GotLength ; - - mov ECX,EDX ; - -GotLength: - cmp ECX,4 ; - jb DoBytes ; - - // Do alignment if neither is dword aligned - test ESI,3 ; - jz Aligned ; - - test EDI,3 ; - jz Aligned ; -DoAlign: - mov AL,[ESI] ; //align ESI to dword bounds - mov DL,[EDI] ; - - cmp AL,DL ; - jnz Unequal ; - - inc ESI ; - inc EDI ; - - test ESI,3 ; - - lea ECX,[ECX-1] ; - jnz DoAlign ; -Aligned: - mov EAX,ECX ; - - // do multiple of 4 bytes at a time - - shr ECX,2 ; - jz TryOdd ; - - repe ; - cmpsd ; - - jnz UnequalQuad ; - -TryOdd: - mov ECX,EAX ; -DoBytes: - // if still equal and not end of string, do up to 3 bytes slightly - // slower. - - and ECX,3 ; - jz Equal ; - - repe ; - cmpsb ; - - jnz Unequal ; -Equal: - mov EAX,a1[4+ESP] ; - mov EDX,a2[4+ESP] ; - - sub EAX,EDX ; - pop ESI ; - - pop EDI ; - ret ; - -UnequalQuad: - mov EDX,[EDI-4] ; - mov EAX,[ESI-4] ; - - cmp AL,DL ; - jnz Unequal ; - - cmp AH,DH ; - jnz Unequal ; - - shr EAX,16 ; - - shr EDX,16 ; - - cmp AL,DL ; - jnz Unequal ; - - cmp AH,DH ; -Unequal: - sbb EAX,EAX ; - pop ESI ; - - or EAX,1 ; - pop EDI ; - - ret ; - } - } - else - { - debug(adi) printf("adCmpChar()\n"); - auto len = a1.length; - if (a2.length < len) - len = a2.length; - auto c = memcmp(cast(char *)a1.ptr, cast(char *)a2.ptr, len); - if (!c) - c = cast(int)a1.length - cast(int)a2.length; - return c; - } -} - -unittest -{ - debug(adi) printf("array.CmpChar unittest\n"); - - auto a = "hello"c; - assert(a > "hel"); - assert(a >= "hel"); - assert(a < "helloo"); - assert(a <= "helloo"); - assert(a > "betty"); - assert(a >= "betty"); - assert(a == "hello"); - assert(a <= "hello"); - assert(a >= "hello"); + TestStruct[] b = [TestStruct(5)]; + TestStruct[] c = [TestStruct(6)]; + assert(_adEq2(*cast(void[]*)&b, *cast(void[]*)&c, typeid(TestStruct[])) == false); + assert(_adEq2(*cast(void[]*)&b, *cast(void[]*)&b, typeid(TestStruct[])) == true); } diff --git a/libphobos/libdruntime/rt/arrayassign.d b/libphobos/libdruntime/rt/arrayassign.d index 389ff92..21d50b0 100644 --- a/libphobos/libdruntime/rt/arrayassign.d +++ b/libphobos/libdruntime/rt/arrayassign.d @@ -6,14 +6,14 @@ * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * Authors: Walter Bright, Kenji Hara - * Source: $(DRUNTIMESRC src/rt/_arrayassign.d) + * Source: $(DRUNTIMESRC rt/_arrayassign.d) */ module rt.arrayassign; private { - import rt.util.array; + import core.internal.util.array; import core.stdc.string; import core.stdc.stdlib; debug(PRINTF) import core.stdc.stdio; diff --git a/libphobos/libdruntime/rt/arraycast.d b/libphobos/libdruntime/rt/arraycast.d deleted file mode 100644 index d16d30d..0000000 --- a/libphobos/libdruntime/rt/arraycast.d +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Implementation of array cast support routines. - * - * Copyright: Copyright Digital Mars 2004 - 2016. - * License: Distributed under the - * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). - * Authors: Walter Bright, Sean Kelly - * Source: $(DRUNTIMESRC src/rt/_arraycast.d) - */ - -module rt.arraycast; - -/****************************************** - * Runtime helper to convert dynamic array of one - * type to dynamic array of another. - * Adjusts the length of the array. - * Throws an error if new length is not aligned. - */ - -extern (C) - -@trusted nothrow -void[] _d_arraycast(size_t tsize, size_t fsize, void[] a) -{ - auto length = a.length; - - auto nbytes = length * fsize; - if (nbytes % tsize != 0) - { - throw new Error("array cast misalignment"); - } - length = nbytes / tsize; - *cast(size_t *)&a = length; // jam new length - return a; -} - -unittest -{ - byte[int.sizeof * 3] b; - int[] i; - short[] s; - - i = cast(int[])b; - assert(i.length == 3); - - s = cast(short[])b; - assert(s.length == 6); - - s = cast(short[])i; - assert(s.length == 6); -} - diff --git a/libphobos/libdruntime/rt/arraycat.d b/libphobos/libdruntime/rt/arraycat.d index f3f05c3..d879480 100644 --- a/libphobos/libdruntime/rt/arraycat.d +++ b/libphobos/libdruntime/rt/arraycat.d @@ -5,7 +5,7 @@ * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * Authors: Walter Bright, Sean Kelly - * Source: $(DRUNTIMESRC src/rt/_arraycat.d) + * Source: $(DRUNTIMESRC rt/_arraycat.d) */ module rt.arraycat; @@ -13,7 +13,7 @@ module rt.arraycat; private { import core.stdc.string; - import rt.util.array; + import core.internal.util.array; debug(PRINTF) import core.stdc.stdio; } diff --git a/libphobos/libdruntime/rt/cast_.d b/libphobos/libdruntime/rt/cast_.d index f34d82e..dcb4438 100644 --- a/libphobos/libdruntime/rt/cast_.d +++ b/libphobos/libdruntime/rt/cast_.d @@ -2,8 +2,9 @@ * Implementation of array assignment support routines. * * Copyright: Copyright Digital Mars 2004 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright, Sean Kelly + * Source: $(DRUNTIMESRC rt/_cast_.d) */ /* Copyright Digital Mars 2004 - 2010. @@ -14,6 +15,19 @@ module rt.cast_; extern (C): +@nogc: +nothrow: +pure: + +// Needed because ClassInfo.opEquals(Object) does a dynamic cast, +// but we are trying to implement dynamic cast. +extern (D) private bool areClassInfosEqual(scope const ClassInfo a, scope const ClassInfo b) @safe +{ + if (a is b) + return true; + // take care of potential duplicates across binaries + return a.name == b.name; +} /****************************************** * Given a pointer: @@ -22,7 +36,7 @@ extern (C): * If it is null, return null. * Else, undefined crash */ -Object _d_toObject(void* p) +Object _d_toObject(return void* p) { if (!p) return null; @@ -74,21 +88,21 @@ void* _d_dynamic_cast(Object o, ClassInfo c) return res; } -int _d_isbaseof2(ClassInfo oc, ClassInfo c, ref size_t offset) +int _d_isbaseof2(scope ClassInfo oc, scope const ClassInfo c, scope ref size_t offset) @safe { - if (oc is c) + if (areClassInfosEqual(oc, c)) return true; do { - if (oc.base is c) + if (oc.base && areClassInfosEqual(oc.base, c)) return true; // Bugzilla 2013: Use depth-first search to calculate offset // from the derived (oc) to the base (c). foreach (iface; oc.interfaces) { - if (iface.classinfo is c || _d_isbaseof2(iface.classinfo, c, offset)) + if (areClassInfosEqual(iface.classinfo, c) || _d_isbaseof2(iface.classinfo, c, offset)) { offset += iface.offset; return true; @@ -101,19 +115,19 @@ int _d_isbaseof2(ClassInfo oc, ClassInfo c, ref size_t offset) return false; } -int _d_isbaseof(ClassInfo oc, ClassInfo c) +int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) @safe { - if (oc is c) + if (areClassInfosEqual(oc, c)) return true; do { - if (oc.base is c) + if (oc.base && areClassInfosEqual(oc.base, c)) return true; foreach (iface; oc.interfaces) { - if (iface.classinfo is c || _d_isbaseof(iface.classinfo, c)) + if (areClassInfosEqual(iface.classinfo, c) || _d_isbaseof(iface.classinfo, c)) return true; } @@ -122,20 +136,3 @@ int _d_isbaseof(ClassInfo oc, ClassInfo c) return false; } - -/********************************* - * Find the vtbl[] associated with Interface ic. - */ -void* _d_interface_vtbl(ClassInfo ic, Object o) -{ - debug(cast_) printf("__d_interface_vtbl(o = %p, ic = %p)\n", o, ic); - - assert(o); - - foreach (iface; typeid(o).interfaces) - { - if (iface.classinfo is ic) - return cast(void*) iface.vtbl; - } - assert(0); -} diff --git a/libphobos/libdruntime/rt/config.d b/libphobos/libdruntime/rt/config.d index 904f721..f7682f3 100644 --- a/libphobos/libdruntime/rt/config.d +++ b/libphobos/libdruntime/rt/config.d @@ -1,68 +1,73 @@ /** -* Configuration options for druntime -* -* Copyright: Copyright Digital Mars 2014. -* License: Distributed under the -* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). -* (See accompanying file LICENSE) -* Authors: Rainer Schuetze -* Source: $(DRUNTIMESRC src/rt/_config.d) +Configuration options for druntime. + +The default way to configure the runtime is by passing command line arguments +starting with `--DRT-` and followed by the option name, e.g. `--DRT-gcopt` to +configure the GC. +When command line parsing is enabled, command line options starting +with `--DRT-` are filtered out before calling main, so the program +will not see them. They are still available via `rt_args()`. + +Configuration via the command line can be disabled by declaring a variable for the +linker to pick up before using it's default from the runtime: + +--- +extern(C) __gshared bool rt_cmdline_enabled = false; +--- + +Likewise, declare a boolean rt_envvars_enabled to enable configuration via the +environment variable `DRT_` followed by the option name, e.g. `DRT_GCOPT`: + +--- +extern(C) __gshared bool rt_envvars_enabled = true; +--- + +Setting default configuration properties in the executable can be done by specifying an +array of options named `rt_options`: + +--- +extern(C) __gshared string[] rt_options = [ "gcopt=precise:1 profile:1"]; +--- + +Evaluation order of options is `rt_options`, then environment variables, then command +line arguments, i.e. if command line arguments are not disabled, they can override +options specified through the environment or embedded in the executable. + +Copyright: Copyright Digital Mars 2014. +License: Distributed under the + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + (See accompanying file LICENSE) +Authors: Rainer Schuetze +Source: $(DRUNTIMESRC rt/_config.d) */ module rt.config; -// The default way to configure the runtime is by passing command line arguments -// starting with "--DRT-" and followed by the option name, e.g. "--DRT-gcopt" to -// configure the GC. -// Command line options starting with "--DRT-" are filtered out before calling main, -// so the program will not see them. They are still available via rt_args(). -// -// Configuration via the command line can be disabled by declaring a variable for the -// linker to pick up before using it's default from the runtime: -// -// extern(C) __gshared bool rt_cmdline_enabled = false; -// -// Likewise, declare a boolean rt_envvars_enabled to enable configuration via the -// environment variable "DRT_" followed by the option name, e.g. "DRT_GCOPT": -// -// extern(C) __gshared bool rt_envvars_enabled = true; -// -// Setting default configuration properties in the executable can be done by specifying an -// array of options named rt_options: -// -// extern(C) __gshared string[] rt_options = [ "gcopt=precise:1 profile:1"]; -// -// Evaluation order of options is rt_options, then environment variables, then command -// line arguments, i.e. if command line arguments are not disabled, they can override -// options specified through the environment or embedded in the executable. - -import core.demangle : cPrefix; - // put each variable in its own COMDAT by making them template instances template rt_envvars_enabled() { - pragma(mangle, cPrefix ~ "rt_envvars_enabled") __gshared bool rt_envvars_enabled = false; + extern(C) pragma(mangle, "rt_envvars_enabled") __gshared bool rt_envvars_enabled = false; } template rt_cmdline_enabled() { - pragma(mangle, cPrefix ~ "rt_cmdline_enabled") __gshared bool rt_cmdline_enabled = true; + extern(C) pragma(mangle, "rt_cmdline_enabled") __gshared bool rt_cmdline_enabled = true; } template rt_options() { - pragma(mangle, cPrefix ~ "rt_options") __gshared string[] rt_options = []; + extern(C) pragma(mangle, "rt_options") __gshared string[] rt_options = []; } import core.stdc.ctype : toupper; import core.stdc.stdlib : getenv; import core.stdc.string : strlen; -extern extern(C) string[] rt_args() @nogc nothrow; +extern extern(C) string[] rt_args() @nogc nothrow @system; alias rt_configCallBack = string delegate(string) @nogc nothrow; /** * get a druntime config option using standard configuration options -* opt name of the option to retreive +* opt name of the option to retrieve * dg if non-null, passes the option through this * delegate and only returns its return value if non-null * reverse reverse the default processing order cmdline/envvar/rt_options diff --git a/libphobos/libdruntime/rt/critical_.d b/libphobos/libdruntime/rt/critical_.d index 9404261..ae18122 100644 --- a/libphobos/libdruntime/rt/critical_.d +++ b/libphobos/libdruntime/rt/critical_.d @@ -2,8 +2,9 @@ * Implementation of support routines for synchronized blocks. * * Copyright: Copyright Digital Mars 2000 - 2011. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright, Sean Kelly + * Source: $(DRUNTIMESRC rt/_critical_.d) */ /* Copyright Digital Mars 2000 - 2011. diff --git a/libphobos/libdruntime/rt/deh.d b/libphobos/libdruntime/rt/deh.d index 440e242..695f2ce 100644 --- a/libphobos/libdruntime/rt/deh.d +++ b/libphobos/libdruntime/rt/deh.d @@ -1,12 +1,37 @@ /** - * Implementation of exception handling support routines. + * Entry point for exception handling support routines. * - * Copyright: Copyright Digital Mars 1999 - 2013. + * There are three style of exception handling being supported by DMD: + * DWARF, Win32, and Win64. The Win64 code also supports POSIX. + * Support for those scheme is in `rt.dwarfeh`, `rt.deh_win32`, and + * `rt.deh_win64_posix`, respectively, and publicly imported here. + * + * When an exception is thrown by the user, the compiler translates + * code like `throw e;` into either `_d_throwdwarf` (for DWARF exceptions) + * or `_d_throwc` (Win32 / Win64), with the `Exception` object as argument. + * + * During those functions' handling, they eventually call `_d_createTrace`, + * which will store inside the `Exception` object the return of + * `_d_traceContext`, which is an object implementing `Throwable.TraceInfo`. + * `_d_traceContext` is a configurable hook, and by default will call + * `core.runtime : defaultTraceHandler`, which itself will call `backtrace` + * or something similar to store an array of stack frames (`void*` pointers) + * in the object it returns. + * Note that `defaultTraceHandler` returns a GC-allocated instance, + * hence a GC allocation can happen in the middle of throwing an `Exception`. + * + * The `Throwable.TraceInfo`-implementing should not resolves function names, + * file and line number until its `opApply` function is called, avoiding the + * overhead of reading the debug infos until the user call `toString`. + * If the user only calls `Throwable.message` (or use `Throwable.msg` directly), + * only the overhead of `backtrace` will be paid, which is minimal enouh. + * + * Copyright: Copyright Digital Mars 1999 - 2020. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright - * Source: $(DRUNTIMESRC src/rt/deh.d) + * Source: $(DRUNTIMESRC rt/deh.d) */ /* NOTE: This file has been patched from the original DMD distribution to @@ -17,10 +42,8 @@ module rt.deh; extern (C) { Throwable.TraceInfo _d_traceContext(void* ptr = null); - void _d_createTrace(Object o, void* context) + void _d_createTrace(Throwable t, void* context) { - auto t = cast(Throwable) o; - if (t !is null && t.info is null && cast(byte*) t !is typeid(t).initializer.ptr) { @@ -39,4 +62,3 @@ else version (Posix) public import rt.deh_win64_posix; else static assert (0, "Unsupported architecture"); - diff --git a/libphobos/libdruntime/rt/dmain2.d b/libphobos/libdruntime/rt/dmain2.d index e6acbd5..328452e 100644 --- a/libphobos/libdruntime/rt/dmain2.d +++ b/libphobos/libdruntime/rt/dmain2.d @@ -1,12 +1,12 @@ /** * Contains druntime startup and shutdown routines. * - * Copyright: Copyright Digital Mars 2000 - 2013. + * Copyright: Copyright Digital Mars 2000 - 2018. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright, Sean Kelly - * Source: $(DRUNTIMESRC src/rt/_dmain2.d) + * Source: $(DRUNTIMESRC rt/_dmain2.d) */ /* NOTE: This file has been patched from the original DMD distribution to @@ -14,22 +14,27 @@ */ module rt.dmain2; -private -{ - import rt.memory; - import rt.sections; - import core.atomic; - import core.stdc.stddef; - import core.stdc.stdlib; - import core.stdc.string; - import core.stdc.stdio; // for printf() - import core.stdc.errno : errno; -} +import rt.memory; +import rt.sections; +import core.atomic; +import core.stdc.stddef; +import core.stdc.stdlib; +import core.stdc.string; +import core.stdc.stdio; // for printf() +import core.stdc.errno : errno; version (Windows) { - private import core.stdc.wchar_; - private import core.sys.windows.windows; + import core.stdc.wchar_; + import core.sys.windows.basetsd : HANDLE; + import core.sys.windows.shellapi : CommandLineToArgvW; + import core.sys.windows.winbase : FreeLibrary, GetCommandLineW, GetProcAddress, + IsDebuggerPresent, LoadLibraryW, LocalFree, WriteFile; + import core.sys.windows.wincon : CONSOLE_SCREEN_BUFFER_INFO, GetConsoleOutputCP, + GetConsoleScreenBufferInfo; + import core.sys.windows.winnls : CP_UTF8, MultiByteToWideChar, WideCharToMultiByte; + import core.sys.windows.winnt : WCHAR; + import core.sys.windows.winuser : MB_ICONERROR, MessageBoxW; pragma(lib, "shell32.lib"); // needed for CommandLineToArgvW } @@ -47,27 +52,33 @@ version (DragonFlyBSD) import core.stdc.fenv; } +// not sure why we can't define this in one place, but this is to keep this +// module from importing core.runtime. +struct UnitTestResult +{ + size_t executed; + size_t passed; + bool runMain; + bool summarize; +} + extern (C) void _d_monitor_staticctor(); extern (C) void _d_monitor_staticdtor(); extern (C) void _d_critical_init(); extern (C) void _d_critical_term(); extern (C) void gc_init(); extern (C) void gc_term(); +extern (C) void thread_init() @nogc; +extern (C) void thread_term() @nogc; extern (C) void lifetime_init(); extern (C) void rt_moduleCtor(); extern (C) void rt_moduleTlsCtor(); extern (C) void rt_moduleDtor(); extern (C) void rt_moduleTlsDtor(); extern (C) void thread_joinAll(); -extern (C) bool runModuleUnitTests(); +extern (C) UnitTestResult runModuleUnitTests(); extern (C) void _d_initMonoTime(); -version (OSX) -{ - // The bottom of the stack - extern (C) __gshared void* __osx_stack_end = cast(void*)0xC0000000; -} - version (CRuntime_Microsoft) { extern(C) void init_msvc(); @@ -83,9 +94,6 @@ extern (C) string[] rt_args() return _d_args; } -// make arguments passed to main available for being filtered by runtime initializers -extern(C) __gshared char[][] _d_main_args = null; - // This variable is only ever set by a debugger on initialization so it should // be fine to leave it as __gshared. extern (C) __gshared bool rt_trapExceptions = true; @@ -123,7 +131,8 @@ extern (C) int rt_init() // this initializes mono time before anything else to allow usage // in other druntime systems. _d_initMonoTime(); - gc_init(); + thread_init(); + // TODO: fixme - calls GC.addRange -> Initializes GC initStaticDataGC(); lifetime_init(); rt_moduleCtor(); @@ -132,7 +141,7 @@ extern (C) int rt_init() } catch (Throwable t) { - _initCount = 0; + atomicStore!(MemoryOrder.raw)(_initCount, 0); _d_print_throwable(t); } _d_critical_term(); @@ -145,7 +154,7 @@ extern (C) int rt_init() */ extern (C) int rt_term() { - if (!_initCount) return 0; // was never initialized + if (atomicLoad!(MemoryOrder.raw)(_initCount) == 0) return 0; // was never initialized if (atomicOp!"-="(_initCount, 1)) return 1; try @@ -154,6 +163,7 @@ extern (C) int rt_term() thread_joinAll(); rt_moduleDtor(); gc_term(); + thread_term(); return 1; } catch (Throwable t) @@ -234,74 +244,22 @@ extern (C) CArgs rt_cArgs() @nogc return _cArgs; } -/*********************************** - * Run the given main function. - * Its purpose is to wrap the D main() - * function and catch any unhandled exceptions. - */ +/// Type of the D main() function (`_Dmain`). private alias extern(C) int function(char[][] args) MainFunc; -extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) +/** + * Sets up the D char[][] command-line args, initializes druntime, + * runs embedded unittests and then runs the given D main() function, + * optionally catching and printing any unhandled exceptions. + */ +extern (C) int _d_run_main(int argc, char** argv, MainFunc mainFunc) { + // Set up _cArgs and array of D char[] slices, then forward to _d_run_main2 + // Remember the original C argc/argv _cArgs.argc = argc; _cArgs.argv = argv; - int result; - - version (OSX) - { /* OSX does not provide a way to get at the top of the - * stack, except for the magic value 0xC0000000. - * But as far as the gc is concerned, argv is at the top - * of the main thread's stack, so save the address of that. - */ - __osx_stack_end = cast(void*)&argv; - } - - version (FreeBSD) version (D_InlineAsm_X86) - { - /* - * FreeBSD/i386 sets the FPU precision mode to 53 bit double. - * Make it 64 bit extended. - */ - ushort fpucw; - asm - { - fstsw fpucw; - or fpucw, 0b11_00_111111; // 11: use 64 bit extended-precision - // 111111: mask all FP exceptions - fldcw fpucw; - } - } - version (CRuntime_Microsoft) - { - // enable full precision for reals - version (D_InlineAsm_X86_64) - { - asm - { - push RAX; - fstcw word ptr [RSP]; - or [RSP], 0b11_00_111111; // 11: use 64 bit extended-precision - // 111111: mask all FP exceptions - fldcw word ptr [RSP]; - pop RAX; - } - } - else version (D_InlineAsm_X86) - { - asm - { - push EAX; - fstcw word ptr [ESP]; - or [ESP], 0b11_00_111111; // 11: use 64 bit extended-precision - // 111111: mask all FP exceptions - fldcw word ptr [ESP]; - pop EAX; - } - } - } - version (Windows) { /* Because we want args[] to be UTF-8, and Windows doesn't guarantee that, @@ -309,10 +267,10 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) * Then, reparse into wargc/wargs, and then use Windows API to convert * to UTF-8. */ - const wchar_t* wCommandLine = GetCommandLineW(); + const wCommandLine = GetCommandLineW(); immutable size_t wCommandLineLength = wcslen(wCommandLine); int wargc; - wchar_t** wargs = CommandLineToArgvW(wCommandLine, &wargc); + auto wargs = CommandLineToArgvW(wCommandLine, &wargc); // assert(wargc == argc); /* argc can be broken by Unicode arguments */ // Allocate args[] on the stack - use wargc @@ -357,6 +315,114 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) else static assert(0); + return _d_run_main2(args, totalArgsLength, mainFunc); +} + +/** + * Windows-specific version for wide command-line arguments, e.g., + * from a wmain/wWinMain C entry point. + * This wide version uses the specified arguments, unlike narrow + * _d_run_main which uses the actual (wide) process arguments instead. + */ +version (Windows) +extern (C) int _d_wrun_main(int argc, wchar** wargv, MainFunc mainFunc) +{ + // Allocate args[] on the stack + char[][] args = (cast(char[]*) alloca(argc * (char[]).sizeof))[0 .. argc]; + + // 1st pass: compute each argument's length as UTF-16 and UTF-8 + size_t totalArgsLength = 0; + foreach (i; 0 .. argc) + { + const warg = wargv[i]; + const size_t wlen = wcslen(warg) + 1; // incl. terminating null + assert(wlen <= cast(size_t) int.max, "wlen cannot exceed int.max"); + const int len = WideCharToMultiByte(CP_UTF8, 0, warg, cast(int) wlen, null, 0, null, null); + args[i] = (cast(char*) wlen)[0 .. len]; // args[i].ptr = wlen, args[i].length = len + totalArgsLength += len; + } + + // Allocate a single buffer for all (null-terminated) argument strings in UTF-8 on the stack + char* utf8Buffer = cast(char*) alloca(totalArgsLength); + + // 2nd pass: convert to UTF-8 and finalize `args` + char* utf8 = utf8Buffer; + foreach (i; 0 .. argc) + { + const wlen = cast(int) args[i].ptr; + const len = cast(int) args[i].length; + WideCharToMultiByte(CP_UTF8, 0, wargv[i], wlen, utf8, len, null, null); + args[i] = utf8[0 .. len-1]; // excl. terminating null + utf8 += len; + } + + // Set C argc/argv; argv is a new stack-allocated array of UTF-8 C strings + char*[] argv = (cast(char**) alloca(argc * (char*).sizeof))[0 .. argc]; + foreach (i, ref arg; argv) + arg = args[i].ptr; + _cArgs.argc = argc; + _cArgs.argv = argv.ptr; + + totalArgsLength -= argc; // excl. null terminator per arg + return _d_run_main2(args, totalArgsLength, mainFunc); +} + +private extern (C) int _d_run_main2(char[][] args, size_t totalArgsLength, MainFunc mainFunc) +{ + int result; + + version (FreeBSD) version (D_InlineAsm_X86) + { + /* + * FreeBSD/i386 sets the FPU precision mode to 53 bit double. + * Make it 64 bit extended. + */ + ushort fpucw; + asm + { + fstsw fpucw; + or fpucw, 0b11_00_111111; // 11: use 64 bit extended-precision + // 111111: mask all FP exceptions + fldcw fpucw; + } + } + version (CRuntime_Microsoft) + { + // enable full precision for reals + version (D_InlineAsm_X86_64) + { + asm + { + push RAX; + fstcw word ptr [RSP]; + or [RSP], 0b11_00_111111; // 11: use 64 bit extended-precision + // 111111: mask all FP exceptions + fldcw word ptr [RSP]; + pop RAX; + } + } + else version (D_InlineAsm_X86) + { + asm + { + push EAX; + fstcw word ptr [ESP]; + or [ESP], 0b11_00_111111; // 11: use 64 bit extended-precision + // 111111: mask all FP exceptions + fldcw word ptr [ESP]; + pop EAX; + } + } + else version (GNU_InlineAsm) + { + size_t fpu_cw; + asm { "fstcw %0" : "=m" (fpu_cw); } + fpu_cw |= 0b11_00_111111; // 11: use 64 bit extended-precision + // 111111: mask all FP exceptions + asm { "fldcw %0" : "=m" (fpu_cw); } + } + } + /* Create a copy of args[] on the stack to be used for main, so that rt_args() * cannot be modified by the user. * Note that when this function returns, _d_args will refer to garbage. @@ -368,28 +434,33 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) char[][] argsCopy = buff[0 .. args.length]; auto argBuff = cast(char*) (buff + args.length); size_t j = 0; + import rt.config : rt_cmdline_enabled; + bool parseOpts = rt_cmdline_enabled!(); foreach (arg; args) { - if (arg.length < 6 || arg[0..6] != "--DRT-") // skip D runtime options - { - argsCopy[j++] = (argBuff[0 .. arg.length] = arg[]); - argBuff += arg.length; - } + // Do not pass Druntime options to the program + if (parseOpts && arg.length >= 6 && arg[0 .. 6] == "--DRT-") + continue; + // https://issues.dlang.org/show_bug.cgi?id=20459 + if (arg == "--") + parseOpts = false; + argsCopy[j++] = (argBuff[0 .. arg.length] = arg[]); + argBuff += arg.length; } args = argsCopy[0..j]; } - bool trapExceptions = rt_trapExceptions; + auto useExceptionTrap = parseExceptionOptions(); version (Windows) { if (IsDebuggerPresent()) - trapExceptions = false; + useExceptionTrap = false; } void tryExec(scope void delegate() dg) { - if (trapExceptions) + if (useExceptionTrap) { try { @@ -417,8 +488,34 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) // thrown during cleanup, however, will abort the cleanup process. void runAll() { - if (rt_init() && runModuleUnitTests()) - tryExec({ result = mainFunc(args); }); + if (rt_init()) + { + auto utResult = runModuleUnitTests(); + assert(utResult.passed <= utResult.executed); + if (utResult.passed == utResult.executed) + { + if (utResult.summarize) + { + if (utResult.passed == 0) + .fprintf(.stderr, "No unittests run\n"); + else + .fprintf(.stderr, "%d modules passed unittests\n", + cast(int)utResult.passed); + } + if (utResult.runMain) + tryExec({ result = mainFunc(args); }); + else + result = EXIT_SUCCESS; + } + else + { + if (utResult.summarize) + .fprintf(.stderr, "%d/%d modules FAILED unittests\n", + cast(int)(utResult.executed - utResult.passed), + cast(int)utResult.executed); + result = EXIT_FAILURE; + } + } else result = EXIT_FAILURE; @@ -441,17 +538,17 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) return result; } -private void formatThrowable(Throwable t, scope void delegate(in char[] s) nothrow sink) +private void formatThrowable(Throwable t, scope void delegate(const scope char[] s) nothrow sink) { - for (; t; t = t.next) + foreach (u; t) { - t.toString(sink); sink("\n"); + u.toString(sink); sink("\n"); - auto e = cast(Error)t; + auto e = cast(Error)u; if (e is null || e.bypassedException is null) continue; sink("=== Bypassed ===\n"); - for (auto t2 = e.bypassedException; t2; t2 = t2.next) + foreach (t2; e.bypassedException) { t2.toString(sink); sink("\n"); } @@ -459,6 +556,18 @@ private void formatThrowable(Throwable t, scope void delegate(in char[] s) nothr } } +private auto parseExceptionOptions() +{ + import rt.config : rt_configOption; + import core.internal.parseoptions : rt_parseOption; + const optName = "trapExceptions"; + auto option = rt_configOption(optName); + auto trap = rt_trapExceptions; + if (option.length) + rt_parseOption(optName, option, trap, ""); + return trap; +} + extern (C) void _d_print_throwable(Throwable t) { // On Windows, a console may not be present to print the output to. @@ -468,17 +577,17 @@ extern (C) void _d_print_throwable(Throwable t) { static struct WSink { - wchar_t* ptr; size_t len; + WCHAR* ptr; size_t len; - void sink(in char[] s) scope nothrow + void sink(const scope char[] s) scope nothrow { if (!s.length) return; int swlen = MultiByteToWideChar( CP_UTF8, 0, s.ptr, cast(int)s.length, null, 0); if (!swlen) return; - auto newPtr = cast(wchar_t*)realloc(ptr, - (this.len + swlen + 1) * wchar_t.sizeof); + auto newPtr = cast(WCHAR*)realloc(ptr, + (this.len + swlen + 1) * WCHAR.sizeof); if (!newPtr) return; ptr = newPtr; auto written = MultiByteToWideChar( @@ -486,7 +595,7 @@ extern (C) void _d_print_throwable(Throwable t) len += written; } - wchar_t* get() { if (ptr) ptr[len] = 0; return ptr; } + typeof(ptr) get() { if (ptr) ptr[len] = 0; return ptr; } void free() { .free(ptr); } } @@ -558,7 +667,7 @@ extern (C) void _d_print_throwable(Throwable t) } } - void sink(in char[] buf) scope nothrow + void sink(const scope char[] buf) scope nothrow { fprintf(stderr, "%.*s", cast(int)buf.length, buf.ptr); } diff --git a/libphobos/libdruntime/rt/dylib_fixes.c b/libphobos/libdruntime/rt/dylib_fixes.c index 7bcf34b..e484fed 100644 --- a/libphobos/libdruntime/rt/dylib_fixes.c +++ b/libphobos/libdruntime/rt/dylib_fixes.c @@ -2,7 +2,7 @@ * OS X support for dynamic libraries. * * Copyright: Copyright Digital Mars 2010 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright */ diff --git a/libphobos/libdruntime/rt/ehalloc.d b/libphobos/libdruntime/rt/ehalloc.d new file mode 100644 index 0000000..1dcd69a --- /dev/null +++ b/libphobos/libdruntime/rt/ehalloc.d @@ -0,0 +1,125 @@ +/** + * Exception allocation, cloning, and release compiler support routines. + * + * Copyright: Copyright (c) 2017 by D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Walter Bright + * Source: $(DRUNTIMESRC rt/_ehalloc.d) + */ + +module rt.ehalloc; + +//debug = PRINTF; + +debug(PRINTF) +{ + import core.stdc.stdio; +} + +/********************************************** + * Allocate an exception of type `ci` from the exception pool. + * It has the same interface as `rt.lifetime._d_newclass()`. + * The class type must be Throwable or derived from it, + * and cannot be a COM or C++ class. The compiler must enforce + * this. + * Returns: + * default initialized instance of the type + */ + +extern (C) Throwable _d_newThrowable(const TypeInfo_Class ci) +{ + debug(PRINTF) printf("_d_newThrowable(ci = %p, %s)\n", ci, cast(char *)ci.name); + + assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass)); + assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass)); + + import core.stdc.stdlib : malloc; + auto init = ci.initializer; + void* p = malloc(init.length); + if (!p) + { + import core.exception : onOutOfMemoryError; + onOutOfMemoryError(); + } + + debug(PRINTF) printf(" p = %p\n", p); + + // initialize it + p[0 .. init.length] = init[]; + + if (!(ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)) + { + // Inform the GC about the pointers in the object instance + import core.memory : GC; + + GC.addRange(p, init.length, ci); + } + + debug(PRINTF) printf("initialization done\n"); + Throwable t = cast(Throwable)p; + t.refcount() = 1; + return t; +} + + +/******************************************** + * Delete exception instance `t` from the exception pool. + * Must have been allocated with `_d_newThrowable()`. + * This is meant to be called at the close of a catch block. + * It's nothrow because otherwise any function with a catch block could + * not be nothrow. + * Input: + * t = Throwable + */ + +nothrow extern (C) void _d_delThrowable(Throwable t) +{ + if (t) + { + debug(PRINTF) printf("_d_delThrowable(%p)\n", t); + + /* If allocated by the GC, don't free it. + * Let the GC handle it. + * Supporting this is necessary while transitioning + * to this new scheme for allocating exceptions. + */ + auto refcount = t.refcount(); + if (refcount == 0) + return; // it was allocated by the GC + + if (refcount == 1) + assert(0); // no zombie objects + + t.refcount() = --refcount; + if (refcount > 1) + return; + + TypeInfo_Class **pc = cast(TypeInfo_Class **)t; + if (*pc) + { + TypeInfo_Class ci = **pc; + + if (!(ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)) + { + // Inform the GC about the pointers in the object instance + import core.memory : GC; + GC.removeRange(cast(void*) t); + } + } + + try + { + import rt.lifetime : rt_finalize; + rt_finalize(cast(void*) t); + } + catch (Throwable t) + { + assert(0); // should never happen since Throwable.~this() is nothrow + } + import core.stdc.stdlib : free; + debug(PRINTF) printf("free(%p)\n", t); + free(cast(void*) t); + } +} diff --git a/libphobos/libdruntime/rt/invariant.d b/libphobos/libdruntime/rt/invariant.d index 4dddfad..e536196 100644 --- a/libphobos/libdruntime/rt/invariant.d +++ b/libphobos/libdruntime/rt/invariant.d @@ -2,8 +2,9 @@ * Implementation of invariant support routines. * * Copyright: Copyright Digital Mars 2007 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright + * Source: $(DRUNTIMESRC rt/_invariant.d) */ /* Copyright Digital Mars 2007 - 2010. diff --git a/libphobos/libdruntime/rt/lifetime.d b/libphobos/libdruntime/rt/lifetime.d index 6a6eb50..f1a9d87 100644 --- a/libphobos/libdruntime/rt/lifetime.d +++ b/libphobos/libdruntime/rt/lifetime.d @@ -7,22 +7,18 @@ * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright, Sean Kelly, Steven Schveighoffer - * Source: $(DRUNTIMESRC src/rt/_lifetime.d) + * Source: $(DRUNTIMESRC rt/_lifetime.d) */ module rt.lifetime; -import core.stdc.stdlib; -import core.stdc.string; -import core.stdc.stdarg; -import core.bitop; +import core.attribute : weak; import core.memory; debug(PRINTF) import core.stdc.stdio; static import rt.tlsgc; alias BlkInfo = GC.BlkInfo; alias BlkAttr = GC.BlkAttr; -import core.exception : onOutOfMemoryError, onFinalizeError, onInvalidMemoryOperationError; private { @@ -52,7 +48,7 @@ extern (C) void lifetime_init() /** * */ -extern (C) void* _d_allocmemory(size_t sz) +extern (C) void* _d_allocmemory(size_t sz) @weak { return GC.malloc(sz); } @@ -60,9 +56,12 @@ extern (C) void* _d_allocmemory(size_t sz) /** * */ -extern (C) Object _d_newclass(const ClassInfo ci) +extern (C) Object _d_newclass(const ClassInfo ci) @weak { + import core.stdc.stdlib; + import core.exception : onOutOfMemoryError; void* p; + auto init = ci.initializer; debug(PRINTF) printf("_d_newclass(ci = %p, %s)\n", ci, cast(char *)ci.name); if (ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass) @@ -71,7 +70,7 @@ extern (C) Object _d_newclass(const ClassInfo ci) * function called by Release() when Release()'s reference count goes * to zero. */ - p = malloc(ci.initializer.length); + p = malloc(init.length); if (!p) onOutOfMemoryError(); } @@ -85,26 +84,26 @@ extern (C) Object _d_newclass(const ClassInfo ci) attr |= BlkAttr.FINALIZE; if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers) attr |= BlkAttr.NO_SCAN; - p = GC.malloc(ci.initializer.length, attr, ci); + p = GC.malloc(init.length, attr, ci); debug(PRINTF) printf(" p = %p\n", p); } debug(PRINTF) { printf("p = %p\n", p); - printf("ci = %p, ci.init.ptr = %p, len = %llu\n", ci, ci.initializer.ptr, cast(ulong)ci.initializer.length); - printf("vptr = %p\n", *cast(void**) ci.initializer); - printf("vtbl[0] = %p\n", (*cast(void***) ci.initializer)[0]); - printf("vtbl[1] = %p\n", (*cast(void***) ci.initializer)[1]); - printf("init[0] = %x\n", (cast(uint*) ci.initializer)[0]); - printf("init[1] = %x\n", (cast(uint*) ci.initializer)[1]); - printf("init[2] = %x\n", (cast(uint*) ci.initializer)[2]); - printf("init[3] = %x\n", (cast(uint*) ci.initializer)[3]); - printf("init[4] = %x\n", (cast(uint*) ci.initializer)[4]); + printf("ci = %p, ci.init.ptr = %p, len = %llu\n", ci, init.ptr, cast(ulong)init.length); + printf("vptr = %p\n", *cast(void**) init); + printf("vtbl[0] = %p\n", (*cast(void***) init)[0]); + printf("vtbl[1] = %p\n", (*cast(void***) init)[1]); + printf("init[0] = %x\n", (cast(uint*) init)[0]); + printf("init[1] = %x\n", (cast(uint*) init)[1]); + printf("init[2] = %x\n", (cast(uint*) init)[2]); + printf("init[3] = %x\n", (cast(uint*) init)[3]); + printf("init[4] = %x\n", (cast(uint*) init)[4]); } // initialize it - p[0 .. ci.initializer.length] = ci.initializer[]; + p[0 .. init.length] = init[]; debug(PRINTF) printf("initialization done\n"); return cast(Object) p; @@ -134,7 +133,7 @@ private extern (D) alias void function (Object) fp_t; /** * */ -extern (C) void _d_delclass(Object* p) +extern (C) void _d_delclass(Object* p) @weak { if (*p) { @@ -169,7 +168,7 @@ extern (C) void _d_delclass(Object* p) * being deleted is a pointer to a struct with a destructor * but doesn't have an overloaded delete operator. */ -extern (C) void _d_delstruct(void** p, TypeInfo_Struct inf) +extern (C) void _d_delstruct(void** p, TypeInfo_Struct inf) @weak { if (*p) { @@ -182,7 +181,7 @@ extern (C) void _d_delstruct(void** p, TypeInfo_Struct inf) } // strip const/immutable/shared/inout from type info -inout(TypeInfo) unqualify(inout(TypeInfo) cti) pure nothrow @nogc +inout(TypeInfo) unqualify(return inout(TypeInfo) cti) pure nothrow @nogc { TypeInfo ti = cast() cti; while (ti) @@ -382,7 +381,7 @@ size_t __arrayAllocLength(ref BlkInfo info, const TypeInfo tinext) pure nothrow /** get the start of the array for the given block */ -void *__arrayStart(BlkInfo info) nothrow pure +void *__arrayStart(return BlkInfo info) nothrow pure { return info.base + ((info.size & BIGLENGTHMASK) ? LARGEPREFIX : 0); } @@ -398,10 +397,26 @@ size_t __arrayPad(size_t size, const TypeInfo tinext) nothrow pure @trusted } /** + clear padding that might not be zeroed by the GC (it assumes it is within the + requested size from the start, but it is actually at the end of the allocated block) + */ +private void __arrayClearPad(ref BlkInfo info, size_t arrsize, size_t padsize) nothrow pure +{ + import core.stdc.string; + if (padsize > MEDPAD && !(info.attr & BlkAttr.NO_SCAN) && info.base) + { + if (info.size < PAGESIZE) + memset(info.base + arrsize, 0, padsize); + else + memset(info.base, 0, LARGEPREFIX); + } +} + +/** allocate an array memory block by applying the proper padding and assigning block attributes if not inherited from the existing block */ -BlkInfo __arrayAlloc(size_t arrsize, const TypeInfo ti, const TypeInfo tinext) nothrow pure +BlkInfo __arrayAlloc(size_t arrsize, const scope TypeInfo ti, const TypeInfo tinext) nothrow pure { import core.checkedint; @@ -417,24 +432,30 @@ BlkInfo __arrayAlloc(size_t arrsize, const TypeInfo ti, const TypeInfo tinext) n uint attr = (!(tinext.flags & 1) ? BlkAttr.NO_SCAN : 0) | BlkAttr.APPENDABLE; if (typeInfoSize) attr |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE; - return GC.qalloc(padded_size, attr, ti); + + auto bi = GC.qalloc(padded_size, attr, tinext); + __arrayClearPad(bi, arrsize, padsize); + return bi; } -BlkInfo __arrayAlloc(size_t arrsize, ref BlkInfo info, const TypeInfo ti, const TypeInfo tinext) +BlkInfo __arrayAlloc(size_t arrsize, ref BlkInfo info, const scope TypeInfo ti, const TypeInfo tinext) { import core.checkedint; if (!info.base) return __arrayAlloc(arrsize, ti, tinext); + immutable padsize = __arrayPad(arrsize, tinext); bool overflow; - auto padded_size = addu(arrsize, __arrayPad(arrsize, tinext), overflow); + auto padded_size = addu(arrsize, padsize, overflow); if (overflow) { return BlkInfo(); } - return GC.qalloc(padded_size, info.attr, ti); + auto bi = GC.qalloc(padded_size, info.attr, tinext); + __arrayClearPad(bi, arrsize, padsize); + return bi; } /** @@ -468,6 +489,8 @@ else { if (!__blkcache_storage) { + import core.stdc.stdlib; + import core.stdc.string; // allocate the block cache for the first time immutable size = BlkInfo.sizeof * N_CACHE_BLOCKS; __blkcache_storage = cast(BlkInfo *)malloc(size); @@ -482,6 +505,7 @@ static ~this() // free the blkcache if (__blkcache_storage) { + import core.stdc.stdlib; free(__blkcache_storage); __blkcache_storage = null; } @@ -670,7 +694,10 @@ extern(C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) /+nothrow+/ // Note: Since we "assume" the append is safe, it means it is not shared. // Since it is not shared, we also know it won't throw (no lock). if (!__setArrayAllocLength(info, newsize, false, tinext)) + { + import core.exception : onInvalidMemoryOperationError; onInvalidMemoryOperationError(); + } // cache the block if not already done. if (!isshared && !bic) @@ -720,14 +747,17 @@ void __doPostblit(void *ptr, size_t len, const TypeInfo ti) * of 0 to get the current capacity. Returns the number of elements that can * actually be stored once the resizing is done. */ -extern(C) size_t _d_arraysetcapacity(const TypeInfo ti, size_t newcapacity, void[]* p) +extern(C) size_t _d_arraysetcapacity(const TypeInfo ti, size_t newcapacity, void[]* p) @weak in { assert(ti); assert(!(*p).length || (*p).ptr); } -body +do { + import core.stdc.string; + import core.exception : onOutOfMemoryError; + // step 1, get the block auto isshared = typeid(ti) is typeid(TypeInfo_Shared); auto bic = isshared ? null : __getBlkInfo((*p).ptr); @@ -890,8 +920,10 @@ Lcontinue: * Allocate a new uninitialized array of length elements. * ti is the type of the resulting array, or pointer to element. */ -extern (C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow +extern (C) void[] _d_newarrayU(const scope TypeInfo ti, size_t length) pure nothrow @weak { + import core.exception : onOutOfMemoryError; + auto tinext = unqualify(ti.next); auto size = tinext.tsize; @@ -949,8 +981,10 @@ Lcontinue: * ti is the type of the resulting array, or pointer to element. * (For when the array is initialized to 0) */ -extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) pure nothrow +extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) pure nothrow @weak { + import core.stdc.string; + void[] result = _d_newarrayU(ti, length); auto tinext = unqualify(ti.next); auto size = tinext.tsize; @@ -962,7 +996,7 @@ extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) pure nothrow /** * For when the array has a non-zero initializer. */ -extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) pure nothrow +extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) pure nothrow @weak { import core.internal.traits : AliasSeq; @@ -983,6 +1017,7 @@ extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) pure nothrow default: { + import core.stdc.string; immutable sz = init.length; for (size_t u = 0; u < size * length; u += sz) memcpy(result.ptr + u, init.ptr, sz); @@ -1036,7 +1071,7 @@ void[] _d_newarrayOpT(alias op)(const TypeInfo ti, size_t[] dims) /** * */ -extern (C) void[] _d_newarraymTX(const TypeInfo ti, size_t[] dims) +extern (C) void[] _d_newarraymTX(const TypeInfo ti, size_t[] dims) @weak { debug(PRINTF) printf("_d_newarraymT(dims.length = %d)\n", dims.length); @@ -1052,7 +1087,7 @@ extern (C) void[] _d_newarraymTX(const TypeInfo ti, size_t[] dims) /** * */ -extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims) +extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims) @weak { debug(PRINTF) printf("_d_newarraymiT(dims.length = %d)\n", dims.length); @@ -1068,12 +1103,13 @@ extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims) * Allocate an uninitialized non-array item. * This is an optimization to avoid things needed for arrays like the __arrayPad(size). */ -extern (C) void* _d_newitemU(in TypeInfo _ti) +extern (C) void* _d_newitemU(scope const TypeInfo _ti) pure nothrow @weak { auto ti = unqualify(_ti); auto flags = !(ti.flags & 1) ? BlkAttr.NO_SCAN : 0; immutable tiSize = structTypeInfoSize(ti); - immutable size = ti.tsize + tiSize; + immutable itemSize = ti.tsize; + immutable size = itemSize + tiSize; if (tiSize) flags |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE; @@ -1081,22 +1117,27 @@ extern (C) void* _d_newitemU(in TypeInfo _ti) auto p = blkInf.base; if (tiSize) + { + *cast(TypeInfo*)(p + itemSize) = null; // the GC might not have cleared this area *cast(TypeInfo*)(p + blkInf.size - tiSize) = cast() ti; + } return p; } /// Same as above, zero initializes the item. -extern (C) void* _d_newitemT(in TypeInfo _ti) +extern (C) void* _d_newitemT(in TypeInfo _ti) pure nothrow @weak { + import core.stdc.string; auto p = _d_newitemU(_ti); memset(p, 0, _ti.tsize); return p; } /// Same as above, for item with non-zero initializer. -extern (C) void* _d_newitemiT(in TypeInfo _ti) +extern (C) void* _d_newitemiT(in TypeInfo _ti) pure nothrow @weak { + import core.stdc.string; auto p = _d_newitemU(_ti); auto init = _ti.initializer(); assert(init.length <= _ti.tsize); @@ -1113,15 +1154,6 @@ struct Array byte* data; } - -/** - * This function has been replaced by _d_delarray_t - */ -extern (C) void _d_delarray(void[]* p) -{ - _d_delarray_t(p, null); -} - debug(PRINTF) { extern(C) void printArrayCache() @@ -1138,7 +1170,7 @@ debug(PRINTF) /** * */ -extern (C) void _d_delarray_t(void[]* p, const TypeInfo_Struct ti) +extern (C) void _d_delarray_t(void[]* p, const TypeInfo_Struct ti) @weak { if (p) { @@ -1164,7 +1196,7 @@ extern (C) void _d_delarray_t(void[]* p, const TypeInfo_Struct ti) } } -unittest +deprecated unittest { __gshared size_t countDtor = 0; struct S @@ -1176,7 +1208,7 @@ unittest auto x = new S[10000]; void* p = x.ptr; assert(GC.addrOf(p) != null); - delete x; + _d_delarray_t(cast(void[]*)&x, typeid(typeof(x[0]))); // delete x; assert(GC.addrOf(p) == null); assert(countDtor == 10000); @@ -1185,7 +1217,7 @@ unittest auto z = y[200 .. 300]; p = z.ptr; assert(GC.addrOf(p) != null); - delete z; + _d_delarray_t(cast(void[]*)&z, typeid(typeof(z[0]))); // delete z; assert(GC.addrOf(p) == null); assert(countDtor == 10000 + 400); } @@ -1193,7 +1225,7 @@ unittest /** * */ -extern (C) void _d_delmemory(void* *p) +extern (C) void _d_delmemory(void* *p) @weak { if (*p) { @@ -1206,7 +1238,7 @@ extern (C) void _d_delmemory(void* *p) /** * */ -extern (C) void _d_callinterfacefinalizer(void *p) +extern (C) void _d_callinterfacefinalizer(void *p) @weak { if (p) { @@ -1220,7 +1252,7 @@ extern (C) void _d_callinterfacefinalizer(void *p) /** * */ -extern (C) void _d_callfinalizer(void* p) +extern (C) void _d_callfinalizer(void* p) @weak { rt_finalize( p ); } @@ -1324,6 +1356,7 @@ void finalize_array2(void* p, size_t size) nothrow } catch (Exception e) { + import core.exception : onFinalizeError; onFinalizeError(si, e); } } @@ -1353,6 +1386,7 @@ void finalize_struct(void* p, size_t size) nothrow } catch (Exception e) { + import core.exception : onFinalizeError; onFinalizeError(ti, e); } } @@ -1393,6 +1427,7 @@ extern (C) void rt_finalize2(void* p, bool det = true, bool resetMemory = true) } catch (Exception e) { + import core.exception : onFinalizeError; onFinalizeError(*pc, e); } finally @@ -1401,12 +1436,12 @@ extern (C) void rt_finalize2(void* p, bool det = true, bool resetMemory = true) } } -extern (C) void rt_finalize(void* p, bool det = true) +extern (C) void rt_finalize(void* p, bool det = true) nothrow { rt_finalize2(p, det, true); } -extern (C) void rt_finalizeFromGC(void* p, size_t size, uint attr) +extern (C) void rt_finalizeFromGC(void* p, size_t size, uint attr) nothrow { // to verify: reset memory necessary? if (!(attr & BlkAttr.STRUCTFINAL)) @@ -1421,14 +1456,17 @@ extern (C) void rt_finalizeFromGC(void* p, size_t size, uint attr) /** * Resize dynamic arrays with 0 initializers. */ -extern (C) void[] _d_arraysetlengthT(const TypeInfo ti, size_t newlength, void[]* p) +extern (C) void[] _d_arraysetlengthT(const TypeInfo ti, size_t newlength, void[]* p) @weak in { assert(ti); assert(!(*p).length || (*p).ptr); } -body +do { + import core.stdc.string; + import core.exception : onOutOfMemoryError; + debug(PRINTF) { //printf("_d_arraysetlengthT(p = %p, sizeelem = %d, newlength = %d)\n", p, sizeelem, newlength); @@ -1436,170 +1474,179 @@ body printf("\tp.ptr = %p, p.length = %d\n", (*p).ptr, (*p).length); } - void* newdata = void; - if (newlength) + if (newlength <= (*p).length) { - if (newlength <= (*p).length) + *p = (*p)[0 .. newlength]; + void* newdata = (*p).ptr; + return newdata[0 .. newlength]; + } + auto tinext = unqualify(ti.next); + size_t sizeelem = tinext.tsize; + + /* Calculate: newsize = newlength * sizeelem + */ + bool overflow = false; + version (D_InlineAsm_X86) + { + size_t newsize = void; + + asm pure nothrow @nogc { - *p = (*p)[0 .. newlength]; - newdata = (*p).ptr; - return newdata[0 .. newlength]; + mov EAX, newlength; + mul EAX, sizeelem; + mov newsize, EAX; + setc overflow; } - auto tinext = unqualify(ti.next); - size_t sizeelem = tinext.tsize; - version (D_InlineAsm_X86) - { - size_t newsize = void; + } + else version (D_InlineAsm_X86_64) + { + size_t newsize = void; - asm pure nothrow @nogc - { - mov EAX, newlength; - mul EAX, sizeelem; - mov newsize, EAX; - jc Loverflow; - } - } - else version (D_InlineAsm_X86_64) + asm pure nothrow @nogc { - size_t newsize = void; - - asm pure nothrow @nogc - { - mov RAX, newlength; - mul RAX, sizeelem; - mov newsize, RAX; - jc Loverflow; - } + mov RAX, newlength; + mul RAX, sizeelem; + mov newsize, RAX; + setc overflow; } - else - { - import core.checkedint : mulu; + } + else + { + import core.checkedint : mulu; + const size_t newsize = mulu(sizeelem, newlength, overflow); + } + if (overflow) + { + onOutOfMemoryError(); + assert(0); + } - bool overflow = false; - size_t newsize = mulu(sizeelem, newlength, overflow); - if (overflow) - goto Loverflow; - } + debug(PRINTF) printf("newsize = %x, newlength = %x\n", newsize, newlength); - debug(PRINTF) printf("newsize = %x, newlength = %x\n", newsize, newlength); + const isshared = typeid(ti) is typeid(TypeInfo_Shared); + + if (!(*p).ptr) + { + // pointer was null, need to allocate + auto info = __arrayAlloc(newsize, ti, tinext); + if (info.base is null) + { + onOutOfMemoryError(); + assert(0); + } + __setArrayAllocLength(info, newsize, isshared, tinext); + if (!isshared) + __insertBlkInfoCache(info, null); + void* newdata = cast(byte *)__arrayStart(info); + memset(newdata, 0, newsize); + *p = newdata[0 .. newlength]; + return *p; + } - auto isshared = typeid(ti) is typeid(TypeInfo_Shared); + const size_t size = (*p).length * sizeelem; + auto bic = isshared ? null : __getBlkInfo((*p).ptr); + auto info = bic ? *bic : GC.query((*p).ptr); - if ((*p).ptr) + /* Attempt to extend past the end of the existing array. + * If not possible, allocate new space for entire array and copy. + */ + bool allocateAndCopy = false; + void* newdata = (*p).ptr; + if (info.base && (info.attr & BlkAttr.APPENDABLE)) + { + // calculate the extent of the array given the base. + const size_t offset = (*p).ptr - __arrayStart(info); + if (info.size >= PAGESIZE) { - newdata = (*p).ptr; - if (newlength > (*p).length) + // size of array is at the front of the block + if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) { - size_t size = (*p).length * sizeelem; - auto bic = isshared ? null : __getBlkInfo((*p).ptr); - auto info = bic ? *bic : GC.query((*p).ptr); - if (info.base && (info.attr & BlkAttr.APPENDABLE)) + // check to see if it failed because there is not + // enough space + if (*(cast(size_t*)info.base) == size + offset) { - // calculate the extent of the array given the base. - size_t offset = (*p).ptr - __arrayStart(info); - if (info.size >= PAGESIZE) + // not enough space, try extending + auto extendsize = newsize + offset + LARGEPAD - info.size; + auto u = GC.extend(info.base, extendsize, extendsize); + if (u) { - // size of array is at the front of the block - if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) - { - // check to see if it failed because there is not - // enough space - if (*(cast(size_t*)info.base) == size + offset) - { - // not enough space, try extending - auto extendsize = newsize + offset + LARGEPAD - info.size; - auto u = GC.extend(info.base, extendsize, extendsize); - if (u) - { - // extend worked, now try setting the length - // again. - info.size = u; - if (__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) - { - if (!isshared) - __insertBlkInfoCache(info, bic); - goto L1; - } - } - } - - // couldn't do it, reallocate - goto L2; - } - else if (!isshared && !bic) + // extend worked, now try setting the length + // again. + info.size = u; + if (__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) { - // add this to the cache, it wasn't present previously. - __insertBlkInfoCache(info, null); + if (!isshared) + __insertBlkInfoCache(info, bic); + memset(newdata + size, 0, newsize - size); + *p = newdata[0 .. newlength]; + return *p; } } - else if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) - { - // could not resize in place - goto L2; - } - else if (!isshared && !bic) - { - // add this to the cache, it wasn't present previously. - __insertBlkInfoCache(info, null); - } } - else - { - if (info.base) - { - L2: - if (bic) - { - // a chance that flags have changed since this was cached, we should fetch the most recent flags - info.attr = GC.getAttr(info.base) | BlkAttr.APPENDABLE; - } - info = __arrayAlloc(newsize, info, ti, tinext); - } - else - { - info = __arrayAlloc(newsize, ti, tinext); - } - if (info.base is null) - goto Loverflow; - - __setArrayAllocLength(info, newsize, isshared, tinext); - if (!isshared) - __insertBlkInfoCache(info, bic); - newdata = cast(byte *)__arrayStart(info); - newdata[0 .. size] = (*p).ptr[0 .. size]; - - // do postblit processing - __doPostblit(newdata, size, tinext); - } - L1: - memset(newdata + size, 0, newsize - size); + // couldn't do it, reallocate + allocateAndCopy = true; + } + else if (!isshared && !bic) + { + // add this to the cache, it wasn't present previously. + __insertBlkInfoCache(info, null); } } - else + else if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) { - // pointer was null, need to allocate - auto info = __arrayAlloc(newsize, ti, tinext); - if (info.base is null) - goto Loverflow; - __setArrayAllocLength(info, newsize, isshared, tinext); - if (!isshared) - __insertBlkInfoCache(info, null); - newdata = cast(byte *)__arrayStart(info); - memset(newdata, 0, newsize); + // could not resize in place + allocateAndCopy = true; + } + else if (!isshared && !bic) + { + // add this to the cache, it wasn't present previously. + __insertBlkInfoCache(info, null); } } else + allocateAndCopy = true; + + if (allocateAndCopy) { - newdata = (*p).ptr; + if (info.base) + { + if (bic) + { + // a chance that flags have changed since this was cached, we should fetch the most recent flags + info.attr = GC.getAttr(info.base) | BlkAttr.APPENDABLE; + } + info = __arrayAlloc(newsize, info, ti, tinext); + } + else + { + info = __arrayAlloc(newsize, ti, tinext); + } + + if (info.base is null) + { + onOutOfMemoryError(); + assert(0); + } + + __setArrayAllocLength(info, newsize, isshared, tinext); + if (!isshared) + __insertBlkInfoCache(info, bic); + newdata = cast(byte *)__arrayStart(info); + newdata[0 .. size] = (*p).ptr[0 .. size]; + + /* Do postblit processing, as we are making a copy and the + * original array may have references. + * Note that this may throw. + */ + __doPostblit(newdata, size, tinext); } + // Zero the unused portion of the newly allocated space + memset(newdata + size, 0, newsize - size); + *p = newdata[0 .. newlength]; return *p; - -Loverflow: - onOutOfMemoryError(); - assert(0); } @@ -1611,205 +1658,221 @@ Loverflow: * initsize size of initializer * ... initializer */ -extern (C) void[] _d_arraysetlengthiT(const TypeInfo ti, size_t newlength, void[]* p) +extern (C) void[] _d_arraysetlengthiT(const TypeInfo ti, size_t newlength, void[]* p) @weak in { assert(!(*p).length || (*p).ptr); } -body +do { - void* newdata; - auto tinext = unqualify(ti.next); - auto sizeelem = tinext.tsize; - auto initializer = tinext.initializer(); - auto initsize = initializer.length; - - assert(sizeelem); - assert(initsize); - assert(initsize <= sizeelem); - assert((sizeelem / initsize) * initsize == sizeelem); + import core.stdc.string; + import core.exception : onOutOfMemoryError; debug(PRINTF) { - printf("_d_arraysetlengthiT(p = %p, sizeelem = %d, newlength = %d, initsize = %d)\n", p, sizeelem, newlength, initsize); + //printf("_d_arraysetlengthiT(p = %p, sizeelem = %d, newlength = %d)\n", p, sizeelem, newlength); if (p) - printf("\tp.data = %p, p.length = %d\n", (*p).ptr, (*p).length); + printf("\tp.ptr = %p, p.length = %d\n", (*p).ptr, (*p).length); } - if (newlength) + if (newlength <= (*p).length) { - version (D_InlineAsm_X86) - { - size_t newsize = void; + *p = (*p)[0 .. newlength]; + void* newdata = (*p).ptr; + return newdata[0 .. newlength]; + } + auto tinext = unqualify(ti.next); + size_t sizeelem = tinext.tsize; - asm - { - mov EAX,newlength ; - mul EAX,sizeelem ; - mov newsize,EAX ; - jc Loverflow ; - } + /* Calculate: newsize = newlength * sizeelem + */ + bool overflow = false; + version (D_InlineAsm_X86) + { + size_t newsize = void; + + asm pure nothrow @nogc + { + mov EAX, newlength; + mul EAX, sizeelem; + mov newsize, EAX; + setc overflow; } - else version (D_InlineAsm_X86_64) + } + else version (D_InlineAsm_X86_64) + { + size_t newsize = void; + + asm pure nothrow @nogc { - size_t newsize = void; + mov RAX, newlength; + mul RAX, sizeelem; + mov newsize, RAX; + setc overflow; + } + } + else + { + import core.checkedint : mulu; + const size_t newsize = mulu(sizeelem, newlength, overflow); + } + if (overflow) + { + onOutOfMemoryError(); + assert(0); + } - asm - { - mov RAX,newlength ; - mul RAX,sizeelem ; - mov newsize,RAX ; - jc Loverflow ; - } + debug(PRINTF) printf("newsize = %x, newlength = %x\n", newsize, newlength); + + const isshared = typeid(ti) is typeid(TypeInfo_Shared); + + static void doInitialize(void *start, void *end, const void[] initializer) + { + if (initializer.length == 1) + { + memset(start, *(cast(ubyte*)initializer.ptr), end - start); } else { - import core.checkedint : mulu; + auto q = initializer.ptr; + immutable initsize = initializer.length; + for (; start < end; start += initsize) + { + memcpy(start, q, initsize); + } + } + } - bool overflow = false; - size_t newsize = mulu(sizeelem, newlength, overflow); - if (overflow) - goto Loverflow; + if (!(*p).ptr) + { + // pointer was null, need to allocate + auto info = __arrayAlloc(newsize, ti, tinext); + if (info.base is null) + { + onOutOfMemoryError(); + assert(0); } - debug(PRINTF) printf("newsize = %x, newlength = %x\n", newsize, newlength); + __setArrayAllocLength(info, newsize, isshared, tinext); + if (!isshared) + __insertBlkInfoCache(info, null); + void* newdata = cast(byte *)__arrayStart(info); + doInitialize(newdata, newdata + newsize, tinext.initializer); + *p = newdata[0 .. newlength]; + return *p; + } + const size_t size = (*p).length * sizeelem; + auto bic = isshared ? null : __getBlkInfo((*p).ptr); + auto info = bic ? *bic : GC.query((*p).ptr); - size_t size = (*p).length * sizeelem; - auto isshared = typeid(ti) is typeid(TypeInfo_Shared); - if ((*p).ptr) + /* Attempt to extend past the end of the existing array. + * If not possible, allocate new space for entire array and copy. + */ + bool allocateAndCopy = false; + void* newdata = (*p).ptr; + + if (info.base && (info.attr & BlkAttr.APPENDABLE)) + { + // calculate the extent of the array given the base. + const size_t offset = (*p).ptr - __arrayStart(info); + if (info.size >= PAGESIZE) { - newdata = (*p).ptr; - if (newlength > (*p).length) + // size of array is at the front of the block + if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) { - auto bic = isshared ? null : __getBlkInfo((*p).ptr); - auto info = bic ? *bic : GC.query((*p).ptr); - - // calculate the extent of the array given the base. - size_t offset = (*p).ptr - __arrayStart(info); - if (info.base && (info.attr & BlkAttr.APPENDABLE)) - { - if (info.size >= PAGESIZE) - { - // size of array is at the front of the block - if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) - { - // check to see if it failed because there is not - // enough space - if (*(cast(size_t*)info.base) == size + offset) - { - // not enough space, try extending - auto extendsize = newsize + offset + LARGEPAD - info.size; - auto u = GC.extend(info.base, extendsize, extendsize); - if (u) - { - // extend worked, now try setting the length - // again. - info.size = u; - if (__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) - { - if (!isshared) - __insertBlkInfoCache(info, bic); - goto L1; - } - } - } - - // couldn't do it, reallocate - goto L2; - } - else if (!isshared && !bic) - { - // add this to the cache, it wasn't present previously. - __insertBlkInfoCache(info, null); - } - } - else if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) - { - // could not resize in place - goto L2; - } - else if (!isshared && !bic) - { - // add this to the cache, it wasn't present previously. - __insertBlkInfoCache(info, null); - } - } - else + // check to see if it failed because there is not + // enough space + if (*(cast(size_t*)info.base) == size + offset) { - // not appendable or not part of the heap yet. - if (info.base) + // not enough space, try extending + auto extendsize = newsize + offset + LARGEPAD - info.size; + auto u = GC.extend(info.base, extendsize, extendsize); + if (u) { - L2: - if (bic) + // extend worked, now try setting the length + // again. + info.size = u; + if (__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) { - // a chance that flags have changed since this was cached, we should fetch the most recent flags - info.attr = GC.getAttr(info.base) | BlkAttr.APPENDABLE; + if (!isshared) + __insertBlkInfoCache(info, bic); + doInitialize(newdata + size, newdata + newsize, tinext.initializer); + *p = newdata[0 .. newlength]; + return *p; } - info = __arrayAlloc(newsize, info, ti, tinext); } - else - { - info = __arrayAlloc(newsize, ti, tinext); - } - __setArrayAllocLength(info, newsize, isshared, tinext); - if (!isshared) - __insertBlkInfoCache(info, bic); - newdata = cast(byte *)__arrayStart(info); - newdata[0 .. size] = (*p).ptr[0 .. size]; - - // do postblit processing - __doPostblit(newdata, size, tinext); } - L1: ; + + // couldn't do it, reallocate + allocateAndCopy = true; + } + else if (!isshared && !bic) + { + // add this to the cache, it wasn't present previously. + __insertBlkInfoCache(info, null); } } - else + else if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset)) { - // length was zero, need to allocate - auto info = __arrayAlloc(newsize, ti, tinext); - __setArrayAllocLength(info, newsize, isshared, tinext); - if (!isshared) - __insertBlkInfoCache(info, null); - newdata = cast(byte *)__arrayStart(info); + // could not resize in place + allocateAndCopy = true; } - - auto q = initializer.ptr; // pointer to initializer - - if (newsize > size) + else if (!isshared && !bic) { - if (initsize == 1) - { - debug(PRINTF) printf("newdata = %p, size = %d, newsize = %d, *q = %d\n", newdata, size, newsize, *cast(byte*)q); - memset(newdata + size, *cast(byte*)q, newsize - size); - } - else - { - for (size_t u = size; u < newsize; u += initsize) - { - memcpy(newdata + u, q, initsize); - } - } + // add this to the cache, it wasn't present previously. + __insertBlkInfoCache(info, null); } } else + allocateAndCopy = true; + + if (allocateAndCopy) { - newdata = (*p).ptr; + if (info.base) + { + if (bic) + { + // a chance that flags have changed since this was cached, we should fetch the most recent flags + info.attr = GC.getAttr(info.base) | BlkAttr.APPENDABLE; + } + info = __arrayAlloc(newsize, info, ti, tinext); + } + else + { + info = __arrayAlloc(newsize, ti, tinext); + } + + if (info.base is null) + { + onOutOfMemoryError(); + assert(0); + } + + __setArrayAllocLength(info, newsize, isshared, tinext); + if (!isshared) + __insertBlkInfoCache(info, bic); + newdata = cast(byte *)__arrayStart(info); + newdata[0 .. size] = (*p).ptr[0 .. size]; + + /* Do postblit processing, as we are making a copy and the + * original array may have references. + * Note that this may throw. + */ + __doPostblit(newdata, size, tinext); } + // Initialize the unused portion of the newly allocated space + doInitialize(newdata + size, newdata + newsize, tinext.initializer); *p = newdata[0 .. newlength]; return *p; - -Loverflow: - onOutOfMemoryError(); - assert(0); } - /** * Append y[] to array x[] */ -extern (C) void[] _d_arrayappendT(const TypeInfo ti, ref byte[] x, byte[] y) +extern (C) void[] _d_arrayappendT(const TypeInfo ti, ref byte[] x, byte[] y) @weak { + import core.stdc.string; auto length = x.length; auto tinext = unqualify(ti.next); auto sizeelem = tinext.tsize; // array element size @@ -1879,6 +1942,7 @@ size_t newCapacity(size_t newlength, size_t size) */ //long mult = 100 + (1000L * size) / (6 * log2plus1(newcap)); //long mult = 100 + (1000L * size) / log2plus1(newcap); + import core.bitop; long mult = 100 + (1000L) / (bsr(newcap) + 1); // testing shows 1.02 for large arrays is about the point of diminishing return @@ -1908,8 +1972,9 @@ size_t newCapacity(size_t newlength, size_t size) * Caller must initialize those elements. */ extern (C) -byte[] _d_arrayappendcTX(const TypeInfo ti, ref byte[] px, size_t n) +byte[] _d_arrayappendcTX(const TypeInfo ti, return scope ref byte[] px, size_t n) @weak { + import core.stdc.string; // This is a cut&paste job from _d_arrayappendT(). Should be refactored. // only optimize array append where ti is not a shared type @@ -2011,28 +2076,28 @@ byte[] _d_arrayappendcTX(const TypeInfo ti, ref byte[] px, size_t n) /** * Append dchar to char[] */ -extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) +extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) @weak { // c could encode into from 1 to 4 characters char[4] buf = void; - byte[] appendthis; // passed to appendT + char[] appendthis; // passed to appendT if (c <= 0x7F) { buf.ptr[0] = cast(char)c; - appendthis = (cast(byte *)buf.ptr)[0..1]; + appendthis = buf[0..1]; } else if (c <= 0x7FF) { buf.ptr[0] = cast(char)(0xC0 | (c >> 6)); buf.ptr[1] = cast(char)(0x80 | (c & 0x3F)); - appendthis = (cast(byte *)buf.ptr)[0..2]; + appendthis = buf[0..2]; } else if (c <= 0xFFFF) { buf.ptr[0] = cast(char)(0xE0 | (c >> 12)); buf.ptr[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); buf.ptr[2] = cast(char)(0x80 | (c & 0x3F)); - appendthis = (cast(byte *)buf.ptr)[0..3]; + appendthis = buf[0..3]; } else if (c <= 0x10FFFF) { @@ -2040,7 +2105,7 @@ extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) buf.ptr[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); buf.ptr[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); buf.ptr[3] = cast(char)(0x80 | (c & 0x3F)); - appendthis = (cast(byte *)buf.ptr)[0..4]; + appendthis = buf[0..4]; } else { @@ -2053,7 +2118,12 @@ extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) // get a typeinfo from the compiler. Assuming shared is the safest option. // Once the compiler is fixed, the proper typeinfo should be forwarded. // - return _d_arrayappendT(typeid(shared char[]), x, appendthis); + + // Hack because _d_arrayappendT takes `x` as a reference + auto xx = cast(shared(char)[])x; + object._d_arrayappendTImpl!(shared(char)[])._d_arrayappendT(xx, cast(shared(char)[])appendthis); + x = cast(byte[])xx; + return x; } unittest @@ -2088,25 +2158,21 @@ unittest /** * Append dchar to wchar[] */ -extern (C) void[] _d_arrayappendwd(ref byte[] x, dchar c) +extern (C) void[] _d_arrayappendwd(ref byte[] x, dchar c) @weak { // c could encode into from 1 to 2 w characters wchar[2] buf = void; - byte[] appendthis; // passed to appendT + wchar[] appendthis; // passed to appendT if (c <= 0xFFFF) { buf.ptr[0] = cast(wchar) c; - // note that although we are passing only 1 byte here, appendT - // interprets this as being an array of wchar, making the necessary - // casts. - appendthis = (cast(byte *)buf.ptr)[0..1]; + appendthis = buf[0..1]; } else { buf.ptr[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); buf.ptr[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); - // ditto from above. - appendthis = (cast(byte *)buf.ptr)[0..2]; + appendthis = buf[0..2]; } // @@ -2114,14 +2180,18 @@ extern (C) void[] _d_arrayappendwd(ref byte[] x, dchar c) // get a typeinfo from the compiler. Assuming shared is the safest option. // Once the compiler is fixed, the proper typeinfo should be forwarded. // - return _d_arrayappendT(typeid(shared wchar[]), x, appendthis); + + auto xx = (cast(shared(wchar)*)x.ptr)[0 .. x.length]; + object._d_arrayappendTImpl!(shared(wchar)[])._d_arrayappendT(xx, cast(shared(wchar)[])appendthis); + x = (cast(byte*)xx.ptr)[0 .. xx.length]; + return x; } /** * */ -extern (C) byte[] _d_arraycatT(const TypeInfo ti, byte[] x, byte[] y) +extern (C) byte[] _d_arraycatT(const TypeInfo ti, byte[] x, byte[] y) @weak out (result) { auto tinext = unqualify(ti.next); @@ -2142,8 +2212,9 @@ out (result) size_t cap = GC.sizeOf(result.ptr); assert(!cap || cap > result.length * sizeelem); } -body +do { + import core.stdc.string; version (none) { /* Cannot use this optimization because: @@ -2186,8 +2257,10 @@ body /** * */ -extern (C) void[] _d_arraycatnTX(const TypeInfo ti, byte[][] arrs) +extern (C) void[] _d_arraycatnTX(const TypeInfo ti, scope byte[][] arrs) @weak { + import core.stdc.string; + size_t length; auto tinext = unqualify(ti.next); auto size = tinext.tsize; // array element size @@ -2225,7 +2298,7 @@ extern (C) void[] _d_arraycatnTX(const TypeInfo ti, byte[][] arrs) * Allocate the array, rely on the caller to do the initialization of the array. */ extern (C) -void* _d_arrayliteralTX(const TypeInfo ti, size_t length) +void* _d_arrayliteralTX(const TypeInfo ti, size_t length) @weak { auto tinext = unqualify(ti.next); auto sizeelem = tinext.tsize; // array element size @@ -2333,7 +2406,7 @@ unittest } // cannot define structs inside unit test block, or they become nested structs. -version (unittest) +version (CoreUnittest) { struct S1 { @@ -2383,12 +2456,22 @@ unittest // Bugzilla 3454 - Inconsistent flag setting in GC.realloc() static void test(size_t multiplier) { - auto p = GC.malloc(8 * multiplier, BlkAttr.NO_SCAN); + auto p = GC.malloc(8 * multiplier, 0); + assert(GC.getAttr(p) == 0); + + // no move, set attr + p = GC.realloc(p, 8 * multiplier + 5, BlkAttr.NO_SCAN); assert(GC.getAttr(p) == BlkAttr.NO_SCAN); - p = GC.realloc(p, 2 * multiplier, BlkAttr.NO_SCAN); + + // shrink, copy attr + p = GC.realloc(p, 2 * multiplier, 0); + assert(GC.getAttr(p) == BlkAttr.NO_SCAN); + + // extend, copy attr + p = GC.realloc(p, 8 * multiplier, 0); assert(GC.getAttr(p) == BlkAttr.NO_SCAN); } - test(1); + test(16); test(1024 * 1024); } @@ -2505,7 +2588,7 @@ unittest // test struct finalizers debug(SENTINEL) {} else -unittest +deprecated unittest { __gshared int dtorCount; static struct S1 @@ -2520,12 +2603,12 @@ unittest dtorCount = 0; S1* s1 = new S1; - delete s1; + _d_delstruct(cast(void**)&s1, typeid(typeof(*s1))); // delete s1; assert(dtorCount == 1); dtorCount = 0; S1[] arr1 = new S1[7]; - delete arr1; + _d_delarray_t(cast(void[]*)&arr1, typeid(typeof(arr1[0]))); // delete arr1; assert(dtorCount == 7); dtorCount = 0; @@ -2596,6 +2679,77 @@ unittest assert(dtorCount == 4); } +// test struct dtor handling not causing false pointers +unittest +{ + // for 64-bit, allocate a struct of size 40 + static struct S + { + size_t[4] data; + S* ptr4; + } + auto p1 = new S; + auto p2 = new S; + p2.ptr4 = p1; + + // a struct with a dtor with size 32, but the dtor will cause + // allocation to be larger by a pointer + static struct A + { + size_t[3] data; + S* ptr3; + + ~this() {} + } + + GC.free(p2); + auto a = new A; // reuse same memory + if (cast(void*)a is cast(void*)p2) // reusage not guaranteed + { + auto ptr = cast(S**)(a + 1); + assert(*ptr != p1); // still same data as p2.ptr4? + } + + // small array + static struct SArr + { + void*[10] data; + } + auto arr1 = new SArr; + arr1.data[] = p1; + GC.free(arr1); + + // allocates 2*A.sizeof + (void*).sizeof (TypeInfo) + 1 (array length) + auto arr2 = new A[2]; + if (cast(void*)arr1 is cast(void*)arr2.ptr) // reusage not guaranteed + { + auto ptr = cast(S**)(arr2.ptr + 2); + assert(*ptr != p1); // still same data as p2.ptr4? + } + + // large array + static struct LArr + { + void*[1023] data; + } + auto larr1 = new LArr; + larr1.data[] = p1; + GC.free(larr1); + + auto larr2 = new S[255]; + if (cast(void*)larr1 is cast(void*)larr2.ptr - LARGEPREFIX) // reusage not guaranteed + { + auto ptr = cast(S**)larr1; + assert(ptr[0] != p1); // 16 bytes array header + assert(ptr[1] != p1); + version (D_LP64) {} else + { + assert(ptr[2] != p1); + assert(ptr[3] != p1); + } + } +} + // test class finalizers exception handling unittest { diff --git a/libphobos/libdruntime/rt/memory.d b/libphobos/libdruntime/rt/memory.d index 220b3d2..99b00c0 100644 --- a/libphobos/libdruntime/rt/memory.d +++ b/libphobos/libdruntime/rt/memory.d @@ -7,7 +7,7 @@ * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright, Sean Kelly - * Source: $(DRUNTIMESRC src/rt/_memory.d) + * Source: $(DRUNTIMESRC rt/_memory.d) */ module rt.memory; diff --git a/libphobos/libdruntime/rt/minfo.d b/libphobos/libdruntime/rt/minfo.d index 4722866..0d5cd22 100644 --- a/libphobos/libdruntime/rt/minfo.d +++ b/libphobos/libdruntime/rt/minfo.d @@ -7,7 +7,7 @@ * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright, Sean Kelly - * Source: $(DRUNTIMESRC src/rt/_minfo.d) + * Source: $(DRUNTIMESRC rt/_minfo.d) */ module rt.minfo; @@ -165,7 +165,7 @@ struct ModuleGroup void sortCtors(string cycleHandling) { import core.bitop : bts, btr, bt, BitRange; - import rt.util.container.hashtab; + import core.internal.container.hashtab; enum OnCycle { @@ -287,7 +287,7 @@ struct ModuleGroup else enum EOL = "\n"; - sink("Cyclic dependency between module "); + sink("Cyclic dependency between module constructors/destructors of "); sink(_modules[sourceIdx].name); sink(" and "); sink(_modules[cycleIdx].name); @@ -544,7 +544,7 @@ struct ModuleGroup * behavior. * * Params: - * edges - The module edges as found in the `importedModules` member of + * edges = The module edges as found in the `importedModules` member of * each ModuleInfo. Generated in sortCtors. * Returns: * true if no cycle is found, false if one was. @@ -566,7 +566,7 @@ struct ModuleGroup } auto stack = (cast(StackRec*).calloc(len, StackRec.sizeof))[0 .. len]; - // TODO: reuse GCBits by moving it to rt.util.container or core.internal + // TODO: reuse GCBits by moving it to core.internal.container immutable nwords = (len + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); auto ctorstart = cast(size_t*).malloc(nwords * size_t.sizeof); auto ctordone = cast(size_t*).malloc(nwords * size_t.sizeof); diff --git a/libphobos/libdruntime/rt/monitor_.d b/libphobos/libdruntime/rt/monitor_.d index 8cb3c3a..6bfce63 100644 --- a/libphobos/libdruntime/rt/monitor_.d +++ b/libphobos/libdruntime/rt/monitor_.d @@ -2,8 +2,9 @@ * Contains the implementation for object monitors. * * Copyright: Copyright Digital Mars 2000 - 2015. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright, Sean Kelly, Martin Nowak + * Source: $(DRUNTIMESRC rt/_monitor_.d) */ /* NOTE: This file has been patched from the original DMD distribution to @@ -25,7 +26,7 @@ in { assert(ownee.__monitor is null); } -body +do { auto m = ensureMonitor(cast(Object) owner); if (m.impl is null) @@ -81,7 +82,7 @@ in { assert(h !is null, "Synchronized object must not be null."); } -body +do { auto m = cast(Monitor*) ensureMonitor(h); auto i = m.impl; @@ -185,6 +186,7 @@ version (GNU) version (SingleThreaded) { +@nogc: alias Mutex = int; void initMutex(Mutex* mtx) @@ -262,7 +264,7 @@ struct Monitor private: -@property ref shared(Monitor*) monitor(Object h) pure nothrow @nogc +@property ref shared(Monitor*) monitor(return Object h) pure nothrow @nogc { return *cast(shared Monitor**)&h.__monitor; } diff --git a/libphobos/libdruntime/rt/obj.d b/libphobos/libdruntime/rt/obj.d deleted file mode 100644 index 97dfbb5..0000000 --- a/libphobos/libdruntime/rt/obj.d +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Contains object comparator functions called by generated code. - * - * Copyright: Copyright Digital Mars 2002 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright - */ - -/* Copyright Digital Mars 2000 - 2010. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module rt.obj; - -extern (C): - -/******************************** - * Compiler helper for operator == for class objects. - */ - -int _d_obj_eq(Object o1, Object o2) -{ - return o1 is o2 || (o1 && o1.opEquals(o2)); -} - - -/******************************** - * Compiler helper for operator <, <=, >, >= for class objects. - */ - -int _d_obj_cmp(Object o1, Object o2) -{ - return o1.opCmp(o2); -} diff --git a/libphobos/libdruntime/rt/profilegc.d b/libphobos/libdruntime/rt/profilegc.d new file mode 100644 index 0000000..45e0d51 --- /dev/null +++ b/libphobos/libdruntime/rt/profilegc.d @@ -0,0 +1,170 @@ +/* + * Data collection and report generation for + * -profile=gc + * switch + * + * Copyright: Copyright Digital Mars 2015 - 2015. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Andrei Alexandrescu and Walter Bright + * Source: $(DRUNTIMESRC rt/_profilegc.d) + */ + +module rt.profilegc; + +private: + +import core.stdc.stdio; +import core.stdc.stdlib; +import core.stdc.string; + +import core.exception : onOutOfMemoryError; +import core.internal.container.hashtab; + +struct Entry { ulong count, size; } + +char[] buffer; +HashTab!(const(char)[], Entry) newCounts; + +__gshared +{ + HashTab!(const(char)[], Entry) globalNewCounts; + string logfilename = "profilegc.log"; +} + +/**** + * Set file name for output. + * A file name of "" means write results to stdout. + * Params: + * name = file name + */ + +extern (C) void profilegc_setlogfilename(string name) +{ + logfilename = name ~ "\0"; +} + +public void accumulate(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow +{ + if (sz == 0) + return; + + char[3 * line.sizeof + 1] buf = void; + auto buflen = snprintf(buf.ptr, buf.length, "%u", line); + + auto length = type.length + 1 + funcname.length + 1 + file.length + 1 + buflen; + if (length > buffer.length) + { + // Enlarge buffer[] so it is big enough + assert(buffer.length > 0 || buffer.ptr is null); + auto p = cast(char*)realloc(buffer.ptr, length); + if (!p) + onOutOfMemoryError(); + buffer = p[0 .. length]; + } + + // "type funcname file:line" + buffer[0 .. type.length] = type[]; + buffer[type.length] = ' '; + buffer[type.length + 1 .. + type.length + 1 + funcname.length] = funcname[]; + buffer[type.length + 1 + funcname.length] = ' '; + buffer[type.length + 1 + funcname.length + 1 .. + type.length + 1 + funcname.length + 1 + file.length] = file[]; + buffer[type.length + 1 + funcname.length + 1 + file.length] = ':'; + buffer[type.length + 1 + funcname.length + 1 + file.length + 1 .. + type.length + 1 + funcname.length + 1 + file.length + 1 + buflen] = buf[0 .. buflen]; + + if (auto pcount = cast(string)buffer[0 .. length] in newCounts) + { // existing entry + pcount.count++; + pcount.size += sz; + } + else + { + auto key = (cast(char*) malloc(char.sizeof * length))[0 .. length]; + key[] = buffer[0..length]; + newCounts[key] = Entry(1, sz); // new entry + } +} + +// Merge thread local newCounts into globalNewCounts +static ~this() +{ + if (newCounts.length) + { + synchronized + { + foreach (name, entry; newCounts) + { + if (!(name in globalNewCounts)) + globalNewCounts[name] = Entry.init; + + globalNewCounts[name].count += entry.count; + globalNewCounts[name].size += entry.size; + } + } + newCounts.reset(); + } + free(buffer.ptr); + buffer = null; +} + +// Write report to stderr +shared static ~this() +{ + static struct Result + { + const(char)[] name; + Entry entry; + + // qsort() comparator to sort by count field + extern (C) static int qsort_cmp(scope const void *r1, scope const void *r2) @nogc nothrow + { + auto result1 = cast(Result*)r1; + auto result2 = cast(Result*)r2; + long cmp = result2.entry.size - result1.entry.size; + if (cmp) return cmp < 0 ? -1 : 1; + cmp = result2.entry.count - result1.entry.count; + if (cmp) return cmp < 0 ? -1 : 1; + if (result2.name == result1.name) return 0; + // ascending order for names reads better + return result2.name > result1.name ? -1 : 1; + } + } + + size_t size = globalNewCounts.length; + Result[] counts = (cast(Result*) malloc(size * Result.sizeof))[0 .. size]; + scope(exit) + free(counts.ptr); + + size_t i; + foreach (name, entry; globalNewCounts) + { + counts[i].name = name; + counts[i].entry = entry; + ++i; + } + + if (counts.length) + { + qsort(counts.ptr, counts.length, Result.sizeof, &Result.qsort_cmp); + + FILE* fp = logfilename.length == 0 ? stdout : fopen((logfilename).ptr, "w"); + if (fp) + { + fprintf(fp, "bytes allocated, allocations, type, function, file:line\n"); + foreach (ref c; counts) + { + fprintf(fp, "%15llu\t%15llu\t%8.*s\n", + cast(ulong)c.entry.size, cast(ulong)c.entry.count, + cast(int) c.name.length, c.name.ptr); + } + if (logfilename.length) + fclose(fp); + } + else + fprintf(stderr, "cannot write profilegc log file '%.*s'", cast(int) logfilename.length, logfilename.ptr); + } +} diff --git a/libphobos/libdruntime/rt/qsort.d b/libphobos/libdruntime/rt/qsort.d deleted file mode 100644 index 079a0f5..0000000 --- a/libphobos/libdruntime/rt/qsort.d +++ /dev/null @@ -1,166 +0,0 @@ -/** - * This is a public domain version of qsort.d. All it does is call C's - * qsort(). - * - * Copyright: Copyright Digital Mars 2000 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, Martin Nowak - */ - -/* Copyright Digital Mars 2000 - 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 rt.qsort; - -//debug=qsort; - -private import core.stdc.stdlib; - -version (OSX) - version = Darwin; -else version (iOS) - version = Darwin; -else version (TVOS) - version = Darwin; -else version (WatchOS) - version = Darwin; - -// qsort_r was added in glibc in 2.8. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88127 -version (CRuntime_Glibc) -{ - version (GNU) - { - import gcc.config : Have_Qsort_R; - enum Glibc_Qsort_R = Have_Qsort_R; - } - else - { - enum Glibc_Qsort_R = true; - } -} -else -{ - enum Glibc_Qsort_R = false; -} - -static if (Glibc_Qsort_R) -{ - alias extern (C) int function(scope const void *, scope const void *, scope void *) Cmp; - extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, Cmp cmp, scope void *arg); - - extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) - { - extern (C) int cmp(scope const void* p1, scope const void* p2, scope void* ti) - { - return (cast(TypeInfo)ti).compare(p1, p2); - } - qsort_r(a.ptr, a.length, ti.tsize, &cmp, cast(void*)ti); - return a; - } -} -else version (FreeBSD) -{ - alias extern (C) int function(scope void *, scope const void *, scope const void *) Cmp; - extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, scope void *thunk, Cmp cmp); - - extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) - { - extern (C) int cmp(scope void* ti, scope const void* p1, scope const void* p2) - { - return (cast(TypeInfo)ti).compare(p1, p2); - } - qsort_r(a.ptr, a.length, ti.tsize, cast(void*)ti, &cmp); - return a; - } -} -else version (DragonFlyBSD) -{ - alias extern (C) int function(scope void *, scope const void *, scope const void *) Cmp; - extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, scope void *thunk, Cmp cmp); - - extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) - { - extern (C) int cmp(scope void* ti, scope const void* p1, scope const void* p2) - { - return (cast(TypeInfo)ti).compare(p1, p2); - } - qsort_r(a.ptr, a.length, ti.tsize, cast(void*)ti, &cmp); - return a; - } -} -else version (Darwin) -{ - alias extern (C) int function(scope void *, scope const void *, scope const void *) Cmp; - extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, scope void *thunk, Cmp cmp); - - extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) - { - extern (C) int cmp(scope void* ti, scope const void* p1, scope const void* p2) - { - return (cast(TypeInfo)ti).compare(p1, p2); - } - qsort_r(a.ptr, a.length, ti.tsize, cast(void*)ti, &cmp); - return a; - } -} -else version (CRuntime_UClibc) -{ - alias extern (C) int function(scope const void *, scope const void *, scope void *) __compar_d_fn_t; - extern (C) void qsort_r(scope void *base, size_t nmemb, size_t size, __compar_d_fn_t cmp, scope void *arg); - - extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) - { - extern (C) int cmp(scope const void* p1, scope const void* p2, scope void* ti) - { - return (cast(TypeInfo)ti).compare(p1, p2); - } - qsort_r(a.ptr, a.length, ti.tsize, &cmp, cast(void*)ti); - return a; - } -} -else -{ - private TypeInfo tiglobal; - - extern (C) void[] _adSort(return scope void[] a, TypeInfo ti) - { - extern (C) int cmp(scope const void* p1, scope const void* p2) - { - return tiglobal.compare(p1, p2); - } - tiglobal = ti; - qsort(a.ptr, a.length, ti.tsize, &cmp); - return a; - } -} - - - -unittest -{ - debug(qsort) printf("array.sort.unittest()\n"); - - int[] a = new int[10]; - - a[0] = 23; - a[1] = 1; - a[2] = 64; - a[3] = 5; - a[4] = 6; - a[5] = 5; - a[6] = 17; - a[7] = 3; - a[8] = 0; - a[9] = -1; - - _adSort(*cast(void[]*)&a, typeid(a[0])); - - for (int i = 0; i < a.length - 1; i++) - { - //printf("i = %d", i); - //printf(" %d %d\n", a[i], a[i + 1]); - assert(a[i] <= a[i + 1]); - } -} diff --git a/libphobos/libdruntime/rt/sections.d b/libphobos/libdruntime/rt/sections.d index 6009a79..006d48d 100644 --- a/libphobos/libdruntime/rt/sections.d +++ b/libphobos/libdruntime/rt/sections.d @@ -5,7 +5,7 @@ * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright, Sean Kelly, Martin Nowak - * Source: $(DRUNTIMESRC src/rt/_sections.d) + * Source: $(DRUNTIMESRC rt/_sections.d) */ /* NOTE: This file has been patched from the original DMD distribution to @@ -26,10 +26,23 @@ version (GNU) public import gcc.sections; else version (CRuntime_Glibc) public import rt.sections_elf_shared; +else version (CRuntime_Musl) + public import rt.sections_elf_shared; else version (FreeBSD) public import rt.sections_elf_shared; else version (NetBSD) public import rt.sections_elf_shared; +else version (OpenBSD) +{ + /** + * OpenBSD is missing support needed for elf_shared. + * See the top of sections_solaris.d for more info. + */ + + public import rt.sections_solaris; +} +else version (DragonFlyBSD) + public import rt.sections_elf_shared; else version (Solaris) public import rt.sections_solaris; else version (Darwin) @@ -47,6 +60,8 @@ else version (CRuntime_Microsoft) public import rt.sections_win64; else version (CRuntime_Bionic) public import rt.sections_android; +else version (CRuntime_UClibc) + public import rt.sections_elf_shared; else static assert(0, "unimplemented"); diff --git a/libphobos/libdruntime/rt/switch_.d b/libphobos/libdruntime/rt/switch_.d deleted file mode 100644 index 73ad636..0000000 --- a/libphobos/libdruntime/rt/switch_.d +++ /dev/null @@ -1,424 +0,0 @@ -/** - * Contains support code for switch blocks using string constants. - * - * Copyright: Copyright Digital Mars 2004 - 2010. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, Sean Kelly - */ - -/* Copyright Digital Mars 2004 - 2010. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) - */ -module rt.switch_; - -private import core.stdc.string; - -/****************************************************** - * Support for switch statements switching on strings. - * Input: - * table[] sorted array of strings generated by compiler - * ca string to look up in table - * Output: - * result index of match in table[] - * -1 if not in table - */ - -extern (C): - -int _d_switch_string(char[][] table, char[] ca) -in -{ - //printf("in _d_switch_string()\n"); - assert(table.length >= 0); - assert(ca.length >= 0); - - // Make sure table[] is sorted correctly - for (size_t j = 1u; j < table.length; j++) - { - auto len1 = table[j - 1].length; - auto len2 = table[j].length; - - assert(len1 <= len2); - if (len1 == len2) - { - int ci; - - ci = memcmp(table[j - 1].ptr, table[j].ptr, len1); - assert(ci < 0); // ci==0 means a duplicate - } - } -} -out (result) -{ - int cj; - - //printf("out _d_switch_string()\n"); - if (result == -1) - { - // Not found - for (auto i = 0u; i < table.length; i++) - { - if (table[i].length == ca.length) - { cj = memcmp(table[i].ptr, ca.ptr, ca.length); - assert(cj != 0); - } - } - } - else - { - assert(0 <= result && cast(size_t)result < table.length); - for (auto i = 0u; 1; i++) - { - assert(i < table.length); - if (table[i].length == ca.length) - { - cj = memcmp(table[i].ptr, ca.ptr, ca.length); - if (cj == 0) - { - assert(i == result); - break; - } - } - } - } -} -body -{ - //printf("body _d_switch_string(%.*s)\n", ca.length, ca.ptr); - size_t low = 0; - size_t high = table.length; - - version (none) - { - // Print table - printf("ca[] = '%s'\n", ca.length, ca.ptr); - for (auto i = 0; i < high; i++) - { - auto pca = table[i]; - printf("table[%d] = %d, '%.*s'\n", i, pca.length, pca.length, pca.ptr); - } - } - if (high && - ca.length >= table[0].length && - ca.length <= table[high - 1].length) - { - // Looking for 0 length string, which would only be at the beginning - if (ca.length == 0) - return 0; - - char c1 = ca[0]; - - // Do binary search - while (low < high) - { - auto mid = (low + high) >> 1; - auto pca = table[mid]; - auto c = cast(sizediff_t)(ca.length - pca.length); - if (c == 0) - { - c = cast(ubyte)c1 - cast(ubyte)pca[0]; - if (c == 0) - { - c = memcmp(ca.ptr, pca.ptr, ca.length); - if (c == 0) - { //printf("found %d\n", mid); - return cast(int)mid; - } - } - } - if (c < 0) - { - high = mid; - } - else - { - low = mid + 1; - } - } - } - - //printf("not found\n"); - return -1; // not found -} - -unittest -{ - switch (cast(char []) "c") - { - case "coo": - default: - break; - } - - int bug5381(string s) - { - switch (s) - { - case "unittest": return 1; - case "D_Version2": return 2; - case "none": return 3; - case "all": return 4; - default: return 5; - } - } - int rc = bug5381("none"); - assert(rc == 3); -} - -/********************************** - * Same thing, but for wide chars. - */ - -int _d_switch_ustring(wchar[][] table, wchar[] ca) -in -{ - //printf("in _d_switch_ustring()\n"); - assert(table.length >= 0); - assert(ca.length >= 0); - - // Make sure table[] is sorted correctly - for (size_t j = 1u; j < table.length; j++) - { - auto len1 = table[j - 1].length; - auto len2 = table[j].length; - - assert(len1 <= len2); - if (len1 == len2) - { - int c; - - c = memcmp(table[j - 1].ptr, table[j].ptr, len1 * wchar.sizeof); - assert(c < 0); // c==0 means a duplicate - } - } -} -out (result) -{ - int c; - - //printf("out _d_switch_ustring()\n"); - if (result == -1) - { - // Not found - for (auto i = 0u; i < table.length; i++) - { - if (table[i].length == ca.length) - { c = memcmp(table[i].ptr, ca.ptr, ca.length * wchar.sizeof); - assert(c != 0); - } - } - } - else - { - assert(0 <= result && cast(size_t)result < table.length); - for (auto i = 0u; 1; i++) - { - assert(i < table.length); - if (table[i].length == ca.length) - { - c = memcmp(table[i].ptr, ca.ptr, ca.length * wchar.sizeof); - if (c == 0) - { - assert(i == result); - break; - } - } - } - } -} -body -{ - //printf("body _d_switch_ustring()\n"); - size_t low = 0; - auto high = table.length; - - version (none) - { - // Print table - wprintf("ca[] = '%.*s'\n", ca.length, ca.ptr); - for (auto i = 0; i < high; i++) - { - auto pca = table[i]; - wprintf("table[%d] = %d, '%.*s'\n", i, pca.length, pca.length, pca.ptr); - } - } - - // Do binary search - while (low < high) - { - auto mid = (low + high) >> 1; - auto pca = table[mid]; - auto c = cast(sizediff_t)(ca.length - pca.length); - if (c == 0) - { - c = memcmp(ca.ptr, pca.ptr, ca.length * wchar.sizeof); - if (c == 0) - { //printf("found %d\n", mid); - return cast(int)mid; - } - } - if (c < 0) - { - high = mid; - } - else - { - low = mid + 1; - } - } - //printf("not found\n"); - return -1; // not found -} - - -unittest -{ - switch (cast(wchar []) "c") - { - case "coo": - default: - break; - } - - int bug5381(wstring ws) - { - switch (ws) - { - case "unittest": return 1; - case "D_Version2": return 2; - case "none": return 3; - case "all": return 4; - default: return 5; - } - } - int rc = bug5381("none"w); - assert(rc == 3); -} - -/********************************** - * Same thing, but for wide chars. - */ - -int _d_switch_dstring(dchar[][] table, dchar[] ca) -in -{ - //printf("in _d_switch_dstring()\n"); - assert(table.length >= 0); - assert(ca.length >= 0); - - // Make sure table[] is sorted correctly - for (auto j = 1u; j < table.length; j++) - { - auto len1 = table[j - 1].length; - auto len2 = table[j].length; - - assert(len1 <= len2); - if (len1 == len2) - { - auto c = memcmp(table[j - 1].ptr, table[j].ptr, len1 * dchar.sizeof); - assert(c < 0); // c==0 means a duplicate - } - } -} -out (result) -{ - //printf("out _d_switch_dstring()\n"); - if (result == -1) - { - // Not found - for (auto i = 0u; i < table.length; i++) - { - if (table[i].length == ca.length) - { auto c = memcmp(table[i].ptr, ca.ptr, ca.length * dchar.sizeof); - assert(c != 0); - } - } - } - else - { - assert(0 <= result && cast(size_t)result < table.length); - for (auto i = 0u; 1; i++) - { - assert(i < table.length); - if (table[i].length == ca.length) - { - auto c = memcmp(table[i].ptr, ca.ptr, ca.length * dchar.sizeof); - if (c == 0) - { - assert(i == result); - break; - } - } - } - } -} -body -{ - //printf("body _d_switch_dstring()\n"); - size_t low = 0; - auto high = table.length; - - version (none) - { - // Print table - wprintf("ca[] = '%.*s'\n", ca.length, ca.ptr); - for (auto i = 0; i < high; i++) - { - auto pca = table[i]; - wprintf("table[%d] = %d, '%.*s'\n", i, pca.length, pca.length, pca.ptr); - } - } - - // Do binary search - while (low < high) - { - auto mid = (low + high) >> 1; - auto pca = table[mid]; - auto c = cast(sizediff_t)(ca.length - pca.length); - if (c == 0) - { - c = memcmp(ca.ptr, pca.ptr, ca.length * dchar.sizeof); - if (c == 0) - { //printf("found %d\n", mid); - return cast(int)mid; - } - } - if (c < 0) - { - high = mid; - } - else - { - low = mid + 1; - } - } - //printf("not found\n"); - return -1; // not found -} - - -unittest -{ - switch (cast(dchar []) "c") - { - case "coo": - default: - break; - } - - int bug5381(dstring ds) - { - switch (ds) - { - case "unittest": return 1; - case "D_Version2": return 2; - case "none": return 3; - case "all": return 4; - default: return 5; - } - } - int rc = bug5381("none"d); - assert(rc == 3); -} diff --git a/libphobos/libdruntime/rt/tlsgc.d b/libphobos/libdruntime/rt/tlsgc.d index db7347f..b13a1b3 100644 --- a/libphobos/libdruntime/rt/tlsgc.d +++ b/libphobos/libdruntime/rt/tlsgc.d @@ -1,8 +1,9 @@ /** * * Copyright: Copyright Digital Mars 2011 - 2012. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Nowak + * Source: $(DRUNTIMESRC rt/tlsgc.d) */ /* Copyright Digital Mars 2011. diff --git a/libphobos/libdruntime/rt/util/array.d b/libphobos/libdruntime/rt/util/array.d deleted file mode 100644 index b2cfb8d..0000000 --- a/libphobos/libdruntime/rt/util/array.d +++ /dev/null @@ -1,72 +0,0 @@ -/** -Array utilities. - -Copyright: Denis Shelomovskij 2013 -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Authors: Denis Shelomovskij -Source: $(DRUNTIMESRC src/rt/util/_array.d) -*/ -module rt.util.array; - - -import core.internal.string; -import core.stdc.stdint; - - -@safe /* pure dmd @@@BUG11461@@@ */ nothrow: - -void enforceTypedArraysConformable(T)(const char[] action, - const T[] a1, const T[] a2, in bool allowOverlap = false) -{ - _enforceSameLength(action, a1.length, a2.length); - if (!allowOverlap) - _enforceNoOverlap(action, arrayToPtr(a1), arrayToPtr(a2), T.sizeof * a1.length); -} - -void enforceRawArraysConformable(const char[] action, in size_t elementSize, - const void[] a1, const void[] a2, in bool allowOverlap = false) -{ - _enforceSameLength(action, a1.length, a2.length); - if (!allowOverlap) - _enforceNoOverlap(action, arrayToPtr(a1), arrayToPtr(a2), elementSize * a1.length); -} - -private void _enforceSameLength(const char[] action, - in size_t length1, in size_t length2) -{ - if (length1 == length2) - return; - - UnsignedStringBuf tmpBuff = void; - string msg = "Array lengths don't match for "; - msg ~= action; - msg ~= ": "; - msg ~= length1.unsignedToTempString(tmpBuff, 10); - msg ~= " != "; - msg ~= length2.unsignedToTempString(tmpBuff, 10); - throw new Error(msg); -} - -private void _enforceNoOverlap(const char[] action, - uintptr_t ptr1, uintptr_t ptr2, in size_t bytes) -{ - const d = ptr1 > ptr2 ? ptr1 - ptr2 : ptr2 - ptr1; - if (d >= bytes) - return; - const overlappedBytes = bytes - d; - - UnsignedStringBuf tmpBuff = void; - string msg = "Overlapping arrays in "; - msg ~= action; - msg ~= ": "; - msg ~= overlappedBytes.unsignedToTempString(tmpBuff, 10); - msg ~= " byte(s) overlap of "; - msg ~= bytes.unsignedToTempString(tmpBuff, 10); - throw new Error(msg); -} - -private uintptr_t arrayToPtr(const void[] array) @trusted -{ - // Ok because the user will never dereference the pointer - return cast(uintptr_t)array.ptr; -} diff --git a/libphobos/libdruntime/rt/util/container/array.d b/libphobos/libdruntime/rt/util/container/array.d deleted file mode 100644 index f5aa3d7..0000000 --- a/libphobos/libdruntime/rt/util/container/array.d +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Array container for internal usage. - * - * Copyright: Copyright Martin Nowak 2013. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Martin Nowak - */ -module rt.util.container.array; - -static import common = rt.util.container.common; - -import core.exception : onOutOfMemoryErrorNoGC; - -struct Array(T) -{ -nothrow: - @disable this(this); - - ~this() - { - reset(); - } - - void reset() - { - length = 0; - } - - @property size_t length() const - { - return _length; - } - - @property void length(size_t nlength) - { - import core.checkedint : mulu; - - bool overflow = false; - size_t reqsize = mulu(T.sizeof, nlength, overflow); - if (!overflow) - { - if (nlength < _length) - foreach (ref val; _ptr[nlength .. _length]) common.destroy(val); - _ptr = cast(T*)common.xrealloc(_ptr, reqsize); - if (nlength > _length) - foreach (ref val; _ptr[_length .. nlength]) common.initialize(val); - _length = nlength; - } - else - onOutOfMemoryErrorNoGC(); - - } - - @property bool empty() const - { - return !length; - } - - @property ref inout(T) front() inout - in { assert(!empty); } - body - { - return _ptr[0]; - } - - @property ref inout(T) back() inout - in { assert(!empty); } - body - { - return _ptr[_length - 1]; - } - - ref inout(T) opIndex(size_t idx) inout - in { assert(idx < length); } - body - { - return _ptr[idx]; - } - - inout(T)[] opSlice() inout - { - return _ptr[0 .. _length]; - } - - inout(T)[] opSlice(size_t a, size_t b) inout - in { assert(a < b && b <= length); } - body - { - return _ptr[a .. b]; - } - - alias length opDollar; - - void insertBack()(auto ref T val) - { - import core.checkedint : addu; - - bool overflow = false; - size_t newlength = addu(length, 1, overflow); - if (!overflow) - { - length = newlength; - back = val; - } - else - onOutOfMemoryErrorNoGC(); - } - - void popBack() - { - length = length - 1; - } - - void remove(size_t idx) - in { assert(idx < length); } - body - { - foreach (i; idx .. length - 1) - _ptr[i] = _ptr[i+1]; - popBack(); - } - - void swap(ref Array other) - { - auto ptr = _ptr; - _ptr = other._ptr; - other._ptr = ptr; - immutable len = _length; - _length = other._length; - other._length = len; - } - - invariant - { - assert(!_ptr == !_length); - } - -private: - T* _ptr; - size_t _length; -} - -unittest -{ - Array!size_t ary; - - assert(ary[] == []); - ary.insertBack(5); - assert(ary[] == [5]); - assert(ary[$-1] == 5); - ary.popBack(); - assert(ary[] == []); - ary.insertBack(0); - ary.insertBack(1); - assert(ary[] == [0, 1]); - assert(ary[0 .. 1] == [0]); - assert(ary[1 .. 2] == [1]); - assert(ary[$ - 2 .. $] == [0, 1]); - size_t idx; - foreach (val; ary) assert(idx++ == val); - foreach_reverse (val; ary) assert(--idx == val); - foreach (i, val; ary) assert(i == val); - foreach_reverse (i, val; ary) assert(i == val); - - ary.insertBack(2); - ary.remove(1); - assert(ary[] == [0, 2]); - - assert(!ary.empty); - ary.reset(); - assert(ary.empty); - ary.insertBack(0); - assert(!ary.empty); - destroy(ary); - assert(ary.empty); - - // not copyable - static assert(!__traits(compiles, { Array!size_t ary2 = ary; })); - Array!size_t ary2; - static assert(!__traits(compiles, ary = ary2)); - static void foo(Array!size_t copy) {} - static assert(!__traits(compiles, foo(ary))); - - ary2.insertBack(0); - assert(ary.empty); - assert(ary2[] == [0]); - ary.swap(ary2); - assert(ary[] == [0]); - assert(ary2.empty); -} - -unittest -{ - alias RC = common.RC!(); - Array!RC ary; - - size_t cnt; - assert(cnt == 0); - ary.insertBack(RC(&cnt)); - assert(cnt == 1); - ary.insertBack(RC(&cnt)); - assert(cnt == 2); - ary.back = ary.front; - assert(cnt == 2); - ary.popBack(); - assert(cnt == 1); - ary.popBack(); - assert(cnt == 0); -} - -unittest -{ - import core.exception; - try - { - // Overflow ary.length. - auto ary = Array!size_t(cast(size_t*)0xdeadbeef, -1); - ary.insertBack(0); - } - catch (OutOfMemoryError) - { - } - try - { - // Overflow requested memory size for common.xrealloc(). - auto ary = Array!size_t(cast(size_t*)0xdeadbeef, -2); - ary.insertBack(0); - } - catch (OutOfMemoryError) - { - } -} diff --git a/libphobos/libdruntime/rt/util/container/common.d b/libphobos/libdruntime/rt/util/container/common.d deleted file mode 100644 index 9e6c013..0000000 --- a/libphobos/libdruntime/rt/util/container/common.d +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Common code for writing containers. - * - * Copyright: Copyright Martin Nowak 2013. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Martin Nowak - */ -module rt.util.container.common; - -import core.stdc.stdlib : malloc, realloc; -public import core.stdc.stdlib : free; -import core.internal.traits : dtorIsNothrow; -nothrow: - -void* xrealloc(void* ptr, size_t sz) nothrow @nogc -{ - import core.exception; - - if (!sz) { .free(ptr); return null; } - if (auto nptr = .realloc(ptr, sz)) return nptr; - .free(ptr); onOutOfMemoryErrorNoGC(); - assert(0); -} - -void* xmalloc(size_t sz) nothrow @nogc -{ - import core.exception; - if (auto nptr = .malloc(sz)) - return nptr; - onOutOfMemoryErrorNoGC(); - assert(0); -} - -void destroy(T)(ref T t) if (is(T == struct) && dtorIsNothrow!T) -{ - scope (failure) assert(0); // nothrow hack - object.destroy(t); -} - -void destroy(T)(ref T t) if (!is(T == struct)) -{ - t = T.init; -} - -void initialize(T)(ref T t) if (is(T == struct)) -{ - import core.stdc.string; - if (auto p = typeid(T).initializer().ptr) - memcpy(&t, p, T.sizeof); - else - memset(&t, 0, T.sizeof); -} - -void initialize(T)(ref T t) if (!is(T == struct)) -{ - t = T.init; -} - -version (unittest) struct RC() -{ -nothrow: - this(size_t* cnt) { ++*(_cnt = cnt); } - ~this() { if (_cnt) --*_cnt; } - this(this) { if (_cnt) ++*_cnt; } - size_t* _cnt; -} diff --git a/libphobos/libdruntime/rt/util/container/hashtab.d b/libphobos/libdruntime/rt/util/container/hashtab.d deleted file mode 100644 index fd9f0f7..0000000 --- a/libphobos/libdruntime/rt/util/container/hashtab.d +++ /dev/null @@ -1,329 +0,0 @@ -/** - * HashTab container for internal usage. - * - * Copyright: Copyright Martin Nowak 2013. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Martin Nowak - */ -module rt.util.container.hashtab; - -import rt.util.container.array; -static import common = rt.util.container.common; - -struct HashTab(Key, Value) -{ - static struct Node - { - Key _key; - Value _value; - Node* _next; - } - - @disable this(this); - - ~this() - { - reset(); - } - - void reset() - { - foreach (p; _buckets) - { - while (p !is null) - { - auto pn = p._next; - common.destroy(*p); - common.free(p); - p = pn; - } - } - _buckets.reset(); - _length = 0; - } - - @property size_t length() const - { - return _length; - } - - @property bool empty() const - { - return !_length; - } - - void remove(in Key key) - in { assert(key in this); } - body - { - ensureNotInOpApply(); - - immutable hash = hashOf(key) & mask; - auto pp = &_buckets[hash]; - while (*pp) - { - auto p = *pp; - if (p._key == key) - { - *pp = p._next; - common.destroy(*p); - common.free(p); - if (--_length < _buckets.length && _length >= 4) - shrink(); - return; - } - else - { - pp = &p._next; - } - } - assert(0); - } - - ref inout(Value) opIndex(Key key) inout - { - return *opIn_r(key); - } - - void opIndexAssign(Value value, Key key) - { - *get(key) = value; - } - - inout(Value)* opIn_r(in Key key) inout - { - if (_buckets.length) - { - immutable hash = hashOf(key) & mask; - for (inout(Node)* p = _buckets[hash]; p !is null; p = p._next) - { - if (p._key == key) - return &p._value; - } - } - return null; - } - - int opApply(scope int delegate(ref Key, ref Value) dg) - { - immutable save = _inOpApply; - _inOpApply = true; - scope (exit) _inOpApply = save; - foreach (p; _buckets) - { - while (p !is null) - { - if (auto res = dg(p._key, p._value)) - return res; - p = p._next; - } - } - return 0; - } - -private: - - Value* get(Key key) - { - if (auto p = opIn_r(key)) - return p; - - ensureNotInOpApply(); - - if (!_buckets.length) - _buckets.length = 4; - - immutable hash = hashOf(key) & mask; - auto p = cast(Node*)common.xmalloc(Node.sizeof); - common.initialize(*p); - p._key = key; - p._next = _buckets[hash]; - _buckets[hash] = p; - if (++_length >= 2 * _buckets.length) - grow(); - return &p._value; - } - - static hash_t hashOf(in ref Key key) @trusted - { - static if (is(Key U : U[])) - return .hashOf(key, 0); - else - return .hashOf((&key)[0 .. 1], 0); - } - - @property hash_t mask() const - { - return _buckets.length - 1; - } - - void grow() - in - { - assert(_buckets.length); - } - body - { - immutable ocnt = _buckets.length; - immutable nmask = 2 * ocnt - 1; - _buckets.length = 2 * ocnt; - for (size_t i = 0; i < ocnt; ++i) - { - auto pp = &_buckets[i]; - while (*pp) - { - auto p = *pp; - - immutable nidx = hashOf(p._key) & nmask; - if (nidx != i) - { - *pp = p._next; - p._next = _buckets[nidx]; - _buckets[nidx] = p; - } - else - { - pp = &p._next; - } - } - } - } - - void shrink() - in - { - assert(_buckets.length >= 2); - } - body - { - immutable ocnt = _buckets.length; - immutable ncnt = ocnt >> 1; - immutable nmask = ncnt - 1; - - for (size_t i = ncnt; i < ocnt; ++i) - { - if (auto tail = _buckets[i]) - { - immutable nidx = i & nmask; - auto pp = &_buckets[nidx]; - while (*pp) - pp = &(*pp)._next; - *pp = tail; - _buckets[i] = null; - } - } - _buckets.length = ncnt; - } - - void ensureNotInOpApply() - { - if (_inOpApply) - assert(0, "Invalid HashTab manipulation during opApply iteration."); - } - - Array!(Node*) _buckets; - size_t _length; - bool _inOpApply; -} - -unittest -{ - HashTab!(int, int) tab; - - foreach (i; 0 .. 100) - tab[i] = 100 - i; - - foreach (i; 0 .. 100) - assert(tab[i] == 100 - i); - - foreach (k, v; tab) - assert(v == 100 - k); - - foreach (i; 0 .. 50) - tab.remove(2 * i); - - assert(tab.length == 50); - - foreach (i; 0 .. 50) - assert(tab[2 * i + 1] == 100 - 2 * i - 1); - - assert(tab.length == 50); - - tab.reset(); - assert(tab.empty); - tab[0] = 0; - assert(!tab.empty); - destroy(tab); - assert(tab.empty); - - // not copyable - static assert(!__traits(compiles, { HashTab!(int, int) tab2 = tab; })); - HashTab!(int, int) tab2; - static assert(!__traits(compiles, tab = tab2)); - static void foo(HashTab!(int, int) copy) {} - static assert(!__traits(compiles, foo(tab))); -} - -unittest -{ - HashTab!(string, size_t) tab; - - tab["foo"] = 0; - assert(tab["foo"] == 0); - ++tab["foo"]; - assert(tab["foo"] == 1); - tab["foo"]++; - assert(tab["foo"] == 2); - - auto s = "fo"; - s ~= "o"; - assert(tab[s] == 2); - assert(tab.length == 1); - tab[s] -= 2; - assert(tab[s] == 0); - tab["foo"] = 12; - assert(tab[s] == 12); - - tab.remove("foo"); - assert(tab.empty); -} - -unittest -{ - alias RC = common.RC!(); - HashTab!(size_t, RC) tab; - - size_t cnt; - assert(cnt == 0); - tab[0] = RC(&cnt); - assert(cnt == 1); - tab[1] = tab[0]; - assert(cnt == 2); - tab.remove(0); - assert(cnt == 1); - tab.remove(1); - assert(cnt == 0); -} - -unittest -{ - import core.exception; - - HashTab!(uint, uint) tab; - foreach (i; 0 .. 5) - tab[i] = i; - bool thrown; - foreach (k, v; tab) - { - try - { - if (k == 3) tab.remove(k); - } - catch (AssertError e) - { - thrown = true; - } - } - assert(thrown); - assert(tab[3] == 3); -} diff --git a/libphobos/libdruntime/rt/util/container/treap.d b/libphobos/libdruntime/rt/util/container/treap.d deleted file mode 100644 index f0c04fd..0000000 --- a/libphobos/libdruntime/rt/util/container/treap.d +++ /dev/null @@ -1,338 +0,0 @@ -/** - * Treap container for internal usage. - * - * Copyright: Copyright Digital Mars 2014 - 2014. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - */ -module rt.util.container.treap; - -static import common = rt.util.container.common; -import rt.util.random; -import rt.qsort; - -struct Treap(E) -{ -nothrow: - static struct Node - { - Node* left, right; - E element; - uint priority; - } - - @disable this(this); - - ~this() - { - removeAll(); - } - - void initialize() - { - rand48.defaultSeed(); - } - - void insert(E element) @nogc - { - root = insert(root, element); - } - - void remove(E element) - { - remove(&root, element); - } - - int opApply(scope int delegate(ref E) nothrow dg) - { - return (cast(const)&this).opApply((ref const E e) => dg(*cast(E*)&e)); - } - - int opApply(scope int delegate(ref const E) nothrow dg) const - { - return opApplyHelper(root, dg); - } - - version (unittest) - bool opEquals(E[] elements) - { - size_t i; - foreach (e; this) - { - if (i >= elements.length) - return false; - if (e != elements[i++]) - return false; - } - return i == elements.length; - } - - void removeAll() - { - removeAll(root); - root = null; - } - - version (unittest) - bool valid() - { - return valid(root); - } - - - version (none) - uint height() - { - static uint height(Node* node) - { - if (!node) - return 0; - auto left = height(node.left); - auto right = height(node.right); - return 1 + (left > right ? left : right); - } - return height(root); - } - - version (none) - size_t count() - { - static size_t count(Node* node) - { - if (!node) - return 0; - return count(node.left) + count(node.right) + 1; - } - return count(root); - } - - -private: - Node* root; - Rand48 rand48; - - Node* allocNode(E element) @nogc - { - Node* node = cast(Node*)common.xmalloc(Node.sizeof); - node.left = node.right = null; - node.priority = rand48(); - node.element = element; - return node; - } - - Node* insert(Node* node, E element) @nogc - { - if (!node) - return allocNode(element); - else if (element < node.element) - { - node.left = insert(node.left, element); - if (node.left.priority < node.priority) - node = rotateR(node); - } - else if (element > node.element) - { - node.right = insert(node.right, element); - if (node.right.priority < node.priority) - node = rotateL(node); - } - else - {} // ignore duplicate - - return node; - } - -static: - - void freeNode(Node* node) - { - common.free(node); - } - - Node* rotateL(Node* root) - { - auto pivot = root.right; - root.right = pivot.left; - pivot.left = root; - return pivot; - } - - Node* rotateR(Node* root) - { - auto pivot = root.left; - root.left = pivot.right; - pivot.right = root; - return pivot; - } - - void remove(Node** ppnode, E element) - { - Node* node = *ppnode; - if (!node) - return; // element not in treap - - if (element < node.element) - { - remove(&node.left, element); - } - else if (element > node.element) - { - remove(&node.right, element); - } - else - { - while (node.left && node.right) - { - if (node.left.priority < node.right.priority) - { - *ppnode = rotateR(node); - ppnode = &(*ppnode).right; - } - else - { - *ppnode = rotateL(node); - ppnode = &(*ppnode).left; - } - } - if (!node.left) - *ppnode = node.right; - else - *ppnode = node.left; - freeNode(node); - } - } - - void removeAll(Node* node) - { - if (!node) - return; - removeAll(node.left); - removeAll(node.right); - freeNode(node); - } - - int opApplyHelper(const Node* node, scope int delegate(ref const E) nothrow dg) - { - if (!node) - return 0; - - int result = opApplyHelper(node.left, dg); - if (result) - return result; - result = dg(node.element); - if (result) - return result; - return opApplyHelper(node.right, dg); - } - - version (unittest) - bool valid(Node* node) - { - if (!node) - return true; - - if (node.left) - { - if (node.left.priority < node.priority) - return false; - if (node.left.element > node.element) - return false; - } - if (node.right) - { - if (node.right.priority < node.priority) - return false; - if (node.right.element < node.element) - return false; - } - return valid(node.left) && valid(node.right); - } -} - -unittest -{ - // randomized unittest for randomized data structure - import /*cstdlib = */core.stdc.stdlib : rand, srand; - import /*ctime = */core.stdc.time : time; - - enum OP { add, remove } - enum initialSize = 1000; - enum randOps = 1000; - - Treap!uint treap; - OP[] ops; - uint[] opdata; - - treap.initialize(); - srand(cast(uint)time(null)); - - uint[] data; -initialLoop: - foreach (i; 0 .. initialSize) - { - data ~= rand(); - treap.insert(data[$-1]); - foreach (e; data[0..$-1]) - if (e == data[$-1]) - { - data = data[0..$-1]; - continue initialLoop; - } - } - _adSort(*cast(void[]*)&data, typeid(data[0])); - assert(treap == data); - assert(treap.valid()); - - for (int i = randOps; i > 0; --i) - { - ops ~= cast(OP)(rand() < uint.max / 2 ? OP.add: OP.remove); - opdata ~= rand(); - } - - foreach (op; ops) - { - if (op == OP.add) - { - treap.insert(opdata[0]); - - size_t i; - for (i = 0; i < data.length; ++i) - if (data[i] >= opdata[0]) - break; - - if (i == data.length || data[i] != opdata[0]) - { // not a duplicate - data.length++; - uint tmp = opdata[0]; - for (; i < data.length; ++i) - { - uint tmp2 = data[i]; - data[i] = tmp; - tmp = tmp2; - } - } - } - else if (!data.length) // nothing to remove - { - opdata = opdata[1..$]; - continue; - } - else - { - uint tmp = data[opdata[0]%data.length]; - treap.remove(tmp); - size_t i; - for (i = 0; data[i] < tmp; ++i) - {} - for (; i < data.length-1; ++i) - data[i] = data[i+1]; - data.length--; - } - assert(treap.valid()); - assert(treap == data); - opdata = opdata[1..$]; - } - - treap.removeAll(); - data.length = 0; - assert(treap == data); -} diff --git a/libphobos/libdruntime/rt/util/random.d b/libphobos/libdruntime/rt/util/random.d deleted file mode 100644 index 69e4cfe..0000000 --- a/libphobos/libdruntime/rt/util/random.d +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Random number generators for internal usage. - * - * Copyright: Copyright Digital Mars 2014. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - */ -module rt.util.random; - -struct Rand48 -{ - private ulong rng_state; - -@safe @nogc nothrow: - - void defaultSeed() - { - import ctime = core.stdc.time : time; - seed(cast(uint)ctime.time(null)); - } - -pure: - - void seed(uint seedval) - { - assert(seedval); - rng_state = cast(ulong)seedval << 16 | 0x330e; - popFront(); - } - - auto opCall() - { - auto result = front; - popFront(); - return result; - } - - @property uint front() - { - return cast(uint)(rng_state >> 16); - } - - void popFront() - { - immutable ulong a = 25214903917; - immutable ulong c = 11; - immutable ulong m_mask = (1uL << 48uL) - 1; - rng_state = (a*rng_state+c) & m_mask; - } - - enum empty = false; -} diff --git a/libphobos/libdruntime/rt/util/typeinfo.d b/libphobos/libdruntime/rt/util/typeinfo.d index 31770a0..d06254c 100644 --- a/libphobos/libdruntime/rt/util/typeinfo.d +++ b/libphobos/libdruntime/rt/util/typeinfo.d @@ -4,8 +4,10 @@ * Copyright: Copyright Kenji Hara 2014-. * License: Boost License 1.0. * Authors: Kenji Hara + * Source: $(DRUNTIMESRC rt/util/_typeinfo.d) */ module rt.util.typeinfo; +import rt.util.utility : d_cfloat, d_cdouble, d_creal, isComplex; static import core.internal.hash; template Floating(T) @@ -35,14 +37,16 @@ if (is(T == float) || is(T == double) || is(T == real)) public alias hashOf = core.internal.hash.hashOf; } + +// @@@DEPRECATED_2.105@@@ template Floating(T) -if (is(T == cfloat) || is(T == cdouble) || is(T == creal)) +if (isComplex!T) { pure nothrow @safe: bool equals(T f1, T f2) { - return f1 == f2; + return f1.re == f2.re && f1.im == f2.im; } int compare(T f1, T f2) @@ -62,12 +66,14 @@ if (is(T == cfloat) || is(T == cdouble) || is(T == creal)) return result; } - public alias hashOf = core.internal.hash.hashOf; + size_t hashOf(scope const T val) + { + return core.internal.hash.hashOf(val.re, core.internal.hash.hashOf(val.im)); + } } template Array(T) -if (is(T == float) || is(T == double) || is(T == real) || - is(T == cfloat) || is(T == cdouble) || is(T == creal)) +if (is(T == float) || is(T == double) || is(T == real)) { pure nothrow @safe: @@ -94,17 +100,56 @@ if (is(T == float) || is(T == double) || is(T == real) || if (int c = Floating!T.compare(s1[u], s2[u])) return c; } - if (s1.length < s2.length) - return -1; - else if (s1.length > s2.length) - return 1; - return 0; + return (s1.length > s2.length) - (s1.length < s2.length); } public alias hashOf = core.internal.hash.hashOf; } -version (unittest) +// @@@DEPRECATED_2.105@@@ +template Array(T) +if (isComplex!T) +{ + pure nothrow @safe: + + bool equals(T[] s1, T[] s2) + { + size_t len = s1.length; + if (len != s2.length) + return false; + for (size_t u = 0; u < len; u++) + { + if (!Floating!T.equals(s1[u], s2[u])) + return false; + } + return true; + } + + int compare(T[] s1, T[] s2) + { + size_t len = s1.length; + if (s2.length < len) + len = s2.length; + for (size_t u = 0; u < len; u++) + { + if (int c = Floating!T.compare(s1[u], s2[u])) + return c; + } + return (s1.length > s2.length) - (s1.length < s2.length); + } + + size_t hashOf(scope const T[] val) + { + size_t hash = 0; + foreach (ref o; val) + { + hash = core.internal.hash.hashOf(Floating!T.hashOf(o), hash); + } + return hash; + } +} + +version (CoreUnittest) { alias TypeTuple(T...) = T; } @@ -162,109 +207,6 @@ unittest ti = typeid(S[3]); assert(ti.getHash(&sa1) == ti.getHash(&sa2)); }(); - - // imaginary types - foreach (F; TypeTuple!(ifloat, idouble, ireal)) - (){ // workaround #2396 - alias S = SX!F; - F f1 = +0.0i, - f2 = -0.0i; - - assert(f1 == f2); - assert(f1 !is f2); - ti = typeid(F); - assert(ti.getHash(&f1) == ti.getHash(&f2)); - - F[] a1 = [f1, f1, f1]; - F[] a2 = [f2, f2, f2]; - assert(a1 == a2); - assert(a1 !is a2); - ti = typeid(F[]); - assert(ti.getHash(&a1) == ti.getHash(&a2)); - - F[][] aa1 = [a1, a1, a1]; - F[][] aa2 = [a2, a2, a2]; - assert(aa1 == aa2); - assert(aa1 !is aa2); - ti = typeid(F[][]); - assert(ti.getHash(&aa1) == ti.getHash(&aa2)); - - S s1 = {f1}, - s2 = {f2}; - assert(s1 == s2); - assert(s1 !is s2); - ti = typeid(S); - assert(ti.getHash(&s1) == ti.getHash(&s2)); - - S[] da1 = [S(f1), S(f1), S(f1)], - da2 = [S(f2), S(f2), S(f2)]; - assert(da1 == da2); - assert(da1 !is da2); - ti = typeid(S[]); - assert(ti.getHash(&da1) == ti.getHash(&da2)); - - S[3] sa1 = {f1}, - sa2 = {f2}; - assert(sa1 == sa2); - assert(sa1[] !is sa2[]); - ti = typeid(S[3]); - assert(ti.getHash(&sa1) == ti.getHash(&sa2)); - }(); - - // complex types - foreach (F; TypeTuple!(cfloat, cdouble, creal)) - (){ // workaround #2396 - alias S = SX!F; - F[4] f = [+0.0 + 0.0i, - +0.0 - 0.0i, - -0.0 + 0.0i, - -0.0 - 0.0i]; - - foreach (i, f1; f) foreach (j, f2; f) if (i != j) - { - assert(f1 == 0 + 0i); - - assert(f1 == f2); - assert(f1 !is f2); - ti = typeid(F); - assert(ti.getHash(&f1) == ti.getHash(&f2)); - - F[] a1 = [f1, f1, f1]; - F[] a2 = [f2, f2, f2]; - assert(a1 == a2); - assert(a1 !is a2); - ti = typeid(F[]); - assert(ti.getHash(&a1) == ti.getHash(&a2)); - - F[][] aa1 = [a1, a1, a1]; - F[][] aa2 = [a2, a2, a2]; - assert(aa1 == aa2); - assert(aa1 !is aa2); - ti = typeid(F[][]); - assert(ti.getHash(&aa1) == ti.getHash(&aa2)); - - S s1 = {f1}, - s2 = {f2}; - assert(s1 == s2); - assert(s1 !is s2); - ti = typeid(S); - assert(ti.getHash(&s1) == ti.getHash(&s2)); - - S[] da1 = [S(f1), S(f1), S(f1)], - da2 = [S(f2), S(f2), S(f2)]; - assert(da1 == da2); - assert(da1 !is da2); - ti = typeid(S[]); - assert(ti.getHash(&da1) == ti.getHash(&da2)); - - S[3] sa1 = {f1}, - sa2 = {f2}; - assert(sa1 == sa2); - assert(sa1[] !is sa2[]); - ti = typeid(S[3]); - assert(ti.getHash(&sa1) == ti.getHash(&sa2)); - } - }(); } // Reduces to `T` if `cond` is `true` or `U` otherwise. @@ -279,7 +221,7 @@ TypeInfo information for built-in types. A `Base` type may be specified, which must be a type with the same layout, alignment, hashing, and equality comparison as type `T`. This saves on code size because parts of `Base` will be reused. Example: -`float` and `ifloat` or `char` and `ubyte`. The implementation assumes `Base` and `T` hash the same, swap +`char` and `ubyte`. The implementation assumes `Base` and `T` hash the same, swap the same, have the same ABI flags, and compare the same for equality. For ordering comparisons, we detect during compilation whether they have different signedness and override appropriately. For initializer, we detect if we need to override. The overriding initializer should be nonzero. @@ -296,7 +238,7 @@ if (T.sizeof == Base.sizeof && T.alignof == Base.alignof) static if (is(T == Base)) override size_t getHash(scope const void* p) { - static if (__traits(isFloating, T)) + static if (__traits(isFloating, T) || isComplex!T) return Floating!T.hashOf(*cast(T*)p); else return hashOf(*cast(const T *)p); @@ -306,7 +248,7 @@ if (T.sizeof == Base.sizeof && T.alignof == Base.alignof) static if (is(T == Base)) override bool equals(in void* p1, in void* p2) { - static if (__traits(isFloating, T)) + static if (__traits(isFloating, T) || isComplex!T) return Floating!T.equals(*cast(T*)p1, *cast(T*)p2); else return *cast(T *)p1 == *cast(T *)p2; @@ -316,7 +258,7 @@ if (T.sizeof == Base.sizeof && T.alignof == Base.alignof) static if (is(T == Base) || (__traits(isIntegral, T) && T.max != Base.max)) override int compare(in void* p1, in void* p2) { - static if (__traits(isFloating, T)) + static if (__traits(isFloating, T) || isComplex!T) { return Floating!T.compare(*cast(T*)p1, *cast(T*)p2); } @@ -375,9 +317,12 @@ if (T.sizeof == Base.sizeof && T.alignof == Base.alignof) } static if (is(T == Base)) - static if (__traits(isFloating, T) && T.mant_dig != 64) + { + static if ((__traits(isFloating, T) && T.mant_dig != 64) || + (isComplex!T && T.re.mant_dig != 64)) // FP types except 80-bit X87 are passed in SIMD register. override @property uint flags() const { return 2; } + } } unittest @@ -414,7 +359,7 @@ TypeInfo information for arrays of built-in types. A `Base` type may be specified, which must be a type with the same layout, alignment, hashing, and equality comparison as type `T`. This saves on code size because parts of `Base` will be reused. Example: -`float` and `ifloat` or `char` and `ubyte`. The implementation assumes `Base` and `T` hash the same, swap +`char` and `ubyte`. The implementation assumes `Base` and `T` hash the same, swap the same, have the same ABI flags, and compare the same for equality. For ordering comparisons, we detect during compilation whether they have different signedness and override appropriately. For initializer, we detect if we need to override. The overriding initializer should be nonzero. @@ -429,7 +374,7 @@ private class TypeInfoArrayGeneric(T, Base = T) : Select!(is(T == Base), TypeInf static if (is(T == Base)) override size_t getHash(scope const void* p) @trusted const { - static if (__traits(isFloating, T)) + static if (__traits(isFloating, T) || isComplex!T) return Array!T.hashOf(*cast(T[]*)p); else return hashOf(*cast(const T[]*) p); @@ -438,7 +383,7 @@ private class TypeInfoArrayGeneric(T, Base = T) : Select!(is(T == Base), TypeInf static if (is(T == Base)) override bool equals(in void* p1, in void* p2) const { - static if (__traits(isFloating, T)) + static if (__traits(isFloating, T) || isComplex!T) { return Array!T.equals(*cast(T[]*)p1, *cast(T[]*)p2); } @@ -455,7 +400,7 @@ private class TypeInfoArrayGeneric(T, Base = T) : Select!(is(T == Base), TypeInf static if (is(T == Base) || (__traits(isIntegral, T) && T.max != Base.max)) override int compare(in void* p1, in void* p2) const { - static if (__traits(isFloating, T)) + static if (__traits(isFloating, T) || isComplex!T) { return Array!T.compare(*cast(T[]*)p1, *cast(T[]*)p2); } @@ -519,12 +464,12 @@ class TypeInfo_v : TypeInfoGeneric!ubyte { return 1; } +} - unittest - { - assert(typeid(void).toString == "void"); - assert(typeid(void).flags == 1); - } +unittest +{ + assert(typeid(void).toString == "void"); + assert(typeid(void).flags == 1); } // All integrals. @@ -545,17 +490,36 @@ static if (is(ucent)) class TypeInfo_zk : TypeInfoGeneric!ucent {} // All simple floating-point types. class TypeInfo_f : TypeInfoGeneric!float {} -class TypeInfo_o : TypeInfoGeneric!(ifloat, float) {} class TypeInfo_d : TypeInfoGeneric!double {} -class TypeInfo_p : TypeInfoGeneric!(idouble, double) {} class TypeInfo_e : TypeInfoGeneric!real {} -class TypeInfo_j : TypeInfoGeneric!(ireal, real) {} + +// All imaginary floating-point types. + +// ifloat @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_o : TypeInfoGeneric!float +{ + override string toString() const pure nothrow @safe { return "ifloat"; } +} + +// idouble @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_p : TypeInfoGeneric!double +{ + override string toString() const pure nothrow @safe { return "idouble"; } +} + +// ireal @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_j : TypeInfoGeneric!real +{ + override string toString() const pure nothrow @safe { return "ireal"; } +} // All complex floating-point types. -// cfloat -class TypeInfo_q : TypeInfoGeneric!cfloat +// cfloat @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_q : TypeInfoGeneric!d_cfloat { + override string toString() const pure nothrow @safe { return "cfloat"; } + const: nothrow: pure: @trusted: static if (__traits(hasMember, TypeInfo, "argTypes")) override int argTypes(out TypeInfo arg1, out TypeInfo arg2) @@ -565,9 +529,11 @@ class TypeInfo_q : TypeInfoGeneric!cfloat } } -// cdouble -class TypeInfo_r : TypeInfoGeneric!cdouble +// cdouble @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_r : TypeInfoGeneric!d_cdouble { + override string toString() const pure nothrow @safe { return "cdouble"; } + const: nothrow: pure: @trusted: static if (__traits(hasMember, TypeInfo, "argTypes")) override int argTypes(out TypeInfo arg1, out TypeInfo arg2) @@ -578,9 +544,11 @@ class TypeInfo_r : TypeInfoGeneric!cdouble } } -// creal -class TypeInfo_c : TypeInfoGeneric!creal +// creal @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_c : TypeInfoGeneric!d_creal { + override string toString() const pure nothrow @safe { return "creal"; } + const: nothrow: pure: @trusted: static if (__traits(hasMember, TypeInfo, "argTypes")) override int argTypes(out TypeInfo arg1, out TypeInfo arg2) @@ -591,18 +559,6 @@ class TypeInfo_c : TypeInfoGeneric!creal } } -static if (__traits(hasMember, TypeInfo, "argTypes")) - unittest - { - TypeInfo t1, t2; - assert(typeid(cfloat).argTypes(t1, t2) == 0 && t1 == typeid(double) && - t2 is null); - assert(typeid(cdouble).argTypes(t1, t2) == 0 && t1 == typeid(double) && - t2 == typeid(double)); - assert(typeid(creal).argTypes(t1, t2) == 0 && t1 == typeid(real) && - t2 == typeid(real)); - } - // Arrays of all integrals. class TypeInfo_Ah : TypeInfoArrayGeneric!ubyte {} class TypeInfo_Ab : TypeInfoArrayGeneric!(bool, ubyte) {} @@ -623,7 +579,7 @@ class TypeInfo_Aw : TypeInfoArrayGeneric!(dchar, uint) {} class TypeInfo_Am : TypeInfoArrayGeneric!ulong {} class TypeInfo_Al : TypeInfoArrayGeneric!(long, ulong) {} -version (unittest) +version (CoreUnittest) private extern (C) void[] _adSort(void[] a, TypeInfo ti); unittest @@ -662,16 +618,50 @@ unittest assert(!(a1 < b1 && b1 < a1)); // Original failing case } -// Arrays of all floating point types. +// Arrays of all simple floating-point types. class TypeInfo_Af : TypeInfoArrayGeneric!float {} -class TypeInfo_Ao : TypeInfoArrayGeneric!(ifloat, float) {} class TypeInfo_Ad : TypeInfoArrayGeneric!double {} -class TypeInfo_Ap : TypeInfoArrayGeneric!(idouble, double) {} class TypeInfo_Ae : TypeInfoArrayGeneric!real {} -class TypeInfo_Aj : TypeInfoArrayGeneric!(ireal, real) {} -class TypeInfo_Aq : TypeInfoArrayGeneric!cfloat {} -class TypeInfo_Ar : TypeInfoArrayGeneric!cdouble {} -class TypeInfo_Ac : TypeInfoArrayGeneric!creal {} + +// Arrays of all imaginary floating-point types. + +// ifloat @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_Ao : TypeInfoArrayGeneric!float +{ + override string toString() const pure nothrow @safe { return "ifloat[]"; } +} + +// idouble @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_Ap : TypeInfoArrayGeneric!double +{ + override string toString() const pure nothrow @safe { return "idouble[]"; } +} + +// ireal @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_Aj : TypeInfoArrayGeneric!real +{ + override string toString() const pure nothrow @safe { return "ireal[]"; } +} + +// Arrays of all complex floating-point types. + +// cfloat @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_Aq : TypeInfoArrayGeneric!d_cfloat +{ + override string toString() const pure nothrow @safe { return "cfloat[]"; } +} + +// cdouble @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_Ar : TypeInfoArrayGeneric!d_cdouble +{ + override string toString() const pure nothrow @safe { return "cdouble[]"; } +} + +// creal @@@DEPRECATED_2.105@@@ +deprecated class TypeInfo_Ac : TypeInfoArrayGeneric!d_creal +{ + override string toString() const pure nothrow @safe { return "creal[]"; } +} // void[] is a bit different, behaves like ubyte[] for comparison purposes. class TypeInfo_Av : TypeInfo_Ah diff --git a/libphobos/libdruntime/rt/util/utf.d b/libphobos/libdruntime/rt/util/utf.d deleted file mode 100644 index 55869b3..0000000 --- a/libphobos/libdruntime/rt/util/utf.d +++ /dev/null @@ -1,920 +0,0 @@ -/******************************************** - * Encode and decode UTF-8, UTF-16 and UTF-32 strings. - * - * For Win32 systems, the C wchar_t type is UTF-16 and corresponds to the D - * wchar type. - * For Posix systems, the C wchar_t type is UTF-32 and corresponds to - * the D utf.dchar type. - * - * UTF character support is restricted to (\u0000 <= character <= \U0010FFFF). - * - * See_Also: - * $(LINK2 http://en.wikipedia.org/wiki/Unicode, Wikipedia)
- * $(LINK http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8)
- * $(LINK http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335) - * - * Copyright: Copyright Digital Mars 2003 - 2016. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, Sean Kelly - * Source: $(DRUNTIMESRC src/rt/util/_utf.d) - */ - -module rt.util.utf; - -extern (C) void onUnicodeError( string msg, size_t idx, string file = __FILE__, size_t line = __LINE__ ) @safe pure; - -/******************************* - * Test if c is a valid UTF-32 character. - * - * \uFFFE and \uFFFF are considered valid by this function, - * as they are permitted for internal use by an application, - * but they are not allowed for interchange by the Unicode standard. - * - * Returns: true if it is, false if not. - */ - -@safe @nogc pure nothrow -bool isValidDchar(dchar c) -{ - /* Note: FFFE and FFFF are specifically permitted by the - * Unicode standard for application internal use, but are not - * allowed for interchange. - * (thanks to Arcane Jill) - */ - - return c < 0xD800 || - (c > 0xDFFF && c <= 0x10FFFF /*&& c != 0xFFFE && c != 0xFFFF*/); -} - -unittest -{ - debug(utf) printf("utf.isValidDchar.unittest\n"); - assert(isValidDchar(cast(dchar)'a') == true); - assert(isValidDchar(cast(dchar)0x1FFFFF) == false); -} - - - -static immutable UTF8stride = -[ - cast(ubyte) - 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, - 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, - 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, - 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, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 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,0xFF,0xFF, -]; - -/** - * stride() returns the length of a UTF-8 sequence starting at index i - * in string s. - * Returns: - * The number of bytes in the UTF-8 sequence or - * 0xFF meaning s[i] is not the start of of UTF-8 sequence. - */ -@safe @nogc pure nothrow -uint stride(in char[] s, size_t i) -{ - return UTF8stride[s[i]]; -} - -/** - * stride() returns the length of a UTF-16 sequence starting at index i - * in string s. - */ -@safe @nogc pure nothrow -uint stride(in wchar[] s, size_t i) -{ uint u = s[i]; - return 1 + (u >= 0xD800 && u <= 0xDBFF); -} - -/** - * stride() returns the length of a UTF-32 sequence starting at index i - * in string s. - * Returns: The return value will always be 1. - */ -@safe @nogc pure nothrow -uint stride(in dchar[] s, size_t i) -{ - return 1; -} - -/******************************************* - * Given an index i into an array of characters s[], - * and assuming that index i is at the start of a UTF character, - * determine the number of UCS characters up to that index i. - */ -@safe pure -size_t toUCSindex(in char[] s, size_t i) -{ - size_t n; - size_t j; - - for (j = 0; j < i; ) - { - j += stride(s, j); - n++; - } - if (j > i) - { - onUnicodeError("invalid UTF-8 sequence", j); - } - return n; -} - -/** ditto */ -@safe pure -size_t toUCSindex(in wchar[] s, size_t i) -{ - size_t n; - size_t j; - - for (j = 0; j < i; ) - { - j += stride(s, j); - n++; - } - if (j > i) - { - onUnicodeError("invalid UTF-16 sequence", j); - } - return n; -} - -/** ditto */ -@safe @nogc pure nothrow -size_t toUCSindex(in dchar[] s, size_t i) -{ - return i; -} - -/****************************************** - * Given a UCS index n into an array of characters s[], return the UTF index. - */ -@safe pure -size_t toUTFindex(in char[] s, size_t n) -{ - size_t i; - - while (n--) - { - uint j = UTF8stride[s[i]]; - if (j == 0xFF) - onUnicodeError("invalid UTF-8 sequence", i); - i += j; - } - return i; -} - -/** ditto */ -@safe @nogc pure nothrow -size_t toUTFindex(in wchar[] s, size_t n) -{ - size_t i; - - while (n--) - { wchar u = s[i]; - - i += 1 + (u >= 0xD800 && u <= 0xDBFF); - } - return i; -} - -/** ditto */ -@safe @nogc pure nothrow -size_t toUTFindex(in dchar[] s, size_t n) -{ - return n; -} - -/* =================== Decode ======================= */ - -/*************** - * Decodes and returns character starting at s[idx]. idx is advanced past the - * decoded character. If the character is not well formed, a UtfException is - * thrown and idx remains unchanged. - */ -@safe pure -dchar decode(in char[] s, ref size_t idx) - in - { - assert(idx >= 0 && idx < s.length); - } - out (result) - { - assert(isValidDchar(result)); - } - body - { - size_t len = s.length; - dchar V; - size_t i = idx; - char u = s[i]; - - if (u & 0x80) - { uint n; - char u2; - - /* 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 - */ - for (n = 1; ; n++) - { - if (n > 4) - goto Lerr; // only do the first 4 of 6 encodings - if (((u << n) & 0x80) == 0) - { - if (n == 1) - goto Lerr; - break; - } - } - - // Pick off (7 - n) significant bits of B from first byte of octet - V = cast(dchar)(u & ((1 << (7 - n)) - 1)); - - if (i + (n - 1) >= len) - goto Lerr; // off end of string - - /* The following combinations are overlong, and illegal: - * 1100000x (10xxxxxx) - * 11100000 100xxxxx (10xxxxxx) - * 11110000 1000xxxx (10xxxxxx 10xxxxxx) - * 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) - * 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) - */ - u2 = s[i + 1]; - if ((u & 0xFE) == 0xC0 || - (u == 0xE0 && (u2 & 0xE0) == 0x80) || - (u == 0xF0 && (u2 & 0xF0) == 0x80) || - (u == 0xF8 && (u2 & 0xF8) == 0x80) || - (u == 0xFC && (u2 & 0xFC) == 0x80)) - goto Lerr; // overlong combination - - for (uint j = 1; j != n; j++) - { - u = s[i + j]; - if ((u & 0xC0) != 0x80) - goto Lerr; // trailing bytes are 10xxxxxx - V = (V << 6) | (u & 0x3F); - } - if (!isValidDchar(V)) - goto Lerr; - i += n; - } - else - { - V = cast(dchar) u; - i++; - } - - idx = i; - return V; - - Lerr: - onUnicodeError("invalid UTF-8 sequence", i); - return V; // dummy return - } - -unittest -{ size_t i; - dchar c; - - debug(utf) printf("utf.decode.unittest\n"); - - static s1 = "abcd"c; - i = 0; - c = decode(s1, i); - assert(c == cast(dchar)'a'); - assert(i == 1); - c = decode(s1, i); - assert(c == cast(dchar)'b'); - assert(i == 2); - - static s2 = "\xC2\xA9"c; - i = 0; - c = decode(s2, i); - assert(c == cast(dchar)'\u00A9'); - assert(i == 2); - - static s3 = "\xE2\x89\xA0"c; - i = 0; - c = decode(s3, i); - assert(c == cast(dchar)'\u2260'); - assert(i == 3); - - static s4 = - [ "\xE2\x89"c[], // too short - "\xC0\x8A", - "\xE0\x80\x8A", - "\xF0\x80\x80\x8A", - "\xF8\x80\x80\x80\x8A", - "\xFC\x80\x80\x80\x80\x8A", - ]; - - for (int j = 0; j < s4.length; j++) - { - try - { - i = 0; - c = decode(s4[j], i); - assert(0); - } - catch (Throwable o) - { - i = 23; - } - assert(i == 23); - } -} - -/** ditto */ -@safe pure -dchar decode(in wchar[] s, ref size_t idx) - in - { - assert(idx >= 0 && idx < s.length); - } - out (result) - { - assert(isValidDchar(result)); - } - body - { - string msg; - dchar V; - size_t i = idx; - uint u = s[i]; - - if (u & ~0x7F) - { if (u >= 0xD800 && u <= 0xDBFF) - { uint u2; - - if (i + 1 == s.length) - { msg = "surrogate UTF-16 high value past end of string"; - goto Lerr; - } - u2 = s[i + 1]; - if (u2 < 0xDC00 || u2 > 0xDFFF) - { msg = "surrogate UTF-16 low value out of range"; - goto Lerr; - } - u = ((u - 0xD7C0) << 10) + (u2 - 0xDC00); - i += 2; - } - else if (u >= 0xDC00 && u <= 0xDFFF) - { msg = "unpaired surrogate UTF-16 value"; - goto Lerr; - } - else if (u == 0xFFFE || u == 0xFFFF) - { msg = "illegal UTF-16 value"; - goto Lerr; - } - else - i++; - } - else - { - i++; - } - - idx = i; - return cast(dchar)u; - - Lerr: - onUnicodeError(msg, i); - return cast(dchar)u; // dummy return - } - -/** ditto */ -@safe pure -dchar decode(in dchar[] s, ref size_t idx) - in - { - assert(idx >= 0 && idx < s.length); - } - body - { - size_t i = idx; - dchar c = s[i]; - - if (!isValidDchar(c)) - goto Lerr; - idx = i + 1; - return c; - - Lerr: - onUnicodeError("invalid UTF-32 value", i); - return c; // dummy return - } - - -/* =================== Encode ======================= */ - -/******************************* - * Encodes character c and appends it to array s[]. - */ -@safe pure nothrow -void encode(ref char[] s, dchar c) - in - { - assert(isValidDchar(c)); - } - body - { - char[] r = s; - - if (c <= 0x7F) - { - r ~= cast(char) c; - } - else - { - char[4] buf; - uint L; - - if (c <= 0x7FF) - { - buf[0] = cast(char)(0xC0 | (c >> 6)); - buf[1] = cast(char)(0x80 | (c & 0x3F)); - L = 2; - } - else if (c <= 0xFFFF) - { - 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) - { - 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(0); - } - r ~= buf[0 .. L]; - } - s = r; - } - -unittest -{ - debug(utf) printf("utf.encode.unittest\n"); - - 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"); -} - -/** ditto */ -@safe pure nothrow -void encode(ref wchar[] s, dchar c) - in - { - assert(isValidDchar(c)); - } - body - { - wchar[] r = s; - - if (c <= 0xFFFF) - { - r ~= cast(wchar) c; - } - else - { - wchar[2] buf; - - buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); - buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); - r ~= buf; - } - s = r; - } - -/** ditto */ -@safe pure nothrow -void encode(ref dchar[] s, dchar c) - in - { - assert(isValidDchar(c)); - } - body - { - s ~= c; - } - -/** -Returns the code length of $(D c) in the encoding using $(D C) as a -code point. The code is returned in character count, not in bytes. - */ -@safe pure nothrow @nogc -ubyte codeLength(C)(dchar 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; - } -} - -/* =================== Validation ======================= */ - -/*********************************** -Checks to see if string is well formed or not. $(D S) can be an array - of $(D char), $(D wchar), or $(D dchar). Throws a $(D UtfException) - if it is not. Use to check all untrusted input for correctness. - */ -@safe pure -void validate(S)(in S s) -{ - auto len = s.length; - for (size_t i = 0; i < len; ) - { - decode(s, i); - } -} - -/* =================== Conversion to UTF8 ======================= */ - -@safe pure nothrow @nogc -char[] toUTF8(char[] buf, dchar c) - in - { - assert(isValidDchar(c)); - } - body - { - if (c <= 0x7F) - { - buf[0] = cast(char) c; - return buf[0 .. 1]; - } - else if (c <= 0x7FF) - { - buf[0] = cast(char)(0xC0 | (c >> 6)); - buf[1] = cast(char)(0x80 | (c & 0x3F)); - return buf[0 .. 2]; - } - else if (c <= 0xFFFF) - { - buf[0] = cast(char)(0xE0 | (c >> 12)); - buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); - buf[2] = cast(char)(0x80 | (c & 0x3F)); - return buf[0 .. 3]; - } - else if (c <= 0x10FFFF) - { - 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 buf[0 .. 4]; - } - assert(0); - } - -/******************* - * Encodes string s into UTF-8 and returns the encoded string. - */ -@safe pure nothrow -string toUTF8(string s) - in - { - validate(s); - } - body - { - return s; - } - -/** ditto */ -@trusted pure -string toUTF8(in wchar[] s) -{ - char[] r; - size_t i; - size_t slen = s.length; - - r.length = slen; - - for (i = 0; i < slen; i++) - { wchar c = s[i]; - - if (c <= 0x7F) - r[i] = cast(char)c; // fast path for ascii - else - { - r.length = i; - foreach (dchar ch; s[i .. slen]) - { - encode(r, ch); - } - break; - } - } - return cast(string)r; -} - -/** ditto */ -@trusted pure -string toUTF8(in dchar[] s) -{ - char[] r; - size_t i; - size_t slen = s.length; - - r.length = slen; - - for (i = 0; i < slen; i++) - { dchar c = s[i]; - - if (c <= 0x7F) - r[i] = cast(char)c; // fast path for ascii - else - { - r.length = i; - foreach (dchar d; s[i .. slen]) - { - encode(r, d); - } - break; - } - } - return cast(string)r; -} - -/* =================== Conversion to UTF16 ======================= */ - -@safe pure nothrow @nogc -wchar[] toUTF16(wchar[] buf, dchar c) - in - { - assert(isValidDchar(c)); - } - body - { - if (c <= 0xFFFF) - { - buf[0] = cast(wchar) c; - return buf[0 .. 1]; - } - else - { - buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); - buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); - return buf[0 .. 2]; - } - } - -/**************** - * Encodes string s into UTF-16 and returns the encoded string. - * toUTF16z() is suitable for calling the 'W' functions in the Win32 API that take - * an LPWSTR or LPCWSTR argument. - */ -@trusted pure -wstring toUTF16(in char[] s) -{ - wchar[] r; - size_t slen = s.length; - - r.length = slen; - r.length = 0; - for (size_t i = 0; i < slen; ) - { - dchar c = s[i]; - if (c <= 0x7F) - { - i++; - r ~= cast(wchar)c; - } - else - { - c = decode(s, i); - encode(r, c); - } - } - return cast(wstring)r; -} - -alias const(wchar)* wptr; -/** ditto */ -@safe pure -wptr toUTF16z(in char[] s) -{ - wchar[] r; - size_t slen = s.length; - - r.length = slen + 1; - r.length = 0; - for (size_t i = 0; i < slen; ) - { - dchar c = s[i]; - if (c <= 0x7F) - { - i++; - r ~= cast(wchar)c; - } - else - { - c = decode(s, i); - encode(r, c); - } - } - r ~= '\000'; - return &r[0]; -} - -/** ditto */ -@safe pure nothrow -wstring toUTF16(wstring s) - in - { - validate(s); - } - body - { - return s; - } - -/** ditto */ -@trusted pure nothrow -wstring toUTF16(in dchar[] s) -{ - wchar[] r; - size_t slen = s.length; - - r.length = slen; - r.length = 0; - for (size_t i = 0; i < slen; i++) - { - encode(r, s[i]); - } - return cast(wstring)r; -} - -/* =================== Conversion to UTF32 ======================= */ - -/***** - * Encodes string s into UTF-32 and returns the encoded string. - */ -@trusted pure -dstring toUTF32(in char[] s) -{ - dchar[] r; - size_t slen = s.length; - size_t j = 0; - - r.length = slen; // r[] will never be longer than s[] - for (size_t i = 0; i < slen; ) - { - dchar c = s[i]; - if (c >= 0x80) - c = decode(s, i); - else - i++; // c is ascii, no need for decode - r[j++] = c; - } - return cast(dstring)r[0 .. j]; -} - -/** ditto */ -@trusted pure -dstring toUTF32(in wchar[] s) -{ - dchar[] r; - size_t slen = s.length; - size_t j = 0; - - r.length = slen; // r[] will never be longer than s[] - for (size_t i = 0; i < slen; ) - { - dchar c = s[i]; - if (c >= 0x80) - c = decode(s, i); - else - i++; // c is ascii, no need for decode - r[j++] = c; - } - return cast(dstring)r[0 .. j]; -} - -/** ditto */ -@safe pure nothrow -dstring toUTF32(dstring s) - in - { - validate(s); - } - body - { - return s; - } - -/* ================================ tests ================================== */ - -unittest -{ - debug(utf) printf("utf.toUTF.unittest\n"); - - auto c = "hello"c[]; - auto w = toUTF16(c); - assert(w == "hello"); - auto d = toUTF32(c); - assert(d == "hello"); - - c = toUTF8(w); - assert(c == "hello"); - d = toUTF32(w); - assert(d == "hello"); - - c = toUTF8(d); - assert(c == "hello"); - w = toUTF16(d); - assert(w == "hello"); - - - c = "hel\u1234o"; - w = toUTF16(c); - assert(w == "hel\u1234o"); - d = toUTF32(c); - assert(d == "hel\u1234o"); - - c = toUTF8(w); - assert(c == "hel\u1234o"); - d = toUTF32(w); - assert(d == "hel\u1234o"); - - c = toUTF8(d); - assert(c == "hel\u1234o"); - w = toUTF16(d); - assert(w == "hel\u1234o"); - - - c = "he\U000BAAAAllo"; - w = toUTF16(c); - //foreach (wchar c; w) printf("c = x%x\n", c); - //foreach (wchar c; cast(wstring)"he\U000BAAAAllo") printf("c = x%x\n", c); - assert(w == "he\U000BAAAAllo"); - d = toUTF32(c); - assert(d == "he\U000BAAAAllo"); - - c = toUTF8(w); - assert(c == "he\U000BAAAAllo"); - d = toUTF32(w); - assert(d == "he\U000BAAAAllo"); - - c = toUTF8(d); - assert(c == "he\U000BAAAAllo"); - w = toUTF16(d); - assert(w == "he\U000BAAAAllo"); - - wchar[2] buf; - auto ret = toUTF16(buf, '\U000BAAAA'); - assert(ret == "\U000BAAAA"); -} diff --git a/libphobos/libdruntime/rt/util/utility.d b/libphobos/libdruntime/rt/util/utility.d new file mode 100644 index 0000000..b1796fd --- /dev/null +++ b/libphobos/libdruntime/rt/util/utility.d @@ -0,0 +1,44 @@ +/** + * Contains various utility functions used by the runtime implementation. + * + * Copyright: Copyright Digital Mars 2016. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Jacob Carlborg + * Source: $(DRUNTIMESRC rt/util/_utility.d) + */ +module rt.util.utility; + +/** + * Asserts that the given condition is `true`. + * + * The assertion is independent from -release, by abort()ing. Regular assertions + * throw an AssertError and thus require an initialized GC, which might not be + * the case (yet or anymore) for the startup/shutdown code in this package + * (called by CRT ctors/dtors etc.). + */ +package(rt) void safeAssert( + bool condition, scope string msg, scope string file = __FILE__, size_t line = __LINE__ +) nothrow @nogc @safe +{ + import core.internal.abort; + condition || abort(msg, file, line); +} + +// @@@DEPRECATED_2.105@@@ +// Remove this when complex types have been removed from the language. +package(rt) +{ + private struct _Complex(T) { T re; T im; } + + enum __c_complex_float : _Complex!float; + enum __c_complex_double : _Complex!double; + enum __c_complex_real : _Complex!real; // This is why we don't use stdc.config + + alias d_cfloat = __c_complex_float; + alias d_cdouble = __c_complex_double; + alias d_creal = __c_complex_real; + + enum isComplex(T) = is(T == d_cfloat) || is(T == d_cdouble) || is(T == d_creal); +} diff --git a/libphobos/src/MERGE b/libphobos/src/MERGE index 01cf594..927d3ee 100644 --- a/libphobos/src/MERGE +++ b/libphobos/src/MERGE @@ -1,4 +1,4 @@ -55bb17543138a87c376a84745f2a30ec00bdecd9 +5ab9ad2561cea35ac33ebc0452c0e6a8d762f27d The first line of this file holds the git revision number of the last merge done from the dlang/phobos repository. diff --git a/libphobos/src/Makefile.am b/libphobos/src/Makefile.am index 9f62510..ba1579d 100644 --- a/libphobos/src/Makefile.am +++ b/libphobos/src/Makefile.am @@ -19,7 +19,7 @@ include $(top_srcdir)/d_rules.am # Make sure GDC can find libdruntime and libphobos include files -D_EXTRA_DFLAGS=-nostdinc -I $(srcdir) \ +D_EXTRA_DFLAGS=-fpreview=dip1000 -fpreview=dtorfields -nostdinc -I $(srcdir) \ -I $(top_srcdir)/libdruntime -I ../libdruntime -I . # D flags for compilation @@ -83,12 +83,12 @@ PHOBOS_DSOURCES = else -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 \ +PHOBOS_DSOURCES = etc/c/curl.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 \ @@ -99,7 +99,9 @@ PHOBOS_DSOURCES = etc/c/curl.d etc/c/sqlite3.d etc/c/zlib.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/aligned_block_list.d \ std/experimental/allocator/building_blocks/allocator_list.d \ + std/experimental/allocator/building_blocks/ascending_page_allocator.d \ std/experimental/allocator/building_blocks/bitmapped_block.d \ std/experimental/allocator/building_blocks/bucketizer.d \ std/experimental/allocator/building_blocks/fallback_allocator.d \ @@ -123,27 +125,34 @@ PHOBOS_DSOURCES = etc/c/curl.d etc/c/sqlite3.d etc/c/zlib.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/math/biguintcore.d \ - std/internal/math/biguintnoasm.d std/internal/math/errorfunction.d \ - std/internal/math/gammafunction.d std/internal/scopebuffer.d \ + std/experimental/typecons.d std/file.d std/format/internal/floats.d \ + std/format/internal/read.d std/format/internal/write.d \ + std/format/package.d std/format/read.d std/format/spec.d \ + std/format/write.d std/functional.d std/getopt.d \ + std/internal/attributes.d std/internal/cstring.d \ + std/internal/math/biguintcore.d std/internal/math/biguintnoasm.d \ + std/internal/math/errorfunction.d std/internal/math/gammafunction.d \ + std/internal/memory.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/internal/windows/advapi32.d std/json.d std/math/algebraic.d \ + std/math/constants.d std/math/exponential.d std/math/hardware.d \ + std/math/operations.d std/math/package.d std/math/remainder.d \ + std/math/rounding.d std/math/traits.d std/math/trigonometry.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/net/isemail.d std/numeric.d std/outbuffer.d std/package.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/tests2.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 + std/stdio.d std/string.d std/sumtype.d std/system.d std/traits.d \ + std/typecons.d std/typetuple.d std/uni/package.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 endif diff --git a/libphobos/src/Makefile.in b/libphobos/src/Makefile.in index f8b7648..9e61bb3 100644 --- a/libphobos/src/Makefile.in +++ b/libphobos/src/Makefile.in @@ -148,7 +148,7 @@ LTLIBRARIES = $(toolexeclib_LTLIBRARIES) am__DEPENDENCIES_1 = am__dirstamp = $(am__leading_dot)dirstamp @ENABLE_LIBDRUNTIME_ONLY_FALSE@am__objects_1 = etc/c/curl.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ etc/c/sqlite3.lo etc/c/zlib.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ etc/c/zlib.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/comparison.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/internal.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/iteration.lo \ @@ -188,7 +188,9 @@ am__dirstamp = $(am__leading_dot)dirstamp @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/encoding.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/exception.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/affix_allocator.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/aligned_block_list.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/allocator_list.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/ascending_page_allocator.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/bitmapped_block.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/bucketizer.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/fallback_allocator.lo \ @@ -216,13 +218,22 @@ am__dirstamp = $(am__leading_dot)dirstamp @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/nulllogger.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/package.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/typecons.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/file.lo std/format.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/file.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/floats.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/read.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/write.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/package.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/read.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/spec.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/write.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/functional.lo std/getopt.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/attributes.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/cstring.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/biguintcore.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/biguintnoasm.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/errorfunction.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/gammafunction.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/memory.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/scopebuffer.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/test/dummyrange.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/test/range.lo \ @@ -233,11 +244,22 @@ am__dirstamp = $(am__leading_dot)dirstamp @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_norm.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_tables.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/windows/advapi32.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/json.lo std/math.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/json.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/algebraic.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/constants.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/exponential.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/hardware.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/operations.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/package.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/remainder.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/rounding.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/traits.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/trigonometry.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/mathspecial.lo std/meta.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/mmfile.lo std/net/curl.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/net/isemail.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/numeric.lo std/outbuffer.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/package.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/parallelism.lo std/path.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/process.lo std/random.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/range/interfaces.lo \ @@ -254,11 +276,13 @@ am__dirstamp = $(am__leading_dot)dirstamp @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/regex/package.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/signals.lo std/socket.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/stdint.lo std/stdio.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/string.lo std/system.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/traits.lo std/typecons.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/typetuple.lo std/uni.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/uri.lo std/utf.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/uuid.lo std/variant.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/string.lo std/sumtype.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/system.lo std/traits.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/typecons.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/typetuple.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/uni/package.lo std/uri.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/utf.lo std/uuid.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/variant.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/windows/charset.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/windows/registry.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/windows/syserror.lo \ @@ -476,7 +500,7 @@ LTDCOMPILE = $(LIBTOOL) --tag=D $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ # Include D build rules # Make sure GDC can find libdruntime and libphobos include files -D_EXTRA_DFLAGS = -nostdinc -I $(srcdir) \ +D_EXTRA_DFLAGS = -fpreview=dip1000 -fpreview=dtorfields -nostdinc -I $(srcdir) \ -I $(top_srcdir)/libdruntime -I ../libdruntime -I . @@ -519,12 +543,12 @@ libgphobos_la_LINK = $(LIBTOOL) --tag=D $(libgphobos_la_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(GDC) $(AM_CFLAGS) $(CFLAGS) \ $(libgphobos_la_LDFLAGS) $(LDFLAGS) -o $@ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@PHOBOS_DSOURCES = etc/c/curl.d etc/c/sqlite3.d etc/c/zlib.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/comparison.d std/algorithm/internal.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/iteration.d std/algorithm/mutation.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/package.d std/algorithm/searching.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/setops.d std/algorithm/sorting.d std/array.d std/ascii.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/base64.d std/bigint.d std/bitmanip.d std/compiler.d std/complex.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@PHOBOS_DSOURCES = etc/c/curl.d etc/c/zlib.d std/algorithm/comparison.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/internal.d std/algorithm/iteration.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/mutation.d std/algorithm/package.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/searching.d std/algorithm/setops.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/algorithm/sorting.d std/array.d std/ascii.d std/base64.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/bigint.d std/bitmanip.d std/compiler.d std/complex.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/concurrency.d std/container/array.d std/container/binaryheap.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/container/dlist.d std/container/package.d std/container/rbtree.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/container/slist.d std/container/util.d std/conv.d std/csv.d \ @@ -535,7 +559,9 @@ libgphobos_la_LINK = $(LIBTOOL) --tag=D $(libgphobos_la_LIBTOOLFLAGS) \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/digest/murmurhash.d std/digest/package.d std/digest/ripemd.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/digest/sha.d std/encoding.d std/exception.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/affix_allocator.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/aligned_block_list.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/allocator_list.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/ascending_page_allocator.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/bitmapped_block.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/bucketizer.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/allocator/building_blocks/fallback_allocator.d \ @@ -559,28 +585,35 @@ libgphobos_la_LINK = $(LIBTOOL) --tag=D $(libgphobos_la_LIBTOOLFLAGS) \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/core.d std/experimental/logger/filelogger.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/multilogger.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/nulllogger.d std/experimental/logger/package.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/typecons.d std/file.d std/format.d std/functional.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/getopt.d std/internal/cstring.d std/internal/math/biguintcore.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/biguintnoasm.d std/internal/math/errorfunction.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/gammafunction.d std/internal/scopebuffer.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/typecons.d std/file.d std/format/internal/floats.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/read.d std/format/internal/write.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/package.d std/format/read.d std/format/spec.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/write.d std/functional.d std/getopt.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/attributes.d std/internal/cstring.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/biguintcore.d std/internal/math/biguintnoasm.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/errorfunction.d std/internal/math/gammafunction.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/memory.d std/internal/scopebuffer.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/test/dummyrange.d std/internal/test/range.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/test/uda.d std/internal/unicode_comp.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_decomp.d std/internal/unicode_grapheme.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_norm.d std/internal/unicode_tables.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/windows/advapi32.d std/json.d std/math.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/windows/advapi32.d std/json.d std/math/algebraic.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/constants.d std/math/exponential.d std/math/hardware.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/operations.d std/math/package.d std/math/remainder.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/rounding.d std/math/traits.d std/math/trigonometry.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/mathspecial.d std/meta.d std/mmfile.d std/net/curl.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/net/isemail.d std/numeric.d std/outbuffer.d std/parallelism.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/path.d std/process.d std/random.d std/range/interfaces.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/range/package.d std/range/primitives.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/net/isemail.d std/numeric.d std/outbuffer.d std/package.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/parallelism.d std/path.d std/process.d std/random.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/range/interfaces.d std/range/package.d std/range/primitives.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/regex/internal/backtracking.d std/regex/internal/generator.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/regex/internal/ir.d std/regex/internal/kickstart.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/regex/internal/parser.d std/regex/internal/tests.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/regex/internal/tests2.d std/regex/internal/thompson.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/regex/package.d std/signals.d std/socket.d std/stdint.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/stdio.d std/string.d std/system.d std/traits.d std/typecons.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/typetuple.d std/uni.d std/uri.d std/utf.d std/uuid.d std/variant.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/windows/charset.d std/windows/registry.d std/windows/syserror.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/xml.d std/zip.d std/zlib.d +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/stdio.d std/string.d std/sumtype.d std/system.d std/traits.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/typecons.d std/typetuple.d std/uni/package.d std/uri.d std/utf.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/uuid.d std/variant.d std/windows/charset.d std/windows/registry.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/windows/syserror.d std/xml.d std/zip.d std/zlib.d # Source file definitions. Boring stuff, auto-generated with @@ -663,7 +696,6 @@ 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 @@ -727,8 +759,12 @@ std/experimental/allocator/building_blocks/$(am__dirstamp): @: > 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/aligned_block_list.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/ascending_page_allocator.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: \ @@ -791,12 +827,25 @@ 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/format/internal/$(am__dirstamp): + @$(MKDIR_P) std/format/internal + @: > std/format/internal/$(am__dirstamp) +std/format/internal/floats.lo: std/format/internal/$(am__dirstamp) +std/format/internal/read.lo: std/format/internal/$(am__dirstamp) +std/format/internal/write.lo: std/format/internal/$(am__dirstamp) +std/format/$(am__dirstamp): + @$(MKDIR_P) std/format + @: > std/format/$(am__dirstamp) +std/format/package.lo: std/format/$(am__dirstamp) +std/format/read.lo: std/format/$(am__dirstamp) +std/format/spec.lo: std/format/$(am__dirstamp) +std/format/write.lo: std/format/$(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/attributes.lo: std/internal/$(am__dirstamp) std/internal/cstring.lo: std/internal/$(am__dirstamp) std/internal/math/$(am__dirstamp): @$(MKDIR_P) std/internal/math @@ -805,6 +854,7 @@ std/internal/math/biguintcore.lo: std/internal/math/$(am__dirstamp) std/internal/math/biguintnoasm.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/memory.lo: std/internal/$(am__dirstamp) std/internal/scopebuffer.lo: std/internal/$(am__dirstamp) std/internal/test/$(am__dirstamp): @$(MKDIR_P) std/internal/test @@ -823,7 +873,19 @@ 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/math/$(am__dirstamp): + @$(MKDIR_P) std/math + @: > std/math/$(am__dirstamp) +std/math/algebraic.lo: std/math/$(am__dirstamp) +std/math/constants.lo: std/math/$(am__dirstamp) +std/math/exponential.lo: std/math/$(am__dirstamp) +std/math/hardware.lo: std/math/$(am__dirstamp) +std/math/operations.lo: std/math/$(am__dirstamp) +std/math/package.lo: std/math/$(am__dirstamp) +std/math/remainder.lo: std/math/$(am__dirstamp) +std/math/rounding.lo: std/math/$(am__dirstamp) +std/math/traits.lo: std/math/$(am__dirstamp) +std/math/trigonometry.lo: std/math/$(am__dirstamp) std/mathspecial.lo: std/$(am__dirstamp) std/meta.lo: std/$(am__dirstamp) std/mmfile.lo: std/$(am__dirstamp) @@ -834,6 +896,7 @@ 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/package.lo: std/$(am__dirstamp) std/parallelism.lo: std/$(am__dirstamp) std/path.lo: std/$(am__dirstamp) std/process.lo: std/$(am__dirstamp) @@ -865,11 +928,15 @@ 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/sumtype.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/uni/$(am__dirstamp): + @$(MKDIR_P) std/uni + @: > std/uni/$(am__dirstamp) +std/uni/package.lo: std/uni/$(am__dirstamp) std/uri.lo: std/$(am__dirstamp) std/utf.lo: std/$(am__dirstamp) std/uuid.lo: std/$(am__dirstamp) @@ -909,6 +976,10 @@ mostlyclean-compile: -rm -f std/experimental/allocator/building_blocks/*.lo -rm -f std/experimental/logger/*.$(OBJEXT) -rm -f std/experimental/logger/*.lo + -rm -f std/format/*.$(OBJEXT) + -rm -f std/format/*.lo + -rm -f std/format/internal/*.$(OBJEXT) + -rm -f std/format/internal/*.lo -rm -f std/internal/*.$(OBJEXT) -rm -f std/internal/*.lo -rm -f std/internal/math/*.$(OBJEXT) @@ -917,6 +988,8 @@ mostlyclean-compile: -rm -f std/internal/test/*.lo -rm -f std/internal/windows/*.$(OBJEXT) -rm -f std/internal/windows/*.lo + -rm -f std/math/*.$(OBJEXT) + -rm -f std/math/*.lo -rm -f std/net/*.$(OBJEXT) -rm -f std/net/*.lo -rm -f std/range/*.$(OBJEXT) @@ -925,6 +998,8 @@ mostlyclean-compile: -rm -f std/regex/*.lo -rm -f std/regex/internal/*.$(OBJEXT) -rm -f std/regex/internal/*.lo + -rm -f std/uni/*.$(OBJEXT) + -rm -f std/uni/*.lo -rm -f std/windows/*.$(OBJEXT) -rm -f std/windows/*.lo @@ -946,14 +1021,18 @@ clean-libtool: -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/format/.libs std/format/_libs + -rm -rf std/format/internal/.libs std/format/internal/_libs -rm -rf std/internal/.libs std/internal/_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/math/.libs std/math/_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/uni/.libs std/uni/_libs -rm -rf std/windows/.libs std/windows/_libs install-toolexeclibDATA: $(toolexeclib_DATA) @$(NORMAL_INSTALL) @@ -1071,14 +1150,18 @@ distclean-generic: -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/format/$(am__dirstamp) + -rm -f std/format/internal/$(am__dirstamp) -rm -f std/internal/$(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/math/$(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/uni/$(am__dirstamp) -rm -f std/windows/$(am__dirstamp) maintainer-clean-generic: diff --git a/libphobos/src/etc/c/curl.d b/libphobos/src/etc/c/curl.d index 2cc588c..98fe74a 100644 --- a/libphobos/src/etc/c/curl.d +++ b/libphobos/src/etc/c/curl.d @@ -257,7 +257,7 @@ enum CurlChunkBgnFunc { /** 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); +alias curl_chunk_bgn_callback = c_long function(const(void) *transfer_info, void *ptr, int remains); /** return codes for CURLOPT_CHUNK_END_FUNCTION */ enum CurlChunkEndFunc { @@ -281,7 +281,7 @@ enum CurlFnMAtchFunc { /** 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); +alias curl_fnmatch_callback = int function(void *ptr, in const(char) *pattern, in const(char) *string); /// seek whence... enum CurlSeekPos { @@ -378,7 +378,7 @@ 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); +alias curl_strdup_callback = char * function(in const(char) *str); /// ditto alias curl_calloc_callback = void* function(size_t nmemb, size_t size); @@ -615,8 +615,8 @@ enum CurlKHMatch { /// alias curl_sshkeycallback = int function(CURL *easy, /** easy handle */ - curl_khkey *knownkey, /** known */ - curl_khkey *foundkey, /** found */ + const(curl_khkey) *knownkey, /** known */ + const(curl_khkey) *foundkey, /** found */ CurlKHMatch m, /** libcurl's view on the keys */ void *clientp /** custom pointer passed from app */ ); @@ -867,7 +867,7 @@ enum CurlOption { /** 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]" */ + `[host]:[port]` */ proxyport, /** size of the POST input data, if strlen() is not good to use */ postfieldsize, @@ -1136,7 +1136,7 @@ enum CurlOption { ftp_ssl_ccc = 154, /** Same as TIMEOUT and CONNECTTIMEOUT, but with ms resolution */ timeout_ms, - connecttimeout_ms, + connecttimeout_ms, /// ditto /** 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, @@ -1365,9 +1365,9 @@ 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); +int curl_strequal(in const(char) *s1, in const(char) *s2); /// ditto -int curl_strnequal(in char *s1, in char *s2, size_t n); +int curl_strnequal(in const(char) *s1, in const(char) *s2, size_t n); } enum CurlForm { nothing, /********** the first one is unused ************/ @@ -1457,7 +1457,7 @@ CURLFORMcode curl_formadd(curl_httppost **httppost, curl_httppost **last_post,. * 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); +alias curl_formget_callback = size_t function(void *arg, in const(char) *buf, size_t len); /** * Name: curl_formget() @@ -1487,7 +1487,7 @@ void curl_formfree(curl_httppost *form); * 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); +char * curl_getenv(in const(char) *variable); /** * Name: curl_version() @@ -1507,10 +1507,10 @@ char * curl_version(); * %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); +char * curl_easy_escape(CURL *handle, in const(char) *string, int length); /** the previous version: */ -char * curl_escape(in char *string, int length); +char * curl_escape(in const(char) *string, int length); /** @@ -1524,10 +1524,10 @@ char * curl_escape(in char *string, int length); * 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); +char * curl_easy_unescape(CURL *handle, in const(char) *string, int length, int *outlength); /** the previous version */ -char * curl_unescape(in char *string, int length); +char * curl_unescape(in const(char) *string, int length); /** * Name: curl_free() @@ -1601,7 +1601,7 @@ struct curl_slist * 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 *); +curl_slist * curl_slist_append(curl_slist *, in const(char) *); /** * Name: curl_slist_free_all() @@ -1621,7 +1621,7 @@ void curl_slist_free_all(curl_slist *); * 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); +time_t curl_getdate(const(char) *p, const(time_t) *unused); /** info about the certificate chain, only for OpenSSL builds. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ diff --git a/libphobos/src/etc/c/sqlite3.d b/libphobos/src/etc/c/sqlite3.d deleted file mode 100644 index 43a72f5..0000000 --- a/libphobos/src/etc/c/sqlite3.d +++ /dev/null @@ -1,2126 +0,0 @@ -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 index b3e9783..a1b20fe 100644 --- a/libphobos/src/etc/c/zlib.d +++ b/libphobos/src/etc/c/zlib.d @@ -38,10 +38,12 @@ import core.stdc.config; */ nothrow: +@nogc: extern (C): -const char[] ZLIB_VERSION = "1.2.11"; -const ZLIB_VERNUM = 0x12b0; +// Those are extern(D) as they should be mangled +extern(D) immutable string ZLIB_VERSION = "1.2.11"; +extern(D) immutable ZLIB_VERNUM = 0x12b0; /* The 'zlib' compression library provides in-memory compression and @@ -227,7 +229,8 @@ enum } /* The deflate compression method (the only one supported in this version) */ -const int Z_NULL = 0; /* for initializing zalloc, zfree, opaque */ +/// for initializing zalloc, zfree, opaque (extern(D) for mangling) +extern(D) immutable void* Z_NULL = null; /* basic functions */ diff --git a/libphobos/src/index.d b/libphobos/src/index.d index 2792b7b..8613a3c 100644 --- a/libphobos/src/index.d +++ b/libphobos/src/index.d @@ -3,9 +3,9 @@ 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 +$(P Generally, the `std` namespace is used for the main modules in the +Phobos standard library. The `etc` namespace is used for external C/C++ +library bindings. The `core` namespace is used for low-level D runtime functions.) $(P The following table is a quick reference guide for which Phobos modules to @@ -41,7 +41,7 @@ $(BOOKTABLE , $(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). + `std.algorithm`. ) ) $(LEADINGROW Containers) @@ -88,12 +88,12 @@ $(BOOKTABLE , $(TD Checked integral types.) ) $(TR - $(TDNW $(MREF std,digest,crc)) - $(TD Cyclic Redundancy Check (32-bit) implementation.) + $(TDNW $(MREF std,digest)) + $(TD Compute digests such as md5, sha1 and crc32.) ) $(TR - $(TDNW $(MREF std,digest,digest)) - $(TD Compute digests such as md5, sha1 and crc32.) + $(TDNW $(MREF std,digest,crc)) + $(TD Cyclic Redundancy Check (32-bit) implementation.) ) $(TR $(TDNW $(MREF std,digest,hmac)) @@ -444,12 +444,16 @@ $(BOOKTABLE , ) $(TR $(TDNW $(MREF std,variant)) - $(TD Discriminated unions and algebraic types.) + $(TD Dynamically-typed variable that can hold a value of any type.) ) $(TR $(TDNW $(MREF core,bitop)) $(TD Low level bit manipulation.) ) + $(TR + $(TDNW $(MREF std,sumtype)) + $(TD Type-safe discriminated union.) + ) $(LEADINGROW Vector programming) $(TR $(TDNW $(MREF core,simd)) diff --git a/libphobos/src/std/algorithm/comparison.d b/libphobos/src/std/algorithm/comparison.d index faa4d44..c952544 100644 --- a/libphobos/src/std/algorithm/comparison.d +++ b/libphobos/src/std/algorithm/comparison.d @@ -1,48 +1,48 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _comparison algorithms. +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)) + `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).) + `(new A()).castSwitch((A a)=>1,(B b)=>2)` returns `1`.) $(T2 clamp, - $(D clamp(1, 3, 6)) returns $(D 3). $(D clamp(4, 3, 6)) returns $(D 4).) + `clamp(1, 3, 6)` returns `3`. `clamp(4, 3, 6)` returns `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).) + `cmp("abc", "abcd")` is `-1`, `cmp("abc", "aba")` is `1`, + and `cmp("abc", "abc")` is `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).) + Return first parameter `p` that passes an `if (p)` test, e.g. + `either(0, 42, 43)` returns `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).) + `equal([1, 2, 3], [1.0, 2.0, 3.0])` returns `true`.) $(T2 isPermutation, - $(D isPermutation([1, 2], [2, 1])) returns $(D true).) + `isPermutation([1, 2], [2, 1])` returns `true`.) $(T2 isSameLength, - $(D isSameLength([1, 2, 3], [4, 5, 6])) returns $(D true).) + `isSameLength([1, 2, 3], [4, 5, 6])` returns `true`.) $(T2 levenshteinDistance, - $(D levenshteinDistance("kitten", "sitting")) returns $(D 3) by using + `levenshteinDistance("kitten", "sitting")` returns `3` by using the $(LINK2 https://en.wikipedia.org/wiki/Levenshtein_distance, - Levenshtein distance _algorithm).) + Levenshtein distance algorithm).) $(T2 levenshteinDistanceAndPath, - $(D levenshteinDistanceAndPath("kitten", "sitting")) returns - $(D tuple(3, "snnnsni")) by using the + `levenshteinDistanceAndPath("kitten", "sitting")` returns + `tuple(3, "snnnsni")` by using the $(LINK2 https://en.wikipedia.org/wiki/Levenshtein_distance, - Levenshtein distance _algorithm).) + Levenshtein distance algorithm).) $(T2 max, - $(D max(3, 4, 2)) returns $(D 4).) + `max(3, 4, 2)` returns `4`.) $(T2 min, - $(D min(3, 4, 2)) returns $(D 2).) + `min(3, 4, 2)` returns `2`.) $(T2 mismatch, - $(D mismatch("oh hi", "ohayo")) returns $(D tuple(" hi", "ayo")).) + `mismatch("oh hi", "ohayo")` returns `tuple(" hi", "ayo")`.) $(T2 predSwitch, - $(D 2.predSwitch(1, "one", 2, "two", 3, "three")) returns $(D "two").) + `2.predSwitch(1, "one", 2, "two", 3, "three")` returns `"two"`.) ) Copyright: Andrei Alexandrescu 2008-. @@ -51,25 +51,25 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_comparison.d) +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.functional : unaryFun, binaryFun; import std.range.primitives; import std.traits; -// FIXME import std.meta : allSatisfy; -import std.typecons; // : tuple, Tuple, Flag, Yes; +import std.typecons : tuple, Tuple, Flag, Yes; + +import std.internal.attributes : betterC; /** -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 +Find `value` _among `values`, returning the 1-based index +of the first matching value in `values`, or `0` if `value` +is not _among `values`. The predicate `pred` is used to compare values, and uses equality by default. Params: @@ -116,7 +116,7 @@ if (isExpressionTuple!values) } /// -@safe unittest +@safe @nogc @betterC unittest { assert(3.among(1, 42, 24, 3, 2)); @@ -130,10 +130,10 @@ if (isExpressionTuple!values) } /** -Alternatively, $(D values) can be passed at compile-time, allowing for a more +Alternatively, `values` can be passed at compile-time, allowing for a more efficient search, but one that only supports matching on equality: */ -@safe unittest +@safe @nogc @betterC unittest { assert(3.among!(2, 3, 4)); assert("bar".among!("foo", "bar", "baz") == 2); @@ -214,19 +214,19 @@ private template indexOfFirstOvershadowingChoiceOnLast(choices...) 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). +The first choice that `switchObject` can be casted to the type +of argument it accepts will be called with `switchObject` casted to that +type, and the value it'll return will be returned by `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 +Throws: If none of the choice matches, a `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 + choices = The `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. @@ -235,7 +235,7 @@ Params: Returns: The value of the selected choice. -Note: $(D castSwitch) can only be used with object types. +Note: `castSwitch` can only be used with object types. */ auto castSwitch(choices...)(Object switchObject) { @@ -254,7 +254,6 @@ auto castSwitch(choices...)(Object switchObject) if (switchObject !is null) { - // Checking for exact matches: const classInfo = typeid(switchObject); foreach (index, choice; choices) @@ -517,7 +516,7 @@ auto castSwitch(choices...)(Object switchObject) /** Clamps a value into the given bounds. -This functions is equivalent to $(D max(lower, min(upper,val))). +This function is equivalent to `max(lower, min(upper, val))`. Params: val = The value to _clamp. @@ -525,23 +524,24 @@ Params: upper = The _upper bound of the _clamp. Returns: - Returns $(D val), if it is between $(D lower) and $(D upper). + Returns `val`, if it is between `lower` and `upper`. Otherwise returns the nearest of the two. */ auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) +if (is(typeof(max(min(val, upper), lower)))) in { import std.functional : greaterThan; - assert(!lower.greaterThan(upper)); + assert(!lower.greaterThan(upper), "Lower can't be greater than upper."); } -body +do { - return max(lower, min(upper, val)); + return max(min(val, upper), lower); } /// -@safe unittest +@safe @nogc @betterC unittest { assert(clamp(2, 1, 3) == 2); assert(clamp(0, 1, 3) == 1); @@ -576,121 +576,160 @@ body // UFCS style assert(Date(1982, 1, 4).clamp(Date.min, Date.max) == Date(1982, 1, 4)); + // Stability + struct A { + int x, y; + int opCmp(ref const A rhs) const { return (x > rhs.x) - (x < rhs.x); } + } + A x, lo, hi; + x.y = 42; + assert(x.clamp(lo, hi).y == 42); } // 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 +Performs a lexicographical comparison on two +$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives). +Iterating `r1` and `r2` in lockstep, `cmp` compares each element +`e1` of `r1` with the corresponding element `e2` in `r2`. If one +of the ranges has been finished, `cmp` returns a negative value +if `r1` has fewer elements than `r2`, a positive value if `r1` +has more elements than `r2`, and `0` if the ranges have the same +number of elements. + +If the ranges are strings, `cmp` performs UTF decoding appropriately and compares the ranges one code point at a time. +A custom predicate may be specified, in which case `cmp` performs +a three-way lexicographical comparison using `pred`. Otherwise +the elements are compared using `opCmp`. + Params: - pred = The predicate used for comparison. + pred = Predicate used for comparison. Without a predicate + specified the ordering implied by `opCmp` is used. 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). - + `0` if the ranges compare equal. A negative value if `r1` is a prefix of `r2` or + the first differing element of `r1` is less than the corresponding element of `r2` + according to `pred`. A positive value if `r2` is a prefix of `r1` or the first + differing element of `r2` is less than the corresponding element of `r1` + according to `pred`. + +Note: + An earlier version of the documentation incorrectly stated that `-1` is the + only negative value returned and `1` is the only positive value returned. + Whether that is true depends on the types being compared. */ -int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) -if (isInputRange!R1 && isInputRange!R2 && !(isSomeString!R1 && isSomeString!R2)) +auto cmp(R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2) { - for (;; r1.popFront(), r2.popFront()) + alias E1 = ElementEncodingType!R1; + alias E2 = ElementEncodingType!R2; + + static if (isDynamicArray!R1 && isDynamicArray!R2 + && __traits(isUnsigned, E1) && __traits(isUnsigned, E2) + && E1.sizeof == 1 && E2.sizeof == 1 + // Both or neither must auto-decode. + && (is(immutable E1 == immutable char) == is(immutable E2 == immutable char))) { - 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; + // dstrcmp algorithm is correct for both ubyte[] and for char[]. + import core.internal.string : dstrcmp; + return dstrcmp(cast(const char[]) r1, cast(const char[]) r2); } -} - -/// 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) + else static if (!(isSomeString!R1 && isSomeString!R2)) { - 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; + for (;; r1.popFront(), r2.popFront()) + { + static if (is(typeof(r1.front.opCmp(r2.front)) R)) + alias Result = R; + else + alias Result = int; + if (r2.empty) return Result(!r1.empty); + if (r1.empty) return Result(-1); + static if (is(typeof(r1.front.opCmp(r2.front)))) + { + auto c = r1.front.opCmp(r2.front); + if (c != 0) return c; + } + else + { + auto a = r1.front, b = r2.front; + if (auto result = (b < a) - (a < b)) return result; + } + } } - - static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof && isLessThan) + else { - static if (typeof(r1[0]).sizeof == 1) + static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof) { - immutable len = min(r1.length, r2.length); - immutable result = __ctfe ? + return () @trusted + { + auto p1 = r1.ptr, p2 = r2.ptr, + pEnd = p1 + min(r1.length, r2.length); + for (; p1 != pEnd; ++p1, ++p2) { - 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; + if (*p1 != *p2) return cast(int) *p1 - cast(int) *p2; + } + static if (typeof(r1[0]).sizeof >= 2 && size_t.sizeof <= uint.sizeof) + return cast(int) r1.length - cast(int) r2.length; + else + return int(r1.length > r2.length) - int(r1.length < r2.length); + }(); } else { - auto p1 = r1.ptr, p2 = r2.ptr, - pEnd = p1 + min(r1.length, r2.length); - for (; p1 != pEnd; ++p1, ++p2) + import std.utf : decode; + + for (size_t i1, i2;;) { - if (*p1 != *p2) return threeWayInt(cast(int) *p1, cast(int) *p2); + if (i1 == r1.length) return -int(i2 < r2.length); + if (i2 == r2.length) return int(1); + immutable c1 = decode(r1, i1), + c2 = decode(r2, i2); + if (c1 != c2) return cast(int) c1 - cast(int) c2; } } - return threeWay(r1.length, r2.length); + } +} + +/// ditto +int cmp(alias pred, R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2) +{ + static if (!(isSomeString!R1 && isSomeString!R2)) + { + for (;; r1.popFront(), r2.popFront()) + { + if (r2.empty) return !r1.empty; + if (r1.empty) return -1; + auto a = r1.front, b = r2.front; + if (binaryFun!pred(a, b)) return -1; + if (binaryFun!pred(b, a)) return 1; + } } else { + import std.utf : decode; + for (size_t i1, i2;;) { - if (i1 == r1.length) return threeWay(i2, r2.length); - if (i2 == r2.length) return threeWay(r1.length, i1); + if (i1 == r1.length) return -int(i2 < r2.length); + if (i2 == r2.length) return 1; immutable c1 = decode(r1, i1), c2 = decode(r2, i2); - if (c1 != c2) return threeWayInt(cast(int) c1, cast(int) c2); + if (c1 != c2) + { + if (binaryFun!pred(c2, c1)) return 1; + if (binaryFun!pred(c1, c2)) return -1; + } } } } /// -@safe unittest +pure @safe unittest { int result; @@ -712,6 +751,8 @@ if (isSomeString!R1 && isSomeString!R2) assert(result > 0); result = cmp("aaa", "aaa"d); assert(result == 0); + result = cmp("aaa"d, "aaa"d); + assert(result == 0); result = cmp(cast(int[])[], cast(int[])[]); assert(result == 0); result = cmp([1, 2, 3], [1, 2, 3]); @@ -724,129 +765,237 @@ if (isSomeString!R1 && isSomeString!R2) assert(result > 0); } +/// Example predicate that compares individual elements in reverse lexical order +pure @safe unittest +{ + int result; + + result = cmp!"a > b"("abc", "abc"); + assert(result == 0); + result = cmp!"a > b"("", ""); + assert(result == 0); + result = cmp!"a > b"("abc", "abcd"); + assert(result < 0); + result = cmp!"a > b"("abcd", "abc"); + assert(result > 0); + result = cmp!"a > b"("abc"d, "abd"); + assert(result > 0); + result = cmp!"a > b"("bbc", "abc"w); + assert(result < 0); + result = cmp!"a > b"("aaa", "aaaa"d); + assert(result < 0); + result = cmp!"a > b"("aaaa", "aaa"d); + assert(result > 0); + result = cmp!"a > b"("aaa", "aaa"d); + assert(result == 0); + result = cmp("aaa"d, "aaa"d); + assert(result == 0); + result = cmp!"a > b"(cast(int[])[], cast(int[])[]); + assert(result == 0); + result = cmp!"a > b"([1, 2, 3], [1, 2, 3]); + assert(result == 0); + result = cmp!"a > b"([1, 3, 2], [1, 2, 3]); + assert(result < 0); + result = cmp!"a > b"([1, 2, 3], [1L, 2, 3, 4]); + assert(result < 0); + result = cmp!"a > b"([1L, 2, 3], [1, 2]); + assert(result > 0); +} + +// cmp for string with custom predicate fails if distinct chars can compare equal +// https://issues.dlang.org/show_bug.cgi?id=18286 +@nogc nothrow pure @safe unittest +{ + static bool ltCi(dchar a, dchar b)// less than, case insensitive + { + import std.ascii : toUpper; + return toUpper(a) < toUpper(b); + } + static assert(cmp!ltCi("apple2", "APPLE1") > 0); + static assert(cmp!ltCi("apple1", "APPLE2") < 0); + static assert(cmp!ltCi("apple", "APPLE1") < 0); + static assert(cmp!ltCi("APPLE", "apple1") < 0); + static assert(cmp!ltCi("apple", "APPLE") == 0); +} + +// for non-string ranges check that opCmp is evaluated only once per pair. +// https://issues.dlang.org/show_bug.cgi?id=18280 +@nogc nothrow @safe unittest +{ + static int ctr = 0; + struct S + { + int opCmp(ref const S rhs) const + { + ++ctr; + return 0; + } + bool opEquals(T)(T o) const { return false; } + size_t toHash() const { return 0; } + } + immutable S[4] a; + immutable S[4] b; + immutable result = cmp(a[], b[]); + assert(result == 0, "neither should compare greater than the other!"); + assert(ctr == a.length, "opCmp should be called exactly once per pair of items!"); +} + +nothrow pure @safe @nogc unittest +{ + import std.array : staticArray; + // Test cmp when opCmp returns float. + struct F + { + float value; + float opCmp(const ref F rhs) const + { + return value - rhs.value; + } + bool opEquals(T)(T o) const { return false; } + size_t toHash() const { return 0; } + } + auto result = cmp([F(1), F(2), F(3)].staticArray[], [F(1), F(2), F(3)].staticArray[]); + assert(result == 0); + assert(is(typeof(result) == float)); + result = cmp([F(1), F(3), F(2)].staticArray[], [F(1), F(2), F(3)].staticArray[]); + assert(result > 0); + result = cmp([F(1), F(2), F(3)].staticArray[], [F(1), F(2), F(3), F(4)].staticArray[]); + assert(result < 0); + result = cmp([F(1), F(2), F(3)].staticArray[], [F(1), F(2)].staticArray[]); + assert(result > 0); +} + +nothrow pure @safe unittest +{ + // Parallelism (was broken by inferred return type "immutable int") + import std.parallelism : task; + auto t = task!cmp("foo", "bar"); +} + // equal /** -Compares two ranges for equality, as defined by predicate $(D pred) -(which is $(D ==) by default). +Compares two ranges for equality, as defined by predicate `pred` +(which is `==` 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). + different element types, as long as `pred(r1.front, r2.front)` + evaluates to `bool`. + Performs $(BIGOH min(r1.length, r2.length)) evaluations of `pred`. + + At least one of the ranges must be finite. If one range involved is infinite, the result is + (statically known to be) `false`. + + If the two ranges are different kinds of UTF code unit (`char`, `wchar`, or + `dchar`), then the arrays are compared using UTF decoding to avoid + accidentally integer-promoting units. 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) + `true` if and only if the two ranges compare _equal element + for element, according to binary predicate `pred`. +/ bool equal(Range1, Range2)(Range1 r1, Range2 r2) if (isInputRange!Range1 && isInputRange!Range2 && + !(isInfinite!Range1 && isInfinite!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) + // Use code points when comparing two ranges of UTF code units that aren't + // the same type. This is for backwards compatibility with autodecode + // strings. + enum useCodePoint = + isSomeChar!(ElementEncodingType!Range1) && isSomeChar!(ElementEncodingType!Range2) && + ElementEncodingType!Range1.sizeof != ElementEncodingType!Range2.sizeof; + + static if (useCodePoint) { - return r1.empty && r2.empty; + import std.utf : byDchar; + return equal(r1.byDchar, r2.byDchar); } - 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)) + else { - import std.utf : byCodeUnit; - - static if (isAutodecodableString!Range1) + static if (isInfinite!Range1 || isInfinite!Range2) { - return equal(r1.byCodeUnit, r2); + // No finite range can be ever equal to an infinite range. + return false; } - else + // Detect default pred and compatible dynamic arrays. + else static if (is(typeof(pred) == string) && pred == "a == b" && + isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2))) { - return equal(r2.byCodeUnit, r1); + return r1 == r2; } - } - //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 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(immutable ElementEncodingType!Range1 == immutable ElementEncodingType!Range2)) { - if (!binaryFun!(pred)(r1.front, r2.front)) return false; + import std.utf : byCodeUnit; + + static if (isAutodecodableString!Range1) + return equal(r1.byCodeUnit, r2); + else + return equal(r2.byCodeUnit, r1); } - return true; - } - else - { - //Generic case, we have to walk both ranges making sure neither is empty - for (; !r1.empty; r1.popFront(), r2.popFront()) + // Try a fast implementation when the ranges have comparable lengths. + else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length))) { - if (r2.empty) return false; - if (!binaryFun!(pred)(r1.front, r2.front)) return false; + 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; } - static if (!isInfinite!Range1) + else + { + //Generic case, we have to walk both ranges making sure neither is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (r2.empty || !binaryFun!(pred)(r1.front, r2.front)) return false; + } return r2.empty; + } } } } /// -@safe unittest +@safe @nogc unittest { import std.algorithm.comparison : equal; - import std.math : approxEqual; + import std.math.operations : isClose; - int[] a = [ 1, 2, 4, 3 ]; - assert(!equal(a, a[1..$])); - assert(equal(a, a)); - assert(equal!((a, b) => a == b)(a, a)); + int[4] 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)); + double[4] 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)); + double[4] c = [ 1.0000000005, 2, 4, 3]; + assert(equal!isClose(b[], c[])); } /++ -Tip: $(D equal) can itself be used as a predicate to other functions. +Tip: `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. In particular, `equal` can be its own predicate, allowing range of range (of range...) comparisons. +/ @safe unittest @@ -864,7 +1013,7 @@ range of range (of range...) comparisons. import std.algorithm.iteration : map; import std.internal.test.dummyrange : ReferenceForwardRange, ReferenceInputRange, ReferenceInfiniteForwardRange; - import std.math : approxEqual; + import std.math.operations : isClose; // various strings assert(equal("æøå", "æøå")); //UTF8 vs UTF8 @@ -898,11 +1047,11 @@ range of range (of range...) comparisons. 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))); + double[] c = [ 1.0000000005, 2, 4, 3]; + assert(equal!isClose(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))); + assert(!equal!isClose(map!"a*3"(b), map!"a*2"(c))); //Tests with some fancy reference ranges. ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]); @@ -923,19 +1072,33 @@ range of range (of range...) comparisons. assert(!equal(cir, ifr)); } -@safe pure unittest +@safe @nogc pure unittest { - import std.utf : byChar, byWchar, byDchar; + import std.utf : byChar, byDchar, byWchar; assert(equal("æøå".byChar, "æøå")); + assert(equal("æøå".byChar, "æøå"w)); + assert(equal("æøå".byChar, "æøå"d)); assert(equal("æøå", "æøå".byChar)); + assert(equal("æøå"w, "æøå".byChar)); + assert(equal("æøå"d, "æøå".byChar)); + + assert(equal("æøå".byWchar, "æøå")); assert(equal("æøå".byWchar, "æøå"w)); + assert(equal("æøå".byWchar, "æøå"d)); + assert(equal("æøå", "æøå".byWchar)); assert(equal("æøå"w, "æøå".byWchar)); + assert(equal("æøå"d, "æøå".byWchar)); + + assert(equal("æøå".byDchar, "æøå")); + assert(equal("æøå".byDchar, "æøå"w)); assert(equal("æøå".byDchar, "æøå"d)); + assert(equal("æøå", "æøå".byDchar)); + assert(equal("æøå"w, "æøå".byDchar)); assert(equal("æøå"d, "æøå".byDchar)); } -@safe pure unittest +@safe @nogc pure unittest { struct R(bool _empty) { enum empty = _empty; @@ -956,37 +1119,14 @@ range of range (of range...) comparisons. 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: +another. Given sequences `s` (source) and `t` (target), a +sequence of `EditOp` encodes the steps that need to be taken to +convert `s` into `t`. For example, if `s = "cat"` and $(D +"cars"), the minimal sequence that transforms `s` into `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 @@ -1074,7 +1214,8 @@ private: import core.checkedint : mulu; bool overflow; const rc = mulu(r, c, overflow); - if (overflow) assert(0); + assert(!overflow, "Overflow during multiplication to determine number " + ~ " of matrix elements"); rows = r; cols = c; if (_matrix.length < rc) @@ -1082,7 +1223,8 @@ private: import core.exception : onOutOfMemoryError; import core.stdc.stdlib : realloc; const nbytes = mulu(rc, _matrix[0].sizeof, overflow); - if (overflow) assert(0); + assert(!overflow, "Overflow during multiplication to determine " + ~ " number of bytes of matrix"); auto m = cast(CostType *) realloc(_matrix.ptr, nbytes); if (!m) onOutOfMemoryError(); @@ -1193,10 +1335,10 @@ private: /** 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. +distance) between `s` and `t`. The Levenshtein distance computes +the minimal amount of edit operations necessary to transform `s` +into `t`. Performs $(BIGOH s.length * t.length) evaluations of $(D +equals) and occupies $(BIGOH min(s.length, t.length)) storage. Params: equals = The binary predicate to compare the elements of the two ranges. @@ -1244,7 +1386,7 @@ if (isForwardRange!(Range1) && isForwardRange!(Range2)) return eq(s.front, t.front) ? 0 : 1; } - if (slen > tlen) + if (slen < tlen) { Levenshtein!(Range1, eq, size_t) lev; return lev.distanceLowMem(s, t, slen, tlen); @@ -1305,8 +1447,8 @@ if (isConvertibleToString!Range1 || isConvertibleToString!Range2) } /** -Returns the Levenshtein distance and the edit path between $(D s) and -$(D t). +Returns the Levenshtein distance and the edit path between `s` and +`t`. Params: equals = The binary predicate to compare the elements of the two ranges. @@ -1367,50 +1509,73 @@ if (isConvertibleToString!Range1 || isConvertibleToString!Range2) assert(levenshteinDistanceAndPath(S("cat"), "rat")[0] == 1); } + // max /** -Iterates the passed arguments and return the maximum value. +Iterates the passed arguments and returns the maximum value. Params: args = The values to select the maximum from. At least two arguments must - be passed. + be passed, and they must be comparable with `<`. Returns: - The maximum of the passed-in args. The type of the returned value is + The maximum of the passed-in values. The type of the returned value is the type among the passed arguments that is able to store the largest value. + If at least one of the arguments is NaN, the result is an unspecified value. + See $(REF maxElement, std,algorithm,searching) for examples on how to cope + with NaNs. See_Also: $(REF maxElement, std,algorithm,searching) */ -MaxType!T max(T...)(T args) -if (T.length >= 2) +auto max(T...)(T args) +if (T.length >= 2 && !is(CommonType!T == void)) { - //Get "a" - static if (T.length <= 2) + // Get left-hand side of the comparison. + static if (T.length == 2) alias a = args[0]; else - auto a = max(args[0 .. ($+1)/2]); + auto a = max(args[0 .. ($ + 1) / 2]); alias T0 = typeof(a); - //Get "b" + // Get right-hand side. static if (T.length <= 3) - alias b = args[$-1]; + alias b = args[$ - 1]; else - auto b = max(args[($+1)/2 .. $]); + 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)); + "Invalid arguments: Cannot compare types " ~ T0.stringof ~ + " and " ~ T1.stringof ~ " for ordering."); + + // Compute the returned type. + static if (is(typeof(mostNegative!T0 < mostNegative!T1))) + // Both are numeric (or character or Boolean), so we choose the one with the highest maximum. + // (We use mostNegative for num/bool/char testing purposes even if it's not used otherwise.) + alias Result = Select!(T1.max > T0.max, T1, T0); + else + // At least one is non-numeric, so just go with the common type. + alias Result = CommonType!(T0, T1); - //Do the "max" proper with a and b + // Perform the computation. import std.functional : lessThan; immutable chooseB = lessThan!(T0, T1)(a, b); - return cast(typeof(return)) (chooseB ? b : a); + return cast(Result) (chooseB ? b : a); +} + +/// ditto +T max(T, U)(T a, U b) +if (is(T == U) && is(typeof(a < b))) +{ + /* Handle the common case without all the template expansions + * of the general case + */ + return a < b ? b : a; } /// -@safe unittest +@safe @betterC @nogc unittest { int a = 5; short b = 6; @@ -1423,7 +1588,7 @@ if (T.length >= 2) assert(e == 2); } -@safe unittest +@safe unittest // not @nogc due to `Date` { int a = 5; short b = 6; @@ -1452,77 +1617,76 @@ if (T.length >= 2) 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. +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. The type of the returned value is + the type among the passed arguments that is able to store the smallest value. + If at least one of the arguments is NaN, the result is an unspecified value. + See $(REF minElement, std,algorithm,searching) for examples on how to cope + with NaNs. + See_Also: $(REF minElement, std,algorithm,searching) */ -MinType!T min(T...)(T args) -if (T.length >= 2) +auto min(T...)(T args) +if (T.length >= 2 && !is(CommonType!T == void)) { - //Get "a" + // Get the left-hand side of the comparison. static if (T.length <= 2) alias a = args[0]; else - auto a = min(args[0 .. ($+1)/2]); + auto a = min(args[0 .. ($ + 1) / 2]); alias T0 = typeof(a); - //Get "b" + // Get the right-hand side. static if (T.length <= 3) - alias b = args[$-1]; + alias b = args[$ - 1]; else - auto b = min(args[($+1)/2 .. $]); + 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)); + "Invalid arguments: Cannot compare types " ~ T0.stringof ~ + " and " ~ T1.stringof ~ " for ordering."); + + // Compute the returned type. + static if (is(typeof(mostNegative!T0 < mostNegative!T1))) + // Both are numeric (or character or Boolean), so we choose the one with the lowest minimum. + // If they have the same minimum, choose the one with the smallest size. + // If both mostNegative and sizeof are equal, go for stability: pick the type of the first one. + alias Result = Select!(mostNegative!T1 < mostNegative!T0 || + mostNegative!T1 == mostNegative!T0 && T1.sizeof < T0.sizeof, + T1, T0); + else + // At least one is non-numeric, so just go with the common type. + alias Result = CommonType!(T0, T1); - //Do the "min" proper with a and b + // Engage! import std.functional : lessThan; - immutable chooseA = lessThan!(T0, T1)(a, b); - return cast(typeof(return)) (chooseA ? a : b); + immutable chooseB = lessThan!(T1, T0)(b, a); + return cast(Result) (chooseB ? b : a); +} + +/// ditto +T min(T, U)(T a, U b) +if (is(T == U) && is(typeof(a < b))) +{ + /* Handle the common case without all the template expansions + * of the general case + */ + return b < a ? b : a; } + /// -@safe unittest +@safe @nogc @betterC unittest { int a = 5; short b = 6; @@ -1533,15 +1697,31 @@ if (T.length >= 2) auto e = min(a, b, c); static assert(is(typeof(e) == double)); assert(e == 2); + ulong f = 0xffff_ffff_ffff; + const uint g = min(f, 0xffff_0000); + assert(g == 0xffff_0000); + dchar h = 100; + uint i = 101; + static assert(is(typeof(min(h, i)) == dchar)); + static assert(is(typeof(min(i, h)) == uint)); + assert(min(h, i) == 100); +} - // With arguments of mixed signedness, the return type is the one that can - // store the lowest values. - a = -10; +/** +With arguments of mixed signedness, the return type is the one that can +store the lowest values. +*/ +@safe @nogc @betterC unittest +{ + int 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. +/// User-defined types that support comparison with < are supported. +@safe unittest // not @nogc due to `Date` +{ 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)); @@ -1553,16 +1733,26 @@ if (T.length >= 2) assert(min(Date.max, Date.min) == Date.min); } +// min must be stable: when in doubt, return the first argument. +@safe unittest +{ + assert(min(1.0, double.nan) == 1.0); + assert(min(double.nan, 1.0) is double.nan); + static struct A { + int x; + string y; + int opCmp(const A a) const { return int(x > a.x) - int(x < a.x); } + } + assert(min(A(1, "first"), A(1, "second")) == A(1, "first")); +} + // mismatch /** -Sequentially compares elements in $(D r1) and $(D r2) in lockstep, and -stops at the first mismatch (according to $(D pred), by default +Sequentially compares elements in `r1` and `r2` in lockstep, and +stops at the first mismatch (according to `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) +evaluations of `pred`. */ Tuple!(Range1, Range2) mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2) @@ -1576,32 +1766,34 @@ if (isInputRange!(Range1) && isInputRange!(Range2)) } /// -@safe unittest +@safe @nogc unittest { - int[] x = [ 1, 5, 2, 7, 4, 3 ]; - double[] y = [ 1.0, 5, 2, 7.3, 4, 8 ]; - auto m = mismatch(x, y); + int[6] x = [ 1, 5, 2, 7, 4, 3 ]; + double[6] 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 +@safe @nogc 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]); + import std.range : only; + + int[3] a = [ 1, 2, 3 ]; + int[4] b = [ 1, 2, 4, 5 ]; + auto mm = mismatch(a[], b[]); + assert(equal(mm[0], only(3))); + assert(equal(mm[1], only(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. +`choices` needs to be composed of pairs of test expressions and return +expressions. Each test-expression is compared with `switchExpression` using +`pred`(`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. @@ -1622,7 +1814,7 @@ 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 +yield true with any test expression - `SwitchError` is thrown. $(D SwitchError) is also thrown if a void return expression was executed without throwing anything. */ @@ -1734,74 +1926,48 @@ auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choi /** 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 +optimized to always take advantage of the `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)). +Infinite ranges are considered of the same length. An infinite range has never the same length as a +finite range. + 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. + `true` if both ranges have the same length, `false` otherwise. */ bool isSameLength(Range1, Range2)(Range1 r1, Range2 r2) -if (isInputRange!Range1 && - isInputRange!Range2 && - !isInfinite!Range1 && - !isInfinite!Range2) +if (isInputRange!Range1 && isInputRange!Range2) { - static if (hasLength!(Range1) && hasLength!(Range2)) + static if (isInfinite!Range1 || isInfinite!Range2) + { + return isInfinite!Range1 && isInfinite!Range2; + } + else 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); + return r2.walkLength(r1.length + 1) == 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); + return r1.walkLength(r2.length + 1) == r2.length; } else { - while (!r1.empty) + for (; !r1.empty; r1.popFront, r2.popFront) { if (r2.empty) - { return false; - } - - r1.popFront; - r2.popFront; } - return r2.empty; } } @@ -1823,7 +1989,7 @@ if (isInputRange!Range1 && } // Test CTFE -@safe pure unittest +@safe @nogc pure @betterC unittest { enum result1 = isSameLength([1, 2, 3], [4, 5, 6]); static assert(result1); @@ -1832,6 +1998,14 @@ if (isInputRange!Range1 && static assert(!result2); } +@safe @nogc pure unittest +{ + import std.range : only; + assert(isSameLength(only(1, 2, 3), only(4, 5, 6))); + assert(isSameLength(only(0.3, 90.4, 23.7, 119.2), only(42.6, 23.6, 95.5, 6.3))); + assert(!isSameLength(only(1, 3, 3), only(4, 5))); +} + @safe nothrow pure unittest { import std.internal.test.dummyrange; @@ -1861,38 +2035,38 @@ if (isInputRange!Range1 && assert(!isSameLength(r11, r12)); } -/// For convenience +// Still functional but not documented anymore. 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 function can allocate if the `Yes.allocateGC` flag is passed. This has +the benefit of have better complexity than the `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) +element's `toHash` method. If customized equality is needed, then the `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). +how to define `pred`. Non-allocating forward range option: $(BIGOH n^2) -Non-allocating forward range option with custom $(D pred): $(BIGOH n^2) +Non-allocating forward range option with custom `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) + allocateGC = `Yes.allocateGC`/`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). + `true` if all of the elements in `r1` appear the same number of times in `r2`. + Otherwise, returns `false`. */ -bool isPermutation(AllocateGC allocate_gc, Range1, Range2) +bool isPermutation(Flag!"allocateGC" allocateGC, Range1, Range2) (Range1 r1, Range2 r2) -if (allocate_gc == Yes.allocateGC && +if (allocateGC == Yes.allocateGC && isForwardRange!Range1 && isForwardRange!Range2 && !isInfinite!Range1 && @@ -2111,7 +2285,7 @@ if (alternatives.length >= 1 && } /// -@safe pure unittest +@safe pure @betterC unittest { const a = 1; const b = 2; @@ -2130,7 +2304,11 @@ if (alternatives.length >= 1 && auto ef = either(e, f); static assert(is(typeof(ef) == int)); assert(ef == f); +} +/// +@safe pure unittest +{ immutable p = 1; immutable q = 2; auto pq = either(p, q); @@ -2141,7 +2319,11 @@ if (alternatives.length >= 1 && assert(either(0, 4) == 4); assert(either(0, 0) == 0); assert(either("", "a") == ""); +} +/// +@safe pure unittest +{ string r = null; assert(either(r, "a") == "a"); assert(either("a", "") == "a"); diff --git a/libphobos/src/std/algorithm/internal.d b/libphobos/src/std/algorithm/internal.d index ca03fd7..3caeefe 100644 --- a/libphobos/src/std/algorithm/internal.d +++ b/libphobos/src/std/algorithm/internal.d @@ -14,22 +14,16 @@ package template algoFormat() } // Internal random array generators -version (unittest) +version (StdUnittest) { package enum size_t maxArraySize = 50; package enum size_t minArraySize = maxArraySize - 1; package string[] rndstuff(T : string)() { - import std.random : Random, unpredictableSeed, uniform; + import std.random : Xorshift, uniform; - static Random rnd; - static bool first = true; - if (first) - { - rnd = Random(unpredictableSeed); - first = false; - } + static rnd = Xorshift(234_567_891); string[] result = new string[uniform(minArraySize, maxArraySize, rnd)]; string alpha = "abcdefghijABCDEFGHIJ"; @@ -46,15 +40,9 @@ version (unittest) package int[] rndstuff(T : int)() { - import std.random : Random, unpredictableSeed, uniform; + import std.random : Xorshift, uniform; - static Random rnd; - static bool first = true; - if (first) - { - rnd = Random(unpredictableSeed); - first = false; - } + static rnd = Xorshift(345_678_912); int[] result = new int[uniform(minArraySize, maxArraySize, rnd)]; foreach (ref i; result) { diff --git a/libphobos/src/std/algorithm/iteration.d b/libphobos/src/std/algorithm/iteration.d index 19cfb77..c3e8487 100644 --- a/libphobos/src/std/algorithm/iteration.d +++ b/libphobos/src/std/algorithm/iteration.d @@ -1,54 +1,60 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _iteration algorithms. +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).) + Eagerly evaluates and caches another range's `front`.) $(T2 cacheBidirectional, - As above, but also provides $(D back) and $(D popBack).) + As above, but also provides `back` and `popBack`.) $(T2 chunkBy, - $(D chunkBy!((a,b) => a[1] == b[1])([[1, 1], [1, 2], [2, 2], [2, 1]])) + `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]).) + `[1, 1]`; the second with the elements `[1, 2]` and `[2, 2]`; + and the third with just `[2, 1]`.) $(T2 cumulativeFold, - $(D cumulativeFold!((a, b) => a + b)([1, 2, 3, 4])) returns a + `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.) + `each!writeln([1, 2, 3])` eagerly prints the numbers `1`, `2` + and `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).) + `filter!(a => a > 0)([1, -1, 2, 0, -3])` iterates over elements `1` + and `2`.) $(T2 filterBidirectional, - Similar to $(D filter), but also provides $(D back) and $(D popBack) at + Similar to `filter`, but also provides `back` and `popBack` at a small increase in cost.) $(T2 fold, - $(D fold!((a, b) => a + b)([1, 2, 3, 4])) returns $(D 10).) + `fold!((a, b) => a + b)([1, 2, 3, 4])` returns `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)).) + `group([5, 2, 2, 3, 3])` returns a range containing the tuples + `tuple(5, 1)`, `tuple(2, 2)`, and `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 - + `joiner(["hello", "world!"], "; ")` returns a range that iterates + over the characters `"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).) + `map!(a => a * 2)([1, 2, 3])` lazily returns a range with the numbers + `2`, `4`, `6`.) +$(T2 mean, + Colloquially known as the average, `mean([1, 2, 3])` returns `2`.) $(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). + `reduce!((a, b) => a + b)([1, 2, 3, 4])` returns `10`. This is the old implementation of `fold`.) +$(T2 splitWhen, + Lazily splits a range by comparing adjacent elements.) $(T2 splitter, Lazily splits a range by a separator.) +$(T2 substitute, + `[1, 2].substitute(1, 0.1)` returns `[0.1, 2]`.) $(T2 sum, - Same as $(D fold), but specialized for accurate summation.) + Same as `fold`, but specialized for accurate summation.) $(T2 uniq, Iterates over the unique elements in a range, which is assumed sorted.) ) @@ -59,17 +65,17 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_iteration.d) +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.functional : unaryFun, binaryFun; import std.range.primitives; import std.traits; +import std.typecons : Flag, Yes, No; private template aggregate(fun...) if (fun.length >= 1) @@ -117,36 +123,36 @@ if (fun.length >= 1) } /++ -$(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, +`cache` eagerly evaluates $(REF_ALTTEXT front, front, std,range,primitives) of `range` +on each construction or call to $(REF_ALTTEXT popFront, popFront, std,range,primitives), +to store the result in a _cache. +The result is then directly returned when $(REF_ALTTEXT front, front, std,range,primitives) 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). +In particular, it can be placed after a call to $(LREF map), or before a call +$(REF filter, std,range) or $(REF tee, std,range) -$(D cache) may provide +`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 +call to `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) +`cache` does not provide random access primitives, +as `cache` would be unable to _cache the random accesses. +If `Range` provides slicing primitives, +then `cache` will provide the same slicing primitives, +but `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 + An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with the cached values of range +/ auto cache(Range)(Range range) if (isInputRange!Range) @@ -203,16 +209,22 @@ if (isBidirectionalRange!Range) assert(counter == iota(-4, 5).length); } +// https://issues.dlang.org/show_bug.cgi?id=15891 +@safe pure unittest +{ + assert([1].map!(x=>[x].map!(y=>y)).cache.front.front == 1); +} + /++ -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. +Tip: `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. +Furthermore, care should be taken composing `cache` with $(REF take, std,range). +By placing `take` before `cache`, then `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 `take` after `cache` +may yield a faster range. Either way, the resulting ranges will be equivalent, but maybe not at the same cost or side effects. @@ -339,9 +351,17 @@ private struct _Cache(R, bool bidir) source = range; if (!range.empty) { - caches[0] = source.front; - static if (bidir) - caches[1] = source.back; + caches[0] = source.front; + static if (bidir) + caches[1] = source.back; + } + else + { + // needed, because the compiler cannot deduce, that 'caches' is initialized + // see https://issues.dlang.org/show_bug.cgi?id=15891 + caches[0] = UE.init; + static if (bidir) + caches[1] = UE.init; } } @@ -353,10 +373,7 @@ private struct _Cache(R, bool bidir) return source.empty; } - static if (hasLength!R) auto length() @property - { - return source.length; - } + mixin ImplementLength!source; E front() @property { @@ -376,7 +393,12 @@ private struct _Cache(R, bool bidir) if (!source.empty) caches[0] = source.front; else - caches = CacheTypes.init; + { + // see https://issues.dlang.org/show_bug.cgi?id=15891 + caches[0] = UE.init; + static if (bidir) + caches[1] = UE.init; + } } static if (bidir) void popBack() { @@ -385,7 +407,11 @@ private struct _Cache(R, bool bidir) if (!source.empty) caches[1] = source.back; else - caches = CacheTypes.init; + { + // see https://issues.dlang.org/show_bug.cgi?id=15891 + caches[0] = UE.init; + caches[1] = UE.init; + } } static if (isForwardRange!R) @@ -430,7 +456,7 @@ private struct _Cache(R, bool bidir) { assert(low <= high, "Bounds error when slicing cache."); } - body + do { import std.range : takeExactly; return this[low .. $].takeExactly(high - low); @@ -440,21 +466,14 @@ private struct _Cache(R, bool bidir) } /** -$(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 +Implements the homonym function (also known as `transform`) present +in many languages of functional flavor. The call `map!(fun)(range)` +returns a range of which elements are obtained by applying `fun(a)` +left to right for all elements `a` in `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)) @@ -462,6 +481,13 @@ See_Also: template map(fun...) if (fun.length >= 1) { + /** + Params: + 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 `Tuple` containing one element for each fun. + */ auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) { import std.meta : AliasSeq, staticMap; @@ -475,7 +501,9 @@ if (fun.length >= 1) alias _funs = staticMap!(unaryFun, fun); alias _fun = adjoin!_funs; - // Once DMD issue #5710 is fixed, this validation loop can be moved into a template. + // Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed + // accross all compilers (as of 2020-04, it wasn't fixed in LDC and GDC), + // this validation loop can be moved into a template. foreach (f; _funs) { static assert(!is(typeof(f(RE.init)) == void), @@ -487,7 +515,8 @@ if (fun.length >= 1) alias _fun = unaryFun!fun; alias _funs = AliasSeq!(_fun); - // Do the validation separately for single parameters due to DMD issue #15777. + // Do the validation separately for single parameters due to + // https://issues.dlang.org/show_bug.cgi?id=15777. static assert(!is(typeof(_fun(RE.init)) == void), "Mapping function(s) must not return void: " ~ _funs.stringof); } @@ -497,19 +526,18 @@ if (fun.length >= 1) } /// -@safe unittest +@safe @nogc 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 ])); + import std.range : chain, only; + auto squares = + chain(only(1, 2, 3, 4), only(5, 6)).map!(a => a * a); + assert(equal(squares, only(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 +Multiple functions can be passed to `map`. In that case, the +element type of `map` is a tuple containing one element for each function. */ @safe unittest @@ -527,7 +555,7 @@ function. } /** -You may alias $(D map) with some function(s) to a symbol and use +You may alias `map` with some function(s) to a symbol and use it separately: */ @safe unittest @@ -539,10 +567,9 @@ it separately: assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); } +// Verify workaround for https://issues.dlang.org/show_bug.cgi?id=15777 @safe unittest { - // Verify workaround for DMD #15777 - import std.algorithm.mutation, std.string; auto foo(string[] args) { @@ -602,7 +629,7 @@ private struct MapResult(alias fun, Range) static if (isRandomAccessRange!R) { - static if (is(typeof(_input[ulong.max]))) + static if (is(typeof(Range.init[ulong.max]))) private alias opIndex_t = ulong; else private alias opIndex_t = uint; @@ -613,15 +640,7 @@ private struct MapResult(alias fun, Range) } } - static if (hasLength!R) - { - @property auto length() - { - return _input.length; - } - - alias opDollar = length; - } + mixin ImplementLength!_input; static if (hasSlicing!R) { @@ -689,7 +708,7 @@ private struct MapResult(alias fun, Range) import std.internal.test.dummyrange; import std.range; import std.typecons : tuple; - import std.random : unpredictableSeed, uniform, Random; + import std.random : uniform, Random = Xorshift; int[] arr1 = [ 1, 2, 3, 4 ]; const int[] arr1Const = arr1; @@ -747,7 +766,7 @@ private struct MapResult(alias fun, Range) assert(fibsSquares.front == 9); auto repeatMap = map!"a"(repeat(1)); - auto gen = Random(unpredictableSeed); + auto gen = Random(123_456_789); auto index = uniform(0, 1024, gen); static assert(isInfinite!(typeof(repeatMap))); assert(repeatMap[index] == 1); @@ -779,7 +798,7 @@ private struct MapResult(alias fun, Range) assert(equal(ms2[0 .. 2], "日本"w)); assert(equal(ms3[0 .. 2], "HE")); - // Issue 5753 + // https://issues.dlang.org/show_bug.cgi?id=5753 static void voidFun(int) {} static int nonvoidFun(int) { return 0; } static assert(!__traits(compiles, map!voidFun([1]))); @@ -788,7 +807,7 @@ private struct MapResult(alias fun, Range) static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1]))); static assert(!__traits(compiles, map!(a => voidFun(a))([1]))); - // Phobos issue #15480 + // https://issues.dlang.org/show_bug.cgi?id=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)); @@ -810,7 +829,7 @@ private struct MapResult(alias fun, Range) { import std.range : iota; - // Issue #10130 - map of iota with const step. + // https://issues.dlang.org/show_bug.cgi?id=10130 - map of iota with const step. const step = 2; assert(map!(i => i)(iota(0, 10, step)).walkLength == 5); @@ -842,35 +861,59 @@ private struct MapResult(alias fun, Range) assert(m.front == immutable(S)(null)); } +// Issue 20928 +@safe unittest +{ + struct Always3 + { + enum empty = false; + auto save() { return this; } + long front() { return 3; } + void popFront() {} + long opIndex(ulong i) { return 3; } + long opIndex(ulong i) immutable { return 3; } + } + + import std.algorithm.iteration : map; + Always3.init.map!(e => e)[ulong.max]; +} + // each /** -Eagerly iterates over $(D r) and calls $(D pred) over _each element. +Eagerly iterates over `r` and calls `fun` with _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. +If no function to call is specified, `each` defaults to doing nothing but +consuming the entire range. `r.front` will be evaluated, but that can be avoided +by specifying a lambda with a `lazy` parameter. -$(D each) also supports $(D opApply)-based iterators, so it will work -with e.g. $(REF parallel, std,parallelism). +`each` also supports `opApply`-based types, so it works with e.g. $(REF +parallel, std,parallelism). + +Normally the entire range is iterated. If partial iteration (early stopping) is +desired, `fun` needs to return a value of type $(REF Flag, +std,typecons)`!"each"` (`Yes.each` to continue iteration, or `No.each` to stop +iteration). Params: - pred = predicate to apply to each element of the range - r = range or iterable over which each iterates + fun = function to apply to _each element of the range + r = range or iterable over which `each` iterates -See_Also: $(REF tee, std,range) +Returns: `Yes.each` if the entire range was iterated, `No.each` in case of early +stopping. +See_Also: $(REF tee, std,range) */ -template each(alias pred = "a") +template each(alias fun = "a") { import std.meta : AliasSeq; import std.traits : Parameters; + import std.typecons : Flag, Yes, No; private: - alias BinaryArgs = AliasSeq!(pred, "i", "a"); + alias BinaryArgs = AliasSeq!(fun, "i", "a"); enum isRangeUnaryIterable(R) = - is(typeof(unaryFun!pred(R.init.front))); + is(typeof(unaryFun!fun(R.init.front))); enum isRangeBinaryIterable(R) = is(typeof(binaryFun!BinaryArgs(0, R.init.front))); @@ -882,21 +925,32 @@ private: enum isForeachUnaryIterable(R) = is(typeof((R r) { foreach (ref a; r) - cast(void) unaryFun!pred(a); + cast(void) unaryFun!fun(a); })); - enum isForeachBinaryIterable(R) = + enum isForeachUnaryWithIndexIterable(R) = is(typeof((R r) { - foreach (ref i, ref a; r) + foreach (i, ref a; r) cast(void) binaryFun!BinaryArgs(i, a); })); + enum isForeachBinaryIterable(R) = + is(typeof((R r) { + foreach (ref a, ref b; r) + cast(void) binaryFun!fun(a, b); + })); + enum isForeachIterable(R) = (!isForwardRange!R || isDynamicArray!R) && - (isForeachUnaryIterable!R || isForeachBinaryIterable!R); + (isForeachUnaryIterable!R || isForeachBinaryIterable!R || + isForeachUnaryWithIndexIterable!R); public: - void each(Range)(Range r) + /** + Params: + r = range or iterable over which each iterates + */ + Flag!"each" each(Range)(Range r) if (!isForeachIterable!Range && ( isRangeIterable!Range || __traits(compiles, typeof(r.front).length))) @@ -908,7 +962,15 @@ public: { while (!r.empty) { - cast(void) unaryFun!pred(r.front); + static if (!is(typeof(unaryFun!fun(r.front)) == Flag!"each")) + { + cast(void) unaryFun!fun(r.front); + } + else + { + if (unaryFun!fun(r.front) == No.each) return No.each; + } + r.popFront(); } } @@ -917,7 +979,14 @@ public: size_t i = 0; while (!r.empty) { - cast(void) binaryFun!BinaryArgs(i, r.front); + static if (!is(typeof(binaryFun!BinaryArgs(i, r.front)) == Flag!"each")) + { + cast(void) binaryFun!BinaryArgs(i, r.front); + } + else + { + if (binaryFun!BinaryArgs(i, r.front) == No.each) return No.each; + } r.popFront(); i++; } @@ -927,71 +996,149 @@ public: { // range interface with >2 parameters. for (auto range = r; !range.empty; range.popFront()) - pred(range.front.expand); + { + static if (!is(typeof(fun(r.front.expand)) == Flag!"each")) + { + cast(void) fun(range.front.expand); + } + else + { + if (fun(range.front.expand)) return No.each; + } + } } + return Yes.each; } - void each(Iterable)(auto ref Iterable r) + /// ditto + Flag!"each" 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); + debug(each) pragma(msg, "Using foreach UNARY for ", Iterable.stringof); + { + foreach (ref e; r) + { + static if (!is(typeof(unaryFun!fun(e)) == Flag!"each")) + { + cast(void) unaryFun!fun(e); + } + else + { + if (unaryFun!fun(e) == No.each) return No.each; + } + } + } + } + else static if (isForeachBinaryIterable!Iterable) + { + debug(each) pragma(msg, "Using foreach BINARY for ", Iterable.stringof); + foreach (ref a, ref b; r) + { + static if (!is(typeof(binaryFun!fun(a, b)) == Flag!"each")) + { + cast(void) binaryFun!fun(a, b); + } + else + { + if (binaryFun!fun(a, b) == No.each) return No.each; + } + } + } + else static if (isForeachUnaryWithIndexIterable!Iterable) + { + debug(each) pragma(msg, "Using foreach INDEX for ", Iterable.stringof); + foreach (i, ref e; r) + { + static if (!is(typeof(binaryFun!BinaryArgs(i, e)) == Flag!"each")) + { + cast(void) binaryFun!BinaryArgs(i, e); + } + else + { + if (binaryFun!BinaryArgs(i, e) == No.each) return No.each; + } + } } - else // if (isForeachBinaryIterable!Iterable) + else { - foreach (ref i, ref e; r) - cast(void) binaryFun!BinaryArgs(i, e); + static assert(0, "Invalid foreach iteratable type " ~ Iterable.stringof ~ " met."); } + return Yes.each; } 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 + auto result = Yes.each; + auto dg(Parameters!(Parameters!(r.opApply)) params) + { + static if (!is(typeof(binaryFun!BinaryArgs(i, e)) == Flag!"each")) + { + fun(params); + return 0; // tells opApply to continue iteration + } + else + { + result = fun(params); + return result == Yes.each ? 0 : -1; + } } r.opApply(&dg); + return result; } } } /// -@system unittest +@safe unittest { import std.range : iota; + import std.typecons : No; - long[] arr; + int[] arr; iota(5).each!(n => arr ~= n); assert(arr == [0, 1, 2, 3, 4]); + // stop iterating early + iota(5).each!((n) { arr ~= n; return No.each; }); + assert(arr == [0, 1, 2, 3, 4, 0]); + // If the range supports it, the value can be mutated in place arr.each!((ref n) => n++); - assert(arr == [1, 2, 3, 4, 5]); + assert(arr == [1, 2, 3, 4, 5, 1]); arr.each!"a++"; - assert(arr == [2, 3, 4, 5, 6]); + assert(arr == [2, 3, 4, 5, 6, 2]); + auto m = arr.map!(n => n); // by-ref lambdas are not allowed for non-ref ranges - static assert(!is(typeof(arr.map!(n => n).each!((ref n) => n++)))); + static assert(!__traits(compiles, m.each!((ref n) => n++))); // The default predicate consumes the range - auto m = arr.map!(n => n); (&m).each(); assert(m.empty); +} + +/// `each` can pass an index variable for iterable objects which support this +@safe unittest +{ + auto arr = new size_t[4]; - // Indexes are also available for in-place mutations - arr[] = 0; arr.each!"a=i"(); - assert(arr == [0, 1, 2, 3, 4]); + assert(arr == [0, 1, 2, 3]); + + arr.each!((i, ref e) => e = i * 2); + assert(arr == [0, 2, 4, 6]); +} - // opApply iterators work as well +/// opApply iterators work as well +@system unittest +{ static class S { int x; @@ -1017,7 +1164,8 @@ public: assert(b == [ 3, 4, 5 ]); } -// #15358: application of `each` with >2 args (opApply) +// https://issues.dlang.org/show_bug.cgi?id=15358 +// application of `each` with >2 args (opApply) @system unittest { import std.range : lockstep; @@ -1032,7 +1180,8 @@ public: assert(c == [7,8,9]); } -// #15358: application of `each` with >2 args (range interface) +// https://issues.dlang.org/show_bug.cgi?id=15358 +// application of `each` with >2 args (range interface) @safe unittest { import std.range : zip; @@ -1047,7 +1196,8 @@ public: assert(res == [9, 12, 15]); } -// #16255: `each` on opApply doesn't support ref +// https://issues.dlang.org/show_bug.cgi?id=16255 +// `each` on opApply doesn't support ref @safe unittest { int[] dynamicArray = [1, 2, 3, 4, 5]; @@ -1063,7 +1213,8 @@ public: assert(staticArray == [3, 4, 5, 6, 7]); } -// #16255: `each` on opApply doesn't support ref +// https://issues.dlang.org/show_bug.cgi?id=16255 +// `each` on opApply doesn't support ref @system unittest { struct S @@ -1079,21 +1230,94 @@ public: assert(s.x == 2); } +// https://issues.dlang.org/show_bug.cgi?id=15357 +// `each` should behave similar to foreach +@safe unittest +{ + import std.range : iota; + + auto arr = [1, 2, 3, 4]; + + // 1 ref parameter + arr.each!((ref e) => e = 0); + assert(arr.sum == 0); + + // 1 ref parameter and index + arr.each!((i, ref e) => e = cast(int) i); + assert(arr.sum == 4.iota.sum); +} + +// https://issues.dlang.org/show_bug.cgi?id=15357 +// `each` should behave similar to foreach +@system unittest +{ + import std.range : iota, lockstep; + + // 2 ref parameters and index + auto arrA = [1, 2, 3, 4]; + auto arrB = [5, 6, 7, 8]; + lockstep(arrA, arrB).each!((ref a, ref b) { + a = 0; + b = 1; + }); + assert(arrA.sum == 0); + assert(arrB.sum == 4); + + // 3 ref parameters + auto arrC = [3, 3, 3, 3]; + lockstep(arrA, arrB, arrC).each!((ref a, ref b, ref c) { + a = 1; + b = 2; + c = 3; + }); + assert(arrA.sum == 4); + assert(arrB.sum == 8); + assert(arrC.sum == 12); +} + +// https://issues.dlang.org/show_bug.cgi?id=15357 +// `each` should behave similar to foreach +@system unittest +{ + import std.range : lockstep; + import std.typecons : Tuple; + + auto a = "abc"; + auto b = "def"; + + // print each character with an index + { + alias Element = Tuple!(size_t, "index", dchar, "value"); + Element[] rForeach, rEach; + foreach (i, c ; a) rForeach ~= Element(i, c); + a.each!((i, c) => rEach ~= Element(i, c)); + assert(rForeach == rEach); + assert(rForeach == [Element(0, 'a'), Element(1, 'b'), Element(2, 'c')]); + } + + // print pairs of characters + { + alias Element = Tuple!(dchar, "a", dchar, "b"); + Element[] rForeach, rEach; + foreach (c1, c2 ; a.lockstep(b)) rForeach ~= Element(c1, c2); + a.lockstep(b).each!((c1, c2) => rEach ~= Element(c1, c2)); + assert(rForeach == rEach); + assert(rForeach == [Element('a', 'd'), Element('b', 'e'), Element('c', 'f')]); + } +} + // filter /** -$(D auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range));) - -Implements the higher order _filter function. The predicate is passed to +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)). +that can be executed via `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). + `filter!(predicate)(range)` returns a new range containing only elements `x` in `range` for + which `predicate(x)` returns `true`. See_Also: $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) @@ -1101,6 +1325,14 @@ See_Also: template filter(alias predicate) if (is(typeof(unaryFun!predicate))) { + /** + Params: + range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of elements + Returns: + A range containing only elements `x` in `range` for + which `predicate(x)` returns `true`. + */ auto filter(Range)(Range range) if (isInputRange!(Unqual!Range)) { return FilterResult!(unaryFun!predicate, Range)(range); @@ -1111,16 +1343,16 @@ if (is(typeof(unaryFun!predicate))) @safe unittest { import std.algorithm.comparison : equal; - import std.math : approxEqual; + import std.math.operations : isClose; import std.range; int[] arr = [ 1, 2, 3, 4, 5 ]; - // Sum all elements + // Filter below 3 auto small = filter!(a => a < 3)(arr); assert(equal(small, [ 1, 2 ])); - // Sum again, but with Uniform Function Call Syntax (UFCS) + // Filter again, but with Uniform Function Call Syntax (UFCS) auto sum = arr.filter!(a => a < 3); assert(equal(sum, [ 1, 2 ])); @@ -1133,7 +1365,7 @@ if (is(typeof(unaryFun!predicate))) // 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 ])); + assert(isClose(r1, [ 2.5 ])); } private struct FilterResult(alias pred, Range) @@ -1176,11 +1408,11 @@ private struct FilterResult(alias pred, Range) void popFront() { + prime; do { _input.popFront(); } while (!_input.empty && !pred(_input.front)); - _primed = true; } @property auto ref front() @@ -1298,10 +1530,25 @@ private struct FilterResult(alias pred, Range) assert(equal(filter!underX(list), [ 1, 2, 3, 4 ])); } +// https://issues.dlang.org/show_bug.cgi?id=19823 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : dropOne; + + auto a = [1, 2, 3, 4]; + assert(a.filter!(a => a != 1).dropOne.equal([3, 4])); + assert(a.filter!(a => a != 2).dropOne.equal([3, 4])); + assert(a.filter!(a => a != 3).dropOne.equal([2, 4])); + assert(a.filter!(a => a != 4).dropOne.equal([2, 3])); + assert(a.filter!(a => a == 1).dropOne.empty); + assert(a.filter!(a => a == 2).dropOne.empty); + assert(a.filter!(a => a == 3).dropOne.empty); + assert(a.filter!(a => a == 4).dropOne.empty); +} + /** - * $(D auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range));) - * - * Similar to $(D filter), except it defines a + * Similar to `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 @@ -1310,17 +1557,19 @@ private struct FilterResult(alias pred, Range) * $(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)). + * accept a string, or any callable that can be executed via `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) { + /** + Params: + r = Bidirectional range of elements + Returns: + A range containing only the elements in `r` for which `pred` returns `true`. + */ auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range)) { return FilterBidiResult!(unaryFun!pred, Range)(r); @@ -1398,22 +1647,23 @@ private struct FilterBidiResult(alias pred, Range) 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 +Similarly to `uniq`, `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), +Equivalence of elements is assessed by using the predicate `pred`, which +defaults to `"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)). +`pred(element, element)`. Params: pred = Binary predicate for determining equivalence of two elements. + R = The range type 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)), +Returns: A range of elements of type `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 +occurrences in that run. This will be an input range if `R` is an input range, and a forward range in all other cases. See_Also: $(LREF chunkBy), which chunks an input range into subranges @@ -1457,6 +1707,12 @@ if (isInputRange!R) if (!_input.empty) popFront(); } + private this(R input, Tuple!(MutableE, uint) current) + { + _input = input; + _current = current; + } + /// void popFront() { @@ -1490,7 +1746,7 @@ if (isInputRange!R) } } - /// + /// Returns: the front of the range @property auto ref front() { assert(!empty, "Attempting to fetch the front of an empty Group."); @@ -1500,11 +1756,9 @@ if (isInputRange!R) static if (isForwardRange!R) { /// - @property typeof(this) save() { - typeof(this) ret = this; - ret._input = this._input.save; - ret._current = this._current; - return ret; + @property typeof(this) save() + { + return Group(_input.save, _current); } } } @@ -1567,7 +1821,7 @@ if (isInputRange!R) import std.algorithm.comparison : equal; import std.typecons : tuple; - // Issue 13857 + // https://issues.dlang.org/show_bug.cgi?id=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), @@ -1575,18 +1829,18 @@ if (isInputRange!R) tuple(7, 1u), tuple(8, 1u), tuple(9, 3u) ])); - // Issue 13162 + // https://issues.dlang.org/show_bug.cgi?id=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 + // https://issues.dlang.org/show_bug.cgi?id=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 {} + class C : I { override size_t toHash() const nothrow @safe { return 0; } } const C[] a4 = [new const C()]; auto g4 = a4.group!"a is b"; assert(g4.front[1] == 1); @@ -1600,18 +1854,52 @@ if (isInputRange!R) assert(equal(g6.front[0], [1])); } +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + auto r = arr.group; + assert(r.equal([ tuple(1,1u), tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); + r.popFront; + assert(r.equal([ tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); + auto s = r.save; + r.popFrontN(2); + assert(r.equal([ tuple(4, 3u), tuple(5, 1u) ])); + assert(s.equal([ tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); + s.popFront; + auto t = s.save; + r.popFront; + s.popFront; + assert(r.equal([ tuple(5, 1u) ])); + assert(s.equal([ tuple(4, 3u), tuple(5, 1u) ])); + assert(t.equal([ tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); +} + +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : refRange; + string s = "foo"; + auto r = refRange(&s).group; + assert(equal(r.save, "foo".group)); + assert(equal(r, "foo".group)); +} + // 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 Range *r; private ElementType!Range prev; - this(Range range, ElementType!Range _prev) + this(ref Range range, ElementType!Range _prev) { - r = range; + r = ⦥ prev = _prev; } @@ -1620,18 +1908,26 @@ if (isInputRange!Range && !isForwardRange!Range) return r.empty || !fun(prev, r.front); } - @property ElementType!Range front() { return r.front; } - void popFront() { r.popFront(); } + @property ElementType!Range front() + { + assert(!empty, "Attempting to fetch the front of an empty chunkBy chunk."); + return r.front; + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty chunkBy chunk."); + r.popFront(); + } } private template ChunkByImplIsUnary(alias pred, Range) { - static if (is(typeof(binaryFun!pred(ElementType!Range.init, - ElementType!Range.init)) : bool)) + alias e = lvalueOf!(ElementType!Range); + + static if (is(typeof(binaryFun!pred(e, e)) : bool)) enum ChunkByImplIsUnary = false; - else static if (is(typeof( - unaryFun!pred(ElementType!Range.init) == - unaryFun!pred(ElementType!Range.init)))) + else static if (is(typeof(unaryFun!pred(e) == unaryFun!pred(e)) : bool)) enum ChunkByImplIsUnary = true; else static assert(0, "chunkBy expects either a binary predicate or "~ @@ -1652,6 +1948,7 @@ if (isInputRange!Range && !isForwardRange!Range) private Range r; private ElementType!Range _prev; + private bool openChunk = false; this(Range _r) { @@ -1673,10 +1970,12 @@ if (isInputRange!Range && !isForwardRange!Range) _prev = typeof(_prev).init; } } - @property bool empty() { return r.empty; } + @property bool empty() { return r.empty && !openChunk; } @property auto front() { + assert(!empty, "Attempting to fetch the front of an empty chunkBy."); + openChunk = true; static if (isUnary) { import std.typecons : tuple; @@ -1691,6 +1990,8 @@ if (isInputRange!Range && !isForwardRange!Range) void popFront() { + assert(!empty, "Attempting to popFront an empty chunkBy."); + openChunk = false; while (!r.empty) { if (!eq(_prev, r.front)) @@ -1702,105 +2003,137 @@ if (isInputRange!Range && !isForwardRange!Range) } } } +// Outer range for forward range version of chunkBy +private struct ChunkByOuter(Range, bool eqEquivalenceAssured) +{ + size_t groupNum; + Range current; + Range next; + static if (!eqEquivalenceAssured) + { + bool nextUpdated; + } +} -// Single-pass implementation of chunkBy for forward ranges. -private struct ChunkByImpl(alias pred, Range) -if (isForwardRange!Range) +// Inner range for forward range version of chunkBy +private struct ChunkByGroup(alias eq, Range, bool eqEquivalenceAssured) { 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; - } + alias OuterRange = ChunkByOuter!(Range, eqEquivalenceAssured); - // Inner range - static struct Group + private size_t groupNum; + static if (eqEquivalenceAssured) { - private size_t groupNum; private Range start; - private Range current; + } + private Range current; - private RefCounted!Impl mothership; + private RefCounted!(OuterRange) mothership; - this(RefCounted!Impl origin) + this(RefCounted!(OuterRange) origin) + { + groupNum = origin.groupNum; + current = origin.current.save; + assert(!current.empty, "Passed range 'r' must not be empty"); + static if (eqEquivalenceAssured) { - groupNum = origin.groupNum; - start = origin.current.save; - current = origin.current.save; - assert(!start.empty); - - mothership = origin; - // Note: this requires reflexivity. + // Check for reflexivity. assert(eq(start.front, current.front), - "predicate is not reflexive"); + "predicate is not reflexive"); } - @property bool empty() { return groupNum == size_t.max; } - @property auto ref front() { return current.front; } + mothership = origin; + } + + @property bool empty() { return groupNum == size_t.max; } + @property auto ref front() { return current.front; } - void popFront() + void popFront() + { + static if (!eqEquivalenceAssured) { - current.popFront(); + auto prevElement = current.front; + } + + current.popFront(); + + static if (eqEquivalenceAssured) + { + //this requires transitivity from the predicate. + immutable nowEmpty = current.empty || !eq(start.front, current.front); + } + else + { + immutable nowEmpty = current.empty || !eq(prevElement, current.front); + } - // Note: this requires transitivity. - if (current.empty || !eq(start.front, current.front)) + + if (nowEmpty) + { + if (groupNum == mothership.groupNum) { - 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; + static if (!eqEquivalenceAssured) { - // If parent range hasn't moved on yet, help it along by - // saving location of start of next Group. - mothership.next = current.save; + mothership.nextUpdated = true; } - - groupNum = size_t.max; } - } - @property auto save() - { - auto copy = this; - copy.current = current.save; - return copy; + groupNum = size_t.max; } } - static assert(isForwardRange!Group); - private RefCounted!Impl impl; + @property auto save() + { + auto copy = this; + copy.current = current.save; + return copy; + } +} + +private enum GroupingOpType{binaryEquivalent, binaryAny, unary} + +// Single-pass implementation of chunkBy for forward ranges. +private struct ChunkByImpl(alias pred, alias eq, GroupingOpType opType, Range) +if (isForwardRange!Range) +{ + import std.typecons : RefCounted; + + enum bool eqEquivalenceAssured = opType != GroupingOpType.binaryAny; + alias OuterRange = ChunkByOuter!(Range, eqEquivalenceAssured); + alias InnerRange = ChunkByGroup!(eq, Range, eqEquivalenceAssured); + + static assert(isForwardRange!InnerRange); + + private RefCounted!OuterRange impl; this(Range r) { - impl = RefCounted!Impl(0, r, r.save); + static if (eqEquivalenceAssured) + { + impl = RefCounted!OuterRange(0, r, r.save); + } + else impl = RefCounted!OuterRange(0, r, r.save, false); } @property bool empty() { return impl.current.empty; } - @property auto front() + static if (opType == GroupingOpType.unary) @property auto front() { - static if (isUnary) - { - import std.typecons : tuple; - return tuple(unaryFun!pred(impl.current.front), Group(impl)); - } - else - { - return Group(impl); - } + import std.typecons : tuple; + return tuple(unaryFun!pred(impl.current.front), InnerRange(impl)); + } + else @property auto front() + { + return InnerRange(impl); } - void popFront() + static if (eqEquivalenceAssured) 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 @@ -1815,15 +2148,69 @@ if (isForwardRange!Range) // Indicate to any remaining Groups that we have moved on. impl.groupNum++; } - - @property auto save() + else void popFront() { - // 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))); + if (impl.nextUpdated) + { + impl.current = impl.next.save; + } + else while (true) + { + auto prevElement = impl.current.front; + impl.current.popFront(); + if (impl.current.empty) break; + if (!eq(prevElement, impl.current.front)) break; + } + + impl.nextUpdated = false; + // 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)), typeof(this).stringof + ~ " must be a forward range"); +} + +//Test for https://issues.dlang.org/show_bug.cgi?id=14909 +@system unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + import std.stdio; + auto n = 3; + auto s = [1,2,3].chunkBy!(a => a+n); + auto t = s.save.map!(x=>x[0]); + auto u = s.map!(x=>x[1]); + assert(t.equal([4,5,6])); + assert(u.equal!equal([[1],[2],[3]])); +} + +//Test for https://issues.dlang.org/show_bug.cgi?id=18751 +@system unittest +{ + import std.algorithm.comparison : equal; + + string[] data = [ "abc", "abc", "def" ]; + int[] indices = [ 0, 1, 2 ]; + + auto chunks = indices.chunkBy!((i, j) => data[i] == data[j]); + assert(chunks.equal!equal([ [ 0, 1 ], [ 2 ] ])); +} + +//Additional test for fix for issues 14909 and 18751 +@system unittest +{ + import std.algorithm.comparison : equal; + auto v = [2,4,8,3,6,9,1,5,7]; + auto i = 2; + assert(v.chunkBy!((a,b) => a % i == b % i).equal!equal([[2,4,8],[3],[6],[9,1,5,7]])); } @system unittest @@ -1886,18 +2273,19 @@ if (isForwardRange!Range) * In other languages this is often called `partitionBy`, `groupBy` * or `sliceWhen`. * - * Equivalence is defined by the predicate $(D pred), which can be either + * Equivalence is defined by the predicate `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)) + * passed to $(REF unaryFun, std,functional). In the binary form, two range elements + * `a` and `b` are considered equivalent if `pred(a,b)` is true. In + * unary form, two elements are considered equivalent if `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. + * reflexive (`pred(x,x)` is always true), symmetric + * (`pred(x,y) == pred(y,x)`), and transitive (`pred(x,y) && pred(y,z)` + * implies `pred(x,z)`). If this is not the case, the range returned by + * chunkBy may assert at runtime or behave erratically. Use $(LREF splitWhen) + * if you want to chunk by a predicate that is not an equivalence relation. * * Params: * pred = Predicate for determining equivalence. @@ -1907,7 +2295,8 @@ if (isForwardRange!Range) * 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. + * subrange itself. Copying the range currently has reference semantics, but this may + * change in the future. * * Notes: * @@ -1923,7 +2312,20 @@ if (isForwardRange!Range) auto chunkBy(alias pred, Range)(Range r) if (isInputRange!Range) { - return ChunkByImpl!(pred, Range)(r); + static if (ChunkByImplIsUnary!(pred, Range)) + { + enum opType = GroupingOpType.unary; + alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b)); + } + else + { + enum opType = GroupingOpType.binaryEquivalent; + alias eq = binaryFun!pred; + } + static if (isForwardRange!Range) + return ChunkByImpl!(pred, eq, opType, Range)(r); + else + return ChunkByImpl!(pred, Range)(r); } /// Showing usage with binary predicate: @@ -1953,20 +2355,6 @@ if (isInputRange!Range) ])); } -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 { @@ -2032,11 +2420,22 @@ version (none) // this example requires support for non-equivalence relations 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(); } + @property auto front() pure @safe nothrow { assert(!empty); return data.front; } + void popFront() pure @safe nothrow { assert(!empty); data.popFront(); } } auto refInputRange(R)(R range) { return new RefInputRange!R(range); } + // An input range API with value semantics. + struct ValInputRange(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 { assert(!empty); return data.front; } + void popFront() pure @safe nothrow { assert(!empty); data.popFront(); } + } + auto valInputRange(R)(R range) { return ValInputRange!R(range); } + { auto arr = [ Item(1,2), Item(1,3), Item(2,3) ]; static assert(isForwardRange!(typeof(arr))); @@ -2067,7 +2466,7 @@ version (none) // this example requires support for non-equivalence relations assert(byY2.front[1].equal([ Item(1,2) ])); } - // Test non-forward input ranges. + // Test non-forward input ranges with reference semantics. { auto range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); auto byX = chunkBy!(a => a.x)(range); @@ -2091,14 +2490,371 @@ version (none) // this example requires support for non-equivalence relations assert(byY.empty); assert(range.empty); } + + // Test non-forward input ranges with value semantics. + { + auto range = valInputRange([ 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); // Opposite of refInputRange test + + range = valInputRange([ 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); // Opposite of refInputRange test + } + + /* https://issues.dlang.org/show_bug.cgi?id=19532 + * General behavior of non-forward input ranges. + * + * - If the same chunk is retrieved multiple times via front, the separate chunk + * instances refer to a shared range segment that advances as a single range. + * - Emptying a chunk via popFront does not implicitly popFront the chunk off + * main range. The chunk is still available via front, it is just empty. + */ + { + import std.algorithm.comparison : equal; + import core.exception : AssertError; + import std.exception : assertThrown; + + auto a = [[0, 0], [0, 1], + [1, 2], [1, 3], [1, 4], + [2, 5], [2, 6], + [3, 7], + [4, 8]]; + + // Value input range + { + auto r = valInputRange(a).chunkBy!((a, b) => a[0] == b[0]); + + size_t numChunks = 0; + while (!r.empty) + { + ++numChunks; + auto chunk = r.front; + while (!chunk.empty) + { + assert(r.front.front[1] == chunk.front[1]); + chunk.popFront; + } + assert(!r.empty); + assert(r.front.empty); + r.popFront; + } + + assert(numChunks == 5); + + // Now front and popFront should assert. + bool thrown = false; + try r.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r.popFront; + catch (AssertError) thrown = true; + assert(thrown); + } + + // Reference input range + { + auto r = refInputRange(a).chunkBy!((a, b) => a[0] == b[0]); + + size_t numChunks = 0; + while (!r.empty) + { + ++numChunks; + auto chunk = r.front; + while (!chunk.empty) + { + assert(r.front.front[1] == chunk.front[1]); + chunk.popFront; + } + assert(!r.empty); + assert(r.front.empty); + r.popFront; + } + + assert(numChunks == 5); + + // Now front and popFront should assert. + bool thrown = false; + try r.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r.popFront; + catch (AssertError) thrown = true; + assert(thrown); + } + + // Ensure that starting with an empty range doesn't create an empty chunk. + { + int[] emptyRange = []; + + auto r1 = valInputRange(emptyRange).chunkBy!((a, b) => a == b); + auto r2 = refInputRange(emptyRange).chunkBy!((a, b) => a == b); + + assert(r1.empty); + assert(r2.empty); + + bool thrown = false; + try r1.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r1.popFront; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r2.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r2.popFront; + catch (AssertError) thrown = true; + assert(thrown); + } + } + + // https://issues.dlang.org/show_bug.cgi?id=19532 - Using roundRobin/chunkBy + { + import std.algorithm.comparison : equal; + import std.range : roundRobin; + + auto a0 = [0, 1, 3, 6]; + auto a1 = [0, 2, 4, 6, 7]; + auto a2 = [1, 2, 4, 6, 8, 8, 9]; + + auto expected = + [[0, 0], [1, 1], [2, 2], [3], [4, 4], [6, 6, 6], [7], [8, 8], [9]]; + + auto r1 = roundRobin(valInputRange(a0), valInputRange(a1), valInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r1.equal!equal(expected)); + + auto r2 = roundRobin(refInputRange(a0), refInputRange(a1), refInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r2.equal!equal(expected)); + + auto r3 = roundRobin(a0, a1, a2).chunkBy!((a, b) => a == b); + assert(r3.equal!equal(expected)); + } + + // https://issues.dlang.org/show_bug.cgi?id=19532 - Using merge/chunkBy + { + import std.algorithm.comparison : equal; + import std.algorithm.sorting : merge; + + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + + auto r1 = merge(valInputRange(a0), valInputRange(a1), valInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r1.equal!equal(expected)); + + auto r2 = merge(refInputRange(a0), refInputRange(a1), refInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r2.equal!equal(expected)); + + auto r3 = merge(a0, a1, a2).chunkBy!((a, b) => a == b); + assert(r3.equal!equal(expected)); + } + + // https://issues.dlang.org/show_bug.cgi?id=19532 - Using chunkBy/map-fold + { + import std.algorithm.comparison : equal; + import std.algorithm.iteration : fold, map; + + auto a = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 9]; + auto expected = [0, 3, 4, 6, 8, 5, 18, 7, 16, 9]; + + auto r1 = a + .chunkBy!((a, b) => a == b) + .map!(c => c.fold!((a, b) => a + b)); + assert(r1.equal(expected)); + + auto r2 = valInputRange(a) + .chunkBy!((a, b) => a == b) + .map!(c => c.fold!((a, b) => a + b)); + assert(r2.equal(expected)); + + auto r3 = refInputRange(a) + .chunkBy!((a, b) => a == b) + .map!(c => c.fold!((a, b) => a + b)); + assert(r3.equal(expected)); + } + + // https://issues.dlang.org/show_bug.cgi?id=16169 + // https://issues.dlang.org/show_bug.cgi?id=17966 + // https://issues.dlang.org/show_bug.cgi?id=19532 + // Using multiwayMerge/chunkBy + { + import std.algorithm.comparison : equal; + import std.algorithm.setops : multiwayMerge; + + { + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + auto r = multiwayMerge([a0, a1, a2]).chunkBy!((a, b) => a == b); + assert(r.equal!equal(expected)); + } + { + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + auto r = + multiwayMerge([valInputRange(a0), valInputRange(a1), valInputRange(a2)]) + .chunkBy!((a, b) => a == b); + assert(r.equal!equal(expected)); + } + { + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + auto r = + multiwayMerge([refInputRange(a0), refInputRange(a1), refInputRange(a2)]) + .chunkBy!((a, b) => a == b); + assert(r.equal!equal(expected)); + } + } + + // https://issues.dlang.org/show_bug.cgi?id=20496 + { + auto r = [1,1,1,2,2,2,3,3,3]; + r.chunkBy!((ref e1, ref e2) => e1 == e2); + } +} + + + +// https://issues.dlang.org/show_bug.cgi?id=13805 +@system unittest +{ + [""].map!((s) => s).chunkBy!((x, y) => true); +} + +/** +Splits a forward range into subranges in places determined by a binary +predicate. + +When iterating, one element of `r` is compared with `pred` to the next +element. If `pred` return true, a new subrange is started for the next element. +Otherwise, they are part of the same subrange. + +If the elements are compared with an inequality (!=) operator, consider +$(LREF chunkBy) instead, as it's likely faster to execute. + +Params: +pred = Predicate for determining where to split. The earlier element in the +source range is always given as the first argument. +r = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to be split. +Returns: a range of subranges of `r`, split such that within a given subrange, +calling `pred` with any pair of adjacent elements as arguments returns `false`. +Copying the range currently has reference semantics, but this may change in the future. + +See_also: +$(LREF splitter), which uses elements as splitters instead of element-to-element +relations. +*/ + +auto splitWhen(alias pred, Range)(Range r) +if (isForwardRange!Range) +{ import std.functional : not; + return ChunkByImpl!(not!pred, not!pred, GroupingOpType.binaryAny, Range)(r); +} + +//FIXME: these should be @safe +/// +nothrow pure @system unittest +{ + import std.algorithm.comparison : equal; + import std.range : dropExactly; + auto source = [4, 3, 2, 11, 0, -3, -3, 5, 3, 0]; + + auto result1 = source.splitWhen!((a,b) => a <= b); + assert(result1.save.equal!equal([ + [4, 3, 2], + [11, 0, -3], + [-3], + [5, 3, 0] + ])); + + //splitWhen, like chunkBy, is currently a reference range (this may change + //in future). Remember to call `save` when appropriate. + auto result2 = result1.dropExactly(2); + assert(result1.save.equal!equal([ + [-3], + [5, 3, 0] + ])); +} + +//ensure we don't iterate the underlying range twice +nothrow @system unittest +{ + import std.algorithm.comparison : equal; + import std.math.algebraic : abs; + + struct SomeRange + { + int[] elements; + static int popfrontsSoFar; + + auto front(){return elements[0];} + nothrow void popFront() + { popfrontsSoFar++; + elements = elements[1 .. $]; + } + auto empty(){return elements.length == 0;} + auto save(){return this;} + } + + auto result = SomeRange([10, 9, 8, 5, 0, 1, 0, 8, 11, 10, 8, 12]) + .splitWhen!((a, b) => abs(a - b) >= 3); + + assert(result.equal!equal([ + [10, 9, 8], + [5], + [0, 1, 0], + [8], + [11, 10, 8], + [12] + ])); + + assert(SomeRange.popfrontsSoFar == 12); } // 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); + auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].splitWhen!((x, y) => ((x*y) % 3) > 0); assert(r.equal!equal([ [1], [2, 3, 4], @@ -2107,10 +2863,25 @@ version (none) // This requires support for non-equivalence relations ])); } -// Issue 13805 -@system unittest +nothrow pure @system unittest { - [""].map!((s) => s).chunkBy!((x, y) => true); + // Grouping by maximum adjacent difference: + import std.math.algebraic : abs; + import std.algorithm.comparison : equal; + auto r3 = [1, 3, 2, 5, 4, 9, 10].splitWhen!((a, b) => abs(a-b) >= 3); + assert(r3.equal!equal([ + [1, 3, 2], + [5, 4], + [9, 10] + ])); +} + +// empty range splitWhen +@nogc nothrow pure @system unittest +{ + int[1] sliceable; + auto result = sliceable[0 .. 0].splitWhen!((a,b) => a+b > 10); + assert(result.empty); } // joiner @@ -2127,13 +2898,21 @@ Params: 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. +A range of elements in the joined range. This will be a bidirectional range if +both outer and inner ranges of `RoR` are at least bidirectional ranges. Else if +both outer and inner ranges of `RoR` are forward ranges, the returned range will +be likewise. Otherwise it will be only an input range. The +$(REF_ALTTEXT range bidirectionality, isBidirectionalRange, std,range,primitives) +is propagated if no separator is specified. See_also: $(REF chain, std,range), which chains a sequence of ranges with compatible elements into a single range. + +Note: +When both outer and inner ranges of `RoR` are bidirectional and the joiner is +iterated from the back to the front, the separator will still be consumed from +front to back, even if it is a bidirectional range too. */ auto joiner(RoR, Separator)(RoR r, Separator sep) if (isInputRange!RoR && isInputRange!(ElementType!RoR) @@ -2144,18 +2923,78 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) { 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{ + bool inputStartsWithEmpty = false; + static if (isBidirectional) + { + private ElementType!RoR _currentBack; + bool inputEndsWithEmpty = false; + } + enum isBidirectional = isBidirectionalRange!RoR && + isBidirectionalRange!(ElementType!RoR); + static if (isRandomAccessRange!Separator) + { + static struct CurrentSep + { + private Separator _sep; + private size_t sepIndex; + private size_t sepLength; // cache the length for performance + auto front() { return _sep[sepIndex]; } + void popFront() { sepIndex++; } + auto empty() { return sepIndex >= sepLength; } + auto save() + { + auto copy = this; + copy._sep = _sep; + return copy; + } + void reset() + { + sepIndex = 0; + } + + void initialize(Separator sep) + { + _sep = sep; + sepIndex = sepLength = _sep.length; + } + } + } + else + { + static struct CurrentSep + { + private Separator _sep; + Separator payload; + + alias payload this; + + auto save() + { + auto copy = this; + copy._sep = _sep; + return copy; + } + + void reset() + { + payload = _sep.save; + } + + void initialize(Separator sep) + { + _sep = sep; + } + } + } + + private CurrentSep _currentSep; + static if (isBidirectional) + { + private CurrentSep _currentBackSep; + } + + private void setItem() + { if (!_items.empty) { // If we're exporting .save, we must not consume any of the @@ -2167,20 +3006,22 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) else _current = _items.front; } - }; + } private void useSeparator() { // Separator must always come after an item. - assert(_currentSep.empty && !_items.empty, - "joiner: internal error"); + assert(_currentSep.empty, + "Attempting to reset a non-empty separator"); + assert(!_items.empty, + "Attempting to use a separator in an empty joiner"); _items.popFront(); // If there are no more items, we're done, since separators are not // terminators. if (_items.empty) return; - if (_sep.empty) + if (_currentSep._sep.empty) { // Advance to the next range in the // input @@ -2189,41 +3030,29 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) _items.popFront(); if (_items.empty) return; } - mixin(setItem); + setItem; } else { - _currentSep = _sep.save; - assert(!_currentSep.empty); + _currentSep.reset; + assert(!_currentSep.empty, "separator must not be 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; + _currentSep.initialize(sep); + static if (isBidirectional) + _currentBackSep.initialize(sep); //mixin(useItem); // _current should be initialized in place if (_items.empty) + { _current = _current.init; // set invalid state + static if (isBidirectional) + _currentBack = _currentBack.init; + } else { // If we're exporting .save, we must not consume any of the @@ -2235,11 +3064,43 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) else _current = _items.front; - if (_current.empty) + static if (isBidirectional) { - // No data in the current item - toggle to use the separator - useSeparator(); - } + _currentBack = _items.back.save; + + if (_currentBack.empty) + { + // No data in the currentBack item - toggle to use + // the separator + inputEndsWithEmpty = true; + } + } + + if (_current.empty) + { + // No data in the current item - toggle to use the separator + inputStartsWithEmpty = true; + + // If RoR contains a single empty element, + // the returned Result will always be empty + import std.range : dropOne; + static if (hasLength!RoR) + { + if (_items.length == 1) + _items.popFront; + } + else static if (isForwardRange!RoR) + { + if (_items.save.dropOne.empty) + _items.popFront; + } + else + { + auto _itemsCopy = _items; + if (_itemsCopy.dropOne.empty) + _items.popFront; + } + } } } @@ -2248,8 +3109,18 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) return _items.empty; } + //no data in the first item of the initial range - use the separator + private enum useSepIfFrontIsEmpty = q{ + if (inputStartsWithEmpty) + { + useSeparator(); + inputStartsWithEmpty = false; + } + }; + @property ElementType!(ElementType!RoR) front() { + mixin(useSepIfFrontIsEmpty); if (!_currentSep.empty) return _currentSep.front; assert(!_current.empty, "Attempting to fetch the front of an empty joiner."); return _current.front; @@ -2259,18 +3130,27 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) { assert(!_items.empty, "Attempting to popFront an empty joiner."); // Using separator? + mixin(useSepIfFrontIsEmpty); + if (!_currentSep.empty) { _currentSep.popFront(); - if (!_currentSep.empty) return; - mixin(useItem); + if (_currentSep.empty && !_items.empty) + { + setItem; + if (_current.empty) + { + // No data in the current item - toggle to use the separator + useSeparator(); + } + } } else { // we're using the range _current.popFront(); - if (!_current.empty) return; - useSeparator(); + if (_current.empty) + useSeparator(); } } @@ -2281,11 +3161,103 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) Result copy = this; copy._items = _items.save; copy._current = _current.save; - copy._sep = _sep.save; copy._currentSep = _currentSep.save; + static if (isBidirectional) + { + copy._currentBack = _currentBack; + copy._currentBackSep = _currentBackSep; + } return copy; } } + + static if (isBidirectional) + { + //no data in the last item of the initial range - use the separator + private enum useSepIfBackIsEmpty = q{ + if (inputEndsWithEmpty) + { + useBackSeparator; + inputEndsWithEmpty = false; + } + }; + + private void setBackItem() + { + if (!_items.empty) + { + _currentBack = _items.back.save; + } + } + + private void useBackSeparator() + { + // Separator must always come after an item. + assert(_currentBackSep.empty, + "Attempting to reset a non-empty separator"); + assert(!_items.empty, + "Attempting to use a separator in an empty joiner"); + _items.popBack(); + + // If there are no more items, we're done, since separators are not + // terminators. + if (_items.empty) return; + + if (_currentBackSep._sep.empty) + { + // Advance to the next range in the + // input + while (_items.back.empty) + { + _items.popBack(); + if (_items.empty) return; + } + setBackItem; + } + else + { + _currentBackSep.reset; + assert(!_currentBackSep.empty, "separator must not be empty"); + } + } + + @property ElementType!(ElementType!RoR) back() + { + mixin(useSepIfBackIsEmpty); + + if (!_currentBackSep.empty) return _currentBackSep.front; + assert(!_currentBack.empty, "Attempting to fetch the back of an empty joiner."); + return _currentBack.back; + } + + void popBack() + { + assert(!_items.empty, "Attempting to popBack an empty joiner."); + + mixin(useSepIfBackIsEmpty); + + if (!_currentBackSep.empty) + { + _currentBackSep.popFront(); + if (_currentBackSep.empty && !_items.empty) + { + setBackItem; + if (_currentBack.empty) + { + // No data in the current item - toggle to use the separator + useBackSeparator(); + } + } + } + else + { + // we're using the range + _currentBack.popBack(); + if (_currentBack.empty) + useBackSeparator(); + } + } + } } return Result(r, sep); } @@ -2305,6 +3277,12 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) assert(["", ""].joiner("xyz").equal("xyz")); } +@safe pure nothrow unittest +{ + //joiner with separator can return a bidirectional range + assert(isBidirectionalRange!(typeof(["abc", "def"].joiner("...")))); +} + @system unittest { import std.algorithm.comparison : equal; @@ -2320,7 +3298,7 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) import std.algorithm.comparison : equal; import std.range; - // Related to issue 8061 + // Related to https://issues.dlang.org/show_bug.cgi?id=8061 auto r = joiner([ inputRangeObject("abc"), inputRangeObject("def"), @@ -2357,7 +3335,7 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) assert(equal(u, "+-abc+-+-def+-")); - // Issue 13441: only(x) as separator + // https://issues.dlang.org/show_bug.cgi?id=13441: only(x) as separator string[][] lines = [null]; lines .joiner(only("b")) @@ -2417,6 +3395,174 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) static assert(isForwardRange!(typeof(joiner([""], "")))); } +@safe pure unittest +{ + { + import std.algorithm.comparison : equal; + auto r = joiner(["abc", "def", "ghi"], "?!"); + char[] res; + while (!r.empty) + { + res ~= r.back; + r.popBack; + } + assert(res.equal("ihg?!fed?!cba")); + } + + { + wchar[] sep = ['Ș', 'Ț']; + auto r = joiner(["","abc",""],sep); + wchar[] resFront; + wchar[] resBack; + + auto rCopy = r.save; + while (!r.empty) + { + resFront ~= r.front; + r.popFront; + } + + while (!rCopy.empty) + { + resBack ~= rCopy.back; + rCopy.popBack; + } + + import std.algorithm.comparison : equal; + + assert(resFront.equal("ȘȚabcȘȚ")); + assert(resBack.equal("ȘȚcbaȘȚ")); + } + + { + import std.algorithm.comparison : equal; + auto r = [""]; + r.popBack; + assert(r.joiner("AB").equal("")); + } + + { + auto r = ["", "", "", "abc", ""].joiner("../"); + auto rCopy = r.save; + + char[] resFront; + char[] resBack; + + while (!r.empty) + { + resFront ~= r.front; + r.popFront; + } + + while (!rCopy.empty) + { + resBack ~= rCopy.back; + rCopy.popBack; + } + + import std.algorithm.comparison : equal; + + assert(resFront.equal("../../../abc../")); + assert(resBack.equal("../cba../../../")); + } + + { + auto r = ["", "abc", ""].joiner("./"); + auto rCopy = r.save; + r.popBack; + rCopy.popFront; + + auto rRev = r.save; + auto rCopyRev = rCopy.save; + + char[] r1, r2, r3, r4; + + while (!r.empty) + { + r1 ~= r.back; + r.popBack; + } + + while (!rCopy.empty) + { + r2 ~= rCopy.front; + rCopy.popFront; + } + + while (!rRev.empty) + { + r3 ~= rRev.front; + rRev.popFront; + } + + while (!rCopyRev.empty) + { + r4 ~= rCopyRev.back; + rCopyRev.popBack; + } + + import std.algorithm.comparison : equal; + + assert(r1.equal("/cba./")); + assert(r2.equal("/abc./")); + assert(r3.equal("./abc")); + assert(r4.equal("./cba")); + } +} + +@system unittest +{ + import std.range; + import std.algorithm.comparison : equal; + + assert(inputRangeObject([""]).joiner("lz").equal("")); +} + +@safe pure unittest +{ + struct inputRangeStrings + { + private string[] strings; + + string front() + { + return strings[0]; + } + + void popFront() + { + strings = strings[1..$]; + } + + bool empty() const + { + return strings.length == 0; + } + } + + auto arr = inputRangeStrings([""]); + + import std.algorithm.comparison : equal; + + assert(arr.joiner("./").equal("")); +} + +@safe pure unittest +{ + auto r = joiner(["", "", "abc", "", ""], ""); + char[] res; + while (!r.empty) + { + res ~= r.back; + r.popBack; + } + + import std.algorithm.comparison : equal; + + assert(res.equal("cba")); +} + + /// Ditto auto joiner(RoR)(RoR r) if (isInputRange!RoR && isInputRange!(ElementType!RoR)) @@ -2426,50 +3572,33 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) 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; - }; + enum isBidirectional = isForwardRange!RoR && isForwardRange!(ElementType!RoR) && + isBidirectionalRange!RoR && isBidirectionalRange!(ElementType!RoR); + static if (isBidirectional) + { + ElementType!RoR _currentBack; + bool reachedFinalElement; + } + this(RoR items, ElementType!RoR current) { _items = items; _current = current; + static if (isBidirectional && hasNested!Result) + _currentBack = typeof(_currentBack).init; } + 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 (isBidirectional && hasNested!Result) + _currentBack = typeof(_currentBack).init; + // field _current must be initialized in constructor, because it is nested struct + mixin(popFrontEmptyElements); + static if (isBidirectional) + mixin(popBackEmptyElements); } static if (isInfinite!RoR) { @@ -2493,16 +3622,45 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) _current.popFront(); if (_current.empty) { - assert(!_items.empty); + assert(!_items.empty, "Attempting to popFront an empty joiner."); _items.popFront(); - mixin(prepare); + mixin(popFrontEmptyElements); } } + + private enum popFrontEmptyElements = q{ + // Skip over empty subranges. + while (!_items.empty && _items.front.empty) + { + _items.popFront(); + } + if (!_items.empty) + { + // 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; + } + else + { + _current = typeof(_current).init; + } + }; + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) { @property auto save() { - return Result(_items.save, _current.save); + auto r = Result(_items.save, _current.save); + static if (isBidirectional) + { + r._currentBack = _currentBack.save; + r.reachedFinalElement = reachedFinalElement; + } + return r; } } @@ -2520,49 +3678,200 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) _current.front = element; } } - } - return Result(r); -} -@safe unittest -{ - import std.algorithm.comparison : equal; - import std.range.interfaces : inputRangeObject; - import std.range : repeat; + static if (isBidirectional) + { + bool checkFinalElement() + { + import std.range : dropOne; - 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])); -} + if (reachedFinalElement) + return true; + static if (hasLength!(typeof(_items))) + { + if (_items.length == 1) + reachedFinalElement = true; + } + else + { + if (_items.save.dropOne.empty) + reachedFinalElement = true; + } -@system unittest -{ - import std.algorithm.comparison : equal; - import std.range.interfaces : inputRangeObject; + return false; + } - // bugzilla 8240 - assert(equal(joiner([inputRangeObject("")]), "")); + @property auto ref back() + { + assert(!empty, "Attempting to fetch the back of an empty joiner."); + if (reachedFinalElement) + return _current.back; + else + return _currentBack.back; + } - // issue 8792 - auto b = [[1], [2], [3]]; - auto jb = joiner(b); - auto js = jb.save; - assert(equal(jb, js)); + void popBack() + { + assert(!_current.empty, "Attempting to popBack an empty joiner."); + if (checkFinalElement) + _current.popBack(); + else + _currentBack.popBack(); + + bool isEmpty = reachedFinalElement ? _current.empty : _currentBack.empty; + if (isEmpty) + { + assert(!_items.empty, "Attempting to popBack an empty joiner."); + _items.popBack(); + mixin(popBackEmptyElements); + } + } + + private enum popBackEmptyElements = q{ + // Skip over empty subranges. + while (!_items.empty && _items.back.empty) + { + _items.popBack(); + } + if (!_items.empty) + { + checkFinalElement; + // 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)) + { + if (reachedFinalElement) + _current = _items.back.save; + else + _currentBack = _items.back.save; + } + else + { + if (reachedFinalElement) + _current = _items.back; + else + _currentBack = _items.back; + } + } + else + { + _current = typeof(_current).init; + _currentBack = typeof(_currentBack).init; + } + }; + + static if (hasAssignableElements!(ElementType!RoR)) + { + @property void back(ElementType!(ElementType!RoR) element) + { + assert(!empty, "Attempting to assign to back of an empty joiner."); + if (reachedFinalElement) + _current.back = element; + else + _currentBack.back = element; + } + + @property void back(ref ElementType!(ElementType!RoR) element) + { + assert(!empty, "Attempting to assign to back of an empty joiner."); + if (reachedFinalElement) + _current.back = element; + else + _currentBack.back = element; + } + } + } + } + return Result(r); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : repeat; + + assert([""].joiner.equal("")); + assert(["", ""].joiner.equal("")); + assert(["", "abc"].joiner.equal("abc")); + assert(["abc", ""].joiner.equal("abc")); + assert(["abc", "def"].joiner.equal("abcdef")); + assert(["Mary", "has", "a", "little", "lamb"].joiner.equal("Maryhasalittlelamb")); + assert("abc".repeat(3).joiner.equal("abcabcabc")); +} + +/// joiner allows in-place mutation! +@safe unittest +{ + import std.algorithm.comparison : equal; + 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])); +} + +/// insert characters fully lazily into a string +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.range : chain, cycle, iota, only, retro, take, zip; + import std.format : format; + + static immutable number = "12345678"; + static immutable delimiter = ","; + auto formatted = number.retro + .zip(3.iota.cycle.take(number.length)) + .map!(z => chain(z[0].only, z[1] == 2 ? delimiter : null)) + .joiner + .retro; + static immutable expected = "12,345,678"; + assert(formatted.equal(expected)); +} + +@safe unittest +{ + import std.range.interfaces : inputRangeObject; + static assert(isInputRange!(typeof(joiner([""])))); + static assert(isForwardRange!(typeof(joiner([""])))); +} + +@safe unittest +{ + // Initial version of PR #6115 caused a compilation failure for + // https://github.com/BlackEdder/ggplotd/blob/d4428c08db5ffdc05dfd29690bf7da9073ea1dc5/source/ggplotd/stat.d#L562-L583 + import std.range : zip; + int[] xCoords = [1, 2, 3]; + int[] yCoords = [4, 5, 6]; + auto coords = zip(xCoords, xCoords[1..$]).map!( (xr) { + return zip(yCoords, yCoords[1..$]).map!( (yr) { + return [ + [[xr[0], xr[0], xr[1]], + [yr[0], yr[1], yr[1]]], + [[xr[0], xr[1], xr[1]], + [yr[0], yr[0], yr[1]]] + ]; + }).joiner; + }).joiner; +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range.interfaces : inputRangeObject; + import std.range : retro; + + // https://issues.dlang.org/show_bug.cgi?id=8240 + assert(equal(joiner([inputRangeObject("")]), "")); + assert(equal(joiner([inputRangeObject("")]).retro, "")); + + // https://issues.dlang.org/show_bug.cgi?id=8792 + auto b = [[1], [2], [3]]; + auto jb = joiner(b); + auto js = jb.save; + assert(equal(jb, js)); auto js2 = jb.save; jb.popFront(); @@ -2573,6 +3882,118 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) assert(!equal(js2, js)); } +// https://issues.dlang.org/show_bug.cgi?id=19213 +@system unittest +{ + auto results = [[1,2], [3,4]].map!(q => q.chunkBy!"a").joiner; + int i = 1; + foreach (ref e; results) + assert(e[0] == i++); +} + +/// joiner can be bidirectional +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + + auto a = [[1, 2, 3], [4, 5]]; + auto j = a.joiner; + j.back = 44; + assert(a == [[1, 2, 3], [4, 44]]); + assert(equal(j.retro, [44, 4, 3, 2, 1])); +} + +// bidirectional joiner: test for filtering empty elements +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + + alias El = (e) => new int(e); + auto a = [null, [null, El(1), null, El(2), null, El(3), null], null, [null, El(4), null, El(5), null]]; + auto j = a.joiner; + + alias deref = a => a is null ? -1 : *a; + auto expected = [-1, 5, -1, 4, -1, -1, 3, -1, 2, -1, 1, -1]; + // works with .save. + assert(j.save.retro.map!deref.equal(expected)); + // and without .save + assert(j.retro.map!deref.equal(expected)); + assert(j.retro.map!deref.equal(expected)); +} + +// bidirectional joiner is @nogc +@safe @nogc unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota, only, retro; + + auto a = only(iota(1, 4), iota(4, 6)); + auto j = a.joiner; + static immutable expected = [5 , 4, 3, 2, 1]; + assert(equal(j.retro, expected)); +} + +// bidirectional joiner supports assignment to the back +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : popBackN; + + auto a = [[1, 2, 3], [4, 5]]; + auto j = a.joiner; + j.back = 55; + assert(a == [[1, 2, 3], [4, 55]]); + j.popBackN(2); + j.back = 33; + assert(a == [[1, 2, 33], [4, 55]]); +} + +// bidirectional joiner works with auto-decoding +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + + auto a = ["😀😐", "😠"]; + auto j = a.joiner; + assert(j.retro.equal("😠😐😀")); +} + +// test two-side iteration +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : popBackN; + + auto arrs = [ + [[1], [2], [3], [4], [5]], + [[1], [2, 3, 4], [5]], + [[1, 2, 3, 4, 5]], + ]; + foreach (arr; arrs) + { + auto a = arr.joiner; + assert(a.front == 1); + assert(a.back == 5); + a.popFront; + assert(a.front == 2); + assert(a.back == 5); + a.popBack; + assert(a.front == 2); + assert(a.back == 4); + a.popFront; + assert(a.front == 3); + assert(a.back == 4); + a.popBack; + assert(a.front == 3); + assert(a.back == 3); + a.popBack; + assert(a.empty); + } +} + @safe unittest { import std.algorithm.comparison : equal; @@ -2667,7 +4088,7 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) to!string("Unexpected result: '%s'"d.algoFormat(result))); } -// Issue 8061 +// https://issues.dlang.org/show_bug.cgi?id=8061 @system unittest { import std.conv : to; @@ -2692,17 +4113,23 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) { return element; } + alias back = front; enum empty = false; - void popFront() + auto save() { + return this; } + void popFront() {} + alias popBack = popFront; + @property void front(int newValue) { element = newValue; } + alias back = front; } static assert(isInputRange!AssignableRange); @@ -2712,34 +4139,53 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) 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); + } + { + auto joined = joiner(repeat(range)); + joined.back = 5; + assert(range.element == 5); + assert(joined.back == 5); - auto joined = joiner(repeat(range)); - joined.front = 5; - assert(range.element == 5); - assert(joined.front == 5); + joined.popBack; + int byRef = 7; + joined.back = byRef; + assert(range.element == byRef); + assert(joined.back == byRef); + } +} - joined.popFront; - int byRef = 7; - joined.front = byRef; - assert(range.element == byRef); - assert(joined.front == byRef); +// https://issues.dlang.org/show_bug.cgi?id=19850 +@safe pure unittest +{ + assert([[0]].joiner.save.back == 0); } /++ -Implements the homonym function (also known as $(D accumulate), $(D -compress), $(D inject), or $(D foldl)) present in various programming +Implements the homonym function (also known as `accumulate`, $(D +compress), `inject`, or `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)) +The call `reduce!(fun)(seed, range)` first assigns `seed` to +an internal variable `result`, also called the accumulator. +Then, for each element `x` in `range`, `result = fun(result, x)` +gets evaluated. Finally, `result` is returned. +The one-argument version `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) + the accumulated `result` Params: fun = one or more functions @@ -2747,11 +4193,11 @@ Params: 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 fold) is functionally equivalent to $(LREF _reduce) with the argument + order reversed, and without the need to use $(REF_ALTTEXT `tuple`,tuple,std,typecons) + 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 + $(LREF sum) is similar to `reduce!((a, b) => a + b)` that offers pairwise summing of floating point numbers. +/ template reduce(fun...) @@ -2764,23 +4210,23 @@ 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. + No-seed version. The first element of `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. + For each function `f` in `fun`, the corresponding + seed type `S` is `Unqual!(typeof(f(e, e)))`, where `e` is an + element of `r`: `ElementType!R` for ranges, + and `ForeachType!R` otherwise. - Once S has been determined, then $(D S s = e;) and $(D s = f(s, e);) + Once S has been determined, then `S s = e;` and `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) + r = an iterable value as defined by `isIterable` Returns: the final result of the accumulator applied to the iterable + + Throws: `Exception` if `r` is empty +/ auto reduce(R)(R r) if (isIterable!R) @@ -2791,7 +4237,13 @@ if (fun.length >= 1) static if (isInputRange!R) { - enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); + // no need to throw if range is statically known to be non-empty + static if (!__traits(compiles, + { + static assert(r.length > 0); + })) + 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); @@ -2804,19 +4256,19 @@ if (fun.length >= 1) } /++ - 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). + 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 - $(D reduce) will operate on an unqualified copy. If this happens - then the returned type will not perfectly match $(D S). + `reduce` will operate on an unqualified copy. If this happens + then the returned type will not perfectly match `S`. - Use $(D fold) instead of $(D reduce) to use the seed version in a UFCS chain. + Use `fold` instead of `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) + r = an iterable value as defined by `isIterable` Returns: the final result of the accumulator applied to the iterable @@ -2853,7 +4305,7 @@ if (fun.length >= 1) alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); static if (mustInitialize) bool initialized = false; - foreach (/+auto ref+/ E e; r) // @@@4707@@@ + foreach (/+auto ref+/ E e; r) // https://issues.dlang.org/show_bug.cgi?id=4707 { foreach (i, f; binfuns) { @@ -2869,7 +4321,7 @@ if (fun.length >= 1) static if (mustInitialize) if (initialized == false) { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; foreach (i, f; binfuns) emplaceRef!(Args[i])(args[i], e); initialized = true; @@ -2880,8 +4332,15 @@ if (fun.length >= 1) 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."); + // no need to throw if range is statically known to be non-empty + static if (!__traits(compiles, + { + static assert(r.length > 0); + })) + { + if (!initialized) + throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value."); + } static if (Args.length == 1) return args[0]; @@ -2891,14 +4350,14 @@ if (fun.length >= 1) } /** -Many aggregate range operations turn out to be solved with $(D reduce) -quickly and easily. The example below illustrates $(D reduce)'s +Many aggregate range operations turn out to be solved with `reduce` +quickly and easily. The example below illustrates `reduce`'s remarkable power and flexibility. */ @safe unittest { import std.algorithm.comparison : max, min; - import std.math : approxEqual; + import std.math.operations : isClose; import std.range; int[] arr = [ 1, 2, 3, 4, 5 ]; @@ -2935,38 +4394,39 @@ remarkable power and flexibility. // 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)); + assert(isClose(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)); + assert(isClose(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 +is shared. That's why `reduce` accepts multiple functions. +If two or more functions are passed, `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.math.operations : isClose; + import std.math.algebraic : 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 + assert(isClose(r[0], 2)); // minimum + assert(isClose(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 + assert(isClose(r[0], 35)); // sum + assert(isClose(r[1], 233)); // sum of squares // Compute average and standard deviation from the above auto avg = r[0] / a.length; assert(avg == 5); @@ -3003,7 +4463,7 @@ The number of seeds must be correspondingly increased. assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]"); } -@system unittest +@safe unittest { import std.algorithm.comparison : max, min; import std.exception : assertThrown; @@ -3015,7 +4475,7 @@ The number of seeds must be correspondingly increased. { bool actEmpty; - int opApply(scope int delegate(ref int) dg) + int opApply(scope int delegate(ref int) @safe dg) { int res; if (actEmpty) return res; @@ -3055,7 +4515,8 @@ The number of seeds must be correspondingly increased. @safe unittest { - // Issue #10408 - Two-function reduce of a const array. + // https://issues.dlang.org/show_bug.cgi?id=10408 + // Two-function reduce of a const array. import std.algorithm.comparison : max, min; import std.typecons : tuple, Tuple; @@ -3068,7 +4529,7 @@ The number of seeds must be correspondingly increased. @safe unittest { - //10709 + // https://issues.dlang.org/show_bug.cgi?id=10709 import std.typecons : tuple, Tuple; enum foo = "a + 0.5 * b"; @@ -3079,11 +4540,11 @@ The number of seeds must be correspondingly increased. assert(r2 == tuple(3, 3)); } -@system unittest +@safe unittest { static struct OpApply { - int opApply(int delegate(ref int) dg) + int opApply(int delegate(ref int) @safe dg) { int[] a = [1, 2, 3]; @@ -3135,7 +4596,8 @@ The number of seeds must be correspondingly increased. assert(minmaxElement([1, 2, 3]) == tuple(1, 3)); } -@safe unittest //12569 +// https://issues.dlang.org/show_bug.cgi?id=12569 +@safe unittest { import std.algorithm.comparison : max, min; import std.typecons : tuple; @@ -3156,13 +4618,27 @@ The number of seeds must be correspondingly increased. static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello")))); } -@safe unittest //13304 +// https://issues.dlang.org/show_bug.cgi?id=13304 +@safe unittest { int[] data; static assert(is(typeof(reduce!((a, b) => a + b)(data)))); assert(data.length == 0); } +// https://issues.dlang.org/show_bug.cgi?id=13880 +// reduce shouldn't throw if the length is statically known +pure nothrow @safe @nogc unittest +{ + import std.algorithm.comparison : min; + int[5] arr; + arr[2] = -1; + assert(arr.reduce!min == -1); + + int[0] arr0; + assert(reduce!min(42, arr0) == 42); +} + //Helper for Reduce private template ReduceSeedType(E) { @@ -3187,31 +4663,39 @@ private template ReduceSeedType(E) /++ -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)) +Implements the homonym function (also known as `accumulate`, $(D +compress), `inject`, or `foldl`) present in various programming +languages of functional flavor. The call `fold!(fun)(range, seed)` +first assigns `seed` to an internal variable `result`, +also called the accumulator. Then, for each element `x` in $(D +range), `result = fun(result, x)` gets evaluated. Finally, $(D +result) is returned. The one-argument version `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) +Params: + fun = the predicate function(s) to apply to the elements 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 + $(LREF sum) is similar to `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. + This is functionally equivalent to $(LREF reduce) with the argument order + reversed, and without the need to use $(REF_ALTTEXT `tuple`,tuple,std,typecons) + for multiple seeds. +/ template fold(fun...) if (fun.length >= 1) { + /** + Params: + r = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to fold + seed = the initial value of the accumulator + Returns: + the accumulated `result` + */ auto fold(R, S...)(R r, S seed) { static if (S.length < 2) @@ -3265,12 +4749,12 @@ if (fun.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 +The call `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 +The returned range contains the values `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 +same value as `fold!(fun)(seed, range)`. +The one-argument version `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 @@ -3289,6 +4773,11 @@ Returns: See_Also: $(HTTP en.wikipedia.org/wiki/Prefix_sum, Prefix Sum) + +Note: + + In functional programming languages this is typically called `scan`, `scanl`, + `scanLeft` or `reductions`. +/ template cumulativeFold(fun...) if (fun.length >= 1) @@ -3299,9 +4788,9 @@ if (fun.length >= 1) /++ 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`: + `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 + Once `S` has been determined, then `S s = e;` and `s = f(s, e);` must both be legal. Params: @@ -3422,13 +4911,7 @@ if (fun.length >= 1) } } - static if (hasLength!R) - { - @property size_t length() - { - return source.length; - } - } + mixin ImplementLength!source; } return Result(range, args); @@ -3440,7 +4923,7 @@ if (fun.length >= 1) { import std.algorithm.comparison : max, min; import std.array : array; - import std.math : approxEqual; + import std.math.operations : isClose; import std.range : chain; int[] arr = [1, 2, 3, 4, 5]; @@ -3477,11 +4960,11 @@ if (fun.length >= 1) // 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])); + assert(isClose(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])); + assert(isClose(r2, [3, 7, 107, 109.5, 112.5])); } /** @@ -3496,20 +4979,20 @@ The number of seeds must be correspondingly increased. { import std.algorithm.comparison : max, min; import std.algorithm.iteration : map; - import std.math : approxEqual; + import std.math.operations : isClose; 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 + assert(isClose(r.map!"a[0]", [3, 3, 3, 3, 3, 2, 2])); // minimum + assert(isClose(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 + assert(isClose(r2.map!"a[0]", [3, 7, 14, 25, 28, 30, 35])); // sum + assert(isClose(r2.map!"a[1]", [9, 25, 74, 195, 204, 208, 233])); // sum of squares } @safe unittest @@ -3551,7 +5034,7 @@ The number of seeds must be correspondingly increased. { import std.algorithm.comparison : max, min; import std.array : array; - import std.math : approxEqual; + import std.math.operations : isClose; import std.typecons : tuple; const float a = 0.0; @@ -3559,10 +5042,10 @@ The number of seeds must be correspondingly increased. float[] c = [1.2, 3, 3.3]; auto r = cumulativeFold!"a + b"(b, a); - assert(approxEqual(r, [1.2, 4.2, 7.5])); + assert(isClose(r, [1.2, 4.2, 7.5])); auto r2 = cumulativeFold!"a + b"(c, a); - assert(approxEqual(r2, [1.2, 4.2, 7.5])); + assert(isClose(r2, [1.2, 4.2, 7.5])); const numbers = [10, 30, 20]; enum m = numbers.cumulativeFold!(min).array; @@ -3573,16 +5056,16 @@ The number of seeds must be correspondingly increased. @safe unittest { - import std.math : approxEqual; + import std.math.operations : isClose; 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])); + assert(isClose(r1, [0, 0.5, 1.5, 3])); + assert(isClose(r2.map!"a[0]", [0, 0.5, 1.5, 3])); + assert(isClose(r2.map!"a[1]", [0, 0.5, 1.5, 3])); } @safe unittest @@ -3602,7 +5085,8 @@ The number of seeds must be correspondingly increased. assert(minmaxElement([1, 2, 3]).equal([tuple(1, 1), tuple(1, 2), tuple(1, 3)])); } -@safe unittest //12569 +// https://issues.dlang.org/show_bug.cgi?id=12569 +@safe unittest { import std.algorithm.comparison : equal, max, min; import std.typecons : tuple; @@ -3625,7 +5109,8 @@ The number of seeds must be correspondingly increased. static assert(!__traits(compiles, cumulativeFold!(all, all)("hello", tuple(1, 1)))); } -@safe unittest //13304 +// https://issues.dlang.org/show_bug.cgi?id=13304 +@safe unittest { int[] data; assert(data.cumulativeFold!((a, b) => a + b).empty); @@ -3652,55 +5137,66 @@ The number of seeds must be correspondingly increased. // 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. +Lazily splits a range using an element or range as a separator. +Separator ranges can be any narrow string type or sliceable range type. 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 +the split range. Use `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)). +The predicate is passed to $(REF binaryFun, std,functional) and accepts +any callable function that can be executed via `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. +Notes: + If splitting a string on whitespace and token compression is desired, + consider using `splitter` without specifying a separator. -If splitting a string on whitespace and token compression is desired, -consider using $(D splitter) without specifying a separator (see fourth overload -below). + If no separator is passed, the $(REF_ALTTEXT, unary, unaryFun, std,functional) + predicate `isTerminator` decides whether to accept an element of `r`. Params: pred = The predicate for comparing each element with the separator, - defaulting to $(D "a == b"). + defaulting to `"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. + split. Must support slicing and `.length` or be a narrow string type. + s = The element (or range) to be treated as the separator + between range segments to be split. + isTerminator = The predicate for deciding where to split the range when no separator is passed + keepSeparators = The flag for deciding if the separators are kept Constraints: - The predicate $(D pred) needs to accept an element of $(D r) and the - separator $(D s). + The predicate `pred` needs to accept an element of `r` and the + separator `s`. Returns: - An input range of the subranges of elements between separators. If $(D r) + An input range of the subranges of elements between separators. If `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. + When a range is used a separator, bidirectionality isn't possible. + + If keepSeparators is equal to Yes.keepSeparators the output will also contain the + separators. + + If an empty range is given, the result is an empty range. If a range with + one separator is given, the result is a range with two empty elements. See_Also: - $(REF _splitter, std,regex) for a version that splits using a regular -expression defined separator. + $(REF _splitter, std,regex) for a version that splits using a regular expression defined separator, + $(REF _split, std,array) for a version that splits eagerly and + $(LREF splitWhen), which compares adjacent elements instead of element against separator. */ -auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s) +auto splitter(alias pred = "a == b", + Flag!"keepSeparators" keepSeparators = No.keepSeparators, + 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 + struct Result { private: Range _input; @@ -3719,9 +5215,14 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) enum _separatorLength = 1; } + static if (keepSeparators) + { + bool _wasSeparator = true; + } + static if (isBidirectionalRange!Range) { - static size_t lastIndexOf(Range haystack, Separator needle) + size_t lastIndexOf(Range haystack, Separator needle) { import std.range : retro; auto r = haystack.retro().find!pred(needle); @@ -3760,10 +5261,27 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) @property Range front() { assert(!empty, "Attempting to fetch the front of an empty splitter."); - if (_frontLength == _unComputed) + static if (keepSeparators) + { + if (!_wasSeparator) + { + _frontLength = _separatorLength; + _wasSeparator = true; + } + else if (_frontLength == _unComputed) + { + auto r = _input.find!pred(_separator); + _frontLength = _input.length - r.length; + _wasSeparator = false; + } + } + else { - auto r = _input.find!pred(_separator); - _frontLength = _input.length - r.length; + if (_frontLength == _unComputed) + { + auto r = _input.find!pred(_separator); + _frontLength = _input.length - r.length; + } } return _input[0 .. _frontLength]; } @@ -3775,19 +5293,37 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) { front; } - assert(_frontLength <= _input.length); - if (_frontLength == _input.length) + assert(_frontLength <= _input.length, "The front position must" + ~ " not exceed the input.length"); + static if (keepSeparators) { - // no more input and need to fetch => done - _frontLength = _atEnd; + if (_frontLength == _input.length && !_wasSeparator) + { + _frontLength = _atEnd; - // Probably don't need this, but just for consistency: - _backLength = _atEnd; + _backLength = _atEnd; + } + else + { + _input = _input[_frontLength .. _input.length]; + _frontLength = _unComputed; + } } else { - _input = _input[_frontLength + _separatorLength .. _input.length]; - _frontLength = _unComputed; + 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; + } } } @@ -3806,16 +5342,40 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) @property Range back() { assert(!empty, "Attempting to fetch the back of an empty splitter."); - if (_backLength == _unComputed) + static if (keepSeparators) { - immutable lastIndex = lastIndexOf(_input, _separator); - if (lastIndex == -1) + if (!_wasSeparator) { - _backLength = _input.length; + _backLength = _separatorLength; + _wasSeparator = true; } - else + else if (_backLength == _unComputed) { - _backLength = _input.length - lastIndex - 1; + immutable lastIndex = lastIndexOf(_input, _separator); + if (lastIndex == -1) + { + _backLength = _input.length; + } + else + { + _backLength = _input.length - lastIndex - 1; + } + _wasSeparator = false; + } + } + else + { + 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]; @@ -3829,17 +5389,34 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) // evaluate back to make sure it's computed back; } - assert(_backLength <= _input.length); - if (_backLength == _input.length) + assert(_backLength <= _input.length, "The end index must not" + ~ " exceed the length of the input"); + static if (keepSeparators) { - // no more input and need to fetch => done - _frontLength = _atEnd; - _backLength = _atEnd; + if (_backLength == _input.length && !_wasSeparator) + { + _frontLength = _atEnd; + _backLength = _atEnd; + } + else + { + _input = _input[0 .. _input.length - _backLength]; + _backLength = _unComputed; + } } else { - _input = _input[0 .. _input.length - _backLength - _separatorLength]; - _backLength = _unComputed; + 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; + } } } } @@ -3848,46 +5425,265 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) return Result(r, s); } -/// +/// Basic splitting with characters and numbers. @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]] ])); + assert("a|bc|def".splitter('|').equal([ "a", "bc", "def" ])); + + int[] a = [1, 0, 2, 3, 0, 4, 5, 6]; + int[][] w = [ [1], [2, 3], [4, 5, 6] ]; + assert(a.splitter(0).equal(w)); } +/// Basic splitting with characters and numbers and keeping sentinels. @safe unittest { - import std.algorithm; - import std.array : array; - import std.internal.test.dummyrange; - import std.range : retro; + import std.algorithm.comparison : equal; + import std.typecons : Yes; - 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("a|bc|def".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "a", "|", "bc", "|", "def" ])); - 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] ][])); + int[] a = [1, 0, 2, 3, 0, 4, 5, 6]; + int[][] w = [ [1], [0], [2, 3], [0], [4, 5, 6] ]; + assert(a.splitter!("a == b", Yes.keepSeparators)(0).equal(w)); +} - // Thoroughly exercise the bidirectional stuff. - auto str = "abc abcd abcde ab abcdefg abcdefghij ab ac ar an at ada"; +/// Adjacent separators. +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert("|ab|".splitter('|').equal([ "", "ab", "" ])); + assert("ab".splitter('|').equal([ "ab" ])); + + assert("a|b||c".splitter('|').equal([ "a", "b", "", "c" ])); + assert("hello world".splitter(' ').equal([ "hello", "", "world" ])); + + auto a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + auto w = [ [1, 2], [], [3], [4, 5], [] ]; + assert(a.splitter(0).equal(w)); +} + +/// Adjacent separators and keeping sentinels. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + assert("|ab|".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "ab", "|", "" ])); + assert("ab".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "ab" ])); + + assert("a|b||c".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "a", "|", "b", "|", "", "|", "c" ])); + assert("hello world".splitter!("a == b", Yes.keepSeparators)(' ') + .equal([ "hello", " ", "", " ", "world" ])); + + auto a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + auto w = [ [1, 2], [0], [], [0], [3], [0], [4, 5], [0], [] ]; + assert(a.splitter!("a == b", Yes.keepSeparators)(0).equal(w)); +} + +/// Empty and separator-only ranges. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : empty; + + assert("".splitter('|').empty); + assert("|".splitter('|').equal([ "", "" ])); + assert("||".splitter('|').equal([ "", "", "" ])); +} + +/// Empty and separator-only ranges and keeping sentinels. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + import std.range : empty; + + assert("".splitter!("a == b", Yes.keepSeparators)('|').empty); + assert("|".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "" ])); + assert("||".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "", "|", "" ])); +} + +/// Use a range for splitting +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert("a=>bc=>def".splitter("=>").equal([ "a", "bc", "def" ])); + assert("a|b||c".splitter("||").equal([ "a|b", "c" ])); + assert("hello world".splitter(" ").equal([ "hello", "world" ])); + + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [3, 0, 4, 5, 0] ]; + assert(a.splitter([0, 0]).equal(w)); + + a = [ 0, 0 ]; + assert(a.splitter([0, 0]).equal([ (int[]).init, (int[]).init ])); + + a = [ 0, 0, 1 ]; + assert(a.splitter([0, 0]).equal([ [], [1] ])); +} + +/// Use a range for splitting +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + assert("a=>bc=>def".splitter!("a == b", Yes.keepSeparators)("=>") + .equal([ "a", "=>", "bc", "=>", "def" ])); + assert("a|b||c".splitter!("a == b", Yes.keepSeparators)("||") + .equal([ "a|b", "||", "c" ])); + assert("hello world".splitter!("a == b", Yes.keepSeparators)(" ") + .equal([ "hello", " ", "world" ])); + + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [0, 0], [3, 0, 4, 5, 0] ]; + assert(a.splitter!("a == b", Yes.keepSeparators)([0, 0]).equal(w)); + + a = [ 0, 0 ]; + assert(a.splitter!("a == b", Yes.keepSeparators)([0, 0]) + .equal([ (int[]).init, [0, 0], (int[]).init ])); + + a = [ 0, 0, 1 ]; + assert(a.splitter!("a == b", Yes.keepSeparators)([0, 0]) + .equal([ [], [0, 0], [1] ])); +} + +/// Custom predicate functions. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.ascii : toLower; + + assert("abXcdxef".splitter!"a.toLower == b"('x').equal( + [ "ab", "cd", "ef" ])); + + auto w = [ [0], [1], [2] ]; + assert(w.splitter!"a.front == b"(1).equal([ [[0]], [[2]] ])); +} + +/// Custom predicate functions. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + import std.ascii : toLower; + + assert("abXcdxef".splitter!("a.toLower == b", Yes.keepSeparators)('x') + .equal([ "ab", "X", "cd", "x", "ef" ])); + + auto w = [ [0], [1], [2] ]; + assert(w.splitter!("a.front == b", Yes.keepSeparators)(1) + .equal([ [[0]], [[1]], [[2]] ])); +} + +/// Use splitter without a separator +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : front; + + assert(equal(splitter!(a => a == '|')("a|bc|def"), [ "a", "bc", "def" ])); + 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]] ])); +} + +/// Leading separators, trailing separators, or no separators. +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert("|ab|".splitter('|').equal([ "", "ab", "" ])); + assert("ab".splitter('|').equal([ "ab" ])); +} + +/// Leading separators, trailing separators, or no separators. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + assert("|ab|".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "ab", "|", "" ])); + assert("ab".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "ab" ])); +} + +/// Splitter returns bidirectional ranges if the delimiter is a single element +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + assert("a|bc|def".splitter('|').retro.equal([ "def", "bc", "a" ])); +} + +/// Splitter returns bidirectional ranges if the delimiter is a single element +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + import std.range : retro; + assert("a|bc|def".splitter!("a == b", Yes.keepSeparators)('|') + .retro.equal([ "def", "|", "bc", "|", "a" ])); +} + +/// Splitting by word lazily +@safe unittest +{ + import std.ascii : isWhite; + import std.algorithm.comparison : equal; + import std.algorithm.iteration : splitter; + + string str = "Hello World!"; + assert(str.splitter!(isWhite).equal(["Hello", "World!"])); +} + +@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] ])); + 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'))) @@ -3912,7 +5708,9 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) assert(split.front == "b "); assert(split.back == "r "); - foreach (DummyType; AllDummyRanges) { // Bug 4408 + // https://issues.dlang.org/show_bug.cgi?id=4408 + foreach (DummyType; AllDummyRanges) + { static if (isRandomAccessRange!DummyType) { static assert(isBidirectionalRange!DummyType); @@ -3939,38 +5737,30 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) 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. +// https://issues.dlang.org/show_bug.cgi?id=18470 +@safe unittest +{ + import std.algorithm.comparison : equal; -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. + const w = [[0], [1], [2]]; + assert(w.splitter!((a, b) => a.front() == b)(1).equal([[[0]], [[2]]])); +} -Constraints: - The predicate $(D pred) needs to accept an element of $(D r) and an - element of $(D s). +// https://issues.dlang.org/show_bug.cgi?id=18470 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.ascii : toLower; -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. + assert("abXcdxef".splitter!"a.toLower == b"('x').equal(["ab", "cd", "ef"])); + assert("abXcdxef".splitter!((a, b) => a.toLower == b)('x').equal(["ab", "cd", "ef"])); +} -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) +/// ditto +auto splitter(alias pred = "a == b", + Flag!"keepSeparators" keepSeparators = No.keepSeparators, + Range, + Separator)(Range r, Separator s) if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) && (hasSlicing!Range || isNarrowString!Range) && isForwardRange!Separator @@ -3986,33 +5776,38 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) Separator _separator; // _frontLength == size_t.max means empty size_t _frontLength = size_t.max; - static if (isBidirectionalRange!Range) - size_t _backLength = size_t.max; + + static if (keepSeparators) + { + bool _wasSeparator = true; + } @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) + static if (keepSeparators) { - import std.range : retro; - _backLength = _input.length - - find!pred(retro(_input), retro(_separator)).source.length; + assert(!_input.empty || _wasSeparator, "The input must not be empty"); + if (_wasSeparator) + { + _frontLength = _input.length - + find!pred(_input, _separator).length; + _wasSeparator = false; + } + else + { + _frontLength = separatorLength(); + _wasSeparator = true; + } + } + else + { + assert(!_input.empty, "The input must not be empty"); + // compute front length + _frontLength = (_separator.empty) ? 1 : + _input.length - find!pred(_input, _separator).length; } } @@ -4038,7 +5833,14 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) { @property bool empty() { - return _frontLength == size_t.max && _input.empty; + static if (keepSeparators) + { + return _frontLength == size_t.max && _input.empty && !_wasSeparator; + } + else + { + return _frontLength == size_t.max && _input.empty; + } } } @@ -4046,28 +5848,32 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) { assert(!empty, "Attempting to popFront an empty splitter."); ensureFrontLength(); - if (_frontLength == _input.length) + + static if (keepSeparators) { - // done, there's no separator in sight - _input = _input[_frontLength .. _frontLength]; - _frontLength = _frontLength.max; - static if (isBidirectionalRange!Range) - _backLength = _backLength.max; - return; + _input = _input[_frontLength .. _input.length]; } - if (_frontLength + separatorLength == _input.length) + else { - // 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; + if (_frontLength == _input.length) + { + // done, there's no separator in sight + _input = _input[_frontLength .. _frontLength]; + _frontLength = _frontLength.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; + return; + } + // Normal case, pop one item and the separator, get ready for + // reading the next item + _input = _input[_frontLength + separatorLength .. _input.length]; } - // 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; } @@ -4086,21 +5892,6 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) 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; @@ -4135,15 +5926,6 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) 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, ","); @@ -4172,11 +5954,11 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) assert(equal(sp6, ["", ""][])); } +// https://issues.dlang.org/show_bug.cgi?id=10773 @safe unittest { import std.algorithm.comparison : equal; - // Issue 10773 auto s = splitter("abc", ""); assert(s.equal(["a", "b", "c"])); } @@ -4193,7 +5975,7 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) @property empty() { return _impl.empty; } @property auto front() { return _impl.front; } void popFront() { _impl = _impl[1..$]; } - @property RefSep save() { return new RefSep(_impl); } + @property RefSep save() scope { return new RefSep(_impl); } @property auto length() { return _impl.length; } } auto sep = new RefSep("->"); @@ -4202,55 +5984,11 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) 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 +/// ditto +auto splitter(alias isTerminator, Range)(Range r) +if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(r.front)))) { - 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]] ])); + return SplitterResult!(unaryFun!isTerminator, Range)(r); } private struct SplitterResult(alias isTerminator, Range) @@ -4291,6 +6029,24 @@ private struct SplitterResult(alias isTerminator, Range) _end = size_t.max; } + static if (fullSlicing) + { + private this(Range input, size_t end) + { + _input = input; + _end = end; + } + } + else + { + private this(Range input, size_t end, Range next) + { + _input = input; + _end = end; + _next = next; + } + } + static if (isInfinite!Range) { enum bool empty = false; // Propagate infiniteness. @@ -4355,11 +6111,10 @@ private struct SplitterResult(alias isTerminator, Range) @property typeof(this) save() { - auto ret = this; - ret._input = _input.save; - static if (!fullSlicing) - ret._next = _next.save; - return ret; + static if (fullSlicing) + return SplitterResult(_input.save, _end); + else + return SplitterResult(_input.save, _end, _next.save); } } @@ -4444,7 +6199,7 @@ private struct SplitterResult(alias isTerminator, Range) import std.algorithm.comparison : equal; import std.uni : isWhite; - //@@@6791@@@ + // https://issues.dlang.org/show_bug.cgi?id=6791 assert(equal( splitter("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"] @@ -4456,54 +6211,126 @@ private struct SplitterResult(alias isTerminator, Range) assert(equal(splitter!"a=='本'"("日本語"), ["日", "語"])); } +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : refRange; + string s = "foobar"; + auto r = refRange(&s).splitter!(c => c == 'b'); + assert(equal!equal(r.save, ["foo", "ar"])); + assert(equal!equal(r.save, ["foo", "ar"])); +} + /++ -Lazily splits the string $(D s) into words, using whitespace as the delimiter. +Lazily splits the character-based range `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 +This function is character-range specific and, contrary to +`splitter!(std.uni.isWhite)`, runs of whitespace will be merged together (no empty tokens will be produced). Params: - s = The string to be split. + s = The character-based range to be split. Must be a string, or a + random-access range of character types. Returns: An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of slices of - the original string split by whitespace. + the original range split by whitespace. +/ -auto splitter(C)(C[] s) -if (isSomeChar!C) +auto splitter(Range)(Range s) +if (isSomeString!Range || + isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && + !isConvertibleToString!Range && + isSomeChar!(ElementEncodingType!Range)) { import std.algorithm.searching : find; static struct Result { private: import core.exception : RangeError; - C[] _s; + Range _s; size_t _frontLength; - void getFirst() pure @safe + void getFirst() { import std.uni : isWhite; + import std.traits : Unqual; + + static if (is(immutable ElementEncodingType!Range == immutable wchar) && + is(immutable ElementType!Range == immutable dchar)) + { + // all unicode whitespace characters fit into a wchar. However, + // this range is a wchar array, so we will treat it like a + // wchar array instead of decoding each code point. + _frontLength = _s.length; // default condition, no spaces + foreach (i; 0 .. _s.length) + if (isWhite(_s[i])) + { + _frontLength = i; + break; + } + } + else static if (is(immutable ElementType!Range == immutable dchar) || + is(immutable ElementType!Range == immutable wchar)) + { + // dchar or wchar range, we can just use find. + auto r = find!(isWhite)(_s.save); + _frontLength = _s.length - r.length; + } + else + { + // need to decode the characters until we find a space. This is + // ported from std.string.stripLeft. + static import std.ascii; + static import std.uni; + import std.utf : decodeFront; + + auto input = _s.save; + size_t iLength = input.length; + + while (!input.empty) + { + auto c = input.front; + if (std.ascii.isASCII(c)) + { + if (std.ascii.isWhite(c)) + break; + input.popFront(); + --iLength; + } + else + { + auto dc = decodeFront(input); + if (std.uni.isWhite(dc)) + break; + iLength = input.length; + } + } + + // sanity check + assert(iLength <= _s.length, "The current index must not" + ~ " exceed the length of the input"); - auto r = find!(isWhite)(_s); - _frontLength = _s.length - r.length; + _frontLength = _s.length - iLength; + } } public: - this(C[] s) pure @safe + this(Range s) { - import std.string : strip; - _s = s.strip(); + import std.string : stripLeft; + _s = s.stripLeft(); getFirst(); } - @property C[] front() pure @safe + @property auto front() { version (assert) if (empty) throw new RangeError(); return _s[0 .. _frontLength]; } - void popFront() pure @safe + void popFront() { import std.string : stripLeft; version (assert) if (empty) throw new RangeError(); @@ -4511,183 +6338,890 @@ if (isSomeChar!C) getFirst(); } - @property bool empty() const @safe pure nothrow + @property bool empty() const { return _s.empty; } - @property inout(Result) save() inout @safe pure nothrow - { - return this; - } - } - return Result(s); + @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; + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ + import std.conv : to; + S a = " a \u2028 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 +{ + // do it with byCodeUnit + import std.conv : to; + import std.string : strip; + import std.utf : byCodeUnit; + + alias BCU = typeof("abc".byCodeUnit()); + + // TDPL example, page 8 + uint[BCU] dictionary; + BCU[3] lines; + lines[0] = "line one".byCodeUnit; + lines[1] = "line \ttwo".byCodeUnit; + lines[2] = "yah last line\ryah".byCodeUnit; + foreach (line; lines) + { + foreach (word; splitter(strip(line))) + { + static assert(is(typeof(word) == BCU)); + if (word in dictionary) continue; // Nothing to do + auto newID = dictionary.length; + dictionary[word] = cast(uint) newID; + } + } + assert(dictionary.length == 5); + assert(dictionary["line".byCodeUnit]== 0); + assert(dictionary["one".byCodeUnit]== 1); + assert(dictionary["two".byCodeUnit]== 2); + assert(dictionary["yah".byCodeUnit]== 3); + assert(dictionary["last".byCodeUnit]== 4); +} + +// https://issues.dlang.org/show_bug.cgi?id=19238 +@safe pure unittest +{ + import std.utf : byCodeUnit; + import std.algorithm.comparison : equal; + auto range = "hello world".byCodeUnit.splitter; + static assert(is(typeof(range.front()) == typeof("hello".byCodeUnit()))); + assert(range.equal(["hello".byCodeUnit, "world".byCodeUnit])); + + // test other space types, including unicode + auto u = " a\t\v\r bcd\u3000 \u2028\t\nef\U00010001 gh"; + assert(equal(splitter(u), ["a", "bcd", "ef\U00010001", "gh"][])); + assert(equal(splitter(u.byCodeUnit), ["a".byCodeUnit, "bcd".byCodeUnit, + "ef\U00010001".byCodeUnit, "gh".byCodeUnit][])); +} + +@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"()))); + } + } +} + +// In same combinations substitute needs to calculate the auto-decoded length +// of its needles +private template hasDifferentAutodecoding(Range, Needles...) +{ + import std.meta : anySatisfy; + /* iff + - the needles needs auto-decoding, but the incoming range doesn't (or vice versa) + - both (range, needle) need auto-decoding and don't share the same common type + */ + enum needlesAreNarrow = anySatisfy!(isNarrowString, Needles); + enum sourceIsNarrow = isNarrowString!Range; + enum hasDifferentAutodecoding = sourceIsNarrow != needlesAreNarrow || + (sourceIsNarrow && needlesAreNarrow && + is(CommonType!(Range, Needles) == void)); +} + +@safe nothrow @nogc pure unittest +{ + import std.meta : AliasSeq; // used for better clarity + + static assert(!hasDifferentAutodecoding!(string, AliasSeq!(string, string))); + static assert(!hasDifferentAutodecoding!(wstring, AliasSeq!(wstring, wstring))); + static assert(!hasDifferentAutodecoding!(dstring, AliasSeq!(dstring, dstring))); + + // the needles needs auto-decoding, but the incoming range doesn't (or vice versa) + static assert(hasDifferentAutodecoding!(string, AliasSeq!(wstring, wstring))); + static assert(hasDifferentAutodecoding!(string, AliasSeq!(dstring, dstring))); + static assert(hasDifferentAutodecoding!(wstring, AliasSeq!(string, string))); + static assert(hasDifferentAutodecoding!(wstring, AliasSeq!(dstring, dstring))); + static assert(hasDifferentAutodecoding!(dstring, AliasSeq!(string, string))); + static assert(hasDifferentAutodecoding!(dstring, AliasSeq!(wstring, wstring))); + + // both (range, needle) need auto-decoding and don't share the same common type + static foreach (T; AliasSeq!(string, wstring, dstring)) + { + static assert(hasDifferentAutodecoding!(T, AliasSeq!(wstring, string))); + static assert(hasDifferentAutodecoding!(T, AliasSeq!(dstring, string))); + static assert(hasDifferentAutodecoding!(T, AliasSeq!(wstring, dstring))); + } +} + +// substitute +/** +Returns a range with all occurrences of `substs` in `r`. +replaced with their substitution. + +Single value replacements (`'ö'.substitute!('ä', 'a', 'ö', 'o', 'ü', 'u)`) are +supported as well and in $(BIGOH 1). + +Params: + r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + value = a single value which can be substituted in $(BIGOH 1) + substs = a set of replacements/substitutions + pred = the equality function to test if element(s) are equal to + a substitution + +Returns: a range with the substitutions replaced. + +See_Also: +$(REF replace, std, array) for an eager replace algorithm or +$(REF translate, std, string), and $(REF tr, std, string) +for string algorithms with translation tables. +*/ +template substitute(substs...) +if (substs.length >= 2 && isExpressions!substs) +{ + import std.range.primitives : ElementType; + import std.traits : CommonType; + + static assert(!(substs.length & 1), "The number of substitution parameters must be even"); + + /** + Substitute single values with compile-time substitution mappings. + Complexity: $(BIGOH 1) due to D's `switch` guaranteeing $(BIGOH 1); + */ + auto substitute(Value)(Value value) + if (isInputRange!Value || !is(CommonType!(Value, typeof(substs[0])) == void)) + { + static if (isInputRange!Value) + { + static if (!is(CommonType!(ElementType!Value, typeof(substs[0])) == void)) + { + // Substitute single range elements with compile-time substitution mappings + return value.map!(a => substitute(a)); + } + else static if (isInputRange!Value && + !is(CommonType!(ElementType!Value, ElementType!(typeof(substs[0]))) == void)) + { + // not implemented yet, fallback to runtime variant for now + return .substitute(value, substs); + } + else + { + static assert(0, `Compile-time substitutions must be elements or ranges of the same type of ` ~ + Value.stringof ~ `.`); + } + } + // Substitute single values with compile-time substitution mappings. + else // static if (!is(CommonType!(Value, typeof(substs[0])) == void)) + { + switch (value) + { + static foreach (i; 0 .. substs.length / 2) + case substs[2 * i]: + return substs[2 * i + 1]; + + default: return value; + } + } + } +} + +/// ditto +auto substitute(alias pred = (a, b) => a == b, R, Substs...)(R r, Substs substs) +if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) +{ + import std.range.primitives : ElementType; + import std.meta : allSatisfy; + import std.traits : CommonType; + + static assert(!(Substs.length & 1), "The number of substitution parameters must be even"); + + enum n = Substs.length / 2; + + // Substitute individual elements + static if (!is(CommonType!(ElementType!R, Substs) == void)) + { + import std.functional : binaryFun; + + // Imitate a value closure to be @nogc + static struct ReplaceElement + { + private Substs substs; + + this(Substs substs) + { + this.substs = substs; + } + + auto opCall(E)(E e) + { + static foreach (i; 0 .. n) + if (binaryFun!pred(e, substs[2 * i])) + return substs[2 * i + 1]; + + return e; + } + } + auto er = ReplaceElement(substs); + return r.map!er; + } + // Substitute subranges + else static if (!is(CommonType!(ElementType!R, ElementType!(Substs[0])) == void) && + allSatisfy!(isForwardRange, Substs)) + { + import std.range : choose, take; + import std.meta : Stride; + + auto replaceElement(E)(E e) + { + alias ReturnA = typeof(e[0]); + alias ReturnB = typeof(substs[0 .. 1].take(1)); + + // 1-based index + const auto hitNr = e[1]; + switch (hitNr) + { + // no hit + case 0: + // use choose trick for non-common range + static if (is(CommonType!(ReturnA, ReturnB) == void)) + return choose(1, e[0], ReturnB.init); + else + return e[0]; + + // all replacements + static foreach (i; 0 .. n) + case i + 1: + // use choose trick for non-common ranges + static if (is(CommonType!(ReturnA, ReturnB) == void)) + return choose(0, e[0], substs[2 * i + 1].take(size_t.max)); + else + return substs[2 * i + 1].take(size_t.max); + default: + assert(0, "hitNr should always be found."); + } + } + + alias Ins = Stride!(2, Substs); + + static struct SubstituteSplitter + { + import std.range : drop; + import std.typecons : Tuple; + + private + { + typeof(R.init.drop(0)) rest; + Ins needles; + + typeof(R.init.take(0)) skip; // skip before next hit + alias Hit = size_t; // 0 iff no hit, otherwise hit in needles[index-1] + alias E = Tuple!(typeof(skip), Hit); + Hit hitNr; // hit number: 0 means no hit, otherwise index+1 to needles that matched + bool hasHit; // is there a replacement hit which should be printed? + + enum hasDifferentAutodecoding = .hasDifferentAutodecoding!(typeof(rest), Ins); + + // calculating the needle length for narrow strings might be expensive -> cache it + static if (hasDifferentAutodecoding) + ptrdiff_t[n] needleLengths = -1; + } + + this(R haystack, Ins needles) + { + this.rest = haystack.drop(0); + this.needles = needles; + if (!haystack.empty) + { + hasHit = true; + popFront; + } + static if (hasNested!(typeof(skip))) + skip = rest.take(0); + } + + /* If `skip` is non-empty, it's returned as (skip, 0) tuple + otherwise a similar (, hitNr) tuple is returned. + `replaceElement` maps based on the second item (`hitNr`). + */ + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty substitute."); + return !skip.empty ? E(skip, 0) : E(typeof(skip).init, hitNr); + } + + static if (isInfinite!R) + enum empty = false; // propagate infiniteness + else + @property bool empty() + { + return skip.empty && !hasHit; + } + + /* If currently in a skipping phase => reset. + Otherwise try to find the next occurrence of `needles` + If valid match + - if there are elements before the match, set skip with these elements + (on the next popFront, the range will be in the skip state once) + - `rest`: advance to the end of the match + - set hasHit + Otherwise skip to the end + */ + void popFront() + { + assert(!empty, "Attempting to popFront an empty substitute."); + if (!skip.empty) + { + skip = typeof(skip).init; // jump over skip + } + else + { + import std.algorithm.searching : countUntil, find; + + auto match = rest.find!pred(needles); + + static if (needles.length >= 2) // variadic version of find (returns a tuple) + { + // find with variadic needles returns a (range, needleNr) tuple + // needleNr is a 1-based index + auto hitValue = match[0]; + hitNr = match[1]; + } + else + { + // find with one needle returns the range + auto hitValue = match; + hitNr = match.empty ? 0 : 1; + } + + if (hitNr == 0) // no more hits + { + skip = rest.take(size_t.max); + hasHit = false; + rest = typeof(rest).init; + } + else + { + auto hitLength = size_t.max; + switchL: switch (hitNr - 1) + { + static foreach (i; 0 .. n) + { + case i: + static if (hasDifferentAutodecoding) + { + import std.utf : codeLength; + + // cache calculated needle length + if (needleLengths[i] != -1) + hitLength = needleLengths[i]; + else + hitLength = needleLengths[i] = codeLength!dchar(needles[i]); + } + else + { + hitLength = needles[i].length; + } + break switchL; + } + default: + assert(0, "hitNr should always be found"); + } + + const pos = rest.countUntil(hitValue); + if (pos > 0) // match not at start of rest + skip = rest.take(pos); + + hasHit = true; + + // iff the source range and the substitutions are narrow strings, + // we can avoid calling the auto-decoding `popFront` (via drop) + static if (isNarrowString!(typeof(hitValue)) && !hasDifferentAutodecoding) + rest = hitValue[hitLength .. $]; + else + rest = hitValue.drop(hitLength); + } + } + } + } + + // extract inputs + Ins ins; + static foreach (i; 0 .. n) + ins[i] = substs[2 * i]; + + return SubstituteSplitter(r, ins) + .map!(a => replaceElement(a)) + .joiner; + } + else + { + static assert(0, "The substitutions must either substitute a single element or a save-able subrange."); + } +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute single elements + assert("do_it".substitute('_', ' ').equal("do it")); + + // substitute multiple, single elements + assert("do_it".substitute('_', ' ', + 'd', 'g', + 'i', 't', + 't', 'o') + .equal("go to")); + + // substitute subranges + assert("do_it".substitute("_", " ", + "do", "done") + .equal("done it")); + + // substitution works for any ElementType + int[] x = [1, 2, 3]; + auto y = x.substitute(1, 0.1); + assert(y.equal([0.1, 2, 3])); + static assert(is(typeof(y.front) == double)); + + import std.range : retro; + assert([1, 2, 3].substitute(1, 0.1).retro.equal([3, 2, 0.1])); +} + +/// Use the faster compile-time overload +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute subranges of a range + assert("apple_tree".substitute!("apple", "banana", + "tree", "shrub").equal("banana_shrub")); + + // substitute subranges of a range + assert("apple_tree".substitute!('a', 'b', + 't', 'f').equal("bpple_free")); + + // substitute values + assert('a'.substitute!('a', 'b', 't', 'f') == 'b'); +} + +/// Multiple substitutes +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : ElementType; + + int[3] x = [1, 2, 3]; + auto y = x[].substitute(1, 0.1) + .substitute(0.1, 0.2); + static assert(is(typeof(y.front) == double)); + assert(y.equal([0.2, 2, 3])); + + auto z = "42".substitute('2', '3') + .substitute('3', '1'); + static assert(is(ElementType!(typeof(z)) == dchar)); + assert(equal(z, "41")); +} + +// Test the first example with compile-time overloads +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute single elements + assert("do_it".substitute!('_', ' ').equal("do it")); + + // substitute multiple, single elements + assert("do_it".substitute!('_', ' ', + 'd', 'g', + 'i', 't', + 't', 'o') + .equal(`go to`)); + + // substitute subranges + assert("do_it".substitute!("_", " ", + "do", "done") + .equal("done it")); + + // substitution works for any ElementType + int[3] x = [1, 2, 3]; + auto y = x[].substitute!(1, 0.1); + assert(y.equal([0.1, 2, 3])); + static assert(is(typeof(y.front) == double)); + + import std.range : retro; + assert([1, 2, 3].substitute!(1, 0.1).retro.equal([3, 2, 0.1])); +} + +// test infinite ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.range : cycle, take; + + int[] x = [1, 2, 3]; + assert(x.cycle.substitute!(1, 0.1).take(4).equal([0.1, 2, 3, 0.1])); + assert(x.cycle.substitute(1, 0.1).take(4).equal([0.1, 2, 3, 0.1])); +} + +// test infinite ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + foreach (R; AllDummyRanges) + { + assert(R.init + .substitute!(2, 22, 3, 33, 5, 55, 9, 99) + .equal([1, 22, 33, 4, 55, 6, 7, 8, 99, 10])); + + assert(R.init + .substitute(2, 22, 3, 33, 5, 55, 9, 99) + .equal([1, 22, 33, 4, 55, 6, 7, 8, 99, 10])); + } +} + +// test multiple replacements +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("alpha.beta.gamma" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.2.3")); + + assert("alpha.beta.gamma." + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.2.3.")); + + assert("beta.beta.beta" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("2.2.2")); + + assert("alpha.alpha.alpha" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.1.1")); } -/// +// test combination of subrange + element replacement @safe pure unittest { import std.algorithm.comparison : equal; - auto a = " a bcd ef gh "; - assert(equal(splitter(a), ["a", "bcd", "ef", "gh"][])); + + assert(("abcDe".substitute("a", "AA", + "b", "DD") + .substitute('A', 'y', + 'D', 'x', + 'e', '1')) + .equal("yyxxcx1")); } +// test const + immutable storage groups @safe pure unittest { import std.algorithm.comparison : equal; - import std.meta : AliasSeq; - foreach (S; AliasSeq!(string, wstring, dstring)) + + auto xyz_abc(T)(T value) { - 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 a = "a"; + const b = "b"; + auto c = "c"; + return value.substitute!("x", a, + "y", b, + "z", c); } + assert(xyz_abc("_x").equal("_a")); + assert(xyz_abc(".y.").equal(".b.")); + assert(xyz_abc("z").equal("c")); + assert(xyz_abc("w").equal("w")); +} - immutable string s = " a bcd ef gh "; - assert(equal(splitter(s), ["a", "bcd", "ef", "gh"][])); +// test with narrow strings (auto-decoding) and subranges +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("äöü€".substitute("ä", "b", "ü", "u").equal("böu€")); + assert("äöü€".substitute!("ä", "b", "ü", "u").equal("böu€")); + assert("ä...öü€".substitute("ä", "b", "ü", "u").equal("b...öu€")); + + auto expected = "emoticons😄😅.😇😈Rock"; + assert("emoticons😄😅😆😇😈rock" + .substitute("r", "R", "😆", ".").equal(expected)); + assert("emoticons😄😅😆😇😈rock" + .substitute!("r", "R", "😆", ".").equal(expected)); } -@safe unittest +// test with narrow strings (auto-decoding) and single elements +@safe pure unittest { - import std.conv : to; - import std.string : strip; + import std.algorithm.comparison : equal; - // 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); + assert("äöü€".substitute('ä', 'b', 'ü', 'u').equal("böu€")); + assert("äöü€".substitute!('ä', 'b', 'ü', 'u').equal("böu€")); + + auto expected = "emoticons😄😅.😇😈Rock"; + assert("emoticons😄😅😆😇😈rock" + .substitute('r', 'R', '😆', '.').equal(expected)); + assert("emoticons😄😅😆😇😈rock" + .substitute!('r', 'R', '😆', '.').equal(expected)); } -@safe unittest +// test auto-decoding {n,w,d} strings X {n,w,d} strings +@safe pure 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("ääöü€".substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€".substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€".substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); - 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("ääöü€"w.substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€"w.substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€"w.substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); - //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("ääöü€"d.substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€"d.substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€"d.substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); - 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))); + // auto-decoding is done before by a different range + assert("ääöü€".filter!(a => true).substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€".filter!(a => true).substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€".filter!(a => true).substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); +} - //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); +// test repeated replacement +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; - 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([1, 2, 3, 1, 1, 2].substitute(1, 0).equal([0, 2, 3, 0, 0, 2])); + assert([1, 2, 3, 1, 1, 2].substitute!(1, 0).equal([0, 2, 3, 0, 0, 2])); + assert([1, 2, 3, 1, 1, 2].substitute(1, 2, 2, 9).equal([2, 9, 3, 2, 2, 9])); +} - //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"()))); +// test @nogc for single element replacements +@safe @nogc unittest +{ + import std.algorithm.comparison : equal; - 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))); + static immutable arr = [1, 2, 3, 1, 1, 2]; + static immutable expected = [0, 2, 3, 0, 0, 2]; - //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"()))); - } - } + assert(arr.substitute!(1, 0).equal(expected)); + assert(arr.substitute(1, 0).equal(expected)); +} + +// test different range types +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + static foreach (DummyType; AllDummyRanges) + {{ + DummyType dummyRange; + + // single substitution + dummyRange.substitute (2, 22).equal([1, 22, 3, 4, 5, 6, 7, 8, 9, 10]); + dummyRange.substitute!(2, 22).equal([1, 22, 3, 4, 5, 6, 7, 8, 9, 10]); + + // multiple substitution + dummyRange.substitute (2, 22, 5, 55, 7, 77).equal([1, 22, 3, 4, 55, 6, 77, 8, 9, 10]); + dummyRange.substitute!(2, 22, 5, 55, 7, 77).equal([1, 22, 3, 4, 55, 6, 77, 8, 9, 10]); + }} +} + +// https://issues.dlang.org/show_bug.cgi?id=19207 +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + assert([1, 2, 3, 4].substitute([1], [7]).equal([7, 2, 3, 4])); + assert([1, 2, 3, 4].substitute([2], [7]).equal([1, 7, 3, 4])); + assert([1, 2, 3, 4].substitute([4], [7]).equal([1, 2, 3, 7])); + assert([1, 2, 3, 4].substitute([2, 3], [7]).equal([1, 7, 4])); + assert([1, 2, 3, 4].substitute([3, 4], [7, 8]).equal([1, 2, 7, 8])); +} + +// tests recognizing empty base ranges +nothrow pure @safe unittest +{ + import std.utf : byCodeUnit; + import std.algorithm.comparison : equal; + + assert("".byCodeUnit.substitute('4', 'A').empty); + assert("".byCodeUnit.substitute('0', 'O', '5', 'S', '1', 'l').empty); + assert("".byCodeUnit.substitute("PKM".byCodeUnit, "PoKeMon".byCodeUnit).empty); + assert("".byCodeUnit.substitute + ( "ding".byCodeUnit, + "dong".byCodeUnit, + "click".byCodeUnit, + "clack".byCodeUnit, + "ping".byCodeUnit, + "latency".byCodeUnit + ).empty); } // sum /** -Sums elements of $(D r), which must be a finite +Sums elements of `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, +conceptually `sum(r)` is equivalent to $(LREF fold)!((a, b) => a + +b)(r, 0), `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 +$(LI If $(REF ElementType, std,range,primitives)!R is a floating-point +type and `R` is a $(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) with -length and slicing, then $(D sum) uses the +length and slicing, then `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 +$(LI If `ElementType!R` is a floating-point type and `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, +`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). +$(DDLINK spec/type, Types, `real`) +precision for `real` inputs and in `double` precision otherwise +(Note this is a special case that deviates from `fold`'s behavior, +which would have kept `float` precision for a `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 +A seed may be passed to `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. +and precision used for summation. If a seed is not passed, one is created with +the value of `typeof(r.front + r.front)(0)`, or `typeof(r.front + r.front).zero` +if no constructor exists that takes an int. 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 +at expense of precision, one can use `fold!((a, b) => a + b)(r, 0)`, which is not specialized for summation. Params: @@ -4705,7 +7239,15 @@ if (isInputRange!R && !isInfinite!R && is(typeof(r.front + r.front))) 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)); + static if (is(typeof(Unqual!Seed(0)))) + enum seedValue = Unqual!Seed(0); + else static if (is(typeof({ Unqual!Seed tmp = Seed.zero; }))) + enum Unqual!Seed seedValue = Seed.zero; + else + static assert(false, + "Could not initiate an initial value for " ~ (Unqual!Seed).stringof + ~ ". Please supply an initial value manually."); + return sum(r, seedValue); } /// ditto auto sum(R, E)(R r, E seed) @@ -4727,6 +7269,36 @@ if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front))) } } +/// 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.operations : isClose; + assert(iota(ulong.max / 2, ulong.max / 2 + 4096).sum(0.0) + .isClose((ulong.max / 2) * 4096.0 + 4096^^2 / 2)); +} + // Pairwise summation http://en.wikipedia.org/wiki/Pairwise_summation private auto sumPairwise(F, R)(R data) if (isInputRange!R && !isInfinite!R) @@ -4815,8 +7387,8 @@ if (isForwardRange!R && !isRandomAccessRange!R) 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)); + import std.math.traits : isPowerOf2; + static assert(isPowerOf2(N), "N must be a power of 2"); static if (N == 2) return sumPair!(needEmptyChecks, F)(r); else return sumPairwiseN!(N/2, needEmptyChecks, F)(r) + sumPairwiseN!(N/2, needEmptyChecks, F)(r); @@ -4825,7 +7397,9 @@ if (isForwardRange!R && !isRandomAccessRange!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); + static assert(isFloatingPoint!Result && isMutable!Result, "The type of" + ~ " Result must be a mutable floating point, not " + ~ Result.stringof); Result c = 0; for (; !r.empty; r.popFront()) { @@ -4837,36 +7411,6 @@ private auto sumKahan(Result, R)(Result result, R r) 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)); @@ -4914,7 +7458,8 @@ private auto sumKahan(Result, R)(Result result, R r) assert(sum(SList!double(1, 2, 3, 4)[]) == 10); } -@safe pure nothrow unittest // 12434 +// https://issues.dlang.org/show_bug.cgi?id=12434 +@safe pure nothrow unittest { immutable a = [10, 20]; auto s1 = sum(a); @@ -4943,15 +7488,154 @@ private auto sumKahan(Result, R)(Result result, R r) assert(repeat(1.0, n).sum == n); } +// Issue 19525 +@safe unittest +{ + import std.datetime : Duration, minutes; + assert([1.minutes].sum() == 1.minutes); +} + +/** +Finds the mean (colloquially known as the average) of a range. + +For built-in numerical types, accurate Knuth & Welford mean calculation +is used. For user-defined types, element by element summation is used. +Additionally an extra parameter `seed` is needed in order to correctly +seed the summation with the equivalent to `0`. + +The first overload of this function will return `T.init` if the range +is empty. However, the second overload will return `seed` on empty ranges. + +This function is $(BIGOH r.length). + +Params: + T = The type of the return value. + r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + seed = For user defined types. Should be equivalent to `0`. + +Returns: + The mean of `r` when `r` is non-empty. +*/ +T mean(T = double, R)(R r) +if (isInputRange!R && + isNumeric!(ElementType!R) && + !isInfinite!R) +{ + if (r.empty) + return T.init; + + Unqual!T meanRes = 0; + size_t i = 1; + + // Knuth & Welford mean calculation + // division per element is slower, but more accurate + for (; !r.empty; r.popFront()) + { + T delta = r.front - meanRes; + meanRes += delta / i++; + } + + return meanRes; +} + +/// ditto +auto mean(R, T)(R r, T seed) +if (isInputRange!R && + !isNumeric!(ElementType!R) && + is(typeof(r.front + seed)) && + is(typeof(r.front / size_t(1))) && + !isInfinite!R) +{ + import std.algorithm.iteration : sum, reduce; + + // per item division vis-a-vis the previous overload is too + // inaccurate for integer division, which the user defined + // types might be representing + static if (hasLength!R) + { + if (r.length == 0) + return seed; + + return sum(r, seed) / r.length; + } + else + { + import std.typecons : tuple; + + if (r.empty) + return seed; + + auto pair = reduce!((a, b) => tuple(a[0] + 1, a[1] + b)) + (tuple(size_t(0), seed), r); + return pair[1] / pair[0]; + } +} + +/// +@safe @nogc pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.traits : isNaN; + + static immutable arr1 = [1, 2, 3]; + static immutable arr2 = [1.5, 2.5, 12.5]; + + assert(arr1.mean.isClose(2)); + assert(arr2.mean.isClose(5.5)); + + assert(arr1[0 .. 0].mean.isNaN); +} + +@safe pure nothrow unittest +{ + import std.internal.test.dummyrange : ReferenceInputRange; + import std.math.operations : isClose; + + auto r1 = new ReferenceInputRange!int([1, 2, 3]); + assert(r1.mean.isClose(2)); + + auto r2 = new ReferenceInputRange!double([1.5, 2.5, 12.5]); + assert(r2.mean.isClose(5.5)); +} + +// Test user defined types +@system pure unittest +{ + import std.bigint : BigInt; + import std.internal.test.dummyrange : ReferenceInputRange; + import std.math.operations : isClose; + + auto bigint_arr = [BigInt("1"), BigInt("2"), BigInt("3"), BigInt("6")]; + auto bigint_arr2 = new ReferenceInputRange!BigInt([ + BigInt("1"), BigInt("2"), BigInt("3"), BigInt("6") + ]); + assert(bigint_arr.mean(BigInt(0)) == BigInt("3")); + assert(bigint_arr2.mean(BigInt(0)) == BigInt("3")); + + BigInt[] bigint_arr3 = []; + assert(bigint_arr3.mean(BigInt(0)) == BigInt(0)); + + struct MyFancyDouble + { + double v; + alias v this; + } + + // both overloads + auto d_arr = [MyFancyDouble(10), MyFancyDouble(15), MyFancyDouble(30)]; + assert(mean!(double)(cast(double[]) d_arr).isClose(18.33333333)); + assert(mean(d_arr, MyFancyDouble(0)).isClose(18.33333333)); +} + // 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 +`pred`, by default `"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 +that can be executed via `pred(element, element)`. If the given range is +bidirectional, `uniq` also yields a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives). Params: @@ -4961,7 +7645,7 @@ Params: Returns: An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of - consecutively unique elements in the original range. If $(D r) is also a + consecutively unique elements in the original range. If `r` is also a forward range or bidirectional range, the returned range will be likewise. */ auto uniq(alias pred = "a == b", Range)(Range r) @@ -5085,7 +7769,8 @@ private struct UniqResult(alias pred, Range) } } -@safe unittest // https://issues.dlang.org/show_bug.cgi?id=17264 +// https://issues.dlang.org/show_bug.cgi?id=17264 +@safe unittest { import std.algorithm.comparison : equal; @@ -5094,12 +7779,16 @@ private struct UniqResult(alias pred, Range) } /** -Lazily computes all _permutations of $(D r) using $(HTTP +Lazily computes all _permutations of `r` using $(HTTP en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm). +Params: + Range = the range type + r = the $(REF_ALTTEXT random access range, isRandomAccessRange, std,range,primitives) + to find the permutations for. Returns: -A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) -the elements of which are an $(REF indexed, std,range) view into $(D r). + A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of elements of which are an $(REF indexed, std,range) view into `r`. See_Also: $(REF nextPermutation, std,algorithm,sorting). @@ -5130,13 +7819,13 @@ if (isRandomAccessRange!Range && hasLength!Range) _empty = r.length == 0; } - /// + /// Returns: `true` if the range is empty, `false` otherwise. @property bool empty() const pure nothrow @safe @nogc { return _empty; } - /// + /// Returns: the front of the range @property auto front() { import std.range : indexed; diff --git a/libphobos/src/std/algorithm/mutation.d b/libphobos/src/std/algorithm/mutation.d index 2b708ad..88191bb 100644 --- a/libphobos/src/std/algorithm/mutation.d +++ b/libphobos/src/std/algorithm/mutation.d @@ -1,59 +1,59 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _mutation algorithms. +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]).) + If `a = [1, 2, 3]` and `b = [4, 5, 6, 7]`, + `bringToFront(a, b)` leaves `a = [4, 5, 6]` and + `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 .. $]).) + `a = [1, 2, 3]` and `b = new int[5]`, then `copy(a, b)` + leaves `b = [1, 2, 3, 0, 0]` and returns `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]).) + e.g., if `a = new int[3]`, then `fill(a, 4)` + leaves `a = [4, 4, 4]` and `fill(a, [3, 4])` leaves + `a = [3, 4, 3]`.) $(T2 initializeAll, - If $(D a = [1.2, 3.4]), then $(D initializeAll(a)) leaves - $(D a = [double.init, double.init]).) + If `a = [1.2, 3.4]`, then `initializeAll(a)` leaves + `a = [double.init, double.init]`.) $(T2 move, - $(D move(a, b)) moves $(D a) into $(D b). $(D move(a)) reads $(D a) + `move(a, b)` moves `a` into `b`. `move(a)` reads `a` destructively when necessary.) $(T2 moveEmplace, - Similar to $(D move) but assumes `target` is uninitialized.) + Similar to `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.) + Similar to `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.) + Similar to `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]).) + If `a = [1, 2, 3]`, `reverse(a)` changes it to `[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]).) + If `a = [1, 1, 0, 1, 1]`, then `strip(a, 1)` and + `strip!(e => e == 1)(a)` returns `[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]).) + predicate. If `a = [1, 1, 0, 1, 1]`, then `stripLeft(a, 1)` and + `stripLeft!(e => e == 1)(a)` returns `[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]).) + If `a = [1, 1, 0, 1, 1]`, then `stripRight(a, 1)` and + `stripRight!(e => e == 1)(a)` returns `[1, 1, 0]`.) $(T2 swap, Swaps two values.) $(T2 swapAt, @@ -70,7 +70,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_mutation.d) +Source: $(PHOBOSSRC std/algorithm/mutation.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) @@ -78,36 +78,36 @@ 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; +import std.traits : isArray, isAssignable, isBlitAssignable, isNarrowString, + Unqual, isSomeChar, isMutable; +import std.meta : allSatisfy; +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 +`bringToFront` takes two ranges `front` and `back`, which may +be of different types. Considering the concatenation of `front` and +`back` one unified range, `bringToFront` rotates that unified +range such that all elements in `back` are brought to the beginning +of the unified range. The relative ordering of elements in `front` +and `back`, respectively, remains unchanged. + +The `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 +`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). +The `bringToFront` function 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. + 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 +Either `front` and `back` are disjoint, or `back` is +reachable from `front` and `front` is not reachable from $(D back). Params: @@ -115,10 +115,10 @@ Params: 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). + The number of elements brought to the front, i.e., the length of `back`. See_Also: - $(HTTP sgi.com/tech/stl/_rotate.html, STL's rotate) + $(LINK2 http://en.cppreference.com/w/cpp/algorithm/rotate, STL's `rotate`) */ size_t bringToFront(InputRange, ForwardRange)(InputRange front, ForwardRange back) if (isInputRange!InputRange && isForwardRange!ForwardRange) @@ -145,71 +145,8 @@ if (isInputRange!InputRange && isForwardRange!ForwardRange) 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 +The simplest use of `bringToFront` is for rotating elements in a buffer. For example: */ @safe unittest @@ -221,10 +158,10 @@ buffer. For example: } /** -The $(D front) range may actually "step over" the $(D back) +The `front` range may actually "step over" the `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). +comfortably right-bounded subranges like `arr[0 .. 4]` above. In +the example below, `r2` is a right subrange of `r1`. */ @safe unittest { @@ -275,15 +212,78 @@ Unicode integrity is not preserved: assert(b == "\247a"); } +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, "Expected front to be 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; +} + @safe unittest { import std.algorithm.comparison : equal; import std.conv : text; - import std.random : Random, unpredictableSeed, uniform; + import std.random : Random = Xorshift, uniform; // a more elaborate test { - auto rnd = Random(unpredictableSeed); + auto rnd = Random(123_456_789); 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); @@ -337,7 +337,7 @@ Unicode integrity is not preserved: assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); } - // Bugzilla 16959 + // https://issues.dlang.org/show_bug.cgi?id=16959 auto arr = ['4', '5', '6', '7', '1', '2', '3']; auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); @@ -351,11 +351,11 @@ private enum bool areCopyCompatibleArrays(T1, T2) = // copy /** -Copies the content of $(D source) into $(D target) and returns the -remaining (unfilled) part of $(D target). +Copies the content of `source` into `target` and returns the +remaining (unfilled) part of `target`. -Preconditions: $(D target) shall have enough room to accommodate -the entirety of $(D source). +Preconditions: `target` shall have enough room to accommodate +the entirety of `source`. Params: source = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) @@ -363,62 +363,66 @@ Params: 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)) +if (isInputRange!SourceRange && isOutputRange!(TargetRange, ElementType!SourceRange)) { - 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 + static if (areCopyCompatibleArrays!(SourceRange, TargetRange)) { - // 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 .. $]; - } -} + const tlen = target.length; + const slen = source.length; + assert(tlen >= slen, + "Cannot copy a source range into a smaller target range."); -/// 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]; + immutable overlaps = () @trusted { + return source.ptr < target.ptr + tlen && + target.ptr < source.ptr + slen; }(); + + if (overlaps) + { + if (source.ptr < target.ptr) + { + foreach_reverse (idx; 0 .. slen) + target[idx] = source[idx]; + } + else + { + 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 .. $]; + } } else { - put(target, source); - return target; + // 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 + { + foreach (element; source) + put(target, element); + return target; + } } } @@ -446,7 +450,7 @@ range elements, different types of ranges are accepted: } /** -To _copy at most $(D n) elements from a range, you may want to use +To _copy at most `n` elements from a range, you may want to use $(REF take, std,range): */ @safe unittest @@ -475,7 +479,7 @@ use $(LREF filter): /** $(REF retro, std,range) can be used to achieve behavior similar to -$(HTTP sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): +$(LINK2 http://en.cppreference.com/w/cpp/algorithm/copy_backward, STL's `copy_backward`'): */ @safe unittest { @@ -511,7 +515,15 @@ $(HTTP sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): assert(a[4 .. 9] == [6, 7, 8, 9, 10]); } - { // Test for bug 7898 + // https://issues.dlang.org/show_bug.cgi?id=21724 + { + int[] a = [1, 2, 3, 4]; + copy(a[0 .. 2], a[1 .. 3]); + assert(a == [1, 1, 2, 4]); + } + + // https://issues.dlang.org/show_bug.cgi?id=7898 + { enum v = { import std.algorithm; @@ -524,29 +536,58 @@ $(HTTP sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): } } +// https://issues.dlang.org/show_bug.cgi?id=13650 @safe unittest { - // Issue 13650 import std.meta : AliasSeq; - foreach (Char; AliasSeq!(char, wchar, dchar)) - { + static 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"); + }} +} + +// https://issues.dlang.org/show_bug.cgi?id=18804 +@safe unittest +{ + static struct NullSink + { + void put(E)(E) {} } + int line = 0; + struct R + { + int front; + @property bool empty() { return line == 1; } + void popFront() { line = 1; } + } + R r; + copy(r, NullSink()); + assert(line == 1); } /** -Assigns $(D value) to each element of input _range $(D range). +Assigns `value` to each element of input range `range`. + +Alternatively, instead of using a single `value` to fill the `range`, +a `filter` $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +can be provided. The length of `filler` and `range` do not need to match, but +`filler` must not be empty. Params: range = An - $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + $(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 + filler = A + $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + representing the _fill pattern. + +Throws: If `filler` is empty. See_Also: $(LREF uninitializedFill) @@ -583,7 +624,8 @@ if ((isInputRange!Range && is(typeof(range.front = value)) || assert(a == [ 5, 5, 5, 5 ]); } -// issue 16342, test fallback on mutable narrow strings +// test fallback on mutable narrow strings +// https://issues.dlang.org/show_bug.cgi?id=16342 @safe unittest { char[] chars = ['a', 'b']; @@ -646,7 +688,7 @@ if ((isInputRange!Range && is(typeof(range.front = value)) || chars[1 .. 3].fill(':'); assert(chars == "a::d"); } -// end issue 16342 +// end https://issues.dlang.org/show_bug.cgi?id=16342 @safe unittest { @@ -712,18 +754,7 @@ if ((isInputRange!Range && is(typeof(range.front = value)) || } } -/** -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. - */ +/// ditto void fill(InputRange, ForwardRange)(InputRange range, ForwardRange filler) if (isInputRange!InputRange && (isForwardRange!ForwardRange @@ -832,12 +863,12 @@ if (isInputRange!InputRange } /** -Initializes all elements of $(D range) with their $(D .init) value. +Initializes all elements of `range` with their `.init` value. Assumes that the elements of the range are uninitialized. Params: range = An - $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that exposes references to its elements and has assignable elements @@ -859,9 +890,9 @@ if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range //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) + static if (!__traits(isZeroInit, T)) { + auto p = typeid(T).initializer(); for ( ; !range.empty ; range.popFront() ) { static if (__traits(isStaticArray, T)) @@ -972,7 +1003,7 @@ if (is(Range == char[]) || is(Range == wchar[])) assert(!typeid(S3).initializer().ptr); assert( typeid(S4).initializer().ptr); - foreach (S; AliasSeq!(S1, S2, S3, S4)) + static foreach (S; AliasSeq!(S1, S2, S3, S4)) { //initializeAll { @@ -1033,8 +1064,8 @@ 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. +If source has internal pointers that point to itself and doesn't define +opPostMove, it cannot be moved, and will trigger an assertion failure. Params: source = Data to copy. @@ -1043,11 +1074,7 @@ Params: */ 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); + moveImpl(target, source); } /// For non-struct types, `move` just performs `target = source`: @@ -1123,7 +1150,7 @@ pure nothrow @safe @nogc unittest move(s21, s22); assert(s21 == s22); }); - // Issue 5661 test(1) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(1) static struct S3 { static struct X { int n = 0; ~this(){n = 0;} } @@ -1136,7 +1163,7 @@ pure nothrow @safe @nogc unittest assert(s31.x.n == 0); assert(s32.x.n == 1); - // Issue 5661 test(2) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(2) static struct S4 { static struct X { int n = 0; this(this){n = 0;} } @@ -1149,7 +1176,7 @@ pure nothrow @safe @nogc unittest assert(s41.x.n == 0); assert(s42.x.n == 1); - // Issue 13990 test + // https://issues.dlang.org/show_bug.cgi?id=13990 test class S5; S5 s51; @@ -1160,13 +1187,9 @@ pure nothrow @safe @nogc unittest } /// Ditto -T move(T)(ref T source) +T move(T)(return scope ref T source) { - // test @safe destructible - static if (__traits(compiles, (T t) @safe {})) - return trustedMoveImpl(source); - else - return moveImpl(source); + return moveImpl(source); } /// Non-copyable structs can still be moved: @@ -1185,34 +1208,71 @@ pure nothrow @safe @nogc unittest assert(s2.a == 2); } -private void trustedMoveImpl(T)(ref T source, ref T target) @trusted +/// `opPostMove` will be called if defined: +pure nothrow @safe @nogc unittest +{ + struct S + { + int a; + void opPostMove(const ref S old) + { + assert(a == old.a); + a++; + } + } + S s1; + s1.a = 41; + S s2 = move(s1); + assert(s2.a == 42); +} + +// https://issues.dlang.org/show_bug.cgi?id=20869 +// `move` should propagate the attributes of `opPostMove` +@system unittest { - moveImpl(source, target); + static struct S + { + void opPostMove(const ref S old) nothrow @system + { + __gshared int i; + new int(i++); // Force @gc impure @system + } + } + + alias T = void function() @system nothrow; + static assert(is(typeof({ S s; move(s); }) == T)); + static assert(is(typeof({ S s; move(s, s); }) == T)); } -private void moveImpl(T)(ref T source, ref T target) +private void moveImpl(T)(ref scope T target, ref return scope T source) { import std.traits : hasElaborateDestructor; static if (is(T == struct)) { - if (&source == &target) return; + // Unsafe when compiling without -dip1000 + if ((() @trusted => &source == &target)()) return; + // Destroy target before overwriting it static if (hasElaborateDestructor!T) target.__xdtor(); } // move and emplace source into target - moveEmplace(source, target); + moveEmplaceImpl(target, source); } -private T trustedMoveImpl(T)(ref T source) @trusted +private T moveImpl(T)(ref return scope T source) { - return moveImpl(source); + // Properly infer safety from moveEmplaceImpl as the implementation below + // might void-initialize pointers in result and hence needs to be @trusted + if (false) moveEmplaceImpl(source, source); + + return trustedMoveImpl(source); } -private T moveImpl(T)(ref T source) +private T trustedMoveImpl(T)(ref return scope T source) @trusted { T result = void; - moveEmplace(source, result); + moveEmplaceImpl(result, source); return result; } @@ -1239,7 +1299,7 @@ private T moveImpl(T)(ref T source) assert(s21 == s22); }); - // Issue 5661 test(1) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(1) static struct S3 { static struct X { int n = 0; ~this(){n = 0;} } @@ -1252,7 +1312,7 @@ private T moveImpl(T)(ref T source) assert(s31.x.n == 0); assert(s32.x.n == 1); - // Issue 5661 test(2) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(2) static struct S4 { static struct X { int n = 0; this(this){n = 0;} } @@ -1265,7 +1325,7 @@ private T moveImpl(T)(ref T source) assert(s41.x.n == 0); assert(s42.x.n == 1); - // Issue 13990 test + // https://issues.dlang.org/show_bug.cgi?id=13990 test class S5; S5 s51; @@ -1289,14 +1349,16 @@ private T moveImpl(T)(ref T source) assert(a.n == 0); } -@safe unittest//Issue 6217 +// https://issues.dlang.org/show_bug.cgi?id=6217 +@safe unittest { import std.algorithm.iteration : map; auto x = map!"a"([1,2,3]); x = move(x); } -@safe unittest// Issue 8055 +// https://issues.dlang.org/show_bug.cgi?id=8055 +@safe unittest { static struct S { @@ -1316,7 +1378,8 @@ private T moveImpl(T)(ref T source) assert(b.x == 0); } -@system unittest// Issue 8057 +// https://issues.dlang.org/show_bug.cgi?id=8057 +@system unittest { int n = 10; struct S @@ -1338,7 +1401,7 @@ private T moveImpl(T)(ref T source) auto b = foo(a); assert(b.x == 1); - // Regression 8171 + // Regression https://issues.dlang.org/show_bug.cgi?id=8171 static struct Array(T) { // nested struct has no member @@ -1352,37 +1415,34 @@ private T moveImpl(T)(ref T source) 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 +private void moveEmplaceImpl(T)(ref scope T target, ref return scope T source) { import core.stdc.string : memcpy, memset; import std.traits : hasAliasing, hasElaborateAssign, hasElaborateCopyConstructor, hasElaborateDestructor, - isAssignable; + hasElaborateMove, + isAssignable, isStaticArray; static if (!is(T == class) && hasAliasing!T) if (!__ctfe) { import std.exception : doesPointTo; - assert(!doesPointTo(source, source), "Cannot move object with internal pointer."); + assert(!(doesPointTo(source, source) && !hasElaborateMove!T), + "Cannot move object with internal pointer unless `opPostMove` is defined."); } static if (is(T == struct)) { - assert(&source !is &target, "source and target must not be identical"); + // Unsafe when compiling without -dip1000 + assert((() @trusted => &source !is &target)(), "source and target must not be identical"); static if (hasElaborateAssign!T || !isAssignable!T) - memcpy(&target, &source, T.sizeof); + () @trusted { memcpy(&target, &source, T.sizeof); }(); else target = source; + static if (hasElaborateMove!T) + __move_post_blt(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) @@ -1393,13 +1453,20 @@ void moveEmplace(T)(ref T source, ref T target) @system 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); + static if (__traits(isZeroInit, T)) + () @trusted { memset(&source, 0, sz); }(); else - memcpy(&source, init.ptr, sz); + { + auto init = typeid(T).initializer(); + () @trusted { memcpy(&source, init.ptr, sz); }(); + } } } + else static if (isStaticArray!T) + { + for (size_t i = 0; i < source.length; ++i) + move(source[i], target[i]); + } else { // Primitive data (including pointers and arrays) or class - @@ -1408,6 +1475,20 @@ void moveEmplace(T)(ref T source, ref T target) @system } } +/** + * 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) pure @system +{ + moveEmplaceImpl(target, source); +} + /// pure nothrow @nogc @system unittest { @@ -1433,6 +1514,24 @@ pure nothrow @nogc @system unittest assert(val == 0); } +// https://issues.dlang.org/show_bug.cgi?id=18913 +@safe unittest +{ + static struct NoCopy + { + int payload; + ~this() { } + @disable this(this); + } + + static void f(NoCopy[2]) { } + + NoCopy[2] ncarray = [ NoCopy(1), NoCopy(2) ]; + + static assert(!__traits(compiles, f(ncarray))); + f(move(ncarray)); +} + // moveAll /** Calls `move(a, b)` for each element `a` in `src` and the corresponding @@ -1447,9 +1546,9 @@ 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. + elements that elements from `src` can be moved into. -Returns: The leftover portion of $(D tgt) after all elements from $(D src) have +Returns: The leftover portion of `tgt` after all elements from `src` have been moved. */ InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) @@ -1528,7 +1627,7 @@ private InputRange2 moveAllImpl(alias moveOp, InputRange1, InputRange2)( && hasSlicing!InputRange2 && isRandomAccessRange!InputRange2) { auto toMove = src.length; - assert(toMove <= tgt.length); + assert(toMove <= tgt.length, "Source buffer needs to be smaller or equal to the target buffer."); foreach (idx; 0 .. toMove) moveOp(src[idx], tgt[idx]); return tgt[toMove .. tgt.length]; @@ -1537,7 +1636,7 @@ private InputRange2 moveAllImpl(alias moveOp, InputRange1, InputRange2)( { for (; !src.empty; src.popFront(), tgt.popFront()) { - assert(!tgt.empty); + assert(!tgt.empty, "Source buffer needs to be smaller or equal to the target buffer."); moveOp(src.front, tgt.front); } return tgt; @@ -1554,7 +1653,7 @@ 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. + elements that elements from `src` can be moved into. Returns: The leftover portions of the two ranges after one or the other of the ranges have been exhausted. @@ -1624,15 +1723,15 @@ 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 +"b", "aBc" ]) according to `toUpper(a) < toUpper(b)`. That +algorithm might choose to swap the two equivalent strings `"abc"` +and `"aBc"`. That does not affect the sorting since both +`["abc", "aBc", "b" ]` and `[ "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 +`[ "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). @@ -1642,12 +1741,12 @@ 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 +Generally, the `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 +constraints. Similarly, `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. +algorithms in this module parameterized by `SwapStrategy` all +choose `SwapStrategy.unstable` as the default. */ enum SwapStrategy @@ -1672,8 +1771,6 @@ enum SwapStrategy /// @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]; @@ -1699,11 +1796,27 @@ enum SwapStrategy assert(arr == [10, 9, 8, 4, 5, 6, 7, 3, 2, 1]); } +private template isValidIntegralTuple(T) +{ + import std.traits : isIntegral; + import std.typecons : isTuple; + static if (isTuple!T) + { + enum isValidIntegralTuple = T.length == 2 && + isIntegral!(typeof(T.init[0])) && isIntegral!(typeof(T.init[0])); + } + else + { + enum isValidIntegralTuple = isIntegral!T; + } +} + + /** 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: +For example, here is how to remove a single element from an array: ---- string[] a = [ "a", "b", "c", "d" ]; @@ -1711,9 +1824,9 @@ 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 +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: ---- @@ -1722,7 +1835,7 @@ 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 +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 @@ -1730,7 +1843,7 @@ 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, +Multiple indices can be passed into `remove`. In that case, elements at the respective indices are all removed. The indices must be passed in increasing order, otherwise an exception occurs. @@ -1741,66 +1854,269 @@ assert(remove(a, 1, 3, 5) == ---- (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. +in the array as it is being progressively shortened.) + +Tuples of two integral offsets can be used to remove an indices range: ---- -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 ]); +int[] a = [ 3, 4, 5, 6, 7]; +assert(remove(a, 1, tuple(1, 3), 9) == [ 3, 6, 7 ]); ---- -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). +The tuple passes in a range closed to the left and open to +the right (consistent with built-in slices), e.g. `tuple(1, 3)` +means indices `1` and `2` but not `3`. -If the need is to remove some elements in the range but the order of +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. + +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). +pass `SwapStrategy.unstable` to `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 +In the case above, the element at slot `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 stability requirement, `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 +The function `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 && + +$(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 == +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 +calls to `range.popFront`.) + $(LI Otherwise, elements are moved +incrementally towards the front of `range`; a given element is never moved several times, but more elements are moved than in the previous -cases.)) +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) + 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 - */ + 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 (Offset.length >= 1 && allSatisfy!(isValidIntegralTuple, Offset)) +{ + // Activate this check when the deprecation of non-integral tuples is over + //import std.traits : isIntegral; + //import std.typecons : isTuple; + //static foreach (T; Offset) + //{ + //static if (isTuple!T) + //{ + //static assert(T.length == 2 && + //isIntegral!(typeof(T.init[0])) && isIntegral!(typeof(T.init[0])), + //"Each offset must be an integral or a tuple of two integrals." ~ + //"Use `arr.remove(pos1, pos2)` or `arr.remove(tuple(start, begin))`"); + //} + //else + //{ + //static assert(isIntegral!T, + //"Each offset must be an integral or a tuple of two integrals." ~ + //"Use `arr.remove(pos1, pos2)` or `arr.remove(tuple(start, begin))`"); + //} + //} + return removeImpl!s(range, offset); +} + +deprecated("Use of non-integral tuples is deprecated. Use remove(tuple(start, end).") Range remove -(SwapStrategy s = SwapStrategy.stable, Range, Offset...) +(SwapStrategy s = SwapStrategy.stable, Range, Offset ...) (Range range, Offset offset) -if (s != SwapStrategy.stable - && isBidirectionalRange!Range - && hasLvalueElements!Range - && hasLength!Range - && Offset.length >= 1) +if (Offset.length >= 1 && !allSatisfy!(isValidIntegralTuple, Offset)) +{ + return removeImpl!s(range, offset); +} + +/// +@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 pure unittest +{ + import std.typecons : tuple; + + // Delete an index + assert([4, 5, 6].remove(1) == [4, 6]); + + // Delete multiple indices + assert([4, 5, 6, 7, 8].remove(1, 3) == [4, 6, 8]); + + // Use an indices range + assert([4, 5, 6, 7, 8].remove(tuple(1, 3)) == [4, 7, 8]); + + // Use an indices range and individual indices + assert([4, 5, 6, 7, 8].remove(0, tuple(1, 3), 4) == [7]); +} + +/// `SwapStrategy.unstable` is faster, but doesn't guarantee the same order of the original array +@safe pure unittest +{ + assert([5, 6, 7, 8].remove!(SwapStrategy.stable)(1) == [5, 7, 8]); + assert([5, 6, 7, 8].remove!(SwapStrategy.unstable)(1) == [5, 8, 7]); +} + +private auto removeImpl(SwapStrategy s, Range, Offset...)(Range range, Offset offset) +{ + static if (isNarrowString!Range) + { + static assert(isMutable!(typeof(range[0])), + "Elements must be mutable to remove"); + static assert(s == SwapStrategy.stable, + "Only stable removing can be done for character arrays"); + return removeStableString(range, offset); + } + else + { + static assert(isBidirectionalRange!Range, + "Range must be bidirectional"); + static assert(hasLvalueElements!Range, + "Range must have Lvalue elements (see std.range.hasLvalueElements)"); + + static if (s == SwapStrategy.unstable) + { + static assert(hasLength!Range, + "Range must have `length` for unstable remove"); + return removeUnstable(range, offset); + } + else static if (s == SwapStrategy.stable) + return removeStable(range, offset); + else + static assert(false, + "Only SwapStrategy.stable and SwapStrategy.unstable are supported"); + } +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.range; + + // https://issues.dlang.org/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 ]); + // https://issues.dlang.org/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]); +} + +// https://issues.dlang.org/show_bug.cgi?id=11576 +@safe unittest +{ + auto arr = [1,2,3]; + arr = arr.remove!(SwapStrategy.unstable)(2); + assert(arr == [1,2]); + +} + +// https://issues.dlang.org/show_bug.cgi?id=12889 +@safe unittest +{ + import std.range; + 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))); + } +} + +@safe unittest +{ + char[] chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + remove(chars, 4); + assert(chars == ['a', 'b', 'c', 'd', 'f', 'g', 'h', 'h']); + + char[] bigChars = "∑œ∆¬é˚˙ƒé∂ß¡¡".dup; + assert(remove(bigChars, tuple(4, 6), 8) == ("∑œ∆¬˙ƒ∂ß¡¡")); + + import std.exception : assertThrown; + assertThrown(remove(bigChars.dup, 1, 0)); + assertThrown(remove(bigChars.dup, tuple(4, 3))); +} + +private Range removeUnstable(Range, Offset...)(Range range, Offset offset) { Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts; foreach (i, v; offset) @@ -1849,7 +2165,7 @@ if (s != SwapStrategy.stable break; } // Advance to next blackout on the left - assert(blackouts[left].pos >= tgtPos); + assert(blackouts[left].pos >= tgtPos, "Next blackout on the left shouldn't appear before the target."); tgt.popFrontExactly(blackouts[left].pos - tgtPos); tgtPos = blackouts[left].pos; @@ -1879,14 +2195,7 @@ if (s != SwapStrategy.stable 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) +private Range removeStable(Range, Offset...)(Range range, Offset offset) { auto result = range; auto src = range, tgt = range; @@ -1930,149 +2239,121 @@ if (s == SwapStrategy.stable return result; } -/// -@safe pure unittest +private Range removeStableString(Range, Offset...)(Range range, Offset offsets) { - 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]); -} + import std.utf : stride; + size_t charIdx = 0; + size_t dcharIdx = 0; + size_t charShift = 0; -@safe unittest -{ - import std.exception : assertThrown; - import std.range; + void skipOne() + { + charIdx += stride(range[charIdx .. $]); + ++dcharIdx; + } - // 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)); -} + void copyBackOne() + { + auto encodedLen = stride(range[charIdx .. $]); + foreach (j; charIdx .. charIdx + encodedLen) + range[j - charShift] = range[j]; + charIdx += encodedLen; + ++dcharIdx; + } -@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 ]); + foreach (pass, i; offsets) + { + static if (is(typeof(i[0])) && is(typeof(i[1]))) + { + auto from = i[0]; + auto delta = i[1] - i[0]; + } + else + { + auto from = i; + enum delta = 1; + } - 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 ]); + import std.exception : enforce; + enforce(dcharIdx <= from && delta >= 0, + "remove(): incorrect ordering of elements to remove"); - 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 ]); + while (dcharIdx < from) + static if (pass == 0) + skipOne(); + else + copyBackOne(); - 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 ]); + auto mark = charIdx; + while (dcharIdx < from + delta) + skipOne(); + charShift += charIdx - mark; + } - 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]); + foreach (i; charIdx .. range.length) + range[i - charShift] = range[i]; - a = iota(0, 10).array(); - assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7)) - == [0, 9, 8, 7, 4, 5]); + return range[0 .. $ - charShift]; } +// Use of dynamic arrays as offsets is too error-prone +// https://issues.dlang.org/show_bug.cgi?id=12086 +// Activate these tests once the deprecation period of remove with non-integral tuples is over @safe unittest { - // Issue 11576 - auto arr = [1,2,3]; - arr = arr.remove!(SwapStrategy.unstable)(2); - assert(arr == [1,2]); + //static assert(!__traits(compiles, [0, 1, 2, 3, 4].remove([1, 3]) == [0, 3, 4])); + static assert(__traits(compiles, [0, 1, 2, 3, 4].remove(1, 3) == [0, 2, 4])); + //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove([1, 3, 4]) == [0, 3, 4]))); + //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove(tuple(1, 3, 4)) == [0, 3, 4]))); -} - -@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))); - } + import std.range : only; + //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove(only(1, 3)) == [0, 3, 4]))); + static assert(__traits(compiles, assert([0, 1, 2, 3, 4].remove(1, 3) == [0, 2, 4]))); } /** 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), +$(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) `range` by removing +elements that satisfy `pred`. If `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), +to eliminate. If `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 + or mutable character arrays Returns: - the range with all of the elements where $(D pred) is $(D true) + the range with all of the elements where `pred` is `true` removed */ -Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range) -(Range range) -if (isBidirectionalRange!Range - && hasLvalueElements!Range) +Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range)(Range range) { import std.functional : unaryFun; - auto result = range; - static if (s != SwapStrategy.stable) + alias pred_ = unaryFun!pred; + static if (isNarrowString!Range) { - for (;!range.empty;) - { - if (!unaryFun!pred(range.front)) - { - range.popFront(); - continue; - } - move(range.back, range.front); - range.popBack(); - result.popBack(); - } + static assert(isMutable!(typeof(range[0])), + "Elements must be mutable to remove"); + static assert(s == SwapStrategy.stable, + "Only stable removing can be done for character arrays"); + return removePredString!pred_(range); } 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(); - } + static assert(isBidirectionalRange!Range, + "Range must be bidirectional"); + static assert(hasLvalueElements!Range, + "Range must have Lvalue elements (see std.range.hasLvalueElements)"); + static if (s == SwapStrategy.unstable) + return removePredUnstable!pred_(range); + else static if (s == SwapStrategy.stable) + return removePredStable!pred_(range); + else + static assert(false, + "Only SwapStrategy.stable and SwapStrategy.unstable are supported"); } - return result; } /// @@ -2104,10 +2385,10 @@ if (isBidirectionalRange!Range [ 1, 3, 3, 4, 5, 5, 6 ]); } -@nogc @system unittest +@nogc @safe unittest { // @nogc test - int[10] arr = [0,1,2,3,4,5,6,7,8,9]; + static int[] arr = [0,1,2,3,4,5,6,7,8,9]; alias pred = e => e < 5; auto r = arr[].remove!(SwapStrategy.unstable)(0); @@ -2125,19 +2406,20 @@ if (isBidirectionalRange!Range import std.meta : AliasSeq; import std.range : iota, only; import std.typecons : Tuple; - alias S = Tuple!(int[2]); + alias E = Tuple!(int, int); + alias S = Tuple!(E); S[] soffsets; foreach (start; 0 .. 5) foreach (end; min(start+1,5) .. 5) - soffsets ~= S([start,end]); - alias D = Tuple!(int[2],int[2]); + soffsets ~= S(E(start,end)); + alias D = Tuple!(E, E); 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]); + doffsets ~= D(E(start1,end1),E(start2,end2)); + alias T = Tuple!(E, E, E); T[] toffsets; foreach (start1; 0 .. 15) foreach (end1; min(start1+1,15) .. 15) @@ -2145,7 +2427,7 @@ if (isBidirectionalRange!Range 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]); + toffsets ~= T(E(start1,end1),E(start2,end2),E(start3,end3)); static void verify(O...)(int[] r, int len, int removed, bool stable, O offsets) { @@ -2154,7 +2436,7 @@ if (isBidirectionalRange!Range assert(r.all!(e => all!(o => e < o[0] || e >= o[1])(offsets.only))); } - foreach (offsets; AliasSeq!(soffsets,doffsets,toffsets)) + static foreach (offsets; AliasSeq!(soffsets,doffsets,toffsets)) foreach (os; offsets) { int len = 5*os.length; @@ -2179,109 +2461,186 @@ if (isBidirectionalRange!Range } } -// 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 +@safe unittest +{ + char[] chars = "abcdefg".dup; + assert(chars.remove!(dc => dc == 'c' || dc == 'f') == "abdeg"); + assert(chars == "abdegfg"); -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) + assert(chars.remove!"a == 'd'" == "abegfg"); + + char[] bigChars = "¥^¨^©é√∆π".dup; + assert(bigChars.remove!(dc => dc == "¨"d[0] || dc == "é"d[0]) == "¥^^©√∆π"); +} + +private Range removePredUnstable(alias pred, Range)(Range range) { - while (!r.empty) + auto result = range; + for (;!range.empty;) { - swap(r.front, r.back); - r.popFront(); - if (r.empty) break; - r.popBack(); + if (!pred(range.front)) + { + range.popFront(); + continue; + } + move(range.back, range.front); + range.popBack(); + result.popBack(); } + return result; } -/// -@safe unittest +private Range removePredStable(alias pred, Range)(Range range) { - int[] arr = [ 1, 2, 3 ]; - reverse(arr); - assert(arr == [ 3, 2, 1 ]); + auto result = range; + auto tgt = range; + for (; !range.empty; range.popFront()) + { + if (pred(range.front)) + { + // yank this guy + result.popBack(); + continue; + } + // keep this guy + move(range.front, tgt.front); + tgt.popFront(); + } + return result; } -///ditto -void reverse(Range)(Range r) -if (isRandomAccessRange!Range && hasLength!Range) +private Range removePredString(alias pred, SwapStrategy s = SwapStrategy.stable, Range) +(Range 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++) + import std.utf : decode; + import std.functional : unaryFun; + + alias pred_ = unaryFun!pred; + + size_t charIdx = 0; + size_t charShift = 0; + while (charIdx < range.length) { - r.swapAt(i, last-i); + size_t start = charIdx; + if (pred_(decode(range, charIdx))) + { + charShift += charIdx - start; + break; + } + } + while (charIdx < range.length) + { + size_t start = charIdx; + auto doRemove = pred_(decode(range, charIdx)); + auto encodedLen = charIdx - start; + if (doRemove) + charShift += encodedLen; + else + foreach (i; start .. charIdx) + range[i - charShift] = range[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]); + return range[0 .. $ - charShift]; } +// reverse /** -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. +Reverses `r` in-place. Performs `r.length / 2` evaluations of `swap`. +UTF sequences consisting of multiple code units are preserved properly. Params: - s = a narrow string + r = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) + with either swappable elements, a random access range with a length member, + or a narrow string + +Returns: `r` -Bugs: - When passing a sting with unicode modifiers on characters, such as $(D \u0301), +Note: + When passing a string with unicode modifiers on characters, such as `\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; + reversing `ba\u0301d` ("bád") will result in d\u0301ab ("d́ab") instead of + `da\u0301b` ("dáb"). - auto r = representation(s); - for (size_t i = 0; i < s.length; ) +See_Also: $(REF retro, std,range) for a lazy reverse without changing `r` +*/ +Range reverse(Range)(Range r) +if (isBidirectionalRange!Range && + (hasSwappableElements!Range || + (hasAssignableElements!Range && hasLength!Range && isRandomAccessRange!Range) || + (isNarrowString!Range && isAssignable!(ElementType!Range)))) +{ + static 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); + } + return r; + } + else static if (isNarrowString!Range && isAssignable!(ElementType!Range)) { - immutable step = stride(s, i); - if (step > 1) + import std.string : representation; + import std.utf : stride; + + auto raw = representation(r); + for (size_t i = 0; i < r.length;) { - .reverse(r[i .. i + step]); - i += step; + immutable step = stride(r, i); + if (step > 1) + { + .reverse(raw[i .. i + step]); + i += step; + } + else + { + ++i; + } } - else + reverse(raw); + return r; + } + else + { + while (!r.empty) { - ++i; + swap(r.front, r.back); + r.popFront(); + if (r.empty) break; + r.popBack(); } + return r; } - reverse(r); +} + +/// +@safe unittest +{ + int[] arr = [ 1, 2, 3 ]; + assert(arr.reverse == [ 3, 2, 1 ]); +} + +@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]; + assert(range.reverse == [3, 2, 1]); } /// @safe unittest { char[] arr = "hello\U00010143\u0100\U00010143".dup; - reverse(arr); - assert(arr == "\U00010143\u0100\U00010143olleh"); + assert(arr.reverse == "\U00010143\u0100\U00010143olleh"); } @safe unittest @@ -2307,12 +2666,12 @@ if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable)) 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) + The `stripLeft` function will strip the `front` of the range, + the `stripRight` function will strip the `back` of the range, + while the `strip` function will strip both the `front` and `back` of the range. - Note that the $(D strip) and $(D stripRight) functions require the range to + Note that the `strip` and `stripRight` functions require the range to be a $(LREF BidirectionalRange) range. All of these functions come in two varieties: one takes a target element, @@ -2445,18 +2804,18 @@ if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) // 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) +Swaps `lhs` and `rhs`. The instances `lhs` and `rhs` are moved in +memory, without ever calling `opAssign`, nor any other function. `T` need not be assignable at all to be swapped. -If $(D lhs) and $(D rhs) reference the same instance, then nothing is done. +If `lhs` and `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 +`lhs` and `rhs` must be mutable. If `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). + lhs = Data to be swapped with `rhs`. + rhs = Data to be swapped with `lhs`. */ void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow @nogc if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) @@ -2499,8 +2858,15 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) return; } - // For non-struct types, suffice to do the classic swap - auto tmp = lhs; + // For non-elaborate-assign types, suffice to do the classic swap + static if (__traits(hasCopyConstructor, T)) + { + // don't invoke any elaborate constructors either + T tmp = void; + tmp = lhs; + } + else + auto tmp = lhs; lhs = rhs; rhs = tmp; } @@ -2585,9 +2951,9 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) static assert(!__traits(compiles, swap(const1, const2))); } +// https://issues.dlang.org/show_bug.cgi?id=4789 @safe unittest { - //Bug# 4789 int[1] s = [1]; swap(s, s); @@ -2625,23 +2991,24 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) assert(s.i3 == 2); } +// https://issues.dlang.org/show_bug.cgi?id=11853 @safe unittest { - //11853 import std.traits : isAssignable; alias T = Tuple!(int, double); static assert(isAssignable!T); } +// https://issues.dlang.org/show_bug.cgi?id=12024 @safe unittest { - // 12024 import std.datetime; SysTime a, b; swap(a, b); } -@system unittest // 9975 +// https://issues.dlang.org/show_bug.cgi?id=9975 +@system unittest { import std.exception : doesPointTo, mayPointTo; static struct S2 @@ -2686,6 +3053,31 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) swap(b1, b2); } +// issue 20732 +@safe unittest +{ + static struct A + { + int x; + this(scope ref return const A other) + { + import std.stdio; + x = other.x; + // note, struct functions inside @safe functions infer ALL + // attributes, so the following 3 lines are meant to prevent this. + new int; // prevent @nogc inference + writeln("impure"); // prevent pure inference + throw new Exception(""); // prevent nothrow inference + } + } + + A a1, a2; + swap(a1, a2); + + A[1] a3, a4; + swap(a3, a4); +} + /// ditto void swap(T)(ref T lhs, ref T rhs) if (is(typeof(lhs.proxySwap(rhs)))) @@ -2820,16 +3212,16 @@ if (isInputRange!R1 && isInputRange!R2) // 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 +Swaps all elements of `r1` with successive elements in `r2`. +Returns a tuple containing the remainder portions of `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) + r1 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with swappable elements - r2 = an $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + r2 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with swappable elements Returns: @@ -2860,7 +3252,7 @@ if (hasSwappableElements!InputRange1 && hasSwappableElements!InputRange2 } /** -Initializes each element of $(D range) with $(D value). +Initializes each element of `range` with `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 @@ -2868,7 +3260,7 @@ uninitializedFill are equivalent). Params: range = An - $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + $(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 @@ -2885,7 +3277,7 @@ if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = val alias T = ElementType!Range; static if (hasElaborateAssign!T) { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; // Must construct stuff by the book for (; !range.empty; range.popFront()) diff --git a/libphobos/src/std/algorithm/package.d b/libphobos/src/std/algorithm/package.d index 4c9a72f..6aacd51 100644 --- a/libphobos/src/std/algorithm/package.d +++ b/libphobos/src/std/algorithm/package.d @@ -79,9 +79,12 @@ $(TR $(SUBREF iteration, group) $(SUBREF iteration, joiner) $(SUBREF iteration, map) + $(SUBREF iteration, mean) $(SUBREF iteration, permutations) $(SUBREF iteration, reduce) + $(SUBREF iteration, splitWhen) $(SUBREF iteration, splitter) + $(SUBREF iteration, substitute) $(SUBREF iteration, sum) $(SUBREF iteration, uniq) ) @@ -152,12 +155,12 @@ 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 +expression that uses the symbol `a` (for unary functions) or the +symbols `a` and `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. +comparison predicates is `"a == b"` for unordered operations and +`"a < b"` for ordered operations. Example: @@ -184,7 +187,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/_algorithm/package.d) +Source: $(PHOBOSSRC std/algorithm/package.d) */ module std.algorithm; diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d index 09073f6..d6d02ba 100644 --- a/libphobos/src/std/algorithm/searching.d +++ b/libphobos/src/std/algorithm/searching.d @@ -1,64 +1,64 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _searching algorithms. +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 + `all!"a > 0"([1, 2, 3, 4])` returns `true` because all elements are positive) $(T2 any, - $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one + `any!"a > 0"([1, 2, -3, -4])` returns `true` because at least one element is positive) $(T2 balancedParens, - $(D balancedParens("((1 + 1) / 2)")) returns $(D true) because the + `balancedParens("((1 + 1) / 2)")` returns `true` because the string has balanced parentheses.) $(T2 boyerMooreFinder, - $(D find("hello world", boyerMooreFinder("or"))) returns $(D "orld") + `find("hello world", boyerMooreFinder("or"))` returns `"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).) + `canFind("hello world", "or")` returns `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).) + predicate. `count([1, 2, 1], 1)` returns `2` and + `count!"a < 0"([1, -3, 0])` returns `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).) + `countUntil(a, b)` returns the number of steps taken in `a` to + reach `b`; for example, `countUntil("hello!", "o")` returns + `4`.) $(T2 commonPrefix, - $(D commonPrefix("parakeet", "parachute")) returns $(D "para").) + `commonPrefix("parakeet", "parachute")` returns `"para"`.) $(T2 endsWith, - $(D endsWith("rocks", "ks")) returns $(D true).) + `endsWith("rocks", "ks")` returns `true`.) $(T2 find, - $(D find("hello world", "or")) returns $(D "orld") using linear search. - (For binary search refer to $(REF sortedRange, std,range).)) + `find("hello world", "or")` returns `"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]).) + `findAdjacent([1, 2, 3, 3, 4])` returns the subrange starting with + two equal adjacent elements, i.e. `[3, 3, 4]`.) $(T2 findAmong, - $(D findAmong("abcd", "qcx")) returns $(D "cd") because $(D 'c') is - among $(D "qcx").) + `findAmong("abcd", "qcx")` returns `"cd"` because `'c'` is + among `"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).) + If `a = "abcde"`, then `findSkip(a, "x")` returns `false` and + leaves `a` unchanged, whereas `findSkip(a, "c")` advances `a` + to `"de"` and returns `true`.) $(T2 findSplit, - $(D findSplit("abcdefg", "de")) returns the three ranges $(D "abc"), - $(D "de"), and $(D "fg").) + `findSplit("abcdefg", "de")` returns a tuple of three ranges `"abc"`, + `"de"`, and `"fg"`.) $(T2 findSplitAfter, - $(D findSplitAfter("abcdefg", "de")) returns the two ranges - $(D "abcde") and $(D "fg").) +`findSplitAfter("abcdefg", "de")` returns a tuple of two ranges `"abcde"` + and `"fg"`.) $(T2 findSplitBefore, - $(D findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc") - and $(D "defg").) + `findSplitBefore("abcdefg", "de")` returns a tuple of two ranges `"abc"` + and `"defg"`.) $(T2 minCount, - $(D minCount([2, 1, 1, 4, 1])) returns $(D tuple(1, 3)).) + `minCount([2, 1, 1, 4, 1])` returns `tuple(1, 3)`.) $(T2 maxCount, - $(D maxCount([2, 4, 1, 4, 1])) returns $(D tuple(4, 2)).) + `maxCount([2, 4, 1, 4, 1])` returns `tuple(4, 2)`.) $(T2 minElement, Selects the minimal element of a range. `minElement([3, 4, 1, 2])` returns `1`.) @@ -67,27 +67,24 @@ $(T2 maxElement, `maxElement([3, 4, 1, 2])` returns `4`.) $(T2 minIndex, Index of the minimal element of a range. - `minElement([3, 4, 1, 2])` returns `2`.) + `minIndex([3, 4, 1, 2])` returns `2`.) $(T2 maxIndex, Index of the maximal element of a range. - `maxElement([3, 4, 1, 2])` returns `1`.) + `maxIndex([3, 4, 1, 2])` returns `1`.) $(T2 minPos, - $(D minPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [1, 3, 4, 1]), + `minPos([2, 3, 1, 3, 4, 1])` returns the subrange `[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]), + `maxPos([2, 3, 1, 3, 4, 1])` returns the subrange `[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).) + Assume `a = "blah"`. Then `skipOver(a, "bi")` leaves `a` + unchanged and returns `false`, whereas `skipOver(a, "bl")` + advances `a` to refer to `"ah"` and returns `true`.) $(T2 startsWith, - $(D startsWith("hello, world", "hello")) returns $(D true).) + `startsWith("hello, world", "hello")` returns `true`.) $(T2 until, Lazily iterates a range until a specific value is found.) ) @@ -98,33 +95,34 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_searching.d) +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.functional : unaryFun, binaryFun; +import std.meta : allSatisfy; import std.range.primitives; import std.traits; -// FIXME -import std.typecons; // : Tuple, Flag, Yes, No; +import std.typecons : Tuple, Flag, Yes, No, tuple; /++ -Checks if $(I _all) of the elements verify $(D pred). +Checks if $(I _all) of the elements satisfy `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). + Returns `true` if and only if the input range `range` is empty + or $(I _all) values found in `range` satisfy the predicate `pred`. + Performs (at most) $(BIGOH range.length) evaluations of `pred`. +/ bool all(Range)(Range range) - if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) + if (isInputRange!Range) { + static assert(is(typeof(unaryFun!pred(range.front))), + "`" ~ pred.stringof[1..$-1] ~ "` isn't a unary predicate function for range.front"); import std.functional : not; return find!(not!(unaryFun!pred))(range).empty; @@ -139,7 +137,7 @@ template all(alias pred = "a") } /++ -$(D all) can also be used without a predicate, if its items can be +`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. @@ -154,20 +152,22 @@ are true. { int x = 1; assert(all!(a => a > x)([2, 3])); + assert(all!"a == 0x00c9"("\xc3\x89")); // Test that `all` auto-decodes. } /++ -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). +Checks if $(I _any) of the elements satisfies `pred`. +`!any` can be used to verify that $(I none) of the elements satisfy +`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). + Returns `true` if and only if the input range `range` is non-empty + and $(I _any) value found in `range` satisfies the predicate + `pred`. + Performs (at most) $(BIGOH range.length) evaluations of `pred`. +/ bool any(Range)(Range range) if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) @@ -185,8 +185,8 @@ template any(alias pred = "a") } /++ -$(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 +`any` can also be used without a predicate, if its items can be +evaluated to true or false in a conditional statement. `!any` can be a convenient way to quickly test that $(I none) of the elements of a range evaluate to true. +/ @@ -208,14 +208,15 @@ evaluate to true. { auto a = [ 1, 2, 0, 4 ]; assert(any!"a == 2"(a)); + assert(any!"a == 0x3000"("\xe3\x80\x80")); // Test that `any` auto-decodes. } // 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 +Checks whether `r` has "balanced parentheses", i.e. all instances +of `lPar` are closed by corresponding instances of `rPar`. The +parameter `maxNestingLevel` controls the nesting level allowed. The +most common uses are the default or `0`. In the latter case, no nesting is allowed. Params: @@ -233,14 +234,25 @@ bool balancedParens(Range, E)(Range r, E lPar, E rPar, if (isInputRange!(Range) && is(typeof(r.front == lPar))) { size_t count; - for (; !r.empty; r.popFront()) + + static if (is(immutable ElementEncodingType!Range == immutable E) && isNarrowString!Range) + { + import std.utf : byCodeUnit; + auto rn = r.byCodeUnit; + } + else + { + alias rn = r; + } + + for (; !rn.empty; rn.popFront()) { - if (r.front == lPar) + if (rn.front == lPar) { if (count > maxNestingLevel) return false; ++count; } - else if (r.front == rPar) + else if (rn.front == rPar) { if (!count) return false; --count; @@ -250,7 +262,7 @@ if (isInputRange!(Range) && is(typeof(r.front == lPar))) } /// -@safe unittest +@safe pure unittest { auto s = "1 + (2 * (3 + 1 / 2)"; assert(!balancedParens(s, '(', ')')); @@ -260,21 +272,23 @@ if (isInputRange!(Range) && is(typeof(r.front == lPar))) assert(!balancedParens(s, '(', ')', 0)); s = "1 + (2 * 3 + 1) / (2 - 5)"; assert(balancedParens(s, '(', ')', 0)); + s = "f(x) = ⌈x⌉"; + assert(balancedParens(s, '⌈', '⌉')); } /** - * Sets up Boyer-Moore matching for use with $(D find) below. + * Sets up Boyer-Moore matching for use with `find` below. * By default, elements are compared for equality. * - * $(D BoyerMooreFinder) allocates GC memory. + * `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 + * An instance of `BoyerMooreFinder` that can be used with `find()` to + * invoke the Boyer-Moore matching algorithm for finding of `needle` in a * given haystack. */ struct BoyerMooreFinder(alias pred, Range) @@ -284,7 +298,7 @@ private: ptrdiff_t[ElementType!(Range)] occ; // GC allocated Range needle; - ptrdiff_t occurrence(ElementType!(Range) c) + ptrdiff_t occurrence(ElementType!(Range) c) scope { auto p = c in occ; return p ? *p : -1; @@ -347,7 +361,7 @@ public: } /// - Range beFound(Range haystack) + Range beFound(Range haystack) scope { import std.algorithm.comparison : max; @@ -363,7 +377,7 @@ public: if (npos == 0) return haystack[hpos .. $]; --npos; } - hpos += max(skip[npos], cast(sizediff_t) npos - occurrence(haystack[npos+hpos])); + hpos += max(skip[npos], cast(ptrdiff_t) npos - occurrence(haystack[npos+hpos])); } return haystack[$ .. $]; } @@ -407,7 +421,7 @@ Returns the common prefix of two ranges. Params: pred = The predicate to use in comparing elements for commonality. Defaults - to equality $(D "a == b"). + to equality `"a == b"`. r1 = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) of elements. @@ -416,9 +430,9 @@ Params: elements. Returns: -A slice of $(D r1) which contains the characters that both ranges start with, +A slice of `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 +`takeExactly(r1, n)`, where `n` is the number of elements in the common prefix of both ranges. See_Also: @@ -542,12 +556,12 @@ if (isNarrowString!R1 && isNarrowString!R2) 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, + static 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 + static foreach (T; AliasSeq!(string, wstring, dstring)) + { assert(commonPrefix(to!S(""), to!T("")).empty); assert(commonPrefix(to!S(""), to!T("hello")).empty); assert(commonPrefix(to!S("hello"), to!T("")).empty); @@ -557,7 +571,7 @@ if (isNarrowString!R1 && isNarrowString!R2) 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 + // https://issues.dlang.org/show_bug.cgi?id=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("Пиво")); @@ -567,7 +581,7 @@ if (isNarrowString!R1 && isNarrowString!R2) 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("П"))); @@ -591,26 +605,26 @@ if (isNarrowString!R1 && isNarrowString!R2) // 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 first version counts the number of elements `x` in `r` for +which `pred(x, value)` is `true`. `pred` defaults to +equality. Performs $(BIGOH haystack.length) evaluations of `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 +The second version returns the number of times `needle` occurs in +`haystack`. Throws an exception if `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). +are not considered, for example `count("aaa", "aa")` is `1`, not +`2`. -The third version counts the elements for which $(D pred(x)) is $(D -true). Performs $(BIGOH haystack.length) evaluations of $(D pred). +The third version counts the elements for which `pred(x)` is $(D +true). Performs $(BIGOH haystack.length) evaluations of `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). +Note: Regardless of the overload, `count` will not accept +infinite ranges for `haystack`. Params: pred = The predicate to evaluate. @@ -723,7 +737,7 @@ if (isInputRange!R && !isInfinite!R) assert(count("日本語") == 3); } -// Issue 11253 +// https://issues.dlang.org/show_bug.cgi?id=11253 @safe nothrow unittest { assert([1, 2, 3].count([2, 3]) == 1); @@ -732,7 +746,7 @@ if (isInputRange!R && !isInfinite!R) /++ 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). + until the given predicate is true for one of the given `needles`. Params: pred = The predicate for determining when to stop counting. @@ -742,13 +756,14 @@ if (isInputRange!R && !isInfinite!R) 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. + element in `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. + `haystack` before reaching an element for which + `startsWith!pred(haystack, needles)` is `true`. If + `startsWith!pred(haystack, needles)` is not `true` for any element in + `haystack`, then `-1` is returned. If only `pred` is provided, + `pred(haystack)` is tested for each element. See_Also: $(REF indexOf, std,string) +/ @@ -756,9 +771,7 @@ 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 .. $]))))) + && allSatisfy!(canTestStartsWith!(pred, R), Rs)) { typeof(return) result; @@ -834,8 +847,10 @@ if (isForwardRange!R } } - //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" - static if (isInfinite!R) assert(0); + // Because of https://issues.dlang.org/show_bug.cgi?id=8804 + // Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(false, R.stringof ~ " must not be an" + ~ " infinite range"); else return -1; } @@ -898,18 +913,7 @@ if (isInputRange!R && 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). - +/ +/// ditto ptrdiff_t countUntil(alias pred, R)(R haystack) if (isInputRange!R && is(typeof(unaryFun!pred(haystack.front)) : bool)) @@ -948,8 +952,10 @@ if (isInputRange!R && } } - //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" - static if (isInfinite!R) assert(0); + // Because of https://issues.dlang.org/show_bug.cgi?id=8804 + // Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(false, R.stringof ~ " must not be an" + ~ " inifite range"); else return -1; } @@ -995,7 +1001,7 @@ if (isInputRange!R && /** Checks if the given range ends with (one of) the given needle(s). -The reciprocal of $(D startsWith). +The reciprocal of `startsWith`. Params: pred = The predicate to use for comparing elements between the range and @@ -1013,16 +1019,15 @@ Params: 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 +with `withOneOfThese[0]`, 2 if it ends with `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). +In the case when no needle parameters are given, return `true` iff back of +`doesThisStart` fulfils predicate `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)) + allSatisfy!(canTestStartsWith!(pred, Range), Needles)) { alias haystack = doesThisEnd; alias needles = withOneOfThese; @@ -1100,7 +1105,7 @@ if (isBidirectionalRange!R1 && enum isDefaultPred = false; static if (isDefaultPred && isArray!R1 && isArray!R2 && - is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) + is(immutable ElementEncodingType!R1 == immutable ElementEncodingType!R2)) { if (haystack.length < needle.length) return false; @@ -1121,16 +1126,27 @@ if (isBidirectionalRange!R && if (doesThisEnd.empty) return false; + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + alias predFunc = binaryFun!pred; // auto-decoding special case static if (isNarrowString!R) { + // statically determine decoding is unnecessary to evaluate pred + static if (isDefaultPred && isSomeChar!E && E.sizeof <= ElementEncodingType!R.sizeof) + return doesThisEnd[$ - 1] == withThis; // specialize for ASCII as to not change previous behavior - if (withThis <= 0x7F) - return predFunc(doesThisEnd[$ - 1], withThis); else - return predFunc(doesThisEnd.back, withThis); + { + if (withThis <= 0x7F) + return predFunc(doesThisEnd[$ - 1], withThis); + else + return predFunc(doesThisEnd.back, withThis); + } } else { @@ -1180,16 +1196,17 @@ if (isInputRange!R && import std.conv : to; import std.meta : AliasSeq; - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 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 + static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { //Lots of strings assert(endsWith(to!S("abc"), to!T(""))); assert(!endsWith(to!S("abc"), to!T("a"))); @@ -1219,11 +1236,11 @@ if (isInputRange!R && 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)) - { + static foreach (T; AliasSeq!(int, short)) + {{ immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; //RA range @@ -1254,7 +1271,30 @@ if (isInputRange!R && //Non-default pred assert(endsWith!("a%10 == b%10")(arr, [14, 15])); assert(!endsWith!("a%10 == b%10")(arr, [15, 14])); - } + }} +} + +@safe pure unittest +{ + //example from issue 19727 + import std.path : asRelativePath; + string[] ext = ["abc", "def", "ghi"]; + string path = "/foo/file.def"; + assert(ext.any!(e => path.asRelativePath("/foo").endsWith(e)) == true); + assert(ext.any!(e => path.asRelativePath("/foo").startsWith(e)) == false); +} + +private enum bool hasConstEmptyMember(T) = is(typeof(((const T* a) => (*a).empty)(null)) : bool); + +// Rebindable doesn't work with structs +// see: https://github.com/dlang/phobos/pull/6136 +private template RebindableOrUnqual(T) +{ + import std.typecons : Rebindable; + static if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) + alias RebindableOrUnqual = Rebindable!T; + else + alias RebindableOrUnqual = Unqual!T; } /** @@ -1278,10 +1318,10 @@ in { assert(!r.empty, "r is an empty range"); } -body +do { alias Element = ElementType!Range; - Unqual!Element seed = r.front; + RebindableOrUnqual!Element seed = r.front; r.popFront(); return extremum!(map, selector)(r, seed); } @@ -1298,86 +1338,87 @@ if (isInputRange!Range && !isInfinite!Range && alias Element = ElementType!Range; alias CommonElement = CommonType!(Element, RangeElementType); - Unqual!CommonElement extremeElement = seedElement; + RebindableOrUnqual!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) + // if we only have one statement in the loop, it can be optimized a lot better + static if (__traits(isSame, map, a => a)) { - foreach (const i; 0 .. r.length) + + // 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 { - MapType mapElement = mapFun(r[i]); - if (selectorFun(mapElement, extremeElementMapped)) + while (!r.empty) { - extremeElement = r[i]; - extremeElementMapped = mapElement; + if (selectorFun(r.front, extremeElement)) + { + extremeElement = r.front; + } + r.popFront(); } } } else { - while (!r.empty) + 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 { - MapType mapElement = mapFun(r.front); - if (selectorFun(mapElement, extremeElementMapped)) + while (!r.empty) { - extremeElement = r.front; - extremeElementMapped = mapElement; + MapType mapElement = mapFun(r.front); + if (selectorFun(mapElement, extremeElementMapped)) + { + extremeElement = r.front; + extremeElementMapped = mapElement; + } + r.popFront(); } - 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)))) +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); + return extremum!(a => a, selector)(r); } // 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)))) +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; + return extremum!(a => a, selector)(r, seedElement); } @safe pure unittest @@ -1391,7 +1432,7 @@ private auto extremum(alias selector = "a < b", Range, assert([[0, 4], [1, 2]].extremum!("a[1]", "a > b") == [0, 4]); // use a custom comparator - import std.math : cmp; + import std.math.operations : cmp; assert([-2., 0, 5].extremum!cmp == 5.0); assert([-2., 0, 2].extremum!`cmp(a, b) < 0` == -2.0); @@ -1455,45 +1496,80 @@ private auto extremum(alias selector = "a < b", Range, assert(arr2d.extremum!"a[1]" == arr2d[1]); } +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + class B + { + int val; + this(int val){ this.val = val; } + } + + const(B) doStuff(const(B)[] v) + { + return v.extremum!"a.val"; + } + assert(doStuff([new B(1), new B(0), new B(2)]).val == 0); + + const(B)[] arr = [new B(0), new B(1)]; + // can't compare directly - https://issues.dlang.org/show_bug.cgi?id=1824 + assert(arr.extremum!"a.val".val == 0); +} + // 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). +Finds an individual element in an $(REF_ALTTEXT input range, isInputRange, std,range,primitives). +Elements of `haystack` are compared with `needle` by using predicate +`pred` with `pred(haystack.front, needle)`. +`find` performs $(BIGOH walkLength(haystack)) evaluations of `pred`. -To _find the last occurrence of $(D needle) in $(D haystack), call $(D -find(retro(haystack), needle)). See $(REF retro, std,range). +The predicate is passed to $(REF binaryFun, std, functional), and can either accept a +string, or any callable that can be executed via `pred(element, element)`. -Params: +To _find the last occurrence of `needle` in a +$(REF_ALTTEXT bidirectional, isBidirectionalRange, std,range,primitives) `haystack`, +call `find(retro(haystack), needle)`. See $(REF retro, std,range). + +If no `needle` is provided, `pred(haystack.front)` will be evaluated on each +element of the input range. -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. +If `input` is a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives), +`needle` can be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) too. +In this case `startsWith!pred(haystack, needle)` is evaluated on each evaluation. -haystack = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) -searched in. +Note: + `find` behaves similar to `dropWhile` in other languages. -needle = The element searched for. +Complexity: + `find` performs $(BIGOH walkLength(haystack)) evaluations of `pred`. + There are specializations that improve performance by taking + advantage of $(REF_ALTTEXT bidirectional, isBidirectionalRange, std,range,primitives) + or $(REF_ALTTEXT random access, isRandomAccess, std,range,primitives) + ranges (where possible). + +Params: + + pred = The predicate for comparing each element with the needle, defaulting to equality `"a == b"`. + The negated predicate `"a != b"` can be used to search instead for the first + element $(I not) matching the needle. -Constraints: + haystack = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + searched in. -$(D isInputRange!InputRange && is(typeof(binaryFun!pred(haystack.front, needle) -: bool))) + needle = The element searched for. 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). + `haystack` advanced such that the front element is the one searched for; + that is, until `binaryFun!pred(haystack.front, needle)` is `true`. If no + such position exists, returns an empty `haystack`. -See_Also: - $(HTTP sgi.com/tech/stl/_find.html, STL's _find) - */ +See_ALso: $(LREF findAdjacent), $(LREF findAmong), $(LREF findSkip), $(LREF findSplit), $(LREF startsWith) +*/ InputRange find(alias pred = "a == b", InputRange, Element)(InputRange haystack, scope Element needle) if (isInputRange!InputRange && - is (typeof(binaryFun!pred(haystack.front, needle)) : bool)) + is (typeof(binaryFun!pred(haystack.front, needle)) : bool) && + !is (typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) { alias R = InputRange; alias E = Element; @@ -1508,7 +1584,7 @@ if (isInputRange!InputRange && // 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 + // https://issues.dlang.org/show_bug.cgi?id=8829 import std.range : SortedRange; static if (is(InputRange : SortedRange!TT, TT) && isDefaultPred) { @@ -1535,7 +1611,8 @@ if (isInputRange!InputRange && { if (!__ctfe && canSearchInCodeUnits!char(needle)) { - static R trustedMemchr(ref R haystack, ref E needle) @trusted nothrow pure + static inout(R) trustedMemchr(ref return scope inout(R) haystack, + ref const scope E needle) @trusted nothrow pure { import core.stdc.string : memchr; auto ptr = memchr(haystack.ptr, needle, haystack.length); @@ -1562,7 +1639,7 @@ if (isInputRange!InputRange && } } - //Previous conditonal optimizations did not succeed. Fallback to + //Previous conditional optimizations did not succeed. Fallback to //unconditional implementations static if (isDefaultPred) { @@ -1594,12 +1671,12 @@ if (isInputRange!InputRange && } else static if (isArray!R) { - //10403 optimization + // https://issues.dlang.org/show_bug.cgi?id=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 + R findHelper(return scope ref R haystack, ref E needle) @trusted nothrow pure { import core.stdc.string : memchr; @@ -1642,38 +1719,29 @@ if (isInputRange!InputRange && /// @safe unittest { - import std.algorithm.comparison : equal; - import std.container : SList; - import std.range; - import std.range.primitives : empty; + import std.range.primitives; - 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])); + auto arr = [1, 2, 4, 4, 4, 4, 5, 6, 9]; + assert(arr.find(4) == [4, 4, 4, 4, 5, 6, 9]); + assert(arr.find(1) == arr); + assert(arr.find(9) == [9]); + assert(arr.find!((a, b) => a > b)(4) == [5, 6, 9]); + assert(arr.find!((a, b) => a < b)(4) == arr); + assert(arr.find(0).empty); + assert(arr.find(10).empty); + assert(arr.find(8).empty); 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 +@safe unittest +{ + import std.range.primitives; + import std.uni : toLower; - // Case-insensitive find of a string - string[] s = [ "Hello", "world", "!" ]; - assert(!find!("toLower(a) == b")(s, "hello").empty); + string[] s = ["Hello", "world", "!"]; + assert(s.find!((a, b) => toLower(a) == b)("hello") == s); } @safe unittest @@ -1700,9 +1768,9 @@ if (isInputRange!InputRange && @safe pure unittest { import std.meta : AliasSeq; - foreach (R; AliasSeq!(string, wstring, dstring)) + static foreach (R; AliasSeq!(string, wstring, dstring)) { - foreach (E; AliasSeq!(char, wchar, dchar)) + static foreach (E; AliasSeq!(char, wchar, dchar)) { assert(find ("hello world", 'w') == "world"); assert(find!((a,b)=>a == b)("hello world", 'w') == "world"); @@ -1741,9 +1809,9 @@ if (isInputRange!InputRange && { byte[] sarr = [1, 2, 3, 4]; ubyte[] uarr = [1, 2, 3, 4]; - foreach (arr; AliasSeq!(sarr, uarr)) + static foreach (arr; AliasSeq!(sarr, uarr)) { - foreach (T; AliasSeq!(byte, ubyte, int, uint)) + static foreach (T; AliasSeq!(byte, ubyte, int, uint)) { assert(find(arr, cast(T) 3) == arr[2 .. $]); assert(find(arr, cast(T) 9) == arr[$ .. $]); @@ -1755,9 +1823,9 @@ if (isInputRange!InputRange && assertCTFEable!dg; } +// https://issues.dlang.org/show_bug.cgi?id=11603 @safe unittest { - // Bugzilla 11603 enum Foo : ubyte { A } assert([Foo.A].find(Foo.A).empty == false); @@ -1765,35 +1833,7 @@ if (isInputRange!InputRange && 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) -*/ +/// ditto InputRange find(alias pred, InputRange)(InputRange haystack) if (isInputRange!InputRange) { @@ -1847,31 +1887,7 @@ if (isInputRange!InputRange) 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). - */ +/// ditto 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)) @@ -1887,7 +1903,7 @@ if (isForwardRange!R1 && isForwardRange!R2 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; } + static TO force(TO, T)(inout T r) @trusted { return cast(TO) r; } return force!R1(.find!(pred, Representation, Representation) (force!Representation(haystack), force!Representation(needle))); } @@ -1975,7 +1991,7 @@ if (isForwardRange!R1 && isForwardRange!R2 // 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 + // https://issues.dlang.org/show_bug.cgi?id=8829 enhancement import std.algorithm.comparison : mismatch; import std.range : SortedRange; static if (is(R1 == R2) @@ -2096,6 +2112,17 @@ if (isForwardRange!R1 && isForwardRange!R2 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)]); } +// https://issues.dlang.org/show_bug.cgi?id=12470 +@safe unittest +{ + import std.array : replace; + inout(char)[] sanitize(inout(char)[] p) + { + return p.replace("\0", " "); + } + assert(sanitize("O\x00o") == "O o"); +} + @safe unittest { import std.algorithm.comparison : equal; @@ -2110,8 +2137,7 @@ if (isForwardRange!R1 && isForwardRange!R2 @safe unittest { - import std.range; - import std.stdio; + import std.range : assumeSorted; 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]); @@ -2155,7 +2181,7 @@ if (isForwardRange!R1 && isForwardRange!R2 assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]); } -//Bug# 8334 +// https://issues.dlang.org/show_bug.cgi?id=8334 @safe unittest { import std.algorithm.iteration : filter; @@ -2171,6 +2197,12 @@ if (isForwardRange!R1 && isForwardRange!R2 assert(find(haystack, filter!"true"(needle)).empty); } +// https://issues.dlang.org/show_bug.cgi?id=11013 +@safe unittest +{ + assert(find!"a == a"("abc","abc") == "abc"); +} + // Internally used by some find() overloads above private R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, scope R2 needle) { @@ -2212,7 +2244,7 @@ private R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, scope R2 needle) } else { - assert(haystack.empty); + assert(haystack.empty, "Haystack must be empty by now"); return haystack; } } @@ -2269,7 +2301,7 @@ private R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, scope R2 needle) } /** -Finds two or more $(D needles) into a $(D haystack). The predicate $(D +Finds two or more `needles` into a `haystack`. The predicate $(D pred) is used throughout to compare elements. By default, elements are compared for equality. @@ -2278,41 +2310,41 @@ 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 +If any of `needles` is a range with elements comparable to +elements in `haystack`, then `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 +needles = One or more items to search for. Each of `needles` must +be either comparable to one element in `haystack`, or be itself a forward range with elements comparable with elements in -$(D haystack). +`haystack`. Returns: -A tuple containing $(D haystack) positioned to match one of the +A tuple containing `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 +needles) (0 if none of `needles` matched, 1 if `needles[0]` +matched, 2 if `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 +`"a"` and `'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 +The relationship between `haystack` and `needles` simply means +that one can e.g. search for individual `int`s or arrays of $(D +int)s in an array of `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 +as well: a `double[]` can be searched for an `int` or a $(D +short[]), and conversely a `long` can be searched for a `float` +or a `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 +subranges at once maximizes cache usage by moving in `haystack` as few times as possible. */ Tuple!(Range, size_t) find(alias pred = "a == b", Range, Ranges...) @@ -2418,7 +2450,7 @@ if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles)))) } /** - * Finds $(D needle) in $(D haystack) efficiently using the + * Finds `needle` in `haystack` efficiently using the * $(LINK2 https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm, * Boyer-Moore) method. * @@ -2427,8 +2459,8 @@ if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles)))) * 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). + * `haystack` advanced such that `needle` is a prefix of it (if no + * such position exists, returns `haystack` advanced to termination). */ RandomAccessRange find(RandomAccessRange, alias pred, InputRange)( RandomAccessRange haystack, scope BoyerMooreFinder!(pred, InputRange) needle) @@ -2473,16 +2505,14 @@ 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. +$(REF among, std,algorithm,comparison) 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). + Returns `true` if and only if any value `v` found in the + input range `range` satisfies the predicate `pred`. + Performs (at most) $(BIGOH haystack.length) evaluations of `pred`. +/ bool canFind(Range)(Range haystack) if (is(typeof(find!pred(haystack)))) @@ -2491,8 +2521,8 @@ template canFind(alias pred="a == b") } /++ - Returns $(D true) if and only if $(D needle) can be found in $(D - range). Performs $(BIGOH haystack.length) evaluations of $(D pred). + Returns `true` if and only if `needle` can be found in $(D + range). Performs $(BIGOH haystack.length) evaluations of `pred`. +/ bool canFind(Range, Element)(Range haystack, scope Element needle) if (is(typeof(find!pred(haystack, needle)))) @@ -2501,14 +2531,14 @@ template canFind(alias pred="a == b") } /++ - Returns the 1-based index of the first needle found in $(D haystack). If no - needle is found, then $(D 0) is returned. + Returns the 1-based index of the first needle found in `haystack`. If no + needle is found, then `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 + will be `true` if one of the needles is found and `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 + `bool` for the same effect or used to get which needle was found first + without having to deal with the tuple that `LREF find` returns for the same operation. +/ size_t canFind(Range, Ranges...)(Range haystack, scope Ranges needles) @@ -2549,6 +2579,17 @@ template canFind(alias pred="a == b") assert( canFind!((string a, string b) => a.startsWith(b))(words, "bees")); } +/// Search for mutliple items in an array of items (search for needles in an array of hay stacks) +@safe unittest +{ + string s1 = "aaa111aaa"; + string s2 = "aaa222aaa"; + string s3 = "aaa333aaa"; + string s4 = "aaa444aaa"; + const hay = [s1, s2, s3, s4]; + assert(hay.canFind!(e => (e.canFind("111", "222")))); +} + @safe unittest { import std.algorithm.internal : rndstuff; @@ -2569,9 +2610,9 @@ template canFind(alias pred="a == b") // 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). +Advances `r` until it finds the first two adjacent elements `a`, +`b` that satisfy `pred(a, b)`. Performs $(BIGOH r.length) +evaluations of `pred`. Params: pred = The predicate to satisfy. @@ -2579,12 +2620,12 @@ Params: 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 +`r` advanced to the first occurrence of two adjacent elements that satisfy +the given predicate. If there are no such two elements, returns `r` advanced until empty. See_Also: - $(HTTP sgi.com/tech/stl/adjacent_find.html, STL's adjacent_find) + $(LINK2 http://en.cppreference.com/w/cpp/algorithm/adjacent_find, STL's `adjacent_find`) */ Range findAdjacent(alias pred = "a == b", Range)(Range r) if (isForwardRange!(Range)) @@ -2599,6 +2640,7 @@ if (isForwardRange!(Range)) } static if (!isInfinite!Range) return ahead; + assert(0); } /// @@ -2636,7 +2678,7 @@ if (isForwardRange!(Range)) ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]); assert(equal(findAdjacent(rfr), [2, 2, 3])); - // Issue 9350 + // https://issues.dlang.org/show_bug.cgi?id=9350 assert(!repeat(1).findAdjacent().empty); } @@ -2644,9 +2686,9 @@ if (isForwardRange!(Range)) /** 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). +Advances `seq` by calling `seq.popFront` until either +`find!(pred)(choices, seq.front)` is `true`, or `seq` becomes empty. +Performs $(BIGOH seq.length * choices.length) evaluations of `pred`. Params: pred = The predicate to use for determining a match. @@ -2656,17 +2698,16 @@ Params: of possible choices. Returns: -$(D seq) advanced to the first matching element, or until empty if there are no +`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) +See_Also: $(LREF find), $(REF std,algorithm,comparison,among) */ 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()) + for (; !seq.empty && find!pred(choices.save, seq.front).empty; seq.popFront()) { } return seq; @@ -2690,10 +2731,24 @@ if (isInputRange!InputRange && isForwardRange!ForwardRange) assert(findAmong!("a == b")(b, [ 4, 6, 7 ][]).empty); } +// https://issues.dlang.org/show_bug.cgi?id=19765 +@system unittest +{ + import std.range.interfaces : inputRangeObject; + auto choices = inputRangeObject("b"); + auto f = "foobar".findAmong(choices); + assert(f == "bar"); +} + // findSkip /** - * Finds $(D needle) in $(D haystack) and positions $(D haystack) - * right after the first occurrence of $(D needle). + * Finds `needle` in `haystack` and positions `haystack` + * right after the first occurrence of `needle`. + * + * If no needle is provided, the `haystack` is advanced as long as `pred` + * evaluates to `true`. + * Similarly, the haystack is positioned so as `pred` evaluates to `false` for + * `haystack.front`. * * Params: * haystack = The @@ -2702,10 +2757,14 @@ if (isInputRange!InputRange && isForwardRange!ForwardRange) * needle = The * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to search * for. + * pred = Custom predicate for comparison of haystack and needle + * + * Returns: `true` if the needle was found, in which case `haystack` is + * positioned after the end of the first occurrence of `needle`; otherwise + * `false`, leaving `haystack` untouched. If no needle is provided, it returns + * the number of times `pred(haystack.front)` returned true. * - * 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. + * See_Also: $(LREF find) */ bool findSkip(alias pred = "a == b", R1, R2)(ref R1 haystack, R2 needle) if (isForwardRange!R1 && isForwardRange!R2 @@ -2736,6 +2795,55 @@ if (isForwardRange!R1 && isForwardRange!R2 assert(findSkip(s, "def") && s.empty); } +// https://issues.dlang.org/show_bug.cgi?id=19020 +@safe unittest +{ + static struct WrapperRange + { + string _r; + @property auto empty() { return _r.empty(); } + @property auto front() { return _r.front(); } + auto popFront() { return _r.popFront(); } + @property auto save() { return WrapperRange(_r.save); } + } + auto tmp = WrapperRange("there is a bug here: *"); + assert(!tmp.findSkip("*/")); + assert(tmp._r == "there is a bug here: *"); +} + +/// ditto +size_t findSkip(alias pred, R1)(ref R1 haystack) +if (isForwardRange!R1 && ifTestable!(typeof(haystack.front), unaryFun!pred)) +{ + size_t result; + while (!haystack.empty && unaryFun!pred(haystack.front)) + { + result++; + haystack.popFront; + } + return result; +} + +/// +@safe unittest +{ + import std.ascii : isWhite; + string s = " abc"; + assert(findSkip!isWhite(s) && s == "abc"); + assert(!findSkip!isWhite(s) && s == "abc"); + + s = " "; + assert(findSkip!isWhite(s) == 2); +} + +@safe unittest +{ + import std.ascii : isWhite; + + auto s = " "; + assert(findSkip!isWhite(s) == 2); +} + /** These functions find the first occurrence of `needle` in `haystack` and then split `haystack` as follows. @@ -2776,16 +2884,9 @@ 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. +and `false` otherwise. -Example: ---- -if (const split = haystack.findSplit(needle)) -{ - doSomethingWithSplit(split); -} ---- +See_Also: $(LREF find) */ auto findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) if (isForwardRange!R1 && isForwardRange!R2) @@ -2802,9 +2903,19 @@ if (isForwardRange!R1 && isForwardRange!R2) asTuple = rhs; } Tuple!(S1, S1, S2) asTuple; - bool opCast(T : bool)() + static if (hasConstEmptyMember!(typeof(asTuple[1]))) + { + bool opCast(T : bool)() const + { + return !asTuple[1].empty; + } + } + else { - return !asTuple[1].empty; + bool opCast(T : bool)() + { + return !asTuple[1].empty; + } } alias asTuple this; } @@ -2843,6 +2954,10 @@ if (isForwardRange!R1 && isForwardRange!R2) pos2 = ++pos1; } } + if (!n.empty) // incomplete match at the end of haystack + { + pos1 = pos2; + } return Result!(typeof(takeExactly(original, pos1)), typeof(h))(takeExactly(original, pos1), takeExactly(haystack, pos2 - pos1), @@ -2866,9 +2981,19 @@ if (isForwardRange!R1 && isForwardRange!R2) asTuple = rhs; } Tuple!(S1, S2) asTuple; - bool opCast(T : bool)() + static if (hasConstEmptyMember!(typeof(asTuple[1]))) { - return !asTuple[0].empty; + bool opCast(T : bool)() const + { + return !asTuple[1].empty; + } + } + else + { + bool opCast(T : bool)() + { + return !asTuple[1].empty; + } } alias asTuple this; } @@ -2888,24 +3013,30 @@ if (isForwardRange!R1 && isForwardRange!R2) auto original = haystack.save; auto h = haystack.save; auto n = needle.save; - size_t pos; + 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; - ++pos; + pos2 = ++pos1; } } - return Result!(typeof(takeExactly(original, pos)), - typeof(haystack))(takeExactly(original, pos), + if (!n.empty) // incomplete match at the end of haystack + { + pos1 = pos2; + haystack = h; + } + return Result!(typeof(takeExactly(original, pos1)), + typeof(haystack))(takeExactly(original, pos1), haystack); } } @@ -2926,9 +3057,19 @@ if (isForwardRange!R1 && isForwardRange!R2) asTuple = rhs; } Tuple!(S1, S2) asTuple; - bool opCast(T : bool)() + static if (hasConstEmptyMember!(typeof(asTuple[1]))) + { + bool opCast(T : bool)() const + { + return !asTuple[0].empty; + } + } + else { - return !asTuple[1].empty; + bool opCast(T : bool)() + { + return !asTuple[0].empty; + } } alias asTuple this; } @@ -2978,6 +3119,29 @@ if (isForwardRange!R1 && isForwardRange!R2) } } +/// Returning a subtype of $(REF Tuple, std,typecons) enables +/// the following convenient idiom: +@safe pure nothrow unittest +{ + // findSplit returns a triplet + if (auto split = "dlang-rocks".findSplit("-")) + { + assert(split[0] == "dlang"); + assert(split[1] == "-"); + assert(split[2] == "rocks"); + } + else assert(0); + + // works with const aswell + if (const split = "dlang-rocks".findSplit("-")) + { + assert(split[0] == "dlang"); + assert(split[1] == "-"); + assert(split[2] == "rocks"); + } + else assert(0); +} + /// @safe pure nothrow unittest { @@ -2996,14 +3160,18 @@ if (isForwardRange!R1 && isForwardRange!R2) 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"); + if (const r1 = findSplitBefore(a, "Sagan")) + { + assert(r1); + assert(r1[0] == "Carl "); + assert(r1[1] == "Sagan Memorial Station"); + } + if (const 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: @@ -3017,7 +3185,7 @@ if (isForwardRange!R1 && isForwardRange!R2) { import std.range.primitives : empty; - auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + immutable a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; auto r = findSplit(a, [9, 1]); assert(!r); assert(r[0] == a); @@ -3029,23 +3197,35 @@ if (isForwardRange!R1 && isForwardRange!R2) 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 .. $]); + { + const r1 = findSplitBefore(a, [9, 1]); + assert(!r1); + assert(r1[0] == a); + assert(r1[1].empty); + } + + if (immutable r1 = findSplitBefore(a, [3, 4])) + { + assert(r1); + assert(r1[0] == a[0 .. 2]); + assert(r1[1] == a[2 .. $]); + } + else assert(0); + + { + const r2 = findSplitAfter(a, [9, 1]); + assert(!r2); + assert(r2[0].empty); + assert(r2[1] == a); + } + + if (immutable r3 = findSplitAfter(a, [3, 4])) + { + assert(r3); + assert(r3[0] == a[0 .. 4]); + assert(r3[1] == a[4 .. $]); + } + else assert(0); } @safe pure nothrow unittest @@ -3065,29 +3245,61 @@ if (isForwardRange!R1 && isForwardRange!R2) assert(equal(r[0], a[0 .. 2])); assert(equal(r[1], a[2 .. 3])); assert(equal(r[2], a[3 .. $])); + r = findSplit(fwd, [8, 9]); + assert(!r); + assert(equal(r[0], a)); + assert(r[1].empty); + assert(r[2].empty); - 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 variable `r2` cannot be `const` because `fwd.front` is mutable + { + auto r1 = findSplitBefore(fwd, [9, 1]); + assert(!r1); + assert(equal(r1[0], a)); + assert(r1[1].empty); + } + + if (auto r1 = findSplitBefore(fwd, [3, 4])) + { + assert(r1); + assert(equal(r1[0], a[0 .. 2])); + assert(equal(r1[1], a[2 .. $])); + } + else assert(0); + + { + auto r1 = findSplitBefore(fwd, [8, 9]); + assert(!r1); + assert(equal(r1[0], a)); + assert(r1[1].empty); + } + + { + auto r2 = findSplitAfter(fwd, [9, 1]); + assert(!r2); + assert(r2[0].empty); + assert(equal(r2[1], a)); + } + + if (auto r2 = findSplitAfter(fwd, [3, 4])) + { + assert(r2); + assert(equal(r2[0], a[0 .. 4])); + assert(equal(r2[1], a[4 .. $])); + } + else assert(0); + + { + auto r2 = findSplitAfter(fwd, [8, 9]); + assert(!r2); + assert(r2[0].empty); + assert(equal(r2[1], a)); + } +} + +@safe pure nothrow @nogc unittest +{ + auto str = "sep,one,sep,two"; auto split = str.findSplitAfter(","); assert(split[0] == "sep,"); @@ -3116,22 +3328,31 @@ if (isForwardRange!R1 && isForwardRange!R2) assert(split[1] == "one"); } +// https://issues.dlang.org/show_bug.cgi?id=11013 +@safe pure unittest +{ + auto var = "abc"; + auto split = var.findSplitBefore!q{a == a}(var); + assert(split[0] == ""); + assert(split[1] == "abc"); +} + // 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` +a value `x` in `range` such that `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 +i.e. transitive (if `pred(a, b) && pred(b, c)` then `pred(a, c)`) and +irreflexive (`pred(a, a)` is `false`). The $(LUCKY trichotomy property of +inequality) is not required: these algorithms 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)). +i.e. `!pred(a, b) && !pred(b, a)`. Params: pred = The ordering predicate to use to determine the extremum (minimum @@ -3141,7 +3362,13 @@ Params: Returns: The minimum, respectively maximum element of a range together with the number it occurs in the range. +Limitations: If at least one of the arguments is NaN, the result is +an unspecified value. See $(REF maxElement, std,algorithm,searching) +for examples on how to cope with NaNs. + Throws: `Exception` if `range.empty`. + +See_Also: $(REF min, std,algorithm,comparison), $(LREF minIndex), $(LREF minElement), $(LREF minPos) */ Tuple!(ElementType!Range, size_t) minCount(alias pred = "a < b", Range)(Range range) @@ -3319,8 +3546,8 @@ if (isInputRange!Range && !isInfinite!Range && } static assert(!isAssignable!S3); - foreach (Type; AliasSeq!(S1, IS1, S2, IS2, S3)) - { + static 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; @@ -3329,7 +3556,7 @@ if (isInputRange!Range && !isInfinite!Range && 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); - } + }} } /** @@ -3347,24 +3574,37 @@ Params: Returns: The minimal element of the passed-in range. +Note: + If at least one of the arguments is NaN, the result is an unspecified value. + + If you want to ignore NaNs, you can use $(REF filter, std,algorithm,iteration) + and $(REF isNaN, std,math) to remove them, before applying minElement. + Add a suitable seed, to avoid error messages if all elements are NaNs: + + --- + .filter!(a=>!a.isNaN).minElement(); + --- + + If you want to get NaN as a result if a NaN is present in the range, + you can use $(REF fold, std,algorithm,iteration) and $(REF isNaN, std,math): + + --- + .fold!((a,b)=>a.isNaN || b.isNaN ? real.nan : a < b ? a : b); + --- + See_Also: - $(REF min, std,algorithm,comparison) + + $(LREF maxElement), $(REF min, std,algorithm,comparison), $(LREF minCount), + $(LREF minIndex), $(LREF minPos) */ -auto minElement(alias map, Range)(Range r) +auto minElement(alias map = (a => a), 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) +auto minElement(alias map = (a => a), Range, RangeElementType = ElementType!Range) (Range r, RangeElementType seed) if (isInputRange!Range && !isInfinite!Range && !is(CommonType!(ElementType!Range, RangeElementType) == void)) @@ -3372,22 +3612,13 @@ if (isInputRange!Range && !isInfinite!Range && 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); + assert([2, 7, 1, 3].minElement == 1); // allows to get the index of an element too assert([5, 3, 7, 9].enumerate.minElement!"a.value" == tuple(1, 3)); @@ -3429,6 +3660,7 @@ auto minElement(Range, RangeElementType = ElementType!Range) DummyType d; assert(d.minElement == 1); assert(d.minElement!(a => a) == 1); + assert(d.minElement!(a => -a) == 10); } // with empty, but seeded ranges @@ -3446,39 +3678,71 @@ auto minElement(Range, RangeElementType = ElementType!Range) assert(arr2d.minElement!"a[1]" == arr2d[1]); } +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + struct A + { + int val; + } + + const(A)[] v = [A(0)]; + assert(v.minElement!"a.val" == A(0)); +} + +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + class B + { + int val; + this(int val){ this.val = val; } + } + + const(B) doStuff(const(B)[] v) + { + return v.minElement!"a.val"; + } + assert(doStuff([new B(1), new B(0), new B(2)]).val == 0); + + const(B)[] arr = [new B(0), new B(1)]; + // can't compare directly - https://issues.dlang.org/show_bug.cgi?id=1824 + assert(arr.minElement!"a.val".val == 0); +} + /** 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: +Complexity: O(n) Exactly `n - 1` comparisons are needed. Params: map = custom accessor for the comparison key - r = range from which the maximum will be selected + r = range from which the maximum element will be selected seed = custom seed to use as initial element Returns: The maximal element of the passed-in range. +Note: + If at least one of the arguments is NaN, the result is an unspecified value. + See $(REF minElement, std,algorithm,searching) for examples on how to cope + with NaNs. + See_Also: - $(REF max, std,algorithm,comparison) + + $(LREF minElement), $(REF max, std,algorithm,comparison), $(LREF maxCount), + $(LREF maxIndex), $(LREF maxPos) */ -auto maxElement(alias map, Range)(Range r) +auto maxElement(alias map = (a => a), 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) +auto maxElement(alias map = (a => a), Range, RangeElementType = ElementType!Range) (Range r, RangeElementType seed) if (isInputRange!Range && !isInfinite!Range && !is(CommonType!(ElementType!Range, RangeElementType) == void)) @@ -3486,15 +3750,6 @@ if (isInputRange!Range && !isInfinite!Range && 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 { @@ -3544,6 +3799,7 @@ if (isInputRange!Range && !isInfinite!Range && DummyType d; assert(d.maxElement == 10); assert(d.maxElement!(a => a) == 10); + assert(d.maxElement!(a => -a) == 1); } // with empty, but seeded ranges @@ -3562,31 +3818,57 @@ if (isInputRange!Range && !isInfinite!Range && assert(arr2d.maxElement!"a[1]" == arr2d[1]); } +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + class B + { + int val; + this(int val){ this.val = val; } + } + + const(B) doStuff(const(B)[] v) + { + return v.maxElement!"a.val"; + } + assert(doStuff([new B(1), new B(0), new B(2)]).val == 2); + + const(B)[] arr = [new B(0), new B(1)]; + // can't compare directly - https://issues.dlang.org/show_bug.cgi?id=1824 + assert(arr.maxElement!"a.val".val == 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 +Formally, the minimum is a value `x` in `range` such that `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 +`range` such that `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`). +i.e. transitive (if `pred(a, b) && pred(b, c)` then `pred(a, c)`) and +irreflexive (`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. + range = The $(REF_ALTTEXT forward range, isForwardRange, 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`. +Limitations: If at least one of the arguments is NaN, the result is +an unspecified value. See $(REF maxElement, std,algorithm,searching) +for examples on how to cope with NaNs. + +See_Also: + $(REF max, std,algorithm,comparison), $(LREF minCount), $(LREF minIndex), $(LREF minElement) */ Range minPos(alias pred = "a < b", Range)(Range range) if (isForwardRange!Range && !isInfinite!Range && @@ -3688,23 +3970,28 @@ Params: range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to search. -Complexity: O(n) - Exactly `n - 1` comparisons are needed. +Complexity: $(BIGOH range.length) + Exactly `range.length - 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. +Limitations: + If at least one of the arguments is NaN, the result is + an unspecified value. See $(REF maxElement, std,algorithm,searching) + for examples on how to cope with NaNs. + See_Also: - $(REF min, std,algorithm,comparison), $(LREF minCount), $(LREF minElement), $(LREF minPos) + $(LREF maxIndex), $(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 && +ptrdiff_t minIndex(alias pred = "a < b", Range)(Range range) +if (isInputRange!Range && !isInfinite!Range && is(typeof(binaryFun!pred(range.front, range.front)))) { if (range.empty) return -1; - sizediff_t minPos = 0; + ptrdiff_t minPos = 0; static if (isRandomAccessRange!Range && hasLength!Range) { @@ -3718,7 +4005,7 @@ if (isForwardRange!Range && !isInfinite!Range && } else { - sizediff_t curPos = 0; + ptrdiff_t curPos = 0; Unqual!(typeof(range.front)) min = range.front; for (range.popFront(); !range.empty; range.popFront()) { @@ -3799,11 +4086,45 @@ if (isForwardRange!Range && !isInfinite!Range && assert(arr2d.minIndex!"a[1] < b[1]" == 2); } +@safe nothrow pure unittest +{ + // InputRange test + + static struct InRange + { + @property int front() + { + return arr[index]; + } + + bool empty() const + { + return arr.length == index; + } + + void popFront() + { + index++; + } + + int[] arr; + size_t index = 0; + } + + static assert(isInputRange!InRange); + + auto arr1 = InRange([5, 2, 3, 4, 5, 3, 6]); + auto arr2 = InRange([7, 3, 8, 2, 1, 4]); + + assert(arr1.minIndex == 1); + assert(arr2.minIndex == 4); +} + /** Computes the index of the first occurrence of `range`'s maximum element. -Complexity: O(n) - Exactly `n - 1` comparisons are needed. +Complexity: $(BIGOH range) + Exactly `range.length - 1` comparisons are needed. Params: pred = The ordering predicate to use to determine the maximum element. @@ -3813,10 +4134,15 @@ Returns: The index of the first encounter of the maximum in `range`. If the `range` is empty, -1 is returned. +Limitations: + If at least one of the arguments is NaN, the result is + an unspecified value. See $(REF maxElement, std,algorithm,searching) + for examples on how to cope with NaNs. + See_Also: - $(REF max, std,algorithm,comparison), $(LREF maxCount), $(LREF maxElement), $(LREF maxPos) + $(LREF minIndex), $(REF max, std,algorithm,comparison), $(LREF maxCount), $(LREF maxElement), $(LREF maxPos) */ -sizediff_t maxIndex(alias pred = "a < b", Range)(Range range) +ptrdiff_t maxIndex(alias pred = "a < b", Range)(Range range) if (isInputRange!Range && !isInfinite!Range && is(typeof(binaryFun!pred(range.front, range.front)))) { @@ -3889,79 +4215,158 @@ if (isInputRange!Range && !isInfinite!Range && } /** -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. +Skip over the initial portion of the first given range (`haystack`) that matches +any of the additionally given ranges (`needles`) fully, or +if no second range is given skip over the elements that fulfill 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))) + range match. Defaults to equality `"a == b"`. +*/ +template skipOver(alias pred = (a, b) => a == b) { - static if (is(typeof(r1[0 .. $] == r2) : bool) - && is(typeof(r2.length > r1.length) : bool) - && is(typeof(r1 = r1[r2.length .. $]))) + enum bool isPredComparable(T) = ifTestable!(T, binaryFun!pred); + + /** + Params: + haystack = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to + move forward. + needles = The $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) + representing the prefix of `r1` to skip over. + es = The element to match. + + Returns: + `true` if the prefix of `haystack` matches any range of `needles` fully + or `pred` evaluates to true, and `haystack` has been advanced to the point past this segment; + otherwise false, and `haystack` is left in its original position. + + Note: + By definition, empty ranges are matched fully and if `needles` contains an empty range, + `skipOver` will return `true`. + */ + bool skipOver(Haystack, Needles...)(ref Haystack haystack, Needles needles) + if (is(typeof(binaryFun!pred(haystack.front, needles[0].front))) && + isForwardRange!Haystack && + allSatisfy!(isInputRange, Needles) && + !is(CommonType!(staticMap!(ElementType, staticMap!(Unqual, Needles))) == void)) { - if (r2.length > r1.length || r1[0 .. r2.length] != r2) + static if (__traits(isSame, pred, (a, b) => a == b) + && is(typeof(haystack[0 .. $] == needles[0]) : bool) + && is(typeof(haystack = haystack[0 .. $])) + && hasLength!Haystack && allSatisfy!(hasLength, Needles)) { + ptrdiff_t longestMatch = -1; + static foreach (r2; needles) + { + if (r2.length <= haystack.length && longestMatch < ptrdiff_t(r2.length) + && (haystack[0 .. r2.length] == r2 || r2.length == 0)) + longestMatch = r2.length; + } + if (longestMatch >= 0) + { + if (longestMatch > 0) + haystack = haystack[longestMatch .. $]; + + return true; + } return false; } - r1 = r1[r2.length .. $]; - return true; - } - else - { - return skipOver!((a, b) => a == b)(r1, r2); + else + { + import std.algorithm.comparison : min; + auto r = haystack.save; + + static if (hasLength!Haystack && allSatisfy!(hasLength, Needles)) + { + import std.algorithm.iteration : map; + import std.algorithm.searching : minElement; + import std.range : only; + // Shortcut opportunity! + if (needles.only.map!(a => a.length).minElement > haystack.length) + return false; + } + + // compatibility: return true if any range was empty + bool hasEmptyRanges; + static foreach (i, r2; needles) + { + if (r2.empty) + hasEmptyRanges = true; + } + + bool hasNeedleMatch; + size_t inactiveNeedlesLen; + bool[Needles.length] inactiveNeedles; + for (; !r.empty; r.popFront) + { + static foreach (i, r2; needles) + { + if (!r2.empty && !inactiveNeedles[i]) + { + if (binaryFun!pred(r.front, r2.front)) + { + r2.popFront; + if (r2.empty) + { + // we skipped over a new match + hasNeedleMatch = true; + inactiveNeedlesLen++; + // skip over haystack + haystack = r; + } + } + else + { + inactiveNeedles[i] = true; + inactiveNeedlesLen++; + } + } + } + + // are we done? + if (inactiveNeedlesLen == needles.length) + break; + } + + if (hasNeedleMatch) + haystack.popFront; + + return hasNeedleMatch || hasEmptyRanges; + } } -} -/// 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) + /// Ditto + bool skipOver(R)(ref R r1) + if (isForwardRange!R && + ifTestable!(typeof(r1.front), unaryFun!pred)) { - // Shortcut opportunity! - if (r2.length > r1.length) + if (r1.empty || !unaryFun!pred(r1.front)) return false; + + do + r1.popFront(); + while (!r1.empty && unaryFun!pred(r1.front)); + return true; } - auto r = r1.save; - while (!r2.empty && !r.empty && binaryFun!pred(r.front, r2.front)) + + /// Ditto + bool skipOver(R, Es...)(ref R r, Es es) + if (isInputRange!R && is(typeof(binaryFun!pred(r.front, es[0])))) { - r.popFront(); - r2.popFront(); - } - if (r2.empty) - r1 = r; - return r2.empty; -} + if (r.empty) + return false; -/// 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)) + static foreach (e; es) + { + if (binaryFun!pred(r.front, e)) + { + r.popFront(); + return true; + } + } return false; - - do - r1.popFront(); - while (!r1.empty && unaryFun!pred(r1.front)); - return true; + } } /// @@ -3972,15 +4377,19 @@ if (isForwardRange!R && auto s1 = "Hello world"; assert(!skipOver(s1, "Ha")); assert(s1 == "Hello world"); - assert(skipOver(s1, "Hell") && s1 == "o world"); + assert(skipOver(s1, "Hell") && s1 == "o world", s1); string[] r1 = ["abc", "def", "hij"]; dstring[] r2 = ["abc"d]; - assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d])); + assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d]), r1[0]); assert(r1 == ["abc", "def", "hij"]); assert(skipOver!((a, b) => a.equal(b))(r1, r2)); assert(r1 == ["def", "hij"]); +} +/// +@safe unittest +{ import std.ascii : isWhite; import std.range.primitives : empty; @@ -3992,38 +4401,16 @@ if (isForwardRange!R && 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)) +/// Variadic skipOver +@safe unittest { - return skipOver!((a, b) => a == b)(r, e); -} + auto s = "Hello world"; + assert(!skipOver(s, "hello", "HellO")); + assert(s == "Hello world"); -/// 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; + // the range is skipped over the longest matching needle is skipped + assert(skipOver(s, "foo", "hell", "Hello ")); + assert(s == "world"); } /// @@ -4047,11 +4434,154 @@ if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R) assert(!s2.skipOver('a')); } +/// Partial instantiation +@safe unittest +{ + import std.ascii : isWhite; + import std.range.primitives : empty; + + alias whitespaceSkiper = skipOver!isWhite; + + auto s2 = "\t\tvalue"; + auto s3 = ""; + auto s4 = "\t\t\t"; + assert(whitespaceSkiper(s2) && s2 == "value"); + assert(!whitespaceSkiper(s2)); + assert(whitespaceSkiper(s4) && s3.empty); +} + +// variadic skipOver +@safe unittest +{ + auto s = "DLang.rocks"; + assert(!s.skipOver("dlang", "DLF", "DLang ")); + assert(s == "DLang.rocks"); + + assert(s.skipOver("dlang", "DLANG", "DLF", "D", "DL", "DLanpp")); + assert(s == "ang.rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("DLang", "DLANG", "DLF", "D", "DL", "DLang ")); + assert(s == ".rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("dlang", "DLANG", "DLF", "D", "DL", "DLang.")); + assert(s == "rocks"); +} + +// variadic with custom pred +@safe unittest +{ + import std.ascii : toLower; + + auto s = "DLang.rocks"; + assert(!s.skipOver("dlang", "DLF", "DLang ")); + assert(s == "DLang.rocks"); + + assert(s.skipOver!((a, b) => a.toLower == b.toLower)("dlang", "DLF", "DLang ")); + assert(s == ".rocks"); +} + +// variadic skipOver with mixed needles +@safe unittest +{ + auto s = "DLang.rocks"; + assert(!s.skipOver("dlang"d, "DLF", "DLang "w)); + assert(s == "DLang.rocks"); + + assert(s.skipOver("dlang", "DLANG"d, "DLF"w, "D"d, "DL", "DLanp")); + assert(s == "ang.rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("DLang", "DLANG"w, "DLF"d, "D"d, "DL", "DLang ")); + assert(s == ".rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("dlang", "DLANG"w, "DLF", "D"d, "DL"w, "DLang."d)); + assert(s == "rocks"); + + import std.algorithm.iteration : filter; + s = "DLang.rocks"; + assert(s.skipOver("dlang", "DLang".filter!(a => true))); + assert(s == ".rocks"); +} + +// variadic skipOver with auto-decoding +@safe unittest +{ + auto s = "☢☣☠.☺"; + assert(s.skipOver("a", "☢", "☢☣☠")); + assert(s == ".☺"); +} + +// skipOver with @nogc +@safe @nogc pure nothrow unittest +{ + static immutable s = [0, 1, 2]; + immutable(int)[] s2 = s[]; + + static immutable skip1 = [0, 2]; + static immutable skip2 = [0, 1]; + assert(s2.skipOver(skip1, skip2)); + assert(s2 == s[2 .. $]); +} + +// variadic skipOver with single elements +@safe unittest +{ + auto s = "DLang.rocks"; + assert(!s.skipOver('a', 'd', 'e')); + assert(s == "DLang.rocks"); + + assert(s.skipOver('a', 'D', 'd', 'D')); + assert(s == "Lang.rocks"); + s = "DLang.rocks"; + + assert(s.skipOver(wchar('a'), dchar('D'), 'd')); + assert(s == "Lang.rocks"); + + dstring dstr = "+Foo"; + assert(!dstr.skipOver('.', '-')); + assert(dstr == "+Foo"); + + assert(dstr.skipOver('+', '-')); + assert(dstr == "Foo"); +} + +// skipOver with empty ranges must return true (compatibility) +@safe unittest +{ + auto s = "DLang.rocks"; + assert(s.skipOver("")); + assert(s.skipOver("", "")); + assert(s.skipOver("", "foo")); + + auto s2 = "DLang.rocks"d; + assert(s2.skipOver("")); + assert(s2.skipOver("", "")); + assert(s2.skipOver("", "foo")); +} + +// dxml regression +@safe unittest +{ + import std.utf : byCodeUnit; + import std.algorithm.comparison : equal; + + bool stripStartsWith(Text)(ref Text text, string needle) + { + return text.skipOver(needle.byCodeUnit()); + } + auto text = ""d.byCodeUnit; + assert(stripStartsWith(text, "")); + assert(text.equal("")); +} + /** 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). +if its front element fulfils predicate `pred`. Params: @@ -4070,24 +4600,38 @@ 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 +with `withOneOfThese[0]`, 2 if it starts with `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 +In the case where `doesThisStart` starts with multiple of the ranges or +elements in `withOneOfThese`, then the shortest one matches (if there are +two which match which are of the same length (e.g. `"a"` and `'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). +In the case when no needle parameters are given, return `true` iff front of +`doesThisStart` fulfils predicate `pred`. */ -uint startsWith(alias pred = "a == b", Range, Needles...)(Range doesThisStart, Needles withOneOfThese) +uint startsWith(alias pred = (a, b) => 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)) + allSatisfy!(canTestStartsWith!(pred, Range), Needles)) { - alias haystack = doesThisStart; + template checkType(T) + { + enum checkType = is(immutable ElementEncodingType!Range == immutable T); + } + + // auto-decoding special case + static if (__traits(isSame, binaryFun!pred, (a, b) => a == b) && + isNarrowString!Range && allSatisfy!(checkType, Needles)) + { + import std.utf : byCodeUnit; + auto haystack = doesThisStart.byCodeUnit; + } + else + { + alias haystack = doesThisStart; + } alias needles = withOneOfThese; // Make one pass looking for empty ranges in needles @@ -4168,17 +4712,18 @@ if (isInputRange!R1 && 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. + // Note: Although narrow strings don't have a "true" length, for a narrow string to start with another + // narrow string, 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)) + ((hasLength!R1 || isNarrowString!R1) && (hasLength!R2 || 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))) + is(immutable ElementEncodingType!R1 == immutable ElementEncodingType!R2)) { //Array slice comparison mode return haystack[0 .. needle.length] == needle; @@ -4231,16 +4776,27 @@ if (isInputRange!R && if (doesThisStart.empty) return false; + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + alias predFunc = binaryFun!pred; // auto-decoding special case static if (isNarrowString!R) { + // statically determine decoding is unnecessary to evaluate pred + static if (isDefaultPred && isSomeChar!E && E.sizeof <= ElementEncodingType!R.sizeof) + return doesThisStart[0] == withThis; // specialize for ASCII as to not change previous behavior - if (withThis <= 0x7F) - return predFunc(doesThisStart[0], withThis); else - return predFunc(doesThisStart.front, withThis); + { + if (withThis <= 0x7F) + return predFunc(doesThisStart[0], withThis); + else + return predFunc(doesThisStart.front, withThis); + } } else { @@ -4295,16 +4851,17 @@ if (isInputRange!R && import std.meta : AliasSeq; import std.range; - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 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 + static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { //Lots of strings assert(startsWith(to!S("abc"), to!T(""))); assert(startsWith(to!S("ab"), to!T("a"))); @@ -4337,16 +4894,16 @@ if (isInputRange!R && 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)) - { + static foreach (T; AliasSeq!(int, short)) + {{ immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; //RA range @@ -4377,12 +4934,18 @@ if (isInputRange!R && //Non-default pred assert(startsWith!("a%10 == b%10")(arr, [10, 11])); assert(!startsWith!("a%10 == b%10")(arr, [10, 12])); - } + }} +} + +private template canTestStartsWith(alias pred, Haystack) +{ + enum bool canTestStartsWith(Needle) = is(typeof( + (ref Haystack h, ref Needle n) => startsWith!pred(h, n))); } /* (Not yet documented.) -Consume all elements from $(D r) that are equal to one of the elements -$(D es). +Consume all elements from `r` that are equal to one of the elements +`es`. */ private void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es) //if (is(typeof(binaryFun!pred(r1.front, es[0])))) @@ -4411,34 +4974,34 @@ private void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es) /** Interval option specifier for `until` (below) and others. -If set to $(D OpenRight.yes), then the interval is open to the right +If set to `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 +Otherwise if set to `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. +Lazily iterates `range` _until the element `e` for which +`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) + 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)). + true should be included in the resulting range (`No.openRight`), or + not (`Yes.openRight`). Returns: - An $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) that + 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 + $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) or higher, this range will be a forward range. */ Until!(pred, Range, Sentinel) @@ -4468,6 +5031,7 @@ if (isInputRange!Range) private bool _done; static if (!is(Sentinel == void)) + { /// this(Range input, Sentinel sentinel, OpenRight openRight = Yes.openRight) @@ -4477,7 +5041,17 @@ if (isInputRange!Range) _openRight = openRight; _done = _input.empty || openRight && predSatisfied(); } + private this(Range input, Sentinel sentinel, OpenRight openRight, + bool done) + { + _input = input; + _sentinel = sentinel; + _openRight = openRight; + _done = done; + } + } else + { /// this(Range input, OpenRight openRight = Yes.openRight) { @@ -4485,6 +5059,13 @@ if (isInputRange!Range) _openRight = openRight; _done = _input.empty || openRight && predSatisfied(); } + private this(Range input, OpenRight openRight, bool done) + { + _input = input; + _openRight = openRight; + _done = done; + } + } /// @property bool empty() @@ -4495,7 +5076,7 @@ if (isInputRange!Range) /// @property auto ref front() { - assert(!empty); + assert(!empty, "Can not get the front of an empty Until"); return _input.front; } @@ -4510,7 +5091,7 @@ if (isInputRange!Range) /// void popFront() { - assert(!empty); + assert(!empty, "Can not popFront of an empty Until"); if (!_openRight) { _done = predSatisfied(); @@ -4526,27 +5107,14 @@ if (isInputRange!Range) 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; - } + /// + @property Until save() + { + static if (is(Sentinel == void)) + return Until(_input.save, _openRight, _done); + else + return Until(_input.save, _sentinel, _openRight, _done); + } } } @@ -4574,7 +5142,8 @@ if (isInputRange!Range) assert(equal(until!"a == 2"(a, No.openRight), [1, 2])); } -@system unittest // bugzilla 13171 +// https://issues.dlang.org/show_bug.cgi?id=13171 +@system unittest { import std.algorithm.comparison : equal; import std.range; @@ -4583,7 +5152,8 @@ if (isInputRange!Range) assert(a == [4]); } -@safe unittest // Issue 10460 +// https://issues.dlang.org/show_bug.cgi?id=10460 +@safe unittest { import std.algorithm.comparison : equal; auto a = [1, 2, 3, 4]; @@ -4592,9 +5162,29 @@ if (isInputRange!Range) assert(equal(a, [0, 0, 3, 4])); } -@safe unittest // Issue 13124 +// https://issues.dlang.org/show_bug.cgi?id=13124 +@safe unittest { import std.algorithm.comparison : among, equal; auto s = "hello how\nare you"; assert(equal(s.until!(c => c.among!('\n', '\r')), "hello how")); } + +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : refRange; + { + string s = "foobar"; + auto r = refRange(&s).until("bar"); + assert(equal(r.save, "foo")); + assert(equal(r.save, "foo")); + } + { + string s = "foobar"; + auto r = refRange(&s).until!(e => e == 'b'); + assert(equal(r.save, "foo")); + assert(equal(r.save, "foo")); + } +} diff --git a/libphobos/src/std/algorithm/setops.d b/libphobos/src/std/algorithm/setops.d index 05a6e7e..ede1831 100644 --- a/libphobos/src/std/algorithm/setops.d +++ b/libphobos/src/std/algorithm/setops.d @@ -40,7 +40,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_setops.d) +Source: $(PHOBOSSRC std/algorithm/setops.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) @@ -49,19 +49,17 @@ module std.algorithm.setops; import std.range.primitives; -// FIXME -import std.functional; // : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun; import std.traits; -// FIXME -import std.meta; // : AliasSeq, staticMap, allSatisfy, anySatisfy; +import std.meta : AliasSeq, staticMap, allSatisfy, anySatisfy; -import std.algorithm.sorting; // : Merge; +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. +range of tuples of elements from each respective range. The conditions for the two-range case are as follows: @@ -69,8 +67,8 @@ 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 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. @@ -348,7 +346,7 @@ if (!allSatisfy!(isForwardRange, R1, R2) || } } -// Issue 13091 +// https://issues.dlang.org/show_bug.cgi?id=13091 pure nothrow @safe @nogc unittest { int[1] a = [1]; @@ -393,7 +391,7 @@ if (ranges.length >= 2 && return mixin(algoFormat("tuple(%(current[%d].front%|,%))", iota(0, current.length))); } - void popFront() + void popFront() scope { foreach_reverse (i, ref r; current) { @@ -406,25 +404,27 @@ if (ranges.length >= 2 && r = ranges[i].save; // rollover } } - @property Result save() + @property Result save() scope return { Result copy = this; foreach (i, r; ranges) { - copy.ranges[i] = r.save; + copy.ranges[i] = ranges[i].save; copy.current[i] = current[i].save; } return copy; } } - static assert(isForwardRange!Result); + static assert(isForwardRange!Result, Result.stringof ~ " must be a forward" + ~ " range"); return Result(ranges); } +// cartesian product of empty ranges should be empty +// https://issues.dlang.org/show_bug.cgi?id=10693 @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); @@ -446,9 +446,9 @@ if (ranges.length >= 2 && assert(cprod.init.empty); } +// https://issues.dlang.org/show_bug.cgi?id=13393 @safe unittest { - // Issue 13393 assert(!cartesianProduct([0],[0],[0]).save.empty); } @@ -486,7 +486,7 @@ if (!allSatisfy!(isForwardRange, R1, R2, RR) || 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(50, 23, 11))); assert(canFind(N3, tuple(9, 3, 0))); } @@ -504,10 +504,10 @@ if (!allSatisfy!(isForwardRange, R1, R2, RR) || assert(canFind(N4, tuple(1, 2, 3, 4))); assert(canFind(N4, tuple(4, 3, 2, 1))); - assert(canFind(N4, tuple(10, 31, 7, 12))); + assert(canFind(N4, tuple(10, 3, 1, 2))); } -// Issue 9878 +// https://issues.dlang.org/show_bug.cgi?id=9878 /// @safe unittest { @@ -546,7 +546,7 @@ pure @safe nothrow @nogc unittest assert(D.front == front1); } -// Issue 13935 +// https://issues.dlang.org/show_bug.cgi?id=13935 @safe unittest { import std.algorithm.iteration : map; @@ -554,12 +554,46 @@ pure @safe nothrow @nogc unittest foreach (pair; cartesianProduct(seq, seq)) {} } +@system unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + static struct SystemRange + { + int[] data; + + int front() @system @property inout + { + return data[0]; + } + + bool empty() @system @property inout + { + return data.length == 0; + } + + void popFront() @system + { + data = data[1 .. $]; + } + + SystemRange save() @system + { + return this; + } + } + + assert(SystemRange([1, 2]).cartesianProduct(SystemRange([3, 4])) + .equal([tuple(1, 3), tuple(1, 4), tuple(2, 3), tuple(2, 4)])); +} + // 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. +`ror`, copies to `tgt` the elements that are common to most ranges, along with their number +of occurrences. All ranges in `ror` are assumed to be sorted by $(D +less). Only the most frequent `tgt.length` elements are returned. Params: less = The predicate the ranges are sorted by. @@ -567,27 +601,27 @@ Params: 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 +The function `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 +is $(BIGOH n * log(tgt.length)), where `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). +requires less memory (`largestPartialIntersection` builds its +result directly in `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. +`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 +Warning: Because `largestPartialIntersection` does not allocate +extra memory, it will leave `ror` modified. Namely, $(D +largestPartialIntersection) assumes ownership of `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 to `largestPartialIntersection` (and perhaps cache the duplicate in between calls). */ void largestPartialIntersection @@ -652,13 +686,13 @@ import std.algorithm.sorting : SortOutput; // FIXME // largestPartialIntersectionWeighted /** -Similar to $(D largestPartialIntersection), but associates a weight +Similar to `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. +`tgt.length`, weight-based ranking elements. Params: less = The predicate the ranges are sorted by. @@ -798,15 +832,15 @@ void largestPartialIntersectionWeighted 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 +complexity of one `popFront` operation is $(BIGOH +log(ror.length)). However, the length of `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) +contained within `ror`. If all ranges have the same length `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). +turn. The output comes sorted (unstably) by `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 @@ -824,12 +858,15 @@ Params: 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 +Warning: Because `MultiwayMerge` does not allocate extra memory, it +will leave `ror` modified. Namely, `MultiwayMerge` assumes ownership +of `ror` and discretionarily swaps and advances elements of it. If +you want `ror` to preserve its contents after the call, you may +want to pass a duplicate to `MultiwayMerge` (and perhaps cache the duplicate in between calls). + +See_Also: $(REF merge, std,algorithm,sorting) for an analogous function that + takes a static number of ranges of possibly disparate types. */ struct MultiwayMerge(alias less, RangeOfRanges) { @@ -883,7 +920,8 @@ struct MultiwayMerge(alias less, RangeOfRanges) return; } // Put the popped range back in the heap - _heap.conditionalInsert(_ror.back) || assert(false); + const bool worked = _heap.conditionalInsert(_ror.back); + assert(worked, "Failed to insert item into heap"); } } @@ -930,7 +968,8 @@ alias nWayUnion = multiwayMerge; alias NWayUnion = MultiwayMerge; /** -Computes the union of multiple ranges. The input ranges are passed +Computes the union of multiple ranges. The +$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) 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`. @@ -949,7 +988,8 @@ See also: $(LREF multiwayMerge) auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) { import std.algorithm.iteration : uniq; - return ror.multiwayMerge.uniq; + import std.functional : not; + return ror.multiwayMerge!(less).uniq!(not!less); } /// @@ -980,17 +1020,26 @@ auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) [ 7 ], ]; assert(equal(multiwayUnion(b), witness)); + + double[][] c = + [ + [9, 8, 8, 8, 7, 6], + [9, 8, 6], + [9, 8, 5] + ]; + auto witness2 = [9, 8, 7, 6, 5]; + assert(equal(multiwayUnion!"a > b"(c), witness2)); } /** -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 +Lazily computes the difference of `r1` and `r2`. The two ranges +are assumed to be sorted by `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. +times in `r1` and `y` times and `r2`, the number of occurences +of `a` in the resulting range is going to be `x-y` if x > y or 0 otherwise. Params: less = Predicate the given ranges are sorted by. @@ -1048,7 +1097,7 @@ public: /// @property auto ref front() { - assert(!empty); + assert(!empty, "Can not get front of empty SetDifference"); return r1.front; } @@ -1095,7 +1144,8 @@ SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) assert(setDifference(r, x).empty); } -@safe unittest // Issue 10460 +// https://issues.dlang.org/show_bug.cgi?id=10460 +@safe unittest { import std.algorithm.comparison : equal; @@ -1107,8 +1157,9 @@ SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) } /** -Lazily computes the intersection of two or more input ranges $(D -ranges). The ranges are assumed to be sorted by $(D less). The element +Lazily computes the intersection of two or more +$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) +`ranges`. The ranges are assumed to be sorted by `less`. The element types of the ranges must have a common type. In the case of multisets, the range with the minimum number of @@ -1179,11 +1230,12 @@ public: /// void popFront() { - assert(!empty); + assert(!empty, "Can not popFront of empty SetIntersection"); static if (Rs.length > 1) foreach (i, ref r; _input) { alias next = _input[(i + 1) % Rs.length]; - assert(!comp(r.front, next.front)); + assert(!comp(r.front, next.front), "Set elements must not" + ~ " contradict the less predicate"); } foreach (ref r; _input) @@ -1196,7 +1248,7 @@ public: /// @property ElementType front() { - assert(!empty); + assert(!empty, "Can not get front of empty SetIntersection"); return _input[0].front; } @@ -1274,20 +1326,20 @@ if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && } /** -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 +Lazily computes the symmetric difference of `r1` and `r2`, +i.e. the elements that are present in exactly one of `r1` and $(D +r2). The two ranges are assumed to be sorted by `less`, and the +output is also sorted by `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. +where `a` is the number of occurences of `x` in `r1`, `b` is the number of +occurences of `x` in `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 +`SetSymmetricDifference` will also be a range of L-values of that type. Params: @@ -1336,7 +1388,7 @@ public: /// void popFront() { - assert(!empty); + assert(!empty, "Can not popFront of empty SetSymmetricDifference"); if (r1.empty) r2.popFront(); else if (r2.empty) r1.popFront(); else @@ -1348,7 +1400,8 @@ public: } else { - assert(comp(r2.front, r1.front)); + assert(comp(r2.front, r1.front), "Elements of R1 and R2" + ~ " must be different"); r2.popFront(); } } @@ -1358,9 +1411,11 @@ public: /// @property auto ref front() { - assert(!empty); + assert(!empty, "Can not get the front of an empty" + ~ " SetSymmetricDifference"); immutable chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front); - assert(chooseR1 || r1.empty || comp(r2.front, r1.front)); + assert(chooseR1 || r1.empty || comp(r2.front, r1.front), "Failed to" + ~ " get appropriate front"); return chooseR1 ? r1.front : r2.front; } @@ -1410,7 +1465,8 @@ setSymmetricDifference(alias less = "a < b", R1, R2) assert(equal(setSymmetricDifference(c, d), [1, 1, 2, 5, 6, 7, 9])); } -@safe unittest // Issue 10460 +// https://issues.dlang.org/show_bug.cgi?id=10460 +@safe unittest { import std.algorithm.comparison : equal; @@ -1435,8 +1491,8 @@ 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 +Lazily computes the union of two or more ranges `rs`. The ranges +are assumed to be sorted by `less`. Elements in the output are unique. The element types of all ranges must have a common type. Params: diff --git a/libphobos/src/std/algorithm/sorting.d b/libphobos/src/std/algorithm/sorting.d index 2400bca..dbfe37f 100644 --- a/libphobos/src/std/algorithm/sorting.d +++ b/libphobos/src/std/algorithm/sorting.d @@ -1,29 +1,28 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _sorting algorithms. +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.) + If `a = [10, 20, 30]` and `b = [40, 6, 15]`, then + `completeSort(a, b)` leaves `a = [6, 10, 15]` and `b = [20, 30, 40]`. + The range `a` must be sorted prior to the call, and as a result the + combination `$(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) + `isPartitioned!"a < 0"([-1, -2, 1, 0, 2])` returns `true` because + the predicate is `true` for a portion of the range and `false` afterwards.) $(T2 isSorted, - $(D isSorted([1, 1, 2, 3])) returns $(D true).) + `isSorted([1, 1, 2, 3])` returns `true`.) $(T2 isStrictlyMonotonic, - $(D isStrictlyMonotonic([1, 1, 2, 3])) returns $(D false).) + `isStrictlyMonotonic([1, 1, 2, 3])` returns `false`.) $(T2 ordered, - $(D ordered(1, 1, 2, 3)) returns $(D true).) + `ordered(1, 1, 2, 3)` returns `true`.) $(T2 strictlyOrdered, - $(D strictlyOrdered(1, 1, 2, 3)) returns $(D false).) + `strictlyOrdered(1, 1, 2, 3)` returns `false`.) $(T2 makeIndex, Creates a separate index for a range.) $(T2 merge, @@ -36,10 +35,13 @@ $(T2 nextEvenPermutation, $(T2 nextPermutation, Computes the next lexicographically greater permutation of a range in-place.) +$(T2 nthPermutation, + Computes the nth 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.) + If `a = [5, 4, 3, 2, 1]`, then `partialSort(a, 3)` leaves + `a[0 .. 3] = [1, 2, 3]`. + The other elements of `a` are left in an unspecified order.) $(T2 partition, Partitions a range according to a unary predicate.) $(T2 partition3, @@ -55,7 +57,7 @@ $(T2 schwartzSort, $(T2 sort, Sorts.) $(T2 topN, - Separates the top elements in a range.) + Separates the top elements in a range, akin to $(LINK2 https://en.wikipedia.org/wiki/Quickselect, Quickselect).) $(T2 topNCopy, Copies out the top elements of a range.) $(T2 topNIndex, @@ -68,7 +70,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_sorting.d) +Source: $(PHOBOSSRC std/algorithm/sorting.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) @@ -76,33 +78,34 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) module std.algorithm.sorting; import std.algorithm.mutation : SwapStrategy; -import std.functional; // : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun; import std.range.primitives; -import std.typecons : Flag; -// FIXME -import std.meta; // : allSatisfy; -import std.range; // : SortedRange; +import std.typecons : Flag, No, Yes; +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. +If set to `SortOutput.no`, the output should not be sorted. -Otherwise if set to $(D SortOutput.yes), the output should be sorted. +Otherwise if set to `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)) +Sorts the random-access range `chain(lhs, rhs)` according to +predicate `less`. + +The left-hand side of the range `lhs` is assumed to be already sorted; +`rhs` is assumed to be unsorted. +The exact strategy chosen depends on the relative sizes of `lhs` and +`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). +rhs.length)) (worst-case) evaluations of $(REF_ALTTEXT swap, swap, std,algorithm,mutation). Params: less = The predicate to sort by. @@ -112,8 +115,9 @@ Params: sorted. */ void completeSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, - RandomAccessRange1, RandomAccessRange2)(SortedRange!(RandomAccessRange1, less) lhs, RandomAccessRange2 rhs) -if (hasLength!(RandomAccessRange2) && hasSlicing!(RandomAccessRange2)) + Lhs , Rhs)(SortedRange!(Lhs, less) lhs, Rhs rhs) +if (hasLength!(Rhs) && hasSlicing!(Rhs) + && hasSwappableElements!Lhs && hasSwappableElements!Rhs) { import std.algorithm.mutation : bringToFront; import std.range : chain, assumeSorted; @@ -143,8 +147,8 @@ if (hasLength!(RandomAccessRange2) && hasSlicing!(RandomAccessRange2)) // 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). +is sorted according to the comparison operation `less`. Performs $(BIGOH r.length) +evaluations of `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. @@ -222,7 +226,7 @@ if (isForwardRange!(Range)) { import std.conv : to; - // Issue 9457 + // https://issues.dlang.org/show_bug.cgi?id=9457 auto x = "abcd"; assert(isSorted(x)); auto y = "acbd"; @@ -291,16 +295,16 @@ if (isForwardRange!Range) } /** -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 +Like `isSorted`, returns `true` if the given `values` are ordered +according to the comparison operation `less`. Unlike `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). +`ordered` allows repeated values, e.g. `ordered(1, 1, 2)` is `true`. To verify +that the values are ordered strictly monotonically, use `strictlyOrdered`; +`strictlyOrdered(1, 1, 2)` is `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 +using `"a <= b"` instead of `"a < b"` is incorrect and will cause failed assertions. Params: @@ -308,8 +312,8 @@ Params: less = The comparison predicate Returns: - $(D true) if the values are ordered; $(D ordered) allows for duplicates, - $(D strictlyOrdered) does not. + `true` if the values are ordered; `ordered` allows for duplicates, + `strictlyOrdered` does not. */ bool ordered(alias less = "a < b", T...)(T values) @@ -366,16 +370,16 @@ if (is(typeof(ordered!less(values)))) // 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). +Partitions a range in two using the given `predicate`. + +Specifically, reorders the range `r = [left, right$(RPAREN)` using $(REF_ALTTEXT swap, swap, std,algorithm,mutation) +such that all elements `i` for which `predicate(i)` is `true` come +before all elements `j` for which `predicate(j)` returns `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). +r.length * log(r.length)) (if stable) evaluations of `less` and $(REF_ALTTEXT swap, swap, std,algorithm,mutation). +The unstable version computes the minimum possible evaluations of `swap` +(roughly half of those performed by the semistable version). Params: predicate = The predicate to partition by. @@ -384,20 +388,18 @@ Params: Returns: -The right part of $(D r) after partitioning. +The right part of `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) +If `ss == SwapStrategy.stable`, `partition` preserves the relative +ordering of all elements `a`, `b` in `r` for which +`predicate(a) == predicate(b)`. +If `ss == SwapStrategy.semistable`, `partition` preserves +the relative ordering of all elements `a`, `b` in the left part of `r` +for which `predicate(a) == predicate(b)`. */ Range partition(alias predicate, SwapStrategy ss, Range)(Range r) -if (ss == SwapStrategy.stable && isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) +if (ss == SwapStrategy.stable && isRandomAccessRange!(Range) && hasLength!Range && + hasSlicing!Range && hasSwappableElements!Range) { import std.algorithm.mutation : bringToFront; @@ -464,7 +466,7 @@ if (ss != SwapStrategy.stable && isInputRange!Range && hasSwappableElements!Rang ++lo; } // found the left bound - assert(lo <= hi); + assert(lo <= hi, "lo must be <= hi"); for (;;) { if (lo == hi) return r[lo .. r.length]; @@ -489,7 +491,7 @@ if (ss != SwapStrategy.stable && isInputRange!Range && hasSwappableElements!Rang result.popFront(); } // found the left bound - assert(!r.empty); + assert(!r.empty, "r must not be empty"); for (;;) { if (pred(r.back)) break; @@ -572,22 +574,23 @@ if (ss != SwapStrategy.stable && isInputRange!Range && hasSwappableElements!Rang // 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: +Hoare partition). + +Specifically, permutes elements of `r` and returns +an index `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)) +$(LI All elements `e` in subrange `r[0 .. k]` satisfy `!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 +$(LI All elements `e` in subrange `r[k .. $]` satisfy `!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 @@ -602,7 +605,7 @@ less = The predicate used for comparison, modeled as a 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` +`0` if `r.length` is `0` Returns: The new position of the pivot @@ -615,9 +618,10 @@ Keynote), Andrei Alexandrescu. */ size_t pivotPartition(alias less = "a < b", Range) (Range r, size_t pivot) -if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) +if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && hasAssignableElements!Range) { - assert(pivot < r.length || r.length == 0 && pivot == 0); + assert(pivot < r.length || r.length == 0 && pivot == 0, "pivot must be" + ~ " less than the length of r or r must be empty and pivot zero"); if (r.length <= 1) return 0; import std.algorithm.mutation : swapAt, move; alias lt = binaryFun!less; @@ -635,17 +639,20 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) 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]); + auto save = r.moveAt(hi); r[hi] = p; // Vacancy is in r[$ - 1] now // Start process for (;;) { // Loop invariant - version (unittest) + version (StdUnittest) { - 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))); + // this used to import std.algorithm.all, but we want to save + // imports when unittests are enabled if possible. + foreach (x; r[0 .. lo]) + assert(!lt(p, x), "p must not be less than x"); + foreach (x; r[hi+1 .. r.length]) + assert(!lt(x, p), "x must not be less than p"); } do ++lo; while (lt(r[lo], p)); r[hi] = r[lo]; @@ -656,17 +663,17 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) // Vacancy is not in r[hi] } // Fixup - assert(lo - hi <= 2); - assert(!lt(p, r[hi])); + assert(lo - hi <= 2, "Following compare not possible"); + assert(!lt(p, r[hi]), "r[hi] must not be less than p"); if (lo == hi + 2) { - assert(!lt(r[hi + 1], p)); + assert(!lt(r[hi + 1], p), "r[hi + 1] must not be less than p"); r[lo] = r[hi + 1]; --lo; } r[lo] = save; if (lt(p, save)) --lo; - assert(!lt(p, r[lo])); + assert(!lt(p, r[lo]), "r[lo] must not be less than p"); } else { @@ -679,14 +686,14 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) if (!lt(r[lo], r[0])) break; } // found the left bound: r[lo] >= r[0] - assert(lo <= hi); + assert(lo <= hi, "lo must be less or equal than 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])); + assert(!lt(r[lo], r[hi]), "r[lo] must not be less than r[hi]"); r.swapAt(lo, hi); } --lo; @@ -747,17 +754,18 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) assert(a == [ 42, 42 ]); import std.algorithm.iteration : map; - import std.random; - import std.stdio; - auto s = unpredictableSeed; - auto g = Random(s); + import std.array : array; + import std.format : format; + import std.random : Random, uniform, Xorshift; + import std.range : iota; + auto s = 123_456_789; + auto g = Xorshift(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])); + assert(a[0 .. pivot].all!(x => x <= a[pivot]), "RNG seed: %d".format(s)); + assert(a[pivot .. $].all!(x => x >= a[pivot]), "RNG seed: %d".format(s)); } test!"a < b"; static bool myLess(int a, int b) @@ -773,7 +781,7 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) 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). +Returns: `true` if `r` is partitioned according to predicate `pred`. */ bool isPartitioned(alias pred, Range)(Range r) if (isForwardRange!(Range)) @@ -799,13 +807,15 @@ if (isForwardRange!(Range)) // 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). +Rearranges elements in `r` in three adjacent ranges and returns +them. + +The first and leftmost range only contains elements in `r` +less than `pivot`. The second and middle range only contains +elements in `r` that are equal to `pivot`. Finally, the third +and rightmost range only contains elements in `r` that are greater +than `pivot`. The less-than test is defined by the binary function +`less`. Params: less = The predicate to use for the rearrangement. @@ -817,7 +827,7 @@ 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. +BUGS: stable `partition3` has not been implemented yet. */ auto partition3(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, E) (Range r, E pivot) @@ -843,15 +853,15 @@ if (ss == SwapStrategy.unstable && isRandomAccessRange!Range for (;; ++j) { if (j == k) break bigloop; - assert(j < r.length); + assert(j < r.length, "j must be less than r.length"); if (lessFun(r[j], pivot)) continue; if (lessFun(pivot, r[j])) break; r.swapAt(i++, j); } - assert(j < k); + assert(j < k, "j must be less than k"); for (;;) { - assert(k > 0); + assert(k > 0, "k must be positive"); if (!lessFun(pivot, r[--k])) { if (lessFun(r[k], pivot)) break; @@ -886,9 +896,9 @@ if (ss == SwapStrategy.unstable && isRandomAccessRange!Range @safe unittest { - import std.random : Random, uniform, unpredictableSeed; + import std.random : Random = Xorshift, uniform; - immutable uint[] seeds = [3923355730, 1927035882, unpredictableSeed]; + immutable uint[] seeds = [3923355730, 1927035882]; foreach (s; seeds) { auto r = Random(s); @@ -916,8 +926,9 @@ if (ss == SwapStrategy.unstable && isRandomAccessRange!Range // 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 +Computes an index for `r` based on the comparison `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 @@ -926,15 +937,15 @@ 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. +collection. The complexity is the same as `sort`'s. -The first overload of $(D makeIndex) writes to a range containing +The first overload of `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 +first overload requires `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 +`makeIndex` overwrites its second argument with the result, but never reallocates it. Params: @@ -943,11 +954,12 @@ Params: 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). +Returns: The pointer-based version returns a `SortedRange` wrapper +over index, of type +`SortedRange!(RangeIndex, (a, b) => binaryFun!less(*a, *b))` +thus reflecting the ordering of the +index. The index-based version returns `void` because the ordering +relation involves not only `index` but also `r`. Throws: If the second argument's length is less than that of the range indexed, an exception is thrown. @@ -960,7 +972,7 @@ makeIndex( RangeIndex) (Range r, RangeIndex index) if (isForwardRange!(Range) && isRandomAccessRange!(RangeIndex) - && is(ElementType!(RangeIndex) : ElementType!(Range)*)) + && is(ElementType!(RangeIndex) : ElementType!(Range)*) && hasAssignableElements!RangeIndex) { import std.algorithm.internal : addressOf; import std.exception : enforce; @@ -984,7 +996,7 @@ void makeIndex( (Range r, RangeIndex index) if (isRandomAccessRange!Range && !isInfinite!Range && isRandomAccessRange!RangeIndex && !isInfinite!RangeIndex && - isIntegral!(ElementType!RangeIndex)) + isIntegral!(ElementType!RangeIndex) && hasAssignableElements!RangeIndex) { import std.conv : to; import std.exception : enforce; @@ -1059,6 +1071,7 @@ if (isRandomAccessRange!Range && !isInfinite!Range && @safe unittest { import std.algorithm.comparison : equal; + import std.range : iota; ubyte[256] index = void; iota(256).makeIndex(index[]); @@ -1085,8 +1098,8 @@ if (Rs.length >= 2 && } import std.functional : binaryFun; + import std.meta : anySatisfy; import std.traits : isCopyable; - import std.typetuple : anySatisfy; private alias comp = binaryFun!less; private alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); @@ -1119,7 +1132,7 @@ if (Rs.length >= 2 && foreach (i, _; Rs) { case i: - assert(!source[i].empty); + assert(!source[i].empty, "Can not get front of empty Merge"); return source[i].front; } } @@ -1158,8 +1171,7 @@ if (Rs.length >= 2 && { if (!source[i].empty) { - assert(previousFront == source[i].front || - comp(previousFront, source[i].front), + assert(!comp(source[i].front, previousFront), "Input " ~ i.stringof ~ " is unsorted"); // @nogc } } @@ -1182,7 +1194,7 @@ if (Rs.length >= 2 && foreach (i, _; Rs) { case i: - assert(!source[i].empty); + assert(!source[i].empty, "Can not get back of empty Merge"); return source[i].back; } } @@ -1225,8 +1237,7 @@ if (Rs.length >= 2 && { if (!source[i].empty) { - assert(previousBack == source[i].back || - comp(source[i].back, previousBack), + assert(!comp(previousBack, source[i].back), "Input " ~ i.stringof ~ " is unsorted"); // @nogc } } @@ -1273,7 +1284,9 @@ if (Rs.length >= 2 && /** 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 + 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 @@ -1308,6 +1321,9 @@ All of its inputs are assumed to be sorted. This can mean that inputs are If any of the inputs `rs` is infinite so is the result (`empty` being always `false`). + +See_Also: $(REF multiwayMerge, std,algorithm,setops) for an analogous function + that merges a dynamic number of ranges. */ Merge!(less, Rs) merge(alias less = "a < b", Rs...)(Rs rs) if (Rs.length >= 2 && @@ -1427,6 +1443,37 @@ if (Rs.length >= 2 && assert(m.empty); } +// Issue 21810: Check for sortedness must not use `==` +@nogc @safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + static immutable a = [ + tuple(1, 1), + tuple(3, 1), + tuple(3, 2), + tuple(5, 1), + ]; + static immutable b = [ + tuple(2, 1), + tuple(3, 1), + tuple(4, 1), + tuple(4, 2), + ]; + static immutable r = [ + tuple(1, 1), + tuple(2, 1), + tuple(3, 1), + tuple(3, 2), + tuple(3, 1), + tuple(4, 1), + tuple(4, 2), + tuple(5, 1), + ]; + assert(merge!"a[0] < b[0]"(a, b).equal(r)); +} + private template validPredicates(E, less...) { static if (less.length == 0) @@ -1440,24 +1487,23 @@ private template validPredicates(E, less...) } /** -$(D auto multiSort(Range)(Range r) - if (validPredicates!(ElementType!Range, less));) +Sorts a range by multiple keys. -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) +The call $(D multiSort!("a.id < b.id", +"a.date > b.date")(r)) sorts the range `r` by `id` ascending, +and sorts elements that have the same `id` by `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 +< b.id : a.date > b.date"(r)), but `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 + The initial range wrapped as a `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)) + if (validPredicates!(ElementType!Range, less) && hasSwappableElements!Range) { import std.meta : AliasSeq; import std.range : assumeSorted; @@ -1485,6 +1531,17 @@ template multiSort(less...) //if (less.length > 1) } } +/// +@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); +} + private bool multiSortPredFun(Range, funs...)(ElementType!Range a, ElementType!Range b) { foreach (f; funs) @@ -1526,17 +1583,6 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range 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; @@ -1556,7 +1602,8 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) assert(pts4.multiSort!("a > b").release.equal(iota(10).retro)); } -@safe unittest //issue 9160 (L-value only comparators) +//https://issues.dlang.org/show_bug.cgi?id=9160 (L-value only comparators) +@safe unittest { static struct A { @@ -1580,7 +1627,9 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) assert(points[1] == A(4, 1)); } -@safe unittest // issue 16179 (cannot access frame of function) +// cannot access frame of function +// https://issues.dlang.org/show_bug.cgi?id=16179 +@safe unittest { auto arr = [[1, 2], [2, 0], [1, 0], [1, 1]]; int c = 3; @@ -1592,7 +1641,8 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) assert(arr == [[1, 0], [1, 1], [1, 2], [2, 0]]); } -@safe unittest //Issue 16413 - @system comparison function +// https://issues.dlang.org/show_bug.cgi?id=16413 - @system comparison function +@safe unittest { bool lt(int a, int b) { return a < b; } static @system auto a = [2, 1]; @@ -1676,7 +1726,7 @@ private void shortSort(alias less, Range)(Range r) break; } - assert(r.length >= 6); + assert(r.length >= 6, "r must have more than 5 elements"); /* The last 5 elements of the range are sorted. Proceed with expanding the sorted portion downward. */ immutable maxJ = r.length - 2; @@ -1687,17 +1737,30 @@ private void shortSort(alias less, Range)(Range r) auto t = r[0]; if (pred(t, r[0])) r[0] = r[0]; }))) // Can we afford to temporarily invalidate the array? { + import core.lifetime : move; + size_t j = i + 1; - auto temp = r[i]; + static if (hasLvalueElements!Range) + auto temp = move(r[i]); + else + auto temp = r[i]; + if (pred(r[j], temp)) { do { - r[j - 1] = r[j]; + static if (hasLvalueElements!Range) + trustedMoveEmplace(r[j], r[j - 1]); + else + r[j - 1] = r[j]; ++j; } while (j < r.length && pred(r[j], temp)); - r[j - 1] = temp; + + static if (hasLvalueElements!Range) + trustedMoveEmplace(temp, r[j - 1]); + else + r[j - 1] = move(temp); } } else @@ -1714,9 +1777,16 @@ private void shortSort(alias less, Range)(Range r) } } +/// @trusted wrapper for moveEmplace +private void trustedMoveEmplace(T)(ref T source, ref T target) @trusted +{ + import core.lifetime : moveEmplace; + moveEmplace(source, target); +} + @safe unittest { - import std.random : Random, uniform; + import std.random : Random = Xorshift, uniform; auto rnd = Random(1); auto a = new int[uniform(100, 200, rnd)]; @@ -1734,7 +1804,7 @@ Sorts the first 5 elements exactly of range r. */ private void sort5(alias lt, Range)(Range r) { - assert(r.length >= 5); + assert(r.length >= 5, "r must have more than 4 elements"); import std.algorithm.mutation : swapAt; @@ -1748,7 +1818,8 @@ private void sort5(alias lt, Range)(Range r) r.swapAt(0, 2); r.swapAt(1, 3); } - assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[3], r[2])); + assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[3], r[2]), "unexpected" + ~ " order"); // 3. Insert 4 into [0, 1, 3] if (lt(r[4], r[1])) @@ -1764,10 +1835,11 @@ private void sort5(alias lt, Range)(Range r) { r.swapAt(3, 4); } - assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[4], r[3])); + assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[4], r[3]), "unexpected" + ~ " order"); // 4. Insert 2 into [0, 1, 3, 4] (note: we already know the last is greater) - assert(!lt(r[4], r[2])); + assert(!lt(r[4], r[2]), "unexpected order"); if (lt(r[2], r[1])) { r.swapAt(1, 2); @@ -1787,6 +1859,7 @@ private void sort5(alias lt, Range)(Range r) { import std.algorithm.iteration : permutations; import std.algorithm.mutation : copy; + import std.range : iota; int[5] buf; foreach (per; iota(5).permutations) @@ -1799,14 +1872,15 @@ private void sort5(alias lt, Range)(Range r) // 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 +Sorts a random-access range according to the predicate `less`. + +Performs $(BIGOH r.length * log(r.length)) evaluations of `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. +Stable sorting requires `hasAssignableElements!Range` to be true. -$(D sort) returns a $(REF SortedRange, std,range) over the original range, +`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. @@ -1815,14 +1889,14 @@ 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 +The predicate is expected to satisfy certain rules in order for `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 +others) when not compiled in release mode, due to the cursory `assumeSorted` +check. Specifically, `sort` expects `less(a,b) && less(b,c)` to imply +`less(a,c)` (transitivity), and, conversely, `!less(a,b) && !less(b,c)` to +imply `!less(a,c)`. Note that the default predicate (`"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. +will always be `false` when either `a` or `b` is NaN. Use $(REF cmp, std,math) instead. Params: @@ -1830,8 +1904,8 @@ Params: 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). +Returns: The initial range wrapped as a `SortedRange` with the predicate +`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. @@ -1916,7 +1990,8 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || double[] numbers = [-0.0, 3.0, -2.0, double.nan, 0.0, -double.nan]; import std.algorithm.comparison : equal; - import std.math : cmp, isIdentical; + import std.math.operations : cmp; + import std.math.traits : isIdentical; sort!((a, b) => cmp(a, b) < 0)(numbers); @@ -1927,7 +2002,10 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || @safe unittest { // Simple regression benchmark - import std.algorithm.iteration, std.algorithm.mutation, std.random; + import std.algorithm.iteration, std.algorithm.mutation; + import std.array : array; + import std.random : Random, uniform; + import std.range : iota; Random rng; int[] a = iota(20148).map!(_ => uniform(-1000, 1000, rng)).array; static uint comps; @@ -1941,7 +2019,7 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || // This should get smaller with time. On occasion it may go larger, but only // if there's thorough justification. - debug enum uint watermark = 1676280; + debug enum uint watermark = 1676220; else enum uint watermark = 1676220; import std.conv; @@ -1955,12 +2033,12 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || { import std.algorithm.internal : rndstuff; import std.algorithm.mutation : swapRanges; - import std.random : Random, unpredictableSeed, uniform; + import std.random : Random = Xorshift, uniform; import std.uni : toUpper; // sort using delegate auto a = new int[100]; - auto rnd = Random(unpredictableSeed); + auto rnd = Random(123_456_789); foreach (ref e; a) { e = uniform(-100, 100, rnd); @@ -2002,14 +2080,14 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || assert(isSorted!("toUpper(a) < toUpper(b)")(b)); { - // Issue 10317 + // https://issues.dlang.org/show_bug.cgi?id=10317 enum E_10317 { a, b } auto a_10317 = new E_10317[10]; sort(a_10317); } { - // Issue 7767 + // https://issues.dlang.org/show_bug.cgi?id=7767 // Unstable sort should complete without an excessive number of predicate calls // This would suggest it's running in quadratic time @@ -2050,16 +2128,29 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || r.sort(); assert(proxySwapCalled); } + + // https://issues.dlang.org/show_bug.cgi?id=20751 + { + static bool refPred(ref int a, ref int b) + { + return a < b; + } + + auto sortedArr = [5,4,3,2,1].sort!refPred; + sortedArr.equalRange(3); + } } private void quickSortImpl(alias less, Range)(Range r, size_t depth) { import std.algorithm.comparison : min, max; import std.algorithm.mutation : swap, swapAt; + import std.conv : to; alias Elem = ElementType!(Range); enum size_t shortSortGetsBetter = max(32, 1024 / Elem.sizeof); - static assert(shortSortGetsBetter >= 1); + static assert(shortSortGetsBetter >= 1, Elem.stringof ~ " " + ~ to!string(Elem.sizeof)); // partition while (r.length > shortSortGetsBetter) @@ -2114,13 +2205,15 @@ 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); + static assert(isRandomAccessRange!Range, Range.stringof ~ " must be a" + ~ " RandomAccessRange"); + static assert(hasLength!Range, Range.stringof ~ " must have length"); + static assert(hasSwappableElements!Range || hasAssignableElements!Range, + Range.stringof ~ " must have swappable of assignable Elements"); alias lessFun = binaryFun!less; - //template because of @@@12410@@@ + //template because of https://issues.dlang.org/show_bug.cgi?id=12410 void heapSort()(Range r) { // If true, there is nothing to do @@ -2135,7 +2228,7 @@ package(std) template HeapOps(alias less, Range) } } - //template because of @@@12410@@@ + //template because of https://issues.dlang.org/show_bug.cgi?id=12410 void buildHeap()(Range r) { immutable n = r.length; @@ -2143,7 +2236,7 @@ package(std) template HeapOps(alias less, Range) { siftDown(r, i, n); } - assert(isHeap(r)); + assert(isHeap(r), "r is not a heap"); } bool isHeap()(Range r) @@ -2160,7 +2253,7 @@ package(std) template HeapOps(alias less, Range) // 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@@@ + // template because of https://issues.dlang.org/show_bug.cgi?id=12410 void siftDown()(Range r, size_t parent, immutable size_t end) { for (;;) @@ -2188,7 +2281,7 @@ package(std) template HeapOps(alias less, Range) // 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@@@ + //template because of https://issues.dlang.org/show_bug.cgi?id=12410 void percolate()(Range r, size_t parent, immutable size_t end) { immutable root = parent; @@ -2232,17 +2325,19 @@ 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); + static assert(isRandomAccessRange!R, R.stringof ~ " must be a" + ~ " RandomAccessRange"); + static assert(hasLength!R, R.stringof ~ " must have a length"); + static assert(hasSlicing!R, R.stringof ~ " must support slicing"); + static assert(hasAssignableElements!R, R.stringof ~ " must support" + ~ " assigning elements"); 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); + bool greater()(auto ref T a, auto ref T b) { return less(b, a); } + bool greaterEqual()(auto ref T a, auto ref T b) { return !less(a, b); } + bool lessEqual()(auto ref T a, auto ref T b) { return !less(b, a); } enum minimalMerge = 128; enum minimalGallop = 7; @@ -2255,6 +2350,7 @@ private template TimSortImpl(alias pred, R) void sort()(R range, T[] temp) { import std.algorithm.comparison : min; + import std.format : format; // Do insertion sort on small range if (range.length <= minimalMerge) @@ -2312,15 +2408,27 @@ private template TimSortImpl(alias pred, R) } // Assert that the code above established the invariant correctly - version (assert) + version (StdUnittest) { - if (stackLen == 2) assert(stack[0].length > stack[1].length); + if (stackLen == 2) + { + assert(stack[0].length > stack[1].length, format! + "stack[0].length %s > stack[1].length %s"( + 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); + assert(stack[k - 2].length > stack[k - 1].length + stack[k].length, + format!"stack[k - 2].length %s > stack[k - 1].length %s + stack[k].length %s"( + stack[k - 2].length, stack[k - 1].length, stack[k].length + )); + assert(stack[k - 1].length > stack[k].length, + format!"stack[k - 1].length %s > stack[k].length %s"( + stack[k - 1].length, stack[k].length + )); } } } @@ -2352,9 +2460,10 @@ private template TimSortImpl(alias pred, R) size_t firstRun()(R range) out(ret) { - assert(ret <= range.length); + assert(ret <= range.length, "ret must be less or equal than" + ~ " range.length"); } - body + do { import std.algorithm.mutation : reverse; @@ -2377,9 +2486,9 @@ private template TimSortImpl(alias pred, R) void binaryInsertionSort()(R range, size_t sortedLen = 1) out { - if (!__ctfe) assert(isSorted!pred(range)); + if (!__ctfe) assert(isSorted!pred(range), "range must be sorted"); } - body + do { import std.algorithm.mutation : move; @@ -2400,8 +2509,17 @@ private template TimSortImpl(alias pred, R) //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); + { + static if (hasLvalueElements!R) + move(range[upper -1], range[upper]); + else + range[upper] = range.moveAt(upper - 1); + } + + static if (hasLvalueElements!R) + move(item, range[lower]); + else + range[lower] = move(item); } } @@ -2409,10 +2527,12 @@ private template TimSortImpl(alias pred, R) 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); + import std.format : format; + assert(stack.length >= 2, "stack be be greater than 1"); + assert(stack.length - at == 2 || stack.length - at == 3, + format!"stack.length - at %s must be 2 or 3"(stack.length - at)); } - body + do { immutable base = stack[at].base; immutable mid = stack[at].length; @@ -2433,13 +2553,16 @@ private template TimSortImpl(alias pred, R) { if (!__ctfe) { - assert(isSorted!pred(range[0 .. mid])); - assert(isSorted!pred(range[mid .. range.length])); + assert(isSorted!pred(range[0 .. mid]), "range[0 .. mid] must be" + ~ " sorted"); + assert(isSorted!pred(range[mid .. range.length]), "range[mid .." + ~ " range.length] must be sorted"); } } - body + do { - assert(mid < range.length); + assert(mid < range.length, "mid must be less than the length of the" + ~ " range"); // Reduce range of elements immutable firstElement = gallopForwardUpper(range[0 .. mid], range[mid]); @@ -2466,9 +2589,9 @@ private template TimSortImpl(alias pred, R) T[] ensureCapacity()(size_t minCapacity, T[] temp) out(ret) { - assert(ret.length >= minCapacity); + assert(ret.length >= minCapacity, "ensuring the capacity failed"); } - body + do { if (temp.length < minCapacity) { @@ -2487,21 +2610,22 @@ private template TimSortImpl(alias pred, R) size_t mergeLo()(R range, immutable size_t mid, size_t minGallop, T[] temp) out { - if (!__ctfe) assert(isSorted!pred(range)); + if (!__ctfe) assert(isSorted!pred(range), "the range must be sorted"); } - body + do { import std.algorithm.mutation : copy; - assert(mid <= range.length); - assert(temp.length >= mid); + assert(mid <= range.length, "mid must be less than the length of the" + ~ " range"); + assert(temp.length >= mid, "temp.length must be greater or equal to mid"); // Copy run into temporary memory temp = temp[0 .. mid]; copy(range[0 .. mid], temp); // Move first element into place - range[0] = range[mid]; + moveEntry(range, mid, range, 0); size_t i = 1, lef = 0, rig = mid + 1; size_t count_lef, count_rig; @@ -2518,14 +2642,14 @@ private template TimSortImpl(alias pred, R) { if (lessEqual(temp[lef], range[rig])) { - range[i++] = temp[lef++]; + moveEntry(temp, lef++, range, i++); if (lef >= lef_end) break outer; ++count_lef; count_rig = 0; } else { - range[i++] = range[rig++]; + moveEntry(range, rig++, range, i++); if (rig >= range.length) break outer; count_lef = 0; ++count_rig; @@ -2536,14 +2660,14 @@ private template TimSortImpl(alias pred, R) do { count_lef = gallopForwardUpper(temp[lef .. $], range[rig]); - foreach (j; 0 .. count_lef) range[i++] = temp[lef++]; + foreach (j; 0 .. count_lef) moveEntry(temp, lef++, range, i++); if (lef >= temp.length) break outer; count_rig = gallopForwardLower(range[rig .. range.length], temp[lef]); - foreach (j; 0 .. count_rig) range[i++] = range[rig++]; + foreach (j; 0 .. count_rig) moveEntry(range, rig++, range, i++); if (rig >= range.length) while (true) { - range[i++] = temp[lef++]; + moveEntry(temp, lef++, range, i++); if (lef >= temp.length) break outer; } @@ -2556,11 +2680,11 @@ private template TimSortImpl(alias pred, R) // Move remaining elements from right while (rig < range.length) - range[i++] = range[rig++]; + moveEntry(range, rig++, range, i++); // Move remaining elements from left while (lef < temp.length) - range[i++] = temp[lef++]; + moveEntry(temp, lef++, range, i++); return minGallop > 0 ? minGallop : 1; } @@ -2570,21 +2694,24 @@ private template TimSortImpl(alias pred, R) size_t mergeHi()(R range, immutable size_t mid, size_t minGallop, T[] temp) out { - if (!__ctfe) assert(isSorted!pred(range)); + if (!__ctfe) assert(isSorted!pred(range), "the range must be sorted"); } - body + do { import std.algorithm.mutation : copy; + import std.format : format; - assert(mid <= range.length); - assert(temp.length >= range.length - mid); + assert(mid <= range.length, "mid must be less or equal to range.length"); + assert(temp.length >= range.length - mid, format! + "temp.length %s >= range.length %s - mid %s"(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]; + moveEntry(range, mid - 1, range, range.length - 1); size_t i = range.length - 2, lef = mid - 2, rig = temp.length - 1; size_t count_lef, count_rig; @@ -2600,19 +2727,19 @@ private template TimSortImpl(alias pred, R) { if (greaterEqual(temp[rig], range[lef])) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 1) { // Move remaining elements from left while (true) { - range[i--] = range[lef]; + moveEntry(range, lef, range, i--); if (lef == 0) break; --lef; } // Move last element into place - range[i] = temp[0]; + moveEntry(temp, 0, range, i); break outer; } @@ -2622,10 +2749,10 @@ private template TimSortImpl(alias pred, R) } else { - range[i--] = range[lef]; + moveEntry(range, lef, range, i--); if (lef == 0) while (true) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 0) break outer; --rig; } @@ -2641,7 +2768,7 @@ private template TimSortImpl(alias pred, R) count_rig = rig - gallopReverseLower(temp[0 .. rig], range[lef]); foreach (j; 0 .. count_rig) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 0) break outer; --rig; } @@ -2649,10 +2776,10 @@ private template TimSortImpl(alias pred, R) count_lef = lef - gallopReverseUpper(range[0 .. lef], temp[rig]); foreach (j; 0 .. count_lef) { - range[i--] = range[lef]; + moveEntry(range, lef, range, i--); if (lef == 0) while (true) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 0) break outer; --rig; } @@ -2676,9 +2803,10 @@ private template TimSortImpl(alias pred, R) size_t gallopSearch(R)(R range, T value) out(ret) { - assert(ret <= range.length); + assert(ret <= range.length, "ret must be less or equal to" + ~ " range.length"); } - body + do { size_t lower = 0, center = 1, upper = range.length; alias gap = center; @@ -2748,6 +2876,21 @@ private template TimSortImpl(alias pred, R) alias gallopForwardUpper = gallopSearch!(false, true); alias gallopReverseLower = gallopSearch!( true, false); alias gallopReverseUpper = gallopSearch!( true, true); + + /// Helper method that moves from[fIdx] into to[tIdx] if both are lvalues and + /// uses a plain assignment if not (necessary for backwards compatibility) + void moveEntry(X, Y)(ref X from, const size_t fIdx, ref Y to, const size_t tIdx) + { + // This template is instantiated with different combinations of range (R) and temp (T[]). + // T[] obviously has lvalue-elements, so checking R should be enough here + static if (hasLvalueElements!R) + { + import core.lifetime : move; + move(from[fIdx], to[tIdx]); + } + else + to[tIdx] = from[fIdx]; + } } @safe unittest @@ -2797,6 +2940,7 @@ private template TimSortImpl(alias pred, R) // Tests the Timsort function for correctness and stability static bool testSort(uint seed) { + import std.format : format; auto arr = genSampleData(seed); // Now sort the array! @@ -2808,12 +2952,15 @@ private template TimSortImpl(alias pred, R) sort!(comp, SwapStrategy.stable)(arr); // Test that the array was sorted correctly - assert(isSorted!comp(arr)); + assert(isSorted!comp(arr), "arr must be sorted"); // 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); + if (arr[i].value == arr[i + 1].value) + assert(arr[i].index < arr[i + 1].index, format! + "arr[i %s].index %s < arr[i + 1].index %s"( + i, arr[i].index, arr[i + 1].index)); } return true; @@ -2823,11 +2970,12 @@ private template TimSortImpl(alias pred, R) testSort(seed); enum result = testSort(seed); - assert(result == true); + assert(result == true, "invalid result"); } +// https://issues.dlang.org/show_bug.cgi?id=4584 @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] @@ -2847,18 +2995,54 @@ private template TimSortImpl(alias pred, R) assert(y == "aebcd"d); } +// https://issues.dlang.org/show_bug.cgi?id=14223 @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); } +@safe unittest +{ + static struct NoCopy + { + pure nothrow @nogc @safe: + + int key; + this(scope const ref NoCopy) + { + assert(false, "Tried to copy struct!"); + } + ref opAssign()(scope const auto ref NoCopy other) + { + assert(false, "Tried to copy struct!"); + } + this(this) {} + } + + static NoCopy[] makeArray(const size_t size) + { + NoCopy[] array = new NoCopy[](size); + foreach (const i, ref t; array[0..$/2]) t.key = cast(int) (size - i); + foreach (const i, ref t; array[$/2..$]) t.key = cast(int) i; + return array; + } + + alias cmp = (ref NoCopy a, ref NoCopy b) => a.key < b.key; + enum minMerge = TimSortImpl!(cmp, NoCopy[]).minimalMerge; + + sort!(cmp, SwapStrategy.unstable)(makeArray(20)); + sort!(cmp, SwapStrategy.stable)(makeArray(minMerge - 5)); + sort!(cmp, SwapStrategy.stable)(makeArray(minMerge + 5)); +} + // schwartzSort /** Alternative sorting method that should be used when comparing keys involves an -expensive computation. Instead of using `less(a, b)` for comparing elements, +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 @@ -2882,46 +3066,75 @@ sort!((a, b) => hashFun(a) < hashFun(b))(array); schwartzSort!(hashFun, "a < b")(array); ---- -The $(D schwartzSort) function might require less temporary data and +The `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 +Schwartz sorting, a function `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. + transform = The transformation to apply. Either a unary function + (`unaryFun!transform(element)`), or a binary function + (`binaryFun!transform(element, index)`). + less = The predicate to sort the transformed elements 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))). +Returns: The initial range wrapped as a `SortedRange` with the +predicate `(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) +if (isRandomAccessRange!R && hasLength!R && hasSwappableElements!R && + !is(typeof(binaryFun!less) == SwapStrategy)) { - import std.conv : emplace; + import core.lifetime : emplace; import std.range : zip, SortedRange; import std.string : representation; - alias T = typeof(unaryFun!transform(r.front)); + static if (is(typeof(unaryFun!transform(r.front)))) + { + alias transformFun = unaryFun!transform; + alias TB = typeof(transformFun(r.front)); + enum isBinary = false; + } + else static if (is(typeof(binaryFun!transform(r.front, 0)))) + { + alias transformFun = binaryFun!transform; + alias TB = typeof(transformFun(r.front, 0)); + enum isBinary = true; + } + else + static assert(false, "unsupported `transform` alias"); + + // The `transform` function might return a qualified type, e.g. const(int). + // Strip qualifiers if possible s.t. the temporary array is sortable. + static if (is(TB : Unqual!TB)) + alias T = Unqual!TB; + else + static assert(false, "`transform` returns an unsortable qualified type: " ~ TB.stringof); + 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]; + if (overflow) assert(false, "multiplication overflowed"); + T[] result = (cast(T*) malloc(nbytes))[0 .. len]; + static if (hasIndirections!T) + { + import core.memory : GC; + GC.addRange(result.ptr, nbytes); + } + return result; } auto xform1 = trustedMalloc(r.length); @@ -2935,13 +3148,21 @@ if (isRandomAccessRange!R && hasLength!R) static void trustedFree(T[] p) @trusted { import core.stdc.stdlib : free; + static if (hasIndirections!T) + { + import core.memory : GC; + GC.removeRange(p.ptr); + } free(p.ptr); } trustedFree(xform1); } for (; length != r.length; ++length) { - emplace(&xform1[length], unaryFun!transform(r[length])); + static if (isBinary) + emplace(&xform1[length], transformFun(r[length], length)); + else + emplace(&xform1[length], transformFun(r[length])); } // Make sure we use ubyte[] and ushort[], not char[] and wchar[] // for the intermediate array, lest zip gets confused. @@ -2957,6 +3178,13 @@ if (isRandomAccessRange!R && hasLength!R) return typeof(return)(r); } +/// ditto +auto schwartzSort(alias transform, SwapStrategy ss, R)(R r) +if (isRandomAccessRange!R && hasLength!R && hasSwappableElements!R) +{ + return schwartzSort!(transform, "a < b", ss, R)(r); +} + /// @safe unittest { @@ -3002,27 +3230,109 @@ if (isRandomAccessRange!R && hasLength!R) @safe unittest { - // issue 4909 + // binary transform function + string[] strings = [ "one", "two", "three" ]; + schwartzSort!((element, index) => size_t.max - index)(strings); + assert(strings == [ "three", "two", "one" ]); +} + +// https://issues.dlang.org/show_bug.cgi?id=4909 +@safe unittest +{ import std.typecons : Tuple; Tuple!(char)[] chars; schwartzSort!"a[0]"(chars); } +// https://issues.dlang.org/show_bug.cgi?id=5924 @safe unittest { - // issue 5924 import std.typecons : Tuple; Tuple!(char)[] chars; schwartzSort!((Tuple!(char) c){ return c[0]; })(chars); } +// https://issues.dlang.org/show_bug.cgi?id=13965 +@safe unittest +{ + import std.typecons : Tuple; + Tuple!(char)[] chars; + schwartzSort!("a[0]", SwapStrategy.stable)(chars); +} + +// https://issues.dlang.org/show_bug.cgi?id=13965 +@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, SwapStrategy.stable)(arr); + + assert(arr[0] == lowEnt); + assert(arr[1] == midEnt); + assert(arr[2] == highEnt); + assert(isSorted!("a < b")(map!(entropy)(arr))); +} + +// https://issues.dlang.org/show_bug.cgi?id=20799 +@safe unittest +{ + import std.range : iota, retro; + import std.array : array; + + auto arr = 1_000_000.iota.retro.array; + arr.schwartzSort!( + n => new int(n), + (a, b) => *a < *b + ); + assert(arr.isSorted()); +} + +// https://issues.dlang.org/show_bug.cgi?id=21183 +@safe unittest +{ + static T get(T)(int) { return T.init; } + + // There's no need to actually sort, just checking type interference + if (false) + { + int[] arr; + + // Fine because there are no indirections + arr.schwartzSort!(get!(const int)); + + // Fine because it decays to immutable(int)* + arr.schwartzSort!(get!(immutable int*)); + + // Disallowed because it would require a non-const reference + static assert(!__traits(compiles, arr.schwartzSort!(get!(const Object)))); + + static struct Wrapper + { + int* ptr; + } + + // Disallowed because Wrapper.ptr would become mutable + static assert(!__traits(compiles, arr.schwartzSort!(get!(const Wrapper)))); + } +} + // 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 +Reorders the random-access range `r` such that the range `r[0 .. mid]` +is the same as if the entire `r` were sorted, and leaves +the range `r[mid .. r.length]` in no particular order. + +Performs $(BIGOH r.length * log(mid)) evaluations of `pred`. The +implementation simply calls `topN!(less, ss)(r, n)` and then $(D sort!(less, ss)(r[0 .. n])). Params: @@ -3077,17 +3387,20 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && // 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 +Reorders the range `r` using $(REF_ALTTEXT swap, swap, std,algorithm,mutation) +such that `r[nth]` refers to the element that would fall there if the range +were fully sorted. + +It is akin to $(LINK2 https://en.wikipedia.org/wiki/Quickselect, Quickselect), +and partitions `r` such that all elements +`e1` from `r[0]` to `r[nth]` satisfy `!less(r[nth], e1)`, +and all elements `e2` from `r[nth]` to `r[r.length]` satisfy +`!less(e2, r[nth])`. Effectively, it finds the `nth + 1` smallest +(according to `less`) elements in `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 stable) evaluations of `less` and $(REF_ALTTEXT swap, swap, std,algorithm,mutation). -If $(D n >= r.length), the algorithm has no effect and returns +If `n >= r.length`, the algorithm has no effect and returns `r[0 .. r.length]`. Params: @@ -3097,9 +3410,10 @@ Params: nth = The index of the element that should be in sorted position after the function is done. +Returns: a slice from `r[0]` to `r[nth]`, excluding `r[nth]` itself. + See_Also: $(LREF topNIndex), - $(HTTP sgi.com/tech/stl/nth_element.html, STL's nth_element) BUGS: @@ -3108,7 +3422,8 @@ 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) +if (isRandomAccessRange!(Range) && hasLength!Range && + hasSlicing!Range && hasAssignableElements!Range) { static assert(ss == SwapStrategy.unstable, "Stable topN not yet implemented"); @@ -3119,10 +3434,11 @@ if (isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) // 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]); + cast(void) 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)); + static assert(is(typeof(r.length) == size_t), + typeof(r.length).stringof ~ " must be of type size_t"); pivotPartition!less(r, 0); } bool useSampling = true; @@ -3130,6 +3446,28 @@ if (isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) return ret; } +/// +@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) => a < b)(v, n); + assert(v[n] == 9); +} + +// https://issues.dlang.org/show_bug.cgi?id=8341 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : zip; + import std.typecons : tuple; + auto a = [10, 30, 20]; + auto b = ["c", "b", "a"]; + assert(topN!"a[0] > b[0]"(zip(a, b), 2).equal([tuple(20, "a"), tuple(30, "b")])); +} + private @trusted void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling) { @@ -3212,7 +3550,7 @@ void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling) } } - assert(pivot != size_t.max); + assert(pivot != size_t.max, "pivot must be not equal to size_t.max"); // See how the pivot fares if (pivot == n) { @@ -3230,20 +3568,11 @@ void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling) } } -/// -@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); + import std.format : format; + assert(r.length >= 9 && n < r.length, "length must be longer than 9" + ~ " and n must be less than 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 @@ -3252,9 +3581,11 @@ private size_t topNPartition(alias lp, R)(R r, size_t n, bool useSampling) // 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); + assert(lo - (r.length - hi) <= 1 || (r.length - hi) - lo <= 1, + format!"straggler check failed lo %s, r.length %s, hi %s"(lo, r.length, hi)); + assert(lo >= ninth * 4, format!"lo %s >= ninth * 4 %s"(lo, ninth * 4)); + assert(r.length - hi >= ninth * 4, + format!"r.length %s - hi %s >= ninth * 4 %s"(r.length, hi, ninth * 4)); // Partition in groups of 3, and the mid tertile again in groups of 3 if (!useSampling) @@ -3270,12 +3601,15 @@ private size_t topNPartition(alias lp, R)(R r, size_t n, bool useSampling) private void p3(alias less, Range)(Range r, size_t lo, immutable size_t hi) { - assert(lo <= hi && hi < r.length); + import std.format : format; + assert(lo <= hi && hi < r.length, + format!"lo %s <= hi %s && hi < r.length %s"(lo, hi, r.length)); immutable ln = hi - lo; for (; lo < hi; ++lo) { - assert(lo >= ln); - assert(lo + ln < r.length); + assert(lo >= ln, format!"lo %s >= ln %s"(lo, ln)); + assert(lo + ln < r.length, format!"lo %s + ln %s < r.length %s"( + lo, ln, r.length)); medianOf!less(r, lo - ln, lo, lo + ln); } } @@ -3283,12 +3617,15 @@ private void p3(alias less, Range)(Range r, size_t lo, immutable size_t hi) private void p4(alias less, Flag!"leanRight" f, Range) (Range r, size_t lo, immutable size_t hi) { - assert(lo <= hi && hi < r.length); + import std.format : format; + assert(lo <= hi && hi < r.length, format!"lo %s <= hi %s && hi < r.length %s"( + lo, hi, r.length)); immutable ln = hi - lo, _2ln = ln * 2; for (; lo < hi; ++lo) { - assert(lo >= ln); - assert(lo + ln < r.length); + assert(lo >= ln, format!"lo %s >= ln %s"(lo, ln)); + assert(lo + ln < r.length, format!"lo %s + ln %s < r.length %s"( + lo, ln, r.length)); static if (f == Yes.leanRight) medianOf!(less, f)(r, lo - _2ln, lo - ln, lo, lo + ln); else @@ -3299,8 +3636,8 @@ private void p4(alias less, Flag!"leanRight" f, Range) 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); + assert(r.length >= 12, "The length of r must be greater than 11"); + assert(n < r.length, "n must be less than the length of r"); immutable _4 = r.length / 4; static if (f == Yes.leanRight) immutable leftLimit = 2 * _4; @@ -3337,19 +3674,23 @@ 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]))); + assert(lo <= pivot, "lo must be less than or equal pivot"); + assert(pivot < hi, "pivot must be less than hi"); + assert(hi <= r.length, "hi must be less than or equal to the length of r"); + assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x)), + "r[lo .. pivot + 1] failed less than test"); + assert(r[pivot + 1 .. hi].all!(x => !lp(x, r[pivot])), + "r[pivot + 1 .. hi] failed less than test"); } 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]))); + assert(r[0 .. pivot + 1].all!(x => !lp(r[pivot], x)), + "r[0 .. pivot + 1] failed less than test"); + assert(r[pivot + 1 .. r.length].all!(x => !lp(x, r[pivot])), + "r[pivot + 1 .. r.length] failed less than test"); } -body +do { import std.algorithm.mutation : swapAt; import std.algorithm.searching : all; @@ -3372,10 +3713,14 @@ body 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]))); + assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x)), + "r[lo .. pivot + 1] failed less than test"); + assert(r[pivot + 1 .. hi + 1].all!(x => !lp(x, r[pivot])), + "r[pivot + 1 .. hi + 1] failed less than test"); + assert(r[0 .. left].all!(x => !lp(r[pivot], x)), + "r[0 .. left] failed less than test"); + assert(r[rite + 1 .. r.length].all!(x => !lp(x, r[pivot])), + "r[rite + 1 .. r.length] failed less than test"); immutable oldPivot = pivot; @@ -3387,7 +3732,7 @@ body if (left == lo) goto done; if (!lp(r[oldPivot], r[left])) continue; --pivot; - assert(!lp(r[oldPivot], r[pivot])); + assert(!lp(r[oldPivot], r[pivot]), "less check failed"); r.swapAt(left, pivot); } // Second loop: make left and pivot meet @@ -3414,7 +3759,7 @@ body if (rite == hi) goto done; if (!lp(r[rite], r[oldPivot])) continue; ++pivot; - assert(!lp(r[pivot], r[oldPivot])); + assert(!lp(r[pivot], r[oldPivot]), "less check failed"); r.swapAt(rite, pivot); } // Second loop: make left and pivot meet @@ -3441,23 +3786,18 @@ done: { 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; + + import std.algorithm.iteration : map; + import std.array : array; + import std.random : uniform; + import std.range : iota; + auto size = uniform(1, 1000); + a = iota(0, size).map!(_ => uniform(0, 1000)).array; 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; @@ -3509,9 +3849,9 @@ private T[] randomArray(Flag!"exactSize" flag = No.exactSize, T = int)( { import std.algorithm.comparison : max, min; import std.algorithm.iteration : reduce; - import std.random : Random, uniform, unpredictableSeed; + import std.random : Random = Xorshift, uniform; - immutable uint[] seeds = [90027751, 2709791795, 1374631933, 995751648, 3541495258, 984840953, unpredictableSeed]; + immutable uint[] seeds = [90027751, 2709791795, 1374631933, 995751648, 3541495258, 984840953]; foreach (s; seeds) { auto r = Random(s); @@ -3534,7 +3874,7 @@ private T[] randomArray(Flag!"exactSize" flag = No.exactSize, T = int)( } } -// bug 12987 +// https://issues.dlang.org/show_bug.cgi?id=12987 @safe unittest { int[] a = [ 25, 7, 9, 2, 0, 5, 21 ]; @@ -3584,7 +3924,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && assert(a == [0, 1, 2, 2, 3]); } -// bug 15421 +// https://issues.dlang.org/show_bug.cgi?id=15421 @system unittest { import std.algorithm.comparison : equal; @@ -3628,7 +3968,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && } } -// bug 15421 +// https://issues.dlang.org/show_bug.cgi?id=15421 @system unittest { auto a = [ 9, 8, 0, 3, 5, 25, 43, 4, 2, 0, 7 ]; @@ -3647,7 +3987,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && assert(a == b); } -// bug 12987 +// https://issues.dlang.org/show_bug.cgi?id=12987 @system unittest { int[] a = [ 5, 7, 2, 6, 7 ]; @@ -3657,7 +3997,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && assert(t == [ 0, 1, 2, 2, 3 ]); } -// bug 15420 +// https://issues.dlang.org/show_bug.cgi?id=15420 @system unittest { int[] a = [ 5, 7, 2, 6, 7 ]; @@ -3668,11 +4008,12 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && } /** -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 +Copies the top `n` elements of the +$(REF_ALTTEXT input range, isInputRange, std,range,primitives) `source` into the +random-access range `target`, where `n = target.length`. + +Elements of `source` are not touched. If $(D +sorted) is `true`, the target is sorted. Otherwise, the target respects the $(HTTP en.wikipedia.org/wiki/Binary_heap, heap property). Params: @@ -3714,10 +4055,10 @@ if (isInputRange!(SRange) && isRandomAccessRange!(TRange) @system unittest { - import std.random : Random, unpredictableSeed, uniform, randomShuffle; + import std.random : Random = Xorshift, uniform, randomShuffle; import std.typecons : Yes; - auto r = Random(unpredictableSeed); + auto r = Random(123_456_789); ptrdiff_t[] a = new ptrdiff_t[uniform(1, 1000, r)]; foreach (i, ref e; a) e = i; randomShuffle(a, r); @@ -3735,7 +4076,7 @@ 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). + Defaults to `a < b`. ss = $(RED (Not implemented yet.)) Specify the swapping strategy. r = A $(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) @@ -3743,13 +4084,13 @@ Params: 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). + determines how many top elements to index in `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 + `r`; or it can have pointers to the element type of `r`, in which case the constructed index will be pointers to the top elements in - $(D r). + `r`. sorted = Determines whether to sort the index by the elements they refer to. @@ -3852,7 +4193,7 @@ 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]` +involved around the median, e.g. `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]`. @@ -3861,9 +4202,9 @@ 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 +"leans left", meaning `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 +Conversely, `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. @@ -3879,37 +4220,45 @@ if (isRandomAccessRange!Range && hasLength!Range && Indexes.length >= 2 && Indexes.length <= 5 && allSatisfy!(isUnsigned, Indexes)) { - assert(r.length >= Indexes.length); + assert(r.length >= Indexes.length, "r.length must be greater equal to" + ~ " Indexes.length"); import std.functional : binaryFun; alias lt = binaryFun!less; enum k = Indexes.length; import std.algorithm.mutation : swapAt; + import std.format : format; alias a = i[0]; - static assert(is(typeof(a) == size_t)); + static assert(is(typeof(a) == size_t), typeof(a).stringof ~ " must be" + ~ " of type size_t"); static if (k >= 2) { alias b = i[1]; - static assert(is(typeof(b) == size_t)); - assert(a != b); + static assert(is(typeof(b) == size_t), typeof(b).stringof ~ " must be" + ~ " of type size_t"); + assert(a != b, "a != b "); } static if (k >= 3) { alias c = i[2]; - static assert(is(typeof(c) == size_t)); - assert(a != c && b != c); + static assert(is(typeof(c) == size_t), typeof(c).stringof ~ " must be" + ~ " of type size_t"); + assert(a != c && b != c, "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 assert(is(typeof(d) == size_t), typeof(d).stringof ~ " must be" + ~ " of type size_t"); + assert(a != d && b != d && c != d, "a != d && b != d && c != d failed"); } 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 assert(is(typeof(e) == size_t), typeof(e).stringof ~ " must be" + ~ " of type size_t"); + assert(a != e && b != e && c != e && d != e, + "a != e && b != e && c != e && d != e failed"); } static if (k == 2) @@ -3942,8 +4291,8 @@ if (isRandomAccessRange!Range && hasLength!Range && if (lt(r[c], r[b])) r.swapAt(b, c); } } - assert(!lt(r[b], r[a])); - assert(!lt(r[c], r[b])); + assert(!lt(r[b], r[a]), "less than check failed"); + assert(!lt(r[c], r[b]), "less than check failed"); } else static if (k == 4) { @@ -3965,12 +4314,12 @@ if (isRandomAccessRange!Range && hasLength!Range && else static if (k == 5) { // Credit: Teppo Niinimäki - version (unittest) scope(success) + version (StdUnittest) 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])); + assert(!lt(r[c], r[a]), "less than check failed"); + assert(!lt(r[c], r[b]), "less than check failed"); + assert(!lt(r[d], r[c]), "less than check failed"); + assert(!lt(r[e], r[c]), "less than check failed"); } if (lt(r[c], r[a])) r.swapAt(a, c); @@ -4025,16 +4374,16 @@ if (isRandomAccessRange!Range && hasLength!Range && // nextPermutation /** - * Permutes $(D range) in-place to the next lexicographically greater + * Permutes `range` in-place to the next lexicographically greater * permutation. * - * The predicate $(D less) defines the lexicographical ordering to be used on + * The predicate `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 + * sorting it according to `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 @@ -4094,7 +4443,7 @@ if (isBidirectionalRange!BidirectionalRange && auto j = find!((a) => binaryFun!less(i.front, a))( takeExactly(retro(range), n)); - assert(!j.empty); // shouldn't happen since i.front < last.front + assert(!j.empty, "j must not be empty"); // shouldn't happen since i.front < last.front swap(i.front, j.front); reverse(takeExactly(retro(range), n)); @@ -4242,7 +4591,7 @@ if (isBidirectionalRange!BidirectionalRange && assert(a == [3,2,1]); } -// Issue 13594 +// https://issues.dlang.org/show_bug.cgi?id=13594 @safe unittest { int[3] a = [1,2,3]; @@ -4252,10 +4601,10 @@ if (isBidirectionalRange!BidirectionalRange && // nextEvenPermutation /** - * Permutes $(D range) in-place to the next lexicographically greater $(I even) + * Permutes `range` in-place to the next lexicographically greater $(I even) * permutation. * - * The predicate $(D less) defines the lexicographical ordering to be used on + * The predicate `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 @@ -4351,7 +4700,7 @@ if (isBidirectionalRange!BidirectionalRange && takeExactly(retro(range), n)); // shouldn't happen since i.front < last.front - assert(!j.empty); + assert(!j.empty, "j must not be empty"); swap(i.front, j.front); oddParity = !oddParity; @@ -4417,9 +4766,9 @@ if (isBidirectionalRange!BidirectionalRange && assert(b == [ 1, 3, 2 ]); } +// https://issues.dlang.org/show_bug.cgi?id=13594 @safe unittest { - // Issue 13594 int[3] a = [1,2,3]; assert(nextEvenPermutation(a[])); assert(a == [2,3,1]); @@ -4431,7 +4780,7 @@ shapes. Here's a non-trivial example: */ @safe unittest { - import std.math : sqrt; + import std.math.algebraic : sqrt; // Print the 60 vertices of a uniform truncated icosahedron (soccer ball) enum real Phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio @@ -4466,3 +4815,177 @@ shapes. Here's a non-trivial example: } assert(n == 60); } + +/** +Permutes `range` into the `perm` permutation. + +The algorithm has a constant runtime complexity with respect to the number of +permutations created. +Due to the number of unique values of `ulong` only the first 21 elements of +`range` can be permuted. The rest of the range will therefore not be +permuted. +This algorithm uses the $(HTTP en.wikipedia.org/wiki/Lehmer_code, Lehmer +Code). + +The algorithm works as follows: +$(D_CODE + auto pem = [4,0,4,1,0,0,0]; // permutation 2982 in factorial + auto src = [0,1,2,3,4,5,6]; // the range to permutate + + auto i = 0; // range index + // range index iterates pem and src in sync + // pem[i] + i is used as index into src + // first src[pem[i] + i] is stored in t + auto t = 4; // tmp value + src = [0,1,2,3,n,5,6]; + + // then the values between i and pem[i] + i are moved one + // to the right + src = [n,0,1,2,3,5,6]; + // at last t is inserted into position i + src = [4,0,1,2,3,5,6]; + // finally i is incremented + ++i; + + // this process is repeated while i < pem.length + + t = 0; + src = [4,n,1,2,3,5,6]; + src = [4,0,1,2,3,5,6]; + ++i; + t = 6; + src = [4,0,1,2,3,5,n]; + src = [4,0,n,1,2,3,5]; + src = [4,0,6,1,2,3,5]; +) + +Returns: + The permuted range. + +Params: + range = The Range to permute. The original ordering will be lost. + perm = The permutation to permutate `range` to. +*/ +auto ref Range nthPermutation(Range) + (auto ref Range range, const ulong perm) +if (isRandomAccessRange!Range && hasLength!Range) +{ + if (!nthPermutationImpl(range, perm)) + { + throw new Exception( + "The range to permutate must not have less" + ~ " elements than the factorial number has digits"); + } + + return range; +} + +/// +pure @safe unittest +{ + auto src = [0, 1, 2, 3, 4, 5, 6]; + auto rslt = [4, 0, 6, 2, 1, 3, 5]; + + src = nthPermutation(src, 2982); + assert(src == rslt); +} + +/** +Returns: `true` in case the permutation worked, `false` in case `perm` had + more digits in the factorial number system than range had elements. + This case must not occur as this would lead to out of range accesses. +*/ +bool nthPermutationImpl(Range) + (auto ref Range range, ulong perm) +if (isRandomAccessRange!Range && hasLength!Range) +{ + import std.range.primitives : ElementType; + import std.numeric : decimalToFactorial; + + // ulong.max has 21 digits in the factorial number system + ubyte[21] fac; + size_t idx = decimalToFactorial(perm, fac); + + if (idx > range.length) + { + return false; + } + + ElementType!Range tmp; + size_t i = 0; + + for (; i < idx; ++i) + { + size_t re = fac[i]; + tmp = range[re + i]; + for (size_t j = re + i; j > i; --j) + { + range[j] = range[j - 1]; + } + range[i] = tmp; + } + + return true; +} + +/// +pure @safe unittest +{ + auto src = [0, 1, 2, 3, 4, 5, 6]; + auto rslt = [4, 0, 6, 2, 1, 3, 5]; + + bool worked = nthPermutationImpl(src, 2982); + assert(worked); + assert(src == rslt); +} + +pure @safe unittest +{ + auto rslt = [4, 0, 6, 2, 1, 3, 5]; + + auto src = nthPermutation([0, 1, 2, 3, 4, 5, 6], 2982); + assert(src == rslt); +} + +pure @safe unittest +{ + auto src = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto rslt = [4, 0, 6, 2, 1, 3, 5, 7, 8, 9, 10]; + + src = nthPermutation(src, 2982); + assert(src == rslt); +} + +pure @safe unittest +{ + import std.exception : assertThrown; + + auto src = [0, 1, 2, 3]; + + assertThrown(nthPermutation(src, 2982)); +} + +pure @safe unittest +{ + import std.internal.test.dummyrange; + import std.meta : AliasSeq; + + auto src = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto rsl = [4, 0, 6, 2, 1, 3, 5, 7, 8, 9, 10]; + + foreach (T; AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, int[]), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, int[]))) + { + static assert(isRandomAccessRange!(T)); + static assert(hasLength!(T)); + auto dr = T(src.dup); + dr = nthPermutation(dr, 2982); + + int idx; + foreach (it; dr) + { + assert(it == rsl[idx++]); + } + } +} diff --git a/libphobos/src/std/array.d b/libphobos/src/std/array.d index 179fa66..ded1196 100644 --- a/libphobos/src/std/array.d +++ b/libphobos/src/std/array.d @@ -5,50 +5,51 @@ 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;) +$(DIVC quickindex, $(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 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. + $(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. + $(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. + $(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. + $(TD Inserts into an existing array at a given position. )) $(TR $(TD $(LREF join)) - $(TD Concatenates a range of ranges into one _array. + $(TD Concatenates a range of ranges into one array. )) $(TR $(TD $(LREF minimallyInitializedArray)) - $(TD Returns a new _array of type $(D T). + $(TD Returns a new array of type `T`. )) $(TR $(TD $(LREF replace)) - $(TD Returns a new _array with all occurrences of a certain subrange replaced. + $(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. + $(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. + $(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. + $(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. + $(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. + $(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 @@ -59,20 +60,24 @@ $(TR $(TH Function Name) $(TH Description) in memory. )) $(TR $(TD $(LREF split)) - $(TD Eagerly split a range or string into an _array. + $(TD Eagerly split a range or string into an array. + )) + $(TR $(TD $(LREF staticArray)) + $(TD Creates a new static array from given data. )) $(TR $(TD $(LREF uninitializedArray)) - $(TD Returns a new _array of type $(D T) without initializing its elements. + $(TD Returns a new array of type `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 +Authors: $(HTTP erdani.org, Andrei Alexandrescu) and + $(HTTP jmdavisprog.com, Jonathan M Davis) -Source: $(PHOBOSSRC std/_array.d) +Source: $(PHOBOSSRC std/array.d) */ module std.array; @@ -85,17 +90,19 @@ 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). + * of range `r`. * - * Narrow strings are handled as a special case in an overload. + * Narrow strings are handled as follows: + * - If autodecoding is turned on (default), then they are handled as a separate overload. + * - If autodecoding is turned off, then this is equivalent to duplicating the array. * * Params: - * r = range (or aggregate with $(D opApply) function) whose elements are copied into the allocated array + * r = range (or aggregate with `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 (isIterable!Range && !isAutodecodableString!Range && !isInfinite!Range) { if (__ctfe) { @@ -114,7 +121,7 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) if (length == 0) return null; - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))(); @@ -138,6 +145,13 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) } } +/// ditto +ForeachType!(PointerTarget!Range)[] array(Range)(Range r) +if (isPointer!Range && isIterable!(PointerTarget!Range) && !isAutodecodableString!Range && !isInfinite!Range) +{ + return array(*r); +} + /// @safe pure nothrow unittest { @@ -156,13 +170,33 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)])); } -@system unittest +@safe pure nothrow unittest +{ + struct MyRange + { + enum front = 123; + enum empty = true; + void popFront() {} + } + + auto arr = (new MyRange).array; + assert(arr.empty); +} + +@safe pure nothrow unittest +{ + immutable int[] a = [1, 2, 3, 4]; + auto b = (&a).array; + assert(b == a); +} + +@safe pure nothrow unittest { import std.algorithm.comparison : equal; struct Foo { int a; - void opAssign(Foo foo) + noreturn opAssign(Foo) { assert(0); } @@ -175,57 +209,102 @@ if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)])); } -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=12315 +@safe pure nothrow unittest { - // Issue 12315 static struct Bug12315 { immutable int i; } enum bug12315 = [Bug12315(123456789)].array(); static assert(bug12315[0].i == 123456789); } -@safe unittest +@safe pure nothrow unittest { import std.range; static struct S{int* p;} auto a = array(immutable(S).init.repeat(5)); + assert(a.length == 5); +} + +// https://issues.dlang.org/show_bug.cgi?id=18995 +@system unittest +{ + import core.memory : __delete; + int nAlive = 0; + struct S + { + bool alive; + this(int) { alive = true; ++nAlive; } + this(this) { nAlive += alive; } + ~this() { nAlive -= alive; alive = false; } + } + + import std.algorithm.iteration : map; + import std.range : iota; + + auto arr = iota(3).map!(a => S(a)).array; + assert(nAlive == 3); + + // No good way to ensure the GC frees this, just call the lifetime function + // directly. + __delete(arr); + + assert(nAlive == 0); +} + +@safe pure nothrow @nogc unittest +{ + //Turn down infinity: + static assert(!is(typeof( + repeat(1).array() + ))); } /** -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` +Convert a narrow autodecoding string to an array type that fully supports +random access. This is handled as a special case and always returns an array +of `dchar` + +NOTE: This function is never used when autodecoding is turned off. 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 + a `dchar[]`, `const(dchar)[]`, or `immutable(dchar)[]` depending on the constness of the input. */ -@trusted ElementType!String[] array(String)(scope String str) -if (isNarrowString!String) +CopyTypeQualifiers!(ElementType!String,dchar)[] array(String)(scope String str) +if (isAutodecodableString!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. + auto temp = str.toUTF32; + /* Unsafe cast. Allowed because toUTF32 makes a new array + and copies all the elements. */ - return cast(typeof(return)) str.toUTF32; + return () @trusted { return cast(CopyTypeQualifiers!(ElementType!String, dchar)[]) temp; } (); } /// -@safe unittest +@safe pure nothrow unittest { import std.range.primitives : isRandomAccessRange; + import std.traits : isAutodecodableString; + + // note that if autodecoding is turned off, `array` will not transcode these. + static if (isAutodecodableString!string) + assert("Hello D".array == "Hello D"d); + else + assert("Hello D".array == "Hello D"); - assert("Hello D".array == "Hello D"d); - static assert(isRandomAccessRange!string == false); + static if (isAutodecodableString!wstring) + assert("Hello D"w.array == "Hello D"d); + else + assert("Hello D"w.array == "Hello D"w); - assert("Hello D"w.array == "Hello D"d); static assert(isRandomAccessRange!dstring == true); } -@system unittest +@safe unittest { - // @system due to array!string import std.conv : to; static struct TestArray { int x; string toString() @safe { return to!string(x); } } @@ -242,7 +321,7 @@ if (isNarrowString!String) static struct OpApply { - int opApply(scope int delegate(ref int) dg) + int opApply(scope int delegate(ref int) @safe dg) { int res; foreach (i; 0 .. 10) @@ -256,11 +335,10 @@ if (isNarrowString!String) } 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); + assert(b == [TestArray(1), TestArray(2)]); class C { @@ -269,23 +347,27 @@ if (isNarrowString!String) override string toString() const @safe { return to!string(x); } } auto c = array([new C(1), new C(2)][]); - //writeln(c); + assert(c[0].x == 1); + assert(c[1].x == 2); auto d = array([1.0, 2.2, 3][]); assert(is(typeof(d) == double[])); - //writeln(d); + assert(d == [1.0, 2.2, 3]); 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); + static if (isAutodecodableString!string) + { + assert(array("ABC") == "ABC"d); + assert(array("ABC".dup) == "ABC"d.dup); + } } -//Bug# 8233 -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=8233 +@safe pure nothrow unittest { assert(array("hello world"d) == "hello world"d); immutable a = [1, 2, 3, 4, 5]; @@ -305,16 +387,16 @@ if (isNarrowString!String) int i; } - foreach (T; AliasSeq!(S, const S, immutable S)) - { + static foreach (T; AliasSeq!(S, const S, immutable S)) + {{ auto arr = [T(1), T(2), T(3), T(4)]; assert(array(arr) == arr); - } + }} } -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=9824 +@safe pure nothrow unittest { - //9824 static struct S { @disable void opAssign(S); @@ -324,8 +406,8 @@ if (isNarrowString!String) arr.array(); } -// Bugzilla 10220 -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=10220 +@safe pure nothrow unittest { import std.algorithm.comparison : equal; import std.exception; @@ -345,25 +427,26 @@ if (isNarrowString!String) }); } -@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. +Returns a newly allocated associative array from a range of key/value tuples +or from a range of keys and a range of values. 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. + keys = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of keys + values = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of values + +Returns: + + A newly allocated associative array out of elements of the input + range, which must be a range of tuples (Key, Value) or + a range of keys and a range of values. If given two ranges of unequal + lengths after the elements of the shorter are exhausted the remaining + elements of the longer will not be considered. + 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) */ @@ -374,7 +457,8 @@ 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(isTuple!E, "assocArray: argument must be a range of tuples," + ~" but was a range of "~E.stringof); static assert(E.length == 2, "assocArray: tuple dimension must be 2"); alias KeyType = E.Types[0]; alias ValueType = E.Types[1]; @@ -386,57 +470,228 @@ if (isInputRange!Range) return aa; } +/// ditto +auto assocArray(Keys, Values)(Keys keys, Values values) +if (isInputRange!Values && isInputRange!Keys) +{ + static if (isDynamicArray!Keys && isDynamicArray!Values + && !isNarrowString!Keys && !isNarrowString!Values) + { + void* aa; + { + // aaLiteral is nothrow when the destructors don't throw + static if (is(typeof(() nothrow + { + import std.range : ElementType; + import std.traits : hasElaborateDestructor; + alias KeyElement = ElementType!Keys; + static if (hasElaborateDestructor!KeyElement) + KeyElement.init.__xdtor(); + + alias ValueElement = ElementType!Values; + static if (hasElaborateDestructor!ValueElement) + ValueElement.init.__xdtor(); + }))) + { + scope(failure) assert(false, "aaLiteral must not throw"); + } + if (values.length > keys.length) + values = values[0 .. keys.length]; + else if (keys.length > values.length) + keys = keys[0 .. values.length]; + aa = aaLiteral(keys, values); + } + alias Key = typeof(keys[0]); + alias Value = typeof(values[0]); + return (() @trusted => cast(Value[Key]) aa)(); + } + else + { + // zip is not always able to infer nothrow + alias Key = ElementType!Keys; + alias Value = ElementType!Values; + static assert(isMutable!Value, "assocArray: value type must be mutable"); + Value[Key] aa; + foreach (key; keys) + { + if (values.empty) break; + + // aa[key] is incorrectly not @safe if the destructor throws + // https://issues.dlang.org/show_bug.cgi?id=18592 + static if (is(typeof(() @safe + { + import std.range : ElementType; + import std.traits : hasElaborateDestructor; + alias KeyElement = ElementType!Keys; + static if (hasElaborateDestructor!KeyElement) + KeyElement.init.__xdtor(); + + alias ValueElement = ElementType!Values; + static if (hasElaborateDestructor!ValueElement) + ValueElement.init.__xdtor(); + }))) + { + () @trusted { + aa[key] = values.front; + }(); + } + else + { + aa[key] = values.front; + } + values.popFront(); + } + return aa; + } +} + /// @safe pure /*nothrow*/ unittest { - import std.range; - import std.typecons; + import std.range : repeat, zip; + import std.typecons : tuple; + import std.range.primitives : autodecodeStrings; auto a = assocArray(zip([0, 1, 2], ["a", "b", "c"])); // aka zipMap - assert(is(typeof(a) == string[int])); + static 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])); + static assert(is(typeof(b) == string[string])); assert(b == ["foo":"bar", "baz":"quux"]); + + static if (autodecodeStrings) + alias achar = dchar; + else + alias achar = immutable(char); + auto c = assocArray("ABCD", true.repeat); + static assert(is(typeof(c) == bool[achar])); + bool[achar] expected = ['D':true, 'A':true, 'B':true, 'C':true]; + assert(c == expected); } -// @@@11053@@@ - Cannot be version (unittest) - recursive instantiation error -@safe unittest +// Cannot be version (StdUnittest) - recursive instantiation error +// https://issues.dlang.org/show_bug.cgi?id=11053 +@safe pure nothrow unittest { import std.typecons; + static assert(!__traits(compiles, [ 1, 2, 3 ].assocArray())); 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 +// https://issues.dlang.org/show_bug.cgi?id=13909 +@safe pure nothrow unittest { import std.typecons; auto a = [tuple!(const string, string)("foo", "bar")]; auto b = [tuple!(string, const string)("foo", "bar")]; + assert(a == b); assert(assocArray(a) == [cast(const(string)) "foo": "bar"]); static assert(!__traits(compiles, assocArray(b))); } +// https://issues.dlang.org/show_bug.cgi?id=5502 +@safe pure nothrow unittest +{ + auto a = assocArray([0, 1, 2], ["a", "b", "c"]); + static assert(is(typeof(a) == string[int])); + assert(a == [0:"a", 1:"b", 2:"c"]); + + auto b = assocArray([0, 1, 2], [3L, 4, 5]); + static assert(is(typeof(b) == long[int])); + assert(b == [0: 3L, 1: 4, 2: 5]); +} + +// https://issues.dlang.org/show_bug.cgi?id=5502 +@safe pure unittest +{ + import std.algorithm.iteration : filter, map; + import std.range : enumerate; + import std.range.primitives : autodecodeStrings; + + auto r = "abcde".enumerate.filter!(a => a.index == 2); + auto a = assocArray(r.map!(a => a.value), r.map!(a => a.index)); + static if (autodecodeStrings) + alias achar = dchar; + else + alias achar = immutable(char); + static assert(is(typeof(a) == size_t[achar])); + assert(a == [achar('c'): size_t(2)]); +} + +@safe nothrow pure unittest +{ + import std.range : iota; + auto b = assocArray(3.iota, 3.iota(6)); + static assert(is(typeof(b) == int[int])); + assert(b == [0: 3, 1: 4, 2: 5]); + + b = assocArray([0, 1, 2], [3, 4, 5]); + assert(b == [0: 3, 1: 4, 2: 5]); +} + +@safe unittest +{ + struct ThrowingElement + { + int i; + static bool b; + ~this(){ + if (b) + throw new Exception(""); + } + } + static assert(!__traits(compiles, () nothrow { assocArray([ThrowingElement()], [0]);})); + assert(assocArray([ThrowingElement()], [0]) == [ThrowingElement(): 0]); + + static assert(!__traits(compiles, () nothrow { assocArray([0], [ThrowingElement()]);})); + assert(assocArray([0], [ThrowingElement()]) == [0: ThrowingElement()]); + + import std.range : iota; + static assert(!__traits(compiles, () nothrow { assocArray(1.iota, [ThrowingElement()]);})); + assert(assocArray(1.iota, [ThrowingElement()]) == [0: ThrowingElement()]); +} + +@system unittest +{ + import std.range : iota; + struct UnsafeElement + { + int i; + static bool b; + ~this(){ + int[] arr; + void* p = arr.ptr + 1; // unsafe + } + } + static assert(!__traits(compiles, () @safe { assocArray(1.iota, [UnsafeElement()]);})); + assert(assocArray(1.iota, [UnsafeElement()]) == [0: UnsafeElement()]); +} + /** Construct a range iterating over an associative array by key/value tuples. -Params: aa = The associative array to iterate over. +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. +Returns: A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +of Tuple's of key and value pairs from the given associative array. The members +of each pair can be accessed by name (`.key` and `.value`). or by integer +index (0 and 1 respectively). */ -auto byPair(AA : Value[Key], Value, Key)(AA aa) +auto byPair(AA)(AA aa) +if (isAssociativeArray!AA) { import std.algorithm.iteration : map; import std.typecons : tuple; - return aa.byKeyValue.map!(pair => tuple(pair.key, pair.value)); + return aa.byKeyValue + .map!(pair => tuple!("key", "value")(pair.key, pair.value)); } /// -@system unittest +@safe pure nothrow unittest { import std.algorithm.sorting : sort; import std.typecons : tuple, Tuple; @@ -447,27 +702,33 @@ auto byPair(AA : Value[Key], Value, Key)(AA aa) // Iteration over key/value pairs. foreach (pair; aa.byPair) { - pairs ~= pair; + if (pair.key == "b") + pairs ~= tuple("B", pair.value); + else + pairs ~= pair; } // Iteration order is implementation-dependent, so we should sort it to get // a fixed order. - sort(pairs); + pairs.sort(); assert(pairs == [ + tuple("B", 2), tuple("a", 1), - tuple("b", 2), tuple("c", 3) ]); } -@system unittest +@safe pure nothrow unittest { import std.typecons : tuple, Tuple; + import std.meta : AliasSeq; auto aa = ["a":2]; auto pairs = aa.byPair(); - static assert(is(typeof(pairs.front) == Tuple!(string,int))); + alias PT = typeof(pairs.front); + static assert(is(PT : Tuple!(string,int))); + static assert(PT.fieldNames == AliasSeq!("key", "value")); static assert(isForwardRange!(typeof(pairs))); assert(!pairs.empty); @@ -481,8 +742,8 @@ auto byPair(AA : Value[Key], Value, Key)(AA aa) assert(savedPairs.front == tuple("a", 2)); } -// Issue 17711 -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=17711 +@safe pure nothrow unittest { const(int[string]) aa = [ "abc": 123 ]; @@ -513,7 +774,8 @@ private template blockAttribute(T) enum blockAttribute = GC.BlkAttr.NO_SCAN; } } -version (unittest) + +@safe unittest { import core.memory : UGC = GC; static assert(!(blockAttribute!void & UGC.BlkAttr.NO_SCAN)); @@ -532,22 +794,28 @@ private template nDimensions(T) } } -version (unittest) +@safe 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). +Returns a new array of type `T` allocated on the garbage collected heap +without initializing its elements. This can be a useful optimization if every +element will be immediately initialized. `T` may be a multidimensional +array. In this case sizes may be specified for any number of dimensions from 0 +to the number in `T`. + +uninitializedArray is `nothrow` and weakly `pure`. -uninitializedArray is nothrow and weakly pure. +uninitializedArray is `@system` if the uninitialized element type has pointers. -uninitializedArray is @system if the uninitialized element type has pointers. +Params: + T = The type of the resulting array elements + sizes = The length dimension(s) of the resulting array +Returns: + An array of `T` with `I.length` dimensions. +/ auto uninitializedArray(T, I...)(I sizes) nothrow @system if (isDynamicArray!T && allSatisfy!(isIntegral, I) && hasIndirections!(ElementEncodingType!T)) @@ -596,13 +864,19 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I) && !hasIndirections!(ElementE } /++ -Returns a new array of type $(D T) allocated on the garbage collected heap. +Returns a new array of type `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). +necessarily the element type's `.init`. -minimallyInitializedArray is nothrow and weakly pure. +minimallyInitializedArray is `nothrow` and weakly `pure`. + +Params: + T = The type of the array elements + sizes = The length dimension(s) of the resulting array +Returns: + An array of `T` with `I.length` dimensions. +/ auto minimallyInitializedArray(T, I...)(I sizes) nothrow @trusted if (isDynamicArray!T && allSatisfy!(isIntegral, I)) @@ -627,8 +901,12 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I)) auto arr = minimallyInitializedArray!(int[])(42); assert(arr.length == 42); - // Elements aren't necessarily initialized to 0 - assert(!arr.equal(0.repeat(42))); + + // Elements aren't necessarily initialized to 0, so don't do this: + // assert(arr.equal(0.repeat(42))); + // If that is needed, initialize the array normally instead: + auto arr2 = new int[42]; + assert(arr2.equal(0.repeat(42))); } @safe pure nothrow unittest @@ -645,6 +923,9 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I)) } } +// from rt/lifetime.d +private extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow; + private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow { static assert(I.length <= nDimensions!T, @@ -656,7 +937,8 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow static if (I.length != 0) { - static assert(is(I[0] == size_t)); + static assert(is(I[0] == size_t), "I[0] must be of type size_t not " + ~ I[0].stringof); alias size = sizes[0]; } @@ -676,7 +958,7 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow ret ~= E.init; } catch (Exception e) - throw new Error(e.msg); + assert(0, e.msg); } else assert(0, "No postblit nor default init on " ~ E.stringof ~ @@ -684,18 +966,24 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow } 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); + /+ + NOTES: + _d_newarrayU is part of druntime, and creates an uninitialized + block, just like GC.malloc. However, it also sets the appropriate + bits, and sets up the block as an appendable array of type E[], + which will inform the GC how to destroy the items in the block + when it gets collected. + + _d_newarrayU returns a void[], but with the length set according + to E.sizeof. + +/ + *(cast(void[]*)&ret) = _d_newarrayU(typeid(E[]), size); static if (minimallyInitialized && hasIndirections!E) - memset(ptr, 0, nbytes); - ret = ptr[0 .. size]; + // _d_newarrayU would have asserted if the multiplication below + // had overflowed, so we don't have to check it again. + memset(ret.ptr, 0, E.sizeof * ret.length); } } else static if (I.length > 1) @@ -716,7 +1004,8 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow assert(s2.length == 0); } -@safe nothrow pure unittest //@@@9803@@@ +// https://issues.dlang.org/show_bug.cgi?id=9803 +@safe nothrow pure unittest { auto a = minimallyInitializedArray!(int*[])(1); assert(a[0] == null); @@ -726,7 +1015,8 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow assert(c[0][0] == null); } -@safe unittest //@@@10637@@@ +// https://issues.dlang.org/show_bug.cgi?id=10637 +@safe pure nothrow unittest { static struct S { @@ -743,15 +1033,24 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow } ~this() { - assert(p != null); + // note, this assert is invalid -- a struct should always be able + // to run its dtor on the .init value, I'm leaving it here + // commented out because the original test case had it. I'm not + // sure what it's trying to prove. + // + // What happens now that minimallyInitializedArray adds the + // destructor run to the GC, is that this assert would fire in the + // GC, which triggers an invalid memory operation. + //assert(p != null); } } auto a = minimallyInitializedArray!(S[])(1); assert(a[0].p == null); enum b = minimallyInitializedArray!(S[])(1); + assert(b[0].p == null); } -@safe nothrow unittest +@safe pure nothrow unittest { static struct S1 { @@ -759,42 +1058,63 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow this(this) @disable; } auto a1 = minimallyInitializedArray!(S1[][])(2, 2); - //enum b1 = minimallyInitializedArray!(S1[][])(2, 2); + assert(a1); static struct S2 { this() @disable; //this(this) @disable; } auto a2 = minimallyInitializedArray!(S2[][])(2, 2); + assert(a2); enum b2 = minimallyInitializedArray!(S2[][])(2, 2); + assert(b2); static struct S3 { //this() @disable; this(this) @disable; } auto a3 = minimallyInitializedArray!(S3[][])(2, 2); + assert(a3); enum b3 = minimallyInitializedArray!(S3[][])(2, 2); + assert(b3); } -// 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)) +/++ +Returns the overlapping portion, if any, of two arrays. Unlike `equal`, +`overlap` only compares the pointers and lengths in the +ranges, not the values referred by them. If `r1` and `r2` have an +overlapping slice, returns that slice. Otherwise, returns the null +slice. + +Params: + a = The first array to compare + b = The second array to compare +Returns: + The overlapping portion of the two arrays. ++/ +CommonType!(T[], U[]) overlap(T, U)(T[] a, U[] b) @trusted +if (is(typeof(a.ptr < b.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; + import std.algorithm.comparison : min; + + auto end = min(a.ptr + a.length, b.ptr + b.length); + // CTFE requires pairing pointer comparisons, which forces a + // slightly inefficient implementation. + if (a.ptr <= b.ptr && b.ptr < a.ptr + a.length) + { + return b.ptr[0 .. end - b.ptr]; + } + + if (b.ptr <= a.ptr && a.ptr < b.ptr + b.length) + { + return a.ptr[0 .. end - a.ptr]; + } + + return null; } /// -@safe pure /*nothrow*/ unittest +@safe pure nothrow unittest { int[] a = [ 10, 11, 12, 13, 14 ]; int[] b = a[1 .. 3]; @@ -802,15 +1122,22 @@ if (is(typeof(r1.ptr < r2.ptr) == bool)) b = b.dup; // overlap disappears even though the content is the same assert(overlap(a, b).empty); + + static test()() @nogc + { + auto a = "It's three o'clock"d; + auto b = a[5 .. 10]; + return b.overlap(a); + } + + //works at compile-time + static assert(test == "three"d); } -@safe /*nothrow*/ unittest +@safe pure 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]); @@ -829,10 +1156,11 @@ if (is(typeof(r1.ptr < r2.ptr) == bool)) test(a, b); assert(overlap(a, b.dup).empty); test(c, d); - assert(overlap(c, d.idup).empty); + assert(overlap(c, d.dup.idup).empty); } -@safe pure nothrow unittest // bugzilla 9836 + // https://issues.dlang.org/show_bug.cgi?id=9836 +@safe pure nothrow unittest { // range primitives for array should work with alias this types struct Wrapper @@ -854,8 +1182,10 @@ if (is(typeof(r1.ptr < r2.ptr) == bool)) private void copyBackwards(T)(T[] src, T[] dest) { import core.stdc.string : memmove; + import std.format : format; - assert(src.length == dest.length); + assert(src.length == dest.length, format! + "src.length %s must equal dest.length %s"(src.length, dest.length)); if (!__ctfe || hasElaborateCopyConstructor!T) { @@ -876,14 +1206,14 @@ private void copyBackwards(T)(T[] src, T[] dest) } /++ - Inserts $(D stuff) (which must be an input range or any number of - implicitly convertible items) in $(D array) at position $(D pos). + Inserts `stuff` (which must be an input range or any number of + implicitly convertible items) in `array` at position `pos`. Params: - array = The array that $(D stuff) will be inserted into. - pos = The position in $(D array) to insert the $(D stuff). + array = The array that `stuff` will be inserted into. + pos = The position in `array` to insert the `stuff`. stuff = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives), - or any number of implicitly convertible items to insert into $(D array). + or any number of implicitly convertible items to insert into `array`. +/ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) if (!isSomeString!(T[]) @@ -891,7 +1221,7 @@ if (!isSomeString!(T[]) { static if (allSatisfy!(isInputRangeWithLengthOrConvertible!T, U)) { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; immutable oldLen = array.length; @@ -950,7 +1280,7 @@ if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) static if (is(Unqual!T == T) && allSatisfy!(isInputRangeWithLengthOrConvertible!dchar, U)) { - import std.utf : codeLength; + import std.utf : codeLength, byDchar; // mutable, can do in place //helper function: re-encode dchar to Ts and store at *ptr static T* putDChar(T* ptr, dchar ch) @@ -994,7 +1324,8 @@ if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) @trusted static void moveToRight(T[] arr, size_t gap) { - static assert(!hasElaborateCopyConstructor!T); + static assert(!hasElaborateCopyConstructor!T, + "T must not have an elaborate copy constructor"); import core.stdc.string : memmove; if (__ctfe) { @@ -1014,7 +1345,7 @@ if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) } else { - foreach (dchar ch; stuff[i]) + foreach (ch; stuff[i].byDchar) ptr = putDChar(ptr, ch); } } @@ -1040,6 +1371,41 @@ if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) assert(a == [ 1, 2, 1, 2, 3, 4 ]); a.insertInPlace(3, 10u, 11); assert(a == [ 1, 2, 1, 10, 11, 2, 3, 4]); + + union U + { + float a = 3.0; + int b; + } + + U u1 = { b : 3 }; + U u2 = { b : 4 }; + U u3 = { b : 5 }; + U[] unionArr = [u2, u3]; + unionArr.insertInPlace(2, [u1]); + assert(unionArr == [u2, u3, u1]); + unionArr.insertInPlace(0, [u3, u2]); + assert(unionArr == [u3, u2, u2, u3, u1]); + + static class C + { + int a; + float b; + + this(int a, float b) { this.a = a; this.b = b; } + } + + C c1 = new C(42, 1.0); + C c2 = new C(0, 0.0); + C c3 = new C(int.max, float.init); + + C[] classArr = [c1, c2, c3]; + insertInPlace(classArr, 3, [c2, c3]); + C[5] classArr1 = classArr; + assert(classArr1 == [c1, c2, c3, c2, c3]); + insertInPlace(classArr, 0, c3, c1); + C[7] classArr2 = classArr; + assert(classArr2 == [c3, c1, c1, c2, c3, c2, c3]); } //constraint helpers @@ -1081,8 +1447,7 @@ private template isInputRangeOrConvertible(E) import std.exception; - bool test(T, U, V)(T orig, size_t pos, U toInsert, V result, - string file = __FILE__, size_t line = __LINE__) + bool test(T, U, V)(T orig, size_t pos, U toInsert, V result) { { static if (is(T == typeof(T.init.dup))) @@ -1127,10 +1492,10 @@ private template isInputRangeOrConvertible(E) new AssertError("testStr failure 3", file, line)); } - foreach (T; AliasSeq!(char, wchar, dchar, + static foreach (T; AliasSeq!(char, wchar, dchar, immutable(char), immutable(wchar), immutable(dchar))) { - foreach (U; AliasSeq!(char, wchar, dchar, + static foreach (U; AliasSeq!(char, wchar, dchar, immutable(char), immutable(wchar), immutable(dchar))) { testStr!(T[], U[])(); @@ -1198,18 +1563,6 @@ private template isInputRangeOrConvertible(E) 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 @@ -1224,7 +1577,8 @@ private template isInputRangeOrConvertible(E) }); } -@system unittest // bugzilla 6874 +// https://issues.dlang.org/show_bug.cgi?id=6874 +@system unittest { import core.memory; // allocate some space @@ -1244,9 +1598,15 @@ private template isInputRangeOrConvertible(E) /++ - Returns whether the $(D front)s of $(D lhs) and $(D rhs) both refer to the + Returns whether the `front`s of `lhs` and `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). + starts at index `0`. + + Params: + lhs = the first array to compare + rhs = the second array to compare + Returns: + `true` if $(D lhs.ptr == rhs.ptr), `false` otherwise. +/ @safe pure nothrow bool sameHead(T)(in T[] lhs, in T[] rhs) @@ -1265,9 +1625,16 @@ pure nothrow bool sameHead(T)(in T[] lhs, in T[] rhs) /++ - Returns whether the $(D back)s of $(D lhs) and $(D rhs) both refer to the + Returns whether the `back`s of `lhs` and `rhs` both refer to the same place in memory, making one of the arrays a slice of the other which - end at index $(D $). + end at index `$`. + + Params: + lhs = the first array to compare + rhs = the second array to compare + Returns: + `true` if both arrays are the same length and $(D lhs.ptr == rhs.ptr), + `false` otherwise. +/ @trusted pure nothrow bool sameTail(T)(in T[] lhs, in T[] rhs) @@ -1286,8 +1653,8 @@ pure nothrow bool sameTail(T)(in T[] lhs, in T[] rhs) @safe pure nothrow unittest { - foreach (T; AliasSeq!(int[], const(int)[], immutable(int)[], const int[], immutable int[])) - { + static 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 .. $]; @@ -1309,7 +1676,7 @@ pure nothrow bool sameTail(T)(in T[] lhs, in T[] rhs) //verifies R-value compatibilty assert(a.sameHead(a[0 .. 0])); assert(a.sameTail(a[$ .. $])); - } + }} } /** @@ -1380,9 +1747,8 @@ if (isInputRange!S && !isDynamicArray!S) { import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) - { - S s; + static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + {{ immutable S t = "abc"; assert(replicate(to!S("1234"), 0) is null); @@ -1392,24 +1758,31 @@ if (isInputRange!S && !isDynamicArray!S) 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). +Eagerly splits `range` into an array, using `sep` as the delimiter. + +When no delimiter is provided, strings are split 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. +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 `range` +or it can be another forward `range`. Params: - s = the string to split + s = the string to split by word if no separator is given + range = the range to split + sep = a value of the same type as the elements of `range` or another + isTerminator = a predicate that splits the range when it returns `true`. Returns: - An array of each word in `s` + An array containing the divided parts of `range` (or the words of `s`). See_Also: -$(REF splitter, std,algorithm,iteration) for a version that splits using any -separator. +$(REF splitter, std,algorithm,iteration) for a lazy version without allocating memory. $(REF splitter, std,regex) for a version that splits using a regular expression defined separator. @@ -1419,7 +1792,7 @@ if (isSomeString!S) { size_t istart; bool inword = false; - S[] result; + auto result = appender!(S[]); foreach (i, dchar c ; s) { @@ -1428,7 +1801,7 @@ if (isSomeString!S) { if (inword) { - result ~= s[istart .. i]; + put(result, s[istart .. i]); inword = false; } } @@ -1442,45 +1815,40 @@ if (isSomeString!S) } } if (inword) - result ~= s[istart .. $]; - return result; + put(result, s[istart .. $]); + return result.data; } /// @safe unittest { - string str = "Hello World!"; - assert(str.split == ["Hello", "World!"]); - - string str2 = "Hello\t\tWorld\t!"; - assert(str2.split == ["Hello", "World", "!"]); + 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"]); } -/** - * `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; - import std.algorithm.iteration : splitter; - string str = "Hello World!"; - assert(str.splitter!(isWhite).equal(["Hello", "World!"])); + assert(str.split == ["Hello", "World!"]); + + string str2 = "Hello\t\tWorld\t!"; + assert(str2.split == ["Hello", "World", "!"]); } @safe unittest { import std.conv : to; - import std.format; + import std.format : 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,)) - { + static foreach (S; AliasSeq!(string, wstring, dstring,)) + {{ auto entries = [ makeEntry!S("", []), @@ -1495,7 +1863,7 @@ if (isSomeString!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"; @@ -1524,43 +1892,12 @@ if (isSomeString!S) assert(a == [[1], [4, 5, 1], [4, 5]]); } -/++ - 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))) +if (isForwardRange!Range && ( + is(typeof(ElementType!Range.init == Separator.init)) || + is(typeof(ElementType!Range.init == ElementType!Separator.init)) && isForwardRange!Separator + )) { import std.algorithm.iteration : splitter; return range.splitter(sep).array; @@ -1573,26 +1910,17 @@ if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(range.front)))) 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, + static 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, ","); @@ -1632,14 +1960,14 @@ if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(range.front)))) 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 + Used by `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). + length could be more expensive than using `Appender`. For now, we only assume arrays are cheap to iterate. +/ @@ -1660,11 +1988,13 @@ private enum bool hasCheapIteration(R) = isArray!R; See_Also: For a lazy version, see $(REF joiner, std,algorithm,iteration) +/ -ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, scope R sep) +ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, R sep) if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && isInputRange!R && - is(Unqual!(ElementType!(ElementType!RoR)) == Unqual!(ElementType!R))) + (is(immutable ElementType!(ElementType!RoR) == immutable ElementType!R) || + (isSomeChar!(ElementType!(ElementType!RoR)) && isSomeChar!(ElementType!R)) + )) { alias RetType = typeof(return); alias RetTypeElement = Unqual!(ElementEncodingType!RetType); @@ -1677,7 +2007,7 @@ if (isInputRange!RoR && // 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))) + !is(immutable ElementEncodingType!RetType == immutable ElementEncodingType!R)) { import std.conv : to; auto sepArr = to!RetType(sep); @@ -1689,7 +2019,7 @@ if (isInputRange!RoR && static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; size_t length; // length of result array size_t rorLength; // length of range ror foreach (r; ror.save) @@ -1723,24 +2053,50 @@ if (isInputRange!RoR && ror.popFront(); for (; !ror.empty; ror.popFront()) { - put(result, sep); + put(result, sepArr); put(result, ror.front); } return result.data; } } -@safe unittest // Issue 14230 +// https://issues.dlang.org/show_bug.cgi?id=14230 +@safe unittest { string[] ary = ["","aa","bb","cc"]; // leaded by _empty_ element assert(ary.join(" @") == " @aa @bb @cc"); // OK in 2.067b1 and olders } +// https://issues.dlang.org/show_bug.cgi?id=21337 +@system unittest +{ + import std.algorithm.iteration : map; + + static class Once + { + bool empty; + + void popFront() + { + empty = true; + } + + int front() + { + return 0; + } + } + + assert([1, 2].map!"[a]".join(new Once) == [1, 0, 2]); +} + /// Ditto ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, scope E sep) if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && - is(E : ElementType!(ElementType!RoR))) + ((is(E : ElementType!(ElementType!RoR))) || + (!autodecodeStrings && isSomeChar!(ElementType!(ElementType!RoR)) && + isSomeChar!E))) { alias RetType = typeof(return); alias RetTypeElement = Unqual!(ElementEncodingType!RetType); @@ -1760,7 +2116,8 @@ if (isInputRange!RoR && } else { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; + import std.format : format; size_t length; size_t rorLength; foreach (r; ror.save) @@ -1784,7 +2141,8 @@ if (isInputRange!RoR && foreach (e; r) emplaceRef(result[len++], e); } - assert(len == result.length); + assert(len == result.length, format! + "len %s must equal result.lenght %s"(len, result.length)); return (() @trusted => cast(RetType) result)(); } } @@ -1802,7 +2160,8 @@ if (isInputRange!RoR && } } -@safe unittest // Issue 10895 +// https://issues.dlang.org/show_bug.cgi?id=10895 +@safe unittest { class A { @@ -1814,9 +2173,11 @@ if (isInputRange!RoR && assert(a[0].length == 3); auto temp = join(a, " "); assert(a[0].length == 3); + assert(temp.length == 3); } -@safe unittest // Issue 14230 +// https://issues.dlang.org/show_bug.cgi?id=14230 +@safe unittest { string[] ary = ["","aa","bb","cc"]; assert(ary.join('@') == "@aa@bb@cc"); @@ -1828,7 +2189,15 @@ if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR))) { alias RetType = typeof(return); - alias RetTypeElement = Unqual!(ElementEncodingType!RetType); + alias ConstRetTypeElement = ElementEncodingType!RetType; + static if (isAssignable!(Unqual!ConstRetTypeElement, ConstRetTypeElement)) + { + alias RetTypeElement = Unqual!ConstRetTypeElement; + } + else + { + alias RetTypeElement = ConstRetTypeElement; + } alias RoRElem = ElementType!RoR; if (ror.empty) @@ -1836,7 +2205,7 @@ if (isInputRange!RoR && static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; size_t length; foreach (r; ror.save) length += r.length; @@ -1845,8 +2214,9 @@ if (isInputRange!RoR && size_t len; foreach (r; ror) foreach (e; r) - emplaceRef(result[len++], e); - assert(len == result.length); + emplaceRef!RetTypeElement(result[len++], e); + assert(len == result.length, + "emplaced an unexpected number of elements"); return (() @trusted => cast(RetType) result)(); } else @@ -1875,50 +2245,61 @@ if (isInputRange!RoR && @safe pure unittest { import std.conv : to; + import std.range.primitives : autodecodeStrings; - foreach (T; AliasSeq!(string,wstring,dstring)) - { + static 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)) - { + static 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)) - { + }} + static 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)) - { + static foreach (T; AliasSeq!(string,wstring,dstring)) + {{ auto arr2 = "Здравствуй\u047CМир\u047CUnicode".to!(T); auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]); - foreach (S; AliasSeq!(wchar,dchar)) - { + static 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 +@safe unittest +{ + class A { } + + const A[][] array; + auto result = array.join; // can't remove constness, so don't try + + static assert(is(typeof(result) == const(A)[])); +} + +@safe unittest { import std.algorithm; import std.conv : to; import std.range; - foreach (R; AliasSeq!(string, wstring, dstring)) - { + static foreach (R; AliasSeq!(string, wstring, dstring)) + {{ R word1 = "日本語"; R word2 = "paul"; R word3 = "jerry"; @@ -1934,8 +2315,8 @@ if (isInputRange!RoR && auto filteredLenWordsArr = [filteredLenWord1, filteredLenWord2, filteredLenWord3]; auto filteredWords = filter!"true"(filteredWordsArr); - foreach (S; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ assert(join(filteredWords, to!S(", ")) == "日本語, paul, jerry"); assert(join(filteredWords, to!(ElementType!S)(',')) == "日本語,paul,jerry"); assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry"); @@ -1969,7 +2350,7 @@ if (isInputRange!RoR && 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"); @@ -1995,7 +2376,7 @@ if (isInputRange!RoR && 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]); @@ -2016,7 +2397,7 @@ if (isInputRange!RoR && assert(join(f([f([1, 2]), f([41, 42])]), f([5, 6])) == [1, 2, 5, 6, 41, 42]); } -// Issue 10683 +// https://issues.dlang.org/show_bug.cgi?id=10683 @safe unittest { import std.range : join; @@ -2025,7 +2406,7 @@ if (isInputRange!RoR && assert([[tuple("x")]].join == [tuple("x")]); } -// Issue 13877 +// https://issues.dlang.org/show_bug.cgi?id=13877 @safe unittest { // Test that the range is iterated only once. @@ -2048,40 +2429,49 @@ if (isInputRange!RoR && /++ - Replace occurrences of `from` with `to` in `subject` in a new - array. If `sink` is defined, then output the new array into - `sink`. + Replace occurrences of `from` with `to` in `subject` in a new array. 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. + 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 + $(REF substitute, std,algorithm,iteration) for 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)) +if ((isForwardRange!R1 && isForwardRange!R2 && (hasLength!R2 || isSomeString!R2)) || + is(Unqual!E : Unqual!R1)) { import std.algorithm.searching : find; + import std.range : dropOne; - if (from.empty) return subject; + static if (isInputRange!R1) + { + if (from.empty) return subject; + alias rSave = a => a.save; + } + else + { + alias rSave = a => a; + } - auto balance = find(subject, from.save); + auto balance = find(subject, rSave(from)); 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); + app.put(rSave(to)); + // replacing an element in an array is different to a range replacement + static if (is(Unqual!E : Unqual!R1)) + replaceInto(app, balance.dropOne, from, to); + else + replaceInto(app, balance[from.length .. $], from, to); return app.data; } @@ -2093,30 +2483,98 @@ if (isDynamicArray!(E[]) && isForwardRange!R1 && isForwardRange!R2 assert("Hello Wörld".replace("l", "h") == "Hehho Wörhd"); } -/// ditto +@safe unittest +{ + assert([1, 2, 3, 4, 2].replace([2], [5]) == [1, 5, 3, 4, 5]); + assert([3, 3, 3].replace([3], [0]) == [0, 0, 0]); + assert([3, 3, 4, 3].replace([3, 3], [1, 1, 1]) == [1, 1, 1, 4, 3]); +} + +// https://issues.dlang.org/show_bug.cgi?id=18215 +@safe unittest +{ + auto arr = ["aaa.dd", "b"]; + arr = arr.replace("aaa.dd", "."); + assert(arr == [".", "b"]); + + arr = ["_", "_", "aaa.dd", "b", "c", "aaa.dd", "e"]; + arr = arr.replace("aaa.dd", "."); + assert(arr == ["_", "_", ".", "b", "c", ".", "e"]); +} + +// https://issues.dlang.org/show_bug.cgi?id=18215 +@safe unittest +{ + assert([[0], [1, 2], [0], [3]].replace([0], [4]) == [[4], [1, 2], [4], [3]]); + assert([[0], [1, 2], [0], [3], [1, 2]] + .replace([1, 2], [0]) == [[0], [0], [0], [3], [0]]); + assert([[0], [1, 2], [0], [3], [1, 2], [0], [1, 2]] + .replace([[0], [1, 2]], [[4]]) == [[4], [0], [3], [1, 2], [4]]); +} + +// https://issues.dlang.org/show_bug.cgi?id=10930 +@safe unittest +{ + assert([0, 1, 2].replace(1, 4) == [0, 4, 2]); + assert("äbö".replace('ä', 'a') == "abö"); +} + +// empty array +@safe unittest +{ + int[] arr; + assert(replace(arr, 1, 2) == []); +} + +/++ + Replace occurrences of `from` with `to` in `subject` and output the result 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 + + See_Also: + $(REF substitute, std,algorithm,iteration) for a lazy replace. + +/ 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)) +if (isOutputRange!(Sink, E) && + ((isForwardRange!R1 && isForwardRange!R2 && (hasLength!R2 || isSomeString!R2)) || + is(Unqual!E : Unqual!R1))) { import std.algorithm.searching : find; + import std.range : dropOne; - if (from.empty) + static if (isInputRange!R1) { - sink.put(subject); - return; + if (from.empty) + { + sink.put(subject); + return; + } + alias rSave = a => a.save; + } + else + { + alias rSave = a => a; } for (;;) { - auto balance = find(subject, from.save); + auto balance = find(subject, rSave(from)); if (balance.empty) { sink.put(subject); break; } sink.put(subject[0 .. subject.length - balance.length]); - sink.put(to.save); - subject = balance[from.length .. $]; + sink.put(rSave(to)); + // replacing an element in an array is different to a range replacement + static if (is(Unqual!E : Unqual!R1)) + subject = balance.dropOne; + else + subject = balance[from.length .. $]; } } @@ -2133,15 +2591,24 @@ if (isOutputRange!(Sink, E) && isDynamicArray!(E[]) assert(sink.data == [1, 4, 6, 4, 5]); } +// empty array +@safe unittest +{ + auto sink = appender!(int[])(); + int[] arr; + replaceInto(sink, arr, 1, 2); + assert(sink.data == []); +} + @safe unittest { import std.algorithm.comparison : cmp; import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + static 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 + static foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + {{ auto s = to!S("This is a foo foo list"); auto from = to!T("foo"); auto into = to!S("silly"); @@ -2157,7 +2624,7 @@ if (isOutputRange!(Sink, E) && isDynamicArray!(E[]) 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"; @@ -2175,15 +2642,27 @@ if (isOutputRange!(Sink, E) && isDynamicArray!(E[]) this(C[] arr){ desired = arr; } void put(C[] part){ assert(skipOver(desired, part)); } } - foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) - { + static 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); - } + }} +} + +// https://issues.dlang.org/show_bug.cgi?id=10930 +@safe unittest +{ + auto sink = appender!(int[])(); + replaceInto(sink, [0, 1, 2], 1, 5); + assert(sink.data == [0, 5, 2]); + + auto sink2 = appender!(dchar[])(); + replaceInto(sink2, "äbö", 'ä', 'a'); + assert(sink2.data == "abö"); } /++ @@ -2198,6 +2677,9 @@ if (isOutputRange!(Sink, E) && isDynamicArray!(E[]) Returns: A new array without changing the contents of `subject`. + + See_Also: + $(REF substitute, std,algorithm,iteration) for a lazy replace. +/ T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff) if (isInputRange!Range && @@ -2207,7 +2689,7 @@ if (isInputRange!Range && static if (hasLength!Range && is(ElementEncodingType!Range : T)) { import std.algorithm.mutation : copy; - assert(from <= to); + assert(from <= to, "from must be before or equal to to"); immutable sliceLen = to - from; auto retval = new Unqual!(T)[](subject.length - sliceLen + stuff.length); retval[0 .. from] = subject[0 .. from]; @@ -2216,7 +2698,14 @@ if (isInputRange!Range && copy(stuff, retval[from .. from + stuff.length]); retval[from + stuff.length .. $] = subject[to .. $]; - return cast(T[]) retval; + static if (is(T == const) || is(T == immutable)) + { + return () @trusted { return cast(T[]) retval; } (); + } + else + { + return cast(T[]) retval; + } } else { @@ -2310,13 +2799,20 @@ if (isInputRange!Range && assert(replace(d, 5, 10, "⁴³²¹⁰"d) == "⁰¹²³⁴⁴³²¹⁰"d); } +// https://issues.dlang.org/show_bug.cgi?id=18166 +@safe pure unittest +{ + auto str = replace("aaaaa"d, 1, 4, "***"d); + assert(str == "a***a"); +} + /++ 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 + array = the array to scan from = the starting index to = the ending index stuff = the items to replace in-between `from` and `to` @@ -2373,9 +2869,9 @@ if (is(typeof(replace(array, from, to, stuff)))) assert(a == [1, 4, 5, 5]); } +// https://issues.dlang.org/show_bug.cgi?id=12889 @safe unittest { - // Bug# 12889 int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]]; int[1][] stuff = [[0], [1]]; replaceInPlace(arr, 4, 6, stuff); @@ -2384,7 +2880,7 @@ if (is(typeof(replace(array, from, to, stuff)))) @system unittest { - // Bug# 14925 + // https://issues.dlang.org/show_bug.cgi?id=14925 char[] a = "mon texte 1".dup; char[] b = "abc".dup; replaceInPlace(a, 4, 9, b); @@ -2451,8 +2947,7 @@ if (is(typeof(replace(array, from, to, stuff)))) 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__) + bool test(T, U, V)(T orig, size_t from, size_t to, U toReplace, V result) { { static if (is(T == typeof(T.init.dup))) @@ -2524,7 +3019,7 @@ if (is(typeof(replace(array, from, to, stuff)))) to = the item to replace `from` with Returns: - A new array without changing the contents of $(D subject), or the original + A new array without changing the contents of `subject`, or the original array if no match is found. +/ E[] replaceFirst(E, R1, R2)(E[] subject, R1 from, R2 to) @@ -2580,12 +3075,12 @@ if (isDynamicArray!(E[]) && import std.algorithm.comparison : cmp; import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], const(char[]), immutable(char[]))) { - foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + static 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"); @@ -2607,11 +3102,11 @@ if (isDynamicArray!(E[]) && 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 +// https://issues.dlang.org/show_bug.cgi?id=8187 @safe unittest { auto res = ["a", "a"]; @@ -2628,7 +3123,7 @@ if (isDynamicArray!(E[]) && to = the item to replace `from` with Returns: - A new array without changing the contents of $(D subject), or the original + A new array without changing the contents of `subject`, or the original array if no match is found. +/ E[] replaceLast(E, R1, R2)(E[] subject, R1 from , R2 to) @@ -2693,12 +3188,12 @@ if (isDynamicArray!(E[]) && import std.algorithm.comparison : cmp; import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], const(char[]), immutable(char[]))) { - foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + static 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"); @@ -2720,7 +3215,7 @@ if (isDynamicArray!(E[]) && assert(cmp(r3, "This is a foo foo list") == 0); assert(replaceLast(r3, to!T("won't find"), to!T("whatever")) is r3); - }(); + }} } } @@ -2737,27 +3232,32 @@ if (isDynamicArray!(E[]) && Returns: A new array that is `s` with `slice` replaced by `replacement[]`. + + See_Also: + $(REF substitute, std,algorithm,iteration) for a lazy replace. +/ 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); + assert(overlap(s, slice) is slice, "slice[] is not a subslice of s[]"); } -body +do { auto result = new T[s.length - slice.length + replacement.length]; - immutable so = slice.ptr - s.ptr; + immutable so = &slice[0] - &s[0]; 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; + return () @trusted inout { + return cast(inout(T)[]) result; + }(); } /// -@system unittest +@safe unittest { auto a = [1, 2, 3, 4, 5]; auto b = replaceSlice(a, a[1 .. 4], [0, 0, 0]); @@ -2765,7 +3265,7 @@ body assert(b == [1, 0, 0, 0, 5]); } -@system unittest +@safe unittest { import std.algorithm.comparison : cmp; @@ -2783,6 +3283,10 @@ 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. + +Params: + A = the array type to simulate. + See_Also: $(LREF appender) */ struct Appender(A) @@ -2796,7 +3300,7 @@ if (isDynamicArray!A) { size_t capacity; Unqual!T[] arr; - bool canExtend = false; + bool tryExtendBlock = false; } private Data* _data; @@ -2807,7 +3311,7 @@ if (isDynamicArray!A) * 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 + this(A arr) @trusted { // initialize to a given array. _data = new Data; @@ -2834,8 +3338,11 @@ if (isDynamicArray!A) * Reserve at least newCapacity elements for appending. Note that more elements * may be reserved than requested. If `newCapacity <= capacity`, then nothing is * done. + * + * Params: + * newCapacity = the capacity the `Appender` should have */ - void reserve(size_t newCapacity) @safe pure nothrow + void reserve(size_t newCapacity) { if (_data) { @@ -2853,7 +3360,7 @@ if (isDynamicArray!A) * 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 + @property size_t capacity() const { return _data ? _data.capacity : 0; } @@ -2862,7 +3369,7 @@ if (isDynamicArray!A) * Use opSlice() from now on. * Returns: The managed array. */ - @property inout(ElementEncodingType!A)[] data() inout @trusted pure nothrow + @property inout(T)[] data() inout @trusted { return this[]; } @@ -2870,7 +3377,7 @@ if (isDynamicArray!A) /** * Returns: The managed array. */ - @property inout(ElementEncodingType!A)[] opSlice() inout @trusted pure nothrow + @property inout(T)[] opSlice() inout @trusted { /* @trusted operation: * casting Unqual!T[] to inout(T)[] @@ -2879,7 +3386,7 @@ if (isDynamicArray!A) } // ensure we can add nelems elements, resizing as necessary - private void ensureAddable(size_t nelems) @trusted pure nothrow + private void ensureAddable(size_t nelems) { if (!_data) _data = new Data; @@ -2913,9 +3420,9 @@ if (isDynamicArray!A) // have better access to the capacity field. auto newlen = appenderNewCapacity!(T.sizeof)(_data.capacity, reqlen); // first, try extending the current block - if (_data.canExtend) + if (_data.tryExtendBlock) { - immutable u = GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof); + immutable u = (() @trusted => GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))(); if (u) { // extend worked, update the capacity @@ -2929,15 +3436,16 @@ if (isDynamicArray!A) import core.checkedint : mulu; bool overflow; const nbytes = mulu(newlen, T.sizeof, overflow); - if (overflow) assert(0); + if (overflow) assert(false, "the reallocation would exceed the " + ~ "available pointer range"); - auto bi = GC.qalloc(nbytes, blockAttribute!T); + auto bi = (() @trusted => 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; + () @trusted { memcpy(bi.base, _data.arr.ptr, len * T.sizeof); }(); + _data.arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])(); + _data.tryExtendBlock = true; // leave the old data, for safety reasons } } @@ -2945,7 +3453,7 @@ if (isDynamicArray!A) private template canPutItem(U) { enum bool canPutItem = - isImplicitlyConvertible!(U, T) || + isImplicitlyConvertible!(Unqual!U, Unqual!T) || isSomeChar!T && isSomeChar!U; } private template canPutConstRange(Range) @@ -2963,7 +3471,11 @@ if (isDynamicArray!A) } /** - * Appends `item` to the managed array. + * Appends `item` to the managed array. Performs encoding for + * `char` types if `A` is a differently typed `char` array. + * + * Params: + * item = the single item to append */ void put(U)(U item) if (canPutItem!U) { @@ -2980,13 +3492,13 @@ if (isDynamicArray!A) } else { - import std.conv : emplaceRef; + import core.internal.lifetime : 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); + emplaceRef!(Unqual!T)(bigData[len], cast() item); //We do this at the end, in case of exceptions _data.arr = bigData; } @@ -3000,7 +3512,11 @@ if (isDynamicArray!A) } /** - * Appends an entire range to the managed array. + * Appends an entire range to the managed array. Performs encoding for + * `char` elements if `A` is a differently typed `char` array. + * + * Params: + * items = the range of items to append */ void put(Range)(Range items) if (canPutRange!Range) { @@ -3023,10 +3539,10 @@ if (isDynamicArray!A) } // make sure we have enough space, then add the items - @trusted auto bigDataFun(size_t extra) + auto bigDataFun(size_t extra) { ensureAddable(extra); - return _data.arr.ptr[0 .. _data.arr.length + extra]; + return (() @trusted => _data.arr.ptr[0 .. _data.arr.length + extra])(); } auto bigData = bigDataFun(items.length); @@ -3042,7 +3558,7 @@ if (isDynamicArray!A) } else { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; foreach (ref it ; bigData[len .. newlen]) { emplaceRef!T(it, items.front); @@ -3053,6 +3569,17 @@ if (isDynamicArray!A) //We do this at the end, in case of exceptions _data.arr = bigData; } + else static if (isSomeChar!T && isSomeChar!(ElementType!Range) && + !is(immutable T == immutable ElementType!Range)) + { + // need to decode and encode + import std.utf : decodeFront; + while (!items.empty) + { + auto c = items.decodeFront; + put(c); + } + } else { //pragma(msg, Range.stringof); @@ -3065,15 +3592,11 @@ if (isDynamicArray!A) } /** - * Appends `rhs` to the managed array. - * Params: - * rhs = Element or range. + * Appends to the managed array. + * + * See_Also: $(LREF Appender.put) */ - void opOpAssign(string op : "~", U)(U rhs) - if (__traits(compiles, put(rhs))) - { - put(rhs); - } + alias opOpAssign(string op : "~") = put; // only allow overwriting data on non-immutable and non-const data static if (isMutable!T) @@ -3083,7 +3606,7 @@ if (isDynamicArray!A) * for appending. * * Note: clear is disabled for immutable or const element types, due to the - * possibility that $(D Appender) might overwrite immutable data. + * possibility that `Appender` might overwrite immutable data. */ void clear() @trusted pure nothrow { @@ -3096,7 +3619,7 @@ if (isDynamicArray!A) /** * Shrinks the managed array to the given length. * - * Throws: $(D Exception) if newlength is greater than the current array length. + * Throws: `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 @@ -3112,15 +3635,60 @@ if (isDynamicArray!A) } } - void toString(Writer)(scope Writer w) + /** + * Gives a string in the form of `Appender!(A)(data)`. + * + * Params: + * w = A `char` accepting + * $(REF_ALTTEXT output range, isOutputRange, std, range, primitives). + * fmt = A $(REF FormatSpec, std, format) which controls how the array + * is formatted. + * Returns: + * A `string` if `writer` is not set; `void` otherwise. + */ + string toString()() const { - import std.format : formattedWrite; - w.formattedWrite(typeof(this).stringof ~ "(%s)", data); + import std.format.spec : singleSpec; + + auto app = appender!string(); + auto spec = singleSpec("%s"); + immutable len = _data ? _data.arr.length : 0; + // different reserve lengths because each element in a + // non-string-like array uses two extra characters for `, `. + static if (isSomeString!A) + { + app.reserve(len + 25); + } + else + { + // Multiplying by three is a very conservative estimate of + // length, as it assumes each element is only one char + app.reserve((len * 3) + 25); + } + toString(app, spec); + return app.data; + } + + import std.format.spec : FormatSpec; + + /// ditto + template toString(Writer) + if (isOutputRange!(Writer, char)) + { + void toString(ref Writer w, scope const ref FormatSpec!char fmt) const + { + import std.format.write : formatValue; + import std.range.primitives : put; + put(w, Unqual!(typeof(this)).stringof); + put(w, '('); + formatValue(w, data, fmt); + put(w, ')'); + } } } /// -@safe unittest +@safe pure nothrow unittest { auto app = appender!string(); string b = "abcdefg"; @@ -3135,17 +3703,30 @@ if (isDynamicArray!A) assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); } -@safe unittest +@safe pure unittest { import std.format : format; + import std.format.spec : singleSpec; + auto app = appender!(int[])(); app.put(1); app.put(2); app.put(3); assert("%s".format(app) == "Appender!(int[])(%s)".format([1,2,3])); + + auto app2 = appender!string(); + auto spec = singleSpec("%s"); + app.toString(app2, spec); + assert(app2[] == "Appender!(int[])([1, 2, 3])"); + + auto app3 = appender!string(); + spec = singleSpec("%(%04d, %)"); + app.toString(app3, spec); + assert(app3[] == "Appender!(int[])(0001, 0002, 0003)"); } -@safe unittest // issue 17251 +// https://issues.dlang.org/show_bug.cgi?id=17251 +@safe pure nothrow unittest { static struct R { @@ -3160,12 +3741,89 @@ if (isDynamicArray!A) app.put(r[]); } +// https://issues.dlang.org/show_bug.cgi?id=13300 +@safe pure nothrow unittest +{ + static test(bool isPurePostblit)() + { + static if (!isPurePostblit) + static int i; + + struct Simple + { + @disable this(); // Without this, it works. + static if (!isPurePostblit) + this(this) { i++; } + else + pure this(this) { } + + private: + this(int tmp) { } + } + + struct Range + { + @property Simple front() { return Simple(0); } + void popFront() { count++; } + @property empty() { return count < 3; } + size_t count; + } + + Range r; + auto a = r.array(); + } + + static assert(__traits(compiles, () pure { test!true(); })); + static assert(!__traits(compiles, () pure { test!false(); })); +} + +// https://issues.dlang.org/show_bug.cgi?id=19572 +@safe pure nothrow unittest +{ + static struct Struct + { + int value; + + int fun() const { return 23; } + + alias fun this; + } + + Appender!(Struct[]) appender; + + appender.put(const(Struct)(42)); + + auto result = appender[][0]; + + assert(result.value != 23); +} + +@safe pure unittest +{ + import std.conv : to; + import std.utf : byCodeUnit; + auto str = "ウェブサイト"; + auto wstr = appender!wstring(); + put(wstr, str.byCodeUnit); + assert(wstr.data == str.to!wstring); +} + +// https://issues.dlang.org/show_bug.cgi?id=21256 +@safe pure unittest +{ + Appender!string app1; + app1.toString(); + + Appender!(int[]) app2; + app2.toString(); +} + //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 +private size_t appenderNewCapacity(size_t TSizeOf)(size_t curLen, size_t reqLen) { import core.bitop : bsr; import std.algorithm.comparison : max; @@ -3186,10 +3844,15 @@ private size_t appenderNewCapacity(size_t TSizeOf)(size_t curLen, size_t reqLen) * original array passed in. * * Tip: Use the `arrayPtr` overload of $(LREF appender) for construction with type-inference. + * + * Params: + * A = The array type to simulate */ struct RefAppender(A) if (isDynamicArray!A) { + private alias T = ElementEncodingType!A; + private { Appender!A impl; @@ -3242,7 +3905,7 @@ if (isDynamicArray!A) /** * 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). + * appending will reallocate, `capacity` returns `0`. */ @property size_t capacity() const { @@ -3252,7 +3915,7 @@ if (isDynamicArray!A) /* Use opSlice() instead. * Returns: the managed array. */ - @property inout(ElementEncodingType!A)[] data() inout + @property inout(T)[] data() inout { return impl[]; } @@ -3267,7 +3930,7 @@ if (isDynamicArray!A) } /// -@system pure nothrow +@safe pure nothrow unittest { int[] a = [1, 2]; @@ -3285,7 +3948,7 @@ unittest /++ Convenience function that returns an $(LREF Appender) instance, - optionally initialized with $(D array). + optionally initialized with `array`. +/ Appender!A appender(A)() if (isDynamicArray!A) @@ -3303,63 +3966,49 @@ Appender!(E[]) appender(A : E[], E)(auto ref A array) @safe pure nothrow unittest { - import std.exception; - { - auto app = appender!(char[])(); - string b = "abcdefg"; - foreach (char c; b) app.put(c); - assert(app[] == "abcdefg"); - } - { - auto app = appender!(char[])(); - string b = "abcdefg"; - foreach (char c; b) app ~= c; - assert(app[] == "abcdefg"); - } - { - int[] a = [ 1, 2 ]; - auto app2 = appender(a); - assert(app2[] == [ 1, 2 ]); - app2.put(3); - app2.put([ 4, 5, 6 ][]); - assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); - app2.put([ 7 ]); - assert(app2[] == [ 1, 2, 3, 4, 5, 6, 7 ]); - } + auto app = appender!(char[])(); + string b = "abcdefg"; + foreach (char c; b) app.put(c); + assert(app[] == "abcdefg"); +} + +@safe pure nothrow unittest +{ + auto app = appender!(char[])(); + string b = "abcdefg"; + foreach (char c; b) app ~= c; + assert(app[] == "abcdefg"); +} +@safe pure nothrow unittest +{ int[] a = [ 1, 2 ]; auto app2 = appender(a); assert(app2[] == [ 1, 2 ]); - app2 ~= 3; - app2 ~= [ 4, 5, 6 ][]; + app2.put(3); + app2.put([ 4, 5, 6 ][]); assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); - app2 ~= [ 7 ]; + app2.put([ 7 ]); assert(app2[] == [ 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[] == [ 1, 2, 3 ]); - assertThrown(app2.shrinkTo(5)); - - const app3 = app2; - assert(app3.capacity >= 3); - assert(app3[] == [1, 2, 3]); - +@safe pure nothrow unittest +{ auto app4 = appender([]); try // shrinkTo may throw { app4.shrinkTo(0); } catch (Exception) assert(0); +} + +// https://issues.dlang.org/show_bug.cgi?id=5663 +// https://issues.dlang.org/show_bug.cgi?id=9725 +@safe pure nothrow unittest +{ + import std.exception : assertNotThrown; - // Issue 5663 & 9725 tests - foreach (S; AliasSeq!(char[], const(char)[], string)) + static foreach (S; AliasSeq!(char[], const(char)[], string)) { { Appender!S app5663i; @@ -3389,6 +4038,12 @@ Appender!(E[]) appender(A : E[], E)(auto ref A array) assert(app5663m[] == "\xE3"); } } +} + +// https://issues.dlang.org/show_bug.cgi?id=10122 +@safe pure nothrow unittest +{ + import std.exception : assertCTFEable; static struct S10122 { @@ -3405,6 +4060,35 @@ Appender!(E[]) appender(A : E[], E)(auto ref A array) }); } +@safe pure nothrow unittest +{ + import std.exception : assertThrown; + + int[] a = [ 1, 2 ]; + auto app2 = appender(a); + assert(app2[] == [ 1, 2 ]); + app2 ~= 3; + app2 ~= [ 4, 5, 6 ][]; + assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); + app2 ~= [ 7 ]; + assert(app2[] == [ 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[] == [ 1, 2, 3 ]); + assertThrown(app2.shrinkTo(5)); + + const app3 = app2; + assert(app3.capacity >= 3); + assert(app3[] == [1, 2, 3]); +} + /// @safe pure nothrow unittest @@ -3426,63 +4110,63 @@ unittest @safe pure nothrow unittest { + auto w = appender!string(); + w.reserve(4); + cast(void) w.capacity; + cast(void) w[]; + try { - auto w = appender!string(); - w.reserve(4); - cast(void) w.capacity; - cast(void) w[]; - try - { - wchar wc = 'a'; - dchar dc = 'a'; - w.put(wc); // decoding may throw - w.put(dc); // decoding may throw - } - catch (Exception) assert(0); + wchar wc = 'a'; + dchar dc = 'a'; + w.put(wc); // decoding may throw + w.put(dc); // decoding may throw } + catch (Exception) assert(0); +} + +@safe pure nothrow unittest +{ + auto w = appender!(int[])(); + w.reserve(4); + cast(void) w.capacity; + cast(void) w[]; + w.put(10); + w.put([10]); + w.clear(); + try { - auto w = appender!(int[])(); - w.reserve(4); - cast(void) w.capacity; - cast(void) w[]; - w.put(10); - w.put([10]); - w.clear(); - try - { - w.shrinkTo(0); - } - catch (Exception) assert(0); + w.shrinkTo(0); + } + catch (Exception) assert(0); - struct N - { - int payload; - alias payload this; - } - w.put(N(1)); - w.put([N(2)]); + 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); + struct S(T) + { + @property bool empty() { return true; } + @property T front() { return T.init; } + void popFront() {} } + S!int r; + w.put(r); } -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=10690 +@safe pure nothrow unittest { - import std.algorithm; - import std.typecons; - //10690 + import std.algorithm.iteration : filter; + import std.typecons : tuple; [tuple(1)].filter!(t => true).array; // No error [tuple("A")].filter!(t => true).array; // error } -@system unittest +@safe pure nothrow unittest { import std.range; //Coverage for put(Range) @@ -3496,16 +4180,25 @@ unittest 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)); +} + +@system pure unittest +{ + import std.range; + struct S2 + { + void opAssign(S2){} + } + auto au2 = Appender!(const(S2)[])(); + auto sc2 = const(S2)(); au2.put(sc2.repeat().take(10)); } -@system unittest +@system pure nothrow unittest { struct S { @@ -3538,9 +4231,9 @@ unittest a2.put([s2]); } -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=9528 +@safe pure nothrow unittest { - //9528 const(E)[] fastCopy(E)(E[] src) { auto app = appender!(const(E)[])(); foreach (i, e; src) @@ -3553,12 +4246,13 @@ unittest S[] s = [ S(new C) ]; auto t = fastCopy(s); // Does not compile + assert(t.length == 1); } -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=10753 +@safe pure unittest { import std.algorithm.iteration : map; - //10753 struct Foo { immutable dchar d; } @@ -3569,7 +4263,7 @@ unittest [1, 2].map!Bar.array; } -@safe unittest +@safe pure nothrow unittest { import std.algorithm.comparison : equal; @@ -3618,7 +4312,7 @@ unittest assert(app8[] == null); } -@safe unittest //Test large allocations (for GC.extend) +@safe pure nothrow unittest //Test large allocations (for GC.extend) { import std.algorithm.comparison : equal; import std.range; @@ -3629,7 +4323,7 @@ unittest assert(equal(app[], 'a'.repeat(100_000))); } -@safe unittest +@safe pure nothrow unittest { auto reference = new ubyte[](2048 + 1); //a number big enough to have a full page (EG: the GC extends) auto arr = reference.dup; @@ -3639,7 +4333,7 @@ unittest assert(reference[] == arr[]); } -@safe unittest // clear method is supported only for mutable element types +@safe pure nothrow unittest // clear method is supported only for mutable element types { Appender!string app; app.put("foo"); @@ -3647,7 +4341,7 @@ unittest assert(app[] == "foo"); } -@safe unittest +@safe pure nothrow unittest { static struct D//dynamic { @@ -3678,7 +4372,7 @@ unittest @system unittest { - // Issue 13077 + // https://issues.dlang.org/show_bug.cgi?id=13077 static class A {} // reduced case @@ -3692,12 +4386,13 @@ unittest return [new shared A].inputRangeObject; } auto res = foo.array; + assert(res.length == 1); } /++ 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. + version of `appender` instead. +/ RefAppender!(E[]) appender(P : E[]*, E)(P arrayPtr) { @@ -3705,7 +4400,7 @@ RefAppender!(E[]) appender(P : E[]*, E)(P arrayPtr) } /// -@system pure nothrow +@safe pure nothrow unittest { int[] a = [1, 2]; @@ -3721,35 +4416,41 @@ unittest assert(app2.capacity >= 5); } -@system unittest +@safe pure nothrow 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[] == "abcdefg"); - assert(arr == "abcdefg"); - } - { - auto arr = new char[0]; - auto app = appender(&arr); - string b = "abcdefg"; - foreach (char c; b) app ~= c; - assert(app[] == "abcdefg"); - assert(arr == "abcdefg"); - } - { - int[] a = [ 1, 2 ]; - auto app2 = appender(&a); - assert(app2[] == [ 1, 2 ]); - assert(a == [ 1, 2 ]); - app2.put(3); - app2.put([ 4, 5, 6 ][]); - assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); - assert(a == [ 1, 2, 3, 4, 5, 6 ]); - } + auto arr = new char[0]; + auto app = appender(&arr); + string b = "abcdefg"; + foreach (char c; b) app.put(c); + assert(app[] == "abcdefg"); + assert(arr == "abcdefg"); +} + +@safe pure nothrow unittest +{ + auto arr = new char[0]; + auto app = appender(&arr); + string b = "abcdefg"; + foreach (char c; b) app ~= c; + assert(app[] == "abcdefg"); + assert(arr == "abcdefg"); +} + +@safe pure nothrow unittest +{ + int[] a = [ 1, 2 ]; + auto app2 = appender(&a); + assert(app2[] == [ 1, 2 ]); + assert(a == [ 1, 2 ]); + app2.put(3); + app2.put([ 4, 5, 6 ][]); + assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); + assert(a == [ 1, 2, 3, 4, 5, 6 ]); +} + +@safe pure nothrow unittest +{ + import std.exception : assertThrown; int[] a = [ 1, 2 ]; auto app2 = appender(&a); @@ -3776,13 +4477,14 @@ unittest assert(app3[] == [1, 2, 3]); } -@safe unittest // issue 14605 +// https://issues.dlang.org/show_bug.cgi?id=14605 +@safe pure nothrow unittest { static assert(isOutputRange!(Appender!(int[]), int)); static assert(isOutputRange!(RefAppender!(int[]), int)); } -@safe unittest +@safe pure nothrow unittest { Appender!(int[]) app; short[] range = [1, 2, 3]; @@ -3790,7 +4492,7 @@ unittest assert(app[] == [1, 2, 3]); } -@safe unittest +@safe pure nothrow unittest { string s = "hello".idup; char[] a = "hello".dup; @@ -3803,3 +4505,269 @@ unittest assert(appS[] == "hellow"); assert(appA[] == "hellow"); } + +/++ +Constructs a static array from `a`. +The type of elements can be specified implicitly so that $(D [1, 2].staticArray) results in `int[2]`, +or explicitly, e.g. $(D [1, 2].staticArray!float) returns `float[2]`. +When `a` is a range whose length is not known at compile time, the number of elements must be +given as template argument (e.g. `myrange.staticArray!2`). +Size and type can be combined, if the source range elements are implicitly +convertible to the requested element type (eg: `2.iota.staticArray!(long[2])`). +When the range `a` is known at compile time, it can also be specified as a +template argument to avoid having to specify the number of elements +(e.g.: `staticArray!(2.iota)` or `staticArray!(double, 2.iota)`). + +Note: `staticArray` returns by value, so expressions involving large arrays may be inefficient. + +Params: + a = The input elements. If there are less elements than the specified length of the static array, + the rest of it is default-initialized. If there are more than specified, the first elements + up to the specified length are used. + rangeLength = outputs the number of elements used from `a` to it. Optional. + +Returns: A static array constructed from `a`. ++/ +pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) +{ + return a; +} + +/// static array from array literal +nothrow pure @safe @nogc unittest +{ + auto a = [0, 1].staticArray; + static assert(is(typeof(a) == int[2])); + assert(a == [0, 1]); +} + +pragma(inline, true) U[n] staticArray(U, T, size_t n)(auto ref T[n] a) +if (!is(T == U) && is(T : U)) +{ + return a[].staticArray!(U[n]); +} + +/// static array from array with implicit casting of elements +nothrow pure @safe @nogc unittest +{ + auto b = [0, 1].staticArray!long; + static assert(is(typeof(b) == long[2])); + assert(b == [0, 1]); +} + +nothrow pure @safe @nogc unittest +{ + int val = 3; + static immutable gold = [1, 2, 3]; + [1, 2, val].staticArray.checkStaticArray!int([1, 2, 3]); + + @nogc void checkNogc() + { + [1, 2, val].staticArray.checkStaticArray!int(gold); + } + + checkNogc(); + + [1, 2, val].staticArray!double.checkStaticArray!double(gold); + [1, 2, 3].staticArray!int.checkStaticArray!int(gold); + + [1, 2, 3].staticArray!(const(int)).checkStaticArray!(const(int))(gold); + [1, 2, 3].staticArray!(const(double)).checkStaticArray!(const(double))(gold); + { + const(int)[3] a2 = [1, 2, 3].staticArray; + } + + [cast(byte) 1, cast(byte) 129].staticArray.checkStaticArray!byte([1, -127]); +} + +/// ditto +auto staticArray(size_t n, T)(scope T a) +if (isInputRange!T) +{ + alias U = ElementType!T; + return staticArray!(U[n], U, n)(a); +} + +/// ditto +auto staticArray(size_t n, T)(scope T a, out size_t rangeLength) +if (isInputRange!T) +{ + alias U = ElementType!T; + return staticArray!(U[n], U, n)(a, rangeLength); +} + +/// ditto +auto staticArray(Un : U[n], U, size_t n, T)(scope T a) +if (isInputRange!T && is(ElementType!T : U)) +{ + size_t extraStackSpace; + return staticArray!(Un, U, n)(a, extraStackSpace); +} + +/// ditto +auto staticArray(Un : U[n], U, size_t n, T)(scope T a, out size_t rangeLength) +if (isInputRange!T && is(ElementType!T : U)) +{ + import std.algorithm.mutation : uninitializedFill; + import std.range : take; + import core.internal.lifetime : emplaceRef; + + if (__ctfe) + { + size_t i; + // Compile-time version to avoid unchecked memory access. + Unqual!U[n] ret; + for (auto iter = a.take(n); !iter.empty; iter.popFront()) + { + ret[i] = iter.front; + i++; + } + + rangeLength = i; + return (() @trusted => cast(U[n]) ret)(); + } + + auto ret = (() @trusted + { + Unqual!U[n] theArray = void; + return theArray; + }()); + + size_t i; + if (true) + { + // ret was void-initialized so let's initialize the unfilled part manually. + // also prevents destructors to be called on uninitialized memory if + // an exception is thrown + scope (exit) ret[i .. $].uninitializedFill(U.init); + + for (auto iter = a.take(n); !iter.empty; iter.popFront()) + { + emplaceRef!U(ret[i++], iter.front); + } + } + + rangeLength = i; + return (() @trusted => cast(U[n]) ret)(); +} + +/// static array from range + size +nothrow pure @safe @nogc unittest +{ + import std.range : iota; + + auto input = 3.iota; + auto a = input.staticArray!2; + static assert(is(typeof(a) == int[2])); + assert(a == [0, 1]); + auto b = input.staticArray!(long[4]); + static assert(is(typeof(b) == long[4])); + assert(b == [0, 1, 2, 0]); +} + +// Tests that code compiles when there is an elaborate destructor and exceptions +// are thrown. Unfortunately can't test that memory is initialized +// before having a destructor called on it. +@safe nothrow unittest +{ + // exists only to allow doing something in the destructor. Not tested + // at the end because value appears to depend on implementation of the. + // function. + static int preventersDestroyed = 0; + + static struct CopyPreventer + { + bool on = false; + this(this) + { + if (on) throw new Exception("Thou shalt not copy past me!"); + } + + ~this() + { + preventersDestroyed++; + } + } + auto normalArray = + [ + CopyPreventer(false), + CopyPreventer(false), + CopyPreventer(true), + CopyPreventer(false), + CopyPreventer(true), + ]; + + try + { + auto staticArray = normalArray.staticArray!5; + assert(false); + } + catch (Exception e){} +} + + +nothrow pure @safe @nogc unittest +{ + auto a = [1, 2].staticArray; + assert(is(typeof(a) == int[2]) && a == [1, 2]); + + import std.range : iota; + + 2.iota.staticArray!2.checkStaticArray!int([0, 1]); + 2.iota.staticArray!(double[2]).checkStaticArray!double([0, 1]); + 2.iota.staticArray!(long[2]).checkStaticArray!long([0, 1]); +} + +nothrow pure @safe @nogc unittest +{ + import std.range : iota; + size_t copiedAmount; + 2.iota.staticArray!1(copiedAmount); + assert(copiedAmount == 1); + 2.iota.staticArray!3(copiedAmount); + assert(copiedAmount == 2); +} + +/// ditto +auto staticArray(alias a)() +if (isInputRange!(typeof(a))) +{ + return .staticArray!(size_t(a.length))(a); +} + +/// ditto +auto staticArray(U, alias a)() +if (isInputRange!(typeof(a))) +{ + return .staticArray!(U[size_t(a.length)])(a); +} + +/// static array from CT range +nothrow pure @safe @nogc unittest +{ + import std.range : iota; + + enum a = staticArray!(2.iota); + static assert(is(typeof(a) == int[2])); + assert(a == [0, 1]); + + enum b = staticArray!(long, 2.iota); + static assert(is(typeof(b) == long[2])); + assert(b == [0, 1]); +} + +nothrow pure @safe @nogc unittest +{ + import std.range : iota; + + enum a = staticArray!(2.iota); + staticArray!(2.iota).checkStaticArray!int([0, 1]); + staticArray!(double, 2.iota).checkStaticArray!double([0, 1]); + staticArray!(long, 2.iota).checkStaticArray!long([0, 1]); +} + +version (StdUnittest) private void checkStaticArray(T, T1, T2)(T1 a, T2 b) nothrow @safe pure @nogc +{ + static assert(is(T1 == T[T1.length])); + assert(a == b, "a must be equal to b"); +} diff --git a/libphobos/src/std/ascii.d b/libphobos/src/std/ascii.d index b430114..3b6face 100644 --- a/libphobos/src/std/ascii.d +++ b/libphobos/src/std/ascii.d @@ -3,9 +3,9 @@ /++ 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 + All of the functions in std.ascii accept Unicode characters but + effectively ignore them if they're not ASCII. All `isX` functions return + `false` for non-ASCII characters, and all `toX` functions do nothing to non-ASCII characters. For functions which operate on Unicode characters, see @@ -46,6 +46,7 @@ $(TR $(TD Constants) $(TD $(LREF whitespace) )) $(TR $(TD Enums) $(TD + $(LREF ControlChar) $(LREF LetterCase) )) )) @@ -54,20 +55,12 @@ $(TR $(TD Enums) $(TD $(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) + Authors: $(HTTP digitalmars.com, Walter Bright) and + $(HTTP jmdavisprog.com, 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 @@ -97,10 +90,10 @@ enum LetterCase : bool } /// -@system unittest +@safe unittest { import std.digest.hmac : hmac; - import std.digest.digest : toHexString; + import std.digest : toHexString; import std.digest.sha : SHA1; import std.string : representation; @@ -110,6 +103,75 @@ enum LetterCase : bool assert(sha1HMAC == "49f2073c7bf58577e8c9ae59fe8cfd37c9ab94e5"); } +/++ + All control characters in the ASCII table ($(HTTPS www.asciitable.com, source)). ++/ +enum ControlChar : char +{ + nul = '\x00', /// Null + soh = '\x01', /// Start of heading + stx = '\x02', /// Start of text + etx = '\x03', /// End of text + eot = '\x04', /// End of transmission + enq = '\x05', /// Enquiry + ack = '\x06', /// Acknowledge + bel = '\x07', /// Bell + bs = '\x08', /// Backspace + tab = '\x09', /// Horizontal tab + lf = '\x0A', /// NL line feed, new line + vt = '\x0B', /// Vertical tab + ff = '\x0C', /// NP form feed, new page + cr = '\x0D', /// Carriage return + so = '\x0E', /// Shift out + si = '\x0F', /// Shift in + dle = '\x10', /// Data link escape + dc1 = '\x11', /// Device control 1 + dc2 = '\x12', /// Device control 2 + dc3 = '\x13', /// Device control 3 + dc4 = '\x14', /// Device control 4 + nak = '\x15', /// Negative acknowledge + syn = '\x16', /// Synchronous idle + etb = '\x17', /// End of transmission block + can = '\x18', /// Cancel + em = '\x19', /// End of medium + sub = '\x1A', /// Substitute + esc = '\x1B', /// Escape + fs = '\x1C', /// File separator + gs = '\x1D', /// Group separator + rs = '\x1E', /// Record separator + us = '\x1F', /// Unit separator + del = '\x7F' /// Delete +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.algorithm.comparison, std.algorithm.searching, std.range, std.traits; + + // Because all ASCII characters fit in char, so do these + static assert(ControlChar.ack.sizeof == 1); + + // All control characters except del are in row starting from 0 + static assert(EnumMembers!ControlChar.only.until(ControlChar.del).equal(iota(32))); + + static assert(ControlChar.nul == '\0'); + static assert(ControlChar.bel == '\a'); + static assert(ControlChar.bs == '\b'); + static assert(ControlChar.ff == '\f'); + static assert(ControlChar.lf == '\n'); + static assert(ControlChar.cr == '\r'); + static assert(ControlChar.tab == '\t'); + static assert(ControlChar.vt == '\v'); +} + +/// +@safe pure nothrow unittest +{ + import std.conv; + //Control character table can be used in place of hexcodes. + with (ControlChar) assert(text("Phobos", us, "Deimos", us, "Tango", rs) == "Phobos\x1FDeimos\x1FTango\x1E"); +} + /// Newline sequence for this system. version (Windows) immutable newline = "\r\n"; @@ -121,7 +183,7 @@ else /++ Params: c = The character to test. - Returns: Whether $(D c) is a letter or a number (0 .. 9, a .. z, A .. Z). + Returns: Whether `c` is a letter or a number (0 .. 9, a .. z, A .. Z). +/ bool isAlphaNum(dchar c) @safe pure nothrow @nogc { @@ -141,6 +203,7 @@ bool isAlphaNum(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; chain(digits, octalDigits, fullHexDigits, letters, lowercase, uppercase)) assert(isAlphaNum(c)); @@ -151,7 +214,7 @@ bool isAlphaNum(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether $(D c) is an ASCII letter (A .. Z, a .. z). + Returns: Whether `c` is an ASCII letter (A .. Z, a .. z). +/ bool isAlpha(dchar c) @safe pure nothrow @nogc { @@ -172,6 +235,7 @@ bool isAlpha(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; chain(letters, lowercase, uppercase)) assert(isAlpha(c)); @@ -182,7 +246,7 @@ bool isAlpha(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether $(D c) is a lowercase ASCII letter (a .. z). + Returns: Whether `c` is a lowercase ASCII letter (a .. z). +/ bool isLower(dchar c) @safe pure nothrow @nogc { @@ -203,6 +267,7 @@ bool isLower(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; lowercase) assert(isLower(c)); @@ -213,7 +278,7 @@ bool isLower(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether $(D c) is an uppercase ASCII letter (A .. Z). + Returns: Whether `c` is an uppercase ASCII letter (A .. Z). +/ bool isUpper(dchar c) @safe pure nothrow @nogc { @@ -234,6 +299,7 @@ bool isUpper(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; uppercase) assert(isUpper(c)); @@ -244,7 +310,7 @@ bool isUpper(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether $(D c) is a digit (0 .. 9). + Returns: Whether `c` is a digit (0 .. 9). +/ bool isDigit(dchar c) @safe pure nothrow @nogc { @@ -266,6 +332,7 @@ bool isDigit(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; digits) assert(isDigit(c)); @@ -276,7 +343,7 @@ bool isDigit(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether $(D c) is a digit in base 8 (0 .. 7). + Returns: Whether `c` is a digit in base 8 (0 .. 7). +/ bool isOctalDigit(dchar c) @safe pure nothrow @nogc { @@ -295,6 +362,7 @@ bool isOctalDigit(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; octalDigits) assert(isOctalDigit(c)); @@ -305,7 +373,7 @@ bool isOctalDigit(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether $(D c) is a digit in base 16 (0 .. 9, A .. F, a .. f). + Returns: Whether `c` is a digit in base 16 (0 .. 9, A .. F, a .. f). +/ bool isHexDigit(dchar c) @safe pure nothrow @nogc { @@ -325,6 +393,7 @@ bool isHexDigit(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; fullHexDigits) assert(isHexDigit(c)); @@ -335,7 +404,7 @@ bool isHexDigit(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether or not $(D c) is a whitespace character. That includes the + Returns: Whether or not `c` is a whitespace character. That includes the space, tab, vertical tab, form feed, carriage return, and linefeed characters. +/ @@ -362,6 +431,7 @@ bool isWhite(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (c; whitespace) assert(isWhite(c)); @@ -372,7 +442,7 @@ bool isWhite(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether $(D c) is a control character. + Returns: Whether `c` is a control character. +/ bool isControl(dchar c) @safe pure nothrow @nogc { @@ -398,6 +468,7 @@ bool isControl(dchar c) @safe pure nothrow @nogc @safe unittest { + import std.range; foreach (dchar c; 0 .. 32) assert(isControl(c)); assert(isControl(127)); @@ -409,7 +480,7 @@ bool isControl(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether or not $(D c) is a punctuation character. That includes + Returns: Whether or not `c` is a punctuation character. That includes all ASCII characters which are not control characters, letters, digits, or whitespace. +/ @@ -454,7 +525,7 @@ bool isPunctuation(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether or not $(D c) is a printable character other than the + Returns: Whether or not `c` is a printable character other than the space character. +/ bool isGraphical(dchar c) @safe pure nothrow @nogc @@ -490,7 +561,7 @@ bool isGraphical(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether or not $(D c) is a printable character - including the + Returns: Whether or not `c` is a printable character - including the space character. +/ bool isPrintable(dchar c) @safe pure nothrow @nogc @@ -525,7 +596,7 @@ bool isPrintable(dchar c) @safe pure nothrow @nogc /++ Params: c = The character to test. - Returns: Whether or not $(D c) is in the ASCII character set - i.e. in the + Returns: Whether or not `c` is in the ASCII character set - i.e. in the range 0 .. 0x7F. +/ pragma(inline, true) @@ -553,24 +624,23 @@ bool isASCII(dchar c) @safe pure nothrow @nogc /++ Converts an ASCII letter to lowercase. - Params: c = A character of any type that implicitly converts to $(D dchar). + Params: c = A character of any type that implicitly converts to `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. + `Unqual!(OriginalType!C)` is returned, whereas if it's a user-defined + type, `dchar` is returned. - Returns: The corresponding lowercase letter, if $(D c) is an uppercase - ASCII character, otherwise $(D c) itself. + Returns: The corresponding lowercase letter, if `c` is an uppercase + ASCII character, otherwise `c` itself. +/ auto toLower(C)(C c) if (is(C : dchar)) { - import std.traits : isAggregateType, OriginalType, Unqual; + import std.traits : OriginalType; - alias OC = OriginalType!C; - static if (isAggregateType!OC) + static if (!__traits(isScalar, C)) alias R = dchar; - else - alias R = Unqual!OC; + else static if (is(immutable OriginalType!C == immutable OC, OC)) + alias R = OC; return isUpper(c) ? cast(R)(cast(R) c + 'a' - 'A') : cast(R) c; } @@ -589,7 +659,8 @@ if (is(C : dchar)) @safe pure nothrow unittest { - foreach (C; AliasSeq!(char, wchar, dchar, immutable char, ubyte)) + import std.meta; + static foreach (C; AliasSeq!(char, wchar, dchar, immutable char, ubyte)) { foreach (i, c; uppercase) assert(toLower(cast(C) c) == lowercase[i]); @@ -615,24 +686,23 @@ if (is(C : dchar)) /++ Converts an ASCII letter to uppercase. - Params: c = Any type which implicitly converts to $(D dchar). In the case + Params: c = Any type which implicitly converts to `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. + `Unqual!(OriginalType!C)` is returned, whereas if it's a user-defined + type, `dchar` is returned. - Returns: The corresponding uppercase letter, if $(D c) is a lowercase ASCII - character, otherwise $(D c) itself. + Returns: The corresponding uppercase letter, if `c` is a lowercase ASCII + character, otherwise `c` itself. +/ auto toUpper(C)(C c) if (is(C : dchar)) { - import std.traits : isAggregateType, OriginalType, Unqual; + import std.traits : OriginalType; - alias OC = OriginalType!C; - static if (isAggregateType!OC) + static if (!__traits(isScalar, C)) alias R = dchar; - else - alias R = Unqual!OC; + else static if (is(immutable OriginalType!C == immutable OC, OC)) + alias R = OC; return isLower(c) ? cast(R)(cast(R) c - ('a' - 'A')) : cast(R) c; } @@ -650,7 +720,8 @@ if (is(C : dchar)) @safe pure nothrow unittest { - foreach (C; AliasSeq!(char, wchar, dchar, immutable char, ubyte)) + import std.meta; + static foreach (C; AliasSeq!(char, wchar, dchar, immutable char, ubyte)) { foreach (i, c; lowercase) assert(toUpper(cast(C) c) == uppercase[i]); @@ -675,6 +746,9 @@ if (is(C : dchar)) @safe unittest //Test both toUpper and toLower with non-builtin { + import std.meta; + import std.traits; + //User Defined [Char|Wchar|Dchar] static struct UDC { char c; alias c this; } static struct UDW { wchar c; alias c this; } @@ -689,7 +763,7 @@ if (is(C : dchar)) enum UDDE : UDD {a = UDD('a'), A = UDD('A')} //User defined types with implicit cast to dchar test. - foreach (Char; AliasSeq!(UDC, UDW, UDD)) + static foreach (Char; AliasSeq!(UDC, UDW, UDD)) { assert(toLower(Char('a')) == 'a'); assert(toLower(Char('A')) == 'a'); @@ -700,7 +774,7 @@ if (is(C : dchar)) } //Various enum tests. - foreach (Enum; AliasSeq!(CE, WE, DE, UDCE, UDWE, UDDE)) + static foreach (Enum; AliasSeq!(CE, WE, DE, UDCE, UDWE, UDDE)) { assert(toLower(Enum.a) == 'a'); assert(toLower(Enum.A) == 'a'); @@ -713,15 +787,15 @@ if (is(C : dchar)) } //Return value type tests for enum of non-UDT. These should be the original type. - foreach (T; AliasSeq!(CE, WE, DE)) - { + static 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 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 index 6211d10..866f700 100644 --- a/libphobos/src/std/base64.d +++ b/libphobos/src/std/base64.d @@ -51,15 +51,16 @@ * 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) + * Source: $(PHOBOSSRC std/base64.d) * Macros: - * LREF2=$(D $2) + * LREF2=`$2` */ module std.base64; -import std.exception; // enforce -import std.range.primitives; // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength -import std.traits; // isArray +import std.exception : enforce; +import std.range.primitives : empty, front, isInputRange, isOutputRange, + isForwardRange, ElementType, hasLength, popFront, put, save; +import std.traits : isArray; // Make sure module header code examples work correctly. @safe unittest @@ -138,8 +139,8 @@ alias Base64URLNoPadding = Base64Impl!('-', '_', Base64.NoPadding); * ----- * * NOTE: - * Encoded strings will not have any padding if the $(D Padding) parameter is - * set to $(D NoPadding). + * Encoded strings will not have any padding if the `Padding` parameter is + * set to `NoPadding`. */ template Base64Impl(char Map62th, char Map63th, char Padding = '=') { @@ -205,19 +206,19 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** - * Encode $(D_PARAM source) into a $(D char[]) buffer using Base64 + * Encode $(D_PARAM source) into a `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. + * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * to _encode. + * buffer = The `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) && + pure char[] encode(R1, R2)(in R1 source, return scope R2 buffer) if (isArray!R1 && is(ElementType!R1 : ubyte) && is(R2 == char[])) in { @@ -227,7 +228,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') { assert(result.length == encodeLength(source.length), "The length of result is different from Base64"); } - body + do { immutable srcLen = source.length; if (srcLen == 0) @@ -310,7 +311,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') // @@@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 + do { immutable srcLen = source.length; if (srcLen == 0) @@ -362,7 +363,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') } // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'. - version (unittest) + version (StdUnittest) assert( bufptr - buffer.ptr == encodeLength(srcLen), "The length of result is different from Base64" @@ -378,26 +379,25 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** * Encodes $(D_PARAM source) into an - * $(LINK2 std_range_primitives.html#isOutputRange, output range) using + * $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) 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. + * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * to _encode. + * range = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) + * to store the encoded result. * * Returns: - * The number of times the output range's $(D put) method was invoked. + * The number of times the output range's `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)) + size_t encode(E, R)(scope const(E)[] source, auto ref R range) + if (is(E : ubyte) && isOutputRange!(R, char) && !is(R == char[])) out(result) { assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); } - body + do { immutable srcLen = source.length; if (srcLen == 0) @@ -405,23 +405,23 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') immutable blocks = srcLen / 3; immutable remain = srcLen % 3; - auto srcptr = source.ptr; - size_t pcount; + auto s = source; // copy for out contract length check + size_t pcount; foreach (Unused; 0 .. blocks) { - immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2]; + immutable val = s[0] << 16 | s[1] << 8 | s[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; + s = s[3 .. $]; pcount += 4; } if (remain) { - immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0); + immutable val = s[0] << 16 | (remain == 2 ? s[1] << 8 : 0); put(range, EncodeMap[val >> 18 ]); put(range, EncodeMap[val >> 12 & 0x3f]); pcount += 2; @@ -453,22 +453,17 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') } /// - @system unittest + @safe pure nothrow unittest { - // @system because encode for OutputRange is @system - struct OutputRange - { - char[] result; - void put(const(char) ch) @safe { result ~= ch; } - } + import std.array : appender; + auto output = appender!string(); 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"); + assert(output.data == "Gis8TV1u"); } @@ -481,12 +476,6 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') 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) @@ -546,7 +535,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') } // @@@BUG@@@ Workaround for DbC problem. - version (unittest) + version (StdUnittest) assert( pcount == encodeLength(srcLen), "The number of put is different from the length of Base64" @@ -563,11 +552,11 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * buffers. * * Params: - * source = The $(LINK2 std_range_primitives.html#isInputRange, input - * range) to _encode. + * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * to _encode. * * Returns: - * A newly-allocated $(D char[]) buffer containing the encoded string. + * A newly-allocated `char[]` buffer containing the encoded string. */ @safe pure char[] encode(Range)(Range source) if (isArray!Range && is(ElementType!Range : ubyte)) @@ -594,12 +583,11 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** - * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 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. + * This range will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + * 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. @@ -616,7 +604,8 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') this(Range range) { range_ = range; - doEncoding(); + if (!empty) + doEncoding(); } @@ -645,8 +634,8 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * Advance the range to the next chunk of encoded data. * * Throws: - * $(D Base64Exception) If invoked when - * $(LREF2 .Base64Impl.Encoder.empty, empty) returns $(D true). + * `Base64Exception` If invoked when + * $(LREF2 .Base64Impl.Encoder.empty, empty) returns `true`. */ void popFront() { @@ -671,11 +660,10 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * 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). + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives). * * Returns: - * A copy of $(D this). + * A copy of `this`. */ @property typeof(this) save() @@ -705,11 +693,11 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** - * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 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. + * It will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + * 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. @@ -764,8 +752,8 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * Advance to the next encoded character. * * Throws: - * $(D Base64Exception) If invoked when $(LREF2 .Base64Impl.Encoder.empty.2, - * empty) returns $(D true). + * `Base64Exception` If invoked when $(LREF2 .Base64Impl.Encoder.empty.2, + * empty) returns `true`. */ void popFront() { @@ -835,11 +823,10 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * 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). + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives). * * Returns: - * A copy of $(D this). + * A copy of `this`. */ @property typeof(this) save() @@ -853,23 +840,23 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** - * Construct an $(D Encoder) that iterates over the Base64 encoding of the - * given $(LINK2 std_range_primitives.html#isInputRange, input range). + * Construct an `Encoder` that iterates over the Base64 encoding of the + * given $(REF_ALTTEXT input range, isInputRange, std,range,primitives). * * Params: - * range = An $(LINK2 std_range_primitives.html#isInputRange, input - * range) over the data to be encoded. + * range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * over the data to be encoded. * * Returns: - * If $(D_PARAM range) is a range of bytes, an $(D Encoder) that iterates + * If $(D_PARAM range) is a range of bytes, an `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 + * If $(D_PARAM range) is a range of ranges of bytes, an `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 + * In both cases, the returned `Encoder` will be a + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) if the + * given `range` is at least a forward range, otherwise it will be only * an input range. * * Example: @@ -982,19 +969,19 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * Decodes $(D_PARAM source) into the given buffer. * * Params: - * source = The $(LINK2 std_range_primitives.html#isInputRange, input - * range) to _decode. + * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * 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 + * `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) && + pure ubyte[] decode(R1, R2)(in R1 source, return scope R2 buffer) if (isArray!R1 && is(ElementType!R1 : dchar) && is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) in { @@ -1005,7 +992,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') immutable expect = realDecodeLength(source); assert(result.length == expect, "The length of result is different from the expected length"); } - body + do { immutable srcLen = source.length; if (srcLen == 0) @@ -1085,13 +1072,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') { 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 + do { immutable srcLen = source.length; if (srcLen == 0) @@ -1143,8 +1124,8 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') } } - // @@@BUG@@@ Workaround for DbC problem. - version (unittest) + // We need to do the check here because we have consumed the length + version (StdUnittest) assert( (bufptr - buffer.ptr) >= (decodeLength(srcLen) - 2), "The length of result is smaller than expected length" @@ -1159,19 +1140,19 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** * Decodes $(D_PARAM source) into a given - * $(LINK2 std_range_primitives.html#isOutputRange, output range). + * $(REF_ALTTEXT output range, isOutputRange, std,range,primitives). * * 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. + * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * to _decode. + * range = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) + * to store the decoded result. * * Returns: - * The number of times the output range's $(D put) method was invoked. + * The number of times the output range's `put` method was invoked. * * Throws: - * $(D Base64Exception) if $(D_PARAM source) contains characters outside the + * `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) @@ -1182,7 +1163,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') immutable expect = realDecodeLength(source); assert(result == expect, "The result of decode is different from the expected"); } - body + do { immutable srcLen = source.length; if (srcLen == 0) @@ -1271,7 +1252,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') //immutable expect = decodeLength(source.length) - 2; //assert(result >= expect, "The length of result is smaller than expected length"); } - body + do { immutable srcLen = source.length; if (srcLen == 0) @@ -1329,7 +1310,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') } // @@@BUG@@@ Workaround for DbC problem. - version (unittest) + version (StdUnittest) assert( pcount >= (decodeLength(srcLen) - 2), "The length of result is smaller than expected length" @@ -1346,11 +1327,11 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * buffers. * * Params: - * source = The $(LINK2 std_range_primitives.html#isInputRange, input - * range) to _decode. + * source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * to _decode. * * Returns: - * A newly-allocated $(D ubyte[]) buffer containing the decoded string. + * A newly-allocated `ubyte[]` buffer containing the decoded string. */ @safe pure ubyte[] decode(Range)(Range source) if (isArray!Range && is(ElementType!Range : dchar)) @@ -1377,12 +1358,11 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** - * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 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. + * This range will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + * 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. @@ -1399,7 +1379,8 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') this(Range range) { range_ = range; - doDecoding(); + if (!empty) + doDecoding(); } @@ -1428,8 +1409,8 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * 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). + * `Base64Exception` if invoked when $(LREF2 .Base64Impl.Decoder.empty, + * empty) returns `true`. */ void popFront() { @@ -1451,10 +1432,9 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * Saves the current iteration state. * * This method is only available if the underlying range is a - * $(LINK2 std_range_primitives.html#isForwardRange, forward - * range). + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) * - * Returns: A copy of $(D this). + * Returns: A copy of `this`. */ @property typeof(this) save() @@ -1488,6 +1468,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') while (data.length % 4 != 0) { range_.popFront(); + enforce(!range_.empty, new Base64Exception("Invalid length of encoded data")); data ~= cast(const(char)[])range_.front; } } @@ -1502,12 +1483,11 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** - * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 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. + * This range will be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + * 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. @@ -1562,8 +1542,8 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * Advance to the next decoded byte. * * Throws: - * $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty, - * empty) returns $(D true). + * `Base64Exception` if invoked when $(LREF2 .Base64Impl.Decoder.empty, + * empty) returns `true`. */ void popFront() { @@ -1644,10 +1624,9 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * Saves the current iteration state. * * This method is only available if the underlying range is a - * $(LINK2 std_range_primitives.html#isForwardRange, forward - * range). + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) * - * Returns: A copy of $(D this). + * Returns: A copy of `this`. */ @property typeof(this) save() @@ -1661,31 +1640,30 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') /** - * Construct a $(D Decoder) that iterates over the decoding of the given + * Construct a `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. + * range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * over the data to be decoded, or a `char` array. Will not accept + * `wchar[]` nor `dchar[]`. * * Returns: - * If $(D_PARAM range) is a range of characters, a $(D Decoder) that + * If $(D_PARAM range) is a range or array of `char`, a `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) + * If $(D_PARAM range) is a range of ranges of characters, a `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. + * the range. * - * 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 + * In both cases, the returned `Decoder` will be a + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) if the + * given `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). + * `Base64Exception`. * * Example: * This example shows decoding over a range of input data lines. @@ -1696,7 +1674,6 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * } * ----- * - * Example: * This example shows decoding one byte at a time. * ----- * auto encoded = Base64.encoder(cast(ubyte[])"0123456789"); @@ -1711,6 +1688,24 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') return typeof(return)(range); } + /// ditto + Decoder!(const(ubyte)[]) decoder()(const(char)[] range) + { + import std.string : representation; + return typeof(return)(range.representation); + } + + /// + @safe pure unittest + { + import std.algorithm.comparison : equal; + string encoded = + "VGhvdSBzaGFsdCBuZXZlciBjb250aW51ZSBhZnRlciBhc3NlcnRpbmcgbnVsbA=="; + + assert(Base64.decoder(encoded) + .equal("Thou shalt never continue after asserting null")); + } + private: @safe @@ -1766,18 +1761,18 @@ class Base64Exception : Exception } /// -@system unittest +@safe 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.exception : assertThrown; import std.file; import std.stdio; @@ -1920,7 +1915,8 @@ class Base64Exception : Exception assert(tv["foobar"] == b.data); a.clear(); b.clear(); } - // @@@9543@@@ These tests were disabled because they actually relied on the input range having length. + // https://issues.dlang.org/show_bug.cgi?id=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 @@ -2039,7 +2035,7 @@ class Base64Exception : Exception } // Regression control for the output range ref bug in encode. -@system unittest +@safe unittest { struct InputRange { @@ -2064,12 +2060,14 @@ class Base64Exception : Exception // Verify that any existing workaround that uses & still works. InputRange ir2; OutputRange or2; - assert(Base64.encode(ir2, &or2) == 8); + () @trusted { + assert(Base64.encode(ir2, &or2) == 8); + }(); assert(or2.result == "Gis8TV1u"); } // Regression control for the output range ref bug in decode. -@system unittest +@safe unittest { struct InputRange { @@ -2094,6 +2092,98 @@ class Base64Exception : Exception // Verify that any existing workaround that uses & still works. InputRange ir2; OutputRange or2; - assert(Base64.decode(ir2, &or2) == 6); + () @trusted { + assert(Base64.decode(ir2, &or2) == 6); + }(); assert(or2.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); } + +// https://issues.dlang.org/show_bug.cgi?id=21679 +// https://issues.dlang.org/show_bug.cgi?id=21706 +@safe unittest +{ + ubyte[][] input; + assert(Base64.encoder(input).empty); + assert(Base64.decoder(input).empty); +} + +@safe unittest +{ + struct InputRange(ubyte[] data) + { + ubyte[] impl = data; + bool empty() { return impl.length == 0; } + ubyte front() { return impl[0]; } + void popFront() { impl = impl[1 .. $]; } + size_t length() { return impl.length; } + } + + struct OutputRange + { + ubyte[] result; + void put(ubyte b) { result ~= b; } + } + + void test_encode(ubyte[] data, string result)() + { + InputRange!data ir; + OutputRange or; + assert(Base64.encode(ir, or) == result.length); + assert(or.result == result); + } + + void test_decode(ubyte[] data, string result)() + { + InputRange!data ir; + OutputRange or; + assert(Base64.decode(ir, or) == result.length); + assert(or.result == result); + } + + test_encode!([], ""); + test_encode!(['x'], "eA=="); + test_encode!([123, 45], "ey0="); + + test_decode!([], ""); + test_decode!(['e', 'A', '=', '='], "x"); + test_decode!(['e', 'y', '0', '='], "{-"); +} + +@system unittest +{ + // checking forward range + auto item = Base64.decoder(Base64.encoder(cast(ubyte[]) "foobar")); + auto copy = item.save(); + item.popFront(); + assert(item.front == 'o'); + assert(copy.front == 'f'); +} + +@system unittest +{ + // checking invalid dchar + dchar[] c = cast(dchar[]) "ääää"; + + import std.exception : assertThrown; + assertThrown!Base64Exception(Base64.decode(c)); +} + +@safe unittest +{ + import std.array : array; + + char[][] input = [['e', 'y'], ['0', '=']]; + assert(Base64.decoder(input).array == [[123, 45]]); +} + +// https://issues.dlang.org/show_bug.cgi?id=21707 +@safe unittest +{ + import std.exception : assertThrown; + + char[][] t1 = [[ 'Z', 'g', '=' ]]; + assertThrown!Base64Exception(Base64.decoder(t1)); + + char[][] t2 = [[ 'e', 'y', '0' ], ['=', '=']]; + assertThrown!Base64Exception(Base64.decoder(t2)); +} diff --git a/libphobos/src/std/bigint.d b/libphobos/src/std/bigint.d index 4c518f2..bbb55c2 100644 --- a/libphobos/src/std/bigint.d +++ b/libphobos/src/std/bigint.d @@ -15,7 +15,7 @@ * * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Don Clugston - * Source: $(PHOBOSSRC std/_bigint.d) + * Source: $(PHOBOSSRC std/bigint.d) */ /* Copyright Don Clugston 2008 - 2010. * Distributed under the Boost Software License, Version 1.0. @@ -27,15 +27,17 @@ module std.bigint; import std.conv : ConvException; -import std.format : FormatSpec, FormatException; +import std.format.spec : FormatSpec; +import std.format : FormatException; import std.internal.math.biguintcore; +import std.internal.math.biguintnoasm : BigDigit; 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 + * 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 @@ -50,7 +52,7 @@ private: bool sign = false; public: /** - * Construct a BigInt from a decimal or hexadecimal string. The number must + * 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. @@ -65,7 +67,7 @@ public: isBidirectionalRange!Range && isSomeChar!(ElementType!Range) && !isInfinite!Range && - !isSomeString!Range) + !isNarrowString!Range) { import std.algorithm.iteration : filterBidirectional; import std.algorithm.searching : startsWith; @@ -116,13 +118,14 @@ public: } /// ditto - this(Range)(Range s) pure if (isSomeString!Range) + this(Range)(Range s) pure + if (isNarrowString!Range) { import std.utf : byCodeUnit; this(s.byCodeUnit); } - @system unittest + @safe unittest { // system because of the dummy ranges eventually call std.array!string import std.exception : assertThrown; @@ -144,30 +147,62 @@ public: assertThrown!ConvException(BigInt(r4)); } - /// Construct a BigInt from a built-in integral type. - this(T)(T x) pure nothrow if (isIntegral!T) + /** + * Construct a `BigInt` from a sign and a magnitude. + * + * The magnitude is an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * of unsigned integers that satisfies either $(REF hasLength, std,range,primitives) + * or $(REF isForwardRange, std,range,primitives). The first (leftmost) + * element of the magnitude is considered the most significant. + * + * Params: + * isNegative = true for negative, false for non-negative + * (ignored when magnitude is zero) + * magnitude = a finite range of unsigned integers + */ + this(Range)(bool isNegative, Range magnitude) if ( + isInputRange!Range && + isUnsigned!(ElementType!Range) && + (hasLength!Range || isForwardRange!Range) && + !isInfinite!Range) + { + data.fromMagnitude(magnitude); + sign = isNegative && !data.isZero; + } + + /// + pure @safe unittest + { + ubyte[] magnitude = [1, 2, 3, 4, 5, 6]; + auto b1 = BigInt(false, magnitude); + assert(cast(long) b1 == 0x01_02_03_04_05_06L); + auto b2 = BigInt(true, magnitude); + assert(cast(long) b2 == -0x01_02_03_04_05_06L); + } + + /// Construct a `BigInt` from a built-in integral type. + this(T)(T x) pure nothrow @safe if (isIntegral!T) { data = data.init; // @@@: Workaround for compiler bug opAssign(x); } /// - @system unittest + @safe 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")); + assert(bigData == BigInt("1_000_000_000_000")); } - /// Construct a BigInt from another BigInt. - this(T)(T x) pure nothrow if (is(Unqual!T == BigInt)) + /// Construct a `BigInt` from another `BigInt`. + this(T)(T x) pure nothrow @safe if (is(immutable T == immutable BigInt)) { opAssign(x); } /// - @system unittest + @safe unittest { const(BigInt) b1 = BigInt("1_234_567_890"); BigInt b2 = BigInt(b1); @@ -175,7 +210,7 @@ public: } /// Assignment from built-in integer types. - BigInt opAssign(T)(T x) pure nothrow if (isIntegral!T) + BigInt opAssign(T)(T x) pure nothrow @safe if (isIntegral!T) { data = cast(ulong) absUnsign(x); sign = (x < 0); @@ -183,7 +218,7 @@ public: } /// - @system unittest + @safe unittest { auto b = BigInt("123"); b = 456; @@ -191,7 +226,7 @@ public: } /// Assignment from another BigInt. - BigInt opAssign(T:BigInt)(T x) pure @nogc + BigInt opAssign(T:BigInt)(T x) pure @nogc @safe { data = x.data; sign = x.sign; @@ -199,7 +234,7 @@ public: } /// - @system unittest + @safe unittest { auto b1 = BigInt("123"); auto b2 = BigInt("456"); @@ -209,9 +244,9 @@ public: /** * Implements assignment operators from built-in integers of the form - * $(D BigInt op= integer). + * `BigInt op= integer`. */ - BigInt opOpAssign(string op, T)(T y) pure nothrow + BigInt opOpAssign(string op, T)(T y) pure nothrow @safe return scope if ((op=="+" || op=="-" || op=="*" || op=="/" || op=="%" || op==">>" || op=="<<" || op=="^^" || op=="|" || op=="&" || op=="^") && isIntegral!T) { @@ -276,10 +311,11 @@ public: else if ((y > 0) == (op=="<<")) { // Sign never changes during left shift - data = data.opShl(u); - } else + data = data.opBinary!(op)(u); + } + else { - data = data.opShr(u); + data = data.opBinary!(op)(u); if (data.isZero()) sign = false; } @@ -289,7 +325,23 @@ public: sign = (y & 1) ? sign : false; data = BigUint.pow(data, u); } - else static if (op=="|" || op=="&" || op=="^") + else static if (op=="&") + { + if (y >= 0 && (y <= 1 || !sign)) // In these cases we can avoid some allocation. + { + static if (T.sizeof <= uint.sizeof && BigDigit.sizeof <= uint.sizeof) + data = cast(ulong) data.peekUint(0) & y; + else + data = data.peekUlong(0) & y; + sign = false; + } + else + { + BigInt b = y; + opOpAssign!op(b); + } + } + else static if (op=="|" || op=="^") { BigInt b = y; opOpAssign!op(b); @@ -299,9 +351,8 @@ public: } /// - @system unittest + @safe unittest { - //@system because opOpAssign is @system auto b = BigInt("1_000_000_000"); b += 12345; @@ -311,10 +362,59 @@ public: assert(b == BigInt("200_002_469")); } + // https://issues.dlang.org/show_bug.cgi?id=16264 + @safe unittest + { + auto a = BigInt( + `335690982744637013564796917901053301979460129353374296317539383938630086938` ~ + `465898213033510992292836631752875403891802201862860531801760096359705447768` ~ + `957432600293361240407059207520920532482429912948952142341440301429494694368` ~ + `264560802292927144211230021750155988283029753927847924288850436812178022006` ~ + `408597793414273953252832688620479083497367463977081627995406363446761896298` ~ + `967177607401918269561385622811274398143647535024987050366350585544531063531` ~ + `7118554808325723941557169427279911052268935775`); + + auto b = BigInt( + `207672245542926038535480439528441949928508406405023044025560363701392340829` ~ + `852529131306106648201340460604257466180580583656068555417076345439694125326` ~ + `843947164365500055567495554645796102453565953360564114634705366335703491527` ~ + `429426780005741168078089657359833601261803592920462081364401456331489106355` ~ + `199133982282631108670436696758342051198891939367812305559960349479160308314` ~ + `068518200681530999860641597181672463704794566473241690395901768680673716414` ~ + `243691584391572899147223065906633310537507956952626106509069491302359792769` ~ + `378934570685117202046921464019396759638376362935855896435623442486036961070` ~ + `534574698959398017332214518246531363445309522357827985468581166065335726996` ~ + `711467464306784543112544076165391268106101754253962102479935962248302404638` ~ + `21737237102628470475027851189594709504`); + + BigInt c = a * b; // Crashes + + assert(c == BigInt( + `697137001950904057507249234183127244116872349433141878383548259425589716813` ~ + `135440660252012378417669596912108637127036044977634382385990472429604619344` ~ + `738746224291111527200379708978133071390303850450970292020176369525401803474` ~ + `998613408923490273129022167907826017408385746675184651576154302536663744109` ~ + `111018961065316024005076097634601030334948684412785487182572502394847587887` ~ + `507385831062796361152176364659197432600147716058873232435238712648552844428` ~ + `058885217631715287816333209463171932255049134340904981280717725999710525214` ~ + `161541960645335744430049558161514565159449390036287489478108344584188898872` ~ + `434914159748515512161981956372737022393466624249130107254611846175580584736` ~ + `276213025837422102290580044755202968610542057651282410252208599309841499843` ~ + `672251048622223867183370008181364966502137725166782667358559333222947265344` ~ + `524195551978394625568228658697170315141077913403482061673401937141405425042` ~ + `283546509102861986303306729882186190883772633960389974665467972016939172303` ~ + `653623175801495207204880400522581834672918935651426160175413277309985678579` ~ + `830872397214091472424064274864210953551447463312267310436493480881235642109` ~ + `668498742629676513172286703948381906930297135997498416573231570483993847269` ~ + `479552708416124555462530834668011570929850407031109157206202741051573633443` ~ + `58105600` + )); + } + /** - * Implements assignment operators of the form $(D BigInt op= BigInt). + * Implements assignment operators of the form `BigInt op= BigInt`. */ - BigInt opOpAssign(string op, T)(T y) pure nothrow + BigInt opOpAssign(string op, T)(T y) pure nothrow @safe scope return if ((op=="+" || op== "-" || op=="*" || op=="|" || op=="&" || op=="^" || op=="/" || op=="%") && is (T: BigInt)) { @@ -361,9 +461,8 @@ public: } /// - @system unittest + @safe unittest { - // @system because opOpAssign is @system auto x = BigInt("123"); auto y = BigInt("321"); x += y; @@ -371,9 +470,9 @@ public: } /** - * Implements binary operators between BigInts. + * Implements binary operators between `BigInt`s. */ - BigInt opBinary(string op, T)(T y) pure nothrow const + BigInt opBinary(string op, T)(T y) pure nothrow @safe const return scope if ((op=="+" || op == "*" || op=="-" || op=="|" || op=="&" || op=="^" || op=="/" || op=="%") && is (T: BigInt)) @@ -383,7 +482,7 @@ public: } /// - @system unittest + @safe unittest { auto x = BigInt("123"); auto y = BigInt("456"); @@ -392,19 +491,20 @@ public: } /** - * Implements binary operators between BigInt's and built-in integers. + * Implements binary operators between `BigInt`'s and built-in integers. */ - BigInt opBinary(string op, T)(T y) pure nothrow const + BigInt opBinary(string op, T)(T y) pure nothrow @safe const return scope if ((op=="+" || op == "*" || op=="-" || op=="/" || op=="|" || op=="&" || op=="^"|| op==">>" || op=="<<" || op=="^^") && isIntegral!T) { BigInt r = this; - return r.opOpAssign!(op)(y); + r.opOpAssign!(op)(y); + return r; } /// - @system unittest + @safe unittest { auto x = BigInt("123"); x *= 300; @@ -418,24 +518,26 @@ public: where applicable, according to the following table. $(TABLE , + $(TR $(TD `BigInt`) $(TD $(CODE_PERCENT)) $(TD `uint`) $(TD $(RARR)) $(TD `long`)) $(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 + auto opBinary(string op, T)(T y) pure nothrow @safe const if (op == "%" && isIntegral!T) { - assert(y != 0); + assert(y != 0, "% 0 not allowed"); + // BigInt % uint => long // BigInt % long => long // BigInt % ulong => BigInt // BigInt % other_type => int - static if (is(Unqual!T == long) || is(Unqual!T == ulong)) + static if (is(immutable T == immutable long) || is(immutable T == immutable ulong)) { auto r = this % BigInt(y); - static if (is(Unqual!T == long)) + static if (is(immutable T == immutable long)) { return r.toLong(); } @@ -448,7 +550,11 @@ public: else { immutable uint u = absUnsign(y); - int rem = BigUint.modInt(data, u); + static if (is(immutable T == immutable uint)) + alias R = long; + else + alias R = int; + R 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; @@ -456,7 +562,7 @@ public: } /// - @system unittest + @safe unittest { auto x = BigInt("1_000_000_500"); long l = 1_000_000L; @@ -472,16 +578,16 @@ public: /** Implements operators with built-in integers on the left-hand side and - BigInt on the right-hand side. + `BigInt` on the right-hand side. */ - BigInt opBinaryRight(string op, T)(T y) pure nothrow const + BigInt opBinaryRight(string op, T)(T y) pure nothrow @safe const if ((op=="+" || op=="*" || op=="|" || op=="&" || op=="^") && isIntegral!T) { return opBinary!(op)(y); } /// - @system unittest + @safe unittest { auto x = BigInt("100"); BigInt y = 123 + x; @@ -499,7 +605,7 @@ public: // BigInt = integer op BigInt /// ditto - BigInt opBinaryRight(string op, T)(T y) pure nothrow const + BigInt opBinaryRight(string op, T)(T y) pure nothrow @safe const if (op == "-" && isIntegral!T) { ulong u = absUnsign(y); @@ -515,7 +621,7 @@ public: // integer = integer op BigInt /// ditto - T opBinaryRight(string op, T)(T x) pure nothrow const + T opBinaryRight(string op, T)(T x) pure nothrow @safe const if ((op=="%" || op=="/") && isIntegral!T) { checkDivByZero(); @@ -540,9 +646,9 @@ public: // const unary operations /** - Implements BigInt unary operators. + Implements `BigInt` unary operators. */ - BigInt opUnary(string op)() pure nothrow const if (op=="+" || op=="-" || op=="~") + BigInt opUnary(string op)() pure nothrow @safe const if (op=="+" || op=="-" || op=="~") { static if (op=="-") { @@ -560,7 +666,7 @@ public: // non-const unary operations /// ditto - BigInt opUnary(string op)() pure nothrow if (op=="++" || op=="--") + BigInt opUnary(string op)() pure nothrow @safe if (op=="++" || op=="--") { static if (op=="++") { @@ -575,7 +681,7 @@ public: } /// - @system unittest + @safe unittest { auto x = BigInt("1234"); assert(-x == BigInt("-1234")); @@ -585,24 +691,38 @@ public: } /** - Implements BigInt equality test with other BigInt's and built-in - integer types. + Implements `BigInt` equality test with other `BigInt`'s and built-in + numeric types. */ - bool opEquals()(auto ref const BigInt y) const pure @nogc + bool opEquals()(auto ref const BigInt y) const pure @nogc @safe { return sign == y.sign && y.data == data; } /// ditto - bool opEquals(T)(T y) const pure nothrow @nogc if (isIntegral!T) + bool opEquals(T)(const T y) const pure nothrow @nogc @safe if (isIntegral!T) { if (sign != (y<0)) return 0; return data.opEquals(cast(ulong) absUnsign(y)); } + /// ditto + bool opEquals(T)(const T y) const pure nothrow @nogc if (isFloatingPoint!T) + { + return 0 == opCmp(y); + } + /// - @system unittest + @safe unittest + { + // Note that when comparing a BigInt to a float or double the + // full precision of the BigInt is always considered, unlike + // when comparing an int to a float or a long to a double. + assert(BigInt(123456789) != cast(float) 123456789); + } + + @safe unittest { auto x = BigInt("12345"); auto y = BigInt("12340"); @@ -616,16 +736,49 @@ public: assert(x != w); } + @safe unittest + { + import std.math.operations : nextDown, nextUp; + + const x = BigInt("0x1abc_de80_0000_0000_0000_0000_0000_0000"); + BigInt x1 = x + 1; + BigInt x2 = x - 1; + + const d = 0x1.abcde8p124; + assert(x == d); + assert(x1 != d); + assert(x2 != d); + assert(x != nextUp(d)); + assert(x != nextDown(d)); + assert(x != double.nan); + + const dL = 0x1.abcde8p124L; + assert(x == dL); + assert(x1 != dL); + assert(x2 != dL); + assert(x != nextUp(dL)); + assert(x != nextDown(dL)); + assert(x != real.nan); + + assert(BigInt(0) == 0.0f); + assert(BigInt(0) == 0.0); + assert(BigInt(0) == 0.0L); + assert(BigInt(0) == -0.0f); + assert(BigInt(0) == -0.0); + assert(BigInt(0) == -0.0L); + assert(BigInt("999_999_999_999_999_999_999_999_999_999_999_999_999") != float.infinity); + } + /** - Implements casting to bool. + Implements casting to `bool`. */ - T opCast(T:bool)() pure nothrow @nogc const + T opCast(T:bool)() pure nothrow @nogc @safe const { return !isZero(); } /// - @system unittest + @safe unittest { // Non-zero values are regarded as true auto x = BigInt("1"); @@ -644,7 +797,7 @@ public: Throws: $(REF ConvOverflowException, std,conv) if the number exceeds the target type's range. */ - T opCast(T:ulong)() /*pure*/ const + T opCast(T:ulong)() pure @safe const { if (isUnsigned!T && sign) { /* throw */ } @@ -667,12 +820,12 @@ public: import std.conv : ConvOverflowException; import std.string : format; throw new ConvOverflowException( - "BigInt(%d) cannot be represented as a %s" - .format(this, T.stringof)); + "BigInt(%s) cannot be represented as a %s" + .format(this.toDecimalString, T.stringof)); } /// - @system unittest + @safe unittest { import std.conv : to, ConvOverflowException; import std.exception : assertThrown; @@ -685,7 +838,7 @@ public: assertThrown!ConvOverflowException(BigInt("-1").to!ubyte); } - @system unittest + @safe unittest { import std.conv : to, ConvOverflowException; import std.exception : assertThrown; @@ -720,19 +873,182 @@ public: } /** - Implements casting to/from qualified BigInt's. + Implements casting to floating point types. + */ + T opCast(T)() @safe nothrow @nogc const if (isFloatingPoint!T) + { + return toFloat!(T, "nearest"); + } + + /// + @system unittest + { + assert(cast(float) BigInt("35540592535949172786332045140593475584") + == 35540592535949172786332045140593475584.0f); + assert(cast(double) BigInt("35540601499647381470685035515422441472") + == 35540601499647381470685035515422441472.0); + assert(cast(real) BigInt("35540601499647381470685035515422441472") + == 35540601499647381470685035515422441472.0L); + + assert(cast(float) BigInt("-0x1345_6780_0000_0000_0000_0000_0000") == -0x1.3456_78p+108f ); + assert(cast(double) BigInt("-0x1345_678a_bcde_f000_0000_0000_0000") == -0x1.3456_78ab_cdefp+108 ); + assert(cast(real) BigInt("-0x1345_678a_bcde_f000_0000_0000_0000") == -0x1.3456_78ab_cdefp+108L); + } + + /// Rounding when casting to floating point + @system unittest + { + // BigInts whose values cannot be exactly represented as float/double/real + // are rounded when cast to float/double/real. When cast to float or + // double or 64-bit real the rounding is strictly defined. When cast + // to extended-precision real the rounding rules vary by environment. + + // BigInts that fall somewhere between two non-infinite floats/doubles + // are rounded to the closer value when cast to float/double. + assert(cast(float) BigInt(0x1aaa_aae7) == 0x1.aaa_aaep+28f); + assert(cast(float) BigInt(0x1aaa_aaff) == 0x1.aaa_ab0p+28f); + assert(cast(float) BigInt(-0x1aaa_aae7) == -0x1.aaaaaep+28f); + assert(cast(float) BigInt(-0x1aaa_aaff) == -0x1.aaaab0p+28f); + + assert(cast(double) BigInt(0x1aaa_aaaa_aaaa_aa77) == 0x1.aaa_aaaa_aaaa_aa00p+60); + assert(cast(double) BigInt(0x1aaa_aaaa_aaaa_aaff) == 0x1.aaa_aaaa_aaaa_ab00p+60); + assert(cast(double) BigInt(-0x1aaa_aaaa_aaaa_aa77) == -0x1.aaa_aaaa_aaaa_aa00p+60); + assert(cast(double) BigInt(-0x1aaa_aaaa_aaaa_aaff) == -0x1.aaa_aaaa_aaaa_ab00p+60); + + // BigInts that fall exactly between two non-infinite floats/doubles + // are rounded away from zero when cast to float/double. (Note that + // in most environments this is NOT the same rounding rule rule used + // when casting int/long to float/double.) + assert(cast(float) BigInt(0x1aaa_aaf0) == 0x1.aaa_ab0p+28f); + assert(cast(float) BigInt(-0x1aaa_aaf0) == -0x1.aaaab0p+28f); + + assert(cast(double) BigInt(0x1aaa_aaaa_aaaa_aa80) == 0x1.aaa_aaaa_aaaa_ab00p+60); + assert(cast(double) BigInt(-0x1aaa_aaaa_aaaa_aa80) == -0x1.aaa_aaaa_aaaa_ab00p+60); + + // BigInts that are bounded on one side by the largest positive or + // most negative finite float/double and on the other side by infinity + // or -infinity are rounded as if in place of infinity was the value + // `2^^(T.max_exp)` when cast to float/double. + assert(cast(float) BigInt("999_999_999_999_999_999_999_999_999_999_999_999_999") == float.infinity); + assert(cast(float) BigInt("-999_999_999_999_999_999_999_999_999_999_999_999_999") == -float.infinity); + + assert(cast(double) BigInt("999_999_999_999_999_999_999_999_999_999_999_999_999") < double.infinity); + assert(cast(real) BigInt("999_999_999_999_999_999_999_999_999_999_999_999_999") < real.infinity); + } + + @safe unittest + { + // Test exponent overflow is correct. + assert(cast(float) BigInt(0x1fffffff) == 0x1.000000p+29f); + assert(cast(double) BigInt(0x1fff_ffff_ffff_fff0) == 0x1.000000p+61); + } + + private T toFloat(T, string roundingMode)() @safe nothrow @nogc const + if (__traits(isFloating, T) && (roundingMode == "nearest" || roundingMode == "truncate")) + { + import core.bitop : bsr; + enum performRounding = (roundingMode == "nearest"); + enum performTruncation = (roundingMode == "truncate"); + static assert(performRounding || performTruncation, "unrecognized rounding mode"); + enum int totalNeededBits = T.mant_dig + int(performRounding); + static if (totalNeededBits <= 64) + { + // We need to examine the top two 64-bit words, not just the top one, + // since the top word could have just a single significant bit. + const ulongLength = data.ulongLength; + const ulong w1 = data.peekUlong(ulongLength - 1); + if (w1 == 0) + return T(0); // Special: exponent should be all zero bits, plus bsr(w1) is undefined. + const ulong w2 = ulongLength < 2 ? 0 : data.peekUlong(ulongLength - 2); + const uint w1BitCount = bsr(w1) + 1; + ulong sansExponent = (w1 << (64 - w1BitCount)) | (w2 >>> (w1BitCount)); + size_t exponent = (ulongLength - 1) * 64 + w1BitCount + 1; + static if (performRounding) + { + sansExponent += 1UL << (64 - totalNeededBits); + if (0 <= cast(long) sansExponent) // Use high bit to detect overflow. + { + // Do not bother filling in the high bit of sansExponent + // with 1. It will be discarded by float and double and 80 + // bit real cannot be on this path with rounding enabled. + exponent += 1; + } + } + static if (T.mant_dig == float.mant_dig) + { + if (exponent >= T.max_exp) + return isNegative ? -T.infinity : T.infinity; + uint resultBits = (uint(isNegative) << 31) | // sign bit + ((0xFF & (exponent - float.min_exp)) << 23) | // exponent + cast(uint) ((sansExponent << 1) >>> (64 - 23)); // mantissa. + // TODO: remove @trusted lambda after DIP 1000 is enabled by default. + return (() @trusted => *cast(float*) &resultBits)(); + } + else static if (T.mant_dig == double.mant_dig) + { + if (exponent >= T.max_exp) + return isNegative ? -T.infinity : T.infinity; + ulong resultBits = (ulong(isNegative) << 63) | // sign bit + ((0x7FFUL & (exponent - double.min_exp)) << 52) | // exponent + ((sansExponent << 1) >>> (64 - 52)); // mantissa. + // TODO: remove @trusted lambda after DIP 1000 is enabled by default. + return (() @trusted => *cast(double*) &resultBits)(); + } + else + { + import core.math : ldexp; + return ldexp(isNegative ? -cast(real) sansExponent : cast(real) sansExponent, + cast(int) exponent - 65); + } + } + else + { + import core.math : ldexp; + const ulongLength = data.ulongLength; + if ((ulongLength - 1) * 64L > int.max) + return isNegative ? -T.infinity : T.infinity; + int scale = cast(int) ((ulongLength - 1) * 64); + const ulong w1 = data.peekUlong(ulongLength - 1); + if (w1 == 0) + return T(0); // Special: bsr(w1) is undefined. + int bitsStillNeeded = totalNeededBits - bsr(w1) - 1; + T acc = ldexp(cast(T) w1, scale); + for (ptrdiff_t i = ulongLength - 2; i >= 0 && bitsStillNeeded > 0; i--) + { + ulong w = data.peekUlong(i); + // To round towards zero we must make sure not to use too many bits. + if (bitsStillNeeded >= 64) + { + acc += ldexp(cast(T) w, scale -= 64); + bitsStillNeeded -= 64; + } + else + { + w = (w >>> (64 - bitsStillNeeded)) << (64 - bitsStillNeeded); + acc += ldexp(cast(T) w, scale -= 64); + break; + } + } + if (isNegative) + acc = -acc; + return cast(T) acc; + } + } + + /** + Implements casting to/from qualified `BigInt`'s. - Warning: Casting to/from $(D const) or $(D immutable) may break type + Warning: Casting to/from `const` or `immutable` may break type system guarantees. Use with care. */ T opCast(T)() pure nothrow @nogc const - if (is(Unqual!T == BigInt)) + if (is(immutable T == immutable BigInt)) { return this; } /// - @system unittest + @safe unittest { const(BigInt) x = BigInt("123"); BigInt y = cast() x; // cast away const @@ -743,17 +1059,17 @@ public: // 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. + Implements 3-way comparisons of `BigInt` with `BigInt` or `BigInt` with + built-in numeric types. */ - int opCmp(ref const BigInt y) pure nothrow @nogc const + int opCmp(ref const BigInt y) pure nothrow @nogc @safe 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) + int opCmp(T)(const T y) pure nothrow @nogc @safe const if (isIntegral!T) { if (sign != (y<0) ) return sign ? -1 : 1; @@ -761,7 +1077,38 @@ public: return sign? -cmp: cmp; } /// ditto - int opCmp(T:BigInt)(const T y) pure nothrow @nogc const + int opCmp(T)(const T y) nothrow @nogc @safe const if (isFloatingPoint!T) + { + import core.bitop : bsr; + import std.math.operations : cmp; + import std.math.traits : isFinite; + + const asFloat = toFloat!(T, "truncate"); + if (asFloat != y) + return cmp(asFloat, y); // handles +/- NaN. + if (!isFinite(y)) + return isNegative ? 1 : -1; + const ulongLength = data.ulongLength; + const w1 = data.peekUlong(ulongLength - 1); + if (w1 == 0) + return 0; // Special: bsr(w1) is undefined. + const numSignificantBits = (ulongLength - 1) * 64 + bsr(w1) + 1; + for (ptrdiff_t bitsRemainingToCheck = numSignificantBits - T.mant_dig, i = 0; + bitsRemainingToCheck > 0; i++, bitsRemainingToCheck -= 64) + { + auto word = data.peekUlong(i); + if (word == 0) + continue; + // Make sure we're only checking digits that are beyond + // the precision of `y`. + if (bitsRemainingToCheck < 64 && (word << (64 - bitsRemainingToCheck)) == 0) + break; // This can only happen on the last loop iteration. + return isNegative ? -1 : 1; + } + return 0; + } + /// ditto + int opCmp(T:BigInt)(const T y) pure nothrow @nogc @safe const { if (sign != y.sign) return sign ? -1 : 1; @@ -770,7 +1117,7 @@ public: } /// - @system unittest + @safe unittest { auto x = BigInt("100"); auto y = BigInt("10"); @@ -783,9 +1130,60 @@ public: assert(x < w); } + /// + @safe unittest + { + auto x = BigInt("0x1abc_de80_0000_0000_0000_0000_0000_0000"); + BigInt y = x - 1; + BigInt z = x + 1; + + double d = 0x1.abcde8p124; + assert(y < d); + assert(z > d); + assert(x >= d && x <= d); + + // Note that when comparing a BigInt to a float or double the + // full precision of the BigInt is always considered, unlike + // when comparing an int to a float or a long to a double. + assert(BigInt(123456789) < cast(float) 123456789); + } + + @safe unittest + { + assert(BigInt("999_999_999_999_999_999_999_999_999_999_999_999_999") < float.infinity); + + // Test `real` works. + auto x = BigInt("0x1abc_de80_0000_0000_0000_0000_0000_0000"); + BigInt y = x - 1; + BigInt z = x + 1; + + real d = 0x1.abcde8p124; + assert(y < d); + assert(z > d); + assert(x >= d && x <= d); + + // Test comparison for numbers of 64 bits or fewer. + auto w1 = BigInt(0x1abc_de80_0000_0000); + auto w2 = w1 - 1; + auto w3 = w1 + 1; + assert(w1.ulongLength == 1); + assert(w2.ulongLength == 1); + assert(w3.ulongLength == 1); + + double e = 0x1.abcde8p+60; + assert(w1 >= e && w1 <= e); + assert(w2 < e); + assert(w3 > e); + + real eL = 0x1.abcde8p+60; + assert(w1 >= eL && w1 <= eL); + assert(w2 < eL); + assert(w3 > eL); + } + /** - Returns: The value of this BigInt as a long, or +/- long.max if outside - the representable range. + Returns: The value of this `BigInt` as a `long`, or `long.max`/`long.min` + if outside the representable range. */ long toLong() @safe pure nothrow const @nogc { @@ -796,7 +1194,7 @@ public: } /// - @system unittest + @safe unittest { auto b = BigInt("12345"); long l = b.toLong(); @@ -804,7 +1202,7 @@ public: } /** - Returns: The value of this BigInt as an int, or +/- int.max if outside + Returns: The value of this `BigInt` as an `int`, or `int.max`/`int.min` if outside the representable range. */ int toInt() @safe pure nothrow @nogc const @@ -816,7 +1214,7 @@ public: } /// - @system unittest + @safe unittest { auto big = BigInt("5_000_000"); auto i = big.toInt(); @@ -828,24 +1226,24 @@ public: 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) + /// Number of significant `uint`s 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) + /// Number of significant `ulong`s 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. + /** Convert the `BigInt` to `string`, passing it to the given sink. * * Params: - * sink = A delegate for accepting possibly piecewise segments of the + * sink = An OutputRange for accepting possibly piecewise segments of the * formatted string. * formatString = A format string specifying the output format. * @@ -858,30 +1256,32 @@ public: * $(TR $(TD null) $(TD Default formatting (same as "d") )) * ) */ - void toString(scope void delegate(const (char)[]) sink, string formatString) const + void toString(Writer)(scope ref Writer sink, string formatString) const { auto f = FormatSpec!char(formatString); f.writeUpToNextSpec(sink); - toString(sink, f); + toString!Writer(sink, f); } /// ditto - void toString(scope void delegate(const(char)[]) sink, ref FormatSpec!char f) const + void toString(Writer)(scope ref Writer sink, scope const 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); + import std.range.primitives : put; + const spec = f.spec; + immutable hex = (spec == 'x' || spec == 'X'); + if (!(spec == 's' || spec == 'd' || spec =='o' || hex)) + throw new FormatException("Format specifier not understood: %" ~ spec); char[] buff; - if (f.spec == 'X') + if (spec == 'X') { buff = data.toHexString(0, '_', 0, f.flZero ? '0' : ' ', LetterCase.upper); } - else if (f.spec == 'x') + else if (spec == 'x') { buff = data.toHexString(0, '_', 0, f.flZero ? '0' : ' ', LetterCase.lower); } - else if (f.spec == 'o') + else if (spec == 'o') { buff = data.toOctalString(); } @@ -889,9 +1289,9 @@ public: { buff = data.toDecimalString(0); } - assert(buff.length > 0); + assert(buff.length > 0, "Invalid buffer length"); - char signChar = isNegative() ? '-' : 0; + char signChar = isNegative ? '-' : 0; auto minw = buff.length + (signChar ? 1 : 0); if (!hex && !signChar && (f.width == 0 || minw < f.width)) @@ -913,27 +1313,30 @@ public: if (!f.flDash && !f.flZero) foreach (i; 0 .. difw) - sink(" "); + put(sink, " "); if (signChar) - sink((&signChar)[0 .. 1]); + { + scope char[1] buf = signChar; + put(sink, buf[]); + } if (!f.flDash && f.flZero) foreach (i; 0 .. difw) - sink("0"); + put(sink, "0"); - sink(buff); + put(sink, buff); if (f.flDash) foreach (i; 0 .. difw) - sink(" "); + put(sink, " "); } /** - $(D toString) is rarely directly invoked; the usual way of using it is via + `toString` is rarely directly invoked; the usual way of using it is via $(REF format, std, format): */ - @system unittest + @safe unittest { import std.format : format; @@ -946,21 +1349,55 @@ public: assert(format("%o", x) == "133764340100"); } + // for backwards compatibility, see unittest below + /// ditto + void toString(scope void delegate(scope const(char)[]) sink, string formatString) const + { + toString!(void delegate(scope const(char)[]))(sink, formatString); + } + + // for backwards compatibility, see unittest below + /// ditto + void toString(scope void delegate(scope const(char)[]) sink, scope const ref FormatSpec!char f) const + { + toString!(void delegate(scope const(char)[]))(sink, f); + } + + // Backwards compatibility test + // BigInt.toString used to only accept a delegate sink function, but this does not work + // well with attributes such as @safe. A template function toString was added that + // works on OutputRanges, but when a delegate was passed in the form of an untyped + // lambda such as `str => dst.put(str)` the parameter type was inferred as `void` and + // the function failed to instantiate. + @system unittest + { + import std.format.spec : FormatSpec; + import std.array : appender; + BigInt num = 503; + auto dst = appender!string(); + num.toString(str => dst.put(str), null); + assert(dst[] == "503"); + num = 504; + auto f = FormatSpec!char(""); + num.toString(str => dst.put(str), f); + assert(dst[] == "503504"); + } + // 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 + Returns: A unique hash of the `BigInt`'s value suitable for use in a hash table. */ - size_t toHash() const @safe nothrow + size_t toHash() const @safe pure nothrow @nogc { return data.toHash() + sign; } /** - $(D toHash) is rarely directly invoked; it is implicitly used when + `toHash` is rarely directly invoked; it is implicitly used when BigInt is used as the key of an associative array. */ - @safe unittest + @safe pure unittest { string[BigInt] aa; aa[BigInt(123)] = "abc"; @@ -970,31 +1407,74 @@ public: assert(aa[BigInt(456)] == "def"); } + /** + * Gets the nth number in the underlying representation that makes up the whole + * `BigInt`. + * + * Params: + * T = the type to view the underlying representation as + * n = The nth number to retrieve. Must be less than $(LREF ulongLength) or + * $(LREF uintLength) with respect to `T`. + * Returns: + * The nth `ulong` in the representation of this `BigInt`. + */ + T getDigit(T = ulong)(size_t n) const + if (is(T == ulong) || is(T == uint)) + { + static if (is(T == ulong)) + { + assert(n < ulongLength(), "getDigit index out of bounds"); + return data.peekUlong(n); + } + else + { + assert(n < uintLength(), "getDigit index out of bounds"); + return data.peekUint(n); + } + } + + /// + @safe pure unittest + { + auto a = BigInt("1000"); + assert(a.ulongLength() == 1); + assert(a.getDigit(0) == 1000); + + assert(a.uintLength() == 1); + assert(a.getDigit!uint(0) == 1000); + + auto b = BigInt("2_000_000_000_000_000_000_000_000_000"); + assert(b.ulongLength() == 2); + assert(b.getDigit(0) == 4584946418820579328); + assert(b.getDigit(1) == 108420217); + + assert(b.uintLength() == 3); + assert(b.getDigit!uint(0) == 3489660928); + assert(b.getDigit!uint(1) == 1067516025); + assert(b.getDigit!uint(2) == 108420217); + } + private: - void negate() @safe pure nothrow @nogc + void negate() @safe pure nothrow @nogc scope { if (!data.isZero()) sign = !sign; } - bool isZero() pure const nothrow @nogc @safe + bool isZero() pure const nothrow @nogc @safe scope { return data.isZero(); } - bool isNegative() pure const nothrow @nogc @safe - { - return sign; - } + alias isNegative = sign; // Generate a runtime error if division by zero occurs - void checkDivByZero() pure const nothrow @safe + void checkDivByZero() pure const nothrow @safe scope { - if (isZero()) - throw new Error("BigInt division by zero"); + assert(!isZero(), "BigInt division by zero"); } } /// -@system unittest +@safe unittest { BigInt a = "9588669891916142"; BigInt b = "7452469135154800"; @@ -1020,27 +1500,29 @@ private: assert(h == a); assert(i == e); BigInt j = "-0x9A56_57f4_7B83_AB78"; + BigInt k = j; j ^^= 11; + assert(k ^^ 11 == j); } /** Params: - x = The $(D BigInt) to convert to a decimal $(D string). + x = The `BigInt` to convert to a decimal `string`. Returns: - A $(D string) that represents the $(D BigInt) as a decimal number. + A `string` that represents the `BigInt` as a decimal number. */ -string toDecimalString(const(BigInt) x) +string toDecimalString(const(BigInt) x) pure nothrow @safe { - string outbuff=""; - void sink(const(char)[] s) { outbuff ~= s; } - x.toString(&sink, "%d"); - return outbuff; + auto buff = x.data.toDecimalString(x.isNegative ? 1 : 0); + if (x.isNegative) + buff[0] = '-'; + return buff; } /// -@system unittest +@safe pure unittest { auto x = BigInt("123"); x *= 1000; @@ -1052,23 +1534,23 @@ string toDecimalString(const(BigInt) x) /** Params: - x = The $(D BigInt) to convert to a hexadecimal $(D string). + x = The `BigInt` to convert to a hexadecimal `string`. Returns: - A $(D string) that represents the $(D BigInt) as a hexadecimal (base 16) + A `string` that represents the `BigInt` as a hexadecimal (base 16) number in upper case. */ -string toHex(const(BigInt) x) +string toHex(const(BigInt) x) @safe { - string outbuff=""; - void sink(const(char)[] s) { outbuff ~= s; } - x.toString(&sink, "%X"); - return outbuff; + import std.array : appender; + auto outbuff = appender!string(); + x.toString(outbuff, "%X"); + return outbuff[]; } /// -@system unittest +@safe unittest { auto x = BigInt("123"); x *= 1000; @@ -1107,32 +1589,35 @@ if (isIntegral!T) } /// -nothrow pure @system +nothrow pure @safe unittest { assert((-1).absUnsign == 1); assert(1.absUnsign == 1); } -nothrow pure @system +nothrow pure @safe unittest { BigInt a, b; a = 1; b = 2; auto c = a + b; + assert(c == 3); } -nothrow pure @system +nothrow pure @safe unittest { long a; BigInt b; auto c = a + b; + assert(c == 0); auto d = b + a; + assert(d == 0); } -nothrow pure @system +nothrow pure @safe unittest { BigInt x = 1, y = 2; @@ -1157,7 +1642,7 @@ unittest assert(incr == BigInt(1)); } -@system unittest +@safe unittest { // Radix conversion assert( toDecimalString(BigInt("-1_234_567_890_123_456_789")) @@ -1174,7 +1659,7 @@ unittest 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 + char[] s1 = "123".dup; // https://issues.dlang.org/show_bug.cgi?id=8164 assert(BigInt(s1) == 123); char[] s2 = "0xABC".dup; assert(BigInt(s2) == 2748); @@ -1186,16 +1671,16 @@ unittest b = long.max / a; assert( b == long.max /(ulong.max - 5)); assert(BigInt(1) - 1 == 0); - assert((-4) % BigInt(5) == -4); // bug 5928 + assert((-4) % BigInt(5) == -4); // https://issues.dlang.org/show_bug.cgi?id=5928 assert(BigInt(-4) % BigInt(5) == -4); - assert(BigInt(2)/BigInt(-3) == BigInt(0)); // bug 8022 - assert(BigInt("-1") > long.min); // bug 9548 + assert(BigInt(2)/BigInt(-3) == BigInt(0)); // https://issues.dlang.org/show_bug.cgi?id=8022 + assert(BigInt("-1") > long.min); // https://issues.dlang.org/show_bug.cgi?id=9548 assert(toDecimalString(BigInt("0000000000000000000000000000000000000000001234567")) == "1234567"); } -@system unittest // Minimum signed value bug tests. +@safe unittest // Minimum signed value bug tests. { assert(BigInt("-0x8000000000000000") == BigInt(long.min)); assert(BigInt("-0x8000000000000000")+1 > BigInt(long.min)); @@ -1216,7 +1701,8 @@ unittest assert((BigInt(int.min)-1)%int.min == -1); } -@system unittest // Recursive division, bug 5568 + // Recursive division (https://issues.dlang.org/show_bug.cgi?id=5568) +@safe unittest { enum Z = 4843; BigInt m = (BigInt(1) << (Z*8) ) - 1; @@ -1236,17 +1722,17 @@ unittest BigInt w = c - b + a; assert(w % m == 0); - // Bug 6819. ^^ + // https://issues.dlang.org/show_bug.cgi?id=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 + // https://issues.dlang.org/show_bug.cgi?id=7993 BigInt n7793 = 10; assert( n7793 / 1 == 10); - // Bug 7973 + // https://issues.dlang.org/show_bug.cgi?id=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; @@ -1257,15 +1743,15 @@ unittest assert(v7973 == 2551700137); v7973 %= i7973; assert(v7973 == 2551700137); - // 8165 + // https://issues.dlang.org/show_bug.cgi?id=8165 BigInt[2] a8165; a8165[0] = a8165[1] = 1; } -@system unittest +@safe unittest { import std.array; - import std.format; + import std.format.write : formattedWrite; immutable string[][] table = [ /* fmt, +10 -10 */ @@ -1313,10 +1799,10 @@ unittest } } -@system unittest +@safe unittest { import std.array; - import std.format; + import std.format.write : formattedWrite; immutable string[][] table = [ /* fmt, +10 -10 */ @@ -1364,10 +1850,10 @@ unittest } } -@system unittest +@safe unittest { import std.array; - import std.format; + import std.format.write : formattedWrite; immutable string[][] table = [ /* fmt, +10 -10 */ @@ -1415,11 +1901,11 @@ unittest } } -// 6448 -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=6448 +@safe unittest { import std.array; - import std.format; + import std.format.write : formattedWrite; auto w1 = appender!string(); auto w2 = appender!string(); @@ -1429,7 +1915,7 @@ unittest BigInt bx = x; formattedWrite(w2, "%010d", bx); assert(w1.data == w2.data); - //8011 + // https://issues.dlang.org/show_bug.cgi?id=8011 BigInt y = -3; ++y; assert(y.toLong() == -2); @@ -1444,13 +1930,13 @@ unittest @safe unittest { - import std.math : abs; - auto r = abs(BigInt(-1000)); // 6486 + import std.math.algebraic : abs; + auto r = abs(BigInt(-1000)); // https://issues.dlang.org/show_bug.cgi?id=6486 assert(r == 1000); - auto r2 = abs(const(BigInt)(-500)); // 11188 + auto r2 = abs(const(BigInt)(-500)); // https://issues.dlang.org/show_bug.cgi?id=11188 assert(r2 == 500); - auto r3 = abs(immutable(BigInt)(-733)); // 11188 + auto r3 = abs(immutable(BigInt)(-733)); // https://issues.dlang.org/show_bug.cgi?id=11188 assert(r3 == 733); // opCast!bool @@ -1458,7 +1944,8 @@ unittest assert(one && !zero); } -@system unittest // 6850 +// https://issues.dlang.org/show_bug.cgi?id=6850 +@safe unittest { pure long pureTest() { BigInt a = 1; @@ -1470,7 +1957,9 @@ unittest assert(pureTest() == 1337); } -@system unittest // 8435 & 10118 +// https://issues.dlang.org/show_bug.cgi?id=8435 +// https://issues.dlang.org/show_bug.cgi?id=10118 +@safe unittest { auto i = BigInt(100); auto j = BigInt(100); @@ -1494,22 +1983,23 @@ unittest assert(keys.empty); } -@system unittest // 11148 +// https://issues.dlang.org/show_bug.cgi?id=11148 +@safe unittest { void foo(BigInt) {} const BigInt cbi = 3; immutable BigInt ibi = 3; - assert(__traits(compiles, foo(cbi))); - assert(__traits(compiles, foo(ibi))); + foo(cbi); + foo(ibi); import std.conv : to; import std.meta : AliasSeq; - foreach (T1; AliasSeq!(BigInt, const(BigInt), immutable(BigInt))) + static foreach (T1; AliasSeq!(BigInt, const(BigInt), immutable(BigInt))) { - foreach (T2; AliasSeq!(BigInt, const(BigInt), immutable(BigInt))) - { + static foreach (T2; AliasSeq!(BigInt, const(BigInt), immutable(BigInt))) + {{ T1 t1 = 2; T2 t2 = t1; @@ -1524,20 +2014,24 @@ unittest assert(t2_2 == t1); assert(t2_2 == 2); - } + }} } BigInt n = 2; n *= 2; + assert(n == 4); } -@safe unittest // 8167 +// https://issues.dlang.org/show_bug.cgi?id=8167 +@safe unittest { BigInt a = BigInt(3); BigInt b = BigInt(a); + assert(b == 3); } -@safe unittest // 9061 +// https://issues.dlang.org/show_bug.cgi?id=9061 +@safe unittest { long l1 = 0x12345678_90ABCDEF; long l2 = 0xFEDCBA09_87654321; @@ -1556,7 +2050,8 @@ unittest assert(l5 == b5); } -@system unittest // 11600 +// https://issues.dlang.org/show_bug.cgi?id=11600 +@safe unittest { import std.conv; import std.exception : assertThrown; @@ -1571,20 +2066,22 @@ unittest assertThrown!ConvException(to!BigInt("-123four")); } -@safe unittest // 11583 +// https://issues.dlang.org/show_bug.cgi?id=11583 +@safe unittest { BigInt x = 0; assert((x > 0) == false); } -@system unittest // 13391 +// https://issues.dlang.org/show_bug.cgi?id=13391 +@safe unittest { BigInt x1 = "123456789"; BigInt x2 = "123456789123456789"; BigInt x3 = "123456789123456789123456789"; import std.meta : AliasSeq; - foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + static 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); @@ -1608,20 +2105,30 @@ unittest assert(x2 == 1); } -@system unittest // 13963 +// https://issues.dlang.org/show_bug.cgi?id=13963 +@safe unittest { BigInt x = 1; import std.meta : AliasSeq; - foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint)) + static foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int)) { assert(is(typeof(x % Int(1)) == int)); } + assert(is(typeof(x % 1U) == long)); assert(is(typeof(x % 1L) == long)); assert(is(typeof(x % 1UL) == BigInt)); + auto x0 = BigInt(uint.max - 1); auto x1 = BigInt(8); + assert(x1 / x == x1); auto x2 = -BigInt(long.min) + 1; + // uint + assert( x0 % uint.max == x0 % BigInt(uint.max)); + assert(-x0 % uint.max == -x0 % BigInt(uint.max)); + assert( x0 % uint.max == long(uint.max - 1)); + assert(-x0 % uint.max == -long(uint.max - 1)); + // long assert(x1 % 2L == 0L); assert(-x1 % 2L == 0L); @@ -1650,31 +2157,32 @@ unittest assert(-x2 % ulong.max == -x2); } -@system unittest // 14124 +// https://issues.dlang.org/show_bug.cgi?id=14124 +@safe unittest { auto x = BigInt(-3); x %= 3; - assert(!x.isNegative()); - assert(x.isZero()); + assert(!x.isNegative); + assert(x.isZero); x = BigInt(-3); x %= cast(ushort) 3; - assert(!x.isNegative()); - assert(x.isZero()); + assert(!x.isNegative); + assert(x.isZero); x = BigInt(-3); x %= 3L; - assert(!x.isNegative()); - assert(x.isZero()); + assert(!x.isNegative); + assert(x.isZero); x = BigInt(3); x %= -3; - assert(!x.isNegative()); - assert(x.isZero()); + assert(!x.isNegative); + assert(x.isZero); } -// issue 15678 -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=15678 +@safe unittest { import std.exception : assertThrown; assertThrown!ConvException(BigInt("")); @@ -1682,8 +2190,8 @@ unittest assertThrown!ConvException(BigInt("1234PUKE")); } -// Issue 6447 -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=6447 +@safe unittest { import std.algorithm.comparison : equal; import std.range : iota; @@ -1698,8 +2206,151 @@ unittest ])); } -// Issue 17330 -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=17330 +@safe unittest { auto b = immutable BigInt("123"); + assert(b == 123); +} + +// https://issues.dlang.org/show_bug.cgi?id=14767 +@safe pure unittest +{ + static immutable a = BigInt("340282366920938463463374607431768211455"); + assert(a == BigInt("340282366920938463463374607431768211455")); + + BigInt plusTwo(in BigInt n) + { + return n + 2; + } + + enum BigInt test1 = BigInt(123); + enum BigInt test2 = plusTwo(test1); + assert(test2 == 125); +} + +/** + * Finds the quotient and remainder for the given dividend and divisor in one operation. + * + * Params: + * dividend = the $(LREF BigInt) to divide + * divisor = the $(LREF BigInt) to divide the dividend by + * quotient = is set to the result of the division + * remainder = is set to the remainder of the division + */ +void divMod(const BigInt dividend, const BigInt divisor, out BigInt quotient, out BigInt remainder) pure nothrow @safe +{ + BigUint q, r; + BigUint.divMod(dividend.data, divisor.data, q, r); + quotient.sign = dividend.sign != divisor.sign; + quotient.data = q; + remainder.sign = dividend.sign; + remainder.data = r; +} + +/// +@safe pure nothrow unittest +{ + auto a = BigInt(123); + auto b = BigInt(25); + BigInt q, r; + + divMod(a, b, q, r); + + assert(q == 4); + assert(r == 23); + assert(q * b + r == a); +} + +// https://issues.dlang.org/show_bug.cgi?id=18086 +@safe pure nothrow unittest +{ + BigInt q = 1; + BigInt r = 1; + BigInt c = 1024; + BigInt d = 100; + + divMod(c, d, q, r); + assert(q == 10); + assert(r == 24); + assert((q * d + r) == c); + + divMod(c, -d, q, r); + assert(q == -10); + assert(r == 24); + assert(q * -d + r == c); + + divMod(-c, -d, q, r); + assert(q == 10); + assert(r == -24); + assert(q * -d + r == -c); + + divMod(-c, d, q, r); + assert(q == -10); + assert(r == -24); + assert(q * d + r == -c); +} + +// https://issues.dlang.org/show_bug.cgi?id=19740 +@safe unittest +{ + BigInt a = BigInt( + "241127122100380210001001124020210001001100000200003101000062221012075223052000021042250111300200000000000" ~ + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + BigInt b = BigInt( + "700200000000500418321000401140010110000022007221432000000141020011323301104104060202100200457210001600142" ~ + "000001012245300100001110215200000000120000000000000000000000000000000000000000000000000000000000000000000" ~ + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + + BigInt c = a * b; + assert(c == BigInt( + "1688372108948068874722901180228375682334987075822938736581472847151834613694489486296103575639363261807341" ~ + "3910091006778604956808730652275328822700182498926542563654351871390166691461743896850906716336187966456064" ~ + "2702007176328110013356024000000000000000000000000000000000000000000000000000000000000000000000000000000000" ~ + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" ~ + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); +} + +@safe unittest +{ + auto n = BigInt("1234"d); +} + +/** +Fast power modulus calculation for $(LREF BigInt) operands. +Params: + base = the $(LREF BigInt) is basic operands. + exponent = the $(LREF BigInt) is power exponent of base. + modulus = the $(LREF BigInt) is modules to be modular of base ^ exponent. +Returns: + The power modulus value of (base ^ exponent) % modulus. +*/ +BigInt powmod(BigInt base, BigInt exponent, BigInt modulus) pure nothrow @safe +{ + BigInt result = 1; + + while (exponent) + { + if (exponent.data.peekUint(0) & 1) + { + result = (result * base) % modulus; + } + + auto tmp = base % modulus; + base = (tmp * tmp) % modulus; + exponent >>= 1; + } + + return result; +} + +/// for powmod +@safe unittest +{ + BigInt base = BigInt("123456789012345678901234567890"); + BigInt exponent = BigInt("1234567890123456789012345678901234567"); + BigInt modulus = BigInt("1234567"); + + BigInt result = powmod(base, exponent, modulus); + assert(result == 359079); } diff --git a/libphobos/src/std/bitmanip.d b/libphobos/src/std/bitmanip.d index 7802dff..2dd6211 100644 --- a/libphobos/src/std/bitmanip.d +++ b/libphobos/src/std/bitmanip.d @@ -4,6 +4,7 @@ Bit-level manipulation facilities. $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD Bit constructs) $(TD @@ -32,58 +33,59 @@ $(TR $(TD Tagging) $(TD $(LREF taggedClassRef) $(LREF taggedPointer) )) -) +)) -Copyright: Copyright Digital Mars 2007 - 2011. +Copyright: Copyright The D Language Foundation 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, + $(HTTP jmdavisprog.com, Jonathan M Davis), Alex Rønne Petersen, Damian Ziemba, Amaury SECHET -Source: $(PHOBOSSRC std/_bitmanip.d) +Source: $(PHOBOSSRC std/bitmanip.d) */ /* - Copyright Digital Mars 2007 - 2012. + Copyright The D Language Foundation 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) +private string myToString(ulong n) pure @safe { import core.internal.string : UnsignedStringBuf, unsignedToTempString; UnsignedStringBuf buf; auto s = unsignedToTempString(n, buf); - return cast(string) s ~ (n > uint.max ? "UL" : "U"); + // pure allows implicit cast to string + return s ~ (n > uint.max ? "UL" : "U"); } +@safe pure unittest +{ + assert(myToString(5) == "5U"); + assert(myToString(uint.max) == "4294967295U"); + assert(myToString(uint.max + 1UL) == "4294967296UL"); +} + + 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 = ""; + enum createAccessors = ""; } else static if (len == 0) { // Fields of length 0 are always zero - enum result = "enum "~T.stringof~" "~name~" = 0;\n"; + enum createAccessors = "enum "~T.stringof~" "~name~" = 0;\n"; } else { @@ -107,8 +109,7 @@ private template createAccessors( static if (is(T == bool)) { - static assert(len == 1); - enum result = + enum createAccessors = // getter "@property bool " ~ name ~ "() @safe pure nothrow @nogc const { return " ~"("~store~" & "~myToString(maskAllElse)~") != 0;}\n" @@ -120,7 +121,7 @@ private template createAccessors( else { // getter - enum result = "@property "~T.stringof~" "~name~"() @safe pure nothrow @nogc const { auto result = " + enum createAccessors = "@property "~T.stringof~" "~name~"() @safe pure nothrow @nogc const { auto result = " ~"("~store~" & " ~ myToString(maskAllElse) ~ ") >>" ~ myToString(offset) ~ ";" @@ -149,7 +150,7 @@ private template createAccessors( private template createStoreName(Ts...) { static if (Ts.length < 2) - enum createStoreName = ""; + enum createStoreName = "_bf"; else enum createStoreName = "_" ~ Ts[1] ~ createStoreName!(Ts[3 .. $]); } @@ -168,22 +169,24 @@ private template createStorageAndFields(Ts...) alias StoreType = ulong; else { - static assert(false, "Field widths must sum to 8, 16, 32, or 64"); + import std.conv : to; + static assert(false, "Field widths must sum to 8, 16, 32, or 64, not " ~ to!string(Size)); alias StoreType = ulong; // just to avoid another error msg } - enum result + + enum createStorageAndFields = "private " ~ StoreType.stringof ~ " " ~ Name ~ ";" - ~ createFields!(Name, 0, Ts).result; + ~ createFields!(Name, 0, Ts); } 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; + enum createFields + = createAccessors!(store, Ts[0], Ts[1], Ts[2], offset) + ~ createFields!(store, offset + Ts[2], Ts[3 .. $]); else - enum result = ""; + enum createFields = ""; } private ulong getBitsForAlign(ulong a) @@ -220,7 +223,7 @@ private template createReferenceAccessor(string store, T, ulong bits, string nam ~" (("~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; + enum createReferenceAccessor = storage ~ storage_accessor ~ ref_accessor; } private template sizeOfBitField(T...) @@ -238,135 +241,154 @@ private template createTaggedReference(T, ulong a, string name, Ts...) "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; + enum createTaggedReference + = createReferenceAccessor!(StoreName, T, sizeOfBitField!Ts, name) + ~ createFields!(StoreName, 0, Ts, size_t, "", T.sizeof * 8 - sizeOfBitField!Ts); } /** -Allows creating bit fields inside $(D_PARAM struct)s and $(D_PARAM -class)es. - -Example: +Allows creating `bitfields` inside `structs`, `classes` and `unions`. + +A `bitfield` consists of one or more entries with a fixed number of +bits reserved for each of the entries. The types of the entries can +be `bool`s, integral types or enumerated types, arbitrarily mixed. +The most efficient type to store in `bitfields` is `bool`, followed +by unsigned types, followed by signed types. + +Each non-`bool` entry of the `bitfield` will be represented by the +number of bits specified by the user. The minimum and the maximum +numbers that represent this domain can be queried by using the name +of the variable followed by `_min` or `_max`. + +Limitation: The number of bits in a `bitfield` is limited to 8, 16, +32 or 64. If padding is needed, an entry should be explicitly +allocated with an empty name. + +Implementation_details: `Bitfields` are internally stored in an +`ubyte`, `ushort`, `uint` or `ulong` depending on the number of bits +used. The bits are filled in the order given by the parameters, +starting with the lowest significant bit. The name of the (private) +variable used for saving the `bitfield` is created by a prefix `_bf` +and concatenating all of the variable names, each preceded by an +underscore. + +Params: T = A list of template parameters divided into chunks of 3 + items. Each chunk consists (in this order) of a type, a + name and a number. Together they define an entry + of the `bitfield`: a variable of the given type and name, + which can hold as many bits as the number denotes. + +Returns: A string that can be used in a `mixin` to add the `bitfield`. + +See_Also: $(REF BitFlags, std,typecons) +*/ +string bitfields(T...)() +{ + import std.conv : to; ----- -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; ----- + static assert(T.length % 3 == 0, + "Wrong number of arguments (" ~ to!string(T.length) ~ "): Must be a multiple of 3"); -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. + static foreach (i, ARG; T) + { + static if (i % 3 == 0) + static assert(is (typeof({ARG tmp = cast (ARG)0; if (ARG.min < 0) {} }())), + "Integral type or `bool` expected, found " ~ ARG.stringof); -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. + // would be nice to check for valid variable names too + static if (i % 3 == 1) + static assert(is (typeof(ARG) : string), + "Variable name expected, found " ~ ARG.stringof); -Example: + static if (i % 3 == 2) + { + static assert(is (typeof({ulong tmp = ARG;}())), + "Integral value expected, found " ~ ARG.stringof); ----- -struct A -{ - mixin(bitfields!( - bool, "flag1", 1, - bool, "flag2", 1, - uint, "", 6)); -} ----- + static if (T[i-1] != "") + { + static assert(!is (T[i-2] : bool) || ARG <= 1, + "Type `bool` is only allowed for single-bit fields"); -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. -*/ + static assert(ARG >= 0 && ARG <= T[i-2].sizeof * 8, + "Wrong size of bitfield: " ~ ARG.stringof); + } + } + } -template bitfields(T...) -{ - enum { bitfields = createStorageAndFields!T.result } + return createStorageAndFields!T; } /** -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. +Create a `bitfield` pack of eight bits, which fit in +one `ubyte`. The `bitfields` are allocated starting from the +least significant bit, i.e. `x` occupies the two least significant bits +of the `bitfields` storage. */ - -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)); + mixin(bitfields!( + uint, "x", 2, + int, "y", 3, + uint, "z", 2, + bool, "flag", 1)); } + A obj; - obj.x = new uint; - obj.b1 = true; - obj.b2 = false; + obj.x = 2; + obj.z = obj.x; + + assert(obj.x == 2); + assert(obj.y == 0); + assert(obj.z == 2); + assert(obj.flag == 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. +The sum of all bit lengths in one `bitfield` instantiation +must be exactly 8, 16, 32, or 64. If padding is needed, just allocate +one bitfield with an empty name. */ - -template taggedClassRef(T, string name, Ts...) -if (is(T == class)) +@safe unittest { - enum taggedClassRef = createTaggedReference!(T, 8, name, Ts).result; + struct A + { + mixin(bitfields!( + bool, "flag1", 1, + bool, "flag2", 1, + uint, "", 6)); + } + + A a; + assert(a.flag1 == 0); + a.flag1 = 1; + assert(a.flag1 == 1); + a.flag1 = 0; + assert(a.flag1 == 0); } -/// +/// enums can be used too @safe unittest { - struct A + enum ABC { A, B, C } + struct EnumTest { - int a; - mixin(taggedClassRef!( - Object, "o", - uint, "i", 2)); + mixin(bitfields!( + ABC, "x", 2, + bool, "y", 1, + ubyte, "z", 5)); } - A obj; - obj.o = new Object(); - obj.i = 3; } @safe pure nothrow @nogc unittest { - // Degenerate bitfields (#8474 / #11160) tests mixed with range tests + // Degenerate bitfields tests mixed with range tests + // https://issues.dlang.org/show_bug.cgi?id=8474 + // https://issues.dlang.org/show_bug.cgi?id=11160 struct Test1 { mixin(bitfields!(uint, "a", 32, @@ -430,73 +452,9 @@ unittest 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))); -} - +// https://issues.dlang.org/show_bug.cgi?id=6686 @safe unittest { - // Bug #6686 union S { ulong bits = ulong.max; mixin (bitfields!( @@ -511,9 +469,9 @@ unittest assert(num.bits == 0xFFFF_FFFF_8000_0001uL); } +// https://issues.dlang.org/show_bug.cgi?id=5942 @safe unittest { - // Bug #5942 struct S { mixin(bitfields!( @@ -576,7 +534,7 @@ unittest assert(i.checkExpectations(true, 7, 7)); } - //Bug# 8876 + //https://issues.dlang.org/show_bug.cgi?id=8876 { struct MoreIntegrals { bool checkExpectations(uint eu, ushort es, uint ei) { return u == eu && s == es && i == ei; } @@ -629,12 +587,11 @@ unittest assert(f.checkExpectations(true)); } -// Issue 12477 +// https://issues.dlang.org/show_bug.cgi?id=12477 @system unittest { import core.exception : AssertError; import std.algorithm.searching : canFind; - import std.bitmanip : bitfields; static struct S { @@ -654,26 +611,184 @@ unittest { 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: +// https://issues.dlang.org/show_bug.cgi?id=15305 +@safe unittest +{ + struct S { + mixin(bitfields!( + bool, "alice", 1, + ulong, "bob", 63, + )); + } ----- -struct FloatRep + S s; + s.bob = long.max - 1; + s.alice = false; + assert(s.bob == long.max - 1); +} + +// https://issues.dlang.org/show_bug.cgi?id=21634 +@safe unittest { - union + struct A + { + mixin(bitfields!(int, "", 1, + int, "gshared", 7)); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=21725 +@safe unittest +{ + struct S { - float value; mixin(bitfields!( - uint, "fraction", 23, - ubyte, "exponent", 8, - bool, "sign", 1)); + uint, q{foo}, 4, + uint, null, 4, + )); } - enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1; } ----- + +/** +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 +`uint*` as specified by the first argument, and is named x, as specified by the second +argument. + +Following arguments works the same way as `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); +} + +/// +@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; +} + +@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); +} + +/** +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 `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); +} + +/// +@safe unittest +{ + struct A + { + int a; + mixin(taggedClassRef!( + Object, "o", + uint, "i", 2)); + } + A obj; + obj.o = new Object(); + obj.i = 3; +} + +@system unittest +{ + 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))); +} + +/** + Allows manipulating the fraction, exponent, and sign parts of a + `float` separately. The definition is: + +---- struct FloatRep { union @@ -686,10 +801,13 @@ struct FloatRep } enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1; } +---- +*/ +alias FloatRep = FloatingPointRepresentation!float; /** Allows manipulating the fraction, exponent, and sign parts of a - $(D_PARAM double) separately. The definition is: + `double` separately. The definition is: ---- struct DoubleRep @@ -706,23 +824,132 @@ struct DoubleRep } ---- */ +alias DoubleRep = FloatingPointRepresentation!double; -struct DoubleRep +private struct FloatingPointRepresentation(T) { + static if (is(T == float)) + { + enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1; + alias FractionType = uint; + alias ExponentType = ubyte; + } + else + { + enum uint bias = 1023, fractionBits = 52, exponentBits = 11, signBits = 1; + alias FractionType = ulong; + alias ExponentType = ushort; + } + union { - double value; + T value; mixin(bitfields!( - ulong, "fraction", 52, - ushort, "exponent", 11, - bool, "sign", 1)); + FractionType, "fraction", fractionBits, + ExponentType, "exponent", exponentBits, + bool, "sign", signBits)); } - enum uint bias = 1023, signBits = 1, fractionBits = 52, exponentBits = 11; } +/// +@safe unittest +{ + FloatRep rep = {value: 0}; + assert(rep.fraction == 0); + assert(rep.exponent == 0); + assert(!rep.sign); + + rep.value = 42; + assert(rep.fraction == 2621440); + assert(rep.exponent == 132); + assert(!rep.sign); + + rep.value = 10; + assert(rep.fraction == 2097152); + assert(rep.exponent == 130); +} + +/// +@safe unittest +{ + FloatRep rep = {value: 1}; + assert(rep.fraction == 0); + assert(rep.exponent == 127); + assert(!rep.sign); + + rep.exponent = 126; + assert(rep.value == 0.5); + + rep.exponent = 130; + assert(rep.value == 8); +} + +/// +@safe unittest +{ + FloatRep rep = {value: 1}; + rep.value = -0.5; + assert(rep.fraction == 0); + assert(rep.exponent == 126); + assert(rep.sign); + + rep.value = -1. / 3; + assert(rep.fraction == 2796203); + assert(rep.exponent == 125); + assert(rep.sign); +} + +/// +@safe unittest +{ + DoubleRep rep = {value: 0}; + assert(rep.fraction == 0); + assert(rep.exponent == 0); + assert(!rep.sign); + + rep.value = 42; + assert(rep.fraction == 1407374883553280); + assert(rep.exponent == 1028); + assert(!rep.sign); + + rep.value = 10; + assert(rep.fraction == 1125899906842624); + assert(rep.exponent == 1026); +} + +/// +@safe unittest +{ + DoubleRep rep = {value: 1}; + assert(rep.fraction == 0); + assert(rep.exponent == 1023); + assert(!rep.sign); + + rep.exponent = 1022; + assert(rep.value == 0.5); + + rep.exponent = 1026; + assert(rep.value == 8); +} + +/// +@safe unittest +{ + DoubleRep rep = {value: 1}; + rep.value = -0.5; + assert(rep.fraction == 0); + assert(rep.exponent == 1022); + assert(rep.sign); + + rep.value = -1. / 3; + assert(rep.fraction == 1501199875790165); + assert(rep.exponent == 1021); + assert(rep.sign); +} + +/// Reading @safe unittest { - // test reading DoubleRep x; x.value = 1.0; assert(x.fraction == 0 && x.exponent == 1023 && !x.sign); @@ -730,50 +957,30 @@ struct DoubleRep 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)); - } } +/// Writing @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); + DoubleRep x; + x.fraction = 1125899906842624; + x.exponent = 1025; + x.sign = true; + assert(x.value == -5.0); } /** - * An array of bits. - */ - +A dynamic array of bits. Each bit in a `BitArray` can be manipulated individually +or by the standard bitwise operators `&`, `|`, `^`, `~`, `>>`, `<<` and also by +other effective member functions; most of them work relative to the `BitArray`'s +dimension (see $(LREF dim)), instead of its $(LREF length). +*/ struct BitArray { private: - import core.bitop : bts, btr, bsf, bt; - import std.format : FormatSpec; + import core.bitop : btc, bts, btr, bsf, bt; + import std.format.spec : FormatSpec; size_t _len; size_t* _ptr; @@ -799,27 +1006,182 @@ private: } public: - /********************************************** - * Gets the amount of native words backing this $(D BitArray). - */ - @property size_t dim() const @nogc pure nothrow @safe + /** + Creates a `BitArray` from a `bool` array, such that `bool` values read + from left to right correspond to subsequent bits in the `BitArray`. + + Params: ba = Source array of `bool` values. + */ + this(in bool[] ba) nothrow pure + { + length = ba.length; + foreach (i, b; ba) + { + this[i] = b; + } + } + + /// + @system unittest + { + import std.algorithm.comparison : equal; + + bool[] input = [true, false, false, true, true]; + auto a = BitArray(input); + assert(a.length == 5); + assert(a.bitsSet.equal([0, 3, 4])); + + // This also works because an implicit cast to bool[] occurs for this array. + auto b = BitArray([0, 0, 1]); + assert(b.length == 3); + assert(b.bitsSet.equal([2])); + } + + /// + @system unittest + { + import std.algorithm.comparison : equal; + import std.array : array; + import std.range : iota, repeat; + + BitArray a = true.repeat(70).array; + assert(a.length == 70); + assert(a.bitsSet.equal(iota(0, 70))); + } + + /** + Creates a `BitArray` from the raw contents of the source array. The + source array is not copied but simply acts as the underlying array + of bits, which stores data as `size_t` units. + + That means a particular care should be taken when passing an array + of a type different than `size_t`, firstly because its length should + be a multiple of `size_t.sizeof`, and secondly because how the bits + are mapped: + --- + size_t[] source = [1, 2, 3, 3424234, 724398, 230947, 389492]; + enum sbits = size_t.sizeof * 8; + auto ba = BitArray(source, source.length * sbits); + foreach (n; 0 .. source.length * sbits) + { + auto nth_bit = cast(bool) (source[n / sbits] & (1L << (n % sbits))); + assert(ba[n] == nth_bit); + } + --- + The least significant bit in any `size_t` unit is the starting bit of this + unit, and the most significant bit is the last bit of this unit. Therefore, + passing e.g. an array of `int`s may result in a different `BitArray` + depending on the processor's endianness. + + This constructor is the inverse of $(LREF opCast). + + Params: + v = Source array. `v.length` must be a multple of `size_t.sizeof`. + numbits = Number of bits to be mapped from the source array, i.e. + length of the created `BitArray`. + */ + this(void[] v, size_t numbits) @nogc nothrow pure + in + { + assert(numbits <= v.length * 8, + "numbits must be less than or equal to v.length * 8"); + assert(v.length % size_t.sizeof == 0, + "v.length must be a multiple of the size of size_t"); + } + do + { + _ptr = cast(size_t*) v.ptr; + _len = numbits; + } + + /// + @system unittest + { + import std.algorithm.comparison : equal; + + auto a = BitArray([1, 0, 0, 1, 1]); + + // Inverse of the cast. + auto v = cast(void[]) a; + auto b = BitArray(v, a.length); + + assert(b.length == 5); + assert(b.bitsSet.equal([0, 3, 4])); + + // a and b share the underlying data. + a[0] = 0; + assert(b[0] == 0); + assert(a == b); + } + + /// + @system unittest + { + import std.algorithm.comparison : equal; + + size_t[] source = [0b1100, 0b0011]; + enum sbits = size_t.sizeof * 8; + auto ba = BitArray(source, source.length * sbits); + // The least significant bit in each unit is this unit's starting bit. + assert(ba.bitsSet.equal([2, 3, sbits, sbits + 1])); + } + + /// + @system unittest + { + // Example from the doc for this constructor. + static immutable size_t[] sourceData = [1, 0b101, 3, 3424234, 724398, 230947, 389492]; + size_t[] source = sourceData.dup; + enum sbits = size_t.sizeof * 8; + auto ba = BitArray(source, source.length * sbits); + foreach (n; 0 .. source.length * sbits) + { + auto nth_bit = cast(bool) (source[n / sbits] & (1L << (n % sbits))); + assert(ba[n] == nth_bit); + } + + // Example of mapping only part of the array. + import std.algorithm.comparison : equal; + + auto bc = BitArray(source, sbits + 1); + assert(bc.bitsSet.equal([0, sbits])); + // Source array has not been modified. + assert(source == sourceData); + } + + // Deliberately undocumented: raw initialization of bit array. + this(size_t len, size_t* ptr) @nogc nothrow pure + { + _len = len; + _ptr = ptr; + } + + /** + Returns: Dimension i.e. the number of native words backing this `BitArray`. + + Technically, this is the length of the underlying array storing bits, which + is equal to `ceil(length / (size_t.sizeof * 8))`, as bits are packed into + `size_t` units. + */ + @property size_t dim() const @nogc nothrow pure @safe { return lenToDim(_len); } - /********************************************** - * Gets the amount of bits in the $(D BitArray). - */ - @property size_t length() const @nogc pure nothrow @safe + /** + Returns: Number of bits in the `BitArray`. + */ + @property size_t length() const @nogc nothrow pure @safe { return _len; } /********************************************** - * Sets the amount of bits in the $(D BitArray). + * Sets the amount of bits in the `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.) + * the final word of the current underlying data regardless + * of whether it is shared between BitArray objects. i.e. D + * dynamic array extension semantics are not followed.) */ @property size_t length(size_t newlen) pure nothrow @system { @@ -836,29 +1198,54 @@ public: _ptr = b.ptr; } + auto oldlen = _len; _len = newlen; + if (oldlen < newlen) + { + auto end = ((oldlen / bitsPerSizeT) + 1) * bitsPerSizeT; + if (end > newlen) + end = newlen; + this[oldlen .. end] = 0; + } } return _len; } + // https://issues.dlang.org/show_bug.cgi?id=20240 + @system unittest + { + BitArray ba; + + ba.length = 1; + ba[0] = 1; + ba.length = 0; + ba.length = 1; + assert(ba[0] == 0); // OK + + ba.length = 2; + ba[1] = 1; + ba.length = 1; + ba.length = 2; + assert(ba[1] == 0); // Fail + } + /********************************************** - * Gets the $(D i)'th bit in the $(D BitArray). + * Gets the `i`'th bit in the `BitArray`. */ bool opIndex(size_t i) const @nogc pure nothrow in { - assert(i < _len); + assert(i < _len, "i must be less than the length"); } - body + do { return cast(bool) bt(_ptr, i); } + /// @system unittest { - debug(bitarray) printf("BitArray.opIndex.unittest\n"); - - void Fun(const BitArray arr) + static void fun(const BitArray arr) { auto x = arr[0]; assert(x == 1); @@ -866,18 +1253,18 @@ public: BitArray a; a.length = 3; a[0] = 1; - Fun(a); + fun(a); } /********************************************** - * Sets the $(D i)'th bit in the $(D BitArray). + * Sets the `i`'th bit in the `BitArray`. */ bool opIndexAssign(bool b, size_t i) @nogc pure nothrow in { - assert(i < _len); + assert(i < _len, "i must be less than the length"); } - body + do { if (b) bts(_ptr, i); @@ -886,8 +1273,206 @@ public: return b; } + /** + Sets all the values in the `BitArray` to the + value specified by `val`. + */ + void opSliceAssign(bool val) @nogc pure nothrow + { + _ptr[0 .. fullWords] = val ? ~size_t(0) : 0; + if (endBits) + { + if (val) + _ptr[fullWords] |= endMask; + else + _ptr[fullWords] &= ~endMask; + } + } + + /// + @system pure nothrow unittest + { + import std.algorithm.comparison : equal; + + auto b = BitArray([1, 0, 1, 0, 1, 1]); + + b[] = true; + // all bits are set + assert(b.bitsSet.equal([0, 1, 2, 3, 4, 5])); + + b[] = false; + // none of the bits are set + assert(b.bitsSet.empty); + } + + /** + Sets the bits of a slice of `BitArray` starting + at index `start` and ends at index ($D end - 1) + with the values specified by `val`. + */ + void opSliceAssign(bool val, size_t start, size_t end) @nogc pure nothrow + in + { + assert(start <= end, "start must be less or equal to end"); + assert(end <= length, "end must be less or equal to the length"); + } + do + { + size_t startBlock = start / bitsPerSizeT; + size_t endBlock = end / bitsPerSizeT; + size_t startOffset = start % bitsPerSizeT; + size_t endOffset = end % bitsPerSizeT; + + if (startBlock == endBlock) + { + size_t startBlockMask = ~((size_t(1) << startOffset) - 1); + size_t endBlockMask = (size_t(1) << endOffset) - 1; + size_t joinMask = startBlockMask & endBlockMask; + if (val) + _ptr[startBlock] |= joinMask; + else + _ptr[startBlock] &= ~joinMask; + return; + } + + if (startOffset != 0) + { + size_t startBlockMask = ~((size_t(1) << startOffset) - 1); + if (val) + _ptr[startBlock] |= startBlockMask; + else + _ptr[startBlock] &= ~startBlockMask; + ++startBlock; + } + if (endOffset != 0) + { + size_t endBlockMask = (size_t(1) << endOffset) - 1; + if (val) + _ptr[endBlock] |= endBlockMask; + else + _ptr[endBlock] &= ~endBlockMask; + } + _ptr[startBlock .. endBlock] = size_t(0) - size_t(val); + } + + /// + @system pure nothrow unittest + { + import std.algorithm.comparison : equal; + import std.range : iota; + import std.stdio; + + auto b = BitArray([1, 0, 0, 0, 1, 1, 0]); + b[1 .. 3] = true; + assert(b.bitsSet.equal([0, 1, 2, 4, 5])); + + bool[72] bitArray; + auto b1 = BitArray(bitArray); + b1[63 .. 67] = true; + assert(b1.bitsSet.equal([63, 64, 65, 66])); + b1[63 .. 67] = false; + assert(b1.bitsSet.empty); + b1[0 .. 64] = true; + assert(b1.bitsSet.equal(iota(0, 64))); + b1[0 .. 64] = false; + assert(b1.bitsSet.empty); + + bool[256] bitArray2; + auto b2 = BitArray(bitArray2); + b2[3 .. 245] = true; + assert(b2.bitsSet.equal(iota(3, 245))); + b2[3 .. 245] = false; + assert(b2.bitsSet.empty); + } + + /** + Flips all the bits in the `BitArray` + */ + void flip() @nogc pure nothrow + { + foreach (i; 0 .. fullWords) + _ptr[i] = ~_ptr[i]; + + if (endBits) + _ptr[fullWords] = (~_ptr[fullWords]) & endMask; + } + + /// + @system pure nothrow unittest + { + import std.algorithm.comparison : equal; + import std.range : iota; + + // positions 0, 2, 4 are set + auto b = BitArray([1, 0, 1, 0, 1, 0]); + b.flip(); + // after flipping, positions 1, 3, 5 are set + assert(b.bitsSet.equal([1, 3, 5])); + + bool[270] bits; + auto b1 = BitArray(bits); + b1.flip(); + assert(b1.bitsSet.equal(iota(0, 270))); + } + + /** + Flips a single bit, specified by `pos` + */ + void flip(size_t i) @nogc pure nothrow + { + bt(_ptr, i) ? btr(_ptr, i) : bts(_ptr, i); + } + + /// + @system pure nothrow unittest + { + auto ax = BitArray([1, 0, 0, 1]); + ax.flip(0); + assert(ax[0] == 0); + + bool[200] y; + y[90 .. 130] = true; + auto ay = BitArray(y); + ay.flip(100); + assert(ay[100] == 0); + } + + /********************************************** + * Counts all the set bits in the `BitArray` + */ + size_t count() const @nogc pure nothrow + { + if (_ptr) + { + size_t bitCount; + foreach (i; 0 .. fullWords) + bitCount += countBitsSet(_ptr[i]); + bitCount += countBitsSet(_ptr[fullWords] & endMask); + return bitCount; + } + else + { + return 0; + } + } + + /// + @system pure nothrow unittest + { + auto a = BitArray([0, 1, 1, 0, 0, 1, 1]); + assert(a.count == 4); + + BitArray b; + assert(b.count == 0); + + bool[200] boolArray; + boolArray[45 .. 130] = true; + auto c = BitArray(boolArray); + assert(c.count == 85); + } + /********************************************** - * Duplicates the $(D BitArray) and its contents. + * Duplicates the `BitArray` and its contents. */ @property BitArray dup() const pure nothrow { @@ -899,26 +1484,22 @@ public: 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]); + foreach (i; 0 .. 3) assert(b[i] == (((i ^ 1) & 1) ? true : false)); - } } /********************************************** - * Support for $(D foreach) loops for $(D BitArray). + * Support for `foreach` loops for `BitArray`. */ int opApply(scope int delegate(ref bool) dg) { @@ -981,11 +1562,10 @@ public: return result; } + /// @system unittest { - debug(bitarray) printf("BitArray.opApply unittest\n"); - - static bool[] ba = [1,0,1]; + bool[] ba = [1,0,1]; auto a = BitArray(ba); @@ -1016,14 +1596,14 @@ public: /********************************************** - * Reverses the bits of the $(D BitArray). + * Reverses the bits of the `BitArray`. */ - @property BitArray reverse() @nogc pure nothrow + @property BitArray reverse() @nogc pure nothrow return out (result) { - assert(result == this); + assert(result == this, "the result must be equal to this"); } - body + do { if (_len >= 2) { @@ -1042,32 +1622,28 @@ public: return this; } + /// @system unittest { - debug(bitarray) printf("BitArray.reverse.unittest\n"); - BitArray b; - static bool[5] data = [1,0,1,1,0]; - int i; + bool[5] data = [1,0,1,1,0]; b = BitArray(data); b.reverse; - for (i = 0; i < data.length; i++) - { + foreach (i; 0 .. data.length) assert(b[i] == data[4 - i]); - } } /********************************************** - * Sorts the $(D BitArray)'s elements. + * Sorts the `BitArray`'s elements. */ - @property BitArray sort() @nogc pure nothrow + @property BitArray sort() @nogc pure nothrow return out (result) { - assert(result == this); + assert(result == this, "the result must be equal to this"); } - body + do { if (_len >= 2) { @@ -1106,22 +1682,21 @@ public: return this; } + /// @system unittest { - debug(bitarray) printf("BitArray.sort.unittest\n"); - - __gshared size_t x = 0b1100011000; - __gshared ba = BitArray(10, &x); + size_t x = 0b1100011000; + auto ba = BitArray(10, &x); ba.sort; - for (size_t i = 0; i < 6; i++) + foreach (i; 0 .. 6) assert(ba[i] == false); - for (size_t i = 6; i < 10; i++) + foreach (i; 6 .. 10) assert(ba[i] == true); } /*************************************** - * Support for operators == and != for $(D BitArray). + * Support for operators == and != for `BitArray`. */ bool opEquals(const ref BitArray a2) const @nogc pure nothrow { @@ -1140,17 +1715,16 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1]; + bool[] bc = [1,0,1,0,1,0,1]; + bool[] bd = [1,0,1,1,1]; + bool[] be = [1,0,1,0,1]; + 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]; + 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); @@ -1168,7 +1742,7 @@ public: } /*************************************** - * Supports comparison operators for $(D BitArray). + * Supports comparison operators for `BitArray`. */ int opCmp(BitArray a2) const @nogc pure nothrow { @@ -1206,17 +1780,16 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1]; + bool[] bc = [1,0,1,0,1,0,1]; + bool[] bd = [1,0,1,1,1]; + bool[] be = [1,0,1,0,1]; + 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]; + 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); @@ -1237,7 +1810,10 @@ public: assert(a >= e); assert(f < g); assert(g <= g); + } + @system unittest + { bool[] v; foreach (i; 1 .. 256) { @@ -1269,7 +1845,7 @@ public: } /*************************************** - * Support for hashing for $(D BitArray). + * Support for hashing for `BitArray`. */ size_t toHash() const @nogc pure nothrow { @@ -1289,105 +1865,67 @@ public: } /*************************************** - * Set this $(D BitArray) to the contents of $(D ba). + * Convert to `void[]`. */ - 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) + inout(void)[] opCast(T : const void[])() inout @nogc pure nothrow { - _len = len; - _ptr = ptr; + return cast(inout void[]) _ptr[0 .. dim]; } /*************************************** - * 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). + * Convert to `size_t[]`. */ - this(void[] v, size_t numbits) pure nothrow - in - { - assert(numbits <= v.length * 8); - assert(v.length % size_t.sizeof == 0); - } - body + inout(size_t)[] opCast(T : const size_t[])() inout @nogc pure nothrow { - _ptr = cast(size_t*) v.ptr; - _len = numbits; - if (endBits) - { - // Need to mask away extraneous bits from v. - _ptr[dim - 1] &= endMask; - } + return _ptr[0 .. dim]; } + /// @system unittest { - debug(bitarray) printf("BitArray.init unittest\n"); - - static bool[] ba = [1,0,1,0,1]; - - auto a = BitArray(ba); - void[] v; + import std.array : array; + import std.range : repeat, take; - 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); + // bit array with 300 elements + auto a = BitArray(true.repeat.take(300).array); + size_t[] v = cast(size_t[]) a; + const blockSize = size_t.sizeof * 8; + assert(v.length == (a.length + blockSize - 1) / blockSize); } - /*************************************** - * Convert to $(D void[]). - */ - void[] opCast(T : void[])() @nogc pure nothrow + // https://issues.dlang.org/show_bug.cgi?id=20606 + @system unittest { - return cast(void[])_ptr[0 .. dim]; - } + import std.meta : AliasSeq; - /*************************************** - * Convert to $(D size_t[]). - */ - size_t[] opCast(T : size_t[])() @nogc pure nothrow - { - return _ptr[0 .. dim]; - } + static foreach (alias T; AliasSeq!(void, size_t)) + {{ + BitArray m; + T[] ma = cast(T[]) m; - @system unittest - { - debug(bitarray) printf("BitArray.opCast unittest\n"); + const BitArray c; + const(T)[] ca = cast(const T[]) c; - static bool[] ba = [1,0,1,0,1]; + immutable BitArray i; + immutable(T)[] ia = cast(immutable T[]) i; - auto a = BitArray(ba); - void[] v = cast(void[]) a; + // Cross-mutability + ca = cast(const T[]) m; + ca = cast(const T[]) i; - assert(v.length == a.dim * size_t.sizeof); + // Invalid cast don't compile + static assert(!is(typeof(cast(T[]) c))); + static assert(!is(typeof(cast(T[]) i))); + static assert(!is(typeof(cast(immutable T[]) m))); + static assert(!is(typeof(cast(immutable T[]) c))); + }} } /*************************************** - * Support for unary operator ~ for $(D BitArray). + * Support for unary operator ~ for `BitArray`. */ - BitArray opCom() const pure nothrow + BitArray opUnary(string op)() const pure nothrow + if (op == "~") { auto dim = this.dim; @@ -1404,11 +1942,10 @@ public: return result; } + /// @system unittest { - debug(bitarray) printf("BitArray.opCom unittest\n"); - - static bool[] ba = [1,0,1,0,1]; + bool[] ba = [1,0,1,0,1]; auto a = BitArray(ba); BitArray b = ~a; @@ -1422,15 +1959,15 @@ public: /*************************************** - * Support for binary bitwise operators for $(D BitArray). + * Support for binary bitwise operators for `BitArray`. */ BitArray opBinary(string op)(const BitArray e2) const pure nothrow if (op == "-" || op == "&" || op == "|" || op == "^") in { - assert(_len == e2.length); + assert(e2.length == _len, "e2 must have the same length as this"); } - body + do { auto dim = this.dim; @@ -1450,10 +1987,9 @@ public: 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]; @@ -1469,12 +2005,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1488,12 +2023,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1507,12 +2041,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1528,15 +2061,15 @@ public: /*************************************** - * Support for operator op= for $(D BitArray). + * Support for operator op= for `BitArray`. */ - BitArray opOpAssign(string op)(const BitArray e2) @nogc pure nothrow + BitArray opOpAssign(string op)(const BitArray e2) @nogc pure nothrow return scope if (op == "-" || op == "&" || op == "|" || op == "^") in { - assert(_len == e2.length); + assert(e2.length == _len, "e2 must have the same length as this"); } - body + do { foreach (i; 0 .. fullWords) { @@ -1559,10 +2092,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1,1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); BitArray c = a; @@ -1575,12 +2109,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1593,12 +2126,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1611,12 +2143,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1629,12 +2160,11 @@ public: 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]; + bool[] ba = [1,0,1,0,1]; + bool[] bb = [1,0,1,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1648,25 +2178,24 @@ public: } /*************************************** - * Support for operator ~= for $(D BitArray). + * Support for operator ~= for `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 + BitArray opOpAssign(string op)(bool b) pure nothrow return scope + if (op == "~") { 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]; + bool[] ba = [1,0,1,0,1]; auto a = BitArray(ba); BitArray b; @@ -1685,8 +2214,8 @@ public: /*************************************** * ditto */ - - BitArray opCatAssign(BitArray b) pure nothrow + BitArray opOpAssign(string op)(BitArray b) pure nothrow return scope + if (op == "~") { auto istart = _len; length = _len + b.length; @@ -1695,12 +2224,11 @@ public: return this; } + /// @system unittest { - debug(bitarray) printf("BitArray.opCatAssign unittest\n"); - - static bool[] ba = [1,0]; - static bool[] bb = [0,1,0]; + bool[] ba = [1,0]; + bool[] bb = [0,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1718,9 +2246,10 @@ public: } /*************************************** - * Support for binary operator ~ for $(D BitArray). + * Support for binary operator ~ for `BitArray`. */ - BitArray opCat(bool b) const pure nothrow + BitArray opBinary(string op)(bool b) const pure nothrow + if (op == "~") { BitArray r; @@ -1731,7 +2260,8 @@ public: } /** ditto */ - BitArray opCat_r(bool b) const pure nothrow + BitArray opBinaryRight(string op)(bool b) const pure nothrow + if (op == "~") { BitArray r; @@ -1743,7 +2273,8 @@ public: } /** ditto */ - BitArray opCat(BitArray b) const pure nothrow + BitArray opBinary(string op)(BitArray b) const pure nothrow + if (op == "~") { BitArray r; @@ -1752,12 +2283,11 @@ public: return r; } + /// @system unittest { - debug(bitarray) printf("BitArray.opCat unittest\n"); - - static bool[] ba = [1,0]; - static bool[] bb = [0,1,0]; + bool[] ba = [1,0]; + bool[] bb = [0,1,0]; auto a = BitArray(ba); auto b = BitArray(bb); @@ -1790,10 +2320,12 @@ public: pure @safe nothrow @nogc in { - assert(nbits < bitsPerSizeT); + assert(nbits < bitsPerSizeT, "nbits must be less than bitsPerSizeT"); } - body + do { + if (nbits == 0) + return lower; return (upper << (bitsPerSizeT - nbits)) | (lower >> nbits); } @@ -1825,10 +2357,12 @@ public: pure @safe nothrow @nogc in { - assert(nbits < bitsPerSizeT); + assert(nbits < bitsPerSizeT, "nbits must be less than bitsPerSizeT"); } - body + do { + if (nbits == 0) + return upper; return (upper << nbits) | (lower >> (bitsPerSizeT - nbits)); } @@ -1853,7 +2387,7 @@ public: } /** - * Operator $(D <<=) support. + * Operator `<<=` 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 @@ -1887,7 +2421,7 @@ public: } /** - * Operator $(D >>=) support. + * Operator `>>=` 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 @@ -1916,8 +2450,14 @@ public: // end of the array. if (wordsToShift < dim) { - _ptr[dim - wordsToShift - 1] = rollRight(0, _ptr[dim - 1] & endMask, - bitsToShift); + if (bitsToShift == 0) + _ptr[dim - wordsToShift - 1] = _ptr[dim - 1]; + else + { + // Special case: if endBits == 0, then also endMask == 0. + size_t lastWord = (endBits ? (_ptr[fullWords] & endMask) : _ptr[fullWords - 1]); + _ptr[dim - wordsToShift - 1] = rollRight(0, lastWord, bitsToShift); + } } import std.algorithm.comparison : min; @@ -1927,6 +2467,57 @@ public: } } + // https://issues.dlang.org/show_bug.cgi?id=17467 + @system unittest + { + import std.algorithm.comparison : equal; + import std.range : iota; + + bool[] buf = new bool[64*3]; + buf[0 .. 64] = true; + BitArray b = BitArray(buf); + assert(equal(b.bitsSet, iota(0, 64))); + b <<= 64; + assert(equal(b.bitsSet, iota(64, 128))); + + buf = new bool[64*3]; + buf[64*2 .. 64*3] = true; + b = BitArray(buf); + assert(equal(b.bitsSet, iota(64*2, 64*3))); + b >>= 64; + assert(equal(b.bitsSet, iota(64, 128))); + } + + // https://issues.dlang.org/show_bug.cgi?id=18134 + // shifting right when length is a multiple of 8 * size_t.sizeof. + @system unittest + { + import std.algorithm.comparison : equal; + import std.array : array; + import std.range : repeat, iota; + + immutable r = size_t.sizeof * 8; + + BitArray a = true.repeat(r / 2).array; + a >>= 0; + assert(a.bitsSet.equal(iota(0, r / 2))); + a >>= 1; + assert(a.bitsSet.equal(iota(0, r / 2 - 1))); + + BitArray b = true.repeat(r).array; + b >>= 0; + assert(b.bitsSet.equal(iota(0, r))); + b >>= 1; + assert(b.bitsSet.equal(iota(0, r - 1))); + + BitArray c = true.repeat(2 * r).array; + c >>= 0; + assert(c.bitsSet.equal(iota(0, 2 * r))); + c >>= 10; + assert(c.bitsSet.equal(iota(0, 2 * r - 10))); + } + + /// @system unittest { import std.format : format; @@ -2017,27 +2608,33 @@ public: * $(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. + * + * Params: + * sink = A `char` accepting + * $(REF_ALTTEXT output range, isOutputRange, std, range, primitives). + * fmt = A $(REF FormatSpec, std,format) which controls how the data + * is displayed. */ - void toString(scope void delegate(const(char)[]) sink, - FormatSpec!char fmt) const + void toString(W)(ref W sink, scope const ref FormatSpec!char fmt) const + if (isOutputRange!(W, char)) { - switch (fmt.spec) + const spec = fmt.spec; + switch (spec) { case 'b': return formatBitString(sink); case 's': return formatBitArray(sink); default: - throw new Exception("Unknown format specifier: %" ~ fmt.spec); + throw new Exception("Unknown format specifier: %" ~ spec); } } /// - @system unittest + @system pure 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); @@ -2053,12 +2650,16 @@ public: @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(); + import std.range : iota, chain; + + return chain( + iota(fullWords) + .filter!(i => _ptr[i])() + .map!(i => BitsSet!size_t(_ptr[i], i * bitsPerSizeT))() + .joiner(), + iota(fullWords * bitsPerSizeT, _len) + .filter!(i => this[i])() + ); } /// @@ -2082,7 +2683,6 @@ public: 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); @@ -2099,7 +2699,17 @@ public: assert(b.bitsSet.equal(iota(wordBits * 2))); } - private void formatBitString(scope void delegate(const(char)[]) sink) const + // https://issues.dlang.org/show_bug.cgi?id=20241 + @system unittest + { + BitArray ba; + ba.length = 2; + ba[1] = 1; + ba.length = 1; + assert(ba.bitsSet.empty); + } + + private void formatBitString(Writer)(auto ref Writer sink) const { if (!length) return; @@ -2107,40 +2717,101 @@ public: auto leftover = _len % 8; foreach (idx; 0 .. leftover) { - char[1] res = cast(char)(this[idx] + '0'); - sink.put(res[]); + put(sink, cast(char)(this[idx] + '0')); } if (leftover && _len > 8) - sink.put("_"); + put(sink, "_"); size_t count; foreach (idx; leftover .. _len) { - char[1] res = cast(char)(this[idx] + '0'); - sink.put(res[]); + put(sink, cast(char)(this[idx] + '0')); if (++count == 8 && idx != _len - 1) { - sink.put("_"); + put(sink, "_"); count = 0; } } } - private void formatBitArray(scope void delegate(const(char)[]) sink) const + private void formatBitArray(Writer)(auto ref Writer sink) const { - sink("["); + put(sink, "["); foreach (idx; 0 .. _len) { - char[1] res = cast(char)(this[idx] + '0'); - sink(res[]); - if (idx+1 < _len) - sink(", "); + put(sink, cast(char)(this[idx] + '0')); + if (idx + 1 < _len) + put(sink, ", "); } - sink("]"); + put(sink, "]"); + } + + // https://issues.dlang.org/show_bug.cgi?id=20639 + // Separate @nogc test because public tests use array literals + // (and workarounds needlessly uglify those examples) + @system @nogc unittest + { + size_t[2] buffer; + BitArray b = BitArray(buffer[], buffer.sizeof * 8); + + b[] = true; + b[0 .. 1] = true; + b.flip(); + b.flip(1); + cast(void) b.count(); } } +/// Slicing & bitsSet +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + + bool[] buf = new bool[64 * 3]; + buf[0 .. 64] = true; + BitArray b = BitArray(buf); + assert(b.bitsSet.equal(iota(0, 64))); + b <<= 64; + assert(b.bitsSet.equal(iota(64, 128))); +} + +/// Concatenation and appending +@system unittest +{ + import std.algorithm.comparison : equal; + + auto b = BitArray([1, 0]); + b ~= true; + assert(b[2] == 1); + b ~= BitArray([0, 1]); + auto c = BitArray([1, 0, 1, 0, 1]); + assert(b == c); + assert(b.bitsSet.equal([0, 2, 4])); +} + +/// Bit flipping +@system unittest +{ + import std.algorithm.comparison : equal; + + auto b = BitArray([1, 1, 0, 1]); + b &= BitArray([0, 1, 1, 0]); + assert(b.bitsSet.equal([1])); + b.flip; + assert(b.bitsSet.equal([0, 2, 3])); +} + +/// String format of bitarrays +@system unittest +{ + import std.format : format; + auto 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"); +} + +/// @system unittest { import std.format : format; @@ -2173,51 +2844,61 @@ public: assert(format("%b", b) == "1_00001111_00001111"); } +@system unittest +{ + BitArray a; + a.length = 5; + foreach (ref bool b; a) + { + assert(b == 0); + b = 1; + } + foreach (bool b; a) + assert(b == 1); +} + /++ Swaps the endianness of the given integral value or character. +/ -T swapEndian(T)(T val) @safe pure nothrow @nogc +T swapEndian(T)(const T val) @safe pure nothrow @nogc if (isIntegral!T || isSomeChar!T || isBoolean!T) { + import core.bitop : bswap, byteswap; 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 if (T.sizeof == 2) + return cast(T) byteswap(cast(ushort) val); + else static if (T.sizeof == 4) + return cast(T) bswap(cast(uint) val); + else static if (T.sizeof == 8) + return cast(T) bswap(cast(ulong) val); else static assert(0, T.stringof ~ " unsupported by swapEndian."); } -private ushort swapEndianImpl(ushort val) @safe pure nothrow @nogc +/// +@safe unittest { - return ((val & 0xff00U) >> 8) | - ((val & 0x00ffU) << 8); -} + assert(42.swapEndian == 704643072); + assert(42.swapEndian.swapEndian == 42); // reflexive + assert(1.swapEndian == 16777216); -private uint swapEndianImpl(uint val) @trusted pure nothrow @nogc -{ - import core.bitop : bswap; - return bswap(val); -} + assert(true.swapEndian == true); + assert(byte(10).swapEndian == 10); + assert(char(10).swapEndian == 10); -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)); + assert(ushort(10).swapEndian == 2560); + assert(long(10).swapEndian == 720575940379279360); + assert(ulong(10).swapEndian == 720575940379279360); } @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); + import std.stdio; + static foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar)) + {{ + scope(failure) writeln("Failed type: ", T.stringof); T val; const T cval; immutable T ival; @@ -2228,6 +2909,9 @@ private ulong swapEndianImpl(ulong val) @trusted pure nothrow @nogc assert(swapEndian(swapEndian(T.min)) == T.min); assert(swapEndian(swapEndian(T.max)) == T.max); + // Check CTFE compiles. + static assert(swapEndian(swapEndian(T(1))) is T(1)); + foreach (i; 2 .. 10) { immutable T maxI = cast(T)(T.max / i); @@ -2242,7 +2926,7 @@ private ulong swapEndianImpl(ulong val) @trusted pure nothrow @nogc static if (isSigned!T) assert(swapEndian(swapEndian(cast(T) 0)) == 0); - // used to trigger BUG6354 + // used to trigger https://issues.dlang.org/show_bug.cgi?id=6354 static if (T.sizeof > 1 && isUnsigned!T) { T left = 0xffU; @@ -2257,7 +2941,7 @@ private ulong swapEndianImpl(ulong val) @trusted pure nothrow @nogc right <<= 8; } } - } + }} } @@ -2267,31 +2951,72 @@ if (canSwapEndianness!T) Unqual!T value; ubyte[T.sizeof] array; - static if (is(FloatingPointTypeOf!T == float)) + static if (is(FloatingPointTypeOf!(Unqual!T) == float)) uint intValue; - else static if (is(FloatingPointTypeOf!T == double)) + else static if (is(FloatingPointTypeOf!(Unqual!T) == double)) ulong intValue; } +// Can't use EndianSwapper union during CTFE. +private auto ctfeRead(T)(const ubyte[T.sizeof] array) +if (__traits(isIntegral, T)) +{ + Unqual!T result; + version (LittleEndian) + foreach_reverse (b; array) + result = cast(Unqual!T) ((result << 8) | b); + else + foreach (b; array) + result = cast(Unqual!T) ((result << 8) | b); + return cast(T) result; +} + +// Can't use EndianSwapper union during CTFE. +private auto ctfeBytes(T)(const T value) +if (__traits(isIntegral, T)) +{ + ubyte[T.sizeof] result; + Unqual!T tmp = value; + version (LittleEndian) + { + foreach (i; 0 .. T.sizeof) + { + result[i] = cast(ubyte) tmp; + tmp = cast(Unqual!T) (tmp >>> 8); + } + } + else + { + foreach_reverse (i; 0 .. T.sizeof) + { + result[i] = cast(ubyte) tmp; + tmp = cast(Unqual!T) (tmp >>> 8); + } + } + return result; +} /++ 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. + returns it as a `ubyte[n]` where `n` is the size of the given type. - Returning a $(D ubyte[n]) helps prevent accidentally using a swapped value + Returning a `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 + `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 +auto nativeToBigEndian(T)(const T val) @safe pure nothrow @nogc if (canSwapEndianness!T) { - return nativeToBigEndianImpl(val); + version (LittleEndian) + return nativeToEndianImpl!true(val); + else + return nativeToEndianImpl!false(val); } /// @@ -2301,37 +3026,48 @@ if (canSwapEndianness!T) ubyte[4] swappedI = nativeToBigEndian(i); assert(i == bigEndianToNative!int(swappedI)); + float f = 123.45f; + ubyte[4] swappedF = nativeToBigEndian(f); + assert(f == bigEndianToNative!float(swappedF)); + + const float cf = 123.45f; + ubyte[4] swappedCF = nativeToBigEndian(cf); + assert(cf == bigEndianToNative!float(swappedCF)); + 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; + const double cd = 123.45; + ubyte[8] swappedCD = nativeToBigEndian(cd); + assert(cd == bigEndianToNative!double(swappedCD)); } -private auto nativeToBigEndianImpl(T)(T val) @safe pure nothrow @nogc -if (isFloatOrDouble!T) +private auto nativeToEndianImpl(bool swap, T)(const T val) @safe pure nothrow @nogc +if (__traits(isIntegral, T)) { - version (LittleEndian) - return floatEndianImpl!(T, true)(val); + if (!__ctfe) + { + static if (swap) + return EndianSwapper!T(swapEndian(val)).array; + else + return EndianSwapper!T(val).array; + } else - return floatEndianImpl!(T, false)(val); + { + // Can't use EndianSwapper in CTFE. + static if (swap) + return ctfeBytes(swapEndian(val)); + else + return ctfeBytes(val); + } } @safe unittest { import std.meta; - foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, + import std.stdio; + static 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. @@ -2343,8 +3079,8 @@ if (isFloatOrDouble!T) * I cannot think of a fix for this that makes consistent sense. */ /*,float, double*/)) - { - scope(failure) writefln("Failed type: %s", T.stringof); + {{ + scope(failure) writeln("Failed type: ", T.stringof); T val; const T cval; immutable T ival; @@ -2356,6 +3092,9 @@ if (isFloatOrDouble!T) assert(bigEndianToNative!T(nativeToBigEndian(T.min)) == T.min); assert(bigEndianToNative!T(nativeToBigEndian(T.max)) == T.max); + //Check CTFE compiles. + static assert(bigEndianToNative!T(nativeToBigEndian(T(1))) is T(1)); + static if (isSigned!T) assert(bigEndianToNative!T(nativeToBigEndian(cast(T) 0)) == 0); @@ -2394,18 +3133,18 @@ if (isFloatOrDouble!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 + returns it. The value is given as a `ubyte[n]` where `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 + Taking a `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). @@ -2413,7 +3152,10 @@ if (isFloatOrDouble!T) T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc if (canSwapEndianness!T && n == T.sizeof) { - return bigEndianToNativeImpl!(T, n)(val); + version (LittleEndian) + return endianToNativeImpl!(true, T, n)(val); + else + return endianToNativeImpl!(false, T, n)(val); } /// @@ -2428,44 +3170,22 @@ if (canSwapEndianness!T && n == T.sizeof) 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. + returns it as a `ubyte[n]` where `n` is the size of the given type. - Returning a $(D ubyte[n]) helps prevent accidentally using a swapped value + Returning a `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 +auto nativeToLittleEndian(T)(const T val) @safe pure nothrow @nogc if (canSwapEndianness!T) { - return nativeToLittleEndianImpl(val); + version (BigEndian) + return nativeToEndianImpl!true(val); + else + return nativeToEndianImpl!false(val); } /// @@ -2480,36 +3200,15 @@ if (canSwapEndianness!T) 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, + import std.stdio; + static 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); + {{ + scope(failure) writeln("Failed type: ", T.stringof); T val; const T cval; immutable T ival; @@ -2521,6 +3220,9 @@ if (isFloatOrDouble!T) assert(littleEndianToNative!T(nativeToLittleEndian(T.min)) == T.min); assert(littleEndianToNative!T(nativeToLittleEndian(T.max)) == T.max); + //Check CTFE compiles. + static assert(littleEndianToNative!T(nativeToLittleEndian(T(1))) is T(1)); + static if (isSigned!T) assert(littleEndianToNative!T(nativeToLittleEndian(cast(T) 0)) == 0); @@ -2537,30 +3239,33 @@ if (isFloatOrDouble!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 + returns it. The value is given as a `ubyte[n]` where `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 + Taking a `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 + `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); + version (BigEndian) + return endianToNativeImpl!(true, T, n)(val); + else + return endianToNativeImpl!(false, T, n)(val); } /// @@ -2575,67 +3280,80 @@ if (canSwapEndianness!T && n == T.sizeof) 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)) +private T endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @nogc nothrow pure @safe +if (__traits(isIntegral, T) && n == T.sizeof) { - version (BigEndian) - return floatEndianImpl!(n, true)(val); + if (!__ctfe) + { + EndianSwapper!T es = { array: val }; + static if (swap) + return swapEndian(es.value); + else + return es.value; + } else - return floatEndianImpl!(n, false)(val); + { + static if (swap) + return swapEndian(ctfeRead!T(val)); + else + return ctfeRead!T(val); + } } -private auto floatEndianImpl(T, bool swap)(T val) @safe pure nothrow @nogc +private auto nativeToEndianImpl(bool swap, T)(const T val) @trusted pure nothrow @nogc if (isFloatOrDouble!T) { - EndianSwapper!T es = void; - es.value = val; - - static if (swap) - es.intValue = swapEndian(es.intValue); - - return es.array; + if (!__ctfe) + { + EndianSwapper!T es = EndianSwapper!T(val); + static if (swap) + es.intValue = swapEndian(es.intValue); + return es.array; + } + else + { + static if (T.sizeof == 4) + uint intValue = *cast(const uint*) &val; + else static if (T.sizeof == 8) + ulong intValue = *cast(const ulong*) & val; + static if (swap) + intValue = swapEndian(intValue); + return ctfeBytes(intValue); + } } -private auto floatEndianImpl(size_t n, bool swap)(ubyte[n] val) @safe pure nothrow @nogc -if (n == 4 || n == 8) +private auto endianToNativeImpl(bool swap, T, size_t n)(ubyte[n] val) @trusted pure nothrow @nogc +if (isFloatOrDouble!T && n == T.sizeof) { - 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; + if (!__ctfe) + { + EndianSwapper!T es = { array: val }; + static if (swap) + es.intValue = swapEndian(es.intValue); + return es.value; + } + else + { + static if (n == 4) + uint x = ctfeRead!uint(val); + else static if (n == 8) + ulong x = ctfeRead!ulong(val); + static if (swap) + x = swapEndian(x); + return *cast(T*) &x; + } } private template isFloatOrDouble(T) { enum isFloatOrDouble = isFloatingPoint!T && - !is(Unqual!(FloatingPointTypeOf!T) == real); + !is(immutable FloatingPointTypeOf!T == immutable real); } @safe unittest { import std.meta; - foreach (T; AliasSeq!(float, double)) + static foreach (T; AliasSeq!(float, double)) { static assert(isFloatOrDouble!(T)); static assert(isFloatOrDouble!(const T)); @@ -2664,7 +3382,7 @@ private template canSwapEndianness(T) @safe unittest { import std.meta; - foreach (T; AliasSeq!(bool, ubyte, byte, ushort, short, uint, int, ulong, + static foreach (T; AliasSeq!(bool, ubyte, byte, ushort, short, uint, int, ulong, long, char, wchar, dchar, float, double)) { static assert(canSwapEndianness!(T)); @@ -2676,7 +3394,7 @@ private template canSwapEndianness(T) } //! - foreach (T; AliasSeq!(real, string, wstring, dstring)) + static foreach (T; AliasSeq!(real, string, wstring, dstring)) { static assert(!canSwapEndianness!(T)); static assert(!canSwapEndianness!(const T)); @@ -2688,18 +3406,18 @@ private template canSwapEndianness(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 + Takes a range of `ubyte`s and converts the first `T.sizeof` bytes to + `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. + T = The integral type to convert the first `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). + available if `hasSlicing!R` is `true`. +/ T peek(T, Endian endianness = Endian.bigEndian, R)(R range) @@ -2745,7 +3463,7 @@ if (canSwapEndianness!T && hasSlicing!R && is(ElementType!R : const ubyte)) { - assert(index); + assert(index, "index must not point to null"); immutable begin = *index; immutable end = begin + T.sizeof; @@ -2781,6 +3499,17 @@ if (canSwapEndianness!T && assert(index == 7); } +/// +@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); +} + @system unittest { { @@ -2975,32 +3704,21 @@ if (canSwapEndianness!T && 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); -} + two = 25.0 + } + static assert(!__traits(compiles, buffer.peek!Real())); + } +} /++ - 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 + Takes a range of `ubyte`s and converts the first `T.sizeof` bytes to + `T`. The value returned is converted from the given endianness to the + native endianness. The `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. + T = The integral type to convert the first `T.sizeof` bytes to. endianness = The endianness that the bytes are assumed to be in. range = The range to read from. +/ @@ -3219,24 +3937,7 @@ if (canSwapEndianness!T && isInputRange!R && is(ElementType!R : const ubyte)) } } -@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 +// https://issues.dlang.org/show_bug.cgi?id=17247 @safe unittest { struct UbyteRange @@ -3253,7 +3954,7 @@ if (canSwapEndianness!T && isInputRange!R && is(ElementType!R : const ubyte)) return UbyteRange(impl[start .. end]); } @property size_t length() { return impl.length; } - size_t opDollar() { return impl.length; } + alias opDollar = length; } static assert(hasSlicing!UbyteRange); @@ -3265,18 +3966,18 @@ if (canSwapEndianness!T && isInputRange!R && is(ElementType!R : const ubyte)) /++ 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). + to the given range of `ubyte`s as a sequence of `T.sizeof` `ubyte`s + starting at index. `hasSlicing!R` must be `true`. Params: - T = The integral type to convert the first $(D T.sizeof) bytes to. + T = The integral type to convert the first `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) +void write(T, Endian endianness = Endian.bigEndian, R)(R range, const T value, size_t index) if (canSwapEndianness!T && isForwardRange!R && hasSlicing!R && @@ -3286,13 +3987,13 @@ if (canSwapEndianness!T && } /++ Ditto +/ -void write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t* index) +void write(T, Endian endianness = Endian.bigEndian, R)(R range, const T value, size_t* index) if (canSwapEndianness!T && isForwardRange!R && hasSlicing!R && is(ElementType!R : ubyte)) { - assert(index); + assert(index, "index must not point to null"); static if (endianness == Endian.bigEndian) immutable bytes = nativeToBigEndian!T(value); @@ -3308,319 +4009,328 @@ if (canSwapEndianness!T && /// @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]); + 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!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]); - } + 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]); +/// +@system unittest +{ + 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!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]); - } + 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); +/// +@system unittest +{ + 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!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); - } + buffer.write!ubyte(8, &index); + assert(buffer == [1, 5, 22, 9, 44, 255, 8, 0]); + assert(index == 7); } +/// bool @system unittest { - { - //bool - ubyte[] buffer = [0, 0]; + ubyte[] buffer = [0, 0]; + buffer.write!bool(false, 0); + assert(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, 0); - assert(buffer == [1, 0]); + buffer.write!bool(true, 1); + assert(buffer == [1, 1]); - buffer.write!bool(true, 1); - assert(buffer == [1, 1]); + buffer.write!bool(false, 1); + assert(buffer == [1, 0]); - 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); - 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); +} - buffer.write!bool(true, &index); - assert(buffer == [0, 1]); - assert(index == 2); - } +/// char(8-bit) +@system unittest +{ + ubyte[] buffer = [0, 0, 0]; - { - //char (8bit) - ubyte[] buffer = [0, 0, 0]; + buffer.write!char('a', 0); + assert(buffer == [97, 0, 0]); - buffer.write!char('a', 0); - assert(buffer == [97, 0, 0]); + buffer.write!char('b', 1); + assert(buffer == [97, 98, 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); - 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('b', &index); - assert(buffer == [97, 98, 0]); - assert(index == 2); + buffer.write!char('c', &index); + assert(buffer == [97, 98, 99]); + assert(index == 3); +} - buffer.write!char('c', &index); - assert(buffer == [97, 98, 99]); - assert(index == 3); - } +/// wchar (16bit - 2x ubyte) +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0]; - { - //wchar (16bit - 2x ubyte) - ubyte[] buffer = [0, 0, 0, 0]; + buffer.write!wchar('ą', 0); + assert(buffer == [1, 5, 0, 0]); - buffer.write!wchar('ą', 0); - assert(buffer == [1, 5, 0, 0]); + buffer.write!wchar('”', 2); + assert(buffer == [1, 5, 32, 29]); - 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); - 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); +} - buffer.write!wchar('ą', &index); - assert(buffer == [1, 7, 1, 5]); - assert(index == 4); - } +/// dchar (32bit - 4x ubyte) +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; - { - //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('ą', 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]); - 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); - 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); +} - buffer.write!dchar('ą', &index); - assert(buffer == [0, 0, 1, 7, 0, 0, 1, 5]); - assert(index == 8); - } +/// float (32bit - 4x ubyte) +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; - { - //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(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]); - 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); - 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); +} - buffer.write!float(32.0f, &index); - assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]); - assert(index == 8); - } +/// double (64bit - 8x ubyte) +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - { - //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(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]); - 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); - 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); +} - 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 +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + enum Foo { - //enum - ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + one = 10, + two = 20, + three = 30 + } - 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.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.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]); - 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); - 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.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); +} - 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 +@system unittest +{ + ubyte[] buffer = [0, 0]; + enum Bool: bool { - //enum - bool - ubyte[] buffer = [0, 0]; + bfalse = false, + btrue = true, + } - enum Bool: bool - { - bfalse = false, - btrue = true, - } + buffer.write!Bool(Bool.btrue, 0); + assert(buffer == [1, 0]); - buffer.write!Bool(Bool.btrue, 0); - assert(buffer == [1, 0]); + buffer.write!Bool(Bool.btrue, 1); + assert(buffer == [1, 1]); - 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); - 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); +} - buffer.write!Bool(Bool.bfalse, &index); - assert(buffer == [0, 0]); - assert(index == 2); - } +/// enum - float +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; + enum Float: float { - //enum - float - ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; + one = 32.0f, + two = 25.0f + } - 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.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]); - 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); - 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); +} - buffer.write!Float(Float.one, &index); - assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]); - assert(index == 8); - } +/// enum - double +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + enum Double: double { - //enum - double - ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + one = 32.0, + two = 25.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.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]); - 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); - 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); +} - 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 +@system unittest +{ + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + enum Real: real { - //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))); + 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). + it to the given range of `ubyte`s (using `put`) as a sequence of + `T.sizeof` `ubyte`s starting at index. `hasSlicing!R` must be + `true`. Params: - T = The integral type to convert the first $(D T.sizeof) bytes to. + T = The integral type to convert the first `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) +void append(T, Endian endianness = Endian.bigEndian, R)(R range, const T value) if (canSwapEndianness!T && isOutputRange!(R, ubyte)) { static if (endianness == Endian.bigEndian) @@ -3646,144 +4356,156 @@ if (canSwapEndianness!T && isOutputRange!(R, ubyte)) assert(buffer.data == [1, 5, 22, 9, 44, 255, 8]); } +/// bool @safe unittest { - import std.array; - { - //bool - auto buffer = appender!(const ubyte[])(); + import std.array : appender; + auto buffer = appender!(const ubyte[])(); - buffer.append!bool(true); - assert(buffer.data == [1]); + buffer.append!bool(true); + assert(buffer.data == [1]); - buffer.append!bool(false); - assert(buffer.data == [1, 0]); - } + buffer.append!bool(false); + assert(buffer.data == [1, 0]); +} - { - //char wchar dchar - auto buffer = appender!(const ubyte[])(); +/// char wchar dchar +@safe unittest +{ + import std.array : appender; + auto buffer = appender!(const ubyte[])(); - buffer.append!char('a'); - assert(buffer.data == [97]); + buffer.append!char('a'); + assert(buffer.data == [97]); - buffer.append!char('b'); - assert(buffer.data == [97, 98]); + buffer.append!char('b'); + assert(buffer.data == [97, 98]); - buffer.append!wchar('ą'); - assert(buffer.data == [97, 98, 1, 5]); + buffer.append!wchar('ą'); + assert(buffer.data == [97, 98, 1, 5]); - buffer.append!dchar('ą'); + buffer.append!dchar('ą'); assert(buffer.data == [97, 98, 1, 5, 0, 0, 1, 5]); - } +} - { - //float double - auto buffer = appender!(const ubyte[])(); +/// float double +@safe unittest +{ + import std.array : appender; + auto buffer = appender!(const ubyte[])(); - buffer.append!float(32.0f); - assert(buffer.data == [66, 0, 0, 0]); + 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]); - } + buffer.append!double(32.0); + assert(buffer.data == [66, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]); +} + +/// enum +@safe unittest +{ + import std.array : appender; + auto buffer = appender!(const ubyte[])(); + enum Foo { - //enum - auto buffer = appender!(const ubyte[])(); + one = 10, + two = 20, + three = 30 + } - enum Foo - { - one = 10, - two = 20, - three = 30 - } + buffer.append!Foo(Foo.one); + assert(buffer.data == [0, 0, 0, 10]); - 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.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]); +} - buffer.append!Foo(Foo.three); - assert(buffer.data == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]); - } +/// enum - bool +@safe unittest +{ + import std.array : appender; + auto buffer = appender!(const ubyte[])(); + enum Bool: bool { - //enum - bool - auto buffer = appender!(const ubyte[])(); + bfalse = false, + btrue = true, + } - enum Bool: bool - { - bfalse = false, - btrue = true, - } + buffer.append!Bool(Bool.btrue); + assert(buffer.data == [1]); - buffer.append!Bool(Bool.btrue); - assert(buffer.data == [1]); + buffer.append!Bool(Bool.bfalse); + assert(buffer.data == [1, 0]); - buffer.append!Bool(Bool.bfalse); - assert(buffer.data == [1, 0]); + buffer.append!Bool(Bool.btrue); + assert(buffer.data == [1, 0, 1]); +} - buffer.append!Bool(Bool.btrue); - assert(buffer.data == [1, 0, 1]); - } +/// enum - float +@safe unittest +{ + import std.array : appender; + auto buffer = appender!(const ubyte[])(); + enum Float: float { - //enum - float - auto buffer = appender!(const ubyte[])(); + one = 32.0f, + two = 25.0f + } - 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.one); - assert(buffer.data == [66, 0, 0, 0]); + buffer.append!Float(Float.two); + assert(buffer.data == [66, 0, 0, 0, 65, 200, 0, 0]); +} - buffer.append!Float(Float.two); - assert(buffer.data == [66, 0, 0, 0, 65, 200, 0, 0]); - } +/// enum - double +@safe unittest +{ + import std.array : appender; + auto buffer = appender!(const ubyte[])(); + enum Double: double { - //enum - double - auto buffer = appender!(const ubyte[])(); + one = 32.0, + two = 25.0 + } - 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.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]); +} - 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 +@safe unittest +{ + import std.array : appender; + auto buffer = appender!(const ubyte[])(); + enum Real: real { - //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))); + 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)) - { + import std.meta : AliasSeq; + static foreach (endianness; [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]; @@ -3791,7 +4513,7 @@ if (canSwapEndianness!T && isOutputRange!(R, ubyte)) size_t index = 0; size_t length = 0; - foreach (T; Types) + static foreach (T; Types) { toWrite.append!(T, endianness)(cast(T) values[index++]); length += T.sizeof; @@ -3801,7 +4523,7 @@ if (canSwapEndianness!T && isOutputRange!(R, ubyte)) assert(toRead.length == length); index = 0; - foreach (T; Types) + static 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)); @@ -3814,34 +4536,27 @@ if (canSwapEndianness!T && isOutputRange!(R, ubyte)) ++index; } assert(toRead.empty); - } + }} } /** -Counts the number of set bits in the binary representation of $(D value). +Counts the number of set bits in the binary representation of `value`. For signed integers, the sign bit is included in the count. */ -private uint countBitsSet(T)(T value) @nogc pure nothrow +private uint countBitsSet(T)(const 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; + import core.bitop : popcnt; + const c = popcnt(cast(ulong) value); } 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; + import core.bitop : popcnt; + const c = popcnt(cast(uint) value); } + // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel else static if (T.sizeof == 2) { uint c = value - ((value >> 1) & 0x5555); @@ -3873,7 +4588,7 @@ if (isIntegral!T) @safe unittest { import std.meta; - foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) { assert(countBitsSet(cast(T) 0) == 0); assert(countBitsSet(cast(T) 1) == 1); @@ -3891,6 +4606,8 @@ if (isIntegral!T) { assert(countBitsSet(T.max) == 8 * T.sizeof); } + // Check CTFE compiles. + static assert(countBitsSet(cast(T) 1) == 1); } assert(countBitsSet(1_000_000) == 7); foreach (i; 0 .. 63) @@ -3916,7 +4633,7 @@ private struct BitsSet(T) _index = startIndex + trailingZerosCount; } - @property size_t front() + @property size_t front() const { return _index; } @@ -3941,12 +4658,12 @@ private struct BitsSet(T) _index += trailingZerosCount + 1; } - @property auto save() + @property BitsSet save() const { return this; } - @property size_t length() + @property size_t length() const { return countBitsSet(_value); } @@ -3956,11 +4673,11 @@ private struct BitsSet(T) } /** -Range that iterates the indices of the set bits in $(D value). +Range that iterates the indices of the set bits in `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 +auto bitsSet(T)(const T value) @nogc pure nothrow if (isIntegral!T) { return BitsSet!T(value); @@ -3984,7 +4701,7 @@ if (isIntegral!T) import std.range : iota; import std.meta; - foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) { assert(bitsSet(cast(T) 0).empty); assert(bitsSet(cast(T) 1).equal([0])); diff --git a/libphobos/src/std/compiler.d b/libphobos/src/std/compiler.d index cb038f9..2f983c5 100644 --- a/libphobos/src/std/compiler.d +++ b/libphobos/src/std/compiler.d @@ -3,12 +3,12 @@ /** * Identify the compiler used and its various features. * - * Copyright: Copyright Digital Mars 2000 - 2011. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/compiler.d) */ -/* Copyright Digital Mars 2000 - 2011. +/* Copyright The D Language Foundation 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) diff --git a/libphobos/src/std/complex.d b/libphobos/src/std/complex.d index 8e488db..756d1ca 100644 --- a/libphobos/src/std/complex.d +++ b/libphobos/src/std/complex.d @@ -1,23 +1,32 @@ // 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. + 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). + the built-in types `cfloat`, `cdouble`, `creal`, `ifloat`, + `idouble`, and `ireal`. + + Macros: + TABLE_SV = + + $0
Special Values
+ PLUSMN = ± + NAN = $(RED NAN) + INFIN = ∞ + PI = π 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) + Source: $(PHOBOSSRC std/complex.d) */ module std.complex; import std.traits; -/** Helper function that returns a _complex number with the specified +/** Helper function that returns a complex number with the specified real and imaginary parts. Params: @@ -28,13 +37,13 @@ import std.traits; 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 + `Complex` instance with real and imaginary parts set + to the values provided as input. If neither `re` nor + `im` are floating-point numbers, the return type will + be `Complex!double`. Otherwise, the return type is deduced using $(D std.traits.CommonType!(R, I)). */ -auto complex(R)(R re) @safe pure nothrow @nogc +auto complex(R)(const R re) @safe pure nothrow @nogc if (is(R : double)) { static if (isFloatingPoint!R) @@ -44,7 +53,7 @@ if (is(R : double)) } /// ditto -auto complex(R, I)(R re, I im) @safe pure nothrow @nogc +auto complex(R, I)(const R re, const I im) @safe pure nothrow @nogc if (is(R : double) && is(I : double)) { static if (isFloatingPoint!R || isFloatingPoint!I) @@ -93,13 +102,13 @@ if (is(R : double) && is(I : double)) } -/** A complex number parametrised by a type $(D T), which must be either - $(D float), $(D double) or $(D real). +/** A complex number parametrised by a type `T`, which must be either + `float`, `double` or `real`. */ struct Complex(T) if (isFloatingPoint!T) { - import std.format : FormatSpec; + import std.format.spec : FormatSpec; import std.range.primitives : isOutputRange; /** The real part of the number. */ @@ -146,12 +155,11 @@ if (isFloatingPoint!T) } /// ditto - void toString(Writer, Char)(scope Writer w, - FormatSpec!Char formatSpec) const + void toString(Writer, Char)(scope Writer w, scope const ref FormatSpec!Char formatSpec) const if (isOutputRange!(Writer, const(Char)[])) { - import std.format : formatValue; - import std.math : signbit; + import std.format.write : formatValue; + import std.math.traits : signbit; import std.range.primitives : put; formatValue(w, re, formatSpec); if (signbit(im) == 0) @@ -174,14 +182,14 @@ if (isFloatingPoint!T) } /// ditto - this(Rx : T, Ry : T)(Rx x, Ry y) + this(Rx : T, Ry : T)(const Rx x, const Ry y) { re = x; im = y; } /// ditto - this(R : T)(R r) + this(R : T)(const R r) { re = r; im = 0; @@ -198,7 +206,7 @@ if (isFloatingPoint!T) } // this = numeric - ref Complex opAssign(R : T)(R r) + ref Complex opAssign(R : T)(const R r) { re = r; im = 0; @@ -214,7 +222,7 @@ if (isFloatingPoint!T) } // this == numeric - bool opEquals(R : T)(R r) const + bool opEquals(R : T)(const R r) const { return re == r && im == 0; } @@ -246,7 +254,7 @@ if (isFloatingPoint!T) } // complex op numeric - Complex!(CommonType!(T,R)) opBinary(string op, R)(R r) const + Complex!(CommonType!(T,R)) opBinary(string op, R)(const R r) const if (isNumeric!R) { alias C = typeof(return); @@ -255,50 +263,68 @@ if (isFloatingPoint!T) } // numeric + complex, numeric * complex - Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(const 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 + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(const 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 + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(const R r) const if (op == "/" && isNumeric!R) { - import std.math : fabs; - typeof(return) w = void; - if (fabs(re) < fabs(im)) + version (FastMath) { - immutable ratio = re/im; - immutable rdivd = r/(re*ratio + im); - - w.re = rdivd*ratio; - w.im = -rdivd; + // Compute norm(this) + immutable norm = re * re + im * im; + // Compute r * conj(this) + immutable prod_re = r * re; + immutable prod_im = r * -im; + // Divide the product by the norm + typeof(return) w = void; + w.re = prod_re / norm; + w.im = prod_im / norm; + return w; } else { - immutable ratio = im/re; - immutable rdivd = r/(re + im*ratio); - - w.re = rdivd; - w.im = -rdivd*ratio; + import core.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; } - - return w; } // numeric ^^ complex - Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R lhs) const + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(const R lhs) const if (op == "^^" && isNumeric!R) { - import std.math : cos, exp, log, sin, PI; + import core.math : cos, sin; + import std.math.exponential : exp, log; + import std.math.constants : PI; Unqual!(CommonType!(T, R)) ab = void, ar = void; if (lhs >= 0) @@ -322,7 +348,7 @@ if (isFloatingPoint!T) // OP-ASSIGN OPERATORS // complex += complex, complex -= complex - ref Complex opOpAssign(string op, C)(C z) + ref Complex opOpAssign(string op, C)(const C z) if ((op == "+" || op == "-") && is(C R == Complex!R)) { mixin ("re "~op~"= z.re;"); @@ -331,7 +357,7 @@ if (isFloatingPoint!T) } // complex *= complex - ref Complex opOpAssign(string op, C)(C z) + ref Complex opOpAssign(string op, C)(const C z) if (op == "*" && is(C R == Complex!R)) { auto temp = re*z.re - im*z.im; @@ -341,36 +367,52 @@ if (isFloatingPoint!T) } // complex /= complex - ref Complex opOpAssign(string op, C)(C z) + ref Complex opOpAssign(string op, C)(const C z) if (op == "/" && is(C R == Complex!R)) { - import std.math : fabs; - if (fabs(z.re) < fabs(z.im)) + version (FastMath) { - 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; + // Compute norm(z) + immutable norm = z.re * z.re + z.im * z.im; + // Compute this * conj(z) + immutable prod_re = re * z.re - im * -z.im; + immutable prod_im = im * z.re + re * -z.im; + // Divide the product by the norm + re = prod_re / norm; + im = prod_im / norm; + return this; } 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; + import core.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; } - return this; } // complex ^^= complex - ref Complex opOpAssign(string op, C)(C z) + ref Complex opOpAssign(string op, C)(const C z) if (op == "^^" && is(C R == Complex!R)) { - import std.math : exp, log, cos, sin; + import core.math : cos, sin; + import std.math.exponential : exp, log; immutable r = abs(this); immutable t = arg(this); immutable ab = r^^z.re * exp(-t*z.im); @@ -382,7 +424,7 @@ if (isFloatingPoint!T) } // complex += numeric, complex -= numeric - ref Complex opOpAssign(string op, U : T)(U a) + ref Complex opOpAssign(string op, U : T)(const U a) if (op == "+" || op == "-") { mixin ("re "~op~"= a;"); @@ -390,7 +432,7 @@ if (isFloatingPoint!T) } // complex *= numeric, complex /= numeric - ref Complex opOpAssign(string op, U : T)(U a) + ref Complex opOpAssign(string op, U : T)(const U a) if (op == "*" || op == "/") { mixin ("re "~op~"= a;"); @@ -399,10 +441,10 @@ if (isFloatingPoint!T) } // complex ^^= real - ref Complex opOpAssign(string op, R)(R r) + ref Complex opOpAssign(string op, R)(const R r) if (op == "^^" && isFloatingPoint!R) { - import std.math : cos, sin; + import core.math : cos, sin; immutable ab = abs(this)^^r; immutable ar = arg(this)*r; re = ab*cos(ar); @@ -411,7 +453,7 @@ if (isFloatingPoint!T) } // complex ^^= int - ref Complex opOpAssign(string op, U)(U i) + ref Complex opOpAssign(string op, U)(const U i) if (op == "^^" && isIntegral!U) { switch (i) @@ -441,6 +483,7 @@ if (isFloatingPoint!T) @safe pure nothrow unittest { import std.complex; + static import core.math; import std.math; enum EPS = double.epsilon; @@ -465,16 +508,16 @@ if (isFloatingPoint!T) 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)); + assert(isClose(abs(ctc), abs(c1)*abs(c2), EPS)); + assert(isClose(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)); + assert(isClose(abs(cdc), abs(c1)/abs(c2), EPS)); + assert(isClose(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)); + assert(isClose(cec.re, 0.1152413197994, 1e-12)); + assert(isClose(cec.im, 0.2187079045274, 1e-12)); // Check complex-real operations. double a = 123.456; @@ -492,12 +535,12 @@ if (isFloatingPoint!T) 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)); + assert(isClose(abs(cdr), abs(c1)/a, EPS)); + assert(isClose(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)); + assert(isClose(abs(cer), abs(c1)^^3, EPS)); + assert(isClose(arg(cer), arg(c1)*3, EPS)); auto rpc = a + c1; assert(rpc == cpr); @@ -510,12 +553,12 @@ if (isFloatingPoint!T) assert(rtc == ctr); auto rdc = a / c1; - assert(approxEqual(abs(rdc), a/abs(c1), EPS)); - assert(approxEqual(arg(rdc), -arg(c1), EPS)); + assert(isClose(abs(rdc), a/abs(c1), EPS)); + assert(isClose(arg(rdc), -arg(c1), EPS)); rdc = a / c2; - assert(approxEqual(abs(rdc), a/abs(c2), EPS)); - assert(approxEqual(arg(rdc), -arg(c2), EPS)); + assert(isClose(abs(rdc), a/abs(c2), EPS)); + assert(isClose(arg(rdc), -arg(c2), EPS)); auto rec1a = 1.0 ^^ c1; assert(rec1a.re == 1.0); @@ -526,26 +569,26 @@ if (isFloatingPoint!T) assert(rec2a.im == 0.0); auto rec1b = (-1.0) ^^ c1; - assert(approxEqual(abs(rec1b), std.math.exp(-PI * c1.im), EPS)); + assert(isClose(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)); + assert(isClose(arg1b, PI, EPS) || isClose(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)); + assert(isClose(abs(rec2b), std.math.exp(-2 * PI), EPS)); + assert(isClose(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)); + assert(isClose(rec3a.re, rec3b.re, 1e-14)); + assert(isClose(rec3a.im, rec3b.im, 1e-14)); 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)); + assert(isClose(rec4a.re, rec4b.re, 1e-14)); + assert(isClose(rec4a.im, rec4b.im, 1e-14)); auto rer = a ^^ complex(2.0, 0.0); auto rcheck = a ^^ 2.0; @@ -558,13 +601,13 @@ if (isFloatingPoint!T) rcheck = (-a) ^^ 2.0; assert(feqrel(rer2.re, rcheck) == double.mant_dig); assert(isIdentical(rer2.re, rcheck)); - assert(approxEqual(rer2.im, 0.0, EPS)); + assert(isClose(rer2.im, 0.0, 0.0, 1e-10)); 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)); + assert(isClose(rer3.im, 0.0, 0.0, EPS)); auto rer4 = a ^^ complex(-2.0, 0.0); rcheck = a ^^ (-2.0); @@ -576,10 +619,10 @@ if (isFloatingPoint!T) foreach (i; 0 .. 6) { auto cei = c1^^i; - assert(approxEqual(abs(cei), abs(c1)^^i, EPS)); + assert(isClose(abs(cei), abs(c1)^^i, 1e-14)); // 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)); + assert(isClose(core.math.cos(arg(cei)), core.math.cos(arg(c1)*i), 1e-14)); } // Check operations between different complex types. @@ -596,22 +639,22 @@ if (isFloatingPoint!T) auto c2c = c2; c1c /= c1; - assert(approxEqual(c1c.re, 1.0, EPS)); - assert(approxEqual(c1c.im, 0.0, EPS)); + assert(isClose(c1c.re, 1.0, EPS)); + assert(isClose(c1c.im, 0.0, 0.0, EPS)); c1c = c1; c1c /= c2; - assert(approxEqual(c1c.re, 0.588235, EPS)); - assert(approxEqual(c1c.im, -0.352941, EPS)); + assert(isClose(c1c.re, 0.5882352941177, 1e-12)); + assert(isClose(c1c.im, -0.3529411764706, 1e-12)); c2c /= c1; - assert(approxEqual(c2c.re, 1.25, EPS)); - assert(approxEqual(c2c.im, 0.75, EPS)); + assert(isClose(c2c.re, 1.25, EPS)); + assert(isClose(c2c.im, 0.75, EPS)); c2c = c2; c2c /= c2; - assert(approxEqual(c2c.re, 1.0, EPS)); - assert(approxEqual(c2c.im, 0.0, EPS)); + assert(isClose(c2c.re, 1.0, EPS)); + assert(isClose(c2c.im, 0.0, 0.0, EPS)); } @safe pure nothrow unittest @@ -697,19 +740,39 @@ if (is(T R == Complex!R)) */ T abs(T)(Complex!T z) @safe pure nothrow @nogc { - import std.math : hypot; + import std.math.algebraic : hypot; return hypot(z.re, z.im); } /// @safe pure nothrow unittest { - static import std.math; + static import core.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)); + assert(abs(complex(1.0L, -2.0L)) == core.math.sqrt(5.0L)); +} + +@safe pure nothrow @nogc unittest +{ + static import core.math; + assert(abs(complex(0.0L, -3.2L)) == 3.2L); + assert(abs(complex(0.0L, 71.6L)) == 71.6L); + assert(abs(complex(-1.0L, 1.0L)) == core.math.sqrt(2.0L)); } +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + static foreach (T; AliasSeq!(float, double, real)) + {{ + static import std.math; + Complex!T a = complex(T(-12), T(3)); + T b = std.math.hypot(a.re, a.im); + assert(std.math.isClose(abs(a), b)); + assert(std.math.isClose(abs(-a), b)); + }} +} /++ Params: @@ -726,17 +789,17 @@ T sqAbs(T)(Complex!T z) @safe pure nothrow @nogc /// @safe pure nothrow unittest { - import std.math; + import std.math.operations : isClose; 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)); + assert(isClose(sqAbs(complex(1.0L, -2.0L)), 5.0L)); + assert(isClose(sqAbs(complex(-3.0L, 1.0L)), 10.0L)); + assert(isClose(sqAbs(complex(1.0f,-1.0f)), 2.0f)); } /// ditto -T sqAbs(T)(T x) @safe pure nothrow @nogc +T sqAbs(T)(const T x) @safe pure nothrow @nogc if (isFloatingPoint!T) { return x*x; @@ -744,11 +807,11 @@ if (isFloatingPoint!T) @safe pure nothrow unittest { - import std.math; + import std.math.operations : isClose; 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)); + assert(isClose(sqAbs(-3.0L), 9.0L)); + assert(isClose(sqAbs(-5.0f), 25.0f)); } @@ -758,14 +821,14 @@ if (isFloatingPoint!T) */ T arg(T)(Complex!T z) @safe pure nothrow @nogc { - import std.math : atan2; + import std.math.trigonometry : atan2; return atan2(z.im, z.re); } /// @safe pure nothrow unittest { - import std.math; + import std.math.constants : PI_2, PI_4; 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); @@ -773,6 +836,30 @@ T arg(T)(Complex!T z) @safe pure nothrow @nogc /** + * Extracts the norm of a complex number. + * Params: + * z = A complex number + * Returns: + * The squared magnitude of `z`. + */ +T norm(T)(Complex!T z) @safe pure nothrow @nogc +{ + return z.re * z.re + z.im * z.im; +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + assert(norm(complex(3.0, 4.0)) == 25.0); + assert(norm(fromPolar(5.0, 0.0)) == 25.0); + assert(isClose(norm(fromPolar(5.0L, PI / 6)), 25.0L)); + assert(isClose(norm(fromPolar(5.0L, 13 * PI / 6)), 25.0L)); +} + + +/** Params: z = A complex number. Returns: The complex conjugate of `z`. */ @@ -788,6 +875,43 @@ Complex!T conj(T)(Complex!T z) @safe pure nothrow @nogc assert(conj(complex(1.0, 2.0)) == complex(1.0, -2.0)); } +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + static foreach (T; AliasSeq!(float, double, real)) + {{ + auto c = Complex!T(7, 3L); + assert(conj(c) == Complex!T(7, -3L)); + auto z = Complex!T(0, -3.2L); + assert(conj(z) == -z); + }} +} + +/** + * Returns the projection of `z` onto the Riemann sphere. + * Params: + * z = A complex number + * Returns: + * The projection of `z` onto the Riemann sphere. + */ +Complex!T proj(T)(Complex!T z) +{ + static import std.math; + + if (std.math.isInfinity(z.re) || std.math.isInfinity(z.im)) + return Complex!T(T.infinity, std.math.copysign(0.0, z.im)); + + return z; +} + +/// +@safe pure nothrow unittest +{ + assert(proj(complex(1.0)) == complex(1.0)); + assert(proj(complex(double.infinity, 5.0)) == complex(double.infinity, 0.0)); + assert(proj(complex(5.0, -double.infinity)) == complex(double.infinity, -0.0)); +} + /** Constructs a complex number given its absolute value and argument. @@ -796,10 +920,10 @@ Complex!T conj(T)(Complex!T z) @safe pure nothrow @nogc argument = The argument Returns: The complex number with the given modulus and argument. */ -Complex!(CommonType!(T, U)) fromPolar(T, U)(T modulus, U argument) +Complex!(CommonType!(T, U)) fromPolar(T, U)(const T modulus, const U argument) @safe pure nothrow @nogc { - import std.math : sin, cos; + import core.math : sin, cos; return Complex!(CommonType!(T,U)) (modulus*cos(argument), modulus*sin(argument)); } @@ -807,22 +931,35 @@ Complex!(CommonType!(T, U)) fromPolar(T, U)(T modulus, U 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)); + import core.math; + import std.math.operations : isClose; + import std.math.algebraic : sqrt; + import std.math.constants : PI_4; + auto z = fromPolar(core.math.sqrt(2.0), PI_4); + assert(isClose(z.re, 1.0L)); + assert(isClose(z.im, 1.0L)); } +version (StdUnittest) +{ + // Helper function for comparing two Complex numbers. + int ceqrel(T)(const Complex!T x, const Complex!T y) @safe pure nothrow @nogc + { + import std.math.operations : feqrel; + const r = feqrel(x.re, y.re); + const i = feqrel(x.im, y.im); + return r < i ? r : i; + } +} /** Trigonometric functions on complex numbers. Params: z = A complex number. - Returns: The sine and cosine of `z`, respectively. + Returns: The sine, cosine and tangent 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); @@ -831,21 +968,20 @@ Complex!T sin(T)(Complex!T z) @safe pure nothrow @nogc /// @safe pure nothrow unittest { - static import std.math; - import std.math : feqrel; + static import core.math; assert(sin(complex(0.0)) == 0.0); - assert(sin(complex(2.0, 0)) == std.math.sin(2.0)); - auto c1 = sin(complex(2.0L, 0)); - auto c2 = complex(std.math.sin(2.0L), 0); - assert(feqrel(c1.re, c2.re) >= real.mant_dig - 1 && - feqrel(c1.im, c2.im) >= real.mant_dig - 1); + assert(sin(complex(2.0, 0)) == core.math.sin(2.0)); } +@safe pure nothrow unittest +{ + static import core.math; + assert(ceqrel(sin(complex(2.0L, 0)), complex(core.math.sin(2.0L))) >= real.mant_dig - 1); +} /// 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); @@ -854,18 +990,235 @@ Complex!T cos(T)(Complex!T z) @safe pure nothrow @nogc /// @safe pure nothrow unittest { + static import core.math; static import std.math; - import std.math : feqrel; assert(cos(complex(0.0)) == 1.0); - assert(cos(complex(1.3)) == std.math.cos(1.3)); - auto c1 = cos(complex(0, 5.2L)); - auto c2 = complex(std.math.cosh(5.2L), 0.0L); - assert(feqrel(c1.re, c2.re) >= real.mant_dig - 1 && - feqrel(c1.im, c2.im) >= real.mant_dig - 1); - auto c3 = cos(complex(1.3L)); - auto c4 = complex(std.math.cos(1.3L), 0.0L); - assert(feqrel(c3.re, c4.re) >= real.mant_dig - 1 && - feqrel(c3.im, c4.im) >= real.mant_dig - 1); + assert(cos(complex(1.3, 0.0)) == core.math.cos(1.3)); + assert(cos(complex(0.0, 5.2)) == std.math.cosh(5.2)); +} + +@safe pure nothrow unittest +{ + static import core.math; + static import std.math; + assert(ceqrel(cos(complex(0, 5.2L)), complex(std.math.cosh(5.2L), 0.0L)) >= real.mant_dig - 1); + assert(ceqrel(cos(complex(1.3L)), complex(core.math.cos(1.3L))) >= real.mant_dig - 1); +} + +/// ditto +Complex!T tan(T)(Complex!T z) @safe pure nothrow @nogc +{ + return sin(z) / cos(z); +} + +/// +@safe pure nothrow @nogc unittest +{ + static import std.math; + assert(ceqrel(tan(complex(1.0, 0.0)), complex(std.math.tan(1.0), 0.0)) >= double.mant_dig - 2); + assert(ceqrel(tan(complex(0.0, 1.0)), complex(0.0, std.math.tanh(1.0))) >= double.mant_dig - 2); +} + +/** + Inverse trigonometric functions on complex numbers. + + Params: z = A complex number. + Returns: The arcsine, arccosine and arctangent of `z`, respectively. +*/ +Complex!T asin(T)(Complex!T z) @safe pure nothrow @nogc +{ + auto ash = asinh(Complex!T(-z.im, z.re)); + return Complex!T(ash.im, -ash.re); +} + +/// +@safe pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + assert(asin(complex(0.0)) == 0.0); + assert(isClose(asin(complex(0.5L)), PI / 6)); +} + +@safe pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + version (DigitalMars) {} else // Disabled because of issue 21376 + assert(isClose(asin(complex(0.5f)), float(PI) / 6)); +} + +/// ditto +Complex!T acos(T)(Complex!T z) @safe pure nothrow @nogc +{ + static import std.math; + auto as = asin(z); + return Complex!T(T(std.math.PI_2) - as.re, as.im); +} + +/// +@safe pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + import std.math.trigonometry : std_math_acos = acos; + assert(acos(complex(0.0)) == std_math_acos(0.0)); + assert(isClose(acos(complex(0.5L)), PI / 3)); +} + +@safe pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + version (DigitalMars) {} else // Disabled because of issue 21376 + assert(isClose(acos(complex(0.5f)), float(PI) / 3)); +} + +/// ditto +Complex!T atan(T)(Complex!T z) @safe pure nothrow @nogc +{ + static import std.math; + const T re2 = z.re * z.re; + const T x = 1 - re2 - z.im * z.im; + + T num = z.im + 1; + T den = z.im - 1; + + num = re2 + num * num; + den = re2 + den * den; + + return Complex!T(T(0.5) * std.math.atan2(2 * z.re, x), + T(0.25) * std.math.log(num / den)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + assert(atan(complex(0.0)) == 0.0); + assert(isClose(atan(sqrt(complex(3.0L))), PI / 3)); + assert(isClose(atan(sqrt(complex(3.0f))), float(PI) / 3)); +} + +/** + Hyperbolic trigonometric functions on complex numbers. + + Params: z = A complex number. + Returns: The hyperbolic sine, cosine and tangent of `z`, respectively. +*/ +Complex!T sinh(T)(Complex!T z) @safe pure nothrow @nogc +{ + static import core.math, std.math; + return Complex!T(std.math.sinh(z.re) * core.math.cos(z.im), + std.math.cosh(z.re) * core.math.sin(z.im)); +} + +/// +@safe pure nothrow unittest +{ + static import std.math; + assert(sinh(complex(0.0)) == 0.0); + assert(sinh(complex(1.0L)) == std.math.sinh(1.0L)); + assert(sinh(complex(1.0f)) == std.math.sinh(1.0f)); +} + +/// ditto +Complex!T cosh(T)(Complex!T z) @safe pure nothrow @nogc +{ + static import core.math, std.math; + return Complex!T(std.math.cosh(z.re) * core.math.cos(z.im), + std.math.sinh(z.re) * core.math.sin(z.im)); +} + +/// +@safe pure nothrow unittest +{ + static import std.math; + assert(cosh(complex(0.0)) == 1.0); + assert(cosh(complex(1.0L)) == std.math.cosh(1.0L)); + assert(cosh(complex(1.0f)) == std.math.cosh(1.0f)); +} + +/// ditto +Complex!T tanh(T)(Complex!T z) @safe pure nothrow @nogc +{ + return sinh(z) / cosh(z); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.trigonometry : std_math_tanh = tanh; + assert(tanh(complex(0.0)) == 0.0); + assert(isClose(tanh(complex(1.0L)), std_math_tanh(1.0L))); + assert(isClose(tanh(complex(1.0f)), std_math_tanh(1.0f))); +} + +/** + Inverse hyperbolic trigonometric functions on complex numbers. + + Params: z = A complex number. + Returns: The hyperbolic arcsine, arccosine and arctangent of `z`, respectively. +*/ +Complex!T asinh(T)(Complex!T z) @safe pure nothrow @nogc +{ + auto t = Complex!T((z.re - z.im) * (z.re + z.im) + 1, 2 * z.re * z.im); + return log(sqrt(t) + z); +} + +/// +@safe pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.trigonometry : std_math_asinh = asinh; + assert(asinh(complex(0.0)) == 0.0); + assert(isClose(asinh(complex(1.0L)), std_math_asinh(1.0L))); + assert(isClose(asinh(complex(1.0f)), std_math_asinh(1.0f))); +} + +/// ditto +Complex!T acosh(T)(Complex!T z) @safe pure nothrow @nogc +{ + return 2 * log(sqrt(T(0.5) * (z + 1)) + sqrt(T(0.5) * (z - 1))); +} + +/// +@safe pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.trigonometry : std_math_acosh = acosh; + assert(acosh(complex(1.0)) == 0.0); + assert(isClose(acosh(complex(3.0L)), std_math_acosh(3.0L))); + assert(isClose(acosh(complex(3.0f)), std_math_acosh(3.0f))); +} + +/// ditto +Complex!T atanh(T)(Complex!T z) @safe pure nothrow @nogc +{ + static import std.math; + const T im2 = z.im * z.im; + const T x = 1 - im2 - z.re * z.re; + + T num = 1 + z.re; + T den = 1 - z.re; + + num = im2 + num * num; + den = im2 + den * den; + + return Complex!T(T(0.25) * (std.math.log(num) - std.math.log(den)), + T(0.5) * std.math.atan2(2 * z.im, x)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.trigonometry : std_math_atanh = atanh; + assert(atanh(complex(0.0)) == 0.0); + assert(isClose(atanh(complex(0.5L)), std_math_atanh(0.5L))); + assert(isClose(atanh(complex(0.5f)), std_math_atanh(0.5f))); } /** @@ -873,29 +1226,50 @@ Complex!T cos(T)(Complex!T z) @safe pure nothrow @nogc 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. + `expi` is included here for convenience and for easy migration of code. */ Complex!real expi(real y) @trusted pure nothrow @nogc { - import std.math : cos, sin; + import core.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))); + import core.math : cos, sin; 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); + assert(expi(1.3e5L) == complex(cos(1.3e5L), sin(1.3e5L))); } +/** + Params: y = A real number. + Returns: The value of cosh(y) + i sinh(y) + + Note: + `coshisinh` is included here for convenience and for easy migration of code. +*/ +Complex!real coshisinh(real y) @safe pure nothrow @nogc +{ + static import core.math; + static import std.math; + if (core.math.fabs(y) <= 0.5) + return Complex!real(std.math.cosh(y), std.math.sinh(y)); + else + { + auto z = std.math.exp(y); + auto zi = 0.5 / z; + z = 0.5 * z; + return Complex!real(z + zi, z - zi); + } +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.trigonometry : cosh, sinh; + assert(coshisinh(3.0L) == complex(cosh(3.0L), sinh(3.0L))); +} /** Params: z = A complex number. @@ -903,7 +1277,7 @@ Complex!real expi(real y) @trusted pure nothrow @nogc */ Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc { - static import std.math; + static import core.math; typeof(return) c; real x,y,w,r; @@ -916,19 +1290,19 @@ Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc real z_re = z.re; real z_im = z.im; - x = std.math.fabs(z_re); - y = std.math.fabs(z_im); + x = core.math.fabs(z_re); + y = core.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))); + w = core.math.sqrt(x) + * core.math.sqrt(0.5 * (1 + core.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))); + w = core.math.sqrt(y) + * core.math.sqrt(0.5 * (r + core.math.sqrt(1 + r * r))); } if (z_re >= 0) @@ -948,29 +1322,31 @@ Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc /// @safe pure nothrow unittest { - static import std.math; + static import core.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)) == core.math.sqrt(1.0L)); assert(sqrt(complex(-1.0L, 0)) == complex(0, 1.0L)); + assert(sqrt(complex(-8.0, -6.0)) == complex(1.0, -3.0)); } @safe pure nothrow unittest { - import std.math : approxEqual; + import std.math.operations : isClose; 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)); + assert(isClose(c1s.re, 1.09868411347)); + assert(isClose(c1s.im, 0.455089860562)); auto c2s = sqrt(c2); - assert(approxEqual(c2s.re, 1.1317134)); - assert(approxEqual(c2s.im, 0.8836155)); + assert(isClose(c2s.re, 1.13171392428)); + assert(isClose(c2s.im, 0.883615530876)); } -// Issue 10881: support %f formatting of complex numbers +// support %f formatting of complex numbers +// https://issues.dlang.org/show_bug.cgi?id=10881 @safe unittest { import std.format : format; @@ -985,7 +1361,7 @@ Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc @safe unittest { // Test wide string formatting - import std.format; + import std.format.write : formattedWrite; wstring wformat(T)(string format, Complex!T c) { import std.array : appender; @@ -1003,3 +1379,526 @@ Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc // Test ease of use (vanilla toString() should be supported) assert(complex(1.2, 3.4).toString() == "1.2+3.4i"); } + +@safe pure nothrow @nogc unittest +{ + auto c = complex(3.0L, 4.0L); + c = sqrt(c); + assert(c.re == 2.0L); + assert(c.im == 1.0L); +} + +/** + * Calculates e$(SUPERSCRIPT x). + * Params: + * x = A complex number + * Returns: + * The complex base e exponential of `x` + * + * $(TABLE_SV + * $(TR $(TH x) $(TH exp(x))) + * $(TR $(TD ($(PLUSMN)0, +0)) $(TD (1, +0))) + * $(TR $(TD (any, +$(INFIN))) $(TD ($(NAN), $(NAN)))) + * $(TR $(TD (any, $(NAN)) $(TD ($(NAN), $(NAN))))) + * $(TR $(TD (+$(INFIN), +0)) $(TD (+$(INFIN), +0))) + * $(TR $(TD (-$(INFIN), any)) $(TD ($(PLUSMN)0, cis(x.im)))) + * $(TR $(TD (+$(INFIN), any)) $(TD ($(PLUSMN)$(INFIN), cis(x.im)))) + * $(TR $(TD (-$(INFIN), +$(INFIN))) $(TD ($(PLUSMN)0, $(PLUSMN)0))) + * $(TR $(TD (+$(INFIN), +$(INFIN))) $(TD ($(PLUSMN)$(INFIN), $(NAN)))) + * $(TR $(TD (-$(INFIN), $(NAN))) $(TD ($(PLUSMN)0, $(PLUSMN)0))) + * $(TR $(TD (+$(INFIN), $(NAN))) $(TD ($(PLUSMN)$(INFIN), $(NAN)))) + * $(TR $(TD ($(NAN), +0)) $(TD ($(NAN), +0))) + * $(TR $(TD ($(NAN), any)) $(TD ($(NAN), $(NAN)))) + * $(TR $(TD ($(NAN), $(NAN))) $(TD ($(NAN), $(NAN)))) + * ) + */ +Complex!T exp(T)(Complex!T x) @trusted pure nothrow @nogc // TODO: @safe +{ + static import std.math; + + // Handle special cases explicitly here, as fromPolar will otherwise + // cause them to return Complex!T(NaN, NaN), or with the wrong sign. + if (std.math.isInfinity(x.re)) + { + if (std.math.isNaN(x.im)) + { + if (std.math.signbit(x.re)) + return Complex!T(0, std.math.copysign(0, x.im)); + else + return x; + } + if (std.math.isInfinity(x.im)) + { + if (std.math.signbit(x.re)) + return Complex!T(0, std.math.copysign(0, x.im)); + else + return Complex!T(T.infinity, -T.nan); + } + if (x.im == 0.0) + { + if (std.math.signbit(x.re)) + return Complex!T(0.0); + else + return Complex!T(T.infinity); + } + } + if (std.math.isNaN(x.re)) + { + if (std.math.isNaN(x.im) || std.math.isInfinity(x.im)) + return Complex!T(T.nan, T.nan); + if (x.im == 0.0) + return x; + } + if (x.re == 0.0) + { + if (std.math.isNaN(x.im) || std.math.isInfinity(x.im)) + return Complex!T(T.nan, T.nan); + if (x.im == 0.0) + return Complex!T(1.0, 0.0); + } + + return fromPolar!(T, T)(std.math.exp(x.re), x.im); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + + assert(exp(complex(0.0, 0.0)) == complex(1.0, 0.0)); + + auto a = complex(2.0, 1.0); + assert(exp(conj(a)) == conj(exp(a))); + + auto b = exp(complex(0.0L, 1.0L) * PI); + assert(isClose(b, -1.0L, 0.0, 1e-15)); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN, isInfinity; + + auto a = exp(complex(0.0, double.infinity)); + assert(a.re.isNaN && a.im.isNaN); + auto b = exp(complex(0.0, double.infinity)); + assert(b.re.isNaN && b.im.isNaN); + auto c = exp(complex(0.0, double.nan)); + assert(c.re.isNaN && c.im.isNaN); + + auto d = exp(complex(+double.infinity, 0.0)); + assert(d == complex(double.infinity, 0.0)); + auto e = exp(complex(-double.infinity, 0.0)); + assert(e == complex(0.0)); + auto f = exp(complex(-double.infinity, 1.0)); + assert(f == complex(0.0)); + auto g = exp(complex(+double.infinity, 1.0)); + assert(g == complex(double.infinity, double.infinity)); + auto h = exp(complex(-double.infinity, +double.infinity)); + assert(h == complex(0.0)); + auto i = exp(complex(+double.infinity, +double.infinity)); + assert(i.re.isInfinity && i.im.isNaN); + auto j = exp(complex(-double.infinity, double.nan)); + assert(j == complex(0.0)); + auto k = exp(complex(+double.infinity, double.nan)); + assert(k.re.isInfinity && k.im.isNaN); + + auto l = exp(complex(double.nan, 0)); + assert(l.re.isNaN && l.im == 0.0); + auto m = exp(complex(double.nan, 1)); + assert(m.re.isNaN && m.im.isNaN); + auto n = exp(complex(double.nan, double.nan)); + assert(n.re.isNaN && n.im.isNaN); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.constants : PI; + import std.math.operations : isClose; + + auto a = exp(complex(0.0, -PI)); + assert(isClose(a, -1.0, 0.0, 1e-15)); + + auto b = exp(complex(0.0, -2.0 * PI / 3.0)); + assert(isClose(b, complex(-0.5L, -0.866025403784438646763L))); + + auto c = exp(complex(0.0, PI / 3.0)); + assert(isClose(c, complex(0.5L, 0.866025403784438646763L))); + + auto d = exp(complex(0.0, 2.0 * PI / 3.0)); + assert(isClose(d, complex(-0.5L, 0.866025403784438646763L))); + + auto e = exp(complex(0.0, PI)); + assert(isClose(e, -1.0, 0.0, 1e-15)); +} + +/** + * Calculate the natural logarithm of x. + * The branch cut is along the negative axis. + * Params: + * x = A complex number + * Returns: + * The complex natural logarithm of `x` + * + * $(TABLE_SV + * $(TR $(TH x) $(TH log(x))) + * $(TR $(TD (-0, +0)) $(TD (-$(INFIN), $(PI)))) + * $(TR $(TD (+0, +0)) $(TD (-$(INFIN), +0))) + * $(TR $(TD (any, +$(INFIN))) $(TD (+$(INFIN), $(PI)/2))) + * $(TR $(TD (any, $(NAN))) $(TD ($(NAN), $(NAN)))) + * $(TR $(TD (-$(INFIN), any)) $(TD (+$(INFIN), $(PI)))) + * $(TR $(TD (+$(INFIN), any)) $(TD (+$(INFIN), +0))) + * $(TR $(TD (-$(INFIN), +$(INFIN))) $(TD (+$(INFIN), 3$(PI)/4))) + * $(TR $(TD (+$(INFIN), +$(INFIN))) $(TD (+$(INFIN), $(PI)/4))) + * $(TR $(TD ($(PLUSMN)$(INFIN), $(NAN))) $(TD (+$(INFIN), $(NAN)))) + * $(TR $(TD ($(NAN), any)) $(TD ($(NAN), $(NAN)))) + * $(TR $(TD ($(NAN), +$(INFIN))) $(TD (+$(INFIN), $(NAN)))) + * $(TR $(TD ($(NAN), $(NAN))) $(TD ($(NAN), $(NAN)))) + * ) + */ +Complex!T log(T)(Complex!T x) @safe pure nothrow @nogc +{ + static import std.math; + + // Handle special cases explicitly here for better accuracy. + // The order here is important, so that the correct path is chosen. + if (std.math.isNaN(x.re)) + { + if (std.math.isInfinity(x.im)) + return Complex!T(T.infinity, T.nan); + else + return Complex!T(T.nan, T.nan); + } + if (std.math.isInfinity(x.re)) + { + if (std.math.isNaN(x.im)) + return Complex!T(T.infinity, T.nan); + else if (std.math.isInfinity(x.im)) + { + if (std.math.signbit(x.re)) + return Complex!T(T.infinity, std.math.copysign(3.0 * std.math.PI_4, x.im)); + else + return Complex!T(T.infinity, std.math.copysign(std.math.PI_4, x.im)); + } + else + { + if (std.math.signbit(x.re)) + return Complex!T(T.infinity, std.math.copysign(std.math.PI, x.im)); + else + return Complex!T(T.infinity, std.math.copysign(0.0, x.im)); + } + } + if (std.math.isNaN(x.im)) + return Complex!T(T.nan, T.nan); + if (std.math.isInfinity(x.im)) + return Complex!T(T.infinity, std.math.copysign(std.math.PI_2, x.im)); + if (x.re == 0.0 && x.im == 0.0) + { + if (std.math.signbit(x.re)) + return Complex!T(-T.infinity, std.math.copysign(std.math.PI, x.im)); + else + return Complex!T(-T.infinity, std.math.copysign(0.0, x.im)); + } + + return Complex!T(std.math.log(abs(x)), arg(x)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import core.math : sqrt; + import std.math.constants : PI; + import std.math.operations : isClose; + + auto a = complex(2.0, 1.0); + assert(log(conj(a)) == conj(log(a))); + + auto b = 2.0 * log10(complex(0.0, 1.0)); + auto c = 4.0 * log10(complex(sqrt(2.0) / 2, sqrt(2.0) / 2)); + assert(isClose(b, c, 0.0, 1e-15)); + + assert(log(complex(-1.0L, 0.0L)) == complex(0.0L, PI)); + assert(log(complex(-1.0L, -0.0L)) == complex(0.0L, -PI)); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN, isInfinity; + import std.math.constants : PI, PI_2, PI_4; + + auto a = log(complex(-0.0L, 0.0L)); + assert(a == complex(-real.infinity, PI)); + auto b = log(complex(0.0L, 0.0L)); + assert(b == complex(-real.infinity, +0.0L)); + auto c = log(complex(1.0L, real.infinity)); + assert(c == complex(real.infinity, PI_2)); + auto d = log(complex(1.0L, real.nan)); + assert(d.re.isNaN && d.im.isNaN); + + auto e = log(complex(-real.infinity, 1.0L)); + assert(e == complex(real.infinity, PI)); + auto f = log(complex(real.infinity, 1.0L)); + assert(f == complex(real.infinity, 0.0L)); + auto g = log(complex(-real.infinity, real.infinity)); + assert(g == complex(real.infinity, 3.0 * PI_4)); + auto h = log(complex(real.infinity, real.infinity)); + assert(h == complex(real.infinity, PI_4)); + auto i = log(complex(real.infinity, real.nan)); + assert(i.re.isInfinity && i.im.isNaN); + + auto j = log(complex(real.nan, 1.0L)); + assert(j.re.isNaN && j.im.isNaN); + auto k = log(complex(real.nan, real.infinity)); + assert(k.re.isInfinity && k.im.isNaN); + auto l = log(complex(real.nan, real.nan)); + assert(l.re.isNaN && l.im.isNaN); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.constants : PI; + import std.math.operations : isClose; + + auto a = log(fromPolar(1.0, PI / 6.0)); + assert(isClose(a, complex(0.0L, 0.523598775598298873077L), 0.0, 1e-15)); + + auto b = log(fromPolar(1.0, PI / 3.0)); + assert(isClose(b, complex(0.0L, 1.04719755119659774615L), 0.0, 1e-15)); + + auto c = log(fromPolar(1.0, PI / 2.0)); + assert(isClose(c, complex(0.0L, 1.57079632679489661923L), 0.0, 1e-15)); + + auto d = log(fromPolar(1.0, 2.0 * PI / 3.0)); + assert(isClose(d, complex(0.0L, 2.09439510239319549230L), 0.0, 1e-15)); + + auto e = log(fromPolar(1.0, 5.0 * PI / 6.0)); + assert(isClose(e, complex(0.0L, 2.61799387799149436538L), 0.0, 1e-15)); + + auto f = log(complex(-1.0L, 0.0L)); + assert(isClose(f, complex(0.0L, PI), 0.0, 1e-15)); +} + +/** + * Calculate the base-10 logarithm of x. + * Params: + * x = A complex number + * Returns: + * The complex base 10 logarithm of `x` + */ +Complex!T log10(T)(Complex!T x) @safe pure nothrow @nogc +{ + static import std.math; + + return log(x) / Complex!T(std.math.log(10.0)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import core.math : sqrt; + import std.math.constants : LN10, PI; + import std.math.operations : isClose; + + auto a = complex(2.0, 1.0); + assert(log10(a) == log(a) / log(complex(10.0))); + + auto b = log10(complex(0.0, 1.0)) * 2.0; + auto c = log10(complex(sqrt(2.0) / 2, sqrt(2.0) / 2)) * 4.0; + assert(isClose(b, c, 0.0, 1e-15)); + + assert(ceqrel(log10(complex(-100.0L, 0.0L)), complex(2.0L, PI / LN10)) >= real.mant_dig - 1); + assert(ceqrel(log10(complex(-100.0L, -0.0L)), complex(2.0L, -PI / LN10)) >= real.mant_dig - 1); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.constants : PI; + import std.math.operations : isClose; + + auto a = log10(fromPolar(1.0, PI / 6.0)); + assert(isClose(a, complex(0.0L, 0.227396058973640224580L), 0.0, 1e-15)); + + auto b = log10(fromPolar(1.0, PI / 3.0)); + assert(isClose(b, complex(0.0L, 0.454792117947280449161L), 0.0, 1e-15)); + + auto c = log10(fromPolar(1.0, PI / 2.0)); + assert(isClose(c, complex(0.0L, 0.682188176920920673742L), 0.0, 1e-15)); + + auto d = log10(fromPolar(1.0, 2.0 * PI / 3.0)); + assert(isClose(d, complex(0.0L, 0.909584235894560898323L), 0.0, 1e-15)); + + auto e = log10(fromPolar(1.0, 5.0 * PI / 6.0)); + assert(isClose(e, complex(0.0L, 1.13698029486820112290L), 0.0, 1e-15)); + + auto f = log10(complex(-1.0L, 0.0L)); + assert(isClose(f, complex(0.0L, 1.36437635384184134748L), 0.0, 1e-15)); +} + +/** + * Calculates x$(SUPERSCRIPT n). + * The branch cut is on the negative axis. + * Params: + * x = base + * n = exponent + * Returns: + * `x` raised to the power of `n` + */ +Complex!T pow(T, Int)(Complex!T x, const Int n) @safe pure nothrow @nogc +if (isIntegral!Int) +{ + alias UInt = Unsigned!(Unqual!Int); + + UInt m = (n < 0) ? -cast(UInt) n : n; + Complex!T y = (m % 2) ? x : Complex!T(1); + + while (m >>= 1) + { + x *= x; + if (m % 2) + y *= x; + } + + return (n < 0) ? Complex!T(1) / y : y; +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + + auto a = complex(1.0, 2.0); + assert(pow(a, 2) == a * a); + assert(pow(a, 3) == a * a * a); + assert(pow(a, -2) == 1.0 / (a * a)); + assert(isClose(pow(a, -3), 1.0 / (a * a * a))); + + auto b = complex(2.0); + assert(ceqrel(pow(b, 3), exp(3 * log(b))) >= double.mant_dig - 1); +} + +/// ditto +Complex!T pow(T)(Complex!T x, const T n) @trusted pure nothrow @nogc +{ + static import std.math; + + if (x == 0.0) + return Complex!T(0.0); + + if (x.im == 0 && x.re > 0.0) + return Complex!T(std.math.pow(x.re, n)); + + Complex!T t = log(x); + return fromPolar!(T, T)(std.math.exp(n * t.re), n * t.im); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + assert(pow(complex(0.0), 2.0) == complex(0.0)); + assert(pow(complex(5.0), 2.0) == complex(25.0)); + + auto a = pow(complex(-1.0, 0.0), 0.5); + assert(isClose(a, complex(0.0, +1.0), 0.0, 1e-16)); + + auto b = pow(complex(-1.0, -0.0), 0.5); + assert(isClose(b, complex(0.0, -1.0), 0.0, 1e-16)); +} + +/// ditto +Complex!T pow(T)(Complex!T x, Complex!T y) @trusted pure nothrow @nogc +{ + return (x == 0) ? Complex!T(0) : exp(y * log(x)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.exponential : exp; + import std.math.constants : PI; + auto a = complex(0.0); + auto b = complex(2.0); + assert(pow(a, b) == complex(0.0)); + + auto c = complex(0.0L, 1.0L); + assert(isClose(pow(c, c), exp((-PI) / 2))); +} + +/// ditto +Complex!T pow(T)(const T x, Complex!T n) @trusted pure nothrow @nogc +{ + static import std.math; + + return (x > 0.0) + ? fromPolar!(T, T)(std.math.pow(x, n.re), n.im * std.math.log(x)) + : pow(Complex!T(x), n); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + assert(pow(2.0, complex(0.0)) == complex(1.0)); + assert(pow(2.0, complex(5.0)) == complex(32.0)); + + auto a = pow(-2.0, complex(-1.0)); + assert(isClose(a, complex(-0.5), 0.0, 1e-16)); + + auto b = pow(-0.5, complex(-1.0)); + assert(isClose(b, complex(-2.0), 0.0, 1e-15)); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.constants : PI; + import std.math.operations : isClose; + + auto a = pow(complex(3.0, 4.0), 2); + assert(isClose(a, complex(-7.0, 24.0))); + + auto b = pow(complex(3.0, 4.0), PI); + assert(ceqrel(b, complex(-152.91512205297134, 35.547499631917738)) >= double.mant_dig - 3); + + auto c = pow(complex(3.0, 4.0), complex(-2.0, 1.0)); + assert(ceqrel(c, complex(0.015351734187477306, -0.0038407695456661503)) >= double.mant_dig - 3); + + auto d = pow(PI, complex(2.0, -1.0)); + assert(ceqrel(d, complex(4.0790296880118296, -8.9872469554541869)) >= double.mant_dig - 1); +} + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + import std.math : RealFormat, floatTraits; + static foreach (T; AliasSeq!(float, double, real)) + {{ + static if (floatTraits!T.realFormat == RealFormat.ibmExtended) + { + /* For IBM real, epsilon is too small (since 1.0 plus any double is + representable) to be able to expect results within epsilon * 100. */ + } + else + { + T eps = T.epsilon * 100; + + T a = -1.0; + T b = 0.5; + Complex!T ref1 = pow(complex(a), complex(b)); + Complex!T res1 = pow(a, complex(b)); + Complex!T res2 = pow(complex(a), b); + assert(abs(ref1 - res1) < eps); + assert(abs(ref1 - res2) < eps); + assert(abs(res1 - res2) < eps); + + T c = -3.2; + T d = 1.4; + Complex!T ref2 = pow(complex(a), complex(b)); + Complex!T res3 = pow(a, complex(b)); + Complex!T res4 = pow(complex(a), b); + assert(abs(ref2 - res3) < eps); + assert(abs(ref2 - res4) < eps); + assert(abs(res3 - res4) < eps); + } + }} +} diff --git a/libphobos/src/std/concurrency.d b/libphobos/src/std/concurrency.d index cf77911..d101ce4 100644 --- a/libphobos/src/std/concurrency.d +++ b/libphobos/src/std/concurrency.d @@ -1,4 +1,49 @@ /** + * $(SCRIPT inhibitQuickIndex = 1;) + * $(DIVC quickindex, + * $(BOOKTABLE, + * $(TR $(TH Category) $(TH Symbols)) + * $(TR $(TD Tid) $(TD + * $(MYREF locate) + * $(MYREF ownerTid) + * $(MYREF register) + * $(MYREF spawn) + * $(MYREF spawnLinked) + * $(MYREF thisTid) + * $(MYREF Tid) + * $(MYREF TidMissingException) + * $(MYREF unregister) + * )) + * $(TR $(TD Message passing) $(TD + * $(MYREF prioritySend) + * $(MYREF receive) + * $(MYREF receiveOnly) + * $(MYREF receiveTimeout) + * $(MYREF send) + * $(MYREF setMaxMailboxSize) + * )) + * $(TR $(TD Message-related types) $(TD + * $(MYREF LinkTerminated) + * $(MYREF MailboxFull) + * $(MYREF MessageMismatch) + * $(MYREF OnCrowding) + * $(MYREF OwnerTerminated) + * $(MYREF PriorityMessageException) + * )) + * $(TR $(TD Scheduler) $(TD + * $(MYREF FiberScheduler) + * $(MYREF Generator) + * $(MYREF Scheduler) + * $(MYREF scheduler) + * $(MYREF ThreadInfo) + * $(MYREF ThreadScheduler) + * $(MYREF yield) + * )) + * $(TR $(TD Misc) $(TD + * $(MYREF initOnce) + * )) + * )) + * * 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 @@ -22,7 +67,7 @@ * 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) + * Source: $(PHOBOSSRC std/concurrency.d) */ /* Copyright Sean Kelly 2009 - 2014. * Distributed under the Boost Software License, Version 1.0. @@ -72,13 +117,38 @@ import std.traits; private { - template hasLocalAliasing(T...) + bool hasLocalAliasing(Types...)() { - static if (!T.length) - enum hasLocalAliasing = false; - else - enum hasLocalAliasing = (std.traits.hasUnsharedAliasing!(T[0]) && !is(T[0] == Tid)) || - std.concurrency.hasLocalAliasing!(T[1 .. $]); + import std.typecons : Rebindable; + + // Works around "statement is not reachable" + bool doesIt = false; + static foreach (T; Types) + { + static if (is(T == Tid)) + { /* Allowed */ } + else static if (is(T : Rebindable!R, R)) + doesIt |= hasLocalAliasing!R; + else static if (is(T == struct)) + doesIt |= hasLocalAliasing!(typeof(T.tupleof)); + else + doesIt |= std.traits.hasUnsharedAliasing!(T); + } + return doesIt; + } + + @safe unittest + { + static struct Container { Tid t; } + static assert(!hasLocalAliasing!(Tid, Container, int)); + } + + // https://issues.dlang.org/show_bug.cgi?id=20097 + @safe unittest + { + import std.datetime.systime : SysTime; + static struct Container { SysTime time; } + static assert(!hasLocalAliasing!(SysTime, Container)); } enum MsgType @@ -159,9 +229,12 @@ private void checkops(T...)(T ops) { + import std.format : format; + foreach (i, t1; T) { - static assert(isFunctionPointer!t1 || isDelegate!t1); + static assert(isFunctionPointer!t1 || isDelegate!t1, + format!"T %d is not a function pointer or delegates"(i)); alias a1 = Parameters!(t1); alias r1 = ReturnType!(t1); @@ -173,7 +246,6 @@ private foreach (t2; T[i + 1 .. $]) { - static assert(isFunctionPointer!t2 || isDelegate!t2); alias a2 = Parameters!(t2); static assert(!is(a1 == a2), @@ -199,7 +271,7 @@ static ~this() // Exceptions /** - * Thrown on calls to $(D receiveOnly) if a message other than the type + * Thrown on calls to `receiveOnly` if a message other than the type * the receiving thread expected is sent. */ class MessageMismatch : Exception @@ -212,7 +284,7 @@ class MessageMismatch : Exception } /** - * Thrown on calls to $(D receive) if the thread that spawned the receiving + * Thrown on calls to `receive` if the thread that spawned the receiving * thread has terminated and no more messages exist. */ class OwnerTerminated : Exception @@ -264,7 +336,7 @@ class PriorityMessageException : Exception /** * Thrown on mailbox crowding if the mailbox is configured with - * $(D OnCrowding.throwException). + * `OnCrowding.throwException`. */ class MailboxFull : Exception { @@ -279,7 +351,7 @@ class MailboxFull : Exception } /** - * Thrown when a Tid is missing, e.g. when $(D ownerTid) doesn't + * Thrown when a Tid is missing, e.g. when `ownerTid` doesn't * find an owner thread. */ class TidMissingException : Exception @@ -315,17 +387,17 @@ public: * 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) + void toString(W)(ref W w) const { - import std.format : formattedWrite; - formattedWrite(sink, "Tid(%x)", cast(void*) mbox); + import std.format.write : formattedWrite; + auto p = () @trusted { return cast(void*) mbox; }(); + formattedWrite(w, "Tid(%x)", p); } } -@system unittest +@safe unittest { - // text!Tid is @system import std.conv : text; Tid tid; assert(text(tid) == "Tid(0)"); @@ -335,6 +407,15 @@ public: assert(text(tid2) == text(tid3)); } +// https://issues.dlang.org/show_bug.cgi?id=21512 +@system unittest +{ + import std.format : format; + + const(Tid) b = spawn(() {}); + assert(format!"%s"(b)[0 .. 4] == "Tid("); +} + /** * Returns: The $(LREF Tid) of the caller's thread. */ @@ -355,7 +436,7 @@ public: /** * Return the Tid of the thread which spawned the caller's thread. * - * Throws: A $(D TidMissingException) exception if + * Throws: A `TidMissingException` exception if * there is no owner thread. */ @property Tid ownerTid() @@ -412,10 +493,10 @@ private template isSpawnable(F, T...) * 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()). + * `Tid`. The calling thread is designated as the owner of the new thread. + * When the owner thread terminates an `OwnerTerminated` message will be + * sent to the new thread, causing an `OwnerTerminated` exception to be + * thrown on `receive()`. * * Params: * fn = The function to execute. @@ -425,46 +506,69 @@ private template isSpawnable(F, T...) * 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 + * `args` must not have unshared aliasing. In other words, all arguments + * to `fn` must either be `shared` or `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)) + * Similarly, if `fn` is a delegate, it must not have unshared aliases, meaning + * `fn` must be either `shared` or `immutable`. */ +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); } +/// +@system unittest +{ + static void f(string msg) + { + assert(msg == "Hello World"); + } + + auto tid = spawn(&f, "Hello World"); +} + +/// Fails: char[] has mutable aliasing. +@system unittest +{ + string msg = "Hello, World!"; + + static void f1(string msg) {} + static assert(!__traits(compiles, spawn(&f1, msg.dup))); + static assert( __traits(compiles, spawn(&f1, msg.idup))); + + static void f2(char[] msg) {} + static assert(!__traits(compiles, spawn(&f2, msg.dup))); + static assert(!__traits(compiles, spawn(&f2, msg.idup))); +} + +/// New thread with anonymous function +@system unittest +{ + spawn({ + ownerTid.send("This is so great!"); + }); + assert(receiveOnly!string == "This is so great!"); +} + +@system unittest +{ + import core.thread : thread_joinAll; + + __gshared string receivedMessage; + static void f1(string msg) + { + receivedMessage = msg; + } + + auto tid1 = spawn(&f1, "Hello World"); + thread_joinAll; + assert(receivedMessage == "Hello World"); +} + /** * Starts fn(args) in a logical thread and will receive a LinkTerminated * message when the operation terminates. @@ -484,7 +588,8 @@ Tid spawn(F, T...)(F fn, T args) if (isSpawnable!(F, T)) * Returns: * A Tid representing the new thread. */ -Tid spawnLinked(F, T...)(F fn, T args) if (isSpawnable!(F, T)) +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); @@ -493,7 +598,8 @@ Tid spawnLinked(F, T...)(F fn, T args) if (isSpawnable!(F, T)) /* * */ -private Tid _spawn(F, T...)(bool linked, F fn, T args) if (isSpawnable!(F, T)) +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); @@ -568,9 +674,10 @@ private Tid _spawn(F, T...)(bool linked, F fn, T args) if (isSpawnable!(F, T)) * 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. + * $(REF spawn, std,concurrency), `T` must not have unshared aliasing. */ void send(T...)(Tid tid, T vals) +in (tid.mbox !is null) { static assert(!hasLocalAliasing!(T), "Aliases to mutable thread-local data not allowed."); _send(tid, vals); @@ -579,11 +686,12 @@ void send(T...)(Tid tid, T 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 + * Send a message to `tid` but place it at the front of `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) +in (tid.mbox !is null) { static assert(!hasLocalAliasing!(T), "Aliases to mutable thread-local data not allowed."); _send(MsgType.priority, tid, vals); @@ -593,6 +701,7 @@ void prioritySend(T...)(Tid tid, T vals) * ditto */ private void _send(T...)(Tid tid, T vals) +in (tid.mbox !is null) { _send(MsgType.standard, tid, vals); } @@ -602,6 +711,7 @@ private void _send(T...)(Tid tid, T vals) * both Tid.send() and .send(). */ private void _send(T...)(MsgType type, Tid tid, T vals) +in (tid.mbox !is null) { auto msg = Message(type, vals); tid.mbox.put(msg); @@ -615,32 +725,16 @@ private void _send(T...)(MsgType type, Tid tid, T vals) * 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 + * the last argument to `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 + * the `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."); } - * ); - * } + * Params: + * ops = Variadic list of function pointers and delegates. Entries + * in this list must not occlude later entries. * - * void main() - * { - * auto tid = spawn(&spawnedFunction); - * send(tid, 42); - * } - * --- + * Throws: $(LREF OwnerTerminated) when the sending thread was terminated. */ void receive(T...)( T ops ) in @@ -649,13 +743,45 @@ in "Cannot receive a message until a thread was spawned " ~ "or thisTid was passed to a running thread."); } -body +do { checkops( ops ); thisInfo.ident.mbox.get( ops ); } +/// +@system unittest +{ + import std.variant : Variant; + + auto process = () + { + receive( + (int i) { ownerTid.send(1); }, + (double f) { ownerTid.send(2); }, + (Variant v) { ownerTid.send(3); } + ); + }; + + { + auto tid = spawn(process); + send(tid, 42); + assert(receiveOnly!int == 1); + } + + { + auto tid = spawn(process); + send(tid, 3.14); + assert(receiveOnly!int == 2); + } + + { + auto tid = spawn(process); + send(tid, "something else"); + assert(receiveOnly!int == 3); + } +} @safe unittest { @@ -677,7 +803,7 @@ body } // Make sure receive() works with free functions as well. -version (unittest) +version (StdUnittest) { private void receiveFunction(int x) {} } @@ -705,31 +831,17 @@ private template receiveOnlyRet(T...) } /** - * Receives only messages with arguments of types $(D T). + * Receives only messages with arguments of the specified types. * - * Throws: $(D MessageMismatch) if a message of types other than $(D T) - * is received. + * Params: + * T = Variadic list of types to be received. * - * Returns: The received message. If $(D T.length) is greater than one, + * Returns: The received message. If `T` has more than one entry, * 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"); - * } - * --- + * Throws: $(LREF MessageMismatch) if a message of types other than `T` + * is received, + * $(LREF OwnerTerminated) when the sending thread was terminated. */ receiveOnlyRet!(T) receiveOnly(T...)() in @@ -737,16 +849,27 @@ 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 +do { import std.format : format; + import std.meta : allSatisfy; import std.typecons : Tuple; Tuple!(T) ret; thisInfo.ident.mbox.get((T val) { static if (T.length) - ret.field = val; + { + static if (allSatisfy!(isAssignable, T)) + { + ret.field = val; + } + else + { + import core.lifetime : emplace; + emplace(&ret, val); + } + } }, (LinkTerminated e) { throw e; }, (OwnerTerminated e) { throw e; }, @@ -765,6 +888,42 @@ body return ret; } +/// +@system unittest +{ + auto tid = spawn( + { + assert(receiveOnly!int == 42); + }); + send(tid, 42); +} + +/// +@system unittest +{ + auto tid = spawn( + { + assert(receiveOnly!string == "text"); + }); + send(tid, "text"); +} + +/// +@system unittest +{ + struct Record { string name; int age; } + + auto tid = spawn( + { + auto msg = receiveOnly!(double, Record); + assert(msg[0] == 0.5); + assert(msg[1].name == "Alice"); + assert(msg[1].age == 31); + }); + + send(tid, 0.5, Record("Alice", 31)); +} + @system unittest { static void t1(Tid mainTid) @@ -786,14 +945,37 @@ body assert(result == "Unexpected message type: expected 'string', got 'int'"); } +// https://issues.dlang.org/show_bug.cgi?id=21663 +@safe unittest +{ + alias test = receiveOnly!(string, bool, bool); +} + /** - * 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. + * Receives a message from another thread and gives up if no match + * arrives within a specified duration. + * + * Receive a message from another thread, or block until `duration` exceeds, + * 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, it will match any message that was not + * matched by an earlier delegate. If more than one argument is sent, + * the `Variant` will contain a $(REF Tuple, std,typecons) of all values + * sent. + * + * Params: + * duration = Duration, how long to wait. If `duration` is negative, + * won't wait at all. + * ops = Variadic list of function pointers and delegates. Entries + * in this list must not occlude later entries. * - * 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. + * Returns: `true` if it received a message and `false` if it timed out waiting + * for one. + * + * Throws: $(LREF OwnerTerminated) when the sending thread was terminated. */ bool receiveTimeout(T...)(Duration duration, T ops) in @@ -801,7 +983,7 @@ 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 +do { checkops(ops); @@ -873,6 +1055,7 @@ private * mailbox. */ void setMaxMailboxSize(Tid tid, size_t messages, OnCrowding doThis) @safe pure +in (tid.mbox !is null) { final switch (doThis) { @@ -899,6 +1082,7 @@ void setMaxMailboxSize(Tid tid, size_t messages, OnCrowding doThis) @safe pure * mailbox. */ void setMaxMailboxSize(Tid tid, size_t messages, bool function(Tid) onCrowdingDoThis) +in (tid.mbox !is null) { tid.mbox.setMaxMsgs(messages, onCrowdingDoThis); } @@ -916,18 +1100,17 @@ private @property Mutex registryLock() return impl; } -private void unregisterMe() +private void unregisterMe(ref ThreadInfo me) { - auto me = thisInfo.ident; - if (thisInfo.ident != Tid.init) + if (me.ident != Tid.init) { synchronized (registryLock) { - if (auto allNames = me in namesByTid) + if (auto allNames = me.ident in namesByTid) { foreach (name; *allNames) tidByName.remove(name); - namesByTid.remove(me); + namesByTid.remove(me.ident); } } } @@ -949,6 +1132,7 @@ private void unregisterMe() * defunct thread. */ bool register(string name, Tid tid) +in (tid.mbox !is null) { synchronized (registryLock) { @@ -1050,7 +1234,18 @@ struct ThreadInfo _send(MsgType.linkDead, tid, ident); if (owner != Tid.init) _send(MsgType.linkDead, owner, ident); - unregisterMe(); // clean up registry entries + unregisterMe(this); // clean up registry entries + } + + // https://issues.dlang.org/show_bug.cgi?id=20160 + @system unittest + { + register("main_thread", thisTid()); + + ThreadInfo t; + t.cleanup(); + + assert(locate("main_thread") == thisTid()); } } @@ -1270,13 +1465,45 @@ class FiberScheduler : Scheduler /** * Returns a Condition analog that yields when wait or notify is called. + * + * Bug: + * For the default implementation, `notifyAll`will behave like `notify`. + * + * Params: + * m = A `Mutex` to use for locking if the condition needs to be waited on + * or notified from multiple `Thread`s. + * If `null`, no `Mutex` will be used and it is assumed that the + * `Condition` is only waited on/notified from one `Thread`. */ Condition newCondition(Mutex m) nothrow { return new FiberCondition(m); } -private: +protected: + /** + * Creates a new Fiber which calls the given delegate. + * + * Params: + * op = The delegate the fiber should call + */ + void create(void delegate() op) nothrow + { + void wrap() + { + scope (exit) + { + thisInfo.cleanup(); + } + op(); + } + + m_fibers ~= new InfoFiber(&wrap); + } + + /** + * Fiber which embeds a ThreadInfo + */ static class InfoFiber : Fiber { ThreadInfo info; @@ -1285,8 +1512,14 @@ private: { super(op); } + + this(void delegate() op, size_t sz) nothrow + { + super(op, sz); + } } +private: class FiberCondition : Condition { this(Mutex m) nothrow @@ -1313,7 +1546,7 @@ private: !notified && !period.isNegative; period = limit - MonoTime.currTime) { - yield(); + this.outer.yield(); } return notified; } @@ -1333,9 +1566,11 @@ private: private: void switchContext() nothrow { - mutex_nothrow.unlock_nothrow(); - scope (exit) mutex_nothrow.lock_nothrow(); - yield(); + if (mutex_nothrow) mutex_nothrow.unlock_nothrow(); + scope (exit) + if (mutex_nothrow) + mutex_nothrow.lock_nothrow(); + this.outer.yield(); } private bool notified; @@ -1365,20 +1600,6 @@ private: } } - 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; @@ -1464,35 +1685,6 @@ 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 @@ -1533,6 +1725,27 @@ class Generator(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. + * sz = The stack size for this fiber. + * guardPageSize = size of the guard page to trap fiber's stack + * overflows. Refer to $(REF Fiber, core,thread)'s + * documentation for more details. + * + * In: + * fn must not be null. + */ + this(void function() fn, size_t sz, size_t guardPageSize) + { + super(fn, sz, guardPageSize); + 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. @@ -1568,6 +1781,27 @@ class Generator(T) : } /** + * 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. + * guardPageSize = size of the guard page to trap fiber's stack + * overflows. Refer to $(REF Fiber, core,thread)'s + * documentation for more details. + * + * In: + * dg must not be null. + */ + this(void delegate() dg, size_t sz, size_t guardPageSize) + { + super(dg, sz, guardPageSize); + call(); + } + + /** * Returns true if the generator is empty. */ final bool empty() @property @@ -1634,6 +1868,28 @@ private: T* m_value; } +/// +@system unittest +{ + auto tid = spawn({ + int i; + while (i < 9) + i = receiveOnly!int; + + ownerTid.send(i * 2); + }); + + auto r = new Generator!int({ + foreach (i; 1 .. 10) + yield(i); + }); + + foreach (e; r) + tid.send(e); + + assert(receiveOnly!int == 18); +} + /** * Yields a value of type T to the caller of the currently executing * generator. @@ -1865,7 +2121,7 @@ private { import std.meta : AliasSeq; - static assert(T.length); + static assert(T.length, "T must not be empty"); static if (isImplicitlyConvertible!(T[0], Duration)) { @@ -1906,7 +2162,8 @@ private bool onLinkDeadMsg(ref Message msg) { - assert(msg.convertsTo!(Tid)); + assert(msg.convertsTo!(Tid), + "Message could be converted to Tid"); auto tid = msg.get!(Tid); if (bool* pDepends = tid in thisInfo.links) @@ -2083,7 +2340,8 @@ private { static void onLinkDeadMsg(ref Message msg) { - assert(msg.convertsTo!(Tid)); + assert(msg.convertsTo!(Tid), + "Message could be converted to Tid"); auto tid = msg.get!(Tid); thisInfo.links.remove(tid); @@ -2228,7 +2486,7 @@ private { import std.exception : enforce; - assert(m_count); + assert(m_count, "Can not remove from empty Range"); Node* n = r.m_prev; enforce(n && n.next, "attempting to remove invalid list node"); @@ -2295,7 +2553,7 @@ private } if (n) { - import std.conv : emplace; + import core.lifetime : emplace; emplace!Node(n, v); } else @@ -2337,12 +2595,11 @@ private } } -version (unittest) +@system unittest { - import std.stdio; import std.typecons : tuple, Tuple; - void testfn(Tid tid) + static void testfn(Tid tid) { receive((float val) { assert(0); }, (int val, int val2) { assert(val == 42 && val2 == 86); @@ -2357,7 +2614,7 @@ version (unittest) prioritySend(tid, "done"); } - void runTest(Tid tid) + static void runTest(Tid tid) { send(tid, 42, 86); send(tid, tuple(42, 86)); @@ -2366,7 +2623,7 @@ version (unittest) receive((string val) { assert(val == "done"); }); } - void simpleTest() + static void simpleTest() { auto tid = spawn(&testfn, thisTid); runTest(tid); @@ -2377,28 +2634,22 @@ version (unittest) runTest(tid); } - @system unittest - { - simpleTest(); - } + simpleTest(); - @system unittest - { - scheduler = new ThreadScheduler; - simpleTest(); - scheduler = null; - } + scheduler = new ThreadScheduler; + simpleTest(); + scheduler = null; } -private @property Mutex initOnceLock() +private @property shared(Mutex) initOnceLock() { - __gshared Mutex lock; - if (auto mtx = cast() atomicLoad!(MemoryOrder.acq)(*cast(shared)&lock)) + static shared Mutex lock; + if (auto mtx = atomicLoad!(MemoryOrder.acq)(lock)) return mtx; - auto mtx = new Mutex; - if (cas(cast(shared)&lock, cast(shared) null, cast(shared) mtx)) + auto mtx = new shared Mutex; + if (cas(&lock, cast(shared) null, mtx)) return mtx; - return cast() atomicLoad!(MemoryOrder.acq)(*cast(shared)&lock); + return atomicLoad!(MemoryOrder.acq)(lock); } /** @@ -2429,7 +2680,7 @@ auto ref initOnce(alias var)(lazy typeof(var) init) { static MySingleton instance() { - static __gshared MySingleton inst; + __gshared MySingleton inst; return initOnce!inst(new MySingleton); } } @@ -2443,14 +2694,14 @@ auto ref initOnce(alias var)(lazy typeof(var) init) { static MySingleton instance() { - static __gshared MySingleton inst; + __gshared MySingleton inst; return initOnce!inst(new MySingleton); } private: this() { val = ++cnt; } size_t val; - static __gshared size_t cnt; + __gshared size_t cnt; } foreach (_; 0 .. 10) @@ -2476,7 +2727,7 @@ auto ref initOnce(alias var)(lazy typeof(var) init) * Returns: * A reference to the initialized variable */ -auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex) +auto ref initOnce(alias var)(lazy typeof(var) init, shared Mutex mutex) { // check that var is global, can't take address of a TLS variable static assert(is(typeof({ __gshared p = &var; })), @@ -2488,7 +2739,7 @@ auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex) { synchronized (mutex) { - if (!atomicLoad!(MemoryOrder.acq)(flag)) + if (!atomicLoad!(MemoryOrder.raw)(flag)) { var = init; atomicStore!(MemoryOrder.rel)(flag, true); @@ -2498,14 +2749,20 @@ auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex) return var; } +/// ditto +auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex) +{ + return initOnce!var(init, cast(shared) mutex); +} + /// 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; + static shared Mutex m; + m = new shared Mutex; spawn({ // use a different mutex for varB to avoid a dead-lock @@ -2529,3 +2786,41 @@ auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex) static assert(!__traits(compiles, initOnce!c(true))); // TLS static assert(!__traits(compiles, initOnce!d(true))); // local variable } + +// test ability to send shared arrays +@system unittest +{ + static shared int[] x = new shared(int)[1]; + auto tid = spawn({ + auto arr = receiveOnly!(shared(int)[]); + arr[0] = 5; + ownerTid.send(true); + }); + tid.send(x); + receiveOnly!(bool); + assert(x[0] == 5); +} + +// https://issues.dlang.org/show_bug.cgi?id=13930 +@system unittest +{ + immutable aa = ["0":0]; + thisTid.send(aa); + receiveOnly!(immutable int[string]); // compile error +} + +// https://issues.dlang.org/show_bug.cgi?id=19345 +@system unittest +{ + static struct Aggregate { const int a; const int[5] b; } + static void t1(Tid mainTid) + { + const sendMe = Aggregate(42, [1, 2, 3, 4, 5]); + mainTid.send(sendMe); + } + + spawn(&t1, thisTid); + auto result1 = receiveOnly!(const Aggregate)(); + immutable expected = Aggregate(42, [1, 2, 3, 4, 5]); + assert(result1 == expected); +} diff --git a/libphobos/src/std/container/array.d b/libphobos/src/std/container/array.d index eee8901..58de5b7 100644 --- a/libphobos/src/std/container/array.d +++ b/libphobos/src/std/container/array.d @@ -4,7 +4,7 @@ * * This module is a submodule of $(MREF std, container). * - * Source: $(PHOBOSSRC std/container/_array.d) + * Source: $(PHOBOSSRC std/container/array.d) * * Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. * @@ -25,7 +25,7 @@ import std.traits; public import std.container.util; /// -@system unittest +pure @system unittest { auto arr = Array!int(0, 2, 3); assert(arr[0] == 0); @@ -52,7 +52,7 @@ public import std.container.util; } /// -@system unittest +pure @system unittest { import std.algorithm.comparison : equal; auto arr = Array!int(1, 2, 3); @@ -71,16 +71,15 @@ public import std.container.util; } /// `Array!bool` packs together values efficiently by allocating one bit per element -@system unittest +pure @system unittest { - Array!bool arr; - arr.insert([true, true, false, true, false]); + auto arr = Array!bool([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 + /* Workaround for https://issues.dlang.org/show_bug.cgi?id=13629 See also: http://forum.dlang.org/post/vbmwhzvawhnkoxrhbnyb@forum.dlang.org */ private A[1] _outer_; @@ -144,26 +143,34 @@ private struct RangeT(A) E moveFront() { - assert(!empty && _a < _outer.length); + assert(!empty, "Attempting to moveFront an empty Array"); + assert(_a < _outer.length, + "Attempting to moveFront using an out of bounds low index on an Array"); return move(_outer._data._payload[_a]); } E moveBack() { - assert(!empty && _b <= _outer.length); + assert(!empty, "Attempting to moveBack an empty Array"); + assert(_b - 1 < _outer.length, + "Attempting to moveBack using an out of bounds high index on an Array"); return move(_outer._data._payload[_b - 1]); } E moveAt(size_t i) { - assert(_a + i < _b && _a + i < _outer.length); + assert(_a + i < _b, + "Attempting to moveAt using an out of bounds index on an Array"); + assert(_a + i < _outer.length, + "Cannot move past the end of the array"); return move(_outer._data._payload[_a + i]); } } ref inout(E) opIndex(size_t i) inout { - assert(_a + i < _b); + assert(_a + i < _b, + "Attempting to fetch using an out of bounds index on an Array"); return _outer[_a + i]; } @@ -174,7 +181,8 @@ private struct RangeT(A) RangeT opSlice(size_t i, size_t j) { - assert(i <= j && _a + j <= _b); + assert(i <= j && _a + j <= _b, + "Attempting to slice using an out of bounds indices on an Array"); return typeof(return)(_outer, _a + i, _a + j); } @@ -185,7 +193,8 @@ private struct RangeT(A) RangeT!(const(A)) opSlice(size_t i, size_t j) const { - assert(i <= j && _a + j <= _b); + assert(i <= j && _a + j <= _b, + "Attempting to slice using an out of bounds indices on an Array"); return typeof(return)(_outer, _a + i, _a + j); } @@ -193,39 +202,45 @@ private struct RangeT(A) { void opSliceAssign(E value) { - assert(_b <= _outer.length); + assert(_b <= _outer.length, + "Attempting to assign using an out of bounds indices on an Array"); _outer[_a .. _b] = value; } void opSliceAssign(E value, size_t i, size_t j) { - assert(_a + j <= _b); + assert(_a + j <= _b, + "Attempting to slice assign using an out of bounds indices on an Array"); _outer[_a + i .. _a + j] = value; } void opSliceUnary(string op)() if (op == "++" || op == "--") { - assert(_b <= _outer.length); + assert(_b <= _outer.length, + "Attempting to slice using an out of bounds indices on an Array"); mixin(op~"_outer[_a .. _b];"); } void opSliceUnary(string op)(size_t i, size_t j) if (op == "++" || op == "--") { - assert(_a + j <= _b); + assert(_a + j <= _b, + "Attempting to slice using an out of bounds indices on an Array"); mixin(op~"_outer[_a + i .. _a + j];"); } void opSliceOpAssign(string op)(E value) { - assert(_b <= _outer.length); + assert(_b <= _outer.length, + "Attempting to slice using an out of bounds indices on an Array"); mixin("_outer[_a .. _b] "~op~"= value;"); } void opSliceOpAssign(string op)(E value, size_t i, size_t j) { - assert(_a + j <= _b); + assert(_a + j <= _b, + "Attempting to slice using an out of bounds indices on an Array"); mixin("_outer[_a + i .. _a + j] "~op~"= value;"); } } @@ -249,9 +264,10 @@ private struct RangeT(A) * instead of `array.map!`). The container itself is not a range. */ struct Array(T) -if (!is(Unqual!T == bool)) +if (!is(immutable T == immutable bool)) { - import core.stdc.stdlib : malloc, realloc, free; + import core.memory : free = pureFree; + import std.internal.memory : enforceMalloc, enforceRealloc; import core.stdc.string : memcpy, memmove, memset; import core.memory : GC; @@ -306,23 +322,9 @@ if (!is(Unqual!T == bool)) 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]; - } + reserve(newLength); initializeAll(_payload.ptr[startEmplace .. newLength]); + _payload = _payload.ptr[0 .. newLength]; } @property size_t capacity() const @@ -333,11 +335,18 @@ if (!is(Unqual!T == bool)) 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 (T.sizeof == 1) + { + const sz = elements; + } + else + { + import core.checkedint : mulu; + bool overflow; + const sz = mulu(elements, T.sizeof, overflow); + if (overflow) + assert(false, "Overflow"); + } static if (hasIndirections!T) { /* Because of the transactional nature of this @@ -347,8 +356,7 @@ if (!is(Unqual!T == bool)) */ immutable oldLength = length; - auto newPayloadPtr = cast(T*) malloc(sz); - newPayloadPtr || assert(false, "std.container.Array.reserve failed to allocate memory"); + auto newPayloadPtr = cast(T*) enforceMalloc(sz); auto newPayload = newPayloadPtr[0 .. oldLength]; // copy old data over to new array @@ -365,8 +373,7 @@ if (!is(Unqual!T == bool)) 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 newPayloadPtr = cast(T*) enforceRealloc(_payload.ptr, sz); auto newPayload = newPayloadPtr[0 .. length]; _payload = newPayload; } @@ -377,12 +384,21 @@ if (!is(Unqual!T == bool)) size_t insertBack(Elem)(Elem elem) if (isImplicitlyConvertible!(Elem, T)) { - import std.conv : emplace; + import core.lifetime : emplace; + assert(_capacity >= length); if (_capacity == length) { - reserve(1 + capacity * 3 / 2); + import core.checkedint : addu; + + bool overflow; + immutable size_t newCapacity = addu(capacity, capacity / 2 + 1, overflow); + if (overflow) + assert(false, "Overflow"); + + reserve(newCapacity); } - assert(capacity > length && _payload.ptr); + assert(capacity > length && _payload.ptr, + "Failed to reserve memory"); emplace(_payload.ptr + _payload.length, elem); _payload = _payload.ptr[0 .. _payload.length + 1]; return 1; @@ -392,21 +408,27 @@ if (!is(Unqual!T == bool)) size_t insertBack(Range)(Range r) if (isInputRange!Range && isImplicitlyConvertible!(ElementType!Range, T)) { + immutable size_t oldLength = length; + static if (hasLength!Range) { - immutable oldLength = length; - reserve(oldLength + r.length); + immutable size_t rLength = r.length; + reserve(oldLength + rLength); } + size_t result; foreach (item; r) { insertBack(item); ++result; } + static if (hasLength!Range) - { - assert(length == oldLength + r.length); - } + assert(result == rLength, "insertBack: range might have changed length"); + + assert(length == oldLength + result, + "Failed to insertBack range"); + return result; } } @@ -419,27 +441,36 @@ if (!is(Unqual!T == bool)) 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) + import core.lifetime : emplace; + + static if (T.sizeof == 1) { - if (p) - GC.addRange(p, T.sizeof * values.length); + const nbytes = values.length; } - + else + { + import core.checkedint : mulu; + bool overflow; + const nbytes = mulu(values.length, T.sizeof, overflow); + if (overflow) + assert(false, "Overflow"); + } + auto p = cast(T*) enforceMalloc(nbytes); + // Before it is added to the gc, initialize the newly allocated memory foreach (i, e; values) { emplace(p + i, e); } + static if (hasIndirections!T) + { + if (p) + GC.addRange(p, T.sizeof * values.length); + } _data = Data(p[0 .. values.length]); } /** - * Constructor taking an input range + * Constructor taking an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) */ this(Range)(Range r) if (isInputRange!Range && isImplicitlyConvertible!(ElementType!Range, T) && !is(Range == T[])) @@ -527,6 +558,17 @@ if (!is(Unqual!T == bool)) } /** + * Returns: the internal representation of the array. + * + * Complexity: $(BIGOH 1). + */ + + T[] data() @system + { + return _data._payload; + } + + /** * Ensures sufficient capacity to accommodate `e` _elements. * If `e < capacity`, this method does nothing. * @@ -542,14 +584,23 @@ if (!is(Unqual!T == bool)) 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 (T.sizeof == 1) + { + const sz = elements; + } + else + { + import core.checkedint : mulu; + bool overflow; + const sz = mulu(elements, T.sizeof, overflow); + if (overflow) + assert(false, "Overflow"); + } + auto p = enforceMalloc(sz); static if (hasIndirections!T) { + // Zero out unused capacity to prevent gc from seeing false pointers + memset(p, 0, sz); GC.addRange(p, sz); } _data = Data(cast(T[]) p[0 .. 0]); @@ -592,19 +643,19 @@ if (!is(Unqual!T == bool)) */ Range opSlice(size_t i, size_t j) { - assert(i <= j && j <= length); + assert(i <= j && j <= length, "Invalid slice bounds"); return typeof(return)(this, i, j); } ConstRange opSlice(size_t i, size_t j) const { - assert(i <= j && j <= length); + assert(i <= j && j <= length, "Invalid slice bounds"); return typeof(return)(this, i, j); } ImmutableRange opSlice(size_t i, size_t j) immutable { - assert(i <= j && j <= length); + assert(i <= j && j <= length, "Invalid slice bounds"); return typeof(return)(this, i, j); } @@ -617,7 +668,8 @@ if (!is(Unqual!T == bool)) */ @property ref inout(T) front() inout { - assert(_data.refCountedStore.isInitialized); + assert(_data.refCountedStore.isInitialized, + "Cannot get front of empty range"); return _data._payload[0]; } @@ -630,7 +682,8 @@ if (!is(Unqual!T == bool)) */ @property ref inout(T) back() inout { - assert(_data.refCountedStore.isInitialized); + assert(_data.refCountedStore.isInitialized, + "Cannot get back of empty range"); return _data._payload[$ - 1]; } @@ -643,7 +696,8 @@ if (!is(Unqual!T == bool)) */ ref inout(T) opIndex(size_t i) inout { - assert(_data.refCountedStore.isInitialized); + assert(_data.refCountedStore.isInitialized, + "Cannot index empty range"); return _data._payload[i]; } @@ -879,10 +933,11 @@ if (!is(Unqual!T == bool)) size_t insertBefore(Stuff)(Range r, Stuff stuff) if (isImplicitlyConvertible!(Stuff, T)) { - import std.conv : emplace; + import core.lifetime : emplace; enforce(r._outer._data is _data && r._a <= length); reserve(length + 1); - assert(_data.refCountedStore.isInitialized); + assert(_data.refCountedStore.isInitialized, + "Failed to allocate capacity to insertBefore"); // Move elements over by one slot memmove(_data._payload.ptr + r._a + 1, _data._payload.ptr + r._a, @@ -896,7 +951,7 @@ if (!is(Unqual!T == bool)) size_t insertBefore(Stuff)(Range r, Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) { - import std.conv : emplace; + import core.lifetime : emplace; enforce(r._outer._data is _data && r._a <= length); static if (isForwardRange!Stuff) { @@ -904,7 +959,8 @@ if (!is(Unqual!T == bool)) auto extra = walkLength(stuff); if (!extra) return 0; reserve(length + extra); - assert(_data.refCountedStore.isInitialized); + assert(_data.refCountedStore.isInitialized, + "Failed to allocate capacity to insertBefore"); // Move elements over by extra slots memmove(_data._payload.ptr + r._a + extra, _data._payload.ptr + r._a, @@ -937,6 +993,8 @@ if (!is(Unqual!T == bool)) /// ditto size_t insertAfter(Stuff)(Range r, Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T) || + isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) { import std.algorithm.mutation : bringToFront; enforce(r._outer._data is _data); @@ -1115,13 +1173,21 @@ if (!is(Unqual!T == bool)) @safe unittest { - // REG https://issues.dlang.org/show_bug.cgi?id=13621 + // https://issues.dlang.org/show_bug.cgi?id=13621 import std.container : Array, BinaryHeap; alias Heap = BinaryHeap!(Array!int); } @system unittest { + // https://issues.dlang.org/show_bug.cgi?id=18800 + static struct S { void* p; } + Array!S a; + a.length = 10; +} + +@system unittest +{ Array!int a; a.reserve(1000); assert(a.length == 0); @@ -1203,7 +1269,8 @@ if (!is(Unqual!T == bool)) assert(a.length == 7); assert(equal(a[1 .. 4], [1, 2, 3])); } -// Test issue 5920 + +// https://issues.dlang.org/show_bug.cgi?id=5920 @system unittest { struct structBug5920 @@ -1242,7 +1309,9 @@ if (!is(Unqual!T == bool)) } 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) + +// Test for https://issues.dlang.org/show_bug.cgi?id=5792 +// (mainly just to check if this piece of code is compilable) @system unittest { auto a = Array!(int[])([[1,2],[3,4]]); @@ -1401,7 +1470,8 @@ if (!is(Unqual!T == bool)) arr ~= s; } -@safe unittest //11459 +// https://issues.dlang.org/show_bug.cgi?id=11459 +@safe unittest { static struct S { @@ -1412,18 +1482,21 @@ if (!is(Unqual!T == bool)) alias B = Array!(shared bool); } -@system unittest //11884 +// https://issues.dlang.org/show_bug.cgi?id=11884 +@system unittest { import std.algorithm.iteration : filter; auto a = Array!int([1, 2, 2].filter!"true"()); } -@safe unittest //8282 +// https://issues.dlang.org/show_bug.cgi?id=8282 +@safe unittest { auto arr = new Array!int; } -@system unittest //6998 +// https://issues.dlang.org/show_bug.cgi?id=6998 +@system unittest { static int i = 0; class C @@ -1448,7 +1521,9 @@ if (!is(Unqual!T == bool)) //Just to make sure the GC doesn't collect before the above test. assert(c.dummy == 1); } -@system unittest //6998-2 + +//https://issues.dlang.org/show_bug.cgi?id=6998 (2) +@system unittest { static class C {int i;} auto c = new C; @@ -1508,6 +1583,39 @@ if (!is(Unqual!T == bool)) ai.insertBack(arr); } +/* + * typeof may give wrong result in case of classes defining `opCall` operator + * https://issues.dlang.org/show_bug.cgi?id=20589 + * + * destructor std.container.array.Array!(MyClass).Array.~this is @system + * so the unittest is @system too + */ +@system unittest +{ + class MyClass + { + T opCall(T)(T p) + { + return p; + } + } + + Array!MyClass arr; +} + +@system unittest +{ + import std.algorithm.comparison : equal; + auto a = Array!int([1,2,3,4,5]); + assert(a.length == 5); + + assert(a.insertAfter(a[0 .. 5], [7, 8]) == 2); + assert(equal(a[], [1,2,3,4,5,7,8])); + + assert(a.insertAfter(a[0 .. 5], 6) == 1); + assert(equal(a[], [1,2,3,4,5,6,7,8])); +} + //////////////////////////////////////////////////////////////////////////////// // Array!bool @@ -1518,7 +1626,7 @@ if (!is(Unqual!T == bool)) * allocating one bit per element. */ struct Array(T) -if (is(Unqual!T == bool)) +if (is(immutable T == immutable bool)) { import std.exception : enforce; import std.typecons : RefCounted, RefCountedAutoInitialize; @@ -1533,7 +1641,8 @@ if (is(Unqual!T == bool)) private @property ref size_t[] data() { - assert(_store.refCountedStore.isInitialized); + assert(_store.refCountedStore.isInitialized, + "Cannot get data of uninitialized Array"); return _store._backend._payload; } @@ -1628,15 +1737,16 @@ if (is(Unqual!T == bool)) /// Ditto @property size_t length() const { - assert(_a <= _b); + assert(_a <= _b, "Invalid bounds"); return _b - _a; } alias opDollar = length; /// ditto Range opSlice(size_t low, size_t high) { + // Note: indexes start at 0, which is equivalent to _a assert( - _a <= low && low <= high && high <= _b, + low <= high && high <= (_b - _a), "Using out of bounds indexes on an Array" ); return Range(_outer, _a + low, _a + high); @@ -1644,6 +1754,46 @@ if (is(Unqual!T == bool)) } /** + * Constructor taking a number of items. + */ + this(U)(U[] values...) + if (isImplicitlyConvertible!(U, T)) + { + reserve(values.length); + foreach (i, v; values) + { + auto rem = i % bitsPerWord; + if (rem) + { + // Fits within the current array + if (v) + { + data[$ - 1] |= (cast(size_t) 1 << rem); + } + else + { + data[$ - 1] &= ~(cast(size_t) 1 << rem); + } + } + else + { + // Need to add more data + _store._backend.insertBack(v); + } + } + _store._length = values.length; + } + + /** + * Constructor taking an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + */ + this(Range)(Range r) + if (isInputRange!Range && isImplicitlyConvertible!(ElementType!Range, T) && !is(Range == T[])) + { + insertBack(r); + } + + /** * Property returning `true` if and only if the array has * no elements. * @@ -1675,10 +1825,7 @@ if (is(Unqual!T == bool)) { return _store.refCountedStore.isInitialized ? _store._length : 0; } - size_t opDollar() const - { - return length; - } + alias opDollar = length; /** * Returns: The maximum number of elements the array can store without @@ -1964,14 +2111,17 @@ if (is(Unqual!T == bool)) size_t insertBack(Stuff)(Stuff stuff) if (isInputRange!Stuff && is(ElementType!Stuff : bool)) { - static if (!hasLength!Stuff) size_t result; + size_t result; + static if (hasLength!Stuff) + result = stuff.length; + for (; !stuff.empty; stuff.popFront()) { insertBack(stuff.front); static if (!hasLength!Stuff) ++result; } - static if (!hasLength!Stuff) return result; - else return stuff.length; + + return result; } /// ditto @@ -2076,6 +2226,8 @@ if (is(Unqual!T == bool)) /// ditto size_t insertAfter(Stuff)(Range r, Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T) || + isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) { import std.algorithm.mutation : bringToFront; // TODO: make this faster, it moves one bit at a time @@ -2132,14 +2284,32 @@ if (is(Unqual!T == bool)) @system unittest { + import std.algorithm.comparison : equal; + + auto a = Array!bool([true, true, false, false, true, false]); + assert(equal(a[], [true, true, false, false, true, false])); +} + +// using Ranges +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + bool[] arr = [true, true, false, false, true, false]; + + auto a = Array!bool(retro(arr)); + assert(equal(a[], retro(arr))); +} + +@system unittest +{ Array!bool a; assert(a.empty); } @system unittest { - Array!bool arr; - arr.insert([false, false, false, false]); + auto arr = Array!bool([false, false, false, false]); assert(arr.front == false); assert(arr.back == false); assert(arr[1] == false); @@ -2149,6 +2319,7 @@ if (is(Unqual!T == bool)) slice.front = true; slice.back = true; slice[1] = true; + slice = slice[0 .. $]; // https://issues.dlang.org/show_bug.cgi?id=19171 assert(slice.front == true); assert(slice.back == true); assert(slice[1] == true); @@ -2157,7 +2328,8 @@ if (is(Unqual!T == bool)) assert(slice.moveAt(1) == true); } -// issue 16331 - uncomparable values are valid values for an array +// uncomparable values are valid values for an array +// https://issues.dlang.org/show_bug.cgi?id=16331 @system unittest { double[] values = [double.nan, double.nan]; @@ -2243,22 +2415,19 @@ if (is(Unqual!T == bool)) @system unittest { - Array!bool a; - a.insertBack([true, false, true, true]); + auto a = Array!bool([true, false, true, true]); assert(a[0 .. 2].length == 2); } @system unittest { - Array!bool a; - a.insertBack([true, false, true, true]); + auto a = Array!bool([true, false, true, true]); assert(a[].length == 4); } @system unittest { - Array!bool a; - a.insertBack([true, false, true, true]); + auto a = Array!bool([true, false, true, true]); assert(a.front); a.front = false; assert(!a.front); @@ -2266,15 +2435,13 @@ if (is(Unqual!T == bool)) @system unittest { - Array!bool a; - a.insertBack([true, false, true, true]); + auto a = Array!bool([true, false, true, true]); assert(a[].length == 4); } @system unittest { - Array!bool a; - a.insertBack([true, false, true, true]); + auto a = Array!bool([true, false, true, true]); assert(a.back); a.back = false; assert(!a.back); @@ -2282,8 +2449,7 @@ if (is(Unqual!T == bool)) @system unittest { - Array!bool a; - a.insertBack([true, false, true, true]); + auto a = Array!bool([true, false, true, true]); assert(a[0] && !a[1]); a[0] &= a[1]; assert(!a[0]); @@ -2292,10 +2458,8 @@ if (is(Unqual!T == bool)) @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]); + auto a = Array!bool([true, false, true, true]); + auto b = Array!bool([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])); @@ -2306,10 +2470,8 @@ if (is(Unqual!T == bool)) @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]); + auto a = Array!bool([true, false, true, true]); + auto b = Array!bool([false, true, false, true, true]); a ~= b; assert(equal( a[], @@ -2318,8 +2480,7 @@ if (is(Unqual!T == bool)) @system unittest { - Array!bool a; - a.insertBack([true, false, true, true]); + auto a = Array!bool([true, false, true, true]); a.clear(); assert(a.capacity == 0); } @@ -2391,6 +2552,30 @@ if (is(Unqual!T == bool)) assert(a[].equal([false, true, true])); } +// https://issues.dlang.org/show_bug.cgi?id=21555 +@system unittest +{ + import std.algorithm.comparison : equal; + Array!bool arr; + size_t len = arr.insertBack([false, true]); + assert(len == 2); +} + +// https://issues.dlang.org/show_bug.cgi?id=21556 +@system unittest +{ + import std.algorithm.comparison : equal; + Array!bool a; + a.insertBack([true, true, false, false, true]); + assert(a.length == 5); + + assert(a.insertAfter(a[0 .. 5], [false, false]) == 2); + assert(equal(a[], [true, true, false, false, true, false, false])); + + assert(a.insertAfter(a[0 .. 5], true) == 1); + assert(equal(a[], [true, true, false, false, true, true, false, false])); +} + @system unittest { import std.conv : to; @@ -2417,3 +2602,35 @@ if (is(Unqual!T == bool)) assert(arr[0] == [1, 2, 3]); assert(arr[1] == [4, 5, 6]); } + +// Change of length reallocates without calling GC. +// https://issues.dlang.org/show_bug.cgi?id=13642 +@system unittest +{ + import core.memory; + class ABC { void func() { int x = 5; } } + + Array!ABC arr; + // Length only allocates if capacity is too low. + arr.reserve(4); + assert(arr.capacity == 4); + + void func() @nogc + { + arr.length = 5; + } + func(); + + foreach (ref b; arr) b = new ABC; + GC.collect(); + arr[1].func(); +} + +@system unittest +{ + Array!int arr = [1, 2, 4, 5]; + int[] data = arr.data(); + + data[0] = 0; + assert(arr[0] == 0); +} diff --git a/libphobos/src/std/container/binaryheap.d b/libphobos/src/std/container/binaryheap.d index 4adf604..e763357 100644 --- a/libphobos/src/std/container/binaryheap.d +++ b/libphobos/src/std/container/binaryheap.d @@ -1,10 +1,10 @@ /** -This module provides a $(D BinaryHeap) (aka priority queue) +This module provides a `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) +Source: $(PHOBOSSRC std/container/binaryheap.d) Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. @@ -37,29 +37,29 @@ public import std.container.util; /** 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 +T[])) or a random-access container type (usually `Array!T`). The +documentation of `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 +accessing the largest element (by using the `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 +If `less` is the less-than operator, which is the default option, +then `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 +Simply extracting elements from a `BinaryHeap` container is +tantamount to lazily fetching elements of `Store` in descending +order. Extracting elements from the `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 +If `Store` is a range, the `BinaryHeap` cannot grow beyond the +size of that range. If `Store` is a container that supports $(D +insertBack), the `BinaryHeap` may grow by adding elements to the container. */ struct BinaryHeap(Store, alias less = "a < b") @@ -95,12 +95,14 @@ if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) // Convenience accessors private @property ref Store _store() { - assert(_payload.refCountedStore.isInitialized); + assert(_payload.refCountedStore.isInitialized, + "BinaryHeap not initialized"); return _payload._store; } private @property ref size_t _length() { - assert(_payload.refCountedStore.isInitialized); + assert(_payload.refCountedStore.isInitialized, + "BinaryHeap not initialized"); return _payload._length; } @@ -135,12 +137,12 @@ if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) public: /** - Converts the store $(D s) into a heap. If $(D initialSize) is - specified, only the first $(D initialSize) elements in $(D s) + Converts the store `s` into a heap. If `initialSize` is + specified, only the first `initialSize` elements in `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). + to `r.length` (if `Store` is a range) or indefinitely (if + `Store` is a container with `insertBack`). Performs + $(BIGOH min(r.length, initialSize)) evaluations of `less`. */ this(Store s, size_t initialSize = size_t.max) { @@ -148,7 +150,7 @@ public: } /** -Takes ownership of a store. After this, manipulating $(D s) may make +Takes ownership of a store. After this, manipulating `s` may make the heap work incorrectly. */ void acquire(Store s, size_t initialSize = size_t.max) @@ -174,8 +176,8 @@ heap. } /** -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), +Clears the heap. Returns the portion of the store from `0` up to +`length`, which satisfies the $(LINK2 https://en.wikipedia.org/wiki/Heap_(data_structure), heap property). */ auto release() @@ -191,7 +193,7 @@ heap property). } /** -Returns $(D true) if the heap is _empty, $(D false) otherwise. +Returns `true` if the heap is _empty, `false` otherwise. */ @property bool empty() { @@ -199,7 +201,7 @@ Returns $(D true) if the heap is _empty, $(D false) otherwise. } /** -Returns a duplicate of the heap. The $(D dup) method is available only if the +Returns a duplicate of the heap. The `dup` method is available only if the underlying store supports it. */ static if (is(typeof((Store s) { return s.dup; }(Store.init)) == Store)) @@ -241,7 +243,7 @@ underlying store (if the store is a container). /** Returns a copy of the _front of the heap, which is the largest element -according to $(D less). +according to `less`. */ @property ElementType!Store front() { @@ -258,7 +260,7 @@ Clears the heap by detaching it from the underlying store. } /** -Inserts $(D value) into the store. If the underlying store is a range +Inserts `value` into the store. If the underlying store is a range and $(D length == capacity), throws an exception. */ size_t insert(ElementType!Store value) @@ -331,7 +333,7 @@ Removes the largest element from the heap. /** 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 +reasons you may want to use `removeFront` with heaps of objects that are expensive to copy. */ ElementType!Store removeAny() @@ -341,7 +343,7 @@ that are expensive to copy. } /** -Replaces the largest element in the store with $(D value). +Replaces the largest element in the store with `value`. */ void replaceFront(ElementType!Store value) { @@ -353,11 +355,11 @@ Replaces the largest element in the store with $(D value). } /** -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 +If the heap has room to grow, inserts `value` into the store and +returns `true`. Otherwise, if $(D less(value, front)), calls $(D +replaceFront(value)) and returns again `true`. Otherwise, leaves +the heap unaffected and returns `false`. This method is useful in +scenarios where the smallest `k` elements of a set of candidates must be collected. */ bool conditionalInsert(ElementType!Store value) @@ -380,13 +382,14 @@ must be collected. /** 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). +method exchanges store.front and value and returns `true`. Otherwise, it +leaves the heap unaffected and returns `false`. */ bool conditionalSwap(ref ElementType!Store value) { _payload.refCountedStore.ensureInitialized(); - assert(_length == _store.length); + assert(_length == _store.length, + "length and number of stored items out of sync"); assert(!_store.empty, "Cannot swap front of an empty heap."); if (!comp(value, _store.front)) return false; // value >= largest @@ -413,7 +416,7 @@ leaves the heap unaffected and returns $(D false). assert(equal(a, [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ])); } -/// $(D BinaryHeap) implements the standard input range interface, allowing +/// `BinaryHeap` implements the standard input range interface, allowing /// lazy iteration of the underlying range in descending order. @system unittest { @@ -425,8 +428,8 @@ leaves the heap unaffected and returns $(D false). } /** -Convenience function that returns a $(D BinaryHeap!Store) object -initialized with $(D s) and $(D initialSize). +Convenience function that returns a `BinaryHeap!Store` object +initialized with `s` and `initialSize`. */ BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s, size_t initialSize = size_t.max) @@ -477,7 +480,8 @@ BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s, assert(h.equal([16, 14, 10, 9, 8, 7, 4, 3, 2, 1])); } -@system unittest // 15675 +// https://issues.dlang.org/show_bug.cgi?id=15675 +@system unittest { import std.container.array : Array; @@ -486,7 +490,8 @@ BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s, assert(heap.front == 12); } -@system unittest // 16072 +// https://issues.dlang.org/show_bug.cgi?id=16072 +@system unittest { auto q = heapify!"a > b"([2, 4, 5]); q.insert(1); @@ -516,11 +521,7 @@ BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s, static struct StructWithoutDup { int[] a; - @disable StructWithoutDup dup() - { - StructWithoutDup d; - return d; - } + @disable StructWithoutDup dup(); alias a this; } @@ -585,7 +586,8 @@ BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s, assert(equal(b, [10, 9, 8, 7, 6, 6, 7, 8, 9, 10])); } -@system unittest // Issue 17314 +// https://issues.dlang.org/show_bug.cgi?id=17314 +@system unittest { import std.algorithm.comparison : equal; int[] a = [5]; diff --git a/libphobos/src/std/container/dlist.d b/libphobos/src/std/container/dlist.d index 633371f..cc3e2e8 100644 --- a/libphobos/src/std/container/dlist.d +++ b/libphobos/src/std/container/dlist.d @@ -4,7 +4,7 @@ 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) +Source: $(PHOBOSSRC std/container/dlist.d) Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. @@ -127,19 +127,19 @@ nothrow @safe pure: } @property - bool empty() const + bool empty() const scope { assert((_first is null) == (_last is null), "DList.Range: Invalidated state"); return !_first; } - @property BaseNode* front() + @property BaseNode* front() return scope { assert(!empty, "DList.Range.front: Range is empty"); return _first; } - void popFront() + void popFront() scope { assert(!empty, "DList.Range.popFront: Range is empty"); if (_first is _last) @@ -153,13 +153,13 @@ nothrow @safe pure: } } - @property BaseNode* back() + @property BaseNode* back() return scope { assert(!empty, "DList.Range.front: Range is empty"); return _last; } - void popBack() + void popBack() scope { assert(!empty, "DList.Range.popBack: Range is empty"); if (_first is _last) @@ -174,13 +174,13 @@ nothrow @safe pure: } /// Forward range primitive. - @property DRange save() { return this; } + @property DRange save() return scope { return this; } } /** Implements a doubly-linked list. -$(D DList) uses reference semantics. +`DList` uses reference semantics. */ struct DList(T) { @@ -222,12 +222,12 @@ struct DList(T) } ref inout(BaseNode*) _first() @property @safe nothrow pure inout { - assert(_root); + assert(_root, "Root pointer must not be null"); return _root._next; } ref inout(BaseNode*) _last() @property @safe nothrow pure inout { - assert(_root); + assert(_root, "Root pointer must not be null"); return _root._prev; } } //end private @@ -241,7 +241,7 @@ Constructor taking a number of nodes } /** -Constructor taking an input range +Constructor taking an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) */ this(Stuff)(Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) @@ -252,8 +252,8 @@ Constructor taking an input range /** Comparison for equality. -Complexity: $(BIGOH min(n, n1)) where $(D n1) is the number of -elements in $(D rhs). +Complexity: $(BIGOH min(n, n1)) where `n1` is the number of +elements in `rhs`. */ bool opEquals()(ref const DList rhs) const if (is(typeof(front == front))) @@ -316,7 +316,7 @@ elements in $(D rhs). } /** -Property returning $(D true) if and only if the container has no +Property returning `true` if and only if the container has no elements. Complexity: $(BIGOH 1) @@ -327,9 +327,9 @@ Complexity: $(BIGOH 1) } /** -Removes all contents from the $(D DList). +Removes all contents from the `DList`. -Postcondition: $(D empty) +Postcondition: `empty` Complexity: $(BIGOH 1) */ @@ -365,7 +365,7 @@ Complexity: $(BIGOH 1) } /** -Forward to $(D opSlice().front). +Forward to `opSlice().front`. Complexity: $(BIGOH 1) */ @@ -376,7 +376,7 @@ Complexity: $(BIGOH 1) } /** -Forward to $(D opSlice().back). +Forward to `opSlice().back`. Complexity: $(BIGOH 1) */ @@ -391,8 +391,8 @@ Complexity: $(BIGOH 1) /+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ /** -Returns a new $(D DList) that's the concatenation of $(D this) and its -argument $(D rhs). +Returns a new `DList` that's the concatenation of `this` and its +argument `rhs`. */ DList opBinary(string op, Stuff)(Stuff rhs) if (op == "~" && is(typeof(insertBack(rhs)))) @@ -403,8 +403,8 @@ argument $(D rhs). } /** -Returns a new $(D DList) that's the concatenation of the argument $(D lhs) -and $(D this). +Returns a new `DList` that's the concatenation of the argument `lhs` +and `this`. */ DList opBinaryRight(string op, Stuff)(Stuff lhs) if (op == "~" && is(typeof(insertFront(lhs)))) @@ -415,7 +415,7 @@ and $(D this). } /** -Appends the contents of the argument $(D rhs) into $(D this). +Appends the contents of the argument `rhs` into `this`. */ DList opOpAssign(string op, Stuff)(Stuff rhs) if (op == "~" && is(typeof(insertBack(rhs)))) @@ -429,8 +429,8 @@ Appends the contents of the argument $(D rhs) into $(D this). /+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ /** -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 +Inserts `stuff` to the front/back of the container. `stuff` can be a +value convertible to `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. @@ -464,18 +464,18 @@ Complexity: $(BIGOH log(n)) alias stableInsertBack = insertBack; /** -Inserts $(D stuff) after range $(D r), which must be a non-empty range +Inserts `stuff` after range `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 +`stuff` can be a value convertible to `T` or a range of objects +convertible to `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). +Complexity: $(BIGOH k + m), where `k` is the number of elements in +`r` and `m` is the length of `stuff`. */ size_t insertBefore(Stuff)(Range r, Stuff stuff) { @@ -515,7 +515,7 @@ 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) +Precondition: `!empty` Returns: The element removed. @@ -538,7 +538,7 @@ 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) +Precondition: `!empty` Complexity: $(BIGOH 1). */ @@ -564,9 +564,9 @@ Complexity: $(BIGOH 1). alias stableRemoveBack = removeBack; /** -Removes $(D howMany) values at the front or back of the +Removes `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, +do not throw if they could not remove `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 @@ -612,11 +612,11 @@ Complexity: $(BIGOH howMany). alias stableRemoveBack = removeBack; /** -Removes all elements belonging to $(D r), which must be a range +Removes all elements belonging to `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). +initially were right after `r`. Complexity: $(BIGOH 1) */ @@ -642,9 +642,12 @@ Complexity: $(BIGOH 1) return remove(r); } + /// ditto + alias stableRemove = remove; + /** -Removes first element of $(D r), wich must be a range obtained originally -from this container, from both DList instance and range $(D r). +Removes first element of `r`, wich must be a range obtained originally +from this container, from both DList instance and range `r`. Compexity: $(BIGOH 1) */ @@ -659,8 +662,8 @@ Compexity: $(BIGOH 1) } /** -Removes last element of $(D r), wich must be a range obtained originally -from this container, from both DList instance and range $(D r). +Removes last element of `r`, wich must be a range obtained originally +from this container, from both DList instance and range `r`. Compexity: $(BIGOH 1) */ @@ -675,8 +678,8 @@ Compexity: $(BIGOH 1) } /** -$(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 +`linearRemove` functions as `remove`, but also accepts ranges that are +result the of a `take` operation. This is a convenient way to remove a fixed amount of elements from the range. Complexity: $(BIGOH r.walkLength) @@ -698,12 +701,47 @@ Complexity: $(BIGOH r.walkLength) } /// ditto - alias stableRemove = remove; - /// ditto alias stableLinearRemove = linearRemove; +/** +Removes the first occurence of an element from the list in linear time. + +Returns: True if the element existed and was successfully removed, false otherwise. + +Params: + value = value of the node to be removed + +Complexity: $(BIGOH n) + */ + bool linearRemoveElement(T value) + { + auto n1 = findNodeByValue(_root, value); + if (n1) + { + auto n2 = n1._next._next; + BaseNode.connect(n1, n2); + return true; + } + + return false; + } + + private: + BaseNode* findNodeByValue(BaseNode* n, T value) + { + if (!n) return null; + auto ahead = n._next; + while (ahead && ahead.getPayload!T() != value) + { + n = ahead; + ahead = n._next; + if (ahead == _last._next) return null; + } + return n; + } + // Helper: Inserts stuff before the node n. size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff) if (isImplicitlyConvertible!(Stuff, T)) @@ -766,6 +804,38 @@ private: { import std.algorithm.comparison : equal; + auto e = DList!int(); + auto b = e.linearRemoveElement(1); + assert(b == false); + assert(e.empty()); + auto a = DList!int(-1, 1, 2, 1, 3, 4); + b = a.linearRemoveElement(1); + assert(equal(a[], [-1, 2, 1, 3, 4])); + assert(b == true); + b = a.linearRemoveElement(-1); + assert(b == true); + assert(equal(a[], [2, 1, 3, 4])); + b = a.linearRemoveElement(1); + assert(b == true); + assert(equal(a[], [2, 3, 4])); + b = a.linearRemoveElement(2); + assert(b == true); + b = a.linearRemoveElement(20); + assert(b == false); + assert(equal(a[], [3, 4])); + b = a.linearRemoveElement(4); + assert(b == true); + assert(equal(a[], [3])); + b = a.linearRemoveElement(3); + assert(b == true); + assert(a.empty()); + a.linearRemoveElement(3); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + //Tests construction signatures alias IntList = DList!int; auto a0 = IntList(); @@ -917,7 +987,7 @@ private: assert(r.back == 1); } -// Issue 8895 +// https://issues.dlang.org/show_bug.cgi?id=8895 @safe unittest { auto a = make!(DList!int)(1,2,3,4); @@ -988,7 +1058,7 @@ private: { import std.algorithm.comparison : equal; - //8905 + // https://issues.dlang.org/show_bug.cgi?id=8905 auto a = DList!int([1, 2, 3, 4]); auto r = a[]; a.stableRemoveBack(); @@ -996,7 +1066,8 @@ private: assert(a[].equal([1, 2, 3, 7])); } -@safe unittest //12566 +// https://issues.dlang.org/show_bug.cgi?id=12566 +@safe unittest { auto dl2 = DList!int([2,7]); dl2.removeFront(); @@ -1005,14 +1076,16 @@ private: assert(dl2.empty, "not empty?!"); } -@safe unittest //13076 +// https://issues.dlang.org/show_bug.cgi?id=13076 +@safe unittest { DList!int list; assert(list.empty); list.clear(); } -@safe unittest //13425 +// https://issues.dlang.org/show_bug.cgi?id=13425 +@safe unittest { import std.range : drop, take; auto list = DList!int([1,2,3,4,5]); @@ -1022,7 +1095,8 @@ private: assert(r.empty); // fails } -@safe unittest //14300 +// https://issues.dlang.org/show_bug.cgi?id=14300 +@safe unittest { interface ITest {} static class Test : ITest {} @@ -1030,7 +1104,8 @@ private: DList!ITest().insertBack(new Test()); } -@safe unittest //15263 +// https://issues.dlang.org/show_bug.cgi?id=15263 +@safe unittest { import std.range : iota; auto a = DList!int(); diff --git a/libphobos/src/std/container/package.d b/libphobos/src/std/container/package.d index fa00505..763da8b 100644 --- a/libphobos/src/std/container/package.d +++ b/libphobos/src/std/container/package.d @@ -6,7 +6,7 @@ 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 +approaches have been used. $(REF make, std,container,util) allows for uniform construction with either approach. --- @@ -21,7 +21,7 @@ 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. +Note that `make` can infer the element type from the given arguments. --- import std.container; @@ -34,7 +34,7 @@ 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. +To make a copy of a container, use the `c.dup` container primitive. --- import std.container, std.range; Array!int originalArray = make!(Array!int)(1, 2, 3); @@ -52,7 +52,7 @@ secondArray[0] = 1; assert(originalArray[0] == 12); --- -$(B Attention:) If the _container is implemented as a class, using an +$(B Attention:) If the container is implemented as a class, using an uninitialized instance can cause a null pointer dereference. --- @@ -62,8 +62,8 @@ 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 +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. @@ -85,11 +85,11 @@ array1.removeBack(); assert(array2.empty); --- It is therefore recommended to always construct containers using -$(REF make, std,_container,util). +$(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. +This is in fact necessary to put containers into another container. +For example, to construct an `Array` of ten empty `Array`s, use +the following that calls `make` ten times. --- import std.container, std.range; @@ -103,47 +103,47 @@ This module consists of the following submodules: $(UL $(LI - The $(MREF std, _container, array) module provides + 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 + 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 + The $(MREF std, container, dlist) module provides a doubly-linked list implementation. ) $(LI - The $(MREF std, _container, rbtree) module + The $(MREF std, container, rbtree) module implements red-black trees. ) $(LI - The $(MREF std, _container, slist) module + 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 $(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 +While some containers offer direct access to their elements e.g. via +`opIndex`, `c.front` or `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 +which is aliased as `C.Range`. For example, the primary range type of +`Array!int` is `Array!int.Range`. + +If the documentation of a member function of a container takes +a parameter of type `Range`, then it refers to the primary range type of +this container. Oftentimes `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. @@ -171,7 +171,7 @@ 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). +type as `Stuff`. --- import std.algorithm.comparison : equal; @@ -192,28 +192,28 @@ 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. +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 +For example the primitives `c.remove(r)` and `c.linearRemove(r)` both +remove the sequence of elements in range `r` from the container `c`. +The primitive `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)). +`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)). +Since a sequence of elements can be removed from a $(MREF_ALTTEXT doubly linked list,std,container,dlist) +in constant time, `DList` provides the primitive `c.remove(r)` +as well as `c.linearRemove(r)`. On the other hand +$(MREF_ALTTEXT Array, std,container, array) only offers `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 +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. +the $(BIGOH ·) column. Below, `C` means a container type, `c` is +a value of container type, $(D n$(SUBSCRIPT x)) represents the effective length of +value `x`, which could be a single element (in which case $(D n$(SUBSCRIPT x)) is +`1`), a container, or a range. $(BOOKTABLE Container primitives, $(TR @@ -222,284 +222,279 @@ $(TR $(TH Description) ) $(TR - $(TDNW $(D C(x))) + $(TDNW `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.) + $(TD Creates a container of type `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 `c.dup`) $(TDNW $(D n$(SUBSCRIPT c))) - $(TD Returns a duplicate of the _container.) + $(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 + $(TD Returns the concatenation of `c` and `r`. `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 + $(TD Returns the concatenation of `x` and `c`. `x` may be a single element or an input range type.) ) $(LEADINGROWN 3, Iteration ) $(TR - $(TD $(D c.Range)) + $(TD `c.Range`) $(TD) - $(TD The primary range type associated with the _container.) + $(TD The primary range type associated with the container.) ) $(TR - $(TD $(D c[])) + $(TD `c[]`) $(TDNW $(D log n$(SUBSCRIPT c))) $(TD Returns a range - iterating over the entire _container, in a _container-defined order.) + 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).) + $(TD Fetches a portion of the container from key `a` to key `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.) + $(TD `c.empty`) + $(TD `1`) + $(TD Returns `true` if the container has no elements, `false` otherwise.) ) $(TR - $(TD $(D c.length)) + $(TD `c.length`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Returns the number of elements in the _container.) + $(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)).) + $(TD Forces the number of elements in the container to `n`. + If the container ends up growing, the added elements are initialized + in a container-dependent manner (usually with `T.init`).) ) $(TR - $(TD $(D c.capacity)) + $(TD `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.) + container without triggering a reallocation.) ) $(TR - $(TD $(D c.reserve(x))) + $(TD `c.reserve(x)`) $(TD $(D n$(SUBSCRIPT c))) - $(TD Forces $(D capacity) to at least $(D x) without reducing it.) + $(TD Forces `capacity` to at least `x` without reducing it.) ) $(LEADINGROWN 3, Access ) $(TR - $(TDNW $(D c.front)) + $(TDNW `c.front`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Returns the first element of the _container, in a _container-defined order.) + $(TD Returns the first element of the container, in a container-defined order.) ) $(TR - $(TDNW $(D c.moveFront)) + $(TDNW `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).) + container. The slot is not removed from the container; it is left + initialized with `T.init`. This routine need not be defined if $(D + front) returns a `ref`.) ) $(TR $(TDNW $(D c.front = v)) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Assigns $(D v) to the first element of the _container.) + $(TD Assigns `v` to the first element of the container.) ) $(TR - $(TDNW $(D c.back)) + $(TDNW `c.back`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Returns the last element of the _container, in a _container-defined order.) + $(TD Returns the last element of the container, in a container-defined order.) ) $(TR - $(TDNW $(D c.moveBack)) + $(TDNW `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).) + container. The slot is not removed from the container; it is left + initialized with `T.init`. This routine need not be defined if $(D + front) returns a `ref`.) ) $(TR $(TDNW $(D c.back = v)) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Assigns $(D v) to the last element of the _container.) + $(TD Assigns `v` to the last element of the container.) ) $(TR - $(TDNW $(D c[x])) + $(TDNW `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 + $(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 `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 + $(TD Destructively reads and returns the value at position `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.) + $(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.) + 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).) + $(TD Returns nonzero if e is found in `c`.) ) $(TR - $(TDNW $(D c.lowerBound(v))) + $(TDNW `c.lowerBound(v)`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Returns a range of all elements strictly less than $(D v).) + $(TD Returns a range of all elements strictly less than `v`.) ) $(TR - $(TDNW $(D c.upperBound(v))) + $(TDNW `c.upperBound(v)`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Returns a range of all elements strictly greater than $(D v).) + $(TD Returns a range of all elements strictly greater than `v`.) ) $(TR - $(TDNW $(D c.equalRange(v))) + $(TDNW `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).) + $(TD Returns a range of all elements in `c` that are equal to `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.) + $(TD Appends `x` to `c`. `x` may be a single element or an input range type.) ) $(TR - $(TDNW $(D c.clear())) + $(TDNW `c.clear()`) $(TDNW $(D n$(SUBSCRIPT c))) - $(TD Removes all elements in $(D c).) + $(TD Removes all elements in `c`.) ) $(TR - $(TDNW $(D c.insert(x))) + $(TDNW `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).) + $(TD Inserts `x` in `c` at a position (or positions) chosen by `c`.) ) $(TR - $(TDNW $(D c.stableInsert(x))) + $(TDNW `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.) + $(TD Same as `c.insert(x)`, but is guaranteed to not invalidate any ranges.) ) $(TR - $(TDNW $(D c.linearInsert(v))) + $(TDNW `c.linearInsert(v)`) $(TDNW $(D n$(SUBSCRIPT c))) - $(TD Same as $(D c.insert(v)) but relaxes complexity to linear.) + $(TD Same as `c.insert(v)` but relaxes complexity to linear.) ) $(TR - $(TDNW $(D c.stableLinearInsert(v))) + $(TDNW `c.stableLinearInsert(v)`) $(TDNW $(D n$(SUBSCRIPT c))) - $(TD Same as $(D c.stableInsert(v)) but relaxes complexity to linear.) + $(TD Same as `c.stableInsert(v)` but relaxes complexity to linear.) ) $(TR - $(TDNW $(D c.removeAny())) + $(TDNW `c.removeAny()`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Removes some element from $(D c) and returns it.) + $(TD Removes some element from `c` and returns it.) ) $(TR - $(TDNW $(D c.stableRemoveAny())) + $(TDNW `c.stableRemoveAny()`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Same as $(D c.removeAny()), but is guaranteed to not invalidate any + $(TD Same as `c.removeAny()`, but is guaranteed to not invalidate any iterators.) ) $(TR - $(TDNW $(D c.insertFront(v))) + $(TDNW `c.insertFront(v)`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Inserts $(D v) at the front of $(D c).) + $(TD Inserts `v` at the front of `c`.) ) $(TR - $(TDNW $(D c.stableInsertFront(v))) + $(TDNW `c.stableInsertFront(v)`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Same as $(D c.insertFront(v)), but guarantees no ranges will be + $(TD Same as `c.insertFront(v)`, but guarantees no ranges will be invalidated.) ) $(TR - $(TDNW $(D c.insertBack(v))) + $(TDNW `c.insertBack(v)`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Inserts $(D v) at the back of $(D c).) + $(TD Inserts `v` at the back of `c`.) ) $(TR - $(TDNW $(D c.stableInsertBack(v))) + $(TDNW `c.stableInsertBack(v)`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Same as $(D c.insertBack(v)), but guarantees no ranges will be + $(TD Same as `c.insertBack(v)`, but guarantees no ranges will be invalidated.) ) $(TR - $(TDNW $(D c.removeFront())) + $(TDNW `c.removeFront()`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Removes the element at the front of $(D c).) + $(TD Removes the element at the front of `c`.) ) $(TR - $(TDNW $(D c.stableRemoveFront())) + $(TDNW `c.stableRemoveFront()`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Same as $(D c.removeFront()), but guarantees no ranges will be + $(TD Same as `c.removeFront()`, but guarantees no ranges will be invalidated.) ) $(TR - $(TDNW $(D c.removeBack())) + $(TDNW `c.removeBack()`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Removes the value at the back of $(D c).) + $(TD Removes the value at the back of `c`.) ) $(TR - $(TDNW $(D c.stableRemoveBack())) + $(TDNW `c.stableRemoveBack()`) $(TDNW $(D log n$(SUBSCRIPT c))) - $(TD Same as $(D c.removeBack()), but guarantees no ranges will be + $(TD Same as `c.removeBack()`, but guarantees no ranges will be invalidated.) ) $(TR - $(TDNW $(D c.remove(r))) + $(TDNW `c.remove(r)`) $(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c))) - $(TD Removes range $(D r) from $(D c).) + $(TD Removes range `r` from `c`.) ) $(TR - $(TDNW $(D c.stableRemove(r))) + $(TDNW `c.stableRemove(r)`) $(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c))) - $(TD Same as $(D c.remove(r)), but guarantees iterators are not + $(TD Same as `c.remove(r)`, but guarantees iterators are not invalidated.) ) $(TR - $(TDNW $(D c.linearRemove(r))) + $(TDNW `c.linearRemove(r)`) $(TDNW $(D n$(SUBSCRIPT c))) - $(TD Removes range $(D r) from $(D c).) + $(TD Removes range `r` from `c`.) ) $(TR - $(TDNW $(D c.stableLinearRemove(r))) + $(TDNW `c.stableLinearRemove(r)`) $(TDNW $(D n$(SUBSCRIPT c))) - $(TD Same as $(D c.linearRemove(r)), but guarantees iterators are not + $(TD Same as `c.linearRemove(r)`, but guarantees iterators are not invalidated.) ) $(TR - $(TDNW $(D c.removeKey(k))) + $(TDNW `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 ) + $(TD Removes an element from `c` by using its key `k`. + The key's type is defined by the container.) ) ) -Source: $(PHOBOSSRC std/_container/package.d) +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. @@ -522,10 +517,10 @@ public import std.container.slist; import std.meta; -/* The following documentation and type $(D TotalContainer) are +/* The following documentation and type `TotalContainer` are intended for developers only. -$(D TotalContainer) is an unimplemented container that illustrates a +`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, @@ -533,7 +528,7 @@ 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 +implemented as a `class` or `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. @@ -545,32 +540,32 @@ 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 +specified otherwise, `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) +If the container has a notion of key-value mapping, `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. +KeyTypes[k]), where `k` is a zero-based unsigned number, defines +the type of the `k`th key of the container. -A container may define both $(D KeyType) and $(D KeyTypes), e.g. in +A container may define both `KeyType` and `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) +If the container has a notion of key-value mapping, `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). +container mapping values of type `K` to values of type `V` +defines `KeyType` to be `K` and `ValueType` to be `V`. */ alias ValueType = T; @@ -587,89 +582,89 @@ Generally a container may define several types of ranges. +/ @property bool empty() { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property ref T front() //ref return optional { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property void front(T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// Ditto T moveFront() { - assert(0); + assert(0, "Not implemented"); } /// Ditto void popFront() { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property ref T back() //ref return optional { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property void back(T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// Ditto T moveBack() { - assert(0); + assert(0, "Not implemented"); } /// Ditto void popBack() { - assert(0); + assert(0, "Not implemented"); } /// Ditto T opIndex(size_t i) //ref return optional { - assert(0); + assert(0, "Not implemented"); } /// Ditto void opIndexAssign(size_t i, T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// Ditto T opIndexUnary(string op)(size_t i) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// Ditto void opIndexOpAssign(string op)(size_t i, T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// Ditto T moveAt(size_t i) { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property size_t length() { - assert(0); + assert(0, "Not implemented"); } } /** -Property returning $(D true) if and only if the container has no +Property returning `true` if and only if the container has no elements. Complexity: $(BIGOH 1) */ @property bool empty() { - assert(0); + assert(0, "Not implemented"); } /** @@ -680,7 +675,7 @@ Complexity: $(BIGOH n). */ @property TotalContainer dup() { - assert(0); + assert(0, "Not implemented"); } /** @@ -690,7 +685,7 @@ Complexity: $(BIGOH log(n)). */ @property size_t length() { - assert(0); + assert(0, "Not implemented"); } /** @@ -701,11 +696,11 @@ Complexity: $(BIGOH log(n)). */ @property size_t capacity() { - assert(0); + assert(0, "Not implemented"); } /** -Ensures sufficient capacity to accommodate $(D n) elements. +Ensures sufficient capacity to accommodate `n` elements. Postcondition: $(D capacity >= n) @@ -714,19 +709,19 @@ $(BIGOH 1). */ void reserve(size_t e) { - assert(0); + assert(0, "Not implemented"); } /** 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()). +convenient and fast method of iteration for `opSlice()`. Complexity: $(BIGOH log(n)) */ Range opSlice() { - assert(0); + assert(0, "Not implemented"); } /** @@ -737,42 +732,42 @@ Complexity: $(BIGOH log(n)) */ Range opSlice(size_t a, size_t b) { - assert(0); + assert(0, "Not implemented"); } /** -Forward to $(D opSlice().front) and $(D opSlice().back), respectively. +Forward to `opSlice().front` and `opSlice().back`, respectively. Complexity: $(BIGOH log(n)) */ @property ref T front() //ref return optional { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property void front(T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// Ditto T moveFront() { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property ref T back() //ref return optional { - assert(0); + assert(0, "Not implemented"); } /// Ditto @property void back(T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// Ditto T moveBack() { - assert(0); + assert(0, "Not implemented"); } /** @@ -780,27 +775,27 @@ Indexing operators yield or modify the value at a specified index. */ ref T opIndex(KeyType) //ref return optional { - assert(0); + assert(0, "Not implemented"); } /// ditto void opIndexAssign(KeyType i, T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// ditto T opIndexUnary(string op)(KeyType i) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// ditto void opIndexOpAssign(string op)(KeyType i, T value) //Only when front does not return by ref { - assert(0); + assert(0, "Not implemented"); } /// ditto T moveAt(KeyType i) { - assert(0); + assert(0, "Not implemented"); } /** @@ -808,55 +803,55 @@ $(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); + assert(0, "Not implemented"); } /** -Returns a range of all elements containing $(D k) (could be empty or a +Returns a range of all elements containing `k` (could be empty or a singleton range). */ Range equalRange(KeyType k) { - assert(0); + assert(0, "Not implemented"); } /** -Returns a range of all elements with keys less than $(D k) (could be +Returns a range of all elements with keys less than `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); + assert(0, "Not implemented"); } /** -Returns a range of all elements with keys larger than $(D k) (could be +Returns a range of all elements with keys larger than `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); + assert(0, "Not implemented"); } /** -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). +Returns a new container that's the concatenation of `this` and its +argument. `opBinaryRight` is only defined if `Stuff` does not +define `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); + assert(0, "Not implemented"); } /// ditto TotalContainer opBinaryRight(string op)(Stuff lhs) if (op == "~") { - assert(0); + assert(0, "Not implemented"); } /** @@ -864,25 +859,25 @@ Forwards to $(D insertAfter(this[], stuff)). */ void opOpAssign(string op)(Stuff stuff) if (op == "~") { - assert(0); + assert(0, "Not implemented"); } /** Removes all contents from the container. The container decides how $(D capacity) is affected. -Postcondition: $(D empty) +Postcondition: `empty` Complexity: $(BIGOH n) */ void clear() { - assert(0); + assert(0, "Not implemented"); } /** -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 +Sets the number of elements in the container to `newSize`. If $(D +newSize) is greater than `length`, the added elements are added to unspecified positions in the container and initialized with $(D .init). @@ -892,48 +887,48 @@ Postcondition: $(D _length == newLength) */ @property void length(size_t newLength) { - assert(0); + assert(0, "Not implemented"); } /** -Inserts $(D stuff) in an unspecified position in the +Inserts `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 +behavior. `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 +The `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 +non-invalidating insertion should use `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) +Complexity: $(BIGOH m * log(n)), where `m` is the number of +elements in `stuff` */ size_t insert(Stuff)(Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } ///ditto size_t stableInsert(Stuff)(Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /** -Same as $(D insert(stuff)) and $(D stableInsert(stuff)) respectively, +Same as `insert(stuff)` and `stableInsert(stuff)` respectively, but relax the complexity constraint to linear. */ size_t linearInsert(Stuff)(Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } ///ditto size_t stableLinearInsert(Stuff)(Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /** @@ -943,7 +938,7 @@ 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) +Precondition: `!empty` Returns: The element removed. @@ -951,16 +946,16 @@ Complexity: $(BIGOH log(n)). */ T removeAny() { - assert(0); + assert(0, "Not implemented"); } /// ditto T stableRemoveAny() { - assert(0); + assert(0, "Not implemented"); } /** -Inserts $(D value) to the front or back of the container. $(D stuff) +Inserts `value` to the front or back of the container. `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 @@ -972,22 +967,22 @@ Complexity: $(BIGOH log(n)). */ size_t insertFront(Stuff)(Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t stableInsertFront(Stuff)(Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t insertBack(Stuff)(Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t stableInsertBack(T value) { - assert(0); + assert(0, "Not implemented"); } /** @@ -997,34 +992,34 @@ 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) +Precondition: `!empty` Complexity: $(BIGOH log(n)). */ void removeFront() { - assert(0); + assert(0, "Not implemented"); } /// ditto void stableRemoveFront() { - assert(0); + assert(0, "Not implemented"); } /// ditto void removeBack() { - assert(0); + assert(0, "Not implemented"); } /// ditto void stableRemoveBack() { - assert(0); + assert(0, "Not implemented"); } /** -Removes $(D howMany) values at the front or back of the +Removes `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, +do not throw if they could not remove `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 @@ -1036,40 +1031,40 @@ Complexity: $(BIGOH howMany * log(n)). */ size_t removeFront(size_t howMany) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t stableRemoveFront(size_t howMany) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t removeBack(size_t howMany) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t stableRemoveBack(size_t howMany) { - assert(0); + assert(0, "Not implemented"); } /** -Removes all values corresponding to key $(D k). +Removes all values corresponding to key `k`. -Complexity: $(BIGOH m * log(n)), where $(D m) is the number of +Complexity: $(BIGOH m * log(n)), where `m` is the number of elements with the same key. Returns: The number of elements removed. */ size_t removeKey(KeyType k) { - assert(0); + assert(0, "Not implemented"); } /** -Inserts $(D stuff) before, after, or instead range $(D r), which must -be a valid range previously extracted from this container. $(D stuff) +Inserts `stuff` before, after, or instead range `r`, which must +be a valid range previously extracted from this container. `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 @@ -1077,76 +1072,76 @@ invalidated. Returns: The number of values inserted. -Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) +Complexity: $(BIGOH n + m), where `m` is the length of `stuff` */ size_t insertBefore(Stuff)(Range r, Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t stableInsertBefore(Stuff)(Range r, Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t insertAfter(Stuff)(Range r, Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t stableInsertAfter(Stuff)(Range r, Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t replace(Stuff)(Range r, Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /// ditto size_t stableReplace(Stuff)(Range r, Stuff stuff) { - assert(0); + assert(0, "Not implemented"); } /** -Removes all elements belonging to $(D r), which must be a range +Removes all elements belonging to `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). +initially were right after `r`. -Complexity: $(BIGOH m * log(n)), where $(D m) is the number of -elements in $(D r) +Complexity: $(BIGOH m * log(n)), where `m` is the number of +elements in `r` */ Range remove(Range r) { - assert(0); + assert(0, "Not implemented"); } /// ditto Range stableRemove(Range r) { - assert(0); + assert(0, "Not implemented"); } /** -Same as $(D remove) above, but has complexity relaxed to linear. +Same as `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). +initially were right after `r`. Complexity: $(BIGOH n) */ Range linearRemove(Range r) { - assert(0); + assert(0, "Not implemented"); } /// ditto Range stableLinearRemove(Range r) { - assert(0); + assert(0, "Not implemented"); } } diff --git a/libphobos/src/std/container/rbtree.d b/libphobos/src/std/container/rbtree.d index 5e31ac2..f8e70fc 100644 --- a/libphobos/src/std/container/rbtree.d +++ b/libphobos/src/std/container/rbtree.d @@ -3,7 +3,7 @@ This module implements a red-black tree container. This module is a submodule of $(MREF std, container). -Source: $(PHOBOSSRC std/container/_rbtree.d) +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. @@ -61,7 +61,7 @@ import std.functional : binaryFun; public import std.container.util; -version (unittest) debug = RBDoChecks; +version (StdUnittest) debug = RBDoChecks; //debug = RBDoChecks; @@ -136,9 +136,12 @@ struct RBNode(V) * Set the left child. Also updates the new child's parent node. This * does not update the previous child. * + * $(RED Warning: If the node this is called on is a local variable, a stack pointer can be + * escaped through `newNode.parent`. It's marked `@trusted` only for backwards compatibility.) + * * Returns newNode */ - @property Node left(Node newNode) + @property Node left(return scope Node newNode) @trusted { _left = newNode; if (newNode !is null) @@ -150,9 +153,12 @@ struct RBNode(V) * Set the right child. Also updates the new child's parent node. This * does not update the previous child. * + * $(RED Warning: If the node this is called on is a local variable, a stack pointer can be + * escaped through `newNode.parent`. It's marked `@trusted` only for backwards compatibility.) + * * Returns newNode */ - @property Node right(Node newNode) + @property Node right(return scope Node newNode) @trusted { _right = newNode; if (newNode !is null) @@ -183,9 +189,9 @@ struct RBNode(V) Node rotateR() in { - assert(_left !is null); + assert(_left !is null, "left node must not be null"); } - body + do { // sets _left._parent also if (isLeftNode) @@ -226,9 +232,9 @@ struct RBNode(V) Node rotateL() in { - assert(_right !is null); + assert(_right !is null, "right node must not be null"); } - body + do { // sets _right._parent also if (isLeftNode) @@ -255,9 +261,9 @@ struct RBNode(V) @property bool isLeftNode() const in { - assert(_parent !is null); + assert(_parent !is null, "parent must not be null"); } - body + do { return _parent._left is &this; } @@ -542,7 +548,8 @@ struct RBNode(V) _parent.right = null; } - // clean references to help GC - Bugzilla 12915 + // clean references to help GC + // https://issues.dlang.org/show_bug.cgi?id=12915 _left = _right = _parent = null; return ret; @@ -662,7 +669,7 @@ private struct RBRange(N) } /** - * Returns $(D true) if the range is _empty + * Returns `true` if the range is _empty */ @property bool empty() const { @@ -706,7 +713,7 @@ private struct RBRange(N) } /** - * Trivial _save implementation, needed for $(D isForwardRange). + * Trivial _save implementation, needed for `isForwardRange`. */ @property RBRange save() { @@ -723,15 +730,15 @@ private struct RBRange(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) + * function, delegate, functor, or any type where $(D less(a, b)) results in a `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). + * elements `a` and `b`, $(D less(a, b) == !less(b, a)). $(D less(a, a)) should + * always equal `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 + * If `allowDuplicates` is set to `true`, then inserting the same element more than + * once continues to add more elements. If it is `false`, duplicate elements are * ignored on insertion. If duplicates are allowed, then new elements are * inserted after all existing duplicate elements. */ @@ -745,11 +752,11 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) alias _less = binaryFun!less; - version (unittest) + version (StdUnittest) { static if (is(typeof(less) == string)) { - private enum doUnittest = isIntegral!T && (less == "a < b" || less == "a > b"); + private enum doUnittest = is(byte : T) && isIntegral!T && (less == "a < b" || less == "a > b"); } else enum doUnittest = false; @@ -789,7 +796,8 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) private void _setup() { - assert(!_end); //Make sure that _setup isn't run more than once. + //Make sure that _setup isn't run more than once. + assert(!_end, "Setup must only be run once"); _begin = _end = allocate(); } @@ -804,7 +812,7 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) } /** - * The range types for $(D RedBlackTree) + * The range types for `RedBlackTree` */ alias Range = RBRange!(RBNode*); alias ConstRange = RBRange!(const(RBNode)*); /// Ditto @@ -874,9 +882,12 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) } } - // 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) + /* add an element to the tree, returns the node added, or the existing node + * if it has already been added and allowDuplicates is false + * Returns: + * true if node was added + */ + private bool _add(return Elem n) { Node result; static if (!allowDuplicates) @@ -884,7 +895,8 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) if (!_end.left) { - _end.left = _begin = result = allocate(n); + result = allocate(n); + (() @trusted { _end.left = _begin = result; }) (); } else { @@ -900,7 +912,8 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) // // add to right of new parent // - newParent.left = result = allocate(n); + result = allocate(n); + (() @trusted { newParent.left = result; }) (); break; } } @@ -921,7 +934,8 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) // // add to right of new parent // - newParent.right = result = allocate(n); + result = allocate(n); + (() @trusted { newParent.right = result; }) (); break; } } @@ -930,19 +944,16 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) if (_begin.left) _begin = _begin.left; } - static if (allowDuplicates) { result.setColor(_end); debug(RBDoChecks) check(); ++_length; - return result; + return true; } else { - import std.typecons : Tuple; - if (added) { ++_length; @@ -950,16 +961,16 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) } debug(RBDoChecks) check(); - return Tuple!(bool, "added", Node, "n")(added, result); + return added; } } /** - * Check if any elements exist in the container. Returns $(D false) if at least + * Check if any elements exist in the container. Returns `false` if at least * one element exists. */ - @property bool empty() + @property bool empty() const // pure, nothrow, @safe, @nogc: are inferred { return _end.left is null; } @@ -1025,7 +1036,7 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) * * Complexity: $(BIGOH 1) */ - Elem front() + inout(Elem) front() inout { return _begin.value; } @@ -1035,13 +1046,13 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) * * Complexity: $(BIGOH log(n)) */ - Elem back() + inout(Elem) back() inout { return _end.prev.value; } /++ - $(D in) operator. Check to see if the given element exists in the + `in` operator. Check to see if the given element exists in the container. Complexity: $(BIGOH log(n)) @@ -1075,7 +1086,7 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) auto thisRange = this[]; auto thatRange = that[]; - return equal!(function(Elem a, Elem b) => !_less(a,b) && !_less(b,a)) + return equal!((Elem a, Elem b) => !_less(a,b) && !_less(b,a)) (thisRange, thatRange); } @@ -1095,6 +1106,134 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) } /** + * Generates a hash for the tree. Note that with a custom comparison function + * it may not hold that if two rbtrees are equal, the hashes of the trees + * will be equal. + */ + override size_t toHash() nothrow @safe + { + size_t hash = cast(size_t) 0x6b63_616c_4264_6552UL; + foreach (ref e; this[]) + // As in boost::hash_combine + // https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine + hash += .hashOf(e) + 0x9e3779b9 + (hash << 6) + (hash >>> 2); + return hash; + } + + 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); + + assert(t1.toHash() == t2.toHash); + + assert(t1.toHash() != t3.toHash); + assert(t2.toHash() != t3.toHash); + + assert(t3.toHash() != t4.toHash); + assert(t1.toHash() != t4.toHash); + + // empty tree + auto t5 = new RedBlackTree(); + auto t6 = new RedBlackTree(); + + assert(t5.toHash() == t6.toHash()); + + auto t7 = new RedBlackTree!string("red", "black"); + auto t8 = new RedBlackTree!string("white", "black"); + auto t9 = new RedBlackTree!string("red", "black"); + + assert(t7.toHash() == t9.toHash()); + assert(t7.toHash() != t8.toHash()); + + static struct MyInt + { + int x; + + @safe: + + this(int init_x) + { + x = init_x; + } + + size_t toHash() const nothrow + { + return typeid(x).getHash(&x) ^ 0xF0F0_F0F0; + } + + int opCmp(const MyInt that) const + { + return (x > that.x) - (x < that.x); + } + + bool opEquals(const MyInt that) const + { + return (this.x == that.x); + } + } + + auto rbt1 = new RedBlackTree!MyInt(MyInt(1), MyInt(2), MyInt(3), MyInt(4)); + auto rbt2 = new RedBlackTree!MyInt(MyInt(1), MyInt(2), MyInt(3), MyInt(4)); + + assert(rbt1.toHash() == rbt2.toHash()); + assert(rbt1.toHash() != t1.toHash()); + + auto rbt3 = new RedBlackTree!MyInt(MyInt(4), MyInt(2), MyInt(3), MyInt(4)); + + assert(rbt1.toHash() != rbt3.toHash()); + + class MyInt2 + { + int x; + + this(int init_x) + { + x = init_x; + } + + override size_t toHash() const @safe nothrow + { + return typeid(x).getHash(&x) ^ 0xF0F0_F0F0; + } + + int opCmp(const MyInt2 that) const + { + return (x > that.x) - (x < that.x); + } + + bool opEquals(const MyInt2 that) const + { + return (this.x == that.x); + } + } + + static bool nullSafeLess(scope const MyInt2 a, scope const MyInt2 b) + { + return a is null ? b !is null : (b !is null && a < b); + } + + auto rbt4 = new RedBlackTree!MyInt2(new MyInt2(1), new MyInt2(9), new MyInt2(3), new MyInt2(42)); + auto rbt5 = new RedBlackTree!MyInt2(new MyInt2(1), new MyInt2(9), new MyInt2(3), new MyInt2(42)); + auto rbt6 = new RedBlackTree!(MyInt2, nullSafeLess)(new MyInt2(9), new MyInt2(3), new MyInt2(42)); + auto rbt7 = new RedBlackTree!(MyInt2, nullSafeLess)(null); + + assert(rbt6.toHash() != rbt5.toHash()); + assert(rbt6.toHash() != rbt4.toHash()); + assert(rbt6.toHash() != rbt7.toHash()); + assert(rbt4.toHash() == rbt5.toHash()); + + auto rbt8 = new RedBlackTree!(MyInt2, nullSafeLess)(null, new MyInt2(9), null, new MyInt2(42)); + auto rbt9 = new RedBlackTree!(MyInt2, nullSafeLess)(null, new MyInt2(9), null, new MyInt2(42)); + auto rbt10 = new RedBlackTree!(MyInt2, nullSafeLess)(new MyInt2(94), null, new MyInt2(147)); + + assert(rbt8.toHash() == rbt9.toHash()); + assert(rbt8.toHash() != rbt10.toHash()); + } + + /** * Removes all elements from the container. * * Complexity: $(BIGOH 1) @@ -1131,7 +1270,7 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) } else { - return(_add(stuff).added ? 1 : 0); + return _add(stuff); } } @@ -1143,7 +1282,9 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) * * Complexity: $(BIGOH m * log(n)) */ - size_t stableInsert(Stuff)(Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, Elem)) + size_t stableInsert(Stuff)(scope Stuff stuff) + if (isInputRange!Stuff && + isImplicitlyConvertible!(ElementType!Stuff, Elem)) { size_t result = 0; static if (allowDuplicates) @@ -1158,8 +1299,7 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) { foreach (e; stuff) { - if (_add(e).added) - ++result; + result += _add(e); } } return result; @@ -1322,7 +1462,7 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) } /++ - Removes the given $(D Take!Range) from the container + Removes the given `Take!Range` from the container Returns: A range containing all of the elements that were after the given range. @@ -1377,7 +1517,7 @@ if (is(typeof(binaryFun!less(T.init, T.init)))) /++ 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, + given which is in the container. If `allowDuplicates` is true, duplicates are removed only if duplicate values are given. Returns: The number of elements removed. @@ -1401,7 +1541,7 @@ assert(equal(rbt[], [5])); } /++ Ditto +/ - size_t removeKey(U)(U[] elems) + size_t removeKey(U)(scope U[] elems) if (isImplicitlyConvertible!(U, Elem)) { immutable lenBefore = length; @@ -1649,7 +1789,7 @@ assert(equal(rbt[], [5])); * 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() + void check() @trusted { // // check implementation of the tree @@ -1712,7 +1852,8 @@ assert(equal(rbt[], [5])); */ 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 { + void toString(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) const + { sink("RedBlackTree("); sink.formatValue(this[], fmt); sink(")"); @@ -1814,11 +1955,18 @@ assert(equal(rbt[], [5])); test!byte(); } +// https://issues.dlang.org/show_bug.cgi?id=19626 +@safe pure unittest +{ + enum T { a, b } + alias t = RedBlackTree!T; +} + import std.range.primitives : isInputRange, ElementType; import std.traits : isArray, isSomeString; /++ - Convenience function for creating a $(D RedBlackTree!E) from a list of + Convenience function for creating a `RedBlackTree!E` from a list of values. Params: @@ -2022,8 +2170,12 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init @safe pure unittest { const rt1 = redBlackTree(5,4,3,2,1); - static assert(is(typeof(rt1.length))); - static assert(is(typeof(5 in rt1))); + void allQualifiers() pure nothrow @safe @nogc { + assert(!rt1.empty); + assert(rt1.length == 5); + assert(5 in rt1); + } + allQualifiers(); static assert(is(typeof(rt1.upperBound(3).front) == const(int))); import std.algorithm.comparison : equal; @@ -2037,21 +2189,24 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init @safe pure unittest { immutable rt1 = redBlackTree(5,4,3,2,1); + static assert(is(typeof(rt1.empty))); static assert(is(typeof(rt1.length))); + static assert(is(typeof(5 in rt1))); 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 +// https://issues.dlang.org/show_bug.cgi?id=15941 @safe pure unittest { class C {} RedBlackTree!(C, "cast(void*)a < cast(void*) b") tree; } -@safe pure unittest // const/immutable elements (issue 17519) +// const/immutable elements (https://issues.dlang.org/show_bug.cgi?id=17519) +@safe pure unittest { RedBlackTree!(immutable int) t1; RedBlackTree!(const int) t2; @@ -2063,3 +2218,13 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init static assert(!__traits(compiles, *t3.front.p = 4)); assert(*t3.front.p == 1); } + +// make sure the comparator can be a delegate +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + auto t = new RedBlackTree!(int, delegate(a, b) => a > b); + t.insert([1, 3, 5, 4, 2]); + assert(t[].equal([5, 4, 3, 2, 1])); +} diff --git a/libphobos/src/std/container/slist.d b/libphobos/src/std/container/slist.d index 820b0bb..0b504b4 100644 --- a/libphobos/src/std/container/slist.d +++ b/libphobos/src/std/container/slist.d @@ -4,7 +4,7 @@ It can be used as a stack. This module is a submodule of $(MREF std, container). -Source: $(PHOBOSSRC std/container/_slist.d) +Source: $(PHOBOSSRC std/container/slist.d) Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. @@ -51,9 +51,10 @@ 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. + `SList` uses reference semantics. */ struct SList(T) +if (!is(T == shared)) { import std.exception : enforce; import std.range : Take; @@ -81,13 +82,13 @@ struct SList(T) private ref inout(Node*) _first() @property @safe nothrow pure inout { - assert(_root); + assert(_root, "root pointer must not be null"); return _root._next; } private static Node * findLastNode(Node * n) { - assert(n); + assert(n, "Node n pointer must not be null"); auto ahead = n._next; while (ahead) { @@ -99,7 +100,8 @@ struct SList(T) private static Node * findLastNode(Node * n, size_t limit) { - assert(n && limit); + assert(n, "Node n pointer must not be null"); + assert(limit, "limit must be greater than 0"); auto ahead = n._next; while (ahead) { @@ -112,7 +114,7 @@ struct SList(T) private static Node * findNode(Node * n, Node * findMe) { - assert(n); + assert(n, "Node n pointer must not be null"); auto ahead = n._next; while (ahead != findMe) { @@ -123,6 +125,60 @@ struct SList(T) return n; } + private static Node* findNodeByValue(Node* n, T value) + { + if (!n) return null; + auto ahead = n._next; + while (ahead && ahead._payload != value) + { + n = ahead; + ahead = n._next; + } + return n; + } + + private static auto createNodeChain(Stuff)(Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T)) + { + import std.range : only; + return createNodeChain(only(stuff)); + } + + private static auto createNodeChain(Stuff)(Stuff stuff) + if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + static struct Chain + { + Node* first; + Node* last; + size_t length; + } + + Chain ch; + + foreach (item; stuff) + { + auto newNode = new Node(null, item); + (ch.first ? ch.last._next : ch.first) = newNode; + ch.last = newNode; + ++ch.length; + } + + return ch; + } + + private static size_t insertAfterNode(Stuff)(Node* n, Stuff stuff) + { + auto ch = createNodeChain(stuff); + + if (!ch.length) return 0; + + ch.last._next = n._next; + n._next = ch.first; + + return ch.length; + } + /** Constructor taking a number of nodes */ @@ -132,7 +188,7 @@ Constructor taking a number of nodes } /** -Constructor taking an input range +Constructor taking an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) */ this(Stuff)(Stuff stuff) if (isInputRange!Stuff @@ -145,8 +201,8 @@ Constructor taking an input range /** Comparison for equality. -Complexity: $(BIGOH min(n, n1)) where $(D n1) is the number of -elements in $(D rhs). +Complexity: $(BIGOH min(n, n1)) where `n1` is the number of +elements in `rhs`. */ bool opEquals(const SList rhs) const { @@ -217,7 +273,7 @@ Defines the container's primary range, which embodies a forward range. } /** -Property returning $(D true) if and only if the container has no +Property returning `true` if and only if the container has no elements. Complexity: $(BIGOH 1) @@ -253,7 +309,7 @@ Complexity: $(BIGOH 1) } /** -Forward to $(D opSlice().front). +Forward to `opSlice().front`. Complexity: $(BIGOH 1) */ @@ -271,37 +327,41 @@ Complexity: $(BIGOH 1) } /** -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). +Returns a new `SList` that's the concatenation of `this` and its +argument. `opBinaryRight` is only defined if `Stuff` does not +define `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; + import std.range : chain, only; + + static if (isInputRange!Stuff) + alias r = rhs; + else + auto r = only(rhs); + + return SList(this[].chain(r)); } /// 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; + import std.range : chain, only; + + static if (isInputRange!Stuff) + alias r = lhs; + else + auto r = only(lhs); + + return SList(r.chain(this[])); } /** -Removes all contents from the $(D SList). +Removes all contents from the `SList`. -Postcondition: $(D empty) +Postcondition: `empty` Complexity: $(BIGOH 1) */ @@ -333,49 +393,26 @@ Complexity: $(BIGOH n) } /** -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 +Inserts `stuff` to the front of the container. `stuff` can be a +value convertible to `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) +Complexity: $(BIGOH m), where `m` is the length of `stuff` */ size_t insertFront(Stuff)(Stuff stuff) - if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + if (isInputRange!Stuff || isImplicitlyConvertible!(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; + return insertAfterNode(_root, stuff); } /// 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 + /// ditto alias stableInsert = insert; /// ditto @@ -386,7 +423,7 @@ 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) +Precondition: `!empty` Returns: The element removed. @@ -409,7 +446,7 @@ 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) +Precondition: `!empty` Complexity: $(BIGOH 1). */ @@ -423,9 +460,9 @@ Complexity: $(BIGOH 1). alias stableRemoveFront = removeFront; /** -Removes $(D howMany) values at the front or back of the +Removes `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, +do not throw if they could not remove `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 @@ -450,22 +487,22 @@ Complexity: $(BIGOH howMany * log(n)). alias stableRemoveFront = removeFront; /** -Inserts $(D stuff) after range $(D r), which must be a range +Inserts `stuff` after range `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 +the list and uses `r` as a potentially fast way to reach the last +node in the list. Ideally `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 +`stuff` can be a value convertible to `T` or a range of objects +convertible to `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). +Complexity: $(BIGOH k + m), where `k` is the number of elements in +`r` and `m` is the length of `stuff`. Example: -------------------- @@ -478,6 +515,7 @@ assert(std.algorithm.equal(sl[], ["a", "b", "c", "d", "e"])); */ size_t insertAfter(Stuff)(Range r, Stuff stuff) + if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T)) { initialize(); if (!_first) @@ -487,27 +525,25 @@ assert(std.algorithm.equal(sl[], ["a", "b", "c", "d", "e"])); } enforce(r._head); auto n = findLastNode(r._head); - SList tmp; - auto result = tmp.insertFront(stuff); - n._next = tmp._first; - return result; + return insertAfterNode(n, stuff); } /** -Similar to $(D insertAfter) above, but accepts a range bounded in +Similar to `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 +the list. For fast insertions after a specified position `r`, use $(D insertAfter(take(r, 1), stuff)). The complexity of that operation -only depends on the number of elements in $(D stuff). +only depends on the number of elements in `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). +Complexity: $(BIGOH k + m), where `k` is the number of elements in +`r` and `m` is the length of `stuff`. */ size_t insertAfter(Stuff)(Take!Range r, Stuff stuff) + if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T)) { auto orig = r.source; if (!orig._head) @@ -524,12 +560,7 @@ $(D r) and $(D m) is the length of $(D stuff). 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; + return insertAfterNode(orig._head, stuff); } /// ditto @@ -555,7 +586,7 @@ Complexity: $(BIGOH n) } /** -Removes a $(D Take!Range) from the list in linear time. +Removes a `Take!Range` from the list in linear time. Returns: A range comprehending the elements after the removed range. @@ -589,6 +620,61 @@ Complexity: $(BIGOH n) /// ditto alias stableLinearRemove = linearRemove; + +/** +Removes the first occurence of an element from the list in linear time. + +Returns: True if the element existed and was successfully removed, false otherwise. + +Params: + value = value of the node to be removed + +Complexity: $(BIGOH n) + */ + bool linearRemoveElement(T value) + { + auto n1 = findNodeByValue(_root, value); + + if (n1 && n1._next) + { + n1._next = n1._next._next; + return true; + } + + return false; + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto e = SList!int(); + auto b = e.linearRemoveElement(2); + assert(b == false); + assert(e.empty()); + auto a = SList!int(-1, 1, 2, 1, 3, 4); + b = a.linearRemoveElement(1); + assert(equal(a[], [-1, 2, 1, 3, 4])); + assert(b == true); + b = a.linearRemoveElement(-1); + assert(b == true); + assert(equal(a[], [2, 1, 3, 4])); + b = a.linearRemoveElement(1); + assert(b == true); + assert(equal(a[], [2, 3, 4])); + b = a.linearRemoveElement(2); + assert(b == true); + b = a.linearRemoveElement(20); + assert(b == false); + assert(equal(a[], [3, 4])); + b = a.linearRemoveElement(4); + assert(b == true); + assert(equal(a[], [3])); + b = a.linearRemoveElement(3); + assert(b == true); + assert(a.empty()); + a.linearRemoveElement(3); } @safe unittest @@ -793,9 +879,9 @@ Complexity: $(BIGOH n) auto s = make!(SList!int)(1, 2, 3); } +// https://issues.dlang.org/show_bug.cgi?id=5193 @safe unittest { - // 5193 static struct Data { const int val; @@ -813,17 +899,17 @@ Complexity: $(BIGOH n) assert(r.front == 1); } +// https://issues.dlang.org/show_bug.cgi?id=14920 @safe unittest { - // issue 14920 SList!int s; s.insertAfter(s[], 1); assert(s.front == 1); } +// https://issues.dlang.org/show_bug.cgi?id=15659 @safe unittest { - // issue 15659 SList!int s; s.clear(); } @@ -844,3 +930,11 @@ Complexity: $(BIGOH n) s.reverse(); assert(s[].equal([3, 2, 1])); } + +@safe unittest +{ + auto s = SList!int([4, 6, 8, 12, 16]); + auto d = s.dup; + assert(d !is s); + assert(d == s); +} diff --git a/libphobos/src/std/container/util.d b/libphobos/src/std/container/util.d index 5be9e7d..cc273a2 100644 --- a/libphobos/src/std/container/util.d +++ b/libphobos/src/std/container/util.d @@ -3,7 +3,7 @@ This module contains some common utilities used by containers. This module is a submodule of $(MREF std, container). -Source: $(PHOBOSSRC std/container/_util.d) +Source: $(PHOBOSSRC std/container/util.d) Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. @@ -32,7 +32,7 @@ if (is(T == struct) || is(T == class)) // 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. + // https://issues.dlang.org/show_bug.cgi?id=13872. static if (arguments.length == 0) { import std.range.primitives : ElementType; @@ -84,7 +84,7 @@ if (is(T == struct) || is(T == class)) assert(equal(rtb2[], "ehlo"d)); } -// Issue 8895 +// https://issues.dlang.org/show_bug.cgi?id=8895 @safe unittest { import std.algorithm.comparison : equal; @@ -162,7 +162,7 @@ if (!is(Container)) assert(equal(rbtmin[], [1, 2, 3])); } -// Issue 13872 +// https://issues.dlang.org/show_bug.cgi?id=13872 @system unittest { import std.container; diff --git a/libphobos/src/std/conv.d b/libphobos/src/std/conv.d index 3560d13..d9db9b0 100644 --- a/libphobos/src/std/conv.d +++ b/libphobos/src/std/conv.d @@ -4,12 +4,12 @@ A one-stop shop for converting values from one type to another. $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD Generic) $(TD $(LREF asOriginalType) $(LREF castFrom) - $(LREF emplace) $(LREF parse) $(LREF to) $(LREF toChars) @@ -30,9 +30,9 @@ $(TR $(TD Exceptions) $(TD $(LREF ConvException) $(LREF ConvOverflowException) )) -) +)) -Copyright: Copyright Digital Mars 2007-. +Copyright: Copyright The D Language Foundation 2007-. License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). @@ -42,7 +42,7 @@ Authors: $(HTTP digitalmars.com, Walter Bright), Adam D. Ruppe, Kenji Hara -Source: $(PHOBOSSRC std/_conv.d) +Source: $(PHOBOSSRC std/conv.d) */ module std.conv; @@ -52,6 +52,7 @@ public import std.ascii : LetterCase; import std.meta; import std.range.primitives; import std.traits; +import std.typecons : Flag, Yes, No, tuple; // Same as std.string.format, but "self-importing". // Helps reduce code and imports, particularly in static asserts. @@ -74,6 +75,13 @@ class ConvException : Exception mixin basicExceptionCtors; } +/// +@safe unittest +{ + import std.exception : assertThrown; + assertThrown!ConvException(to!int("abc")); +} + private auto convError(S, T)(S source, string fn = __FILE__, size_t ln = __LINE__) { string msg; @@ -118,7 +126,7 @@ private auto parseError(lazy string msg, string fn = __FILE__, size_t ln = __LIN 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, "\"")); + throw parseError(text("unexpected end of input when expecting \"", c, "\"")); if (source.front != c) throw parseError(text("\"", c, "\" is missing"), fn, ln); source.popFront(); @@ -129,7 +137,7 @@ private T toStr(T, S)(S src) if (isSomeString!T) { - // workaround for Bugzilla 14198 + // workaround for https://issues.dlang.org/show_bug.cgi?id=14198 static if (is(S == bool) && is(typeof({ T s = "string"; }))) { return src ? "true" : "false"; @@ -137,7 +145,8 @@ private else { import std.array : appender; - import std.format : FormatSpec, formatValue; + import std.format.spec : FormatSpec; + import std.format.write : formatValue; auto w = appender!T(); FormatSpec!(ElementEncodingType!T) f; @@ -159,7 +168,7 @@ private template isNullToStr(S, T) { enum isNullToStr = isImplicitlyConvertible!(S, T) && - (is(Unqual!S == typeof(null))) && isExactSomeString!T; + (is(immutable S == immutable typeof(null))) && isExactSomeString!T; } } @@ -175,6 +184,13 @@ class ConvOverflowException : ConvException } } +/// +@safe unittest +{ + import std.exception : assertThrown; + assertThrown!ConvOverflowException(to!ubyte(1_000_000)); +} + /** 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 @@ -290,6 +306,29 @@ template to(T) } /** + Conversion from string types to char types enforces the input + to consist of a single code point, and said code point must + fit in the target type. Otherwise, $(LREF ConvException) is thrown. + */ +@safe pure unittest +{ + import std.exception : assertThrown; + + assert(to!char("a") == 'a'); + assertThrown(to!char("ñ")); // 'ñ' does not fit into a char + assert(to!wchar("ñ") == 'ñ'); + assertThrown(to!wchar("😃")); // '😃' does not fit into a wchar + assert(to!dchar("😃") == '😃'); + + // Using wstring or dstring as source type does not affect the result + assert(to!char("a"w) == 'a'); + assert(to!char("a"d) == 'a'); + + // Two code points cannot be converted to a single one + assertThrown(to!char("ab")); +} + +/** * 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. @@ -347,21 +386,21 @@ template to(T) * 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)).) + * (`char`, `wchar`, `dchar`) character widths and any + * combination of qualifiers (mutable, `const`, or `immutable`).) * $(LI Converts array (other than strings) _to string. - * Each element is converted by calling $(D to!T).) + * Each element is converted by calling `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 + * Each element is converted by calling `to!T`.) + * $(LI Object _to string conversion calls `toString` against the object or + * returns `"null"` if the object is null.) + * $(LI Struct _to string conversion calls `toString` against the struct if * it is defined.) - * $(LI For structs that do not define $(D toString), the conversion _to string + * $(LI For structs that do not define `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 Boolean values are converted to `"true"` or `"false"`.) + * $(LI `char`, `wchar`, `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. @@ -370,9 +409,10 @@ template to(T) * 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).)) + * $(LI Pointer to string conversions convert the pointer to a `size_t` value. + * If pointer is `char*`, treat it as C-style strings. + * In that case, this function is `@system`.)) + * See $(REF formatValue, std,format) on how toString should be defined. */ @system pure unittest // @system due to cast and ptr { @@ -414,6 +454,21 @@ template to(T) assert(text(null) == "null"); } +// Test `scope` inference of parameters of `text` +@safe unittest +{ + static struct S + { + int* x; // make S a type with pointers + string toString() const scope + { + return "S"; + } + } + scope S s; + assert(text("a", s) == "aS"); +} + // Tests for issue 11390 @safe pure /+nothrow+/ unittest { @@ -427,12 +482,12 @@ template to(T) @safe pure unittest { import std.exception; - foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + static 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)) + static foreach (T; AliasSeq!(float, double, real)) { assertThrown!ConvException(to!T(" 0")); } @@ -449,6 +504,39 @@ template to(T) assertThrown!ConvException(to!AA(" [1:1]")); } +// https://issues.dlang.org/show_bug.cgi?id=20623 +@safe pure nothrow unittest +{ + // static class C + // { + // override string toString() const + // { + // return "C()"; + // } + // } + + static struct S + { + bool b; + int i; + float f; + int[] a; + int[int] aa; + S* p; + // C c; // TODO: Fails because of hasToString + + void fun() inout + { + static foreach (const idx; 0 .. this.tupleof.length) + { + { + const _ = this.tupleof[idx].to!string(); + } + } + } + } +} + /** If the source type is implicitly convertible to the target type, $(D to) simply performs the implicit conversion. @@ -480,9 +568,10 @@ if (isImplicitlyConvertible!(S, T) && return value; } +// https://issues.dlang.org/show_bug.cgi?id=9523: Allow identity enum conversion @safe pure nothrow unittest { - enum E { a } // Issue 9523 - Allow identity enum conversion + enum E { a } auto e = to!E(E.a); assert(e == E.a); } @@ -494,18 +583,18 @@ if (isImplicitlyConvertible!(S, T) && assert(a == b); } -// Tests for issue 6377 +// https://issues.dlang.org/show_bug.cgi?id=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 + static foreach (S; AliasSeq!(byte, short, int, long)) + {{ alias U = Unsigned!S; - foreach (Sint; AliasSeq!(S, const S, immutable S)) - foreach (Uint; AliasSeq!(U, const U, immutable U)) - { + static foreach (Sint; AliasSeq!(S, const S, immutable S)) + static foreach (Uint; AliasSeq!(U, const U, immutable U)) + {{ // positive overflow Uint un = Uint.max; assertThrown!ConvOverflowException(to!Sint(un), @@ -515,53 +604,53 @@ if (isImplicitlyConvertible!(S, T) && 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 + static foreach (i, S1; AliasSeq!(byte, short, int, long)) + static foreach ( S2; AliasSeq!(byte, short, int, long)[i+1..$]) + {{ 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)) - { + static foreach (Uint; AliasSeq!(U1, const U1, immutable U1)) + static 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)) - { + static foreach (Uint; AliasSeq!(U2, const U2, immutable U2)) + static 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)) - { + static foreach (Sint; AliasSeq!(S1, const S1, immutable S1)) + static 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)) - { + static foreach (Sint; AliasSeq!(S2, const S2, immutable S2)) + static foreach (Uint; AliasSeq!(U1, const U1, immutable U1)) + {{ Sint sn = -1; assertThrown!ConvOverflowException(to!Uint(sn)); - } - }(); + }} + }} } /* @@ -631,7 +720,7 @@ if (!isImplicitlyConvertible!(S, T) && /** When target type supports 'converting construction', it is used. -$(UL $(LI If target type is struct, $(D T(value)) is used.) +$(UL $(LI If target type is struct, `T(value)` is used.) $(LI If target type is class, $(D new T(value)) is used.)) */ private T toImpl(T, S)(S value) @@ -641,7 +730,7 @@ if (!isImplicitlyConvertible!(S, T) && return T(value); } -// Bugzilla 3961 +// https://issues.dlang.org/show_bug.cgi?id=3961 @safe pure unittest { struct Int @@ -670,7 +759,7 @@ if (!isImplicitlyConvertible!(S, T) && Int3 i3 = to!Int3(1); } -// Bugzilla 6808 +// https://issues.dlang.org/show_bug.cgi?id=6808 @safe pure unittest { static struct FakeBigInt @@ -835,9 +924,9 @@ if (!isImplicitlyConvertible!(S, T) && 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 + static foreach (m1; 0 .. 5) // enumerate modifiers + static foreach (m2; 0 .. 5) // ditto + {{ alias srcmod = AddModifier!m1; alias tgtmod = AddModifier!m2; @@ -871,7 +960,7 @@ if (!isImplicitlyConvertible!(S, T) && 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 } - }(); + }} } /** @@ -965,7 +1054,8 @@ if (!(isImplicitlyConvertible!(S, T) && } import std.array : appender; - import std.format : FormatSpec, formatValue; + import std.format.spec : FormatSpec; + import std.format.write : formatValue; //Default case, delegate to format //Note: we don't call toStr directly, to avoid duplicate work. @@ -982,18 +1072,18 @@ if (!(isImplicitlyConvertible!(S, T) && } } -// Bugzilla 14042 +// https://issues.dlang.org/show_bug.cgi?id=14042 @system unittest { immutable(char)* ptr = "hello".ptr; auto result = ptr.to!(char[]); } -// Bugzilla 8384 +// https://issues.dlang.org/show_bug.cgi?id=8384 @system unittest { void test1(T)(T lp, string cmp) { - foreach (e; AliasSeq!(char, wchar, dchar)) + static foreach (e; AliasSeq!(char, wchar, dchar)) { test2!(e[])(lp, cmp); test2!(const(e)[])(lp, cmp); @@ -1006,12 +1096,12 @@ if (!(isImplicitlyConvertible!(S, T) && assert(to!string(to!D(lp)) == cmp); } - foreach (e; AliasSeq!("Hello, world!", "Hello, world!"w, "Hello, world!"d)) + static 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)) + static foreach (e; AliasSeq!("", ""w, ""d)) { test1(e, ""); test1(e.ptr, ""); @@ -1024,10 +1114,11 @@ if (!(isImplicitlyConvertible!(S, T) && private T toImpl(T, S)(ref S value) if (!(isImplicitlyConvertible!(S, T) && !isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) && - !isInfinite!S && isExactSomeString!T && !isCopyable!S) + !isInfinite!S && isExactSomeString!T && !isCopyable!S && !isStaticArray!S) { import std.array : appender; - import std.format : FormatSpec, formatValue; + import std.format.spec : FormatSpec; + import std.format.write : formatValue; auto w = appender!T(); FormatSpec!(ElementEncodingType!T) f; @@ -1035,7 +1126,7 @@ if (!(isImplicitlyConvertible!(S, T) && return w.data; } -// Bugzilla 16108 +// https://issues.dlang.org/show_bug.cgi?id=16108 @system unittest { static struct A @@ -1063,8 +1154,20 @@ if (!(isImplicitlyConvertible!(S, T) && assert(to!string(b) == "B(0, false)"); } +// https://issues.dlang.org/show_bug.cgi?id=20070 +@safe unittest +{ + void writeThem(T)(ref inout(T) them) + { + assert(them.to!string == "[1, 2, 3, 4]"); + } + + const(uint)[4] vals = [ 1, 2, 3, 4 ]; + writeThem(vals); +} + /* - Check whether type $(D T) can be used in a switch statement. + Check whether type `T` can be used in a switch statement. This is useful for compile-time generation of switch case statements. */ private template isSwitchable(E) @@ -1176,14 +1279,14 @@ if (is (T == immutable) && isExactSomeString!T && is(S == enum)) import std.exception; // Conversion representing integer values with string - foreach (Int; AliasSeq!(ubyte, ushort, uint, ulong)) + static 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)) + static foreach (Int; AliasSeq!(byte, short, int, long)) { assert(to!string(Int(0)) == "0"); assert(to!string(Int(9)) == "9"); @@ -1224,7 +1327,7 @@ if (is (T == immutable) && isExactSomeString!T && is(S == enum)) a = new A; assert(to!string(a) == "an A"); - // Bug 7660 + // https://issues.dlang.org/show_bug.cgi?id=7660 class C { override string toString() const { return "C"; } } struct S { C c; alias c this; } S s; s.c = new C(); @@ -1264,12 +1367,13 @@ if (is (T == immutable) && isExactSomeString!T && is(S == enum)) // 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) + // base type is signed (https://issues.dlang.org/show_bug.cgi?id=7909) + enum EI : int { a = -1, b = 0, c = 1 } 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)) + static foreach (E; AliasSeq!(EB, EU, EI, EF, EC, ES)) { assert(to! string(E.a) == "a"c); assert(to!wstring(E.a) == "a"w); @@ -1297,23 +1401,23 @@ if (is (T == immutable) && isExactSomeString!T && is(S == enum)) assert(to!string(E.doo) == "foo"); assert(to!string(E.bar) == "bar"); - foreach (S; AliasSeq!(string, wstring, dstring, const(char[]), const(wchar[]), const(dchar[]))) - { + static 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[])) - { + static 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 @@ -1322,9 +1426,9 @@ if (isIntegral!S && isExactSomeString!T) in { - assert(radix >= 2 && radix <= 36); + assert(radix >= 2 && radix <= 36, "radix must be in range [2,36]"); } -body +do { alias EEType = Unqual!(ElementEncodingType!T); @@ -1373,7 +1477,7 @@ body @safe pure nothrow unittest { - foreach (Int; AliasSeq!(uint, ulong)) + static foreach (Int; AliasSeq!(uint, ulong)) { assert(to!string(Int(16), 16) == "10"); assert(to!string(Int(15), 2u) == "1111"); @@ -1383,7 +1487,7 @@ body assert(to!string(Int(0x1234AF), 16u, LetterCase.lower) == "1234af"); } - foreach (Int; AliasSeq!(int, long)) + static foreach (Int; AliasSeq!(int, long)) { assert(to!string(Int(-10), 10u) == "-10"); } @@ -1402,6 +1506,12 @@ if (!isImplicitlyConvertible!(S, T) && (isNumeric!S || isSomeChar!S || isBoolean!S) && (isNumeric!T || isSomeChar!T || isBoolean!T) && !is(T == enum)) { + static if (isFloatingPoint!S && isIntegral!T) + { + import std.math.traits : isNaN; + if (value.isNaN) throw new ConvException("Input was NaN"); + } + enum sSmallest = mostNegative!S; enum tSmallest = mostNegative!T; static if (sSmallest < 0) @@ -1413,7 +1523,8 @@ if (!isImplicitlyConvertible!(S, T) && } else { - static assert(tSmallest < 0); + static assert(tSmallest < 0, + "minimum value of T must be smaller than 0"); immutable good = value >= tSmallest; } if (!good) @@ -1486,9 +1597,24 @@ if (!isImplicitlyConvertible!(S, T) && } +@safe unittest +{ + import std.exception; + import std.math.traits : isNaN; + + double d = double.nan; + float f = to!float(d); + assert(f.isNaN); + assert(to!double(f).isNaN); + assertThrown!ConvException(to!int(d)); + assertThrown!ConvException(to!int(f)); + auto ex = collectException(d.to!int); + assert(ex.msg == "Input was NaN"); +} + /** Array-to-array conversion (except when target is a string type) -converts each element in turn by using $(D to). +converts each element in turn by using `to`. */ private T toImpl(T, S)(S value) if (!isImplicitlyConvertible!(S, T) && @@ -1510,7 +1636,7 @@ if (!isImplicitlyConvertible!(S, T) && import std.array : appender; auto w = appender!(E[])(); w.reserve(value.length); - foreach (i, ref e; value) + foreach (ref e; value) { w.put(to!E(e)); } @@ -1535,7 +1661,7 @@ if (!isImplicitlyConvertible!(S, T) && auto f = to!(float[][])(e); assert(f[0] == b && f[1] == b); - // Test for bug 8264 + // Test for https://issues.dlang.org/show_bug.cgi?id=8264 struct Wrap { string wrap; @@ -1543,7 +1669,7 @@ if (!isImplicitlyConvertible!(S, T) && } Wrap[] warr = to!(Wrap[])(["foo", "bar"]); // should work - // Issue 12633 + // https://issues.dlang.org/show_bug.cgi?id=12633 import std.conv : to; const s2 = ["10", "20"]; @@ -1573,7 +1699,7 @@ Associative array to associative array conversion converts each key and each value in turn. */ private T toImpl(T, S)(S value) -if (isAssociativeArray!S && +if (!isImplicitlyConvertible!(S, T) && isAssociativeArray!S && isAssociativeArray!T && !is(T == enum)) { /* This code is potentially unsafe. @@ -1602,7 +1728,9 @@ if (isAssociativeArray!S && auto b = to!(double[dstring])(a); assert(b["0"d] == 1 && b["1"d] == 2); } -@safe unittest // Bugzilla 8705, from doc + +// https://issues.dlang.org/show_bug.cgi?id=8705, from doc +@safe unittest { import std.exception; int[string][double[int[]]] a; @@ -1619,82 +1747,91 @@ if (isAssociativeArray!S && auto d = to!(immutable(short[immutable wstring])[immutable string[double[]]])(c); } -private void testIntegralToFloating(Integral, Floating)() +@safe unittest { - Integral a = 42; - auto b = to!Floating(a); - assert(a == b); - assert(a == to!Integral(b)); + import std.algorithm.comparison : equal; + import std.array : byPair; + + int[int] a; + assert(a.to!(int[int]) == a); + assert(a.to!(const(int)[int]).byPair.equal(a.byPair)); } -private void testFloatingToIntegral(Floating, Integral)() +@safe pure unittest { - import std.math : floatTraits, RealFormat; - - 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 + static void testIntegralToFloating(Integral, Floating)() { - // no go for unsigned types - assert(convFails!(Floating, Integral, ConvOverflowException)(a)); + Integral a = 42; + auto b = to!Floating(a); + assert(a == b); + assert(a == to!Integral(b)); } - // convert to the smallest integral value - a = 0.0 + Integral.min; - static if (Integral.min < 0) + static void testFloatingToIntegral(Floating, Integral)() { - a = -a; // -Integral.min not representable as an Integral + import std.math : floatTraits, RealFormat; + + bool convFails(Source, Target, E)(Source src) + { + try + cast(void) 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 + || floatTraits!Floating.realFormat == RealFormat.ieeeExtended53); + } + 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 || floatTraits!Floating.realFormat == RealFormat.ieeeExtended53); + a = 0.0 + Integral.max; + assert(to!Integral(a) == Integral.max + || Floating.sizeof <= Integral.sizeof + || floatTraits!Floating.realFormat == RealFormat.ieeeExtended53); + ++a; // no more representable as an Integral + assert(convFails!(Floating, Integral, ConvOverflowException)(a) + || Floating.sizeof <= Integral.sizeof + || floatTraits!Floating.realFormat == RealFormat.ieeeExtended53); + // 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); + } } - 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 - || floatTraits!Floating.realFormat == RealFormat.ieeeExtended53); - a = 0.0 + Integral.max; - assert(to!Integral(a) == Integral.max - || Floating.sizeof <= Integral.sizeof - || floatTraits!Floating.realFormat == RealFormat.ieeeExtended53); - ++a; // no more representable as an Integral - assert(convFails!(Floating, Integral, ConvOverflowException)(a) - || Floating.sizeof <= Integral.sizeof - || floatTraits!Floating.realFormat == RealFormat.ieeeExtended53); - // 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); @@ -1781,9 +1918,12 @@ private void testFloatingToIntegral(Floating, Integral)() foreach (T; AllNumerics) { T a = 42; - assert(to!string(a) == "42"); - assert(to!wstring(a) == "42"w); - assert(to!dstring(a) == "42"d); + string s = to!string(a); + assert(s == "42", s); + wstring ws = to!wstring(a); + assert(ws == "42"w, to!string(ws)); + dstring ds = to!dstring(a); + assert(ds == "42"d, to!string(ds)); // array test T[] b = new T[2]; b[0] = 42; @@ -1814,7 +1954,9 @@ $(UL */ private T toImpl(T, S)(S value) if (isInputRange!S && isSomeChar!(ElementEncodingType!S) && - !isExactSomeString!T && is(typeof(parse!T(value)))) + !isExactSomeString!T && is(typeof(parse!T(value))) && + // issue 20539 + !(is(T == enum) && is(typeof(value == OriginalType!T.init)) && !isSomeString!(OriginalType!T))) { scope(success) { @@ -1843,26 +1985,27 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S) && @safe pure unittest { - // Issue 6668 - ensure no collaterals thrown + // https://issues.dlang.org/show_bug.cgi?id=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)) - { + static foreach (Str; AliasSeq!(string, wstring, dstring)) + {{ Str a = "123"; assert(to!int(a) == 123); assert(to!double(a) == 123); - } + }} - // 6255 + // https://issues.dlang.org/show_bug.cgi?id=6255 auto n = to!int("FF", 16); assert(n == 255); } -// bugzilla 15800 +// https://issues.dlang.org/show_bug.cgi?id=15800 @safe unittest { import std.utf : byCodeUnit, byChar, byWchar, byDchar; @@ -1881,6 +2024,49 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S) && } /** +String, or string-like input range, to char type not directly +supported by parse parses the first dchar of the source. + +Returns: the first code point of the input range, converted + to type T. + +Throws: ConvException if the input range contains more than + a single code point, or if the code point does not + fit into a code unit of type T. +*/ +private T toImpl(T, S)(S value) +if (isSomeChar!T && !is(typeof(parse!T(value))) && + is(typeof(parse!dchar(value)))) +{ + import std.utf : encode; + + immutable dchar codepoint = parse!dchar(value); + if (!value.empty) + throw new ConvException(convFormat("Cannot convert \"%s\" to %s because it " ~ + "contains more than a single code point.", + value, T.stringof)); + T[dchar.sizeof / T.sizeof] decodedCodepoint; + if (encode(decodedCodepoint, codepoint) != 1) + throw new ConvException(convFormat("First code point '%s' of \"%s\" does not fit into a " ~ + "single %s code unit", codepoint, value, T.stringof)); + return decodedCodepoint[0]; +} + +@safe pure unittest +{ + import std.exception : assertThrown; + + assert(toImpl!wchar("a") == 'a'); + + assert(toImpl!char("a"d) == 'a'); + assert(toImpl!char("a"w) == 'a'); + assert(toImpl!wchar("a"d) == 'a'); + + assertThrown!ConvException(toImpl!wchar("ab")); + assertThrown!ConvException(toImpl!char("😃"d)); +} + +/** 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. @@ -1913,6 +2099,48 @@ if (is(T == enum) && !is(S == enum) assert(m1 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]); } +// https://issues.dlang.org/show_bug.cgi?id=20539 +@safe pure unittest +{ + import std.exception : assertNotThrown; + + // To test that the bug is fixed it is required that the struct is static, + // otherwise, the frame pointer makes the test pass even if the bug is not + // fixed. + + static struct A + { + auto opEquals(U)(U) + { + return true; + } + } + + enum ColorA + { + red = A() + } + + assertNotThrown("xxx".to!ColorA); + + // This is a guard for the future. + + struct B + { + auto opEquals(U)(U) + { + return true; + } + } + + enum ColorB + { + red = B() + } + + assertNotThrown("xxx".to!ColorB); +} + /*************************************************************** Rounded conversion from floating point to integral. @@ -1923,10 +2151,19 @@ template roundTo(Target) { Target roundTo(Source)(Source value) { - import std.math : trunc; + import core.math : abs = fabs; + import std.math.exponential : log2; + import std.math.rounding : trunc; static assert(isFloatingPoint!Source); static assert(isIntegral!Target); + + // If value >= 2 ^^ (real.mant_dig - 1), the number is an integer + // and adding 0.5 won't work, but we allready know, that we do + // not have to round anything. + if (log2(abs(value)) >= real.mant_dig - 1) + return to!Target(value); + return to!Target(trunc(value + (value < 0 ? -0.5L : 0.5L))); } } @@ -1949,7 +2186,7 @@ template roundTo(Target) { import std.exception; // boundary values - foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint)) + static 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); @@ -1958,24 +2195,61 @@ template roundTo(Target) } } +@safe unittest +{ + import std.exception; + assertThrown!ConvException(roundTo!int(float.init)); + auto ex = collectException(roundTo!int(float.init)); + assert(ex.msg == "Input was NaN"); +} + +// https://issues.dlang.org/show_bug.cgi?id=5232 +@safe pure unittest +{ + static if (real.mant_dig >= 64) + ulong maxOdd = ulong.max; + else + ulong maxOdd = (1UL << real.mant_dig) - 1; + + real r1 = maxOdd; + assert(roundTo!ulong(r1) == maxOdd); + + real r2 = maxOdd - 1; + assert(roundTo!ulong(r2) == maxOdd - 1); + + real r3 = maxOdd / 2; + assert(roundTo!ulong(r3) == maxOdd / 2); + + real r4 = maxOdd / 2 + 1; + assert(roundTo!ulong(r4) == maxOdd / 2 + 1); + + // this is only an issue on computers where real == double + long l = -((1L << double.mant_dig) - 1); + double r5 = l; + assert(roundTo!long(r5) == l); +} + /** -The $(D parse) family of functions works quite like the $(D to) +The `parse` family of functions works quite like the `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.)) + as string literals - are not accepted: use `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`. +This overload converts a 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) + doCount = the flag for deciding to report the number of consumed characters Returns: - A `bool` +$(UL + $(LI A `bool` if `doCount` is set to `No.doCount`) + $(LI A `tuple` containing a `bool` and a `size_t` if `doCount` is set to `Yes.doCount`)) Throws: A $(LREF ConvException) if the range does not represent a `bool`. @@ -1984,10 +2258,10 @@ 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) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source source) if (isInputRange!Source && isSomeChar!(ElementType!Source) && - is(Unqual!Target == bool)) + is(immutable Target == immutable bool)) { import std.ascii : toLower; @@ -2018,7 +2292,16 @@ if (isInputRange!Source && static if (isNarrowString!Source) source = cast(Source) s; - return result; + static if (doCount) + { + if (result) + return tuple!("data", "count")(result, 4); + return tuple!("data", "count")(result, 5); + } + else + { + return result; + } } } Lerr: @@ -2028,9 +2311,19 @@ Lerr: /// @safe unittest { + import std.typecons : Flag, Yes, No; auto s = "true"; bool b = parse!bool(s); assert(b); + auto s2 = "true"; + bool b2 = parse!(bool, string, No.doCount)(s2); + assert(b2); + auto s3 = "true"; + auto b3 = parse!(bool, string, Yes.doCount)(s3); + assert(b3.data && b3.count == 4); + auto s4 = "falSE"; + auto b4 = parse!(bool, string, Yes.doCount)(s4); + assert(!b4.data && b4.count == 5); } @safe unittest @@ -2073,25 +2366,37 @@ to an integral value. Params: Target = the integral type to convert to s = the lvalue of an input range + doCount = the flag for deciding to report the number of consumed characters Returns: - A number of type `Target` +$(UL + $(LI A number of type `Target` if `doCount` is set to `No.doCount`) + $(LI A `tuple` containing a number of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`)) 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) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(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; + auto v = .parse!(Select!(Target.min < 0, int, uint), Source, Yes.doCount)(s); + auto result = (() @trusted => cast (Target) v.data)(); + if (result == v.data) + { + static if (doCount) + { + return tuple!("data", "count")(result, v.count); + } + else + { + return result; + } + } throw new ConvOverflowException("Overflow in integral conversion"); } else @@ -2116,6 +2421,8 @@ if (isSomeChar!(ElementType!Source) && alias source = s; } + size_t count = 0; + if (source.empty) goto Lerr; @@ -2129,6 +2436,7 @@ if (isSomeChar!(ElementType!Source) && sign = true; goto case '+'; case '+': + ++count; source.popFront(); if (source.empty) @@ -2147,6 +2455,7 @@ if (isSomeChar!(ElementType!Source) && { Target v = cast(Target) c; + ++count; source.popFront(); while (!source.empty) @@ -2162,7 +2471,7 @@ if (isSomeChar!(ElementType!Source) && // Note: `v` can become negative here in case of parsing // the most negative value: v = cast(Target) (v * 10 + c); - + ++count; source.popFront(); } else @@ -2175,7 +2484,14 @@ if (isSomeChar!(ElementType!Source) && static if (isNarrowString!Source) s = cast(Source) source; - return v; + static if (doCount) + { + return tuple!("data", "count")(v, count); + } + else + { + return v; + } } Lerr: static if (isNarrowString!Source) @@ -2188,10 +2504,15 @@ Lerr: /// @safe pure unittest { + import std.typecons : Flag, Yes, No; string s = "123"; auto a = parse!int(s); assert(a == 123); + string s1 = "123"; + auto a1 = parse!(int, string, Yes.doCount)(s1); + assert(a1.data == 123 && a1.count == 3); + // parse only accepts lvalues static assert(!__traits(compiles, parse!int("123"))); } @@ -2200,6 +2521,7 @@ Lerr: @safe pure unittest { import std.string : tr; + import std.typecons : Flag, Yes, No; string test = "123 \t 76.14"; auto a = parse!uint(test); assert(a == 123); @@ -2209,11 +2531,22 @@ Lerr: auto b = parse!double(test); assert(b == 76.14); assert(test == ""); + + string test2 = "123 \t 76.14"; + auto a2 = parse!(uint, string, Yes.doCount)(test2); + assert(a2.data == 123 && a2.count == 3); + assert(test2 == " \t 76.14");// parse bumps string + test2 = tr(test2, " \t\n\r", "", "d"); // skip ws + assert(test2 == "76.14"); + auto b2 = parse!(double, string, Yes.doCount)(test2); + assert(b2.data == 76.14 && b2.count == 5); + assert(test2 == ""); + } @safe pure unittest { - foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + static foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) { { assert(to!Int("0") == 0); @@ -2308,74 +2641,75 @@ Lerr: @safe pure unittest { import std.exception; + + immutable string[] errors = + [ + "", + "-", + "+", + "-+", + " ", + " 0", + "0 ", + "- 0", + "1-", + "xx", + "123h", + "-+1", + "--1", + "+-1", + "++1", + ]; + + immutable string[] unsignedErrors = + [ + "+5", + "-78", + ]; + // parsing error check - foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + static 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)); - } + foreach (j, s; errors) + assertThrown!ConvException(to!Int(s)); // parse!SomeUnsigned cannot parse head sign. static if (isUnsigned!Int) { - immutable string[] errors2 = - [ - "+5", - "-78", - ]; - foreach (j, s; errors2) + foreach (j, s; unsignedErrors) assertThrown!ConvException(to!Int(s)); } } + immutable string[] positiveOverflowErrors = + [ + "128", // > byte.max + "256", // > ubyte.max + "32768", // > short.max + "65536", // > ushort.max + "2147483648", // > int.max + "4294967296", // > uint.max + "9223372036854775808", // > long.max + "18446744073709551616", // > ulong.max + ]; // 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..$]) + static foreach (i, Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + foreach (j, s; positiveOverflowErrors[i..$]) assertThrown!ConvOverflowException(to!Int(s)); } + immutable string[] negativeOverflowErrors = + [ + "-129", // < byte.min + "-32769", // < short.min + "-2147483649", // < int.min + "-9223372036854775809", // < long.min + ]; // 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..$]) + static foreach (i, Int; AliasSeq!(byte, short, int, long)) + { + foreach (j, s; negativeOverflowErrors[i..$]) assertThrown!ConvOverflowException(to!Int(s)); } } @@ -2416,9 +2750,16 @@ Lerr: 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"); }); + + assertCTFEable!({ string s = "1234abc"; assert(parse!( int, string, Yes.doCount)(s) == + tuple( 1234, 4) && s == "abc"); }); + assertCTFEable!({ string s = "-1234abc"; assert(parse!( int, string, Yes.doCount)(s) == + tuple(-1234, 5) && s == "abc"); }); + assertCTFEable!({ string s = "1234abc"; assert(parse!(uint, string, Yes.doCount)(s) == + tuple( 1234 ,4) && s == "abc"); }); } -// Issue 13931 +// https://issues.dlang.org/show_bug.cgi?id=13931 @safe pure unittest { import std.exception; @@ -2427,7 +2768,7 @@ Lerr: assertThrown!ConvOverflowException("-92233720368547758080".to!long()); } -// Issue 14396 +// https://issues.dlang.org/show_bug.cgi?id=14396 @safe pure unittest { struct StrInputRange @@ -2441,23 +2782,39 @@ Lerr: } auto input = StrInputRange("777"); assert(parse!int(input) == 777); + + auto input2 = StrInputRange("777"); + assert(parse!(int, StrInputRange, Yes.doCount)(input2) == tuple(777, 3)); +} + +// https://issues.dlang.org/show_bug.cgi?id=9621 +@safe pure unittest +{ + string s1 = "[ \"\\141\", \"\\0\", \"\\41\", \"\\418\" ]"; + assert(parse!(string[])(s1) == ["a", "\0", "!", "!8"]); + + s1 = "[ \"\\141\", \"\\0\", \"\\41\", \"\\418\" ]"; + auto len = s1.length; + assert(parse!(string[], string, Yes.doCount)(s1) == tuple(["a", "\0", "!", "!8"], len)); } /// ditto -Target parse(Target, Source)(ref Source source, uint radix) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source source, uint radix) if (isSomeChar!(ElementType!Source) && isIntegral!Target && !is(Target == enum)) in { - assert(radix >= 2 && radix <= 36); + assert(radix >= 2 && radix <= 36, "radix must be in range [2,36]"); } -body +do { import core.checkedint : mulu, addu; import std.exception : enforce; if (radix == 10) - return parse!Target(source); + { + return parse!(Target, Source, doCount)(source); + } enforce!ConvException(!source.empty, "s must not be empty in integral parse"); @@ -2474,6 +2831,8 @@ body alias s = source; } + size_t count = 0; + auto found = false; do { uint c = s.front; @@ -2499,13 +2858,30 @@ body auto nextv = v.mulu(radix, overflow).addu(c - '0', overflow); enforce!ConvOverflowException(!overflow && nextv <= Target.max, "Overflow in integral conversion"); v = cast(Target) nextv; + ++count; s.popFront(); + found = true; } while (!s.empty); + if (!found) + { + static if (isNarrowString!Source) + throw convError!(Source, Target)(cast(Source) source); + else + throw convError!(Source, Target)(source); + } + static if (isNarrowString!Source) source = cast(Source) s; - return v; + static if (doCount) + { + return tuple!("data", "count")(v, count); + } + else + { + return v; + } } @safe pure unittest @@ -2516,38 +2892,63 @@ body assert(parse!int(s = "0", i) == 0); assert(parse!int(s = "1", i) == 1); assert(parse!byte(s = "10", i) == i); + assert(parse!(int, string, Yes.doCount)(s = "0", i) == tuple(0, 1)); + assert(parse!(int, string, Yes.doCount)(s = "1", i) == tuple(1, 1)); + assert(parse!(byte, string, Yes.doCount)(s = "10", i) == tuple(i, 2)); } assert(parse!int(s = "0011001101101", 2) == 0b0011001101101); assert(parse!int(s = "765", 8) == octal!765); + assert(parse!int(s = "000135", 8) == octal!"135"); assert(parse!int(s = "fCDe", 16) == 0xfcde); - // 6609 + // https://issues.dlang.org/show_bug.cgi?id=6609 assert(parse!int(s = "-42", 10) == -42); assert(parse!ubyte(s = "ff", 16) == 0xFF); } -@safe pure unittest // bugzilla 7302 +// https://issues.dlang.org/show_bug.cgi?id=7302 +@safe pure unittest { import std.range : cycle; auto r = cycle("2A!"); auto u = parse!uint(r, 16); assert(u == 42); assert(r.front == '!'); + + auto r2 = cycle("2A!"); + auto u2 = parse!(uint, typeof(r2), Yes.doCount)(r2, 16); + assert(u2.data == 42 && u2.count == 2); + assert(r2.front == '!'); } -@safe pure unittest // bugzilla 13163 +// https://issues.dlang.org/show_bug.cgi?id=13163 +@safe pure unittest { import std.exception; foreach (s; ["fff", "123"]) assertThrown!ConvOverflowException(s.parse!ubyte(16)); } -@safe pure unittest // bugzilla 17282 +// https://issues.dlang.org/show_bug.cgi?id=17282 +@safe pure unittest { auto str = "0=\x00\x02\x55\x40&\xff\xf0\n\x00\x04\x55\x40\xff\xf0~4+10\n"; assert(parse!uint(str) == 0); + + str = "0=\x00\x02\x55\x40&\xff\xf0\n\x00\x04\x55\x40\xff\xf0~4+10\n"; + assert(parse!(uint, string, Yes.doCount)(str) == tuple(0, 1)); +} + +// https://issues.dlang.org/show_bug.cgi?id=18248 +@safe pure unittest +{ + import std.exception : assertThrown; + + auto str = ";"; + assertThrown(str.parse!uint(16)); + assertThrown(str.parse!(uint, string, Yes.doCount)(16)); } /** @@ -2556,20 +2957,25 @@ body * Params: * Target = the `enum` type to convert to * s = the lvalue of the range to _parse + * doCount = the flag for deciding to report the number of consumed characters * * Returns: - * An `enum` of type `Target` + $(UL + * $(LI An `enum` of type `Target` if `doCount` is set to `No.doCount`) + * $(LI A `tuple` containing an `enum` of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`)) * * Throws: * A $(LREF ConvException) if type `Target` does not have a member * represented by `s`. */ -Target parse(Target, Source)(ref Source s) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (isSomeString!Source && !is(Source == enum) && is(Target == enum)) { import std.algorithm.searching : startsWith; - Target result; + import std.traits : Unqual, EnumMembers; + + Unqual!Target result; size_t longest_match = 0; foreach (i, e; EnumMembers!Target) @@ -2585,7 +2991,14 @@ if (isSomeString!Source && !is(Source == enum) && if (longest_match > 0) { s = s[longest_match .. $]; - return result ; + static if (doCount) + { + return tuple!("data", "count")(result, longest_match); + } + else + { + return result; + } } throw new ConvException( @@ -2596,10 +3009,16 @@ if (isSomeString!Source && !is(Source == enum) && /// @safe unittest { + import std.typecons : Flag, Yes, No, tuple; enum EnumType : bool { a = true, b = false, c = a } auto str = "a"; assert(parse!EnumType(str) == EnumType.a); + auto str2 = "a"; + assert(parse!(EnumType, string, No.doCount)(str2) == EnumType.a); + auto str3 = "a"; + assert(parse!(EnumType, string, Yes.doCount)(str3) == tuple(EnumType.a, 1)); + } @safe unittest @@ -2613,17 +3032,22 @@ if (isSomeString!Source && !is(Source == enum) && 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)) + static 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); + assert(to!(const E)("a") == E.a); + assert(to!(immutable E)("a") == E.a); + assert(to!(shared E)("a") == E.a); + assertThrown!ConvException(to!E("d")); } } -@safe pure unittest // bugzilla 4744 +// https://issues.dlang.org/show_bug.cgi?id=4744 +@safe pure unittest { enum A { member1, member11, member111 } assert(to!A("member1" ) == A.member1 ); @@ -2631,6 +3055,10 @@ if (isSomeString!Source && !is(Source == enum) && assert(to!A("member111") == A.member111); auto s = "member1111"; assert(parse!A(s) == A.member111 && s == "1"); + auto s2 = "member1111"; + assert(parse!(A, string, No.doCount)(s2) == A.member111 && s2 == "1"); + auto s3 = "member1111"; + assert(parse!(A, string, Yes.doCount)(s3) == tuple(A.member111, 9) && s3 == "1"); } /** @@ -2639,15 +3067,19 @@ if (isSomeString!Source && !is(Source == enum) && * Params: * Target = a floating point type * source = the lvalue of the range to _parse + * doCount = the flag for deciding to report the number of consumed characters * * Returns: - * A floating point number of type `Target` + $(UL + * $(LI A floating point number of type `Target` if `doCount` is set to `No.doCount`) + * $(LI A `tuple` containing a floating point number of·type `Target` and a `size_t` + * if `doCount` is set to `Yes.doCount`)) * * Throws: - * A $(LREF ConvException) if `p` is empty, if no number could be + * A $(LREF ConvException) if `source` is empty, if no number could be * parsed, or if an overflow occurred. */ -Target parse(Target, Source)(ref Source source) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source source) if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && isFloatingPoint!Target && !is(Target == enum)) { @@ -2678,36 +3110,48 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum return new ConvException(text(msg, " for input \"", source, "\"."), fn, ln); } - enforce(!p.empty, bailOut()); + size_t count = 0; bool sign = false; switch (p.front) { case '-': sign = true; + ++count; p.popFront(); enforce(!p.empty, bailOut()); if (toLower(p.front) == 'i') goto case 'i'; break; case '+': + ++count; p.popFront(); enforce(!p.empty, bailOut()); break; case 'i': case 'I': // inf + ++count; p.popFront(); enforce(!p.empty && toUpper(p.front) == 'N', bailOut("error converting input to floating point")); + ++count; p.popFront(); enforce(!p.empty && toUpper(p.front) == 'F', bailOut("error converting input to floating point")); // skip past the last 'f' + ++count; p.popFront(); static if (isNarrowString!Source) source = cast(Source) p; - return sign ? -Target.infinity : Target.infinity; + static if (doCount) + { + return tuple!("data", "count")(sign ? -Target.infinity : Target.infinity, count); + } + else + { + return sign ? -Target.infinity : Target.infinity; + } default: {} } @@ -2715,31 +3159,53 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum bool startsWithZero = p.front == '0'; if (startsWithZero) { + ++count; p.popFront(); if (p.empty) { static if (isNarrowString!Source) source = cast(Source) p; - return sign ? -0.0 : 0.0; + static if (doCount) + { + return tuple!("data", "count")(cast (Target) (sign ? -0.0 : 0.0), count); + } + else + { + return sign ? -0.0 : 0.0; + } } isHex = p.front == 'x' || p.front == 'X'; - if (isHex) p.popFront(); + if (isHex) + { + ++count; + p.popFront(); + } } else if (toLower(p.front) == 'n') { // nan + ++count; p.popFront(); enforce(!p.empty && toUpper(p.front) == 'A', bailOut("error converting input to floating point")); + ++count; p.popFront(); enforce(!p.empty && toUpper(p.front) == 'N', bailOut("error converting input to floating point")); // skip past the last 'n' + ++count; p.popFront(); static if (isNarrowString!Source) source = cast(Source) p; - return typeof(return).nan; + static if (doCount) + { + return tuple!("data", "count")(Target.nan, count); + } + else + { + return typeof(return).nan; + } } /* @@ -2817,12 +3283,14 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum exp += expIter; } exp -= dot; + ++count; p.popFront(); if (p.empty) break; i = p.front; if (i == '_') { + ++count; p.popFront(); if (p.empty) break; @@ -2831,6 +3299,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum } if (i == '.' && !dot) { + ++count; p.popFront(); dot += expIter; } @@ -2855,13 +3324,15 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum char sexp = 0; int e = 0; + ++count; p.popFront(); enforce(!p.empty, new ConvException("Unexpected end of input")); switch (p.front) { case '-': sexp++; goto case; - case '+': p.popFront(); + case '+': ++count; + p.popFront(); break; default: {} } @@ -2872,6 +3343,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum { e = e * 10 + p.front - '0'; } + ++count; p.popFront(); sawDigits = true; } @@ -2884,7 +3356,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum ldval = ldval * msscale + lsdec; if (isHex) { - import std.math : ldexp; + import core.math : ldexp; // Exponent is power of 2, not power of 10 ldval = ldexp(ldval,exp); @@ -2922,22 +3394,54 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum static if (isNarrowString!Source) source = cast(Source) p; - return sign ? -ldval : ldval; + static if (doCount) + { + return tuple!("data", "count")(cast (Target) (sign ? -ldval : ldval), count); + } + else + { + return cast (Target) (sign ? -ldval : ldval); + } } /// @safe unittest { - import std.math : approxEqual; + import std.math.operations : isClose; + import std.math.traits : isNaN, isInfinity; + import std.typecons : Flag, Yes, No; auto str = "123.456"; - - assert(parse!double(str).approxEqual(123.456)); + assert(parse!double(str).isClose(123.456)); + auto str2 = "123.456"; + assert(parse!(double, string, No.doCount)(str2).isClose(123.456)); + auto str3 = "123.456"; + auto r = parse!(double, string, Yes.doCount)(str3); + assert(r.data.isClose(123.456)); + assert(r.count == 7); + auto str4 = "-123.456"; + r = parse!(double, string, Yes.doCount)(str4); + assert(r.data.isClose(-123.456)); + assert(r.count == 8); + auto str5 = "+123.456"; + r = parse!(double, string, Yes.doCount)(str5); + assert(r.data.isClose(123.456)); + assert(r.count == 8); + auto str6 = "inf0"; + r = parse!(double, string, Yes.doCount)(str6); + assert(isInfinity(r.data) && r.count == 3 && str6 == "0"); + auto str7 = "-0"; + auto r2 = parse!(float, string, Yes.doCount)(str7); + assert(r2.data.isClose(0.0) && r2.count == 2); + auto str8 = "nan"; + auto r3 = parse!(real, string, Yes.doCount)(str8); + assert(isNaN(r3.data) && r3.count == 3); } @safe unittest { import std.exception; - import std.math : isNaN, fabs; + import std.math.traits : isNaN, isInfinity; + import std.math.algebraic : fabs; // Compare reals with given precision bool feq(in real rx, in real ry, in real precision = 0.000001L) @@ -2960,14 +3464,14 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum return f; } - foreach (Float; AliasSeq!(float, double, real)) + static 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("123e-2") == Literal!Float(123e-2L)); assert(to!Float("123.") == Literal!Float(123.0)); assert(to!Float(".375") == Literal!Float(.375)); @@ -2996,6 +3500,16 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum assert(to!string(d) == to!string(1.79769e+308)); assert(to!string(d) == to!string(double.max)); + auto z = real.max / 2L; + static assert(is(typeof(z) == real)); + assert(!isNaN(z)); + assert(!isInfinity(z)); + string a = to!string(z); + real b = to!real(a); + string c = to!string(b); + + assert(c == a, "\n" ~ c ~ "\n" ~ a); + assert(to!string(to!real(to!string(real.max / 2L))) == to!string(real.max / 2L)); // min and max @@ -3017,6 +3531,26 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum } r = to!real(to!string(real.max)); assert(to!string(r) == to!string(real.max)); + + real pi = 3.1415926535897932384626433832795028841971693993751L; + string fullPrecision = "3.1415926535897932384626433832795028841971693993751"; + assert(feq(parse!real(fullPrecision), pi, 2*real.epsilon)); + string fullPrecision2 = "3.1415926535897932384626433832795028841971693993751"; + assert(feq(parse!(real, string, No.doCount)(fullPrecision2), pi, 2*real.epsilon)); + string fullPrecision3= "3.1415926535897932384626433832795028841971693993751"; + auto len = fullPrecision3.length; + auto res = parse!(real, string, Yes.doCount)(fullPrecision3); + assert(feq(res.data, pi, 2*real.epsilon)); + assert(res.count == len); + + real x = 0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252L; + string full = "0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252"; + assert(parse!real(full) == x); + string full2 = "0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252"; + assert(parse!(real, string, No.doCount)(full2) == x); + string full3 = "0x1.FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFAAFAAFAFAFAFAFAFAFAP-252"; + auto len2 = full3.length; + assert(parse!(real, string, Yes.doCount)(full3) == tuple(x, len2)); } // Tests for the double implementation @@ -3037,6 +3571,10 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0xA_BCDE_F012_3456); assert(strtod("0x1ABCDEF0123456p10", null) == x); + s = "0x1A_BCDE_F012_3456p10"; + auto len = s.length; + assert(parse!(real, string, Yes.doCount)(s) == tuple(x, len)); + //Should be parsed exactly: 10 bit mantissa s = "0x3FFp10"; x = parse!real(s); @@ -3048,7 +3586,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum //60 bit mantissa, round up s = "0xFFF_FFFF_FFFF_FFFFp10"; x = parse!real(s); - assert(approxEqual(x, 0xFFF_FFFF_FFFF_FFFFp10)); + assert(isClose(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); @@ -3056,7 +3594,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum //60 bit mantissa, round down s = "0xFFF_FFFF_FFFF_FF90p10"; x = parse!real(s); - assert(approxEqual(x, 0xFFF_FFFF_FFFF_FF90p10)); + assert(isClose(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); @@ -3064,7 +3602,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum //61 bit mantissa, round up 2 s = "0x1F0F_FFFF_FFFF_FFFFp10"; x = parse!real(s); - assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FFFFp10)); + assert(isClose(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); @@ -3072,7 +3610,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum //61 bit mantissa, round down 2 s = "0x1F0F_FFFF_FFFF_FF10p10"; x = parse!real(s); - assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FF10p10)); + assert(isClose(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); @@ -3092,6 +3630,10 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum x = parse!real(s); assert(x == 0); assert(strtod("0x1FFFFFFFFFFFFFp-2000", null) == x); + + s = "0x1FFFFFFFFFFFFFp-2000"; + len = s.length; + assert(parse!(real, string, Yes.doCount)(s) == tuple(x, len)); } } @@ -3128,8 +3670,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum int i; static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple) - // Our parser is currently limited to ieeeExtended precision - enum s = "0x1.FFFFFFFFFFFFFFFEp-16382"; + enum s = "0x1.FFFFFFFFFFFFFFFFFFFFFFFFFFFFp-16382"; else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended) enum s = "0x1.FFFFFFFFFFFFFFFEp-16382"; else static if (floatTraits!real.realFormat == RealFormat.ieeeExtended53) @@ -3173,33 +3714,41 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum { import std.exception; - // Bugzilla 4959 + // https://issues.dlang.org/show_bug.cgi?id=4959 { auto s = "0 "; auto x = parse!double(s); assert(s == " "); assert(x == 0.0); } + { + auto s = "0 "; + auto x = parse!(double, string, Yes.doCount)(s); + assert(s == " "); + assert(x == tuple(0.0, 1)); + } - // Bugzilla 3369 + // https://issues.dlang.org/show_bug.cgi?id=3369 assert(to!float("inf") == float.infinity); assert(to!float("-inf") == -float.infinity); - // Bugzilla 6160 + // https://issues.dlang.org/show_bug.cgi?id=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 + // https://issues.dlang.org/show_bug.cgi?id=6258 assertThrown!ConvException(to!real("-")); assertThrown!ConvException(to!real("in")); - // Bugzilla 7055 + // https://issues.dlang.org/show_bug.cgi?id=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"]; + auto ssOK = ["1.", "1.1.1", "1.e5", "2e1e", "2a", "2e1_1", "3.4_", + "inf", "-inf", "infa", "-infa", "inf2e2", "-inf2e2", + "nan", "-NAN", "+NaN", "-nAna", "NAn2e2", "-naN2e2"]; + auto ssKO = ["", " ", "2e", "2e+", "2e-", "2ee", "2e++1", "2e--1", "2e_1", + "+inf", "-in", "I", "+N", "-NaD", "0x3.F"]; foreach (s; ssOK) parse!double(s); foreach (s; ssKO) @@ -3212,53 +3761,74 @@ 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) + doCount = the flag for deciding to report the number of consumed characters Returns: - A character of type `Target` +$(UL + $(LI A character of type `Target` if `doCount` is set to `No.doCount`) + $(LI A `tuple` containing a character of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`)) Throws: A $(LREF ConvException) if the range is empty. */ -Target parse(Target, Source)(ref Source s) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (isSomeString!Source && !is(Source == enum) && - staticIndexOf!(Unqual!Target, dchar, Unqual!(ElementEncodingType!Source)) >= 0) + staticIndexOf!(immutable Target, immutable dchar, immutable ElementEncodingType!Source) >= 0) { if (s.empty) throw convError!(Source, Target)(s); - static if (is(Unqual!Target == dchar)) + static if (is(immutable Target == immutable dchar)) { Target result = s.front; s.popFront(); - return result; + static if (doCount) + { + return tuple!("data", "count")(result, 1); + } + else + { + return result; + } + } else { // Special case: okay so parse a Char off a Char[] Target result = s[0]; s = s[1 .. $]; - return result; + static if (doCount) + { + return tuple!("data", "count")(result, 1); + } + else + { + return result; + } } } @safe pure unittest { - foreach (Str; AliasSeq!(string, wstring, dstring)) + static foreach (Str; AliasSeq!(string, wstring, dstring)) { - foreach (Char; AliasSeq!(char, wchar, dchar)) - { - static if (is(Unqual!Char == dchar) || + static foreach (Char; AliasSeq!(char, wchar, dchar)) + {{ + static if (is(immutable Char == immutable dchar) || Char.sizeof == ElementEncodingType!Str.sizeof) { Str s = "aaa"; assert(parse!Char(s) == 'a'); assert(s == "aa"); + assert(parse!(Char, typeof(s), No.doCount)(s) == 'a'); + assert(s == "a"); + assert(parse!(Char, typeof(s), Yes.doCount)(s) == tuple('a', 1) && s == ""); } - } + }} } } /// ditto -Target parse(Target, Source)(ref Source s) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (!isSomeString!Source && isInputRange!Source && isSomeChar!(ElementType!Source) && isSomeChar!Target && Target.sizeof >= ElementType!Source.sizeof && !is(Target == enum)) { @@ -3266,16 +3836,30 @@ if (!isSomeString!Source && isInputRange!Source && isSomeChar!(ElementType!Sourc throw convError!(Source, Target)(s); Target result = s.front; s.popFront(); - return result; + static if (doCount) + { + return tuple!("data", "count")(result, 1); + } + else + { + return result; + } } /// @safe pure unittest { + import std.typecons : Flag, Yes, No; auto s = "Hello, World!"; char first = parse!char(s); assert(first == 'H'); assert(s == "ello, World!"); + char second = parse!(char, string, No.doCount)(s); + assert(second == 'e'); + assert(s == "llo, World!"); + auto third = parse!(char, string, Yes.doCount)(s); + assert(third.data == 'l' && third.count == 1); + assert(s == "lo, World!"); } @@ -3298,8 +3882,13 @@ if (!isSomeString!Source && isInputRange!Source && isSomeChar!(ElementType!Sourc assert(parse!bool(f) == false); assert(f == " killer whale"d); + f = "False killer whale"d; + assert(parse!(bool, dstring, Yes.doCount)(f) == tuple(false, 5)); + assert(f == " killer whale"d); + auto m = "maybe"; assertThrown!ConvException(parse!bool(m)); + assertThrown!ConvException(parse!(bool, string, Yes.doCount)(m)); assert(m == "maybe"); // m shouldn't change on failure auto s = "true"; @@ -3314,17 +3903,20 @@ 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) + doCount = the flag for deciding to report the number of consumed characters Returns: - `null` +$(UL + $(LI `null` if `doCount` is set to `No.doCount`) + $(LI A `tuple` containing `null` and a `size_t` if `doCount` is set to `Yes.doCount`)) Throws: A $(LREF ConvException) if the range doesn't represent `null`. */ -Target parse(Target, Source)(ref Source s) +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (isInputRange!Source && isSomeChar!(ElementType!Source) && - is(Unqual!Target == typeof(null))) + is(immutable Target == immutable typeof(null))) { import std.ascii : toLower; foreach (c; "null") @@ -3333,13 +3925,21 @@ if (isInputRange!Source && throw parseError("null should be case-insensitive 'null'"); s.popFront(); } - return null; + static if (doCount) + { + return tuple!("data", "count")(null, 4); + } + else + { + return null; + } } /// @safe pure unittest { import std.exception : assertThrown; + import std.typecons : Flag, Yes, No; alias NullType = typeof(null); auto s1 = "null"; @@ -3350,8 +3950,14 @@ if (isInputRange!Source && assert(parse!NullType(s2) is null); assert(s2 == ""); + auto s3 = "nuLlNULl"; + assert(parse!(NullType, string, No.doCount)(s3) is null); + auto r = parse!(NullType, string, Yes.doCount)(s3); + assert(r.data is null && r.count == 4); + auto m = "maybe"; assertThrown!ConvException(parse!NullType(m)); + assertThrown!ConvException(parse!(NullType, string, Yes.doCount)(m)); assert(m == "maybe"); // m shouldn't change on failure auto s = "NULL"; @@ -3359,7 +3965,7 @@ if (isInputRange!Source && } //Used internally by parse Array/AA, to remove ascii whites -package void skipWS(R)(ref R r) +package auto skipWS(R, Flag!"doCount" doCount = No.doCount)(ref R r) { import std.ascii : isWhite; static if (isSomeString!R) @@ -3370,34 +3976,58 @@ package void skipWS(R)(ref R r) if (!isWhite(c)) { r = r[i .. $]; - return; + static if (doCount) + { + return i; + } + else + { + return; + } } } + auto len = r.length; r = r[0 .. 0]; //Empty string with correct type. - return; + static if (doCount) + { + return len; + } + else + { + return; + } } else { - for (; !r.empty && isWhite(r.front); r.popFront()) - {} + size_t i = 0; + for (; !r.empty && isWhite(r.front); r.popFront(), ++i) + { } + static if (doCount) + { + return i; + } } } /** * 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. + * '[')), right bracket (default `']'`), and element separator (by + * default `','`). 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 + * doCount = the flag for deciding to report the number of consumed characters * * Returns: - * An array of type `Target` + $(UL + * $(LI An array of type `Target` if `doCount` is set to `No.doCount`) + * $(LI A `tuple` containing an array of type `Target` and a `size_t` if `doCount` is set to `Yes.doCount`)) */ -Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',') +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s, dchar lbracket = '[', + dchar rbracket = ']', dchar comma = ',') if (isSomeString!Source && !is(Source == enum) && isDynamicArray!Target && !is(Target == enum)) { @@ -3406,33 +4036,48 @@ if (isSomeString!Source && !is(Source == enum) && auto result = appender!Target(); parseCheck!s(lbracket); - skipWS(s); + size_t count = 1 + skipWS!(Source, Yes.doCount)(s); if (s.empty) throw convError!(Source, Target)(s); if (s.front == rbracket) { s.popFront(); - return result.data; + static if (doCount) + { + return tuple!("data", "count")(result.data, ++count); + } + else + { + return result.data; + } } - for (;; s.popFront(), skipWS(s)) + for (;; s.popFront(), count += 1 + skipWS!(Source, Yes.doCount)(s)) { if (!s.empty && s.front == rbracket) break; - result ~= parseElement!(ElementType!Target)(s); - skipWS(s); + auto r = parseElement!(WideElementType!Target, Source, Yes.doCount)(s); + result ~= r.data; + count += r.count + skipWS!(Source, Yes.doCount)(s); if (s.empty) throw convError!(Source, Target)(s); if (s.front != comma) break; } parseCheck!s(rbracket); - - return result.data; + static if (doCount) + { + return tuple!("data", "count")(result.data, ++count); + } + else + { + return result.data; + } } /// @safe pure unittest { + import std.typecons : Flag, Yes, No; auto s1 = `[['h', 'e', 'l', 'l', 'o'], "world"]`; auto a1 = parse!(string[])(s1); assert(a1 == ["hello", "world"]); @@ -3440,10 +4085,18 @@ if (isSomeString!Source && !is(Source == enum) && auto s2 = `["aaa", "bbb", "ccc"]`; auto a2 = parse!(string[])(s2); assert(a2 == ["aaa", "bbb", "ccc"]); + + auto s3 = `[['h', 'e', 'l', 'l', 'o'], "world"]`; + auto len3 = s3.length; + auto a3 = parse!(string[], string, Yes.doCount)(s3); + assert(a3.data == ["hello", "world"]); + assert(a3.count == len3); } -@safe unittest // Bugzilla 9615 +// https://issues.dlang.org/show_bug.cgi?id=9615 +@safe unittest { + import std.typecons : Flag, Yes, No, tuple; string s0 = "[1,2, ]"; string s1 = "[1,2, \t\v\r\n]"; string s2 = "[1,2]"; @@ -3451,16 +4104,38 @@ if (isSomeString!Source && !is(Source == enum) && assert(s1.parse!(int[]) == [1,2]); assert(s2.parse!(int[]) == [1,2]); + s0 = "[1,2, ]"; + auto len0 = s0.length; + s1 = "[1,2, \t\v\r\n]"; + auto len1 = s1.length; + s2 = "[1,2]"; + auto len2 = s2.length; + assert(s0.parse!(int[], string, Yes.doCount) == tuple([1,2], len0)); + assert(s1.parse!(int[], string, Yes.doCount) == tuple([1,2], len1)); + assert(s2.parse!(int[], string, Yes.doCount) == tuple([1,2], len2)); + string s3 = `["a","b",]`; string s4 = `["a","b"]`; assert(s3.parse!(string[]) == ["a","b"]); assert(s4.parse!(string[]) == ["a","b"]); + s3 = `["a","b",]`; + auto len3 = s3.length; + assert(s3.parse!(string[], string, Yes.doCount) == tuple(["a","b"], len3)); + + s3 = `[ ]`; + assert(tuple([], s3.length) == s3.parse!(string[], string, Yes.doCount)); + import std.exception : assertThrown; string s5 = "[,]"; string s6 = "[, \t,]"; assertThrown!ConvException(parse!(string[])(s5)); assertThrown!ConvException(parse!(int[])(s6)); + + s5 = "[,]"; + s6 = "[,·\t,]"; + assertThrown!ConvException(parse!(string[], string, Yes.doCount)(s5)); + assertThrown!ConvException(parse!(string[], string, Yes.doCount)(s6)); } @safe unittest @@ -3491,15 +4166,20 @@ if (isSomeString!Source && !is(Source == enum) && @safe pure unittest { import std.exception; + import std.typecons : Flag, Yes, No; //Check proper failure auto s = "[ 1 , 2 , 3 ]"; + auto s2 = s.save; foreach (i ; 0 .. s.length-1) { auto ss = s[0 .. i]; assertThrown!ConvException(parse!(int[])(ss)); + assertThrown!ConvException(parse!(int[], string, Yes.doCount)(ss)); } int[] arr = parse!(int[])(s); + auto arr2 = parse!(int[], string, Yes.doCount)(s2); + arr = arr2.data; } @safe pure unittest @@ -3526,12 +4206,15 @@ if (isSomeString!Source && !is(Source == enum) && "unicode 日 sun", "very long 日 sun" ]; + string s3 = s1.save; assert(s2 == parse!(string[])(s1)); assert(s1.empty); + assert(tuple(s2, s3.length) == parse!(string[], string, Yes.doCount)(s3)); } /// ditto -Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',') +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s, dchar lbracket = '[', + dchar rbracket = ']', dchar comma = ',') if (isExactSomeString!Source && isStaticArray!Target && !is(Target == enum)) { @@ -3541,7 +4224,7 @@ if (isExactSomeString!Source && Target result = void; parseCheck!s(lbracket); - skipWS(s); + size_t count = 1 + skipWS!(Source, Yes.doCount)(s); if (s.empty) throw convError!(Source, Target)(s); if (s.front == rbracket) @@ -3551,15 +4234,23 @@ if (isExactSomeString!Source && else { s.popFront(); - return result; + static if (doCount) + { + return tuple!("data", "count")(result, ++count); + } + else + { + return result; + } } } - for (size_t i = 0; ; s.popFront(), skipWS(s)) + for (size_t i = 0; ; s.popFront(), count += 1 + skipWS!(Source, Yes.doCount)(s)) { if (i == result.length) goto Lmanyerr; - result[i++] = parseElement!(ElementType!Target)(s); - skipWS(s); + auto r = parseElement!(ElementType!Target, Source, Yes.doCount)(s); + result[i++] = r.data; + count += r.count + skipWS!(Source, Yes.doCount)(s); if (s.empty) throw convError!(Source, Target)(s); if (s.front != comma) @@ -3570,8 +4261,15 @@ if (isExactSomeString!Source && } } parseCheck!s(rbracket); + static if (doCount) + { + return tuple!("data", "count")(result, ++count); + } + else + { + return result; + } - return result; Lmanyerr: throw parseError(text("Too many elements in input, ", result.length, " elements expected.")); @@ -3587,22 +4285,28 @@ Lfewerr: auto s1 = "[1,2,3,4]"; auto sa1 = parse!(int[4])(s1); assert(sa1 == [1,2,3,4]); + s1 = "[1,2,3,4]"; + assert(tuple([1,2,3,4], s1.length) == parse!(int[4], string, Yes.doCount)(s1)); auto s2 = "[[1],[2,3],[4]]"; auto sa2 = parse!(int[][3])(s2); assert(sa2 == [[1],[2,3],[4]]); + s2 = "[[1],[2,3],[4]]"; + assert(tuple([[1],[2,3],[4]], s2.length) == parse!(int[][3], string, Yes.doCount)(s2)); auto s3 = "[1,2,3]"; assertThrown!ConvException(parse!(int[4])(s3)); + assertThrown!ConvException(parse!(int[4], string, Yes.doCount)(s3)); auto s4 = "[1,2,3,4,5]"; assertThrown!ConvException(parse!(int[4])(s4)); + assertThrown!ConvException(parse!(int[4], string, Yes.doCount)(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 ',')). + * '[')), right bracket (default `']'`), key-value separator (default $(D + * ':')), and element seprator (by default `','`). * * Params: * s = the string to parse @@ -3610,11 +4314,15 @@ Lfewerr: * 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 + * doCount = the flag for deciding to report the number of consumed characters * * Returns: - * An associative array of type `Target` + $(UL + * $(LI An associative array of type `Target` if `doCount` is set to `No.doCount`) + * $(LI A `tuple` containing an associative array of type `Target` and a `size_t` + * if `doCount` is set to `Yes.doCount`)) */ -Target parse(Target, Source)(ref Source s, dchar lbracket = '[', +auto parse(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar keyval = ':', dchar comma = ',') if (isSomeString!Source && !is(Source == enum) && isAssociativeArray!Target && !is(Target == enum)) @@ -3625,47 +4333,75 @@ if (isSomeString!Source && !is(Source == enum) && Target result; parseCheck!s(lbracket); - skipWS(s); + size_t count = 1 + skipWS!(Source, Yes.doCount)(s); if (s.empty) throw convError!(Source, Target)(s); if (s.front == rbracket) { s.popFront(); - return result; + static if (doCount) + { + return tuple!("data", "count")(result, ++count); + } + else + { + return result; + } } - for (;; s.popFront(), skipWS(s)) + for (;; s.popFront(), count += 1 + skipWS!(Source, Yes.doCount)(s)) { - auto key = parseElement!KeyType(s); - skipWS(s); + auto key = parseElement!(KeyType, Source, Yes.doCount)(s); + count += key.count + skipWS!(Source, Yes.doCount)(s); parseCheck!s(keyval); - skipWS(s); - auto val = parseElement!ValType(s); - skipWS(s); - result[key] = val; + count += 1 + skipWS!(Source, Yes.doCount)(s); + auto val = parseElement!(ValType, Source, Yes.doCount)(s); + count += val.count + skipWS!(Source, Yes.doCount)(s); + result[key.data] = val.data; if (s.empty) throw convError!(Source, Target)(s); if (s.front != comma) break; } parseCheck!s(rbracket); - - return result; + static if (doCount) + { + return tuple!("data", "count")(result, ++count); + } + else + { + return result; + } } /// @safe pure unittest { + import std.typecons : Flag, Yes, No, tuple; + import std.range.primitives : save; + import std.array : assocArray; auto s1 = "[1:10, 2:20, 3:30]"; + auto copyS1 = s1.save; auto aa1 = parse!(int[int])(s1); assert(aa1 == [1:10, 2:20, 3:30]); + assert(tuple([1:10, 2:20, 3:30], copyS1.length) == parse!(int[int], string, Yes.doCount)(copyS1)); auto s2 = `["aaa":10, "bbb":20, "ccc":30]`; + auto copyS2 = s2.save; auto aa2 = parse!(int[string])(s2); assert(aa2 == ["aaa":10, "bbb":20, "ccc":30]); + assert(tuple(["aaa":10, "bbb":20, "ccc":30], copyS2.length) == + parse!(int[string], string, Yes.doCount)(copyS2)); auto s3 = `["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]`; + auto copyS3 = s3.save; auto aa3 = parse!(int[][string])(s3); assert(aa3 == ["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]); + assert(tuple(["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]], copyS3.length) == + parse!(int[][string], string, Yes.doCount)(copyS3)); + + auto s4 = `[]`; + int[int] emptyAA; + assert(tuple(emptyAA, s4.length) == parse!(int[int], string, Yes.doCount)(s4)); } @safe pure unittest @@ -3674,21 +4410,28 @@ if (isSomeString!Source && !is(Source == enum) && //Check proper failure auto s = "[1:10, 2:20, 3:30]"; + auto s2 = s.save; foreach (i ; 0 .. s.length-1) { auto ss = s[0 .. i]; assertThrown!ConvException(parse!(int[int])(ss)); + assertThrown!ConvException(parse!(int[int], string, Yes.doCount)(ss)); } int[int] aa = parse!(int[int])(s); + auto aa2 = parse!(int[int], string, Yes.doCount)(s2); + aa = aa2[0]; + } -private dchar parseEscape(Source)(ref Source s) +private auto parseEscape(Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (isInputRange!Source && isSomeChar!(ElementType!Source)) { parseCheck!s('\\'); + size_t count = 1; if (s.empty) throw parseError("Unterminated escape sequence"); + // consumes 1 element from Source dchar getHexDigit()(ref Source s_ = s) // workaround { import std.ascii : isAlpha, isHexDigit; @@ -3703,13 +4446,71 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source)) return isAlpha(c) ? ((c & ~0x20) - ('A' - 10)) : c - '0'; } + // We need to do octals separate, because they need a lookahead to find out, + // where the escape sequence ends. + auto first = s.front; + if (first >= '0' && first <= '7') + { + dchar c1 = s.front; + ++count; + s.popFront(); + if (s.empty) + { + static if (doCount) + { + return tuple!("data", "count")(cast (dchar) (c1 - '0'), count); + } + else + { + return cast (dchar) (c1 - '0'); + } + } + dchar c2 = s.front; + if (c2 < '0' || c2 > '7') + { + static if (doCount) + { + return tuple!("data", "count")(cast (dchar)(c1 - '0'), count); + } + else + { + return cast (dchar)(c1 - '0'); + } + } + ++count; + s.popFront(); + dchar c3 = s.front; + if (c3 < '0' || c3 > '7') + { + static if (doCount) + { + return tuple!("data", "count")(cast (dchar) (8 * (c1 - '0') + (c2 - '0')), count); + } + else + { + return cast (dchar) (8 * (c1 - '0') + (c2 - '0')); + } + } + ++count; + s.popFront(); + if (c1 > '3') + throw parseError("Octal sequence is larger than \\377"); + static if (doCount) + { + return tuple!("data", "count")(cast (dchar) (64 * (c1 - '0') + 8 * (c2 - '0') + (c3 - '0')), count); + } + else + { + return cast (dchar) (64 * (c1 - '0') + 8 * (c2 - '0') + (c3 - '0')); + } + } + dchar result; - switch (s.front) + switch (first) { case '"': result = '\"'; break; case '\'': result = '\''; break; - case '0': result = '\0'; break; case '?': result = '\?'; break; case '\\': result = '\\'; break; case 'a': result = '\a'; break; @@ -3722,12 +4523,14 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source)) case 'x': result = getHexDigit() << 4; result |= getHexDigit(); + count += 2; break; case 'u': result = getHexDigit() << 12; result |= getHexDigit() << 8; result |= getHexDigit() << 4; result |= getHexDigit(); + count += 4; break; case 'U': result = getHexDigit() << 28; @@ -3738,6 +4541,7 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source)) result |= getHexDigit() << 8; result |= getHexDigit() << 4; result |= getHexDigit(); + count += 8; break; default: throw parseError("Unknown escape character " ~ to!string(s.front)); @@ -3747,31 +4551,44 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source)) s.popFront(); - return result; + static if (doCount) + { + return tuple!("data", "count")(cast (dchar) result, ++count); + } + else + { + return cast (dchar) result; + } } @safe pure unittest { string[] s1 = [ `\"`, `\'`, `\?`, `\\`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`, //Normal escapes - //`\141`, //@@@9621@@@ Octal escapes. + `\141`, `\x61`, - `\u65E5`, `\U00012456` - //`\&`, `\"`, //@@@9621@@@ Named Character Entities. + `\u65E5`, `\U00012456`, + // https://issues.dlang.org/show_bug.cgi?id=9621 (Named Character Entities) + //`\&`, `\"`, ]; + string[] copyS1 = s1 ~ s1[0 .. 0]; const(dchar)[] s2 = [ '\"', '\'', '\?', '\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v', //Normal escapes - //'\141', //@@@9621@@@ Octal escapes. + '\141', '\x61', - '\u65E5', '\U00012456' - //'\&', '\"', //@@@9621@@@ Named Character Entities. + '\u65E5', '\U00012456', + // https://issues.dlang.org/show_bug.cgi?id=9621 (Named Character Entities) + //'\&', '\"', ]; foreach (i ; 0 .. s1.length) { assert(s2[i] == parseEscape(s1[i])); assert(s1[i].empty); + + assert(tuple(s2[i], copyS1[i].length) == parseEscape!(string, Yes.doCount)(copyS1[i])); + assert(copyS1[i].empty); } } @@ -3788,16 +4605,20 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source)) `\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 + `\777`, //Octal is larger than a byte + `\80`, //Wrong digit at beginning of octal `\u123`, //Premature hex end `\U123123` //Premature hex end ]; foreach (s ; ss) + { assertThrown!ConvException(parseEscape(s)); + assertThrown!ConvException(parseEscape!(string, Yes.doCount)(s)); + } } // Undocumented -Target parseElement(Target, Source)(ref Source s) +auto parseElement(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && isExactSomeString!Target) { @@ -3808,15 +4629,26 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum if (s.empty) throw convError!(Source, Target)(s); if (s.front == '[') - return parse!Target(s); + { + return parse!(Target, Source, doCount)(s); + } parseCheck!s('\"'); + size_t count = 1; if (s.empty) throw convError!(Source, Target)(s); if (s.front == '\"') { s.popFront(); - return result.data; + static if (doCount) + { + return tuple!("data", "count")(result.data, ++count); + } + else + { + return result.data; + } + } while (true) { @@ -3826,29 +4658,41 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum { case '\"': s.popFront(); - return result.data; + static if (doCount) + { + return tuple!("data", "count")(result.data, ++count); + } + else + { + return result.data; + } case '\\': - result.put(parseEscape(s)); + auto r = parseEscape!(typeof(s), Yes.doCount)(s); + result.put(r[0]); + count += r[1]; break; default: result.put(s.front); + ++count; s.popFront(); break; } } - assert(0); + assert(false, "Unexpected fallthrough"); } // ditto -Target parseElement(Target, Source)(ref Source s) +auto parseElement(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && - isSomeChar!Target && !is(Target == enum)) + is(CharTypeOf!Target == dchar) && !is(Target == enum)) { - Target c; + Unqual!Target c; parseCheck!s('\''); + size_t count = 1; if (s.empty) throw convError!(Source, Target)(s); + ++count; // for the following if-else sequence if (s.front != '\\') { c = s.front; @@ -3857,16 +4701,33 @@ if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum else c = parseEscape(s); parseCheck!s('\''); - - return c; + static if (doCount) + { + return tuple!("data", "count")(c, ++count); + } + else + { + return c; + } } // ditto -Target parseElement(Target, Source)(ref Source s) +auto parseElement(Target, Source, Flag!"doCount" doCount = No.doCount)(ref Source s) if (isInputRange!Source && isSomeChar!(ElementType!Source) && !isSomeString!Target && !isSomeChar!Target) { - return parse!Target(s); + return parse!(Target, Source, doCount)(s); +} + +// Use this when parsing a type that will ultimately be appended to a +// string. +package template WideElementType(T) +{ + alias E = ElementType!T; + static if (isSomeChar!E) + alias WideElementType = dchar; + else + alias WideElementType = E; } @@ -3925,26 +4786,47 @@ private S textImpl(S, U...)(U args) else { import std.array : appender; + import std.traits : isSomeChar, isSomeString; auto app = appender!S(); - foreach (arg; args) - app.put(to!S(arg)); + // assume that on average, parameters will have less + // than 20 elements + app.reserve(U.length * 20); + // Must be static foreach because of https://issues.dlang.org/show_bug.cgi?id=21209 + static foreach (arg; args) + { + static if ( + isSomeChar!(typeof(arg)) || isSomeString!(typeof(arg)) || + ( isInputRange!(typeof(arg)) && isSomeChar!(ElementType!(typeof(arg))) ) + ) + app.put(arg); + else static if ( + + is(immutable typeof(arg) == immutable uint) || is(immutable typeof(arg) == immutable ulong) || + is(immutable typeof(arg) == immutable int) || is(immutable typeof(arg) == immutable long) + ) + // https://issues.dlang.org/show_bug.cgi?id=17712#c15 + app.put(textImpl!(S)(arg)); + else + 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 +The `octal` facility provides a means to declare a number in base 8. +Using `octal!177` or `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 +`int`, it is an `int`. Otherwise, it is a `long`. But, if the +user specifically asks for a `long` with the `L` suffix, always +give the `long`. Give an unsigned iff it is asked for with the $(D +U) or `u` suffix. _Octals created from integers preserve the type of the passed-in integral. See_Also: @@ -3962,7 +4844,7 @@ if (isOctalLiteral(num)) else static if ((!octalFitsInInt!(num) || literalIsLong!(num)) && literalIsUnsigned!(num)) enum octal = octal!ulong(num); else - static assert(false); + static assert(false, "Unusable input " ~ num); } /// Ditto @@ -3975,12 +4857,14 @@ if (is(typeof(decimalInteger)) && isIntegral!(typeof(decimalInteger))) /// @safe unittest { - // same as 0177 - auto x = octal!177; + // Same as 0177 + auto a = octal!177; // octal is a compile-time device - enum y = octal!160; + enum b = octal!160; // Create an unsigned octal - auto z = octal!"1_000_000u"; + auto c = octal!"1_000_000u"; + // Leading zeros are allowed when converting from a string + auto d = octal!"0001_200_000"; } /* @@ -3989,7 +4873,7 @@ if (is(typeof(decimalInteger)) && isIntegral!(typeof(decimalInteger))) */ private T octal(T)(const string num) { - assert(isOctalLiteral(num)); + assert(isOctalLiteral(num), num ~ " is not an octal literal"); T value = 0; @@ -4011,6 +4895,9 @@ private T octal(T)(const string num) { int a = octal!int("10"); assert(a == 8); + + int b = octal!int("000137"); + assert(b == 95); } /* @@ -4030,12 +4917,41 @@ private template octalFitsInInt(string octalNum) private string strippedOctalLiteral(string original) { string stripped = ""; + bool leading_zeros = true; foreach (c; original) - if (c >= '0' && c <= '7') - stripped ~= c; + { + if (!('0' <= c && c <= '7')) + continue; + if (c == '0') + { + if (leading_zeros) + continue; + } + else + { + leading_zeros = false; + } + stripped ~= c; + } + if (stripped.length == 0) + { + assert(leading_zeros); + return "0"; + } return stripped; } +@safe unittest +{ + static assert(strippedOctalLiteral("7") == "7"); + static assert(strippedOctalLiteral("123") == "123"); + static assert(strippedOctalLiteral("00123") == "123"); + static assert(strippedOctalLiteral("01230") == "1230"); + static assert(strippedOctalLiteral("0") == "0"); + static assert(strippedOctalLiteral("00_000") == "0"); + static assert(strippedOctalLiteral("000_000_12_300") == "12300"); +} + private template literalIsLong(string num) { static if (num.length > 1) @@ -4059,8 +4975,8 @@ private template literalIsUnsigned(string num) /* 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. +The format is specified in spec/lex.html. The leading zeros are allowed, +but not required. */ @safe pure nothrow @nogc private bool isOctalLiteral(const string num) @@ -4068,34 +4984,30 @@ 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; + // Must start with a digit. if (num[0] < '0' || num[0] > '7') return false; foreach (i, c; num) { - if ((c < '0' || c > '7') && c != '_') // not a legal character + if (('0' <= c && c <= '7') || c == '_') // a legal character + continue; + + if (i < num.length - 2) + return false; + + // gotta check for those suffixes + if (c != 'U' && c != 'u' && c != 'L') + return false; + if (i != num.length - 1) { - 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 - } - } + // 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 } } @@ -4115,6 +5027,9 @@ private bool isOctalLiteral(const string num) static assert(octal!"7" == 7); static assert(octal!"10" == 8); static assert(octal!"666" == 438); + static assert(octal!"0004001" == 2049); + static assert(octal!"00" == 0); + static assert(octal!"0_0" == 0); static assert(octal!45 == 37); static assert(octal!0 == 0); @@ -4123,6 +5038,7 @@ private bool isOctalLiteral(const string num) static assert(octal!666 == 438); static assert(octal!"66_6" == 438); + static assert(octal!"0_0_66_6" == 438); static assert(octal!2520046213 == 356535435); static assert(octal!"2520046213" == 356535435); @@ -4167,977 +5083,25 @@ private bool isOctalLiteral(const string num) 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"). +// emplace() used to be here but was moved to druntime +public import core.lifetime : emplace; -This makes it easier to use, easier to be safe, and faster in a non-inline -build. +// https://issues.dlang.org/show_bug.cgi?id=9559 +@safe unittest +{ + 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); +} -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 +@system unittest //http://forum.dlang.org/post/nxbdgtdlmwscocbiypjs@forum.dlang.org { import std.array : array; import std.datetime : SysTime, UTC; - import std.math : isNaN; + import std.math.traits : isNaN; static struct A { @@ -5173,235 +5137,11 @@ version (unittest) 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 + // Check fix for https://issues.dlang.org/show_bug.cgi?id=2971 assert(equal(map!(to!int)(["42", "34", "345"]), [42, 34, 345])); } @@ -5415,12 +5155,12 @@ if (isIntegral!T && isOutputRange!(W, char)) if (value < 0) { SignedStringBuf buf = void; - put(writer, signedToTempString(value, buf, 10)); + put(writer, signedToTempString(value, buf)); } else { UnsignedStringBuf buf = void; - put(writer, unsignedToTempString(value, buf, 10)); + put(writer, unsignedToTempString(value, buf)); } } @@ -5434,10 +5174,10 @@ if (isIntegral!T && isOutputRange!(W, char)) /** - 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)). + Returns the corresponding _unsigned value for `x` (e.g. if `x` has type + `int`, it returns $(D cast(uint) x)). The advantage compared to the cast + is that you do not need to rewrite the cast if `x` later changes type + (e.g from `int` to `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). @@ -5460,30 +5200,39 @@ if (isIntegral!T) immutable u3 = unsigned(s); //explicitly qualified } +/// Ditto +auto unsigned(T)(T x) +if (isSomeChar!T) +{ + // All characters are unsigned + static assert(T.min == 0, T.stringof ~ ".min must be zero"); + return cast(Unqual!T) x; +} + @safe unittest { - foreach (T; AliasSeq!(byte, ubyte)) + static 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 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 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 foreach (T; AliasSeq!(long, ulong)) { static assert(is(typeof(unsigned(cast(T) 1)) == ulong)); static assert(is(typeof(unsigned(cast(const T) 1)) == ulong)); @@ -5491,17 +5240,9 @@ if (isIntegral!T) } } -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 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)); @@ -5511,10 +5252,10 @@ if (isSomeChar!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)). + Returns the corresponding _signed value for `x` (e.g. if `x` has type + `uint`, it returns $(D cast(int) x)). The advantage compared to the cast + is that you do not need to rewrite the cast if `x` later changes type + (e.g from `uint` to `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). @@ -5540,28 +5281,28 @@ if (isIntegral!T) @system unittest { - foreach (T; AliasSeq!(byte, ubyte)) + static 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 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 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 foreach (T; AliasSeq!(long, ulong)) { static assert(is(typeof(signed(cast(T) 1)) == long)); static assert(is(typeof(signed(cast(const T) 1)) == long)); @@ -5569,9 +5310,9 @@ if (isIntegral!T) } } +// https://issues.dlang.org/show_bug.cgi?id=10874 @safe unittest { - // issue 10874 enum Test { a = 0 } ulong l = 0; auto t = l.to!Test; @@ -5582,7 +5323,8 @@ if (isIntegral!T) 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)) +OriginalType!E asOriginalType(E)(E value) +if (is(E == enum)) { return value; } @@ -5615,7 +5357,7 @@ template castFrom(From) /** Params: To = The type _to cast _to. - value = The value _to cast. It must be of type $(D From), + value = The value _to cast. It must be of type `From`, otherwise a compile-time error is emitted. Returns: @@ -5680,7 +5422,7 @@ template castFrom(From) } /** -Check the correctness of a string for $(D hexString). +Check the correctness of a string for `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). @@ -5784,32 +5526,32 @@ 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. +starting with `'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. + a `string`, a `wstring` or a `dstring`, according to the type of hexData. */ template hexString(string hexData) if (hexData.isHexLiteral) { - immutable hexString = hexStrImpl(hexData); + enum hexString = mixin(hexToString(hexData)); } /// ditto template hexString(wstring hexData) if (hexData.isHexLiteral) { - immutable hexString = hexStrImpl(hexData); + enum wstring hexString = mixin(hexToString(hexData)); } /// ditto template hexString(dstring hexData) if (hexData.isHexLiteral) { - immutable hexString = hexStrImpl(hexData); + enum dstring hexString = mixin(hexToString(hexData)); } /// @@ -5824,45 +5566,70 @@ if (hexData.isHexLiteral) assert(string3 == "0J1K"d); } +@safe nothrow pure private +{ + /* These are meant to be used with CTFE. + * They cause the instantiations of hexStrLiteral() + * to be in Phobos, not user code. + */ + string hexToString(string s) + { + return hexStrLiteral(s); + } + + wstring hexToString(wstring s) + { + return hexStrLiteral(s); + } + + dstring hexToString(dstring s) + { + return hexStrLiteral(s); + } +} + /* - 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) + Turn a hexadecimal string into a regular string literal. + I.e. "dead beef" is transformed into "\xde\xad\xbe\xef" + suitable for use in a mixin. + Params: + hexData is string, wstring, or dstring and validated by isHexLiteral() + */ +@trusted nothrow pure +private auto hexStrLiteral(String)(scope String hexData) { import std.ascii : isHexDigit; - alias C = Unqual!(ElementEncodingType!String); + alias C = Unqual!(ElementEncodingType!String); // char, wchar or dchar C[] result; - result.length = hexData.length / 2; - size_t cnt; - ubyte v; + result.length = 1 + hexData.length * 2 + 1; // don't forget the " " + /* Use a pointer because we know it won't overrun, + * and this will reduce the size of the function substantially + * by not doing the array bounds checks. + * This is why this function is @trusted. + */ + auto r = result.ptr; + r[0] = '"'; + size_t cnt = 0; 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) + if ((cnt & 1) == 0) { - v = cast(ubyte)((v << 4) | x); - result[cnt / 2] = v; + r[1 + cnt] = '\\'; + r[1 + cnt + 1] = 'x'; + cnt += 2; } - else - v = x; + r[1 + cnt] = c; ++cnt; } } - result.length = cnt / 2; + r[1 + cnt] = '"'; + result.length = 1 + cnt + 1; // trim off any excess length return result; } + @safe unittest { // compile time @@ -5889,8 +5656,8 @@ private auto hexStrImpl(String)(scope String hexData) 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)))) + (is(immutable T == immutable uint) || is(immutable T == immutable ulong) || + radix == 10 && (is(immutable T == immutable int) || is(immutable T == immutable long)))) { alias UT = Unqual!T; @@ -5967,7 +5734,7 @@ if ((radix == 2 || radix == 8 || radix == 10 || radix == 16) && char[(UT.sizeof == 4) ? 10 + isSigned!T : 20] buf = void; } - Result result = void; + Result result; result.initialize(value); return result; } @@ -5980,7 +5747,7 @@ if ((radix == 2 || radix == 8 || radix == 10 || radix == 16) && else static if (radix == 16) enum SHIFT = 4; else - static assert(0); + static assert(false, "radix must be 2, 8, 10, or 16"); static struct Result { this(UT value) @@ -6036,12 +5803,27 @@ if ((radix == 2 || radix == 8 || radix == 10 || radix == 16) && } } +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert(toChars(1).equal("1")); + assert(toChars(1_000_000).equal("1000000")); + + assert(toChars!(2)(2U).equal("10")); + assert(toChars!(16)(255U).equal("ff")); + assert(toChars!(16, char, LetterCase.upper)(255U).equal("FF")); +} + @safe unittest { import std.array; import std.range; + assert(toChars(123) == toChars(123)); + { assert(toChars!2(0u).array == "0"); assert(toChars!2(0Lu).array == "0"); diff --git a/libphobos/src/std/csv.d b/libphobos/src/std/csv.d index b10f1e6..7f5c2b2 100644 --- a/libphobos/src/std/csv.d +++ b/libphobos/src/std/csv.d @@ -2,7 +2,7 @@ /** * Implements functionality to read Comma Separated Values and its variants - * from an input range of $(D dchar). + * from an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of `dchar`. * * Comma Separated Values provide a simple means to transfer and store * tabular data. It has been common for programs to use their own @@ -53,11 +53,11 @@ * } * ------- * - * When an input contains a header the $(D Contents) can be specified as an + * When an input contains a header the `Contents` can be specified as an * associative array. Passing null to signify that a header is present. * * ------- - * auto text = "Name,Occupation,Salary\r" + * auto text = "Name,Occupation,Salary\r" ~ * "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n"; * * foreach (record; csvReader!(string[string]) @@ -67,6 +67,18 @@ * record["Name"], record["Occupation"], * record["Salary"]); * } + * + * // To read the same string from the file "filename.csv": + * + * auto file = File("filename.csv", "r"); + * + * foreach (record; csvReader!(string[string]) + * (file.byLine.joiner("\n"), 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, @@ -87,12 +99,12 @@ * Copyright: Copyright 2011 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Jesse Phillips - * Source: $(PHOBOSSRC std/_csv.d) + * Source: $(PHOBOSSRC std/csv.d) */ module std.csv; import std.conv; -import std.exception; // basicExceptionCtors +import std.exception : basicExceptionCtors; import std.range.primitives; import std.traits; @@ -105,14 +117,15 @@ import std.traits; * HeaderMismatchException) for details. * * When performing type conversions, $(REF ConvException, std,conv) is stored in - * the $(D next) field. + * the `next` field. */ class CSVException : Exception { /// size_t row, col; - // FIXME: Use std.exception.basicExceptionCtors here once bug #11500 is fixed + // FIXME: Use std.exception.basicExceptionCtors here once + // https://issues.dlang.org/show_bug.cgi?id=11500 is fixed this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @nogc @safe pure nothrow @@ -141,6 +154,27 @@ class CSVException : Exception } } +/// +@safe unittest +{ + import std.exception : collectException; + import std.algorithm.searching : count; + string text = "a,b,c\nHello,65"; + auto ex = collectException!CSVException(csvReader(text).count); + assert(ex.toString == "(Row: 0, Col: 0) Row 2's length 2 does not match previous length of 3."); +} + +/// +@safe unittest +{ + import std.exception : collectException; + import std.algorithm.searching : count; + import std.typecons : Tuple; + string text = "a,b\nHello,65"; + auto ex = collectException!CSVException(csvReader!(Tuple!(string,int))(text).count); + assert(ex.toString == "(Row: 1, Col: 2) Unexpected 'b' when converting from type string to type int"); +} + @safe pure unittest { import std.string; @@ -179,6 +213,14 @@ class IncompleteCellException : CSVException mixin basicExceptionCtors; } +/// +@safe unittest +{ + import std.exception : assertThrown; + string text = "a,\"b,c\nHello,65,2.5"; + assertThrown!IncompleteCellException(text.csvReader(["a","b","c"])); +} + @safe pure unittest { auto e1 = new Exception("Foobar"); @@ -211,6 +253,14 @@ class HeaderMismatchException : CSVException mixin basicExceptionCtors; } +/// +@safe unittest +{ + import std.exception : assertThrown; + string text = "a,b,c\nHello,65,2.5"; + assertThrown!HeaderMismatchException(text.csvReader(["b","c","invalid"])); +} + @safe pure unittest { auto e1 = new Exception("Foobar"); @@ -240,188 +290,252 @@ enum Malformed throwException /// Use exceptions when input has incorrect CSV. } +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.searching : count; + import std.exception : assertThrown; + + string text = "a,b,c\nHello,65,\"2.5"; + assertThrown!IncompleteCellException(text.csvReader.count); + + // ignore the exceptions and try to handle invalid CSV + auto firstLine = text.csvReader!(string, Malformed.ignore)(null).front; + assert(firstLine.equal(["Hello", "65", "2.5"])); +} + /** - * 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). - */ +Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) +for iterating over records found in `input`. + +An optional `header` can be provided. The first record will be read in +as the header. If `Contents` is a struct then the header provided is +expected to correspond to the fields in the struct. When `Contents` is +not a type which can contain the entire record, the `header` must be +provided in the same order as the input or an exception is thrown. + +Returns: + An input range R as defined by + $(REF isInputRange, std,range,primitives). When `Contents` is a + struct, class, or an associative array, the element type of R is + `Contents`, otherwise the element type of R is itself a range with + element type `Contents`. + + If a `header` argument is provided, + the returned range provides a `header` field for accessing the header + from the input in array form. + +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 `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) + Separator delimiter = ',', Separator quote = '"', + bool allowInconsistentDelimiterCount = false) +if (isInputRange!Range && is(immutable ElementType!Range == immutable dchar) && isSomeChar!(Separator) && !is(Contents T : T[U], U : string)) { return CsvReader!(Contents,ErrorLevel,Range, Unqual!(ElementType!Range),string[]) - (input, delimiter, quote); + (input, delimiter, quote, allowInconsistentDelimiterCount); } -/** - * 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). - */ +/// ditto 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) + Separator delimiter = ',', Separator quote = '"', + bool allowInconsistentDelimiterCount = false) +if (isInputRange!Range && is(immutable ElementType!Range == immutable dchar) && isSomeChar!(Separator) && isForwardRange!Header && isSomeString!(ElementType!Header)) { return CsvReader!(Contents,ErrorLevel,Range, Unqual!(ElementType!Range),Header) - (input, header, delimiter, quote); + (input, header, delimiter, quote, allowInconsistentDelimiterCount); } -/// +/// ditto 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) + Separator delimiter = ',', Separator quote = '"', + bool allowInconsistentDelimiterCount = false) +if (isInputRange!Range && is(immutable ElementType!Range == immutable dchar) && isSomeChar!(Separator) && is(Header : typeof(null))) { return CsvReader!(Contents,ErrorLevel,Range, Unqual!(ElementType!Range),string[]) - (input, cast(string[]) null, delimiter, quote); + (input, cast(string[]) null, delimiter, quote, + allowInconsistentDelimiterCount); +} + + +/** +The `Contents` of the input can be provided if all the records are the +same type such as all integer data: +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + string text = "76,26,22"; + auto records = text.csvReader!int; + assert(records.equal!equal([ + [76, 26, 22], + ])); +} + +/** +Using a struct with modified delimiter: +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + string text = "Hello;65;2.5\nWorld;123;7.5"; + struct Layout + { + string name; + int value; + double other; + } + + auto records = text.csvReader!Layout(';'); + assert(records.equal([ + Layout("Hello", 65, 2.5), + Layout("World", 123, 7.5), + ])); +} + +/** +Specifying `ErrorLevel` as $(LREF 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. +*/ +@safe unittest +{ + string text = "A \" is now part of the data"; + auto records = text.csvReader!(string, Malformed.ignore); + auto record = records.front; + + assert(record.front == text); +} + +/// Read only column "b" +@safe unittest +{ + import std.algorithm.comparison : equal; + string text = "a,b,c\nHello,65,63.63\nWorld,123,3673.562"; + auto records = text.csvReader!int(["b"]); + + assert(records.equal!equal([ + [65], + [123], + ])); +} + +/// Read while rearranging the columns by specifying a header with a different order" +@safe unittest +{ + import std.algorithm.comparison : equal; + string text = "a,b,c\nHello,65,2.5\nWorld,123,7.5"; + struct Layout + { + int value; + double other; + string name; + } + + auto records = text.csvReader!Layout(["b","c","a"]); + assert(records.equal([ + Layout(65, 2.5, "Hello"), + Layout(123, 7.5, "World") + ])); +} + +/** +The header can also be left empty if the input contains a header row +and all columns should be iterated. +The header from the input can always be accessed from the `header` field. +*/ +@safe unittest +{ + string text = "a,b,c\nHello,65,63.63"; + auto records = text.csvReader(null); + + assert(records.header == ["a","b","c"]); +} + +/** +Handcrafted csv files tend to have an variable amount of columns. + +By default `std.csv` will throw if the number of columns on a line +is unequal to the number of columns of the first line. +To allow, or disallow, a variable amount of columns a `bool` can be passed to +all overloads of the `csvReader` function as shown below. +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + + string text = "76,26,22\n1,2\n3,4,5,6"; + auto records = text.csvReader!int(',', '"', true); + + assert(records.equal!equal([ + [76, 26, 22], + [1, 2], + [3, 4, 5, 6] + ])); +} + +/// ditto +@safe unittest +{ + import std.algorithm.comparison : equal; + + static struct Three + { + int a; + int b; + int c; + } + + string text = "76,26,22\n1,2\n3,4,5,6"; + auto records = text.csvReader!Three(',', '"', true); + + assert(records.equal([ + Three(76, 26, 22), + Three(1, 2, 0), + Three(3, 4, 5) + ])); +} + +/// ditto +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto text = "Name,Occupation,Salary\r" ~ + "Joe,Carpenter,300000\nFred,Blacksmith\r\n"; + + auto r = csvReader!(string[string])(text, null, ',', '"', true); + + assert(r.equal([ + [ "Name" : "Joe", "Occupation" : "Carpenter", "Salary" : "300000" ], + [ "Name" : "Fred", "Occupation" : "Blacksmith" ] + ])); } // Test standard iteration over input. @@ -506,7 +620,7 @@ if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) // Test structure conversion interface with unicode. @safe pure unittest { - import std.math : abs; + import std.math.algebraic : abs; wstring str = "\U00010143Hello,65,63.63\nWorld,123,3673.562"w; struct Layout @@ -554,7 +668,7 @@ if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) // Test struct & header interface and same unicode @safe unittest { - import std.math : abs; + import std.math.algebraic : abs; string str = "a,b,c\nHello,65,63.63\n➊➋➂❹,123,3673.562"; struct Layout @@ -779,12 +893,12 @@ private pure struct Input(Range, Malformed ErrorLevel) * 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 + * created in a similar manner to allow `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) + && is(immutable ElementType!Range == immutable dchar) && isForwardRange!Header && isSomeString!(ElementType!Header)) { private: @@ -793,6 +907,7 @@ private: Separator _quote; size_t[] indices; bool _empty; + bool _allowInconsistentDelimiterCount; static if (is(Contents == struct) || is(Contents == class)) { Contents recordContent; @@ -834,11 +949,19 @@ public: * } * ------- */ - this(Range input, Separator delimiter, Separator quote) + this(Range input, Separator delimiter, Separator quote, + bool allowInconsistentDelimiterCount) { _input = new Input!(Range, ErrorLevel)(input); _separator = delimiter; _quote = quote; + _allowInconsistentDelimiterCount = allowInconsistentDelimiterCount; + + if (_input.range.empty) + { + _empty = true; + return; + } prime(); } @@ -864,11 +987,19 @@ public: * 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) + this(Range input, Header colHeaders, Separator delimiter, Separator quote, + bool allowInconsistentDelimiterCount) { _input = new Input!(Range, ErrorLevel)(input); _separator = delimiter; _quote = quote; + _allowInconsistentDelimiterCount = allowInconsistentDelimiterCount; + + if (_input.range.empty) + { + _empty = true; + return; + } size_t[string] colToIndex; foreach (h; colHeaders) @@ -877,7 +1008,8 @@ public: } auto r = CsvRecord!(string, ErrorLevel, Range, Separator) - (_input, _separator, _quote, indices); + (_input, _separator, _quote, indices, + _allowInconsistentDelimiterCount); size_t colIndex; foreach (col; r) @@ -890,6 +1022,8 @@ public: } // The above loop empties the header row. recordRange._empty = true; + recordRange._allowInconsistentDelimiterCount = + allowInconsistentDelimiterCount; indices.length = colToIndex.length; int i; @@ -940,19 +1074,19 @@ public: * $(REF isInputRange, std,range,primitives). * * Returns: - * If $(D Contents) is a struct, will be filled with record data. + * If `Contents` is a struct, will be filled with record data. * - * If $(D Contents) is a class, will be filled with record data. + * If `Contents` is a class, will be filled with record data. * - * If $(D Contents) is a associative array, will be filled + * If `Contents` is a associative array, will be filled * with record data. * - * If $(D Contents) is non-struct, a $(LREF CsvRecord) will be + * If `Contents` is non-struct, a $(LREF CsvRecord) will be * returned. */ @property auto front() { - assert(!empty); + assert(!empty, "Attempting to fetch the front of an empty CsvReader"); static if (is(Contents == struct) || is(Contents == class)) { return recordContent; @@ -1028,12 +1162,14 @@ public: static if (is(Contents == struct) || is(Contents == class)) { recordRange = typeof(recordRange) - (_input, _separator, _quote, null); + (_input, _separator, _quote, null, + _allowInconsistentDelimiterCount); } else { recordRange = typeof(recordRange) - (_input, _separator, _quote, indices); + (_input, _separator, _quote, indices, + _allowInconsistentDelimiterCount); } static if (is(Contents T : T[U], U : string)) @@ -1106,7 +1242,7 @@ public: string str = `76;^26^;22`; int[] ans = [76,26,22]; auto records = CsvReader!(int,Malformed.ignore,string,char,string[]) - (str, ';', '^'); + (str, ';', '^', false); foreach (record; records) { @@ -1114,7 +1250,7 @@ public: } } -// Bugzilla 15545 +// https://issues.dlang.org/show_bug.cgi?id=15545 // @system due to the catch for Throwable @system pure unittest { @@ -1128,7 +1264,7 @@ public: /* * This input range is accessible through $(LREF CsvReader) when the - * requested $(D Contents) type is neither a structure or an associative array. + * requested `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)) @@ -1141,24 +1277,28 @@ private: Contents curContentsoken; typeof(appender!(dchar[])()) _front; bool _empty; + bool _allowInconsistentDelimiterCount; size_t[] _popCount; public: /* * Params: - * input = Pointer to a character input range + * input = Pointer to a character $(REF_ALTTEXT input range, isInputRange, std,range,primitives) * 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) + Separator quote, size_t[] indices, + bool allowInconsistentDelimiterCount) { _input = input; _separator = delimiter; _quote = quote; + _front = appender!(dchar[])(); _popCount = indices.dup; + _allowInconsistentDelimiterCount = allowInconsistentDelimiterCount; // If a header was given, each call to popFront will need // to eliminate so many tokens. This calculates @@ -1187,7 +1327,7 @@ public: */ @property Contents front() @safe pure { - assert(!empty); + assert(!empty, "Attempting to fetch the front of an empty CsvRecord"); return curContentsoken; } @@ -1241,23 +1381,38 @@ public: { _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)); + { + if (_input.rowLength != 0 && _input.col != _input.rowLength + && !_allowInconsistentDelimiterCount) + { + 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) + { + if (_input.rowLength != 0 && _input.col > _input.rowLength) + { + if (!_allowInconsistentDelimiterCount) + { throw new CSVException( format("Row %s's length %s does not match "~ "previous length of %s.", _input.row, _input.col, _input.rowLength)); + } + else + { + _empty = true; + return; + } + } + } } // Separator is left on the end of input from the last call. @@ -1367,7 +1522,7 @@ void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, Separator sep, Separator quote, bool startQuoted = false) if (isSomeChar!Separator && isInputRange!Range - && is(Unqual!(ElementType!Range) == dchar) + && is(immutable ElementType!Range == immutable dchar) && isOutputRange!(Output, dchar)) { bool quoted = startQuoted; @@ -1388,7 +1543,8 @@ if (isSomeChar!Separator && isInputRange!Range while (!input.empty) { - assert(!(quoted && escQuote)); + assert(!(quoted && escQuote), + "Invalid quotation state in csvNextToken"); if (!quoted) { // When not quoted the token ends at sep @@ -1667,7 +1823,7 @@ if (isSomeChar!Separator && isInputRange!Range assert(a.data == ""d); } -// Bugzilla 8908 +// https://issues.dlang.org/show_bug.cgi?id=8908 @safe pure unittest { string csv = ` 1.0, 2.0, 3.0 @@ -1699,3 +1855,23 @@ if (isSomeChar!Separator && isInputRange!Range ++i; } } + +// https://issues.dlang.org/show_bug.cgi?id=21629 +@safe pure unittest +{ + import std.typecons : Tuple; + struct Reccord + { + string a; + string b; + } + + auto header = ["a" ,"b"]; + string input = ""; + assert(csvReader!Reccord(input).empty, "This should be empty"); + assert(csvReader!Reccord(input, header).empty, "This should be empty"); + assert(csvReader!(Tuple!(string,string))(input).empty, "This should be empty"); + assert(csvReader!(string[string])(input, header).empty, "This should be empty"); + assert(csvReader!(string[string])(input, null).empty, "This should be empty"); + assert(csvReader!(int)(input, null).empty, "This should be empty"); +} diff --git a/libphobos/src/std/datetime/date.d b/libphobos/src/std/datetime/date.d index d571f5f..ebdaba4 100644 --- a/libphobos/src/std/datetime/date.d +++ b/libphobos/src/std/datetime/date.d @@ -1,18 +1,52 @@ // Written in the D programming language - /++ + +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Main date types) $(TD + $(LREF Date) + $(LREF DateTime) +)) +$(TR $(TD Other date types) $(TD + $(LREF Month) + $(LREF DayOfWeek) + $(LREF TimeOfDay) +)) +$(TR $(TD Date checking) $(TD + $(LREF valid) + $(LREF validTimeUnits) + $(LREF yearIsLeapYear) + $(LREF isTimePoint) + $(LREF enforceValid) +)) +$(TR $(TD Date conversion) $(TD + $(LREF daysToDayOfWeek) + $(LREF monthsToMonth) +)) +$(TR $(TD Time units) $(TD + $(LREF cmpTimeUnits) + $(LREF timeStrings) +)) +$(TR $(TD Other) $(TD + $(LREF AllowDayOverflow) + $(LREF DateTimeException) +)) +)) + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - Authors: Jonathan M Davis - Source: $(PHOBOSSRC std/datetime/_date.d) + Authors: $(HTTP jmdavisprog.com, Jonathan M Davis) + Source: $(PHOBOSSRC std/datetime/date.d) +/ module std.datetime.date; -import core.time; +import core.time : TimeException; import std.traits : isSomeString, Unqual; import std.typecons : Flag; +import std.range.primitives : isOutputRange; -version (unittest) import std.exception : assertThrown; - +version (StdUnittest) import std.exception : assertThrown; @safe unittest { @@ -47,6 +81,13 @@ enum Month : ubyte dec /// } +/// +@safe pure unittest +{ + assert(Date(2018, 10, 1).month == Month.oct); + assert(DateTime(1, 1, 1).month == Month.jan); +} + /++ Represents the 7 days of the Gregorian week (Sunday is 0). @@ -62,6 +103,12 @@ enum DayOfWeek : ubyte sat /// } +/// +@safe pure unittest +{ + assert(Date(2018, 10, 1).dayOfWeek == DayOfWeek.mon); + assert(DateTime(5, 5, 5).dayOfWeek == DayOfWeek.thu); +} /++ In some date calculations, adding months or years can cause the date to fall @@ -75,9 +122,9 @@ enum DayOfWeek : ubyte AllowDayOverflow only applies to calculations involving months or years. - If set to $(D AllowDayOverflow.no), then day overflow is not allowed. + If set to `AllowDayOverflow.no`, then day overflow is not allowed. - Otherwise, if set to $(D AllowDayOverflow.yes), then day overflow is + Otherwise, if set to `AllowDayOverflow.yes`, then day overflow is allowed. +/ alias AllowDayOverflow = Flag!"allowDayOverflow"; @@ -85,26 +132,27 @@ 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"). + unit and going to the largest. It does not include `"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") + Includes `"hnsecs"` (hecto-nanoseconds (100 ns)), + `"usecs"` (microseconds), `"msecs"` (milliseconds), `"seconds"`, + `"minutes"`, `"hours"`, `"days"`, `"weeks"`, `"months"`, and + `"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. + 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). `DateTime` + is intended primarily for calendar-based uses rather than precise time + operations. +/ struct DateTime { @@ -115,7 +163,7 @@ public: 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 + this(Date date, TimeOfDay tod = TimeOfDay.init) @safe pure nothrow @nogc { _date = date; _tod = tod; @@ -175,7 +223,7 @@ public: /++ - Compares this $(LREF DateTime) with the given $(D DateTime.). + Compares this $(LREF DateTime) with the given `DateTime.`. Returns: $(BOOKTABLE, @@ -184,7 +232,7 @@ public: $(TR $(TD this > rhs) $(TD > 0)) ) +/ - int opCmp(in DateTime rhs) const @safe pure nothrow @nogc + int opCmp(DateTime rhs) const @safe pure nothrow @nogc { immutable dateResult = _date.opCmp(rhs._date); @@ -424,7 +472,7 @@ public: Params: date = The Date to set this $(LREF DateTime)'s date portion to. +/ - @property void date(in Date date) @safe pure nothrow @nogc + @property void date(Date date) @safe pure nothrow @nogc { _date = date; } @@ -477,7 +525,7 @@ public: 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 + @property void timeOfDay(TimeOfDay tod) @safe pure nothrow @nogc { _tod = tod; } @@ -544,7 +592,7 @@ public: @safe unittest { - static void testDT(DateTime dt, int year, in DateTime expected, size_t line = __LINE__) + static void testDT(DateTime dt, int year, DateTime expected, size_t line = __LINE__) { dt.year = year; assert(dt == expected); @@ -571,7 +619,7 @@ public: 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. + $(REF DateTimeException,std,datetime,date) if `isAD` is true. +/ @property short yearBC() const @safe pure { @@ -588,7 +636,7 @@ public: @safe unittest { - assertThrown!DateTimeException((in DateTime dt){dt.yearBC;}(DateTime(Date(1, 1, 1)))); + assertThrown!DateTimeException((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); @@ -686,7 +734,7 @@ public: @safe unittest { - static void testDT(DateTime dt, Month month, in DateTime expected = DateTime.init, size_t line = __LINE__) + static void testDT(DateTime dt, Month month, DateTime expected = DateTime.init, size_t line = __LINE__) { dt.month = month; assert(expected != DateTime.init); @@ -1004,8 +1052,8 @@ public: /++ - Adds the given number of years or months to this $(LREF DateTime). A - negative number will subtract. + Adds the given number of years or months to this $(LREF DateTime), + mutating it. 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 @@ -1021,6 +1069,9 @@ public: $(LREF DateTime). allowOverflow = Whether the days should be allowed to overflow, causing the month to increment. + + Returns: + A reference to the `DateTime` (`this`). +/ ref DateTime add(string units) (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow @nogc @@ -1066,8 +1117,8 @@ public: /++ - Adds the given number of years or months to this $(LREF DateTime). A - negative number will subtract. + Adds the given number of years or months to this $(LREF DateTime), + mutating it. 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 @@ -1083,6 +1134,9 @@ public: $(LREF DateTime). allowOverflow = Whether the days should be allowed to overflow, causing the month to increment. + + Returns: + A reference to the `DateTime` (`this`). +/ ref DateTime roll(string units) (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow @nogc @@ -1136,20 +1190,23 @@ public: /++ - Adds the given number of units to this $(LREF DateTime). A negative - number will subtract. + Adds the given number of units to this $(LREF DateTime), mutating it. 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"). + Accepted units are `"days"`, `"minutes"`, `"hours"`, + `"minutes"`, and `"seconds"`. Params: units = The units to add. value = The number of $(D_PARAM units) to add to this $(LREF DateTime). + + Returns: + A reference to the `DateTime` (`this`). +/ ref DateTime roll(string units)(long value) @safe pure nothrow @nogc if (units == "days") @@ -1191,7 +1248,7 @@ public: } - // Shares documentation with "days" version. + /// ditto ref DateTime roll(string units)(long value) @safe pure nothrow @nogc if (units == "hours" || units == "minutes" || @@ -1204,7 +1261,7 @@ public: // Test roll!"hours"(). @safe unittest { - static void testDT(DateTime orig, int hours, in DateTime expected, size_t line = __LINE__) + static void testDT(DateTime orig, int hours, DateTime expected, size_t line = __LINE__) { orig.roll!"hours"(hours); assert(orig == expected); @@ -1507,7 +1564,7 @@ public: // Test roll!"minutes"(). @safe unittest { - static void testDT(DateTime orig, int minutes, in DateTime expected, size_t line = __LINE__) + static void testDT(DateTime orig, int minutes, DateTime expected, size_t line = __LINE__) { orig.roll!"minutes"(minutes); assert(orig == expected); @@ -1807,7 +1864,7 @@ public: // Test roll!"seconds"(). @safe unittest { - static void testDT(DateTime orig, int seconds, in DateTime expected, size_t line = __LINE__) + static void testDT(DateTime orig, int seconds, DateTime expected, size_t line = __LINE__) { orig.roll!"seconds"(seconds); assert(orig == expected); @@ -2063,6 +2120,7 @@ public: } + import core.time : Duration; /++ Gives the result of adding or subtracting a $(REF Duration, core,time) from this $(LREF DateTime). @@ -2107,6 +2165,8 @@ public: @safe unittest { + import core.time : dur; + 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))); @@ -2186,6 +2246,7 @@ public: @safe unittest { + import core.time : dur; 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) == @@ -2277,12 +2338,13 @@ public: $(TR $(TD DateTime) $(TD -) $(TD DateTime) $(TD -->) $(TD duration)) ) +/ - Duration opBinary(string op)(in DateTime rhs) const @safe pure nothrow @nogc + Duration opBinary(string op)(DateTime rhs) const @safe pure nothrow @nogc if (op == "-") { immutable dateResult = _date - rhs.date; immutable todResult = _tod - rhs._tod; + import core.time : dur; return dur!"hnsecs"(dateResult.total!"hnsecs" + todResult.total!"hnsecs"); } @@ -2290,6 +2352,7 @@ public: { auto dt = DateTime(1999, 7, 6, 12, 30, 33); + import core.time : dur; 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)) == @@ -2362,7 +2425,7 @@ public: Params: rhs = The $(LREF DateTime) to subtract from this one. +/ - int diffMonths(in DateTime rhs) const @safe pure nothrow @nogc + int diffMonths(DateTime rhs) const @safe pure nothrow @nogc { return _date.diffMonths(rhs._date); } @@ -2598,6 +2661,18 @@ public: /++ + The year of the ISO 8601 week calendar that this $(LREF DateTime) is in. + + See_Also: + $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date) + +/ + @property short isoWeekYear() const @safe pure nothrow + { + return _date.isoWeekYear; + } + + + /++ $(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. @@ -2790,24 +2865,39 @@ public: /++ - Converts this $(LREF DateTime) to a string with the format YYYYMMDDTHHMMSS. + Converts this $(LREF DateTime) to a string with the format `YYYYMMDDTHHMMSS`. + If `writer` is set, the resulting string will be written directly to it. + + Params: + writer = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toISOString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(18); try - { - return format!("%sT%02d%02d%02d")( - _date.toISOString(), - _tod._hour, - _tod._minute, - _tod._second - ); - } + toISOString(w); catch (Exception e) - { - assert(0, "format() threw."); - } + assert(0, "toISOString() threw."); + return w.data; + } + + /// ditto + void toISOString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + _date.toISOString(writer); + formattedWrite!("T%02d%02d%02d")( + writer, + _tod._hour, + _tod._minute, + _tod._second + ); } /// @@ -2852,24 +2942,39 @@ public: /++ Converts this $(LREF DateTime) to a string with the format - YYYY-MM-DDTHH:MM:SS. + `YYYY-MM-DDTHH:MM:SS`. If `writer` is set, the resulting + string will be written directly to it. + + Params: + writer = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toISOExtString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(20); try - { - return format!("%sT%02d:%02d:%02d")( - _date.toISOExtString(), - _tod._hour, - _tod._minute, - _tod._second - ); - } + toISOExtString(w); catch (Exception e) - { - assert(0, "format() threw."); - } + assert(0, "toISOExtString() threw."); + return w.data; + } + + /// ditto + void toISOExtString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + _date.toISOExtString(writer); + formattedWrite!("T%02d:%02d:%02d")( + writer, + _tod._hour, + _tod._minute, + _tod._second + ); } /// @@ -2913,24 +3018,39 @@ public: /++ Converts this $(LREF DateTime) to a string with the format - YYYY-Mon-DD HH:MM:SS. + `YYYY-Mon-DD HH:MM:SS`. If `writer` is set, the resulting + string will be written directly to it. + + Params: + writer = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toSimpleString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(22); try - { - return format!("%s %02d:%02d:%02d")( - _date.toSimpleString(), - _tod._hour, - _tod._minute, - _tod._second - ); - } + toSimpleString(w); catch (Exception e) - { - assert(0, "format() threw."); - } + assert(0, "toSimpleString() threw."); + return w.data; + } + + /// ditto + void toSimpleString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + _date.toSimpleString(writer); + formattedWrite!(" %02d:%02d:%02d")( + writer, + _tod._hour, + _tod._minute, + _tod._second + ); } /// @@ -3011,7 +3131,12 @@ public: assert(idt.toString()); } - + /// ditto + void toString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + toSimpleString(writer); + } /++ Creates a $(LREF DateTime) from a string with the format YYYYMMDDTHHMMSS. @@ -3025,18 +3150,19 @@ public: not in the ISO format or if the resulting $(LREF DateTime) would not be valid. +/ - static DateTime fromISOString(S)(in S isoString) @safe pure + static DateTime fromISOString(S)(scope const S isoString) @safe pure if (isSomeString!S) { import std.algorithm.searching : countUntil; import std.exception : enforce; import std.format : format; import std.string : strip; + import std.utf : byCodeUnit; auto str = strip(isoString); enforce(str.length >= 15, new DateTimeException(format("Invalid ISO String: %s", isoString))); - auto t = str.countUntil('T'); + auto t = str.byCodeUnit.countUntil('T'); enforce(t != -1, new DateTimeException(format("Invalid ISO String: %s", isoString))); @@ -3090,7 +3216,7 @@ public: 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("20101222T172201") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 1))); 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))); @@ -3099,14 +3225,14 @@ public: assert(DateTime.fromISOString(" 19990706T123033 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) assert(DateTime.fromISOString(to!S("20121221T141516")) == DateTime(2012, 12, 21, 14, 15, 16)); } } @@ -3125,18 +3251,19 @@ public: 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 + static DateTime fromISOExtString(S)(scope const S isoExtString) @safe pure if (isSomeString!(S)) { import std.algorithm.searching : countUntil; import std.exception : enforce; import std.format : format; import std.string : strip; + import std.utf : byCodeUnit; auto str = strip(isoExtString); enforce(str.length >= 15, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); - auto t = str.countUntil('T'); + auto t = str.byCodeUnit.countUntil('T'); enforce(t != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); @@ -3189,7 +3316,7 @@ public: 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("2010-12-22T17:22:01") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 1))); 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))); @@ -3198,14 +3325,14 @@ public: assert(DateTime.fromISOExtString(" 1999-07-06T12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static 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)); } } @@ -3224,18 +3351,19 @@ public: not in the correct format or if the resulting $(LREF DateTime) would not be valid. +/ - static DateTime fromSimpleString(S)(in S simpleString) @safe pure + static DateTime fromSimpleString(S)(scope const S simpleString) @safe pure if (isSomeString!(S)) { import std.algorithm.searching : countUntil; import std.exception : enforce; import std.format : format; import std.string : strip; + import std.utf : byCodeUnit; auto str = strip(simpleString); enforce(str.length >= 15, new DateTimeException(format("Invalid string format: %s", simpleString))); - auto t = str.countUntil(' '); + auto t = str.byCodeUnit.countUntil(' '); enforce(t != -1, new DateTimeException(format("Invalid string format: %s", simpleString))); @@ -3286,7 +3414,7 @@ public: 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))); + DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 1))); 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") == @@ -3301,14 +3429,14 @@ public: DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static 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)); } } @@ -3324,7 +3452,7 @@ public: assert(result._date == Date.min); assert(result._tod == TimeOfDay.min); } - body + do { auto dt = DateTime.init; dt._date._year = short.min; @@ -3351,7 +3479,7 @@ public: assert(result._date == Date.max); assert(result._tod == TimeOfDay.max); } - body + do { auto dt = DateTime.init; dt._date._year = short.max; @@ -3384,6 +3512,7 @@ private: +/ ref DateTime _addSeconds(long seconds) return @safe pure nothrow @nogc { + import core.time : convert; long hnsecs = convert!("seconds", "hnsecs")(seconds); hnsecs += convert!("hours", "hnsecs")(_tod._hour); hnsecs += convert!("minutes", "hnsecs")(_tod._minute); @@ -3412,7 +3541,7 @@ private: @safe unittest { - static void testDT(DateTime orig, int seconds, in DateTime expected, size_t line = __LINE__) + static void testDT(DateTime orig, int seconds, DateTime expected, size_t line = __LINE__) { orig._addSeconds(seconds); assert(orig == expected); @@ -3585,6 +3714,29 @@ private: TimeOfDay _tod; } +/// +@safe pure unittest +{ + import core.time : days, seconds; + + auto dt = DateTime(2000, 6, 1, 10, 30, 0); + + assert(dt.date == Date(2000, 6, 1)); + assert(dt.timeOfDay == TimeOfDay(10, 30, 0)); + assert(dt.dayOfYear == 153); + assert(dt.dayOfWeek == DayOfWeek.thu); + + dt += 10.days + 100.seconds; + assert(dt == DateTime(2000, 6, 11, 10, 31, 40)); + + assert(dt.toISOExtString() == "2000-06-11T10:31:40"); + assert(dt.toISOString() == "20000611T103140"); + assert(dt.toSimpleString() == "2000-Jun-11 10:31:40"); + + assert(DateTime.fromISOExtString("2018-01-01T12:00:00") == DateTime(2018, 1, 1, 12, 0, 0)); + assert(DateTime.fromISOString("20180101T120000") == DateTime(2018, 1, 1, 12, 0, 0)); + assert(DateTime.fromSimpleString("2018-Jan-01 12:00:00") == DateTime(2018, 1, 1, 12, 0, 0)); +} /++ Represents a date in the @@ -3592,10 +3744,10 @@ private: 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 + Year, month, and day are kept separately internally so that `Date` is optimized for calendar-based operations. - $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian + `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. @@ -3634,7 +3786,7 @@ public: import std.exception : assertNotThrown; assert(Date(1, 1, 1) == Date.init); - static void testDate(in Date date, int year, int month, int day) + static void testDate(Date date, int year, int month, int day) { assert(date._year == year); assert(date._month == month); @@ -3829,7 +3981,7 @@ public: $(TR $(TD this > rhs) $(TD > 0)) ) +/ - int opCmp(in Date rhs) const @safe pure nothrow @nogc + int opCmp(Date rhs) const @safe pure nothrow @nogc { if (_year < rhs._year) return -1; @@ -3994,7 +4146,7 @@ public: date.year = year; } - static void testDate(Date date, int year, in Date expected) + static void testDate(Date date, int year, Date expected) { date.year = year; assert(date == expected); @@ -4017,7 +4169,7 @@ public: 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. + $(REF DateTimeException,std,datetime,date) if `isAD` is true. +/ @property ushort yearBC() const @safe pure { @@ -4038,7 +4190,7 @@ public: @safe unittest { - assertThrown!DateTimeException((in Date date){date.yearBC;}(Date(1, 1, 1))); + assertThrown!DateTimeException((Date date){date.yearBC;}(Date(1, 1, 1))); auto date = Date(0, 7, 6); const cdate = Date(0, 7, 6); @@ -4139,7 +4291,7 @@ public: @safe unittest { - static void testDate(Date date, Month month, in Date expected = Date.init) + static void testDate(Date date, Month month, Date expected = Date.init) { date.month = month; assert(expected != Date.init); @@ -4304,8 +4456,8 @@ public: /++ - Adds the given number of years or months to this $(LREF Date). A - negative number will subtract. + Adds the given number of years or months to this $(LREF Date), mutating + it. 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 @@ -4321,6 +4473,9 @@ public: $(LREF Date). allowOverflow = Whether the day should be allowed to overflow, causing the month to increment. + + Returns: + A reference to the `Date` (`this`). +/ @safe pure nothrow @nogc ref Date add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @@ -5086,8 +5241,8 @@ public: /++ - Adds the given number of years or months to this $(LREF Date). A negative - number will subtract. + Adds the given number of years or months to this $(LREF Date), mutating + it. 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 @@ -5103,6 +5258,9 @@ public: $(LREF Date). allowOverflow = Whether the day should be allowed to overflow, causing the month to increment. + + Returns: + A reference to the `Date` (`this`). +/ @safe pure nothrow @nogc ref Date roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @@ -5731,18 +5889,21 @@ public: /++ - Adds the given number of units to this $(LREF Date). A negative number - will subtract. + Adds the given number of units to this $(LREF Date), mutating it. 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"). + The only accepted units are `"days"`. Params: - units = The units to add. Must be $(D "days"). + units = The units to add. Must be `"days"`. days = The number of days to add to this $(LREF Date). + + Returns: + A reference to the `Date` (`this`). +/ ref Date roll(string units)(long days) @safe pure nothrow @nogc if (units == "days") @@ -5966,7 +6127,7 @@ public: static assert(!__traits(compiles, idate.roll!"days"(12))); } - + import core.time : Duration; /++ Gives the result of adding or subtracting a $(REF Duration, core,time) from @@ -6006,6 +6167,7 @@ public: { auto date = Date(1999, 7, 6); + import core.time : dur; 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)); @@ -6080,6 +6242,7 @@ public: @safe unittest { + import core.time : dur; 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)); @@ -6145,9 +6308,10 @@ public: $(TR $(TD Date) $(TD -) $(TD Date) $(TD -->) $(TD duration)) ) +/ - Duration opBinary(string op)(in Date rhs) const @safe pure nothrow @nogc + Duration opBinary(string op)(Date rhs) const @safe pure nothrow @nogc if (op == "-") { + import core.time : dur; return dur!"days"(this.dayOfGregorianCal - rhs.dayOfGregorianCal); } @@ -6155,6 +6319,7 @@ public: { auto date = Date(1999, 7, 6); + import core.time : dur; 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)); @@ -6199,7 +6364,7 @@ public: Params: rhs = The $(LREF Date) to subtract from this one. +/ - int diffMonths(in Date rhs) const @safe pure nothrow @nogc + int diffMonths(Date rhs) const @safe pure nothrow @nogc { immutable yearDiff = _year - rhs._year; immutable monthDiff = _month - rhs._month; @@ -6729,13 +6894,19 @@ public: /++ - The ISO 8601 week of the year that this $(LREF Date) is in. + The ISO 8601 week and year of the year that this $(LREF Date) is in. + + Returns: + An anonymous struct with the members $(D isoWeekYear) for the + resulting year and $(D isoWeek) for the resulting ISO week. See_Also: $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date) +/ - @property ubyte isoWeek() const @safe pure nothrow + @property auto isoWeekAndYear() const @safe pure nothrow { + struct ISOWeekAndYear { short isoWeekYear; ubyte isoWeek; } + immutable weekday = dayOfWeek; immutable adjustedWeekday = weekday == DayOfWeek.sun ? 7 : weekday; immutable week = (dayOfYear - adjustedWeekday + 10) / 7; @@ -6750,24 +6921,35 @@ public: case DayOfWeek.tue: case DayOfWeek.wed: case DayOfWeek.thu: - return 1; + return ISOWeekAndYear(cast(short) (_year + 1), 1); case DayOfWeek.fri: case DayOfWeek.sat: case DayOfWeek.sun: - return 53; + return ISOWeekAndYear(_year, 53); default: assert(0, "Invalid ISO Week"); } } else if (week > 0) - return cast(ubyte) week; + return ISOWeekAndYear(_year, cast(ubyte) week); else - return Date(_year - 1, 12, 31).isoWeek; + return Date(_year - 1, 12, 31).isoWeekAndYear; } catch (Exception e) assert(0, "Date's constructor threw."); } + /++ + 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 + { + return isoWeekAndYear().isoWeek; + } + @safe unittest { // Test A.D. @@ -6829,6 +7011,105 @@ public: static assert(!__traits(compiles, idate.isoWeek = 3)); } + /++ + The year inside the ISO 8601 week calendar that this $(LREF Date) is in. + + May differ from $(LREF year) between 28 December and 4 January. + + See_Also: + $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date) + +/ + @property short isoWeekYear() const @safe pure nothrow + { + return isoWeekAndYear().isoWeekYear; + } + + @safe unittest + { + // Test A.D. + assert(Date(2009, 12, 28).isoWeekYear == 2009); + assert(Date(2009, 12, 29).isoWeekYear == 2009); + assert(Date(2009, 12, 30).isoWeekYear == 2009); + assert(Date(2009, 12, 31).isoWeekYear == 2009); + assert(Date(2010, 1, 1).isoWeekYear == 2009); + assert(Date(2010, 1, 2).isoWeekYear == 2009); + assert(Date(2010, 1, 3).isoWeekYear == 2009); + assert(Date(2010, 1, 4).isoWeekYear == 2010); + assert(Date(2010, 1, 5).isoWeekYear == 2010); + assert(Date(2010, 1, 6).isoWeekYear == 2010); + assert(Date(2010, 1, 7).isoWeekYear == 2010); + assert(Date(2010, 1, 8).isoWeekYear == 2010); + assert(Date(2010, 1, 9).isoWeekYear == 2010); + assert(Date(2010, 1, 10).isoWeekYear == 2010); + assert(Date(2010, 1, 11).isoWeekYear == 2010); + assert(Date(2010, 12, 31).isoWeekYear == 2010); + + assert(Date(2004, 12, 26).isoWeekYear == 2004); + assert(Date(2004, 12, 27).isoWeekYear == 2004); + assert(Date(2004, 12, 28).isoWeekYear == 2004); + assert(Date(2004, 12, 29).isoWeekYear == 2004); + assert(Date(2004, 12, 30).isoWeekYear == 2004); + assert(Date(2004, 12, 31).isoWeekYear == 2004); + assert(Date(2005, 1, 1).isoWeekYear == 2004); + assert(Date(2005, 1, 2).isoWeekYear == 2004); + assert(Date(2005, 1, 3).isoWeekYear == 2005); + + assert(Date(2005, 12, 31).isoWeekYear == 2005); + assert(Date(2007, 1, 1).isoWeekYear == 2007); + + assert(Date(2007, 12, 30).isoWeekYear == 2007); + assert(Date(2007, 12, 31).isoWeekYear == 2008); + assert(Date(2008, 1, 1).isoWeekYear == 2008); + + assert(Date(2008, 12, 28).isoWeekYear == 2008); + assert(Date(2008, 12, 29).isoWeekYear == 2009); + assert(Date(2008, 12, 30).isoWeekYear == 2009); + assert(Date(2008, 12, 31).isoWeekYear == 2009); + assert(Date(2009, 1, 1).isoWeekYear == 2009); + assert(Date(2009, 1, 2).isoWeekYear == 2009); + assert(Date(2009, 1, 3).isoWeekYear == 2009); + assert(Date(2009, 1, 4).isoWeekYear == 2009); + + // Test B.C. + assert(Date(0, 12, 31).isoWeekYear == 0); + assert(Date(0, 1, 4).isoWeekYear == 0); + assert(Date(0, 1, 1).isoWeekYear == -1); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.isoWeekYear == 1999); + assert(idate.isoWeekYear == 1999); + } + + static Date fromISOWeek(short isoWeekYear, ubyte isoWeek, DayOfWeek weekday) @safe pure nothrow @nogc + { + immutable adjustedWeekday = weekday == DayOfWeek.sun ? 7 : weekday; + immutable dayOffset = (isoWeek - 1) * 7 + adjustedWeekday; + + Date date; + date._year = isoWeekYear; + date._month = Month.jan; + date._day = 3; + immutable startOfYear = date.dayOfWeek; + return date._addDays(dayOffset - startOfYear); + } + + @safe unittest + { + // Test -30000 days to 30000 days for matching construction <-> deconstruction + Date date = Date(1, 1, 1); + date._addDays(-30_000); + foreach (day; 0 .. 60_000) + { + const year = date.isoWeekYear; + const dow = date.dayOfWeek; + const isoWeek = date.isoWeek; + const reversed = Date.fromISOWeek(year, isoWeek, dow); + assert(reversed == date, date.toISOExtString ~ " != " ~ reversed.toISOExtString); + date = date._addDays(1); + } + } + /++ $(LREF Date) for the last day in the month that this $(LREF Date) is in. @@ -7027,27 +7308,25 @@ public: /++ - Converts this $(LREF Date) to a string with the format YYYYMMDD. + Converts this $(LREF Date) to a string with the format `YYYYMMDD`. + If `writer` is set, the resulting string will be written directly + to it. + + Params: + writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toISOString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(8); 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); - } + toISOString(w); catch (Exception e) - assert(0, "format() threw."); + assert(0, "toISOString() threw."); + return w.data; } /// @@ -7082,28 +7361,56 @@ public: assert(idate.toISOString() == "19990706"); } + /// ditto + void toISOString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + if (_year >= 0) + { + if (_year < 10_000) + formattedWrite(writer, "%04d%02d%02d", _year, _month, _day); + else + formattedWrite(writer, "+%05d%02d%02d", _year, _month, _day); + } + else if (_year > -10_000) + formattedWrite(writer, "%05d%02d%02d", _year, _month, _day); + else + formattedWrite(writer, "%06d%02d%02d", _year, _month, _day); + } + + @safe pure unittest + { + import std.array : appender; + + auto w = appender!(char[])(); + Date(2010, 7, 4).toISOString(w); + assert(w.data == "20100704"); + w.clear(); + Date(1998, 12, 25).toISOString(w); + assert(w.data == "19981225"); + } + /++ - Converts this $(LREF Date) to a string with the format YYYY-MM-DD. + Converts this $(LREF Date) to a string with the format `YYYY-MM-DD`. + If `writer` is set, the resulting string will be written directly + to it. + + Params: + writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toISOExtString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(10); 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); - } + toISOExtString(w); catch (Exception e) - assert(0, "format() threw."); + assert(0, "toISOExtString() threw."); + return w.data; } /// @@ -7138,28 +7445,56 @@ public: assert(idate.toISOExtString() == "1999-07-06"); } + /// ditto + void toISOExtString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + if (_year >= 0) + { + if (_year < 10_000) + formattedWrite(writer, "%04d-%02d-%02d", _year, _month, _day); + else + formattedWrite(writer, "+%05d-%02d-%02d", _year, _month, _day); + } + else if (_year > -10_000) + formattedWrite(writer, "%05d-%02d-%02d", _year, _month, _day); + else + formattedWrite(writer, "%06d-%02d-%02d", _year, _month, _day); + } + + @safe pure unittest + { + import std.array : appender; + + auto w = appender!(char[])(); + Date(2010, 7, 4).toISOExtString(w); + assert(w.data == "2010-07-04"); + w.clear(); + Date(-4, 1, 5).toISOExtString(w); + assert(w.data == "-0004-01-05"); + } + /++ - Converts this $(LREF Date) to a string with the format YYYY-Mon-DD. + Converts this $(LREF Date) to a string with the format `YYYY-Mon-DD`. + If `writer` is set, the resulting string will be written directly + to it. + + Params: + writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toSimpleString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(11); 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); - } + toSimpleString(w); catch (Exception e) - assert(0, "format() threw."); + assert(0, "toSimpleString() threw."); + return w.data; } /// @@ -7194,6 +7529,35 @@ public: assert(idate.toSimpleString() == "1999-Jul-06"); } + /// ditto + void toSimpleString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + if (_year >= 0) + { + if (_year < 10_000) + formattedWrite(writer, "%04d-%s-%02d", _year, monthToString(_month), _day); + else + formattedWrite(writer, "+%05d-%s-%02d", _year, monthToString(_month), _day); + } + else if (_year > -10_000) + formattedWrite(writer, "%05d-%s-%02d", _year, monthToString(_month), _day); + else + formattedWrite(writer, "%06d-%s-%02d", _year, monthToString(_month), _day); + } + + @safe pure unittest + { + import std.array : appender; + + auto w = appender!(char[])(); + Date(9, 12, 4).toSimpleString(w); + assert(w.data == "0009-Dec-04"); + w.clear(); + Date(-10000, 10, 20).toSimpleString(w); + assert(w.data == "-10000-Oct-20"); + } /++ Converts this $(LREF Date) to a string. @@ -7233,6 +7597,12 @@ public: assert(idate.toString()); } + /// ditto + void toString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + toSimpleString(writer); + } /++ Creates a $(LREF Date) from a string with the format YYYYMMDD. Whitespace @@ -7246,7 +7616,7 @@ public: not in the ISO format or if the resulting $(LREF Date) would not be valid. +/ - static Date fromISOString(S)(in S isoString) @safe pure + static Date fromISOString(S)(scope const S isoString) @safe pure if (isSomeString!S) { import std.algorithm.searching : startsWith; @@ -7363,14 +7733,14 @@ public: assert(Date.fromISOString(" 19990706 ") == Date(1999, 7, 6)); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) assert(Date.fromISOString(to!S("20121221")) == Date(2012, 12, 21)); } } @@ -7389,43 +7759,40 @@ public: 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 + static Date fromISOExtString(S)(scope const 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.algorithm.searching : startsWith; + import std.conv : to, ConvException; 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 str = strip(isoExtString); + short year; + ubyte month, day; - auto day = dstr[$-2 .. $]; - auto month = dstr[$-5 .. $-3]; - auto year = dstr[0 .. $-6]; + if (str.length < 10 || str[$-3] != '-' || str[$-6] != '-') + throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)); - 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))); + auto yearStr = str[0 .. $-6]; + auto signAtBegining = cast(bool) yearStr.startsWith('-', '+'); + if ((yearStr.length > 4) != signAtBegining) + { + throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)); + } - if (year.length > 4) + try { - 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))); + day = to!ubyte(str[$-2 .. $]); + month = to!ubyte(str[$-5 .. $-3]); + year = to!short(yearStr); + } + catch (ConvException) + { + throw 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)); + return Date(year, month, day); } /// @@ -7504,14 +7871,14 @@ public: assert(Date.fromISOExtString(" 1999-07-06 ") == Date(1999, 7, 6)); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) assert(Date.fromISOExtString(to!S("2012-12-21")) == Date(2012, 12, 21)); } } @@ -7530,40 +7897,40 @@ public: not in the correct format or if the resulting $(LREF Date) would not be valid. +/ - static Date fromSimpleString(S)(in S simpleString) @safe pure + static Date fromSimpleString(S)(scope const 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.algorithm.searching : startsWith; + import std.conv : to, ConvException; 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 str = strip(simpleString); - auto day = dstr[$-2 .. $]; - auto month = monthFromString(to!string(dstr[$-6 .. $-3])); - auto year = dstr[0 .. $-7]; + if (str.length < 11 || str[$-3] != '-' || str[$-7] != '-') + throw new DateTimeException(format!"Invalid string format: %s"(simpleString)); - 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))); + int year; + uint day; + auto month = monthFromString(str[$ - 6 .. $ - 3]); + auto yearStr = str[0 .. $ - 7]; + auto signAtBegining = cast(bool) yearStr.startsWith('-', '+'); + if ((yearStr.length > 4) != signAtBegining) + { + throw new DateTimeException(format!"Invalid string format: %s"(simpleString)); + } - if (year.length > 4) + try { - enforce(year.startsWith('-', '+'), - new DateTimeException(format("Invalid string format: %s", simpleString))); - enforce(all!isDigit(year[1..$]), - new DateTimeException(format("Invalid string format: %s", simpleString))); + day = to!uint(str[$ - 2 .. $]); + year = to!int(yearStr); + } + catch (ConvException) + { + throw 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)); + return Date(year, month, day); } /// @@ -7642,14 +8009,14 @@ public: assert(Date.fromSimpleString(" 1999-Jul-06 ") == Date(1999, 7, 6)); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) assert(Date.fromSimpleString(to!S("2012-Dec-21")) == Date(2012, 12, 21)); } } @@ -7727,7 +8094,7 @@ package: decrease) to the month would cause it to overflow (or underflow) the current year. - $(D _addDays(numDays)) is effectively equivalent to + `_addDays(numDays)` is effectively equivalent to $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days). Params: @@ -7912,6 +8279,28 @@ package: ubyte _day = 1; } +/// +@safe pure unittest +{ + import core.time : days; + + auto d = Date(2000, 6, 1); + + assert(d.dayOfYear == 153); + assert(d.dayOfWeek == DayOfWeek.thu); + + d += 10.days; + assert(d == Date(2000, 6, 11)); + + assert(d.toISOExtString() == "2000-06-11"); + assert(d.toISOString() == "20000611"); + assert(d.toSimpleString() == "2000-Jun-11"); + + assert(Date.fromISOExtString("2018-01-01") == Date(2018, 1, 1)); + assert(Date.fromISOString("20180101") == Date(2018, 1, 1)); + assert(Date.fromSimpleString("2018-Jan-01") == Date(2018, 1, 1)); +} + /++ Represents a time of day with hours, minutes, and seconds. It uses 24 hour @@ -7983,7 +8372,7 @@ public: $(TR $(TD this > rhs) $(TD > 0)) ) +/ - int opCmp(in TimeOfDay rhs) const @safe pure nothrow @nogc + int opCmp(TimeOfDay rhs) const @safe pure nothrow @nogc { if (_hour < rhs._hour) return -1; @@ -8194,24 +8583,28 @@ public: /++ - Adds the given number of units to this $(LREF TimeOfDay). A negative - number will subtract. + Adds the given number of units to this $(LREF TimeOfDay), mutating it. 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"). + Accepted units are `"hours"`, `"minutes"`, and `"seconds"`. Params: units = The units to add. value = The number of $(D_PARAM units) to add to this $(LREF TimeOfDay). + + Returns: + A reference to the `TimeOfDay` (`this`). +/ ref TimeOfDay roll(string units)(long value) @safe pure nothrow @nogc if (units == "hours") { + import core.time : dur; return this += dur!"hours"(value); } @@ -8256,7 +8649,7 @@ public: } - // Shares documentation with "hours" version. + /// ditto ref TimeOfDay roll(string units)(long value) @safe pure nothrow @nogc if (units == "minutes" || units == "seconds") { @@ -8281,7 +8674,7 @@ public: // Test roll!"minutes"(). @safe unittest { - static void testTOD(TimeOfDay orig, int minutes, in TimeOfDay expected, size_t line = __LINE__) + static void testTOD(TimeOfDay orig, int minutes, TimeOfDay expected, size_t line = __LINE__) { orig.roll!"minutes"(minutes); assert(orig == expected); @@ -8365,7 +8758,7 @@ public: // Test roll!"seconds"(). @safe unittest { - static void testTOD(TimeOfDay orig, int seconds, in TimeOfDay expected, size_t line = __LINE__) + static void testTOD(TimeOfDay orig, int seconds, TimeOfDay expected, size_t line = __LINE__) { orig.roll!"seconds"(seconds); assert(orig == expected); @@ -8436,6 +8829,7 @@ public: } + import core.time : Duration; /++ Gives the result of adding or subtracting a $(REF Duration, core,time) from this $(LREF TimeOfDay). @@ -8480,6 +8874,7 @@ public: { auto tod = TimeOfDay(12, 30, 33); + import core.time : dur; 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)); @@ -8547,6 +8942,7 @@ public: @safe unittest { + import core.time : dur; auto duration = dur!"hours"(12); assert(TimeOfDay(12, 30, 33) + dur!"hours"(7) == TimeOfDay(19, 30, 33)); @@ -8603,12 +8999,13 @@ public: Params: rhs = The $(LREF TimeOfDay) to subtract from this one. +/ - Duration opBinary(string op)(in TimeOfDay rhs) const @safe pure nothrow @nogc + Duration opBinary(string op)(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; + import core.time : dur; return dur!"seconds"(lhsSec - rhsSec); } @@ -8616,6 +9013,7 @@ public: { auto tod = TimeOfDay(12, 30, 33); + import core.time : dur; 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)); @@ -8642,15 +9040,32 @@ public: /++ - Converts this $(LREF TimeOfDay) to a string with the format HHMMSS. + Converts this $(LREF TimeOfDay) to a string with the format `HHMMSS`. + If `writer` is set, the resulting string will be written directly to it. + + Params: + writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toISOString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(6); try - return format("%02d%02d%02d", _hour, _minute, _second); + toISOString(w); catch (Exception e) - assert(0, "format() threw."); + assert(0, "toISOString() threw."); + return w.data; + } + + /// ditto + void toISOString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + formattedWrite(writer, "%02d%02d%02d", _hour, _minute, _second); } /// @@ -8672,15 +9087,32 @@ public: /++ - Converts this $(LREF TimeOfDay) to a string with the format HH:MM:SS. + Converts this $(LREF TimeOfDay) to a string with the format `HH:MM:SS`. + If `writer` is set, the resulting string will be written directly to it. + + Params: + writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toISOExtString() const @safe pure nothrow { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(8); try - return format("%02d:%02d:%02d", _hour, _minute, _second); + toISOExtString(w); catch (Exception e) - assert(0, "format() threw."); + assert(0, "toISOExtString() threw."); + return w.data; + } + + /// ditto + void toISOExtString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + import std.format.write : formattedWrite; + formattedWrite(writer, "%02d:%02d:%02d", _hour, _minute, _second); } /// @@ -8723,12 +9155,24 @@ public: `fromISOString` and `fromISOExtString`. The format returned by toString may or may not change in the future. + + Params: + writer = A `char` accepting $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ string toString() const @safe pure nothrow { return toISOExtString(); } + /// ditto + void toString(W)(ref W writer) const + if (isOutputRange!(W, char)) + { + toISOExtString(writer); + } + @safe unittest { auto tod = TimeOfDay(12, 30, 33); @@ -8752,7 +9196,7 @@ public: not in the ISO format or if the resulting $(LREF TimeOfDay) would not be valid. +/ - static TimeOfDay fromISOString(S)(in S isoString) @safe pure + static TimeOfDay fromISOString(S)(scope const S isoString) @safe pure if (isSomeString!S) { import std.conv : to, text, ConvException; @@ -8851,14 +9295,14 @@ public: assert(TimeOfDay.fromISOString(" 011217 ") == TimeOfDay(1, 12, 17)); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) assert(TimeOfDay.fromISOString(to!S("141516")) == TimeOfDay(14, 15, 16)); } } @@ -8877,34 +9321,32 @@ public: 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 + static TimeOfDay fromISOExtString(S)(scope const 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.conv : ConvException, text, to; import std.string : strip; - auto dstr = to!dstring(strip(isoExtString)); - - enforce(dstr.length == 8, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + auto str = strip(isoExtString); + int hours, minutes, seconds; - auto hours = dstr[0 .. 2]; - auto minutes = dstr[3 .. 5]; - auto seconds = dstr[6 .. $]; + if (str.length != 8 || str[2] != ':' || str[5] != ':') + throw new DateTimeException(text("Invalid ISO Extended String: ", isoExtString)); - 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))); + 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[3 .. 5]); + seconds = cast(int) to!uint(str[6 .. $]); + } + catch (ConvException) + { + throw new DateTimeException(text("Invalid ISO Extended String: ", isoExtString)); + } - return TimeOfDay(to!int(hours), to!int(minutes), to!int(seconds)); + return TimeOfDay(hours, minutes, seconds); } /// @@ -8978,14 +9420,14 @@ public: assert(TimeOfDay.fromISOExtString(" 01:12:17 ") == TimeOfDay(1, 12, 17)); } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) assert(TimeOfDay.fromISOExtString(to!S("14:15:16")) == TimeOfDay(14, 15, 16)); } } @@ -9045,6 +9487,7 @@ private: +/ ref TimeOfDay _addSeconds(long seconds) return @safe pure nothrow @nogc { + import core.time : convert; long hnsecs = convert!("seconds", "hnsecs")(seconds); hnsecs += convert!("hours", "hnsecs")(_hour); hnsecs += convert!("minutes", "hnsecs")(_minute); @@ -9068,7 +9511,7 @@ private: @safe unittest { - static void testTOD(TimeOfDay orig, int seconds, in TimeOfDay expected, size_t line = __LINE__) + static void testTOD(TimeOfDay orig, int seconds, TimeOfDay expected, size_t line = __LINE__) { orig._addSeconds(seconds); assert(orig == expected); @@ -9170,6 +9613,22 @@ package: enum ubyte maxSecond = 60 - 1; } +/// +@safe pure unittest +{ + import core.time : minutes, seconds; + + auto t = TimeOfDay(12, 30, 0); + + t += 10.minutes + 100.seconds; + assert(t == TimeOfDay(12, 41, 40)); + + assert(t.toISOExtString() == "12:41:40"); + assert(t.toISOString() == "124140"); + + assert(TimeOfDay.fromISOExtString("15:00:00") == TimeOfDay(15, 0, 0)); + assert(TimeOfDay.fromISOString("015000") == TimeOfDay(1, 50, 0)); +} /++ Returns whether the given value is valid for the given unit type when in a @@ -9240,7 +9699,7 @@ if (units == "days") thrown. Throws: - $(LREF DateTimeException) if $(D valid!units(value)) is false. + $(LREF DateTimeException) if `valid!units(value)` is false. +/ void enforceValid(string units)(int value, string file = __FILE__, size_t line = __LINE__) @safe pure if (units == "months" || @@ -9272,8 +9731,26 @@ if (units == "months" || } } +/// +@safe pure unittest +{ + import std.exception : assertThrown, assertNotThrown; + + assertNotThrown(enforceValid!"months"(10)); + assertNotThrown(enforceValid!"seconds"(40)); + + assertThrown!DateTimeException(enforceValid!"months"(0)); + assertThrown!DateTimeException(enforceValid!"hours"(24)); + assertThrown!DateTimeException(enforceValid!"minutes"(60)); + assertThrown!DateTimeException(enforceValid!"seconds"(60)); +} + /++ + Because the validity of the day number depends on both on the year + and month of which the day is occurring, take all three variables + to validate the day. + Params: units = The units of time to validate. year = The year of the day to validate. @@ -9295,6 +9772,20 @@ if (units == "days") throw new DateTimeException(format("%s is not a valid day in %s in %s", day, month, year), file, line); } +/// +@safe pure unittest +{ + import std.exception : assertThrown, assertNotThrown; + + assertNotThrown(enforceValid!"days"(2000, Month.jan, 1)); + // leap year + assertNotThrown(enforceValid!"days"(2000, Month.feb, 29)); + + assertThrown!DateTimeException(enforceValid!"days"(2001, Month.feb, 29)); + assertThrown!DateTimeException(enforceValid!"days"(2000, Month.jan, 32)); + assertThrown!DateTimeException(enforceValid!"days"(2000, Month.apr, 31)); +} + /++ Returns the number of days from the current day of the week to the given @@ -9518,20 +10009,20 @@ bool yearIsLeapYear(int year) @safe pure nothrow @nogc 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). + 1. `T` must define a static property named `min` which is the smallest + value of `T` as `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). + 2. `T` must define a static property named `max` which is the largest + value of `T` as `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). + 3. `T` must define an `opBinary` for addition and subtraction that + accepts $(REF Duration, core,time) and returns `Unqual!T`. - 4. $(D T) must define an $(D opOpAssign) for addition and subtraction that + 4. `T` must define an `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). + 5. `T` must define a `opBinary` for subtraction which accepts `T` + and returns $(REF Duration, core,time). +/ template isTimePoint(T) { @@ -9598,13 +10089,13 @@ private: import std.datetime.systime; import std.meta : AliasSeq; - foreach (TP; AliasSeq!(Date, DateTime, SysTime, TimeOfDay)) + static 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 foreach (T; AliasSeq!(float, string, Duration, Interval!Date, PosInfInterval!Date, NegInfInterval!Date)) static assert(!isTimePoint!T, T.stringof); } @@ -9612,7 +10103,7 @@ private: /++ 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 + `"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. +/ @@ -9637,8 +10128,8 @@ bool validTimeUnits(string[] units...) @safe pure nothrow @nogc /++ - Compares two time unit strings. $(D "years") are the largest units and - $(D "hnsecs") are the smallest. + Compares two time unit strings. `"years"` are the largest units and + `"hnsecs"` are the smallest. Returns: $(BOOKTABLE, @@ -9657,12 +10148,11 @@ int cmpTimeUnits(string lhs, string rhs) @safe pure import std.exception : enforce; import std.format : format; - auto tstrings = timeStrings; - immutable indexOfLHS = countUntil(tstrings, lhs); - immutable indexOfRHS = countUntil(tstrings, rhs); + immutable indexOfLHS = countUntil(timeStrings, lhs); + immutable indexOfRHS = countUntil(timeStrings, rhs); - enforce(indexOfLHS != -1, format("%s is not a valid TimeString", lhs)); - enforce(indexOfRHS != -1, format("%s is not a valid TimeString", rhs)); + enforce!DateTimeException(indexOfLHS != -1, format("%s is not a valid TimeString", lhs)); + enforce!DateTimeException(indexOfRHS != -1, format("%s is not a valid TimeString", rhs)); if (indexOfLHS < indexOfRHS) return -1; @@ -9675,9 +10165,13 @@ int cmpTimeUnits(string lhs, string rhs) @safe pure /// @safe pure unittest { + import std.exception : assertThrown; + assert(cmpTimeUnits("hours", "hours") == 0); assert(cmpTimeUnits("hours", "weeks") < 0); assert(cmpTimeUnits("months", "seconds") > 0); + + assertThrown!DateTimeException(cmpTimeUnits("month", "second")); } @safe unittest @@ -9700,11 +10194,11 @@ int cmpTimeUnits(string lhs, string rhs) @safe pure /++ - Compares two time unit strings at compile time. $(D "years") are the largest - units and $(D "hnsecs") are the smallest. + Compares two time unit strings at compile time. `"years"` are the largest + units and `"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 + This template is used instead of `cmpTimeUnits` because exceptions + can't be thrown at compile time and `cmpTimeUnits` must enforce that the strings it's given are valid time unit strings. This template uses a template constraint instead. @@ -9721,6 +10215,13 @@ if (validTimeUnits(lhs, rhs)) enum CmpTimeUnits = cmpTimeUnitsCTFE(lhs, rhs); } +/// +@safe pure unittest +{ + static assert(CmpTimeUnits!("years", "weeks") > 0); + static assert(CmpTimeUnits!("days", "days") == 0); + static assert(CmpTimeUnits!("seconds", "hours") < 0); +} // Helper function for CmpTimeUnits. private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow @nogc @@ -9740,26 +10241,16 @@ private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow @nogc @safe unittest { - import std.format : format; - import std.meta : AliasSeq; - - static string genTest(size_t index) + static foreach (i; 0 .. timeStrings.length) { - 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); + static assert(CmpTimeUnits!(timeStrings[i], timeStrings[i]) == 0); - foreach (units; timeStrings[0 .. index]) - test ~= format(`assert(CmpTimeUnits!("%s", "%s") == 1);`, currUnits, units); + static foreach (next; timeStrings[i + 1 .. $]) + static assert(CmpTimeUnits!(timeStrings[i], next) == -1); - return test; + static foreach (prev; timeStrings[0 .. i]) + static assert(CmpTimeUnits!(timeStrings[i], prev) == 1); } - - static assert(timeStrings.length == 10); - foreach (n; AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) - mixin(genTest(n)); } @@ -9794,7 +10285,7 @@ in { assert(valid!"months"(month)); } -body +do { switch (month) { @@ -10021,7 +10512,8 @@ string monthToString(Month month) @safe pure $(REF DateTimeException,std,datetime,date) if the given month is not a valid month string. +/ -Month monthFromString(string monthStr) @safe pure +Month monthFromString(T)(T monthStr) @safe pure +if (isSomeString!T) { import std.format : format; switch (monthStr) @@ -10051,31 +10543,33 @@ Month monthFromString(string monthStr) @safe pure case "Dec": return Month.dec; default: - throw new DateTimeException(format("Invalid month %s", monthStr)); + throw new DateTimeException(format!"Invalid month %s"(monthStr)); } } @safe unittest { - import std.stdio : writeln; + import std.conv : to; 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)); + assertThrown!DateTimeException(monthFromString(badStr), badStr); } foreach (month; EnumMembers!Month) { - scope(failure) writeln(month); - assert(monthFromString(monthToString(month)) == month); + assert(monthFromString(monthToString(month)) == month, month.to!string); } } -version (unittest) +// NOTE: all the non-simple array literals are wrapped in functions, because +// otherwise importing causes re-evaluation of the static initializers using +// CTFE with unittests enabled +version (StdUnittest) { +private @safe: // 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]; @@ -10093,31 +10587,43 @@ version (unittest) } } - MonthDay[] testMonthDays = [MonthDay(1, 1), + MonthDay[] testMonthDays() + { + static MonthDay[] result = [MonthDay(1, 1), MonthDay(1, 2), MonthDay(3, 17), MonthDay(7, 4), MonthDay(10, 27), MonthDay(12, 30), MonthDay(12, 31)]; + return result; + } auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31]; - auto testTODs = [TimeOfDay(0, 0, 0), + TimeOfDay[] testTODs() + { + static result = [TimeOfDay(0, 0, 0), TimeOfDay(0, 0, 1), TimeOfDay(0, 1, 0), TimeOfDay(1, 0, 0), TimeOfDay(13, 13, 13), TimeOfDay(23, 59, 59)]; + return result; + } 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[] testTODsThrown() + { + static result = [TimeOfDay(0, 0, 0), TimeOfDay(13, 13, 13), TimeOfDay(23, 59, 59)]; + return result; + } Date[] testDatesBC; Date[] testDatesAD; @@ -10127,7 +10633,9 @@ version (unittest) // 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[] testGregDaysBC() + { + static result = [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)), @@ -10209,8 +10717,12 @@ version (unittest) GregDay(-30, Date(0, 12, 1)), GregDay(-1, Date(0, 12, 30)), GregDay(0, Date(0, 12, 31))]; + return result; + } - auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)), + GregDay[] testGregDaysAD() + { + static result = [GregDay(1, Date(1, 1, 1)), GregDay(2, Date(1, 1, 2)), GregDay(32, Date(1, 2, 1)), GregDay(365, Date(1, 12, 31)), @@ -10278,10 +10790,14 @@ version (unittest) GregDay(734_562, Date(2012, 2, 29)), GregDay(734_563, Date(2012, 3, 1)), GregDay(734_858, Date(2012, 12, 21))]; + return result; + } // 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[] testDaysOfYear() + { + static result = [DayOfYear(1, MonthDay(1, 1)), DayOfYear(2, MonthDay(1, 2)), DayOfYear(3, MonthDay(1, 3)), DayOfYear(31, MonthDay(1, 31)), @@ -10309,8 +10825,12 @@ version (unittest) DayOfYear(363, MonthDay(12, 29)), DayOfYear(364, MonthDay(12, 30)), DayOfYear(365, MonthDay(12, 31))]; + return result; + } - auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear[] testDaysOfLeapYear() + { + static result = [DayOfYear(1, MonthDay(1, 1)), DayOfYear(2, MonthDay(1, 2)), DayOfYear(3, MonthDay(1, 3)), DayOfYear(31, MonthDay(1, 31)), @@ -10339,8 +10859,10 @@ version (unittest) DayOfYear(364, MonthDay(12, 29)), DayOfYear(365, MonthDay(12, 30)), DayOfYear(366, MonthDay(12, 31))]; + return result; + } - void initializeTests() @safe + void initializeTests() { foreach (year; testYearsBC) { diff --git a/libphobos/src/std/datetime/interval.d b/libphobos/src/std/datetime/interval.d index d1eec45..741088a 100644 --- a/libphobos/src/std/datetime/interval.d +++ b/libphobos/src/std/datetime/interval.d @@ -1,9 +1,36 @@ // Written in the D programming language /++ +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Main types) $(TD + $(LREF Interval) + $(LREF Direction) +)) +$(TR $(TD Special intervals) $(TD + $(LREF everyDayOfWeek) + $(LREF everyMonth) + $(LREF everyDuration) +)) +$(TR $(TD Special intervals) $(TD + $(LREF NegInfInterval) + $(LREF PosInfInterval) +)) +$(TR $(TD Underlying ranges) $(TD + $(LREF IntervalRange) + $(LREF NegInfIntervalRange) + $(LREF PosInfIntervalRange) +)) +$(TR $(TD Flags) $(TD + $(LREF PopFirst) +)) +)) + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - Authors: Jonathan M Davis - Source: $(PHOBOSSRC std/datetime/_interval.d) + Authors: $(HTTP jmdavisprog.com, Jonathan M Davis) + Source: $(PHOBOSSRC std/datetime/interval.d) +/ module std.datetime.interval; @@ -11,10 +38,11 @@ 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.range.primitives : isOutputRange; +import std.traits : isIntegral; import std.typecons : Flag; -version (unittest) import std.exception : assertThrown; +version (StdUnittest) import std.exception : assertThrown; /++ @@ -37,36 +65,36 @@ enum Direction /++ - Used to indicate whether $(D popFront) should be called immediately upon + Used to indicate whether `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 + range for an interval, `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 + match what the function generates, then use `PopFirst.yes` to indicate + that the range should have `popFront` called on it before the range is + returned so that `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). + use `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 + `PopFirst.yes` would tell the function which returned the range that + `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 + the Easter following the original `front`, while when iterating backward, + it would be the Easter prior to the original `front`). If + `PopFirst.no` were used, then `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 + If set to `PopFirst.no`, then popFront is not called before returning the range. - Otherwise, if set to $(D PopFirst.yes), then popFront is called before + Otherwise, if set to `PopFirst.yes`, then popFront is called before returning the range. +/ alias PopFirst = Flag!"popFirst"; @@ -75,7 +103,7 @@ alias PopFirst = Flag!"popFirst"; /++ Represents an interval of time. - An $(D Interval) has a starting point and an end point. The interval of time + An `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. @@ -85,7 +113,7 @@ alias PopFirst = Flag!"popFirst"; $(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 + A range can be obtained from an `Interval`, allowing iteration over that interval, with the exact time points which are iterated over depending on the function which generates the range. +/ @@ -108,8 +136,8 @@ public: 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)) + this(U)(scope const TP begin, scope const U end) pure + if (is(immutable TP == immutable U)) { if (!_valid(begin, end)) throw new DateTimeException("Arguments would result in an invalid Interval."); @@ -125,7 +153,7 @@ public: Throws: $(REF DateTimeException,std,datetime,date) if the resulting - $(D end) is before $(D begin). + `end` is before `begin`. Example: -------------------- @@ -133,7 +161,7 @@ public: Interval!Date(Date(1996, 1, 2), Date(1996, 1, 5))); -------------------- +/ - this(D)(in TP begin, in D duration) pure + this(D)(scope const TP begin, scope const D duration) pure if (__traits(compiles, begin + duration)) { _begin = cast(TP) begin; @@ -186,7 +214,7 @@ public: The starting point of the interval. It is included in the interval. Params: - timePoint = The time point to set $(D begin) to. + timePoint = The time point to set `begin` to. Throws: $(REF DateTimeException,std,datetime,date) if the resulting @@ -234,7 +262,7 @@ public: /++ - Returns the duration between $(D begin) and $(D end). + Returns the duration between `begin` and `end`. Example: -------------------- @@ -284,7 +312,7 @@ public: Date(2012, 3, 1))); -------------------- +/ - bool contains(in TP timePoint) const pure + bool contains(scope const TP timePoint) const pure { _enforceNotEmpty(); return timePoint >= _begin && timePoint < _end; @@ -313,7 +341,7 @@ public: Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1)))); -------------------- +/ - bool contains(in Interval interval) const pure + bool contains(scope const Interval interval) const pure { _enforceNotEmpty(); interval._enforceNotEmpty(); @@ -343,7 +371,7 @@ public: PosInfInterval!Date(Date(1999, 5, 4)))); -------------------- +/ - bool contains(in PosInfInterval!TP interval) const pure + bool contains(scope const PosInfInterval!TP interval) const pure { _enforceNotEmpty(); return false; @@ -370,7 +398,7 @@ public: NegInfInterval!Date(Date(1996, 5, 4)))); -------------------- +/ - bool contains(in NegInfInterval!TP interval) const pure + bool contains(scope const NegInfInterval!TP interval) const pure { _enforceNotEmpty(); return false; @@ -400,7 +428,7 @@ public: Date(2012, 3, 1))); -------------------- +/ - bool isBefore(in TP timePoint) const pure + bool isBefore(scope const TP timePoint) const pure { _enforceNotEmpty(); return _end <= timePoint; @@ -430,7 +458,7 @@ public: Interval!Date(Date(2012, 3, 1), Date(2013, 5, 1)))); -------------------- +/ - bool isBefore(in Interval interval) const pure + bool isBefore(scope const Interval interval) const pure { _enforceNotEmpty(); interval._enforceNotEmpty(); @@ -458,7 +486,7 @@ public: PosInfInterval!Date(Date(2013, 3, 7)))); -------------------- +/ - bool isBefore(in PosInfInterval!TP interval) const pure + bool isBefore(scope const PosInfInterval!TP interval) const pure { _enforceNotEmpty(); return _end <= interval._begin; @@ -485,7 +513,7 @@ public: NegInfInterval!Date(Date(1996, 5, 4)))); -------------------- +/ - bool isBefore(in NegInfInterval!TP interval) const pure + bool isBefore(scope const NegInfInterval!TP interval) const pure { _enforceNotEmpty(); return false; @@ -515,7 +543,7 @@ public: Date(2012, 3, 1))); -------------------- +/ - bool isAfter(in TP timePoint) const pure + bool isAfter(scope const TP timePoint) const pure { _enforceNotEmpty(); return timePoint < _begin; @@ -545,7 +573,7 @@ public: Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); -------------------- +/ - bool isAfter(in Interval interval) const pure + bool isAfter(scope const Interval interval) const pure { _enforceNotEmpty(); interval._enforceNotEmpty(); @@ -573,7 +601,7 @@ public: PosInfInterval!Date(Date(1999, 5, 4)))); -------------------- +/ - bool isAfter(in PosInfInterval!TP interval) const pure + bool isAfter(scope const PosInfInterval!TP interval) const pure { _enforceNotEmpty(); return false; @@ -597,7 +625,7 @@ public: NegInfInterval!Date(Date(1996, 1, 2)))); -------------------- +/ - bool isAfter(in NegInfInterval!TP interval) const pure + bool isAfter(scope const NegInfInterval!TP interval) const pure { _enforceNotEmpty(); return _begin >= interval._end; @@ -626,7 +654,7 @@ public: Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); -------------------- +/ - bool intersects(in Interval interval) const pure + bool intersects(scope const Interval interval) const pure { _enforceNotEmpty(); interval._enforceNotEmpty(); @@ -653,7 +681,7 @@ public: PosInfInterval!Date(Date(2012, 3, 1)))); -------------------- +/ - bool intersects(in PosInfInterval!TP interval) const pure + bool intersects(scope const PosInfInterval!TP interval) const pure { _enforceNotEmpty(); return _end > interval._begin; @@ -679,7 +707,7 @@ public: NegInfInterval!Date(Date(2000, 1, 2)))); -------------------- +/ - bool intersects(in NegInfInterval!TP interval) const pure + bool intersects(scope const NegInfInterval!TP interval) const pure { _enforceNotEmpty(); return _begin < interval._end; @@ -707,7 +735,7 @@ public: Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17))); -------------------- +/ - Interval intersection(in Interval interval) const + Interval intersection(scope const Interval interval) const { import std.format : format; @@ -742,7 +770,7 @@ public: Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1))); -------------------- +/ - Interval intersection(in PosInfInterval!TP interval) const + Interval intersection(scope const PosInfInterval!TP interval) const { import std.format : format; @@ -774,7 +802,7 @@ public: Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1))); -------------------- +/ - Interval intersection(in NegInfInterval!TP interval) const + Interval intersection(scope const NegInfInterval!TP interval) const { import std.format : format; @@ -808,7 +836,7 @@ public: Interval!Date(Date(1989, 3, 1), Date(2012, 3, 1)))); -------------------- +/ - bool isAdjacent(in Interval interval) const pure + bool isAdjacent(scope const Interval interval) const pure { _enforceNotEmpty(); interval._enforceNotEmpty(); @@ -836,7 +864,7 @@ public: PosInfInterval!Date(Date(2012, 3, 1)))); -------------------- +/ - bool isAdjacent(in PosInfInterval!TP interval) const pure + bool isAdjacent(scope const PosInfInterval!TP interval) const pure { _enforceNotEmpty(); return _end == interval._begin; @@ -863,7 +891,7 @@ public: NegInfInterval!Date(Date(2000, 1, 2)))); -------------------- +/ - bool isAdjacent(in NegInfInterval!TP interval) const pure + bool isAdjacent(scope const NegInfInterval!TP interval) const pure { _enforceNotEmpty(); return _begin == interval._end; @@ -891,7 +919,7 @@ public: Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7))); -------------------- +/ - Interval merge(in Interval interval) const + Interval merge(scope const Interval interval) const { import std.format : format; @@ -926,7 +954,7 @@ public: PosInfInterval!Date(Date(1996, 1 , 2))); -------------------- +/ - PosInfInterval!TP merge(in PosInfInterval!TP interval) const + PosInfInterval!TP merge(scope const PosInfInterval!TP interval) const { import std.format : format; @@ -958,7 +986,7 @@ public: NegInfInterval!Date(Date(2013, 1 , 12))); -------------------- +/ - NegInfInterval!TP merge(in NegInfInterval!TP interval) const + NegInfInterval!TP merge(scope const NegInfInterval!TP interval) const { import std.format : format; @@ -992,7 +1020,7 @@ public: Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7))); -------------------- +/ - Interval span(in Interval interval) const pure + Interval span(scope const Interval interval) const pure { _enforceNotEmpty(); interval._enforceNotEmpty(); @@ -1027,7 +1055,7 @@ public: PosInfInterval!Date(Date(1996, 1 , 2))); -------------------- +/ - PosInfInterval!TP span(in PosInfInterval!TP interval) const pure + PosInfInterval!TP span(scope const PosInfInterval!TP interval) const pure { _enforceNotEmpty(); return PosInfInterval!TP(_begin < interval._begin ? _begin : interval._begin); @@ -1057,7 +1085,7 @@ public: NegInfInterval!Date(Date(2013, 1 , 12))); -------------------- +/ - NegInfInterval!TP span(in NegInfInterval!TP interval) const pure + NegInfInterval!TP span(scope const NegInfInterval!TP interval) const pure { _enforceNotEmpty(); return NegInfInterval!TP(_end > interval._end ? _end : interval._end); @@ -1113,14 +1141,14 @@ public: 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"()) + It effectively calls `add!"years"()` and then `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 + on `begin` and `end`, causing their month to increment. Throws: @@ -1236,15 +1264,15 @@ public: { /++ 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 + it subtracts the given number of months/years from `begin` and + adds them to `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 + on `begin` and `end`, causing their month to increment. dir = The direction in time to expand the interval. @@ -1325,29 +1353,29 @@ public: /++ Returns a range which iterates forward over the interval, starting - at $(D begin), using $(D_PARAM func) to generate each successive time + at `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 + The range's `front` is the interval's `begin`. $(D_PARAM func) is + used to generate the next `front` when `popFront` is called. If + $(D_PARAM popFirst) is `PopFirst.yes`, then `popFront` is called + before the range is returned (so that `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 + current `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. + time point equal to or beyond the `end` of the interval. There are helper functions in this module which generate common - delegates to pass to $(D fwdRange). Their documentation starts with + delegates to pass to `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 + popFirst = Whether `popFront` should be called on the range before returning it. Throws: @@ -1359,14 +1387,14 @@ public: 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 + functions to pass to `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. + `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 + not logically pure to `fwdRange`. If $(D_PARAM func) is given the same time point with two different calls, it must return the same result both times. @@ -1376,7 +1404,7 @@ public: 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. + auto func = delegate (scope const Date date) // For iterating over even-numbered days. { if ((date.day & 1) == 0) return date + dur!"days"(2); @@ -1404,7 +1432,7 @@ public: assert(range.empty); -------------------- +/ - IntervalRange!(TP, Direction.fwd) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + IntervalRange!(TP, Direction.fwd) fwdRange(TP delegate(scope const TP) func, PopFirst popFirst = PopFirst.no) const { _enforceNotEmpty(); @@ -1419,29 +1447,29 @@ public: /++ Returns a range which iterates backwards over the interval, starting - at $(D end), using $(D_PARAM func) to generate each successive time + at `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 + The range's `front` is the interval's `end`. $(D_PARAM func) is + used to generate the next `front` when `popFront` is called. If + $(D_PARAM popFirst) is `PopFirst.yes`, then `popFront` is called + before the range is returned (so that `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 + the current `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. + time point equal to or less than the `begin` of the interval. There are helper functions in this module which generate common - delegates to pass to $(D bwdRange). Their documentation starts with + delegates to pass to `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 + popFirst = Whether `popFront` should be called on the range before returning it. Throws: @@ -1453,14 +1481,14 @@ public: 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 + functions to pass to `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. + `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 + not logically pure to `fwdRange`. If $(D_PARAM func) is given the same time point with two different calls, it must return the same result both times. @@ -1470,7 +1498,7 @@ public: 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. + auto func = delegate (scope const Date date) // For iterating over even-numbered days. { if ((date.day & 1) == 0) return date - dur!"days"(2); @@ -1498,7 +1526,7 @@ public: assert(range.empty); -------------------- +/ - IntervalRange!(TP, Direction.bwd) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + IntervalRange!(TP, Direction.bwd) bwdRange(TP delegate(scope const TP) func, PopFirst popFirst = PopFirst.no) const { _enforceNotEmpty(); @@ -1510,47 +1538,38 @@ public: 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. + Params: + w = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ - // 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 + string toString() const @safe nothrow { - import std.format : format; + import std.array : appender; + auto app = appender!string(); try - return format("[%s - %s)", _begin, _end); + toString(app); catch (Exception e) - assert(0, "format() threw."); + assert(0, "toString() threw."); + return app.data; } + /// ditto + void toString(Writer)(ref Writer w) const + if (isOutputRange!(Writer, char)) + { + import std.range.primitives : put; + put(w, '['); + _begin.toString(w); + put(w, " - "); + _end.toString(w); + put(w, ')'); + } +private: /+ Throws: $(REF DateTimeException,std,datetime,date) if this interval is @@ -1570,7 +1589,7 @@ private: 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 + static bool _valid(scope const TP begin, scope const TP end) pure nothrow @trusted { return begin <= end; } @@ -2350,7 +2369,7 @@ private: auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); - static void testInterval(in Interval!Date interval1, in Interval!Date interval2) + static void testInterval(scope const Interval!Date interval1, scope const Interval!Date interval2) { interval1.isAdjacent(interval2); } @@ -2459,7 +2478,7 @@ private: auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); - static void testInterval(I)(in Interval!Date interval1, in I interval2) + static void testInterval(I)(scope const Interval!Date interval1, scope const I interval2) { interval1.merge(interval2); } @@ -2604,7 +2623,7 @@ private: auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); - static void testInterval(in Interval!Date interval1, in Interval!Date interval2) + static void testInterval(scope const Interval!Date interval1, scope const Interval!Date interval2) { interval1.span(interval2); } @@ -2739,14 +2758,15 @@ private: auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); - static void testIntervalFail(Interval!Date interval, in Duration duration) + static void testIntervalFail(Interval!Date interval, scope const 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__) + static void testInterval(I)(I interval, scope const Duration duration, + scope const I expected, size_t line = __LINE__) { interval.shift(duration); assert(interval == expected); @@ -2832,7 +2852,7 @@ private: auto interval = Interval!Date(Date(2000, 7, 4), Date(2012, 1, 7)); - static void testIntervalFail(I)(I interval, in Duration duration) + static void testIntervalFail(I)(I interval, scope const Duration duration) { interval.expand(duration); } @@ -2840,7 +2860,8 @@ private: 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__) + static void testInterval(I)(I interval, scope const Duration duration, + scope const I expected, size_t line = __LINE__) { interval.expand(duration); assert(interval == expected); @@ -3011,7 +3032,7 @@ private: // Verify Examples. { auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9)); - auto func = delegate (in Date date) + auto func = delegate (scope const Date date) { if ((date.day & 1) == 0) return date + dur!"days"(2); @@ -3079,7 +3100,7 @@ private: // Verify Examples. { auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9)); - auto func = delegate (in Date date) + auto func = delegate (scope const Date date) { if ((date.day & 1) == 0) return date - dur!"days"(2); @@ -3129,8 +3150,8 @@ private: /++ 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 + Any ranges which iterate over a `PosInfInterval` are infinite. So, the + main purpose of using `PosInfInterval` is to create an infinite range which starts at a fixed point in time and goes to positive infinity. +/ struct PosInfInterval(TP) @@ -3146,7 +3167,7 @@ public: auto interval = PosInfInterval!Date(Date(1996, 1, 2)); -------------------- +/ - this(in TP begin) pure nothrow + this(scope const TP begin) pure nothrow { _begin = cast(TP) begin; } @@ -3154,7 +3175,7 @@ auto interval = PosInfInterval!Date(Date(1996, 1, 2)); /++ Params: - rhs = The $(D PosInfInterval) to assign to this one. + rhs = The `PosInfInterval` to assign to this one. +/ ref PosInfInterval opAssign(const ref PosInfInterval rhs) pure nothrow { @@ -3165,7 +3186,7 @@ auto interval = PosInfInterval!Date(Date(1996, 1, 2)); /++ Params: - rhs = The $(D PosInfInterval) to assign to this one. + rhs = The `PosInfInterval` to assign to this one. +/ ref PosInfInterval opAssign(PosInfInterval rhs) pure nothrow { @@ -3192,7 +3213,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).begin == Date(1996, 1, 2)); The starting point of the interval. It is included in the interval. Params: - timePoint = The time point to set $(D begin) to. + timePoint = The time point to set `begin` to. +/ @property void begin(TP timePoint) pure nothrow { @@ -3251,7 +3272,7 @@ 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 + bool contains(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return interval._begin >= _begin; @@ -3273,7 +3294,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains( PosInfInterval!Date(Date(1995, 7, 2)))); -------------------- +/ - bool contains(in PosInfInterval interval) const pure nothrow + bool contains(scope const PosInfInterval interval) const pure nothrow { return interval._begin >= _begin; } @@ -3294,7 +3315,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains( NegInfInterval!Date(Date(1996, 5, 4)))); -------------------- +/ - bool contains(in NegInfInterval!TP interval) const pure nothrow + bool contains(scope const NegInfInterval!TP interval) const pure nothrow { return false; } @@ -3316,7 +3337,7 @@ 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 + bool isBefore(scope const TP timePoint) const pure nothrow { return false; } @@ -3346,7 +3367,7 @@ 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 + bool isBefore(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return false; @@ -3372,7 +3393,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore( PosInfInterval!Date(Date(2013, 3, 7)))); -------------------- +/ - bool isBefore(in PosInfInterval interval) const pure nothrow + bool isBefore(scope const PosInfInterval interval) const pure nothrow { return false; } @@ -3394,7 +3415,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore( NegInfInterval!Date(Date(1996, 5, 4)))); -------------------- +/ - bool isBefore(in NegInfInterval!TP interval) const pure nothrow + bool isBefore(scope const NegInfInterval!TP interval) const pure nothrow { return false; } @@ -3413,7 +3434,7 @@ 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 + bool isAfter(scope const TP timePoint) const pure nothrow { return timePoint < _begin; } @@ -3442,7 +3463,7 @@ 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 + bool isAfter(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return _begin >= interval._end; @@ -3468,7 +3489,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter( PosInfInterval!Date(Date(1999, 5, 4)))); -------------------- +/ - bool isAfter(in PosInfInterval interval) const pure nothrow + bool isAfter(scope const PosInfInterval interval) const pure nothrow { return false; } @@ -3490,7 +3511,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter( NegInfInterval!Date(Date(2000, 7, 1)))); -------------------- +/ - bool isAfter(in NegInfInterval!TP interval) const pure nothrow + bool isAfter(scope const NegInfInterval!TP interval) const pure nothrow { return _begin >= interval._end; } @@ -3518,7 +3539,7 @@ 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 + bool intersects(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return interval._end > _begin; @@ -3544,7 +3565,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects( PosInfInterval!Date(Date(1999, 5, 4)))); -------------------- +/ - bool intersects(in PosInfInterval interval) const pure nothrow + bool intersects(scope const PosInfInterval interval) const pure nothrow { return true; } @@ -3566,7 +3587,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects( NegInfInterval!Date(Date(2000, 7, 1)))); -------------------- +/ - bool intersects(in NegInfInterval!TP interval) const pure nothrow + bool intersects(scope const NegInfInterval!TP interval) const pure nothrow { return _begin < interval._end; } @@ -3593,7 +3614,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17))); -------------------- +/ - Interval!TP intersection(in Interval!TP interval) const + Interval!TP intersection(scope const Interval!TP interval) const { import std.format : format; @@ -3623,7 +3644,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( PosInfInterval!Date(Date(1999, 1 , 12))); -------------------- +/ - PosInfInterval intersection(in PosInfInterval interval) const pure nothrow + PosInfInterval intersection(scope const PosInfInterval interval) const pure nothrow { return PosInfInterval(_begin < interval._begin ? interval._begin : _begin); } @@ -3650,7 +3671,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( Interval!Date(Date(1996, 1 , 2), Date(2013, 1, 12))); -------------------- +/ - Interval!TP intersection(in NegInfInterval!TP interval) const + Interval!TP intersection(scope const NegInfInterval!TP interval) const { import std.format : format; @@ -3681,7 +3702,7 @@ 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 + bool isAdjacent(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return _begin == interval._end; @@ -3707,7 +3728,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( PosInfInterval!Date(Date(1996, 1, 2)))); -------------------- +/ - bool isAdjacent(in PosInfInterval interval) const pure nothrow + bool isAdjacent(scope const PosInfInterval interval) const pure nothrow { return false; } @@ -3729,7 +3750,7 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( NegInfInterval!Date(Date(2000, 7, 1)))); -------------------- +/ - bool isAdjacent(in NegInfInterval!TP interval) const pure nothrow + bool isAdjacent(scope const NegInfInterval!TP interval) const pure nothrow { return _begin == interval._end; } @@ -3747,8 +3768,8 @@ assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( empty. Note: - There is no overload for $(D merge) which takes a - $(D NegInfInterval), because an interval + There is no overload for `merge` which takes a + `NegInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -3763,7 +3784,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( PosInfInterval!Date(Date(1996, 1 , 2))); -------------------- +/ - PosInfInterval merge(in Interval!TP interval) const + PosInfInterval merge(scope const Interval!TP interval) const { import std.format : format; @@ -3781,8 +3802,8 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( interval = The interval to merge with this interval. Note: - There is no overload for $(D merge) which takes a - $(D NegInfInterval), because an interval + There is no overload for `merge` which takes a + `NegInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -3797,7 +3818,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( PosInfInterval!Date(Date(1996, 1 , 2))); -------------------- +/ - PosInfInterval merge(in PosInfInterval interval) const pure nothrow + PosInfInterval merge(scope const PosInfInterval interval) const pure nothrow { return PosInfInterval(_begin < interval._begin ? _begin : interval._begin); } @@ -3817,8 +3838,8 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( is empty. Note: - There is no overload for $(D span) which takes a - $(D NegInfInterval), because an interval + There is no overload for `span` which takes a + `NegInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -3837,7 +3858,7 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).span( PosInfInterval!Date(Date(1996, 1 , 2))); -------------------- +/ - PosInfInterval span(in Interval!TP interval) const pure + PosInfInterval span(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return PosInfInterval(_begin < interval._begin ? _begin : interval._begin); @@ -3854,8 +3875,8 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).span( interval. Note: - There is no overload for $(D span) which takes a - $(D NegInfInterval), because an interval + There is no overload for `span` which takes a + `NegInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -3870,14 +3891,14 @@ assert(PosInfInterval!Date(Date(1996, 1, 2)).span( PosInfInterval!Date(Date(1996, 1 , 2))); -------------------- +/ - PosInfInterval span(in PosInfInterval interval) const pure nothrow + PosInfInterval span(scope const 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 + Shifts the `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). @@ -3908,19 +3929,19 @@ assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13))); __traits(compiles, begin.add!"years"(1))) { /++ - Shifts the $(D begin) of this interval forward or backwards in time + Shifts the `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 + `begin`. It effectively calls `add!"years"()` and then + `add!"months"()` on `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. + on `begin`, causing its month to increment. Throws: $(REF DateTimeException,std,datetime,date) if this interval is @@ -3982,13 +4003,13 @@ assert(interval2 == PosInfInterval!Date(Date(1996, 1, 4))); { /++ Expands the interval forwards and/or backwards in time. Effectively, - it subtracts the given number of months/years from $(D begin). + it subtracts the given number of months/years from `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. + on `begin`, causing its month to increment. Throws: $(REF DateTimeException,std,datetime,date) if this interval is @@ -4021,27 +4042,27 @@ assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2))); /++ Returns a range which iterates forward over the interval, starting - at $(D begin), using $(D_PARAM func) to generate each successive time + at `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 + The range's `front` is the interval's `begin`. $(D_PARAM func) is + used to generate the next `front` when `popFront` is called. If + $(D_PARAM popFirst) is `PopFirst.yes`, then `popFront` is called + before the range is returned (so that `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 + current `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 + delegates to pass to `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 + popFirst = Whether `popFront` should be called on the range before returning it. Throws: @@ -4053,14 +4074,14 @@ assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2))); 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 + functions to pass to `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. + `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 + not logically pure to `fwdRange`. If $(D_PARAM func) is given the same time point with two different calls, it must return the same result both times. @@ -4070,7 +4091,7 @@ assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2))); Example: -------------------- auto interval = PosInfInterval!Date(Date(2010, 9, 1)); -auto func = delegate (in Date date) //For iterating over even-numbered days. +auto func = delegate (scope const Date date) //For iterating over even-numbered days. { if ((date.day & 1) == 0) return date + dur!"days"(2); @@ -4098,7 +4119,7 @@ range.popFront(); assert(!range.empty); -------------------- +/ - PosInfIntervalRange!(TP) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + PosInfIntervalRange!(TP) fwdRange(TP delegate(scope const TP) func, PopFirst popFirst = PopFirst.no) const { auto range = PosInfIntervalRange!(TP)(this, func); @@ -4112,9 +4133,9 @@ assert(!range.empty); /+ 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. + // Due to bug https://issues.dlang.org/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(); @@ -4124,9 +4145,9 @@ assert(!range.empty); /++ 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. + // Due to bug https://issues.dlang.org/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(); @@ -4240,7 +4261,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + static void testInterval(scope const PosInfInterval!Date posInfInterval, scope const Interval!Date interval) { posInfInterval.contains(interval); } @@ -4365,7 +4386,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + static void testInterval(scope const PosInfInterval!Date posInfInterval, scope const Interval!Date interval) { posInfInterval.isBefore(interval); } @@ -4489,7 +4510,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + static void testInterval(scope const PosInfInterval!Date posInfInterval, scope const Interval!Date interval) { posInfInterval.isAfter(interval); } @@ -4586,7 +4607,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + static void testInterval(scope const PosInfInterval!Date posInfInterval, scope const Interval!Date interval) { posInfInterval.intersects(interval); } @@ -4683,7 +4704,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(I, J)(in I interval1, in J interval2) + static void testInterval(I, J)(scope const I interval1, scope const J interval2) { interval1.intersection(interval2); } @@ -4801,7 +4822,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + static void testInterval(scope const PosInfInterval!Date posInfInterval, scope const Interval!Date interval) { posInfInterval.isAdjacent(interval); } @@ -4897,7 +4918,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + static void testInterval(scope const PosInfInterval!Date posInfInterval, scope const Interval!Date interval) { posInfInterval.merge(interval); } @@ -5006,7 +5027,7 @@ private: auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + static void testInterval(scope const PosInfInterval!Date posInfInterval, scope const Interval!Date interval) { posInfInterval.span(interval); } @@ -5117,7 +5138,8 @@ private: auto interval = PosInfInterval!Date(Date(2010, 7, 4)); - static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + static void testInterval(I)(I interval, scope const Duration duration, + scope const I expected, size_t line = __LINE__) { interval.shift(duration); assert(interval == expected); @@ -5196,7 +5218,8 @@ private: auto interval = PosInfInterval!Date(Date(2000, 7, 4)); - static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + static void testInterval(I)(I interval, scope const Duration duration, + scope const I expected, size_t line = __LINE__) { interval.expand(duration); assert(interval == expected); @@ -5290,7 +5313,7 @@ private: //Verify Examples. auto interval = PosInfInterval!Date(Date(2010, 9, 1)); - auto func = delegate (in Date date) + auto func = delegate (scope const Date date) { if ((date.day & 1) == 0) return date + dur!"days"(2); @@ -5338,8 +5361,8 @@ private: 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 + Any ranges which iterate over a `NegInfInterval` are infinite. So, the + main purpose of using `NegInfInterval` is to create an infinite range which starts at negative infinity and goes to a fixed end point. Iterate over it in reverse. +/ @@ -5356,7 +5379,7 @@ public: auto interval = PosInfInterval!Date(Date(1996, 1, 2)); -------------------- +/ - this(in TP end) pure nothrow + this(scope const TP end) pure nothrow { _end = cast(TP) end; } @@ -5364,7 +5387,7 @@ auto interval = PosInfInterval!Date(Date(1996, 1, 2)); /++ Params: - rhs = The $(D NegInfInterval) to assign to this one. + rhs = The `NegInfInterval` to assign to this one. +/ ref NegInfInterval opAssign(const ref NegInfInterval rhs) pure nothrow { @@ -5375,7 +5398,7 @@ auto interval = PosInfInterval!Date(Date(1996, 1, 2)); /++ Params: - rhs = The $(D NegInfInterval) to assign to this one. + rhs = The `NegInfInterval` to assign to this one. +/ ref NegInfInterval opAssign(NegInfInterval rhs) pure nothrow { @@ -5462,7 +5485,7 @@ 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 + bool contains(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return interval._end <= _end; @@ -5484,7 +5507,7 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains( PosInfInterval!Date(Date(1999, 5, 4)))); -------------------- +/ - bool contains(in PosInfInterval!TP interval) const pure nothrow + bool contains(scope const PosInfInterval!TP interval) const pure nothrow { return false; } @@ -5505,7 +5528,7 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains( NegInfInterval!Date(Date(2013, 7, 9)))); -------------------- +/ - bool contains(in NegInfInterval interval) const pure nothrow + bool contains(scope const NegInfInterval interval) const pure nothrow { return interval._end <= _end; } @@ -5525,7 +5548,7 @@ 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 + bool isBefore(scope const TP timePoint) const pure nothrow { return timePoint >= _end; } @@ -5554,7 +5577,7 @@ 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 + bool isBefore(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return _end <= interval._begin; @@ -5577,7 +5600,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore( PosInfInterval!Date(Date(2012, 3, 1)))); -------------------- +/ - bool isBefore(in PosInfInterval!TP interval) const pure nothrow + bool isBefore(scope const PosInfInterval!TP interval) const pure nothrow { return _end <= interval._begin; } @@ -5603,7 +5626,7 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore( NegInfInterval!Date(Date(2013, 7, 9)))); -------------------- +/ - bool isBefore(in NegInfInterval interval) const pure nothrow + bool isBefore(scope const NegInfInterval interval) const pure nothrow { return false; } @@ -5626,7 +5649,7 @@ 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 + bool isAfter(scope const TP timePoint) const pure nothrow { return false; } @@ -5659,7 +5682,7 @@ 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 + bool isAfter(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return false; @@ -5685,7 +5708,7 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( PosInfInterval!Date(Date(2012, 3, 1)))); -------------------- +/ - bool isAfter(in PosInfInterval!TP interval) const pure nothrow + bool isAfter(scope const PosInfInterval!TP interval) const pure nothrow { return false; } @@ -5710,7 +5733,7 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( NegInfInterval!Date(Date(2013, 7, 9)))); -------------------- +/ - bool isAfter(in NegInfInterval interval) const pure nothrow + bool isAfter(scope const NegInfInterval interval) const pure nothrow { return false; } @@ -5738,7 +5761,7 @@ 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 + bool intersects(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return interval._begin < _end; @@ -5761,7 +5784,7 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects( PosInfInterval!Date(Date(2012, 3, 1)))); -------------------- +/ - bool intersects(in PosInfInterval!TP interval) const pure nothrow + bool intersects(scope const PosInfInterval!TP interval) const pure nothrow { return interval._begin < _end; } @@ -5785,7 +5808,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects( NegInfInterval!Date(Date(2013, 7, 9)))); -------------------- +/ - bool intersects(in NegInfInterval!TP interval) const pure nothrow + bool intersects(scope const NegInfInterval!TP interval) const pure nothrow { return true; } @@ -5812,7 +5835,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1))); -------------------- +/ - Interval!TP intersection(in Interval!TP interval) const + Interval!TP intersection(scope const Interval!TP interval) const { import std.format : format; @@ -5846,7 +5869,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1))); -------------------- +/ - Interval!TP intersection(in PosInfInterval!TP interval) const + Interval!TP intersection(scope const PosInfInterval!TP interval) const { import std.format : format; @@ -5874,7 +5897,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( NegInfInterval!Date(Date(2012, 3 , 1))); -------------------- +/ - NegInfInterval intersection(in NegInfInterval interval) const nothrow + NegInfInterval intersection(scope const NegInfInterval interval) const nothrow { return NegInfInterval(_end < interval._end ? _end : interval._end); } @@ -5906,7 +5929,7 @@ 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 + bool isAdjacent(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return interval._begin == _end; @@ -5929,7 +5952,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( PosInfInterval!Date(Date(2012, 3, 1)))); -------------------- +/ - bool isAdjacent(in PosInfInterval!TP interval) const pure nothrow + bool isAdjacent(scope const PosInfInterval!TP interval) const pure nothrow { return interval._begin == _end; } @@ -5954,7 +5977,7 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( NegInfInterval!Date(Date(2012, 3, 1)))); -------------------- +/ - bool isAdjacent(in NegInfInterval interval) const pure nothrow + bool isAdjacent(scope const NegInfInterval interval) const pure nothrow { return false; } @@ -5971,8 +5994,8 @@ assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( 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 + There is no overload for `merge` which takes a + `PosInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -5987,7 +6010,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( NegInfInterval!Date(Date(2015, 9 , 2))); -------------------- +/ - NegInfInterval merge(in Interval!TP interval) const + NegInfInterval merge(scope const Interval!TP interval) const { import std.format : format; @@ -6005,8 +6028,8 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( interval = The interval to merge with this interval. Note: - There is no overload for $(D merge) which takes a - $(D PosInfInterval), because an interval + There is no overload for `merge` which takes a + `PosInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -6021,7 +6044,7 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( NegInfInterval!Date(Date(2013, 1 , 12))); -------------------- +/ - NegInfInterval merge(in NegInfInterval interval) const pure nothrow + NegInfInterval merge(scope const NegInfInterval interval) const pure nothrow { return NegInfInterval(_end > interval._end ? _end : interval._end); } @@ -6041,8 +6064,8 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( is empty. Note: - There is no overload for $(D span) which takes a - $(D PosInfInterval), because an interval + There is no overload for `span` which takes a + `PosInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -6061,7 +6084,7 @@ assert(NegInfInterval!Date(Date(1600, 1, 7)).span( NegInfInterval!Date(Date(2017, 7 , 1))); -------------------- +/ - NegInfInterval span(in Interval!TP interval) const pure + NegInfInterval span(scope const Interval!TP interval) const pure { interval._enforceNotEmpty(); return NegInfInterval(_end > interval._end ? _end : interval._end); @@ -6078,8 +6101,8 @@ assert(NegInfInterval!Date(Date(1600, 1, 7)).span( interval. Note: - There is no overload for $(D span) which takes a - $(D PosInfInterval), because an interval + There is no overload for `span` which takes a + `PosInfInterval`, because an interval going from negative infinity to positive infinity is not possible. @@ -6094,14 +6117,14 @@ assert(NegInfInterval!Date(Date(2012, 3, 1)).span( NegInfInterval!Date(Date(2013, 1 , 12))); -------------------- +/ - NegInfInterval span(in NegInfInterval interval) const pure nothrow + NegInfInterval span(scope const 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 + Shifts the `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). @@ -6132,18 +6155,18 @@ assert(interval2 == NegInfInterval!Date( Date(2012, 2, 15))); __traits(compiles, end.add!"years"(1))) { /++ - Shifts the $(D end) of this interval forward or backwards in time by + Shifts the `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"()) + effectively calls `add!"years"()` and then `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. + on `end`, causing its month to increment. Throws: $(REF DateTimeException,std,datetime,date) if empty is true or @@ -6211,7 +6234,7 @@ assert(interval2 == NegInfInterval!Date(Date(2012, 2, 28))); 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. + on `end`, causing their month to increment. Throws: $(REF DateTimeException,std,datetime,date) if empty is true or @@ -6244,27 +6267,27 @@ assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1))); /++ Returns a range which iterates backwards over the interval, starting - at $(D end), using $(D_PARAM func) to generate each successive time + at `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 + The range's `front` is the interval's `end`. $(D_PARAM func) is + used to generate the next `front` when `popFront` is called. If + $(D_PARAM popFirst) is `PopFirst.yes`, then `popFront` is called + before the range is returned (so that `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 + the current `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 + delegates to pass to `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 + popFirst = Whether `popFront` should be called on the range before returning it. Throws: @@ -6276,14 +6299,14 @@ assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1))); 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 + functions to pass to `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. + `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 + not logically pure to `fwdRange`. If $(D_PARAM func) is given the same time point with two different calls, it must return the same result both times. @@ -6293,7 +6316,7 @@ assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1))); Example: -------------------- auto interval = NegInfInterval!Date(Date(2010, 9, 9)); -auto func = delegate (in Date date) //For iterating over even-numbered days. +auto func = delegate (scope const Date date) //For iterating over even-numbered days. { if ((date.day & 1) == 0) return date - dur!"days"(2); @@ -6320,7 +6343,7 @@ range.popFront(); assert(!range.empty); -------------------- +/ - NegInfIntervalRange!(TP) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + NegInfIntervalRange!(TP) bwdRange(TP delegate(scope const TP) func, PopFirst popFirst = PopFirst.no) const { auto range = NegInfIntervalRange!(TP)(this, func); @@ -6334,9 +6357,9 @@ assert(!range.empty); /+ 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. + // Due to bug https://issues.dlang.org/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(); @@ -6346,9 +6369,9 @@ assert(!range.empty); /++ 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. + // Due to bug https://issues.dlang.org/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(); @@ -6460,7 +6483,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + static void testInterval(scope const NegInfInterval!Date negInfInterval, scope const Interval!Date interval) { negInfInterval.contains(interval); } @@ -6586,7 +6609,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + static void testInterval(scope const NegInfInterval!Date negInfInterval, scope const Interval!Date interval) { negInfInterval.isBefore(interval); } @@ -6708,7 +6731,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + static void testInterval(scope const NegInfInterval!Date negInfInterval, scope const Interval!Date interval) { negInfInterval.isAfter(interval); } @@ -6809,7 +6832,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + static void testInterval(scope const NegInfInterval!Date negInfInterval, scope const Interval!Date interval) { negInfInterval.intersects(interval); } @@ -6906,7 +6929,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(I, J)(in I interval1, in J interval2) + static void testInterval(I, J)(scope const I interval1, scope const J interval2) { interval1.intersection(interval2); } @@ -7024,7 +7047,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + static void testInterval(scope const NegInfInterval!Date negInfInterval, scope const Interval!Date interval) { negInfInterval.isAdjacent(interval); } @@ -7122,7 +7145,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(I, J)(in I interval1, in J interval2) + static void testInterval(I, J)(scope const I interval1, scope const J interval2) { interval1.merge(interval2); } @@ -7231,7 +7254,7 @@ private: auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(I, J)(in I interval1, in J interval2) + static void testInterval(I, J)(scope const I interval1, scope const J interval2) { interval1.span(interval2); } @@ -7342,7 +7365,8 @@ private: auto interval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + static void testInterval(I)(I interval, scope const Duration duration, + scope const I expected, size_t line = __LINE__) { interval.shift(duration); assert(interval == expected); @@ -7426,7 +7450,8 @@ private: auto interval = NegInfInterval!Date(Date(2012, 1, 7)); - static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + static void testInterval(I)(I interval, + scope const Duration duration, scope const I expected, size_t line = __LINE__) { interval.expand(duration); assert(interval == expected); @@ -7520,7 +7545,7 @@ private: //Verify Examples. auto interval = NegInfInterval!Date(Date(2010, 9, 9)); - auto func = delegate (in Date date) + auto func = delegate (scope const Date date) { if ((date.day & 1) == 0) return date - dur!"days"(2); @@ -7570,27 +7595,27 @@ private: Range-generating function. Returns a delegate which returns the next time point with the given - $(D DayOfWeek) in a range. + `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 + are all the same day of the week. e.g. passing `DayOfWeek.mon` to + `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). + `fwdRange`, use `Direction.fwd`. If passing it to + `bwdRange`, use `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 +TP delegate(scope const 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 func(scope const TP tp) { TP retval = cast(TP) tp; immutable days = daysToDayOfWeek(retval.dayOfWeek, dayOfWeek); @@ -7678,22 +7703,22 @@ if (isTimePoint!TP && 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. + date which had the 25th as its day and passed `Month.dec` to + `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 + after the previous time point, `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). + `fwdRange`, use `Direction.fwd`. If passing it to + `bwdRange`, use `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) +TP delegate(scope const TP) everyMonth(TP, Direction dir = Direction.fwd)(int month) if (isTimePoint!TP && (dir == Direction.fwd || dir == Direction.bwd) && __traits(hasMember, TP, "month") && @@ -7704,7 +7729,7 @@ if (isTimePoint!TP && enforceValid!"months"(month); - TP func(in TP tp) + TP func(scope const TP tp) { TP retval = cast(TP) tp; immutable months = monthsToMonth(retval.month, month); @@ -7812,23 +7837,23 @@ if (isTimePoint!TP && 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 + are apart by the given duration e.g. passing `dur!"days"(3)` to + `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). + `fwdRange`, use `Direction.fwd`. If passing it to + `bwdRange`, use `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 +TP delegate(scope const 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) + TP func(scope const TP tp) { static if (dir == Direction.fwd) return tp + duration; @@ -7896,39 +7921,39 @@ if (isTimePoint!TP && 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 + The difference between this version of `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 + the number of years and months (along with an `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 + Note that if iterating forward, `add!"years"()` is called on the given + time point, then `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. + `add!"months"()` is called, and finally `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 + `AllowDayOverflow.yes` or `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)). + from `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). + value to `fwdRange`, use `Direction.fwd`. If + passing it to `bwdRange`, use `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 + `begin` and `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) +TP delegate(scope const TP) everyDuration(TP, Direction dir = Direction.fwd, D) (int years, int months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes, @@ -7939,7 +7964,7 @@ if (isTimePoint!TP && __traits(compiles, TP.init.add!"months"(months)) && (dir == Direction.fwd || dir == Direction.bwd)) { - TP func(in TP tp) + TP func(scope const TP tp) { static if (dir == Direction.fwd) { @@ -8039,24 +8064,24 @@ if (isTimePoint!TP && /++ 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 + `IntervalRange` is only ever constructed by $(LREF Interval). However, when + it is constructed, it is given a function, `func`, which is used to + generate the time points which are iterated over. `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 + the interval `Interval!Date`, pass a function to $(LREF Interval)'s + `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 + function would then be used by `IntervalRange`'s `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 + $(D dir == Direction.bwd) then $(D front == interval.end). `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 + forward in time, the time point that `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 @@ -8065,20 +8090,20 @@ if (isTimePoint!TP && 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. + `end`, then `front` becomes `end`. If iterating backwards, and the + generated time point is before `begin`, then `front` becomes + `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 + Also note that while normally the `begin` of an interval is included in + it and its `end` is excluded from it, if $(D dir == Direction.bwd), then + `begin` is treated as excluded and `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) + $(LREF Interval)'s functions which care about whether `begin` or `end` + is included or excluded are ever called by `IntervalRange`. `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. + called on it which care about whether `begin` or `end` are included or + excluded will treat `begin` as included and `end` as excluded. +/ struct IntervalRange(TP, Direction dir) if (isTimePoint!TP && dir != Direction.both) @@ -8087,7 +8112,7 @@ public: /++ Params: - rhs = The $(D IntervalRange) to assign to this one. + rhs = The `IntervalRange` to assign to this one. +/ ref IntervalRange opAssign(ref IntervalRange rhs) pure nothrow { @@ -8105,7 +8130,7 @@ public: /++ - Whether this $(D IntervalRange) is empty. + Whether this `IntervalRange` is empty. +/ @property bool empty() const pure nothrow { @@ -8131,20 +8156,20 @@ public: /++ - Pops $(D front) from the range, using $(D func) to generate the next + Pops `front` from the range, using `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 + of the range, then `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). + greater than the interval's `end`, then `front` is set to + `end`. If iterating backwards, and the generated time point is less + than the interval's `begin`, then `front` is set to `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), + iterating forward and the generated time point is before `front`, or if iterating backwards and the generated time point is after - $(D front)). + `front`). +/ void popFront() { @@ -8176,7 +8201,7 @@ public: /++ - Returns a copy of $(D this). + Returns a copy of `this`. +/ @property IntervalRange save() pure nothrow { @@ -8185,7 +8210,7 @@ public: /++ - The interval that this $(D IntervalRange) currently covers. + The interval that this `IntervalRange` currently covers. +/ @property Interval!TP interval() const pure nothrow { @@ -8196,14 +8221,14 @@ public: /++ The function used to generate the next time point in the range. +/ - TP delegate(in TP) func() pure nothrow @property + TP delegate(scope const TP) func() pure nothrow @property { return _func; } /++ - The $(D Direction) that this range iterates in. + The `Direction` that this range iterates in. +/ @property Direction direction() const pure nothrow { @@ -8219,7 +8244,7 @@ private: 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 + this(const Interval!TP interval, TP delegate(scope const TP) func) pure nothrow @safe { _func = func; _interval = interval; @@ -8243,7 +8268,7 @@ private: $(REF DateTimeException,std,datetime,date) if $(D_PARAM newTP) is in the wrong direction. +/ - void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const + void _enforceCorrectDirection(scope const TP newTP, size_t line = __LINE__) const { import std.format : format; @@ -8269,7 +8294,7 @@ private: Interval!TP _interval; - TP delegate(in TP) _func; + TP delegate(scope const TP) _func; } //Test that IntervalRange satisfies the range predicates that it's supposed to satisfy. @@ -8282,8 +8307,8 @@ private: 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)); + // Commented out due to bug https://issues.dlang.org/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))); @@ -8306,25 +8331,25 @@ private: import std.datetime.systime; { - Date dateFunc(in Date date) { return date; } + Date dateFunc(scope const 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; } + TimeOfDay todFunc(scope const 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; } + DateTime dtFunc(scope const 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; } + SysTime stFunc(scope const 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); @@ -8378,7 +8403,8 @@ private: { 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)); + assertThrown!DateTimeException( + (scope const 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)); @@ -8395,7 +8421,8 @@ private: { 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)); + assertThrown!DateTimeException( + (scope const 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)); @@ -8573,27 +8600,27 @@ private: /++ - A range over a $(D PosInfInterval). It is an infinite range. + A range over a `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) + `PosInfIntervalRange` is only ever constructed by `PosInfInterval`. + However, when it is constructed, it is given a function, `func`, which + is used to generate the time points which are iterated over. `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 + over all of the days in the interval `PosInfInterval!Date`, pass a + function to `PosInfInterval`'s `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 + `PosInfIntervalRange`'s `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 + range is infinite, use a function such as `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 + forwards, never backwards. `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 + points that `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. +/ @@ -8604,7 +8631,7 @@ public: /++ Params: - rhs = The $(D PosInfIntervalRange) to assign to this one. + rhs = The `PosInfIntervalRange` to assign to this one. +/ ref PosInfIntervalRange opAssign(ref PosInfIntervalRange rhs) pure nothrow { @@ -8637,12 +8664,12 @@ public: /++ - Pops $(D front) from the range, using $(D func) to generate the next + Pops `front` from the range, using `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). + point is less than `front`. +/ void popFront() { @@ -8653,7 +8680,7 @@ public: /++ - Returns a copy of $(D this). + Returns a copy of `this`. +/ @property PosInfIntervalRange save() pure nothrow { @@ -8673,7 +8700,7 @@ public: /++ The function used to generate the next time point in the range. +/ - TP delegate(in TP) func() pure nothrow @property + TP delegate(scope const TP) func() pure nothrow @property { return _func; } @@ -8687,7 +8714,7 @@ private: 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 + this(const PosInfInterval!TP interval, TP delegate(scope const TP) func) pure nothrow { _func = func; _interval = interval; @@ -8699,7 +8726,7 @@ private: $(REF DateTimeException,std,datetime,date) if $(D_PARAM newTP) is in the wrong direction. +/ - void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const + void _enforceCorrectDirection(scope const TP newTP, size_t line = __LINE__) const { import std.format : format; @@ -8713,7 +8740,7 @@ private: PosInfInterval!TP _interval; - TP delegate(in TP) _func; + TP delegate(scope const TP) _func; } //Test that PosInfIntervalRange satisfies the range predicates that it's supposed to satisfy. @@ -8727,8 +8754,8 @@ private: 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)); + // Commented out due to bug https://issues.dlang.org/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)); @@ -8749,25 +8776,25 @@ private: import std.datetime.systime; { - Date dateFunc(in Date date) { return date; } + Date dateFunc(scope const 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; } + TimeOfDay todFunc(scope const 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; } + DateTime dtFunc(scope const 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; } + SysTime stFunc(scope const SysTime st) { return cast(SysTime) st; } auto posInfInterval = PosInfInterval!SysTime(SysTime(DateTime(2010, 7, 4, 12, 1, 7))); auto ir = PosInfIntervalRange!SysTime(posInfInterval, &stFunc); } @@ -8848,37 +8875,37 @@ private: /++ - A range over a $(D NegInfInterval). It is an infinite range. + A range over a `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) + `NegInfIntervalRange` is only ever constructed by `NegInfInterval`. + However, when it is constructed, it is given a function, `func`, which + is used to generate the time points which are iterated over. `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 + `NegInfInterval!Date`, pass a function to `NegInfInterval`'s + `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 + function would then be used by `NegInfIntervalRange`'s `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. + `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 + backwards, never forwards. `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 + points that `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. + Also note that while normally the `end` of an interval is excluded from + it, `NegInfIntervalRange` treats it as if it were included. This allows + for the same behavior as with `PosInfIntervalRange`. This works + because none of `NegInfInterval`'s functions which care about whether + `end` is included or excluded are ever called by + `NegInfIntervalRange`. `interval` returns a normal interval, so any + `NegInfInterval` functions which are called on it which care about + whether `end` is included or excluded will treat `end` as excluded. +/ struct NegInfIntervalRange(TP) if (isTimePoint!TP) @@ -8887,7 +8914,7 @@ public: /++ Params: - rhs = The $(D NegInfIntervalRange) to assign to this one. + rhs = The `NegInfIntervalRange` to assign to this one. +/ ref NegInfIntervalRange opAssign(ref NegInfIntervalRange rhs) pure nothrow { @@ -8921,12 +8948,12 @@ public: /++ - Pops $(D front) from the range, using $(D func) to generate the next + Pops `front` from the range, using `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). + point is greater than `front`. +/ void popFront() { @@ -8937,7 +8964,7 @@ public: /++ - Returns a copy of $(D this). + Returns a copy of `this`. +/ @property NegInfIntervalRange save() pure nothrow { @@ -8957,7 +8984,7 @@ public: /++ The function used to generate the next time point in the range. +/ - TP delegate(in TP) func() pure nothrow @property + TP delegate(scope const TP) func() pure nothrow @property { return _func; } @@ -8971,7 +8998,7 @@ private: 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 + this(const NegInfInterval!TP interval, TP delegate(scope const TP) func) pure nothrow { _func = func; _interval = interval; @@ -8983,7 +9010,7 @@ private: $(REF DateTimeException,std,datetime,date) if $(D_PARAM newTP) is in the wrong direction. +/ - void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const + void _enforceCorrectDirection(scope const TP newTP, size_t line = __LINE__) const { import std.format : format; @@ -8997,7 +9024,7 @@ private: NegInfInterval!TP _interval; - TP delegate(in TP) _func; + TP delegate(scope const TP) _func; } //Test that NegInfIntervalRange satisfies the range predicates that it's supposed to satisfy. @@ -9010,8 +9037,8 @@ private: 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)); + // Commented out due to bug https://issues.dlang.org/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)); @@ -9031,25 +9058,25 @@ private: import std.datetime.systime; { - Date dateFunc(in Date date) { return date; } + Date dateFunc(scope const 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; } + TimeOfDay todFunc(scope const 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; } + DateTime dtFunc(scope const 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); } + SysTime stFunc(scope const SysTime st) { return cast(SysTime)(st); } auto negInfInterval = NegInfInterval!SysTime(SysTime(DateTime(2012, 1, 7, 14, 0, 0))); auto ir = NegInfIntervalRange!(SysTime)(negInfInterval, &stFunc); } diff --git a/libphobos/src/std/datetime/package.d b/libphobos/src/std/datetime/package.d index 976d06d..8e9f5ae 100644 --- a/libphobos/src/std/datetime/package.d +++ b/libphobos/src/std/datetime/package.d @@ -1,102 +1,80 @@ // Written in the D programming language /++ - Module containing Date/Time functionality. + $(SCRIPT inhibitQuickIndex = 1;) - 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. + Phobos provides the following functionality for time: - 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); --------------------- + $(DIVC quickindex, + $(BOOKTABLE , + $(TR $(TH Functionality) $(TH Symbols) + ) + $(TR + $(TD Points in Time) + $(TD + $(REF_ALTTEXT Date, Date, std, datetime, date)$(NBSP) + $(REF_ALTTEXT TimeOfDay, TimeOfDay, std, datetime, date)$(NBSP) + $(REF_ALTTEXT DateTime, DateTime, std, datetime, date)$(NBSP) + $(REF_ALTTEXT SysTime, SysTime, std, datetime, systime)$(NBSP) + ) + ) + $(TR + $(TD Timezones) + $(TD + $(REF_ALTTEXT TimeZone, TimeZone, std, datetime, timezone)$(NBSP) + $(REF_ALTTEXT UTC, UTC, std, datetime, timezone)$(NBSP) + $(REF_ALTTEXT LocalTime, LocalTime, std, datetime, timezone)$(NBSP) + $(REF_ALTTEXT PosixTimeZone, PosixTimeZone, std, datetime, timezone)$(NBSP) + $(REF_ALTTEXT WindowsTimeZone, WindowsTimeZone, std, datetime, timezone)$(NBSP) + $(REF_ALTTEXT SimpleTimeZone, SimpleTimeZone, std, datetime, timezone)$(NBSP) + ) + ) + $(TR + $(TD Intervals and Ranges of Time) + $(TD + $(REF_ALTTEXT Interval, Interval, std, datetime, interval)$(NBSP) + $(REF_ALTTEXT PosInfInterval, PosInfInterval, std, datetime, interval)$(NBSP) + $(REF_ALTTEXT NegInfInterval, NegInfInterval, std, datetime, interval)$(NBSP) + ) + ) + $(TR + $(TD Durations of Time) + $(TD + $(REF_ALTTEXT Duration, Duration, core, time)$(NBSP) + $(REF_ALTTEXT weeks, weeks, core, time)$(NBSP) + $(REF_ALTTEXT days, days, core, time)$(NBSP) + $(REF_ALTTEXT hours, hours, core, time)$(NBSP) + $(REF_ALTTEXT minutes, minutes, core, time)$(NBSP) + $(REF_ALTTEXT seconds, seconds, core, time)$(NBSP) + $(REF_ALTTEXT msecs, msecs, core, time)$(NBSP) + $(REF_ALTTEXT usecs, usecs, core, time)$(NBSP) + $(REF_ALTTEXT hnsecs, hnsecs, core, time)$(NBSP) + $(REF_ALTTEXT nsecs, nsecs, core, time)$(NBSP) + ) + ) + $(TR + $(TD Time Measurement and Benchmarking) + $(TD + $(REF_ALTTEXT MonoTime, MonoTime, core, time)$(NBSP) + $(REF_ALTTEXT StopWatch, StopWatch, std, datetime, stopwatch)$(NBSP) + $(REF_ALTTEXT benchmark, benchmark, std, datetime, stopwatch)$(NBSP) + ) + ) + )) - 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). + This functionality is separated into the following modules - 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)). + $(UL + $(LI $(MREF std, datetime, date) for points in time without timezones.) + $(LI $(MREF std, datetime, timezone) for classes which represent timezones.) + $(LI $(MREF std, datetime, systime) for a point in time with a timezone.) + $(LI $(MREF std, datetime, interval) for types which represent series of points in time.) + $(LI $(MREF std, datetime, stopwatch) for measuring time.) + ) See_Also: - $(DDLINK intro-to-_datetime, Introduction to std.datetime, - Introduction to std._datetime)
+ $(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)
@@ -104,11 +82,48 @@ auto restoredTime = SysTime.fromISOExtString(timeString); 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) + Authors: $(HTTP jmdavisprog.com, Jonathan M Davis) and Kato Shoichi + Source: $(PHOBOSSRC std/datetime/package.d) +/ module std.datetime; +/// Get the current time from the system clock +@safe unittest +{ + import std.datetime.systime : SysTime, Clock; + + SysTime currentTime = Clock.currTime(); +} + +/** +Construct a specific point in time without timezone information +and get its ISO string. + */ +@safe unittest +{ + import std.datetime.date : DateTime; + + auto dt = DateTime(2018, 1, 1, 12, 30, 10); + assert(dt.toISOString() == "20180101T123010"); + assert(dt.toISOExtString() == "2018-01-01T12:30:10"); +} + +/** +Construct a specific point in time in the UTC timezone and +add two days. + */ +@safe unittest +{ + import std.datetime.systime : SysTime; + import std.datetime.timezone : UTC; + import core.time : days; + + auto st = SysTime(DateTime(2018, 1, 1, 12, 30, 10), UTC()); + assert(st.toISOExtString() == "2018-01-01T12:30:10Z"); + st += 2.days; + assert(st.toISOExtString() == "2018-01-03T12:30:10Z"); +} + public import core.time; public import std.datetime.date; public import std.datetime.interval; @@ -142,592 +157,9 @@ import std.typecons : Flag, Yes, No; @safe unittest { import std.traits : hasUnsharedAliasing; - /* Issue 6642 */ + /* https://issues.dlang.org/show_bug.cgi?id=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 index 5d2a980..d8ecf7d 100644 --- a/libphobos/src/std/datetime/stopwatch.d +++ b/libphobos/src/std/datetime/stopwatch.d @@ -5,17 +5,46 @@ For convenience, this module publicly imports $(MREF core,time). +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Main functionality) $(TD + $(LREF StopWatch) + $(LREF benchmark) +)) +$(TR $(TD Flags) $(TD + $(LREF AutoStart) +)) +)) + $(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.) + gone through the deprecation cycle and have been fully removed, then + this module will be publicly imported in std.datetime.package. The + old, deprecated symbols has been removed from the documentation in + December 2019 and currently scheduled to be fully removed from Phobos + after 2.094.) + + So, for now, when using std.datetime.stopwatch, if other modules from + std.datetime are needed, then either import them individually rather than + importing std.datetime, or use selective or static imports to import + std.datetime.stopwatch. e.g. + + ---------------------------------------------------------------------------- + import std.datetime; + import std.datetime.stopwatch : benchmark, StopWatch; + ---------------------------------------------------------------------------- + + The compiler will then know to use the symbols from std.datetime.stopwatch + rather than the deprecated ones from 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) + Authors: $(HTTP jmdavisprog.com, Jonathan M Davis) and Kato Shoichi + Source: $(PHOBOSSRC std/datetime/stopwatch.d) +/ module std.datetime.stopwatch; @@ -23,75 +52,42 @@ public import core.time; import std.typecons : Flag; /++ - Used by StopWatch to indicate whether it should start immediately upon - construction. + 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. + If set to `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. + Otherwise, if set to `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. + 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 + If `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 @@ -183,7 +179,7 @@ public: +/ void start() @safe nothrow @nogc in { assert(!_running, "start was called when the StopWatch was already running."); } - body + do { _running = true; _timeStarted = MonoTime.currTime; @@ -211,7 +207,7 @@ public: +/ void stop() @safe nothrow @nogc in { assert(_running, "stop was called when the StopWatch was not running."); } - body + do { _running = false; _ticksElapsed += MonoTime.currTime.ticks - _timeStarted.ticks; @@ -244,7 +240,7 @@ public: 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). + Calling $(LREF reset) will reset this to `Duration.zero`. +/ Duration peek() @safe const nothrow @nogc { @@ -349,6 +345,55 @@ private: long _ticksElapsed; // Total time that the StopWatch ran before it was stopped last. } +/// Measure a time in milliseconds, microseconds, or nanoseconds +@safe nothrow @nogc unittest +{ + auto sw = StopWatch(AutoStart.no); + sw.start(); + // ... Insert operations to be timed here ... + sw.stop(); + + long msecs = sw.peek.total!"msecs"; + long usecs = sw.peek.total!"usecs"; + long nsecs = sw.peek.total!"nsecs"; + + assert(usecs >= msecs * 1000); + assert(nsecs >= usecs * 1000); +} + +/// +@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; +} + /++ Benchmarks code for speed assessment and comparison. @@ -360,9 +405,9 @@ private: 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. + each function `n` times. The first value is the length of time that + it took to call `fun[0]` `n` times. The second value is the length + of time it took to call `fun[1]` `n` times. Etc. +/ Duration[fun.length] benchmark(fun...)(uint n) { @@ -401,13 +446,22 @@ Duration[fun.length] benchmark(fun...)(uint n) int a; void f0() nothrow {} - void f1() nothrow { auto b = to!string(a); } + void f1() nothrow @trusted { + // do not allow any optimizer to optimize this function away + import core.thread : getpid; + import core.stdc.stdio : printf; + auto b = getpid.to!string; + if (getpid == 1) // never happens, but prevents optimization + printf("%p", &b); + } + + auto sw = StopWatch(AutoStart.yes); auto r = benchmark!(f0, f1)(1000); + auto total = sw.peek(); 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)); + assert(r[1] >= Duration.zero); + assert(r[0] <= total); + assert(r[1] <= total); } @safe nothrow @nogc unittest diff --git a/libphobos/src/std/datetime/systime.d b/libphobos/src/std/datetime/systime.d index 913d360..18b8524 100644 --- a/libphobos/src/std/datetime/systime.d +++ b/libphobos/src/std/datetime/systime.d @@ -1,9 +1,34 @@ // Written in the D programming language /++ + +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Types) $(TD + $(LREF Clock) + $(LREF SysTime) + $(LREF DosFileTime) +)) +$(TR $(TD Conversion) $(TD + $(LREF parseRFC822DateTime) + $(LREF DosFileTimeToSysTime) + $(LREF FILETIMEToStdTime) + $(LREF FILETIMEToSysTime) + $(LREF stdTimeToFILETIME) + $(LREF stdTimeToUnixTime) + $(LREF SYSTEMTIMEToSysTime) + $(LREF SysTimeToDosFileTime) + $(LREF SysTimeToFILETIME) + $(LREF SysTimeToSYSTEMTIME) + $(LREF unixTimeToStdTime) +)) +)) + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - Authors: Jonathan M Davis - Source: $(PHOBOSSRC std/datetime/_systime.d) + Authors: $(HTTP jmdavisprog.com, Jonathan M Davis) + Source: $(PHOBOSSRC std/datetime/systime.d) +/ module std.datetime.systime; @@ -16,18 +41,58 @@ else version (TVOS) else version (WatchOS) version = Darwin; -import core.time; -import std.datetime.date; -import std.datetime.timezone; +/// Get the current time as a $(LREF SysTime) +@safe unittest +{ + import std.datetime.timezone : LocalTime; + SysTime today = Clock.currTime(); + assert(today.timezone is LocalTime()); +} + +/// Construct a $(LREF SysTime) from a ISO time string +@safe unittest +{ + import std.datetime.date : DateTime; + import std.datetime.timezone : UTC; + + auto st = SysTime.fromISOExtString("2018-01-01T10:30:00Z"); + assert(st == SysTime(DateTime(2018, 1, 1, 10, 30, 0), UTC())); +} + +/// Make a specific point in time in the New York timezone +@safe unittest +{ + import core.time : hours; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone; + + auto ny = SysTime( + DateTime(2018, 1, 1, 10, 30, 0), + new immutable SimpleTimeZone(-5.hours, "America/New_York") + ); + + // ISO standard time strings + assert(ny.toISOString() == "20180101T103000-05:00"); + assert(ny.toISOExtString() == "2018-01-01T10:30:00-05:00"); +} + +// Note: reconsider using specific imports below after +// https://issues.dlang.org/show_bug.cgi?id=17630 has been fixed +import core.time;// : ClockType, convert, dur, Duration, seconds, TimeException; +import std.datetime.date;// : _monthNames, AllowDayOverflow, CmpTimeUnits, Date, + //DateTime, DateTimeException, DayOfWeek, enforceValid, getDayOfWeek, maxDay, + //Month, splitUnitsFromHNSecs, TimeOfDay, validTimeUnits, yearIsLeapYear; +import std.datetime.timezone;// : LocalTime, SimpleTimeZone, TimeZone, UTC; import std.exception : enforce; import std.format : format; import std.range.primitives; -import std.traits : isIntegral, isSigned, isSomeString, Unqual; +import std.traits : isIntegral, isSigned, isSomeString, isNarrowString; version (Windows) { import core.stdc.time : time_t; - import core.sys.windows.windows; + import core.sys.windows.winbase; + import core.sys.windows.winnt; import core.sys.windows.winsock2; } else version (Posix) @@ -36,7 +101,7 @@ else version (Posix) import core.sys.posix.sys.types : time_t; } -version (unittest) +version (StdUnittest) { import core.exception : AssertError; import std.exception : assertThrown; @@ -88,7 +153,7 @@ public: @safe unittest { import std.format : format; - import std.stdio : writefln; + import core.time; assert(currTime().timezone is LocalTime()); assert(currTime(UTC()).timezone is UTC()); @@ -111,9 +176,8 @@ public: 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); + static foreach (ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second)) + {{ static if (clockSupported(ct)) { auto value1 = Clock.currTime!ct; @@ -121,7 +185,7 @@ public: assert(value1 <= value2, format("%s %s (ClockType: %s)", value1, value2, ct)); assert(abs(value1 - value2) <= seconds(2), format("ClockType.%s", ct)); } - } + }} } @@ -334,9 +398,8 @@ public: @safe unittest { import std.format : format; - import std.math : abs; + import std.math.algebraic : abs; import std.meta : AliasSeq; - import std.stdio : writefln; enum limit = convert!("seconds", "hnsecs")(2); auto norm1 = Clock.currStdTime; @@ -344,9 +407,8 @@ public: 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); + static foreach (ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second)) + {{ static if (clockSupported(ct)) { auto value1 = Clock.currStdTime!ct; @@ -354,21 +416,29 @@ public: assert(value1 <= value2, format("%s %s (ClockType: %s)", value1, value2, ct)); assert(abs(value1 - value2) <= limit); } - } + }} } private: - @disable this() {} + @disable this(); +} + +/// Get the current time as a $(LREF SysTime) +@safe unittest +{ + import std.datetime.timezone : LocalTime; + SysTime today = Clock.currTime(); + assert(today.timezone is LocalTime()); } /++ - $(D SysTime) is the type used to get the current time from the + `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 + `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, @@ -376,39 +446,41 @@ private: calendar-based operations, and getting individual units from it such as years or days is going to involve conversions and be less efficient. + An $(I hnsec) (hecto-nanosecond) is 100 nanoseconds. There are 10,000,000 hnsecs in a second. + 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). + the type to use. For system time, use `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 + $(LREF Clock.currTime) will return the current time as a `SysTime`. + To convert a `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 + `SysTime`, use `SysTime`'s constructor, and pass in the intended 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 + $(REF DateTime,std,datetime,date) to a `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 + `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. + `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 + `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 + and `UTC` is the $(REF TimeZone,std,datetime,timezone) class which + represents UTC. `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 + `SysTime`'s range is from approximately 29,000 B.C. to approximately 29,000 A.D. +/ struct SysTime @@ -431,7 +503,7 @@ public: 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 + this(DateTime dateTime, immutable TimeZone tz = null) @safe nothrow { try this(dateTime, Duration.zero, tz); @@ -458,6 +530,11 @@ public: 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); + + static void testScope(scope ref DateTime dt) @safe + { + auto st = SysTime(dt); + } } /++ @@ -474,10 +551,10 @@ public: be in the given time zone. Throws: - $(REF DateTimeException,std,datetime,date) if $(D fracSecs) is negative or if it's + $(REF DateTimeException,std,datetime,date) if `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 + this(DateTime dateTime, 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.")); @@ -494,6 +571,7 @@ public: @safe unittest { + import core.time; static void test(DateTime dt, Duration fracSecs, immutable TimeZone tz, long expected) { auto sysTime = SysTime(dt, fracSecs, tz); @@ -514,6 +592,11 @@ public: assertThrown!DateTimeException(SysTime(DateTime.init, hnsecs(-1), UTC())); assertThrown!DateTimeException(SysTime(DateTime.init, seconds(1), UTC())); + + static void testScope(scope ref DateTime dt, scope ref Duration d) @safe + { + auto st = SysTime(dt, d); + } } /++ @@ -528,7 +611,7 @@ public: 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 + this(Date date, immutable TimeZone tz = null) @safe nothrow { _timezone = tz is null ? LocalTime() : tz; @@ -556,6 +639,11 @@ public: test(Date(1, 1, 1), UTC(), 0); test(Date(1, 1, 2), UTC(), 864000000000); test(Date(0, 12, 31), UTC(), -864000000000); + + static void testScope(scope ref Date d) @safe + { + auto st = SysTime(d); + } } /++ @@ -598,28 +686,37 @@ public: } } + /++ Params: rhs = The $(LREF SysTime) to assign to this one. + + Returns: The `this` of this `SysTime`. +/ - ref SysTime opAssign(const ref SysTime rhs) return @safe pure nothrow + ref SysTime opAssign()(auto ref const(SysTime) rhs) return @safe pure nothrow scope { _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 + @safe unittest { - _stdTime = rhs._stdTime; - _timezone = rhs._timezone; - return this; + SysTime st; + st = SysTime(DateTime(2012, 12, 21, 1, 2, 3), UTC()); + assert(st == SysTime(DateTime(2012, 12, 21, 1, 2, 3), UTC())); + + const other = SysTime(DateTime(19, 1, 7, 13, 14, 15), LocalTime()); + st = other; + assert(st == other); + + static void testScope(scope ref SysTime left, const scope SysTime right) @safe + { + left = right; + } } + /++ Checks for equality between this $(LREF SysTime) and the given $(LREF SysTime). @@ -627,13 +724,7 @@ public: 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 + bool opEquals()(auto ref const(SysTime) rhs) @safe const pure nothrow scope { return _stdTime == rhs._stdTime; } @@ -669,18 +760,25 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 33, 30)); assert(st == st); assert(st == cst); - //assert(st == ist); + assert(st == ist); assert(cst == st); assert(cst == cst); - //assert(cst == ist); - //assert(ist == st); - //assert(ist == cst); - //assert(ist == ist); + assert(cst == ist); + assert(ist == st); + assert(ist == cst); + assert(ist == ist); + + static void testScope(scope ref SysTime left, const scope SysTime right) @safe + { + assert(left == right); + assert(right == left); + } } + /++ Compares this $(LREF SysTime) with the given $(LREF SysTime). @@ -693,7 +791,7 @@ public: $(TR $(TD this > rhs) $(TD > 0)) ) +/ - int opCmp(in SysTime rhs) @safe const pure nothrow + int opCmp()(auto ref const(SysTime) rhs) @safe const pure nothrow scope { if (_stdTime < rhs._stdTime) return -1; @@ -757,22 +855,29 @@ public: 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)); + 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(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); + assert(cst.opCmp(ist) == 0); + assert(ist.opCmp(st) == 0); + assert(ist.opCmp(cst) == 0); + assert(ist.opCmp(ist) == 0); + + static void testScope(scope ref SysTime left, const scope SysTime right) @safe + { + assert(left < right); + assert(right > left); + } } - /** - * Returns: A hash of the $(LREF SysTime) - */ - size_t toHash() const @nogc pure nothrow @safe + + /++ + Returns: A hash of the $(LREF SysTime). + +/ + size_t toHash() const @nogc pure nothrow @safe scope { static if (is(size_t == ulong)) return _stdTime; @@ -809,13 +914,19 @@ public: 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); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toHash(); + } } + /++ Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive are B.C. +/ - @property short year() @safe const nothrow + @property short year() @safe const nothrow scope { return (cast(Date) this).year; } @@ -849,9 +960,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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); + assert(ist.year == 1999); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.year; + } } /++ @@ -865,7 +981,7 @@ public: $(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 + @property void year(int year) @safe scope { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -897,7 +1013,7 @@ public: { import std.range : chain; - static void test(SysTime st, int year, in SysTime expected) + static void test(SysTime st, int year, SysTime expected) { st.year = year; assert(st == expected); @@ -937,18 +1053,23 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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)); + static assert(!__traits(compiles, ist.year = 7)); + + static void testScope(scope ref SysTime st) @safe + { + st.year = 42; + } } /++ 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. + $(REF DateTimeException,std,datetime,date) if `isAD` is true. +/ - @property ushort yearBC() @safe const + @property ushort yearBC() @safe const scope { return (cast(Date) this).yearBC; } @@ -978,11 +1099,16 @@ public: 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)); + 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)); + static assert(!__traits(compiles, ist.year = 12)); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.yearBC; + } } @@ -996,7 +1122,7 @@ public: $(REF DateTimeException,std,datetime,date) if a non-positive value is given. +/ - @property void yearBC(int year) @safe + @property void yearBC(int year) @safe scope { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -1027,7 +1153,7 @@ public: @safe unittest { import std.range : chain; - static void test(SysTime st, int year, in SysTime expected) + static void test(SysTime st, int year, SysTime expected) { st.yearBC = year; assert(st == expected, format("SysTime: %s", st)); @@ -1074,17 +1200,23 @@ public: 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)); + 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)); + static assert(!__traits(compiles, ist.yearBC = 12)); + + static void testScope(scope ref SysTime st) @safe + { + st.yearBC = 42; + } } + /++ Month of a Gregorian Year. +/ - @property Month month() @safe const nothrow + @property Month month() @safe const nothrow scope { return (cast(Date) this).month; } @@ -1129,9 +1261,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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); + assert(ist.month == 7); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.month; + } } @@ -1145,7 +1282,7 @@ public: $(REF DateTimeException,std,datetime,date) if the given month is not a valid month. +/ - @property void month(Month month) @safe + @property void month(Month month) @safe scope { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -1168,7 +1305,7 @@ public: import std.algorithm.iteration : filter; import std.range : chain; - static void test(SysTime st, Month month, in SysTime expected) + static void test(SysTime st, Month month, SysTime expected) { st.month = cast(Month) month; assert(st == expected); @@ -1237,15 +1374,20 @@ public: } 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.month = Month.dec)); + static assert(!__traits(compiles, ist.month = Month.dec)); + + static void testScope(scope ref SysTime st) @safe + { + st.month = Month.dec; + } } /++ Day of a Gregorian Month. +/ - @property ubyte day() @safe const nothrow + @property ubyte day() @safe const nothrow scope { return (cast(Date) this).day; } @@ -1291,9 +1433,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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); + assert(ist.day == 6); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.day; + } } @@ -1307,7 +1454,7 @@ public: $(REF DateTimeException,std,datetime,date) if the given day is not a valid day of the current month. +/ - @property void day(int day) @safe + @property void day(int day) @safe scope { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -1391,16 +1538,21 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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)); + static assert(!__traits(compiles, ist.day = 27)); + + static void testScope(scope ref SysTime st) @safe + { + st.day = 12; + } } /++ Hours past midnight. +/ - @property ubyte hour() @safe const nothrow + @property ubyte hour() @safe const nothrow scope { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -1450,9 +1602,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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); + assert(ist.hour == 12); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.hour; + } } @@ -1466,7 +1623,7 @@ public: $(REF DateTimeException,std,datetime,date) if the given hour are not a valid hour of the day. +/ - @property void hour(int hour) @safe + @property void hour(int hour) @safe scope { enforceValid!"hours"(hour); @@ -1509,16 +1666,21 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); static assert(!__traits(compiles, cst.hour = 27)); - //static assert(!__traits(compiles, ist.hour = 27)); + static assert(!__traits(compiles, ist.hour = 27)); + + static void testScope(scope ref SysTime st) @safe + { + st.hour = 12; + } } /++ Minutes past the current hour. +/ - @property ubyte minute() @safe const nothrow + @property ubyte minute() @safe const nothrow scope { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -1570,9 +1732,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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); + assert(ist.minute == 30); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.minute; + } } @@ -1586,7 +1753,7 @@ public: $(REF DateTimeException,std,datetime,date) if the given minute are not a valid minute of an hour. +/ - @property void minute(int minute) @safe + @property void minute(int minute) @safe scope { enforceValid!"minutes"(minute); @@ -1632,16 +1799,21 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); static assert(!__traits(compiles, cst.minute = 27)); - //static assert(!__traits(compiles, ist.minute = 27)); + static assert(!__traits(compiles, ist.minute = 27)); + + static void testScope(scope ref SysTime st) @safe + { + st.minute = 12; + } } /++ Seconds past the current minute. +/ - @property ubyte second() @safe const nothrow + @property ubyte second() @safe const nothrow scope { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -1694,9 +1866,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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); + assert(ist.second == 33); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.second; + } } @@ -1710,7 +1887,7 @@ public: $(REF DateTimeException,std,datetime,date) if the given second are not a valid second of a minute. +/ - @property void second(int second) @safe + @property void second(int second) @safe scope { enforceValid!"seconds"(second); @@ -1758,9 +1935,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); static assert(!__traits(compiles, cst.seconds = 27)); - //static assert(!__traits(compiles, ist.seconds = 27)); + static assert(!__traits(compiles, ist.seconds = 27)); + + static void testScope(scope ref SysTime st) @safe + { + st.second = 12; + } } @@ -1768,7 +1950,7 @@ public: 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 + @property Duration fracSecs() @safe const nothrow scope { auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime); @@ -1797,6 +1979,7 @@ public: @safe unittest { import std.range : chain; + import core.time; assert(SysTime(0, UTC()).fracSecs == Duration.zero); assert(SysTime(1, UTC()).fracSecs == hnsecs(1)); @@ -1825,9 +2008,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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); + assert(ist.fracSecs == Duration.zero); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.fracSecs; + } } @@ -1843,7 +2031,7 @@ public: $(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 + @property void fracSecs(Duration fracSecs) @safe scope { 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.")); @@ -1890,6 +2078,7 @@ public: @safe unittest { import std.range : chain; + import core.time; foreach (fracSec; testFracSecs) { @@ -1907,9 +2096,14 @@ public: 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)); + 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))); + static assert(!__traits(compiles, ist.fracSecs = msecs(7))); + + static void testScope(scope ref SysTime st) @safe + { + st.fracSecs = Duration.zero; + } } @@ -1917,13 +2111,14 @@ public: 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 + @property long stdTime() @safe const pure nothrow scope @nogc { return _stdTime; } @safe unittest { + import core.time; assert(SysTime(0).stdTime == 0); assert(SysTime(1).stdTime == 1); assert(SysTime(-1).stdTime == -1); @@ -1931,9 +2126,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cst.stdTime > 0); - //assert(ist.stdTime > 0); + assert(ist.stdTime > 0); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.stdTime; + } } @@ -1944,14 +2144,15 @@ public: Params: stdTime = The number of hnsecs since January 1st, 1 A.D. UTC. +/ - @property void stdTime(long stdTime) @safe pure nothrow + @property void stdTime(long stdTime) @safe pure nothrow scope { _stdTime = stdTime; } @safe unittest { - static void test(long stdTime, in SysTime expected, size_t line = __LINE__) + import core.time; + static void test(long stdTime, SysTime expected, size_t line = __LINE__) { auto st = SysTime(0, UTC()); st.stdTime = stdTime; @@ -1965,9 +2166,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); static assert(!__traits(compiles, cst.stdTime = 27)); - //static assert(!__traits(compiles, ist.stdTime = 27)); + static assert(!__traits(compiles, ist.stdTime = 27)); + + static void testScope(scope ref SysTime st) @safe + { + st.stdTime = 42; + } } @@ -1978,11 +2184,22 @@ public: hours - adjust the time to this $(LREF SysTime)'s time zone before returning. +/ - @property immutable(TimeZone) timezone() @safe const pure nothrow + @property immutable(TimeZone) timezone() @safe const pure nothrow scope { return _timezone; } + @safe unittest + { + assert(SysTime.init.timezone is InitTimeZone()); + assert(SysTime(DateTime.init, UTC()).timezone is UTC()); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.timezone; + } + } + /++ The current time zone of this $(LREF SysTime). It's internal time is @@ -1995,7 +2212,7 @@ public: 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 + @property void timezone(immutable TimeZone timezone) @safe pure nothrow scope { if (timezone is null) _timezone = LocalTime(); @@ -2003,14 +2220,40 @@ public: _timezone = timezone; } + @safe unittest + { + SysTime st; + st.timezone = null; + assert(st.timezone is LocalTime()); + st.timezone = UTC(); + assert(st.timezone is UTC()); + + static void testScope(scope ref SysTime st) @safe + { + st.timezone = UTC(); + } + } + /++ Returns whether DST is in effect for this $(LREF SysTime). +/ - @property bool dstInEffect() @safe const nothrow + @property bool dstInEffect() @safe const nothrow scope { return _timezone.dstInEffect(_stdTime); - // This function's unit testing is done in the time zone classes. + } + + // This function's full unit testing is done in the time zone classes, but + // this verifies that SysTime.init works correctly, since historically, it + // has segfaulted due to a null _timezone. + @safe unittest + { + assert(!SysTime.init.dstInEffect); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.dstInEffect; + } } @@ -2018,23 +2261,37 @@ public: 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 + @property Duration utcOffset() @safe const nothrow scope { return _timezone.utcOffsetAt(_stdTime); } + // This function's full unit testing is done in the time zone classes, but + // this verifies that SysTime.init works correctly, since historically, it + // has segfaulted due to a null _timezone. + @safe unittest + { + assert(SysTime.init.utcOffset == Duration.zero); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.utcOffset; + } + } + /++ 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 + SysTime toLocalTime() @safe const pure nothrow scope { return SysTime(_stdTime, LocalTime()); } @safe unittest { + import core.time; { auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27)); assert(sysTime == sysTime.toLocalTime()); @@ -2053,26 +2310,37 @@ public: assert(sysTime.toLocalTime().timezone !is UTC()); assert(sysTime.toLocalTime().timezone !is stz); } + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toLocalTime(); + } } /++ Returns a $(LREF SysTime) with the same std time as this one, but with - $(D UTC) as its time zone. + `UTC` as its time zone. +/ - SysTime toUTC() @safe const pure nothrow + SysTime toUTC() @safe const pure nothrow scope { return SysTime(_stdTime, UTC()); } @safe unittest { + import core.time; 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); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toUTC(); + } } @@ -2080,7 +2348,7 @@ public: 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 + SysTime toOtherTZ(immutable TimeZone tz) @safe const pure nothrow scope { if (tz is null) return SysTime(_stdTime, LocalTime()); @@ -2090,6 +2358,7 @@ public: @safe unittest { + import core.time; 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)); @@ -2097,6 +2366,12 @@ public: assert(sysTime.toOtherTZ(stz).timezone is stz); assert(sysTime.toOtherTZ(stz).timezone !is LocalTime()); assert(sysTime.toOtherTZ(stz).timezone !is UTC()); + assert(sysTime.toOtherTZ(null).timezone is LocalTime()); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toOtherTZ(null); + } } @@ -2116,8 +2391,8 @@ public: 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 + closest value that can be held in 32 bits will be used (so `int.max` + if it goes over and `int.min` if it goes under). However, no attempt is made to deal with integer overflow if the return type is long. Params: @@ -2129,7 +2404,7 @@ public: A signed integer representing the unix time which is equivalent to this SysTime. +/ - T toUnixTime(T = time_t)() @safe const pure nothrow + T toUnixTime(T = time_t)() @safe const pure nothrow scope if (is(T == int) || is(T == long)) { return stdTimeToUnixTime!T(_stdTime); @@ -2152,13 +2427,19 @@ public: auto ca = SysTime(DateTime(2007, 12, 22, 8, 14, 45), pst); assert(ca.toUnixTime() == 1_198_340_085); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toUnixTime(); + } } @safe unittest { import std.meta : AliasSeq; + import core.time; assert(SysTime(DateTime(1970, 1, 1), UTC()).toUnixTime() == 0); - foreach (units; AliasSeq!("hnsecs", "usecs", "msecs")) + static foreach (units; ["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); @@ -2214,6 +2495,7 @@ public: @safe unittest { + import core.time; 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())); @@ -2229,17 +2511,17 @@ public: /++ - Returns a $(D timeval) which represents this $(LREF SysTime). + Returns a `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 + If `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 + `tv_sec`. (so `int.max` if it goes over and `int.min` if it goes under). +/ - timeval toTimeVal() @safe const pure nothrow + timeval toTimeVal() @safe const pure nothrow scope { immutable tv_sec = toUnixTime!(typeof(timeval.tv_sec))(); immutable fracHNSecs = removeUnitsFromHNSecs!"seconds"(_stdTime - 621_355_968_000_000_000L); @@ -2249,6 +2531,7 @@ public: @safe unittest { + import core.time; 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)); @@ -2267,22 +2550,27 @@ public: 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)); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toTimeVal(); + } } version (StdDdoc) { - private struct timespec {} + version (Windows) private struct timespec {} /++ - Returns a $(D timespec) which represents this $(LREF SysTime). + Returns a `timespec` which represents this $(LREF SysTime). $(BLUE This function is Posix-Only.) +/ - timespec toTimeSpec() @safe const pure nothrow; + timespec toTimeSpec() @safe const pure nothrow scope; } else version (Posix) { - timespec toTimeSpec() @safe const pure nothrow + timespec toTimeSpec() @safe const pure nothrow scope { immutable tv_sec = toUnixTime!(typeof(timespec.tv_sec))(); immutable fracHNSecs = removeUnitsFromHNSecs!"seconds"(_stdTime - 621_355_968_000_000_000L); @@ -2292,6 +2580,7 @@ public: @safe unittest { + import core.time; 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)); @@ -2317,13 +2606,18 @@ public: timespec(-1, 0)); assert(SysTime(DateTime(1969, 12, 31, 23, 59, 58), usecs(17), UTC()).toTimeSpec() == timespec(-1, -999_983_000)); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toTimeSpec(); + } } } /++ - Returns a $(D tm) which represents this $(LREF SysTime). + Returns a `tm` which represents this $(LREF SysTime). +/ - tm toTM() @safe const nothrow + tm toTM() @safe const nothrow scope { auto dateTime = cast(DateTime) this; tm timeInfo; @@ -2342,7 +2636,7 @@ public: { import std.utf : toUTFz; timeInfo.tm_gmtoff = cast(int) convert!("hnsecs", "seconds")(adjTime - _stdTime); - auto zone = (timeInfo.tm_isdst ? _timezone.dstName : _timezone.stdName); + auto zone = timeInfo.tm_isdst ? _timezone.dstName : _timezone.stdName; timeInfo.tm_zone = zone.toUTFz!(char*)(); } @@ -2352,11 +2646,13 @@ public: @system unittest { import std.conv : to; + import core.time; version (Posix) { - scope(exit) clearTZEnvVar(); + import std.datetime.timezone : clearTZEnvVar, setTZEnvVar; setTZEnvVar("America/Los_Angeles"); + scope(exit) clearTZEnvVar(); } { @@ -2406,6 +2702,34 @@ public: assert(to!string(timeInfo.tm_zone) == "PDT"); } } + + // This is more to verify that SysTime.init.toTM() doesn't segfault and + // does something sane rather than that the value is anything + // particularly useful. + { + auto timeInfo = SysTime.init.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 == -1899); + assert(timeInfo.tm_wday == 1); + assert(timeInfo.tm_yday == 0); + assert(timeInfo.tm_isdst == 0); + + version (Posix) + { + assert(timeInfo.tm_gmtoff == 0); + assert(to!string(timeInfo.tm_zone) == "SysTime.init's timezone"); + } + } + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toTM(); + } } @@ -2428,7 +2752,7 @@ public: 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 + ref SysTime add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow scope if (units == "years" || units == "months") { auto hnsecs = adjTime; @@ -2479,6 +2803,7 @@ public: // Test add!"years"() with AllowDayOverflow.yes @safe unittest { + import core.time; // Test A.D. { auto sysTime = SysTime(Date(1999, 7, 6)); @@ -2673,14 +2998,20 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.add!"years"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.add!"years"(42); + } } // Test add!"years"() with AllowDayOverflow.no @safe unittest { + import core.time; // Test A.D. { auto sysTime = SysTime(Date(1999, 7, 6)); @@ -2886,6 +3217,7 @@ public: // Test add!"months"() with AllowDayOverflow.yes @safe unittest { + import core.time; // Test A.D. { auto sysTime = SysTime(Date(1999, 7, 6)); @@ -3224,14 +3556,20 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.add!"months"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.add!"months"(42); + } } // Test add!"months"() with AllowDayOverflow.no @safe unittest { + import core.time; // Test A.D. { auto sysTime = SysTime(Date(1999, 7, 6)); @@ -3590,8 +3928,9 @@ public: 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") + ref SysTime roll(string units) + (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow scope + if (units == "years") { return add!"years"(value, allowOverflow); } @@ -3630,16 +3969,22 @@ public: { 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)); + 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))); + static assert(!__traits(compiles, ist.roll!"years"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"years"(42); + } } // Shares documentation with "years" overload. - ref SysTime roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow - if (units == "months") + ref SysTime roll(string units) + (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow scope + if (units == "months") { auto hnsecs = adjTime; auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; @@ -3668,6 +4013,7 @@ public: // Test roll!"months"() with AllowDayOverflow.yes @safe unittest { + import core.time; // Test A.D. { auto sysTime = SysTime(Date(1999, 7, 6)); @@ -4038,14 +4384,20 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.roll!"months"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"months"(42); + } } // Test roll!"months"() with AllowDayOverflow.no @safe unittest { + import core.time; // Test A.D. { auto sysTime = SysTime(Date(1999, 7, 6)); @@ -4425,9 +4777,9 @@ public: 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"). + Accepted units are `"days"`, `"minutes"`, `"hours"`, + `"minutes"`, `"seconds"`, `"msecs"`, `"usecs"`, and + `"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 @@ -4438,7 +4790,7 @@ public: value = The number of $(D_PARAM units) to add to this $(LREF SysTime). +/ - ref SysTime roll(string units)(long value) @safe nothrow + ref SysTime roll(string units)(long value) @safe nothrow scope if (units == "days") { auto hnsecs = adjTime; @@ -4523,6 +4875,7 @@ public: @safe unittest { + import core.time; // Test A.D. { auto sysTime = SysTime(Date(1999, 2, 28)); @@ -4790,14 +5143,19 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.roll!"days"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"days"(42); + } } // Shares documentation with "days" version. - ref SysTime roll(string units)(long value) @safe nothrow + ref SysTime roll(string units)(long value) @safe nothrow scope if (units == "hours" || units == "minutes" || units == "seconds") { try @@ -4841,7 +5199,8 @@ public: // Test roll!"hours"(). @safe unittest { - static void testST(SysTime orig, int hours, in SysTime expected, size_t line = __LINE__) + import core.time; + static void testST(SysTime orig, int hours, SysTime expected, size_t line = __LINE__) @safe { orig.roll!"hours"(hours); if (orig != expected) @@ -5050,15 +5409,21 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.roll!"hours"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"hours"(42); + } } // Test roll!"minutes"(). @safe unittest { - static void testST(SysTime orig, int minutes, in SysTime expected, size_t line = __LINE__) + import core.time; + static void testST(SysTime orig, int minutes, SysTime expected, size_t line = __LINE__) @safe { orig.roll!"minutes"(minutes); if (orig != expected) @@ -5260,15 +5625,21 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.roll!"minutes"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"minutes"(42); + } } // Test roll!"seconds"(). @safe unittest { - static void testST(SysTime orig, int seconds, in SysTime expected, size_t line = __LINE__) + import core.time; + static void testST(SysTime orig, int seconds, SysTime expected, size_t line = __LINE__) @safe { orig.roll!"seconds"(seconds); if (orig != expected) @@ -5448,14 +5819,19 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.roll!"seconds"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"seconds"(42); + } } // Shares documentation with "days" version. - ref SysTime roll(string units)(long value) @safe nothrow + ref SysTime roll(string units)(long value) @safe nothrow scope if (units == "msecs" || units == "usecs" || units == "hnsecs") { auto hnsecs = adjTime; @@ -5485,7 +5861,8 @@ public: // Test roll!"msecs"(). @safe unittest { - static void testST(SysTime orig, int milliseconds, in SysTime expected, size_t line = __LINE__) + import core.time; + static void testST(SysTime orig, int milliseconds, SysTime expected, size_t line = __LINE__) @safe { orig.roll!"msecs"(milliseconds); if (orig != expected) @@ -5582,15 +5959,21 @@ public: } 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))); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"msecs"(4))); + static assert(!__traits(compiles, ist.roll!"msecs"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"msecs"(42); + } } // Test roll!"usecs"(). @safe unittest { - static void testST(SysTime orig, long microseconds, in SysTime expected, size_t line = __LINE__) + import core.time; + static void testST(SysTime orig, long microseconds, SysTime expected, size_t line = __LINE__) @safe { orig.roll!"usecs"(microseconds); if (orig != expected) @@ -5711,15 +6094,21 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.roll!"usecs"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"usecs"(42); + } } // Test roll!"hnsecs"(). @safe unittest { - static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__) + import core.time; + static void testST(SysTime orig, long hnsecs, SysTime expected, size_t line = __LINE__) @safe { orig.roll!"hnsecs"(hnsecs); if (orig != expected) @@ -5852,9 +6241,14 @@ public: } const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); - //immutable ist = 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))); + static assert(!__traits(compiles, ist.roll!"hnsecs"(4))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.roll!"hnsecs"(42); + } } @@ -5874,7 +6268,7 @@ public: 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 + SysTime opBinary(string op)(Duration duration) @safe const pure nothrow scope if (op == "+" || op == "-") { SysTime retval = SysTime(this._stdTime, this._timezone); @@ -5904,6 +6298,7 @@ public: @safe unittest { + import core.time; 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))); @@ -5940,7 +6335,7 @@ public: 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__) + static void testST(SysTime orig, long hnsecs, SysTime expected, size_t line = __LINE__) @safe { auto result = orig + dur!"hnsecs"(hnsecs); if (result != expected) @@ -6064,11 +6459,16 @@ public: 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)); + 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(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))); + assert(ist - duration == SysTime(DateTime(1999, 7, 6, 12, 30, 21))); + + static void testScope(scope ref SysTime st, scope ref Duration d) @safe + { + auto result = st + d; + } } @@ -6088,7 +6488,7 @@ public: 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 + ref SysTime opOpAssign(string op)(Duration duration) @safe pure nothrow scope if (op == "+" || op == "-") { immutable hnsecs = duration.total!"hnsecs"; @@ -6098,6 +6498,7 @@ public: @safe unittest { + import core.time; 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))); @@ -6135,7 +6536,7 @@ public: 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__) + static void testST(SysTime orig, long hnsecs, SysTime expected, size_t line = __LINE__) @safe { auto r = orig += dur!"hnsecs"(hnsecs); if (orig != expected) @@ -6267,11 +6668,17 @@ public: 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)); + 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, ist += duration)); static assert(!__traits(compiles, cst -= duration)); - //static assert(!__traits(compiles, ist -= duration)); + static assert(!__traits(compiles, ist -= duration)); + + static void testScope(scope ref SysTime st, scope ref Duration d) @safe + { + auto result1 = st += d; + auto result2 = st -= d; + } } @@ -6285,7 +6692,7 @@ public: $(TR $(TD SysTime) $(TD -) $(TD SysTime) $(TD -->) $(TD duration)) ) +/ - Duration opBinary(string op)(in SysTime rhs) @safe const pure nothrow + Duration opBinary(string op)(SysTime rhs) @safe const pure nothrow scope if (op == "-") { return dur!"hnsecs"(_stdTime - rhs._stdTime); @@ -6293,6 +6700,7 @@ public: @safe unittest { + import core.time; 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)) == @@ -6346,9 +6754,15 @@ public: dur!"hnsecs"(-1)); version (Posix) + { + import std.datetime.timezone : PosixTimeZone; immutable tz = PosixTimeZone.getTimeZone("America/Los_Angeles"); + } else version (Windows) + { + import std.datetime.timezone : WindowsTimeZone; immutable tz = WindowsTimeZone.getTimeZone("Pacific Standard Time"); + } { auto dt = DateTime(2011, 1, 13, 8, 17, 2); @@ -6360,18 +6774,23 @@ public: 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)); + 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(ist - st == Duration.zero); assert(st - cst == Duration.zero); assert(cst - cst == Duration.zero); - //assert(ist - cst == Duration.zero); + assert(ist - cst == Duration.zero); - //assert(st - ist == Duration.zero); - //assert(cst - ist == Duration.zero); - //assert(ist - ist == Duration.zero); + assert(st - ist == Duration.zero); + assert(cst - ist == Duration.zero); + assert(ist - ist == Duration.zero); + + static void testScope(scope ref SysTime left, scope ref SysTime right) @safe + { + auto result = left - right; + } } @@ -6396,7 +6815,7 @@ public: Params: rhs = The $(LREF SysTime) to subtract from this one. +/ - int diffMonths(in SysTime rhs) @safe const nothrow + int diffMonths(scope SysTime rhs) @safe const nothrow scope { return (cast(Date) this).diffMonths(cast(Date) rhs); } @@ -6404,6 +6823,7 @@ public: /// @safe unittest { + import core.time; import std.datetime.date : Date; assert(SysTime(Date(1999, 2, 1)).diffMonths( @@ -6421,65 +6841,83 @@ public: @safe unittest { + import core.time; 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)); + 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(ist.diffMonths(st) == 0); assert(st.diffMonths(cst) == 0); assert(cst.diffMonths(cst) == 0); - //assert(ist.diffMonths(cst) == 0); + assert(ist.diffMonths(cst) == 0); + + assert(st.diffMonths(ist) == 0); + assert(cst.diffMonths(ist) == 0); + assert(ist.diffMonths(ist) == 0); - //assert(st.diffMonths(ist) == 0); - //assert(cst.diffMonths(ist) == 0); - //assert(ist.diffMonths(ist) == 0); + static void testScope(scope ref SysTime left, scope ref SysTime right) @safe + { + auto result = left.diffMonths(right); + } } /++ Whether this $(LREF SysTime) is in a leap year. +/ - @property bool isLeapYear() @safe const nothrow + @property bool isLeapYear() @safe const nothrow scope { return (cast(Date) this).isLeapYear; } @safe unittest { + import core.time; 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(!st.isLeapYear); assert(!cst.isLeapYear); - //assert(!ist.isLeapYear); + assert(!ist.isLeapYear); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.isLeapYear; + } } /++ Day of the week this $(LREF SysTime) is on. +/ - @property DayOfWeek dayOfWeek() @safe const nothrow + @property DayOfWeek dayOfWeek() @safe const nothrow scope { return getDayOfWeek(dayOfGregorianCal); } @safe unittest { + import core.time; 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)); + 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); + assert(ist.dayOfWeek == DayOfWeek.tue); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.dayOfWeek; + } } /++ Day of the year this $(LREF SysTime) is on. +/ - @property ushort dayOfYear() @safe const nothrow + @property ushort dayOfYear() @safe const nothrow scope { return (cast(Date) this).dayOfYear; } @@ -6487,6 +6925,7 @@ public: /// @safe unittest { + import core.time; import std.datetime.date : DateTime; assert(SysTime(DateTime(1999, 1, 1, 12, 22, 7)).dayOfYear == 1); @@ -6496,12 +6935,18 @@ public: @safe unittest { + import core.time; 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(st.dayOfYear == 187); assert(cst.dayOfYear == 187); - //assert(ist.dayOfYear == 187); + assert(ist.dayOfYear == 187); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.dayOfYear; + } } @@ -6512,7 +6957,7 @@ public: day = The day of the year to set which day of the year this $(LREF SysTime) is on. +/ - @property void dayOfYear(int day) @safe + @property void dayOfYear(int day) @safe scope { immutable hnsecs = adjTime; immutable days = convert!("hnsecs", "days")(hnsecs); @@ -6528,20 +6973,26 @@ public: @safe unittest { + import core.time; 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)); + 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)); + static assert(!__traits(compiles, ist.dayOfYear = 12)); + + static void testScope(scope ref SysTime st) @safe + { + st.dayOfYear = 42; + } } /++ The Xth day of the Gregorian Calendar that this $(LREF SysTime) is on. +/ - @property int dayOfGregorianCal() @safe const nothrow + @property int dayOfGregorianCal() @safe const nothrow scope { immutable adjustedTime = adjTime; @@ -6560,6 +7011,7 @@ public: /// @safe unittest { + import core.time; import std.datetime.date : DateTime; assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).dayOfGregorianCal == 1); @@ -6576,6 +7028,7 @@ public: @safe unittest { + import core.time; // 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); @@ -6737,16 +7190,22 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cst.dayOfGregorianCal == 729_941); - //assert(ist.dayOfGregorianCal == 729_941); - } - + assert(ist.dayOfGregorianCal == 729_941); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.dayOfGregorianCal; + } + } + // Test that the logic for the day of the Gregorian Calendar is consistent // between Date and SysTime. @safe unittest { + import core.time; void test(Date date, SysTime st, size_t line = __LINE__) { if (date.dayOfGregorianCal != st.dayOfGregorianCal) @@ -6912,7 +7371,7 @@ public: days = The day of the Gregorian Calendar to set this $(LREF SysTime) to. +/ - @property void dayOfGregorianCal(int days) @safe nothrow + @property void dayOfGregorianCal(int days) @safe nothrow scope { auto hnsecs = adjTime; hnsecs = removeUnitsFromHNSecs!"days"(hnsecs); @@ -6934,6 +7393,7 @@ public: /// @safe unittest { + import core.time; import std.datetime.date : DateTime; auto st = SysTime(DateTime(0, 1, 1, 12, 0, 0)); @@ -6964,7 +7424,8 @@ public: @safe unittest { - void testST(SysTime orig, int day, in SysTime expected, size_t line = __LINE__) + import core.time; + void testST(SysTime orig, int day, SysTime expected, size_t line = __LINE__) @safe { orig.dayOfGregorianCal = day; if (orig != expected) @@ -7001,7 +7462,7 @@ public: auto st = SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212)); - void testST2(int day, in SysTime expected, size_t line = __LINE__) + void testST2(int day, SysTime expected, size_t line = __LINE__) @safe { st.dayOfGregorianCal = day; if (st != expected) @@ -7156,9 +7617,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); static assert(!__traits(compiles, cst.dayOfGregorianCal = 7)); - //static assert(!__traits(compiles, ist.dayOfGregorianCal = 7)); + static assert(!__traits(compiles, ist.dayOfGregorianCal = 7)); + + static void testScope(scope ref SysTime st) @safe + { + st.dayOfGregorianCal = 42; + } } @@ -7168,7 +7634,7 @@ public: See_Also: $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date). +/ - @property ubyte isoWeek() @safe const nothrow + @property ubyte isoWeek() @safe const nothrow scope { return (cast(Date) this).isoWeek; } @@ -7176,6 +7642,7 @@ public: /// @safe unittest { + import core.time; import std.datetime.date : Date; auto st = SysTime(Date(1999, 7, 6)); @@ -7187,12 +7654,20 @@ public: assert(ist.isoWeek == 41); } + @safe unittest + { + static void testScope(scope ref SysTime st) @safe + { + auto result = st.isoWeek; + } + } + /++ $(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 + @property SysTime endOfMonth() @safe const nothrow scope { immutable hnsecs = adjTime; immutable days = getUnitsFromHNSecs!"days"(hnsecs); @@ -7238,6 +7713,7 @@ public: @safe unittest { + import core.time; // 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))); @@ -7272,16 +7748,21 @@ public: 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)); + 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))); + assert(ist.endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999))); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.endOfMonth; + } } /++ The last day in the month that this $(LREF SysTime) is in. +/ - @property ubyte daysInMonth() @safe const nothrow + @property ubyte daysInMonth() @safe const nothrow scope { return Date(dayOfGregorianCal).daysInMonth; } @@ -7289,6 +7770,7 @@ public: /// @safe unittest { + import core.time; import std.datetime.date : DateTime; assert(SysTime(DateTime(1999, 1, 6, 0, 0, 0)).daysInMonth == 31); @@ -7299,6 +7781,7 @@ public: @safe unittest { + import core.time; // 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); @@ -7330,16 +7813,21 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cst.daysInMonth == 31); - //assert(ist.daysInMonth == 31); + assert(ist.daysInMonth == 31); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.daysInMonth; + } } /++ Whether the current year is a date in A.D. +/ - @property bool isAD() @safe const nothrow + @property bool isAD() @safe const nothrow scope { return adjTime >= 0; } @@ -7347,6 +7835,7 @@ public: /// @safe unittest { + import core.time; import std.datetime.date : DateTime; assert(SysTime(DateTime(1, 1, 1, 12, 7, 0)).isAD); @@ -7357,6 +7846,7 @@ public: @safe unittest { + import core.time; 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); @@ -7365,9 +7855,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cst.isAD); - //assert(ist.isAD); + assert(ist.isAD); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.isAD; + } } @@ -7378,7 +7873,7 @@ public: 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 + @property long julianDay() @safe const nothrow scope { immutable jd = dayOfGregorianCal + 1_721_425; return hour < 12 ? jd - 1 : jd; @@ -7386,6 +7881,7 @@ public: @safe unittest { + import core.time; assert(SysTime(DateTime(-4713, 11, 24, 0, 0, 0)).julianDay == -1); assert(SysTime(DateTime(-4713, 11, 24, 12, 0, 0)).julianDay == 0); @@ -7411,9 +7907,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cst.julianDay == 2_451_366); - //assert(ist.julianDay == 2_451_366); + assert(ist.julianDay == 2_451_366); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.julianDay; + } } @@ -7422,13 +7923,14 @@ public: any time on this date (since, the modified Julian day changes at midnight). +/ - @property long modJulianDay() @safe const nothrow + @property long modJulianDay() @safe const nothrow scope { return dayOfGregorianCal + 1_721_425 - 2_400_001; } @safe unittest { + import core.time; assert(SysTime(DateTime(1858, 11, 17, 0, 0, 0)).modJulianDay == 0); assert(SysTime(DateTime(1858, 11, 17, 12, 0, 0)).modJulianDay == 0); @@ -7436,23 +7938,29 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cst.modJulianDay == 51_365); - //assert(ist.modJulianDay == 51_365); + assert(ist.modJulianDay == 51_365); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.modJulianDay; + } } /++ Returns a $(REF Date,std,datetime,date) equivalent to this $(LREF SysTime). +/ - Date opCast(T)() @safe const nothrow - if (is(Unqual!T == Date)) + Date opCast(T)() @safe const nothrow scope + if (is(immutable T == immutable Date)) { return Date(dayOfGregorianCal); } @safe unittest { + import core.time; 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)); @@ -7470,9 +7978,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cast(Date) cst != Date.init); - //assert(cast(Date) ist != Date.init); + assert(cast(Date) ist != Date.init); + + static void testScope(scope ref SysTime st) @safe + { + auto result = cast(Date) st; + } } @@ -7480,8 +7993,8 @@ public: Returns a $(REF DateTime,std,datetime,date) equivalent to this $(LREF SysTime). +/ - DateTime opCast(T)() @safe const nothrow - if (is(Unqual!T == DateTime)) + DateTime opCast(T)() @safe const nothrow scope + if (is(immutable T == immutable DateTime)) { try { @@ -7506,6 +8019,7 @@ public: @safe unittest { + import core.time; 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)); @@ -7530,9 +8044,14 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cast(DateTime) cst != DateTime.init); - //assert(cast(DateTime) ist != DateTime.init); + assert(cast(DateTime) ist != DateTime.init); + + static void testScope(scope ref SysTime st) @safe + { + auto result = cast(DateTime) st; + } } @@ -7540,8 +8059,8 @@ public: Returns a $(REF TimeOfDay,std,datetime,date) equivalent to this $(LREF SysTime). +/ - TimeOfDay opCast(T)() @safe const nothrow - if (is(Unqual!T == TimeOfDay)) + TimeOfDay opCast(T)() @safe const nothrow scope + if (is(immutable T == immutable TimeOfDay)) { try { @@ -7563,6 +8082,7 @@ public: @safe unittest { + import core.time; 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)); @@ -7580,23 +8100,36 @@ public: 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)); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); assert(cast(TimeOfDay) cst != TimeOfDay.init); - //assert(cast(TimeOfDay) ist != TimeOfDay.init); + assert(cast(TimeOfDay) ist != TimeOfDay.init); + + static void testScope(scope ref SysTime st) @safe + { + auto result = cast(TimeOfDay) st; + } } - // Temporary hack until bug http://d.puremagic.com/issues/show_bug.cgi?id=4867 is fixed. + // Temporary hack until bug https://issues.dlang.org/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)) + SysTime opCast(T)() @safe const pure nothrow scope + if (is(immutable T == immutable SysTime)) { return SysTime(_stdTime, _timezone); } + @safe unittest + { + static void testScope(scope ref SysTime st) @safe + { + auto result = cast(SysTime) st; + } + } + /++ Converts this $(LREF SysTime) to a string with the format @@ -7611,7 +8144,7 @@ public: 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 + zone is `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. @@ -7628,45 +8161,67 @@ public: 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: + writer = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ - string toISOString() @safe const nothrow + string toISOString() @safe const nothrow scope { + import std.array : appender; + auto app = appender!string(); + app.reserve(30); try - { - immutable adjustedTime = adjTime; - long hnsecs = adjustedTime; - - auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + toISOString(app); + catch (Exception e) + assert(0, "toISOString() threw."); + return app.data; + } - if (hnsecs < 0) - { - hnsecs += convert!("hours", "hnsecs")(24); - --days; - } + /// ditto + void toISOString(W)(ref W writer) const scope + if (isOutputRange!(W, char)) + { + immutable adjustedTime = adjTime; + long hnsecs = adjustedTime; - auto hour = splitUnitsFromHNSecs!"hours"(hnsecs); - auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs); - auto second = splitUnitsFromHNSecs!"seconds"(hnsecs); + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; - auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, - cast(int) minute, cast(int) second)); - auto fracSecStr = fracSecsToISOString(cast(int) hnsecs); + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } - if (_timezone is LocalTime()) - return dateTime.toISOString() ~ fracSecStr; + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable second = splitUnitsFromHNSecs!"seconds"(hnsecs); - if (_timezone is UTC()) - return dateTime.toISOString() ~ fracSecStr ~ "Z"; + auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, + cast(int) minute, cast(int) second)); - immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + if (_timezone is LocalTime()) + { + dateTime.toISOString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + return; + } - return format("%s%s%s", - dateTime.toISOString(), - fracSecStr, - SimpleTimeZone.toISOExtString(utcOffset)); + if (_timezone is UTC()) + { + dateTime.toISOString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + put(writer, 'Z'); + return; } - catch (Exception e) - assert(0, "format() threw."); + + immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + + dateTime.toISOString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + SimpleTimeZone.toISOExtString(writer, utcOffset); } /// @@ -7690,6 +8245,7 @@ public: @safe unittest { + import core.time; // 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"); @@ -7735,9 +8291,14 @@ public: 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); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.toISOString() == "19990706T123033"); + assert(ist.toISOString() == "19990706T123033"); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toISOString(); + } } @@ -7755,50 +8316,72 @@ public: 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 + zone is `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. + + Params: + writer = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ - string toISOExtString() @safe const nothrow + string toISOExtString() @safe const nothrow scope { + import std.array : appender; + auto app = appender!string(); + app.reserve(35); try - { - immutable adjustedTime = adjTime; - long hnsecs = adjustedTime; - - auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + toISOExtString(app); + catch (Exception e) + assert(0, "toISOExtString() threw."); + return app.data; + } - if (hnsecs < 0) - { - hnsecs += convert!("hours", "hnsecs")(24); - --days; - } + /// ditto + void toISOExtString(W)(ref W writer) const scope + if (isOutputRange!(W, char)) + { + immutable adjustedTime = adjTime; + long hnsecs = adjustedTime; - auto hour = splitUnitsFromHNSecs!"hours"(hnsecs); - auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs); - auto second = splitUnitsFromHNSecs!"seconds"(hnsecs); + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; - auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, - cast(int) minute, cast(int) second)); - auto fracSecStr = fracSecsToISOString(cast(int) hnsecs); + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } - if (_timezone is LocalTime()) - return dateTime.toISOExtString() ~ fracSecStr; + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable second = splitUnitsFromHNSecs!"seconds"(hnsecs); - if (_timezone is UTC()) - return dateTime.toISOExtString() ~ fracSecStr ~ "Z"; + immutable dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, + cast(int) minute, cast(int) second)); - immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + if (_timezone is LocalTime()) + { + dateTime.toISOExtString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + return; + } - return format("%s%s%s", - dateTime.toISOExtString(), - fracSecStr, - SimpleTimeZone.toISOExtString(utcOffset)); + if (_timezone is UTC()) + { + dateTime.toISOExtString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + put(writer, 'Z'); + return; } - catch (Exception e) - assert(0, "format() threw."); + + immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + + dateTime.toISOExtString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + SimpleTimeZone.toISOExtString(writer, utcOffset); } /// @@ -7822,6 +8405,7 @@ public: @safe unittest { + import core.time; // 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() == @@ -7873,9 +8457,14 @@ public: "-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); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.toISOExtString() == "1999-07-06T12:30:33"); + assert(ist.toISOExtString() == "1999-07-06T12:30:33"); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toISOExtString(); + } } /++ @@ -7891,50 +8480,72 @@ public: 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 + zone is `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. + + Params: + writer = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ - string toSimpleString() @safe const nothrow + string toSimpleString() @safe const nothrow scope { + import std.array : appender; + auto app = appender!string(); + app.reserve(35); try - { - immutable adjustedTime = adjTime; - long hnsecs = adjustedTime; - - auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + toSimpleString(app); + catch (Exception e) + assert(0, "toSimpleString() threw."); + return app.data; + } - if (hnsecs < 0) - { - hnsecs += convert!("hours", "hnsecs")(24); - --days; - } + /// ditto + void toSimpleString(W)(ref W writer) const scope + if (isOutputRange!(W, char)) + { + immutable adjustedTime = adjTime; + long hnsecs = adjustedTime; - auto hour = splitUnitsFromHNSecs!"hours"(hnsecs); - auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs); - auto second = splitUnitsFromHNSecs!"seconds"(hnsecs); + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; - auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, - cast(int) minute, cast(int) second)); - auto fracSecStr = fracSecsToISOString(cast(int) hnsecs); + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } - if (_timezone is LocalTime()) - return dateTime.toSimpleString() ~ fracSecStr; + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable second = splitUnitsFromHNSecs!"seconds"(hnsecs); - if (_timezone is UTC()) - return dateTime.toSimpleString() ~ fracSecStr ~ "Z"; + immutable dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, + cast(int) minute, cast(int) second)); - immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + if (_timezone is LocalTime()) + { + dateTime.toSimpleString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + return; + } - return format("%s%s%s", - dateTime.toSimpleString(), - fracSecStr, - SimpleTimeZone.toISOExtString(utcOffset)); + if (_timezone is UTC()) + { + dateTime.toSimpleString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + put(writer, 'Z'); + return; } - catch (Exception e) - assert(0, "format() threw."); + + immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + + dateTime.toSimpleString(writer); + fracSecsToISOString(writer, cast(int) hnsecs); + SimpleTimeZone.toISOExtString(writer, utcOffset); } /// @@ -7958,6 +8569,7 @@ public: @safe unittest { + import core.time; // 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"); @@ -8010,9 +8622,14 @@ public: "-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); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.toSimpleString() == "1999-Jul-06 12:30:33"); + assert(ist.toSimpleString() == "1999-Jul-06 12:30:33"); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toSimpleString(); + } } @@ -8038,20 +8655,39 @@ public: `fromISOString`, `fromISOExtString`, and `fromSimpleString`. The format returned by toString may or may not change in the future. + + Params: + writer = A `char` accepting + $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + Returns: + A `string` when not using an output range; `void` otherwise. +/ - string toString() @safe const nothrow + string toString() @safe const nothrow scope { return toSimpleString(); } + /// ditto + void toString(W)(ref W writer) const scope + if (isOutputRange!(W, char)) + { + toSimpleString(writer); + } + @safe unittest { + import core.time; 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()); + immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(__traits(compiles, st.toString())); + static assert(__traits(compiles, cst.toString())); + static assert(__traits(compiles, ist.toString())); + + static void testScope(scope ref SysTime st) @safe + { + auto result = st.toString(); + } } @@ -8060,7 +8696,7 @@ public: 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 + The exact format is exactly as described in `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 @@ -8071,7 +8707,7 @@ public: 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 + then `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) @@ -8103,44 +8739,65 @@ public: 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 + static SysTime fromISOString(S)(scope const S isoString, immutable TimeZone tz = null) @safe if (isSomeString!S) { import std.algorithm.searching : startsWith, find; import std.conv : to; import std.string : strip; + import std.utf : byCodeUnit; - auto dstr = to!dstring(strip(isoString)); - immutable skipFirst = dstr.startsWith('+', '-') != 0; + auto str = strip(isoString); + immutable skipFirst = str.startsWith('+', '-'); - auto found = (skipFirst ? dstr[1..$] : dstr).find('.', 'Z', '+', '-'); - auto dateTimeStr = dstr[0 .. $ - found[0].length]; + auto found = (skipFirst ? str[1..$] : str).byCodeUnit.find('.', 'Z', '+', '-'); + auto dateTimeStr = str[0 .. $ - found[0].length]; - dstring fracSecStr; - dstring zoneStr; + typeof(str.byCodeUnit) foundTZ; // needs to have longer lifetime than zoneStr + typeof(str) fracSecStr; + typeof(str) zoneStr; if (found[1] != 0) { if (found[1] == 1) { - auto foundTZ = found[0].find('Z', '+', '-'); + foundTZ = found[0].find('Z', '+', '-')[0]; - if (foundTZ[1] != 0) + if (foundTZ.length != 0) { - fracSecStr = found[0][0 .. $ - foundTZ[0].length]; - zoneStr = foundTZ[0]; + static if (isNarrowString!S) + { + fracSecStr = found[0][0 .. $ - foundTZ.length].source; + zoneStr = foundTZ.source; + } + else + { + fracSecStr = found[0][0 .. $ - foundTZ.length]; + zoneStr = foundTZ; + } } else - fracSecStr = found[0]; + { + static if (isNarrowString!S) + fracSecStr = found[0].source; + else + fracSecStr = found[0]; + } } else - zoneStr = found[0]; + { + static if (isNarrowString!S) + zoneStr = found[0].source; + else + zoneStr = found[0]; + } } try { auto dateTime = DateTime.fromISOString(dateTimeStr); auto fracSec = fracSecsFromISOString(fracSecStr); + Rebindable!(immutable TimeZone) parsedZone; if (zoneStr.empty) @@ -8205,6 +8862,7 @@ public: @safe unittest { + import core.time; foreach (str; ["", "20100704000000", "20100704 000000", "20100704t000000", "20100704T000000.", "20100704T000000.A", "20100704T000000.Z", "20100704T000000.0000000A", "20100704T000000.00000000A", @@ -8245,7 +8903,7 @@ public: throw new AssertError("unittest failure", __FILE__, line); } - test("20101222T172201", SysTime(DateTime(2010, 12, 22, 17, 22, 01))); + test("20101222T172201", SysTime(DateTime(2010, 12, 22, 17, 22, 1))); 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))); @@ -8253,16 +8911,16 @@ public: 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))); + test("19070707T121212.0", SysTime(DateTime(1907, 7, 7, 12, 12, 12))); + test("19070707T121212.0000000", SysTime(DateTime(1907, 7, 7, 12, 12, 12))); + test("19070707T121212.0000001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), hnsecs(1))); + test("20100704T000000.00000000", SysTime(Date(2010, 7, 4))); + test("20100704T000000.00000009", SysTime(Date(2010, 7, 4))); + test("20100704T000000.00000019", SysTime(DateTime(2010, 7, 4), hnsecs(1))); + test("19070707T121212.000001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), usecs(1))); + test("19070707T121212.0000010", SysTime(DateTime(1907, 7, 7, 12, 12, 12), usecs(1))); + test("19070707T121212.001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), msecs(1))); + test("19070707T121212.0010000", SysTime(DateTime(1907, 7, 7, 12, 12, 12), msecs(1))); auto west60 = new immutable SimpleTimeZone(hours(-1)); auto west90 = new immutable SimpleTimeZone(minutes(-90)); @@ -8271,60 +8929,71 @@ public: 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("20101222T172201Z", SysTime(DateTime(2010, 12, 22, 17, 22, 1), UTC())); + test("20101222T172201-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west60)); + test("20101222T172201-01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west60)); + test("20101222T172201-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west90)); + test("20101222T172201-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west480)); + test("20101222T172201+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("20101222T172201+01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("20101222T172201+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("20101222T172201+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 1), 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)); + test("20101222T172201.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(2_341_200), UTC())); + test("20101222T172201.23112-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(2_311_200), west60)); + test("20101222T172201.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(4_500_000), west60)); + test("20101222T172201.1-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_000_000), west90)); + test("20101222T172201.55-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(5_500_000), west480)); + test("20101222T172201.1234567+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_234_567), east60)); + test("20101222T172201.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("20101222T172201.0000000+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("20101222T172201.45+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(4_500_000), east480)); + + // for dstring coverage + assert(SysTime.fromISOString("20101222T172201.23112-0100"d) == SysTime( + DateTime(2010, 12, 22, 17, 22, 1), hnsecs(2_311_200), west60)); + assert(SysTime.fromISOString("19070707T121212.0010000"d) == SysTime( + DateTime(1907, 7, 7, 12, 12, 12), msecs(1))); // @@@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 + // currently because of https://issues.dlang.org/show_bug.cgi?id=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, 1), west60)); + test("20101222T172201-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west90)); + test("20101222T172201-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west480)); + test("20101222T172201+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("20101222T172201+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("20101222T172201+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east480)); + + test("20101222T172201.23112-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(2_311_200), west60)); + test("20101222T172201.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_000_000), west90)); + test("20101222T172201.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(5_500_000), west480)); + test("20101222T172201.1234567+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_234_567), east60)); + test("20101222T172201.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("20101222T172201.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(4_500_000), east480)); + + static void testScope(scope ref string str) @safe + { + auto result = SysTime.fromISOString(str); + } + } + + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) { assert(SysTime.fromISOString(to!S("20121221T141516Z")) == SysTime(DateTime(2012, 12, 21, 14, 15, 16), UTC())); @@ -8338,7 +9007,7 @@ public: 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) + The exact format is exactly as described in `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 @@ -8349,7 +9018,7 @@ public: 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 + then `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) @@ -8370,34 +9039,35 @@ public: 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 + static SysTime fromISOExtString(S)(scope const S isoExtString, immutable TimeZone tz = null) @safe if (isSomeString!(S)) { import std.algorithm.searching : countUntil, find; import std.conv : to; - import std.string : strip; + import std.string : strip, indexOf; - auto dstr = to!dstring(strip(isoExtString)); + auto str = strip(isoExtString); - auto tIndex = dstr.countUntil('T'); + auto tIndex = str.indexOf('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]; + auto found = str[tIndex + 1 .. $].find('.', 'Z', '+', '-'); + auto dateTimeStr = str[0 .. $ - found[0].length]; - dstring fracSecStr; - dstring zoneStr; + typeof(str) foundTZ; // needs to have longer lifetime than zoneStr + typeof(str) fracSecStr; + typeof(str) zoneStr; if (found[1] != 0) { if (found[1] == 1) { - auto foundTZ = found[0].find('Z', '+', '-'); + foundTZ = found[0].find('Z', '+', '-')[0]; - if (foundTZ[1] != 0) + if (foundTZ.length != 0) { - fracSecStr = found[0][0 .. $ - foundTZ[0].length]; - zoneStr = foundTZ[0]; + fracSecStr = found[0][0 .. $ - foundTZ.length]; + zoneStr = foundTZ; } else fracSecStr = found[0]; @@ -8468,6 +9138,7 @@ public: @safe unittest { + import core.time; foreach (str; ["", "20100704000000", "20100704 000000", "20100704t000000", "20100704T000000.", "20100704T000000.0", "2010-07:0400:00:00", "2010-07-04 00:00:00", @@ -8508,7 +9179,7 @@ public: throw new AssertError("unittest failure", __FILE__, line); } - test("2010-12-22T17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01))); + test("2010-12-22T17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 1))); 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))); @@ -8516,16 +9187,16 @@ public: 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))); + test("1907-07-07T12:12:12.0", SysTime(DateTime(1907, 7, 7, 12, 12, 12))); + test("1907-07-07T12:12:12.0000000", SysTime(DateTime(1907, 7, 7, 12, 12, 12))); + test("1907-07-07T12:12:12.0000001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), hnsecs(1))); + test("2010-07-04T00:00:00.00000000", SysTime(Date(2010, 7, 4))); + test("2010-07-04T00:00:00.00000009", SysTime(Date(2010, 7, 4))); + test("2010-07-04T00:00:00.00000019", SysTime(DateTime(2010, 7, 4), hnsecs(1))); + test("1907-07-07T12:12:12.000001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), usecs(1))); + test("1907-07-07T12:12:12.0000010", SysTime(DateTime(1907, 7, 7, 12, 12, 12), usecs(1))); + test("1907-07-07T12:12:12.001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), msecs(1))); + test("1907-07-07T12:12:12.0010000", SysTime(DateTime(1907, 7, 7, 12, 12, 12), msecs(1))); auto west60 = new immutable SimpleTimeZone(hours(-1)); auto west90 = new immutable SimpleTimeZone(minutes(-90)); @@ -8534,38 +9205,44 @@ public: 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-12-22T17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 1), UTC())); + test("2010-12-22T17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west60)); + test("2010-12-22T17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west60)); + test("2010-12-22T17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west90)); + test("2010-12-22T17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west480)); + test("2010-12-22T17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("2010-12-22T17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("2010-12-22T17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("2010-12-22T17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), 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.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 1), 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)); + SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(2_311_200), west60)); + test("2010-12-22T17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(4_500_000), west60)); + test("2010-12-22T17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_000_000), west90)); + test("2010-12-22T17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), 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)); + SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_234_567), east60)); + test("2010-12-22T17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("2010-12-22T17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("2010-12-22T17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(4_500_000), east480)); + + static void testScope(scope ref string str) @safe + { + auto result = SysTime.fromISOExtString(str); + } } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { + import core.time; import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static 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())); @@ -8579,7 +9256,7 @@ public: 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 + The exact format is exactly as described in `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 @@ -8590,7 +9267,7 @@ public: 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 + then `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) @@ -8602,7 +9279,7 @@ public: Params: simpleString = A string formatted in the way that - $(D toSimpleString) formats dates and times. + `toSimpleString` formats dates and times. tz = The time zone to convert the given time to (no conversion occurs if null). @@ -8611,34 +9288,35 @@ public: 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 + static SysTime fromSimpleString(S)(scope const S simpleString, immutable TimeZone tz = null) @safe if (isSomeString!(S)) { - import std.algorithm.searching : countUntil, find; + import std.algorithm.searching : find; import std.conv : to; - import std.string : strip; + import std.string : strip, indexOf; - auto dstr = to!dstring(strip(simpleString)); + auto str = strip(simpleString); - auto spaceIndex = dstr.countUntil(' '); + auto spaceIndex = str.indexOf(' '); 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]; + auto found = str[spaceIndex + 1 .. $].find('.', 'Z', '+', '-'); + auto dateTimeStr = str[0 .. $ - found[0].length]; - dstring fracSecStr; - dstring zoneStr; + typeof(str) foundTZ; // needs to have longer lifetime than zoneStr + typeof(str) fracSecStr; + typeof(str) zoneStr; if (found[1] != 0) { if (found[1] == 1) { - auto foundTZ = found[0].find('Z', '+', '-'); + foundTZ = found[0].find('Z', '+', '-')[0]; - if (foundTZ[1] != 0) + if (foundTZ.length != 0) { - fracSecStr = found[0][0 .. $ - foundTZ[0].length]; - zoneStr = foundTZ[0]; + fracSecStr = found[0][0 .. $ - foundTZ.length]; + zoneStr = foundTZ; } else fracSecStr = found[0]; @@ -8710,6 +9388,7 @@ public: @safe unittest { + import core.time; 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", @@ -8752,7 +9431,7 @@ public: throw new AssertError("unittest failure", __FILE__, line); } - test("2010-Dec-22 17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01))); + test("2010-Dec-22 17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 1))); 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))); @@ -8760,16 +9439,16 @@ public: 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))); + test("1907-Jul-07 12:12:12.0", SysTime(DateTime(1907, 7, 7, 12, 12, 12))); + test("1907-Jul-07 12:12:12.0000000", SysTime(DateTime(1907, 7, 7, 12, 12, 12))); + test("2010-Jul-04 00:00:00.00000000", SysTime(Date(2010, 7, 4))); + test("2010-Jul-04 00:00:00.00000009", SysTime(Date(2010, 7, 4))); + test("2010-Jul-04 00:00:00.00000019", SysTime(DateTime(2010, 7, 4), hnsecs(1))); + test("1907-Jul-07 12:12:12.0000001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), hnsecs(1))); + test("1907-Jul-07 12:12:12.000001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), usecs(1))); + test("1907-Jul-07 12:12:12.0000010", SysTime(DateTime(1907, 7, 7, 12, 12, 12), usecs(1))); + test("1907-Jul-07 12:12:12.001", SysTime(DateTime(1907, 7, 7, 12, 12, 12), msecs(1))); + test("1907-Jul-07 12:12:12.0010000", SysTime(DateTime(1907, 7, 7, 12, 12, 12), msecs(1))); auto west60 = new immutable SimpleTimeZone(hours(-1)); auto west90 = new immutable SimpleTimeZone(minutes(-90)); @@ -8778,38 +9457,44 @@ public: 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-Dec-22 17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 1), UTC())); + test("2010-Dec-22 17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west60)); + test("2010-Dec-22 17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west60)); + test("2010-Dec-22 17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west90)); + test("2010-Dec-22 17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), west480)); + test("2010-Dec-22 17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("2010-Dec-22 17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("2010-Dec-22 17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("2010-Dec-22 17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), 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.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 1), 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)); + SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(2_311_200), west60)); + test("2010-Dec-22 17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(4_500_000), west60)); + test("2010-Dec-22 17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_000_000), west90)); + test("2010-Dec-22 17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), 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)); + SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(1_234_567), east60)); + test("2010-Dec-22 17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east60)); + test("2010-Dec-22 17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 1), east90)); + test("2010-Dec-22 17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 1), hnsecs(4_500_000), east480)); + + static void testScope(scope ref string str) @safe + { + auto result = SysTime.fromSimpleString(str); + } } - // bug# 17801 + // https://issues.dlang.org/show_bug.cgi?id=17801 @safe unittest { + import core.time; import std.conv : to; import std.meta : AliasSeq; - foreach (C; AliasSeq!(char, wchar, dchar)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { - foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + static 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())); @@ -8857,9 +9542,9 @@ public: private: /+ - Returns $(D stdTime) converted to $(LREF SysTime)'s time zone. + Returns `stdTime` converted to $(LREF SysTime)'s time zone. +/ - @property long adjTime() @safe const nothrow + @property long adjTime() @safe const nothrow scope { return _timezone.utcToTZ(_stdTime); } @@ -8868,24 +9553,95 @@ private: /+ Converts the given hnsecs from $(LREF SysTime)'s time zone to std time. +/ - @property void adjTime(long adjTime) @safe nothrow + @property void adjTime(long adjTime) @safe nothrow scope { _stdTime = _timezone.tzToUTC(adjTime); } - // Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5058 - /+ - invariant() + final class InitTimeZone : TimeZone + { + public: + + static immutable(InitTimeZone) opCall() @safe pure nothrow @nogc { return _initTimeZone; } + + @property override bool hasDST() @safe const nothrow @nogc { return false; } + + override bool dstInEffect(long stdTime) @safe const nothrow @nogc { return false; } + + override long utcToTZ(long stdTime) @safe const nothrow @nogc { return 0; } + + override long tzToUTC(long adjTime) @safe const nothrow @nogc { return 0; } + + override Duration utcOffsetAt(long stdTime) @safe const nothrow @nogc { return Duration.zero; } + + private: + + this() @safe immutable pure + { + super("SysTime.init's timezone", "SysTime.init's timezone", "SysTime.init's timezone"); + } + + static immutable InitTimeZone _initTimeZone = new immutable(InitTimeZone); + } + + // https://issues.dlang.org/show_bug.cgi?id=17732 + @safe unittest + { + assert(SysTime.init.timezone is InitTimeZone()); + assert(SysTime.init.toISOString() == "00010101T000000+00:00"); + assert(SysTime.init.toISOExtString() == "0001-01-01T00:00:00+00:00"); + assert(SysTime.init.toSimpleString() == "0001-Jan-01 00:00:00+00:00"); + assert(SysTime.init.toString() == "0001-Jan-01 00:00:00+00:00"); + } + + // Assigning a value to _timezone in SysTime.init currently doesn't work due + // to https://issues.dlang.org/show_bug.cgi?id=17740. So, to hack around + // that problem, these accessors have been added so that we can insert a + // runtime check for null and then use InitTimeZone for SysTime.init (which + // which is the only case where _timezone would be null). This thus fixes + // the problem with segfaulting when using SysTime.init but at the cost of + // what should be an unnecessary null check. Once 17740 has finally been + // fixed, _timezoneStorage should be removed, these accessors should be + // removed, and the _timezone variable declaration should be restored. + pragma(inline, true) @property _timezone() @safe const pure nothrow @nogc + { + return _timezoneStorage is null ? InitTimeZone() : _timezoneStorage; + } + + pragma(inline, true) @property void _timezone(immutable TimeZone tz) @safe pure nothrow @nogc scope { - 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)."); + _timezoneStorage = tz; } - +/ long _stdTime; - Rebindable!(immutable TimeZone) _timezone; + Rebindable!(immutable TimeZone) _timezoneStorage; + //Rebindable!(immutable TimeZone) _timezone = InitTimeZone(); +} + +/// +@safe unittest +{ + import core.time : days, hours, seconds; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone, UTC; + + // make a specific point in time in the UTC timezone + auto st = SysTime(DateTime(2018, 1, 1, 10, 30, 0), UTC()); + // make a specific point in time in the New York timezone + auto ny = SysTime( + DateTime(2018, 1, 1, 10, 30, 0), + new immutable SimpleTimeZone(-5.hours, "America/New_York") + ); + + // ISO standard time strings + assert(st.toISOString() == "20180101T103000Z"); + assert(st.toISOExtString() == "2018-01-01T10:30:00Z"); + + // add two days and 30 seconds + st += 2.days + 30.seconds; + assert(st.toISOExtString() == "2018-01-03T10:30:30Z"); } @@ -8902,7 +9658,7 @@ private: "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 + as an integer in hnsecs 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 @@ -8916,7 +9672,7 @@ private: See_Also: SysTime.fromUnixTime +/ -long unixTimeToStdTime(long unixTime) @safe pure nothrow +long unixTimeToStdTime(long unixTime) @safe pure nothrow @nogc { return 621_355_968_000_000_000L + convert!("seconds", "hnsecs")(unixTime); } @@ -8934,7 +9690,7 @@ long unixTimeToStdTime(long unixTime) @safe pure nothrow assert(unixTimeToStdTime(int.max) == 642_830_804_470_000_000L); assert(SysTime(unixTimeToStdTime(int.max)) == - SysTime(DateTime(2038, 1, 19, 3, 14, 07), UTC())); + SysTime(DateTime(2038, 1, 19, 3, 14, 7), UTC())); assert(unixTimeToStdTime(-127_127) == 621_354_696_730_000_000L); assert(SysTime(unixTimeToStdTime(-127_127)) == @@ -8983,8 +9739,8 @@ long unixTimeToStdTime(long unixTime) @safe pure nothrow 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 + closest value that can be held in 32 bits will be used (so `int.max` + if it goes over and `int.min` if it goes under). However, no attempt is made to deal with integer overflow if the return type is long. Params: @@ -9079,31 +9835,31 @@ version (StdDdoc) /++ $(BLUE This function is Windows-Only.) - Converts a $(D SYSTEMTIME) struct to a $(LREF SysTime). + Converts a `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 + st = The `SYSTEMTIME` struct to convert. + tz = The time zone that the time in the `SYSTEMTIME` struct is + assumed to be (if the `SYSTEMTIME` was supplied by a Windows + system call, the `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. + `SYSTEMTIME` will not fit in a $(LREF SysTime), which is highly + unlikely to happen given that `SysTime.max` is in 29,228 A.D. and + the maximum `SYSTEMTIME` is in 30,827 A.D. +/ - SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe; + SysTime SYSTEMTIMEToSysTime(const scope SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe; /++ $(BLUE This function is Windows-Only.) - Converts a $(LREF SysTime) to a $(D SYSTEMTIME) struct. + Converts a $(LREF SysTime) to a `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 + The `SYSTEMTIME` which is returned will be set using the given + $(LREF SysTime)'s time zone, so to get the `SYSTEMTIME` in UTC, set the $(LREF SysTime)'s time zone to UTC. Params: @@ -9111,24 +9867,24 @@ version (StdDdoc) Throws: $(REF DateTimeException,std,datetime,date) if the given - $(LREF SysTime) will not fit in a $(D SYSTEMTIME). This will only + $(LREF SysTime) will not fit in a `SYSTEMTIME`. This will only happen if the $(LREF SysTime)'s date is prior to 1601 A.D. +/ - SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe; + SYSTEMTIME SysTimeToSYSTEMTIME(scope SysTime sysTime) @safe; /++ $(BLUE This function is Windows-Only.) - Converts a $(D FILETIME) struct to the number of hnsecs since midnight, + Converts a `FILETIME` struct to the number of hnsecs since midnight, January 1st, 1 A.D. Params: - ft = The $(D FILETIME) struct to convert. + ft = The `FILETIME` struct to convert. Throws: $(REF DateTimeException,std,datetime,date) if the given - $(D FILETIME) cannot be represented as the return value. + `FILETIME` cannot be represented as the return value. +/ long FILETIMEToStdTime(scope const FILETIME* ft) @safe; @@ -9136,16 +9892,16 @@ version (StdDdoc) /++ $(BLUE This function is Windows-Only.) - Converts a $(D FILETIME) struct to a $(LREF SysTime). + Converts a `FILETIME` struct to a $(LREF SysTime). Params: - ft = The $(D FILETIME) struct to convert. + ft = The `FILETIME` struct to convert. tz = The time zone that the $(LREF SysTime) will be in - ($(D FILETIME)s are in UTC). + (`FILETIME`s are in UTC). Throws: $(REF DateTimeException,std,datetime,date) if the given - $(D FILETIME) will not fit in a $(LREF SysTime). + `FILETIME` will not fit in a $(LREF SysTime). +/ SysTime FILETIMEToSysTime(scope const FILETIME* ft, immutable TimeZone tz = LocalTime()) @safe; @@ -9154,7 +9910,7 @@ version (StdDdoc) $(BLUE This function is Windows-Only.) Converts a number of hnsecs since midnight, January 1st, 1 A.D. to a - $(D FILETIME) struct. + `FILETIME` struct. Params: stdTime = The number of hnsecs since midnight, January 1st, 1 A.D. @@ -9162,7 +9918,7 @@ version (StdDdoc) Throws: $(REF DateTimeException,std,datetime,date) if the given value will - not fit in a $(D FILETIME). + not fit in a `FILETIME`. +/ FILETIME stdTimeToFILETIME(long stdTime) @safe; @@ -9170,22 +9926,22 @@ version (StdDdoc) /++ $(BLUE This function is Windows-Only.) - Converts a $(LREF SysTime) to a $(D FILETIME) struct. + Converts a $(LREF SysTime) to a `FILETIME` struct. - $(D FILETIME)s are always in UTC. + `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). + $(LREF SysTime) will not fit in a `FILETIME`. +/ - FILETIME SysTimeToFILETIME(SysTime sysTime) @safe; + FILETIME SysTimeToFILETIME(scope SysTime sysTime) @safe; } else version (Windows) { - SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe + SysTime SYSTEMTIMEToSysTime(const scope SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe { const max = SysTime.max; @@ -9229,6 +9985,7 @@ else version (Windows) auto dt = DateTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + import core.time : msecs; return SysTime(dt, msecs(st.wMilliseconds), tz); } @@ -9238,12 +9995,17 @@ else version (Windows) SYSTEMTIME st = void; GetSystemTime(&st); auto converted = SYSTEMTIMEToSysTime(&st, UTC()); - + import core.time : abs; assert(abs((converted - sysTime)) <= dur!"seconds"(2)); + + static void testScope(scope SYSTEMTIME* st) @safe + { + auto result = SYSTEMTIMEToSysTime(st); + } } - SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe + SYSTEMTIME SysTimeToSYSTEMTIME(scope SysTime sysTime) @safe { immutable dt = cast(DateTime) sysTime; @@ -9280,6 +10042,11 @@ else version (Windows) assert(st.wMinute == result.wMinute); assert(st.wSecond == result.wSecond); assert(st.wMilliseconds == result.wMilliseconds); + + static void testScope(scope ref SysTime st) @safe + { + auto result = SysTimeToSYSTEMTIME(st); + } } private enum hnsecsFrom1601 = 504_911_232_000_000_000L; @@ -9315,7 +10082,13 @@ else version (Windows) auto converted = FILETIMEToSysTime(&ft); + import core.time : abs; assert(abs((converted - sysTime)) <= dur!"seconds"(2)); + + static void testScope(scope FILETIME* ft) @safe + { + auto result = FILETIMEToSysTime(ft); + } } @@ -9334,7 +10107,7 @@ else version (Windows) return ft; } - FILETIME SysTimeToFILETIME(SysTime sysTime) @safe + FILETIME SysTimeToFILETIME(scope SysTime sysTime) @safe { return stdTimeToFILETIME(sysTime.stdTime); } @@ -9352,6 +10125,11 @@ else version (Windows) assert(ft.dwLowDateTime == result.dwLowDateTime); assert(ft.dwHighDateTime == result.dwHighDateTime); + + static void testScope(scope ref SysTime st) @safe + { + auto result = SysTimeToFILETIME(st); + } } } @@ -9369,7 +10147,7 @@ alias DosFileTime = uint; 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 + $(REF DateTimeException,std,datetime,date) if the `DosFileTime` is invalid. +/ SysTime DosFileTimeToSysTime(DosFileTime dft, immutable TimeZone tz = LocalTime()) @safe @@ -9392,13 +10170,24 @@ SysTime DosFileTimeToSysTime(DosFileTime dft, immutable TimeZone tz = LocalTime( throw new DateTimeException("Invalid DosFileTime", __FILE__, __LINE__, dte); } +/// @safe unittest { + import std.datetime.date : DateTime; + 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))); } +@safe unittest +{ + static void testScope(scope ref DosFileTime dft) @safe + { + auto result = DosFileTimeToSysTime(dft); + } +} + /++ Converts from $(LREF SysTime) to DOS file date/time. @@ -9408,9 +10197,9 @@ SysTime DosFileTimeToSysTime(DosFileTime dft, immutable TimeZone tz = LocalTime( Throws: $(REF DateTimeException,std,datetime,date) if the given - $(LREF SysTime) cannot be converted to a $(D DosFileTime). + $(LREF SysTime) cannot be converted to a `DosFileTime`. +/ -DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe +DosFileTime SysTimeToDosFileTime(scope SysTime sysTime) @safe { auto dateTime = cast(DateTime) sysTime; @@ -9431,17 +10220,28 @@ DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe return cast(DosFileTime) retval; } +/// @safe unittest { + import std.datetime.date : DateTime; + 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); } +@safe unittest +{ + static void testScope(scope ref SysTime st) @safe + { + auto result = SysTimeToDosFileTime(st); + } +} + /++ - 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 + The given array of `char` or random-access range of `char` or + `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 @@ -9456,10 +10256,10 @@ DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe 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 + If the time zone is `"-0000"` (or considered to be equivalent to + `"-0000"` by section 4.3 of the spec), a + $(REF SimpleTimeZone,std,datetime,timezone) with a utc offset of `0` is + used rather than $(REF UTC,std,datetime,timezone), whereas `"+0000"` uses $(REF UTC,std,datetime,timezone). Note that because $(LREF SysTime) does not currently support having a second @@ -9467,7 +10267,7 @@ DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe 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 + `"\n"` in folding whitespace in the place of `"\r\n"`, because the HTTP spec requires it. Throws: @@ -9475,16 +10275,16 @@ DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe follow the grammar for a date-time field or if the resulting $(LREF SysTime) is invalid. +/ -SysTime parseRFC822DateTime()(in char[] value) @safe +SysTime parseRFC822DateTime()(scope const char[] value) @safe { import std.string : representation; return parseRFC822DateTime(value.representation); } /++ Ditto +/ -SysTime parseRFC822DateTime(R)(R value) @safe +SysTime parseRFC822DateTime(R)(scope R value) if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && - (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte))) + (is(immutable ElementType!R == immutable char) || is(immutable ElementType!R == immutable ubyte))) { import std.algorithm.searching : find, all; import std.ascii : isDigit, isAlpha, isPrintable; @@ -9704,7 +10504,7 @@ afterMon: stripAndCheckLen(value[3 .. value.length], "1200:00A".length); assertThrown!DateTimeException(parseRFC822DateTime(badStr)); } -version (unittest) void testParse822(alias cr)(string str, SysTime expected, size_t line = __LINE__) +version (StdUnittest) private void testParse822(alias cr)(string str, SysTime expected, size_t line = __LINE__) { import std.format : format; auto value = cr(str); @@ -9713,7 +10513,7 @@ version (unittest) void testParse822(alias cr)(string str, SysTime expected, siz 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__) +version (StdUnittest) private void testBadParse822(alias cr)(string str, size_t line = __LINE__) { try parseRFC822DateTime(cr(str)); @@ -9724,6 +10524,7 @@ version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LI @system unittest { + import core.time; import std.algorithm.iteration : filter, map; import std.algorithm.searching : canFind; import std.array : array; @@ -9748,11 +10549,12 @@ version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LI static auto start() { Rand3Letters retval; retval.popFront(); return retval; } } - foreach (cr; AliasSeq!(function(string a){return cast(char[]) a;}, + static 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 + {(){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 scope(failure) writeln(typeof(cr).stringof); alias test = testParse822!cr; alias testBad = testBadParse822!cr; @@ -9990,7 +10792,12 @@ version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LI testBad(cast(string) currStr); testBad((cast(string) currStr) ~ " "); } - }(); + }();} + + static void testScope(scope ref string str) @safe + { + auto result = parseRFC822DateTime(str); + } } // Obsolete Format per section 4.3 of RFC 5322. @@ -10014,11 +10821,12 @@ version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LI 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;}, + static 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 + {(){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 scope(failure) writeln(typeof(cr).stringof); alias test = testParse822!cr; { @@ -10128,7 +10936,7 @@ version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LI // test time zones { - auto dt = DateTime(1982, 05, 03, 12, 22, 04); + auto dt = DateTime(1982, 5, 3, 12, 22, 4); 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)))); @@ -10189,13 +10997,13 @@ version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LI // test that the checks for minimum length work correctly and avoid // any RangeErrors. - test("7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + test("7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 0, 0, 0), new immutable SimpleTimeZone(Duration.zero))); - test("Fri,7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + test("Fri,7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 0, 0, 0), new immutable SimpleTimeZone(Duration.zero))); - test("7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + test("7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 0, 0, 0), new immutable SimpleTimeZone(Duration.zero))); - test("Fri,7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + test("Fri,7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 0, 0, 0), new immutable SimpleTimeZone(Duration.zero))); auto tooShortMsg = collectExceptionMsg!DateTimeException(parseRFC822DateTime("")); @@ -10208,7 +11016,7 @@ version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LI assert(collectExceptionMsg!DateTimeException(parseRFC822DateTime(value)) == tooShortMsg); } } - }(); + }();} } @@ -10217,24 +11025,32 @@ private: /+ Returns the given hnsecs as an ISO string of fractional seconds. +/ -static string fracSecsToISOString(int hnsecs) @safe pure nothrow +string fracSecsToISOString(int hnsecs) @safe pure nothrow { - assert(hnsecs >= 0); - + import std.array : appender; + auto w = appender!string(); try - { - if (hnsecs == 0) - return ""; + fracSecsToISOString(w, hnsecs); + catch (Exception e) + assert(0, "fracSecsToISOString() threw."); + return w.data; +} - string isoString = format(".%07d", hnsecs); +void fracSecsToISOString(W)(ref W writer, int hnsecs) +{ + import std.conv : toChars; + import std.range : padLeft; - while (isoString[$ - 1] == '0') - isoString.popBack(); + assert(hnsecs >= 0); - return isoString; - } - catch (Exception e) - assert(0, "format() threw."); + if (hnsecs == 0) + return; + + put(writer, '.'); + auto chars = hnsecs.toChars.padLeft('0', 7); + while (chars.back == '0') + chars.popBack(); + put(writer, chars); } @safe unittest @@ -10269,7 +11085,7 @@ static string fracSecsToISOString(int hnsecs) @safe pure nothrow Returns a Duration corresponding to to the given ISO string of fractional seconds. +/ -static Duration fracSecsFromISOString(S)(in S isoString) @trusted pure +static Duration fracSecsFromISOString(S)(scope const S isoString) @safe pure if (isSomeString!S) { import std.algorithm.searching : all; @@ -10301,6 +11117,7 @@ if (isSomeString!S) @safe unittest { + import core.time; static void testFSInvalid(string isoString) { fracSecsFromISOString(isoString); @@ -10417,7 +11234,7 @@ if (validTimeUnits(units) && /+ 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). + `'`') 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 @@ -10438,7 +11255,7 @@ if (validTimeUnits(units) && +/ R _stripCFWS(R)(R range) if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && - (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte))) + (is(immutable ElementType!R == immutable char) || is(immutable ElementType!R == immutable ubyte))) { immutable e = range.length; outer: for (size_t i = 0; i < e; ) @@ -10506,9 +11323,9 @@ if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && import std.stdio : writeln; import std.string : representation; - foreach (cr; AliasSeq!(function(string a){return cast(ubyte[]) a;}, + static 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); @@ -10596,7 +11413,7 @@ if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && 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 @@ -10624,22 +11441,23 @@ if (isIntegral!T && isSigned!T) // The constraints on R were already covered by { 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); + assert(_convDigits!int(to!string(i)) == i, i.to!string); } foreach (str; ["-42", "+42", "1a", "1 ", " ", " 42 "]) { - scope(failure) writeln(str); - assert(_convDigits!int(str) == -1); + assert(_convDigits!int(str) == -1, str); } } -version (unittest) +// NOTE: all the non-simple array literals are wrapped in functions, because +// otherwise importing causes re-evaluation of the static initializers using +// CTFE with unittests enabled +version (StdUnittest) { +private @safe: // Variables to help in testing. Duration currLocalDiffFromUTC; immutable (TimeZone)[] testTZs; @@ -10661,31 +11479,43 @@ version (unittest) } } - MonthDay[] testMonthDays = [MonthDay(1, 1), + MonthDay[] testMonthDays() + { + static result = [MonthDay(1, 1), MonthDay(1, 2), MonthDay(3, 17), MonthDay(7, 4), MonthDay(10, 27), MonthDay(12, 30), MonthDay(12, 31)]; + return result; + } auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31]; - auto testTODs = [TimeOfDay(0, 0, 0), + TimeOfDay[] testTODs() + { + static result = [TimeOfDay(0, 0, 0), TimeOfDay(0, 0, 1), TimeOfDay(0, 1, 0), TimeOfDay(1, 0, 0), TimeOfDay(13, 13, 13), TimeOfDay(23, 59, 59)]; + return result; + } 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[] testTODsThrown() + { + static result = [TimeOfDay(0, 0, 0), TimeOfDay(13, 13, 13), TimeOfDay(23, 59, 59)]; + return result; + } Date[] testDatesBC; Date[] testDatesAD; @@ -10700,7 +11530,9 @@ version (unittest) // 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[] testGregDaysBC() + { + static result = [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)), @@ -10782,8 +11614,12 @@ version (unittest) GregDay(-30, Date(0, 12, 1)), GregDay(-1, Date(0, 12, 30)), GregDay(0, Date(0, 12, 31))]; + return result; + } - auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)), + GregDay[] testGregDaysAD() + { + static result = [GregDay(1, Date(1, 1, 1)), GregDay(2, Date(1, 1, 2)), GregDay(32, Date(1, 2, 1)), GregDay(365, Date(1, 12, 31)), @@ -10851,10 +11687,14 @@ version (unittest) GregDay(734_562, Date(2012, 2, 29)), GregDay(734_563, Date(2012, 3, 1)), GregDay(734_858, Date(2012, 12, 21))]; + return result; + } // 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[] testDaysOfYear() + { + static result = [DayOfYear(1, MonthDay(1, 1)), DayOfYear(2, MonthDay(1, 2)), DayOfYear(3, MonthDay(1, 3)), DayOfYear(31, MonthDay(1, 31)), @@ -10882,8 +11722,12 @@ version (unittest) DayOfYear(363, MonthDay(12, 29)), DayOfYear(364, MonthDay(12, 30)), DayOfYear(365, MonthDay(12, 31))]; + return result; + } - auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear[] testDaysOfLeapYear() + { + static result = [DayOfYear(1, MonthDay(1, 1)), DayOfYear(2, MonthDay(1, 2)), DayOfYear(3, MonthDay(1, 3)), DayOfYear(31, MonthDay(1, 31)), @@ -10912,8 +11756,10 @@ version (unittest) DayOfYear(364, MonthDay(12, 29)), DayOfYear(365, MonthDay(12, 30)), DayOfYear(366, MonthDay(12, 31))]; + return result; + } - void initializeTests() @safe + void initializeTests() { import std.algorithm.sorting : sort; import std.typecons : Rebindable; @@ -10922,11 +11768,13 @@ version (unittest) version (Posix) { + import std.datetime.timezone : PosixTimeZone; immutable otherTZ = lt < 0 ? PosixTimeZone.getTimeZone("Australia/Sydney") : PosixTimeZone.getTimeZone("America/Denver"); } else version (Windows) { + import std.datetime.timezone : WindowsTimeZone; immutable otherTZ = lt < 0 ? WindowsTimeZone.getTimeZone("AUS Eastern Standard Time") : WindowsTimeZone.getTimeZone("Mountain Standard Time"); } diff --git a/libphobos/src/std/datetime/timezone.d b/libphobos/src/std/datetime/timezone.d index 9b744ff..5df42e7 100644 --- a/libphobos/src/std/datetime/timezone.d +++ b/libphobos/src/std/datetime/timezone.d @@ -1,18 +1,37 @@ // Written in the D programming language /++ + +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Time zones) $(TD + $(LREF TimeZone) + $(LREF UTC) + $(LREF LocalTime) + $(LREF PosixTimeZone) + $(LREF WindowsTimeZone) + $(LREF SimpleTimeZone) +)) +$(TR $(TD Utilities) $(TD + $(LREF clearTZEnvVar) + $(LREF parseTZConversions) + $(LREF setTZEnvVar) + $(LREF TZConversions) +)) +)) + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - Authors: Jonathan M Davis - Source: $(PHOBOSSRC std/datetime/_timezone.d) + Authors: $(HTTP jmdavisprog.com, 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; +import core.time : abs, convert, dur, Duration, hours, minutes; +import std.datetime.systime : Clock, stdTimeToUnixTime, SysTime; +import std.range.primitives : back, empty, front, isOutputRange, popFront; +import std.traits : isIntegral, isSomeString; version (OSX) version = Darwin; @@ -26,7 +45,7 @@ else version (WatchOS) version (Windows) { import core.stdc.time : time_t; - import core.sys.windows.windows; + import core.sys.windows.winbase; import core.sys.windows.winsock2; import std.windows.registry; @@ -42,7 +61,7 @@ else version (Posix) import core.sys.posix.sys.types : time_t; } -version (unittest) import std.exception : assertThrown; +version (StdUnittest) import std.exception : assertThrown; /++ @@ -54,8 +73,13 @@ 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). + The name of the time zone. Exactly how the time zone name is formatted + depends on the derived class. In the case of $(LREF PosixTimeZone), it's + the TZ Database name, whereas with $(LREF WindowsTimeZone), it's the + name that Windows chose to give the registry key for that time zone + (typically the name that they give $(LREF stdTime) if the OS is in + English). For other time zone types, what it is depends on how they're + implemented. See_Also: $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ @@ -98,7 +122,7 @@ 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 + but will still return true for `hasDST` because the time zone did at some point have DST. +/ @property abstract bool hasDST() @safe const nothrow; @@ -213,6 +237,7 @@ public: //assert(tz.dstName == dstName); //Locale-dependent assert(tz.hasDST == hasDST); + import std.datetime.date : DateTime; 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); @@ -233,11 +258,14 @@ public: { setTZEnvVar(tzName); - static void testTM(in SysTime st) + static void testTM(scope const SysTime st) { - import core.stdc.time : localtime, tm; + import core.stdc.time : tm; + import core.sys.posix.time : localtime_r; + time_t unixTime = st.toUnixTime(); - tm* osTimeInfo = localtime(&unixTime); + tm osTimeInfo = void; + localtime_r(&unixTime, &osTimeInfo); tm ourTimeInfo = st.toTM(); assert(ourTimeInfo.tm_sec == osTimeInfo.tm_sec); @@ -292,6 +320,7 @@ public: return tz; } + import std.datetime.date : DateTime; 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), @@ -299,6 +328,7 @@ public: /+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)]; + import std.datetime.date : DateTimeException; version (Posix) { version (FreeBSD) enum utcZone = "Etc/UTC"; @@ -360,6 +390,7 @@ public: // a DST switch. foreach (hour; -12 .. 13) { + import std.exception : enforce; auto st = SysTime(dstSwitches[i][0] + dur!"hours"(hour), tz); immutable targetHour = hour < 0 ? hour + 24 : hour; @@ -419,7 +450,7 @@ public: 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. + // https://issues.dlang.org/show_bug.cgi?id=3659 makes this necessary. auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1); foreach (hour; -24 .. 25) @@ -435,6 +466,7 @@ public: __FILE__, line); } + import std.exception : enforce; enforce((utc + offset).hour == local.hour, msg("1")); enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2")); } @@ -510,7 +542,7 @@ public: +/ static immutable(LocalTime) opCall() @trusted pure nothrow { - alias FuncType = @safe pure nothrow immutable(LocalTime) function(); + alias FuncType = immutable(LocalTime) function() @safe pure nothrow; return (cast(FuncType)&singleton)(); } @@ -518,14 +550,12 @@ public: 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. + In principle, this is the name of the local time zone. However, + 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 `stdName` and `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 @@ -566,7 +596,7 @@ public: GetTimeZoneInformation(&tzInfo); // Cannot use to!string() like this should, probably due to bug - // http://d.puremagic.com/issues/show_bug.cgi?id=5016 + // https://issues.dlang.org/show_bug.cgi?id=5016 //return to!string(tzInfo.StandardName); wchar[32] str; @@ -651,7 +681,7 @@ public: GetTimeZoneInformation(&tzInfo); // Cannot use to!string() like this should, probably due to bug - // http://d.puremagic.com/issues/show_bug.cgi?id=5016 + // https://issues.dlang.org/show_bug.cgi?id=5016 //return to!string(tzInfo.DaylightName); wchar[32] str; @@ -714,7 +744,7 @@ 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 + dates but will still return true for `hasDST` because the time zone did at some point have DST. +/ @property override bool hasDST() @trusted const nothrow @@ -727,6 +757,7 @@ public: { try { + import std.datetime.date : Date; 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; @@ -779,17 +810,23 @@ public: +/ override bool dstInEffect(long stdTime) @trusted const nothrow { - import core.stdc.time : localtime, tm; + import core.stdc.time : tm; + time_t unixTime = stdTimeToUnixTime(stdTime); version (Posix) { - tm* timeInfo = localtime(&unixTime); + import core.sys.posix.time : localtime_r; + + tm timeInfo = void; + localtime_r(&unixTime, &timeInfo); return cast(bool)(timeInfo.tm_isdst); } else version (Windows) { + import core.stdc.time : localtime; + // Apparently Windows isn't smart enough to deal with negative time_t. if (unixTime >= 0) { @@ -823,7 +860,7 @@ public: time. See_Also: - $(D TimeZone.utcToTZ) + `TimeZone.utcToTZ` +/ override long utcToTZ(long stdTime) @trusted const nothrow { @@ -831,9 +868,11 @@ public: return stdTime + convert!("seconds", "hnsecs")(tm_gmtoff(stdTime)); else version (Posix) { - import core.stdc.time : localtime, tm; + import core.stdc.time : tm; + import core.sys.posix.time : localtime_r; time_t unixTime = stdTimeToUnixTime(stdTime); - tm* timeInfo = localtime(&unixTime); + tm timeInfo = void; + localtime_r(&unixTime, &timeInfo); return stdTime + convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff); } @@ -858,7 +897,7 @@ public: time to UTC from the appropriate time zone. See_Also: - $(D TimeZone.tzToUTC) + `TimeZone.tzToUTC` Params: adjTime = The time in this time zone that needs to be adjusted to @@ -868,15 +907,17 @@ public: { version (Posix) { - import core.stdc.time : localtime, tm; + import core.stdc.time : tm; + import core.sys.posix.time : localtime_r; time_t unixTime = stdTimeToUnixTime(adjTime); immutable past = unixTime - cast(time_t) convert!("days", "seconds")(1); - tm* timeInfo = localtime(past < unixTime ? &past : &unixTime); + tm timeInfo = void; + localtime_r(past < unixTime ? &past : &unixTime, &timeInfo); immutable pastOffset = timeInfo.tm_gmtoff; immutable future = unixTime + cast(time_t) convert!("days", "seconds")(1); - timeInfo = localtime(future > unixTime ? &future : &unixTime); + localtime_r(future > unixTime ? &future : &unixTime, &timeInfo); immutable futureOffset = timeInfo.tm_gmtoff; if (pastOffset == futureOffset) @@ -886,7 +927,7 @@ public: unixTime -= cast(time_t) convert!("hours", "seconds")(1); unixTime -= pastOffset; - timeInfo = localtime(&unixTime); + localtime_r(&unixTime, &timeInfo); return adjTime - convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff); } @@ -915,6 +956,7 @@ public: { scope(exit) clearTZEnvVar(); + import std.datetime.date : DateTime; 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), @@ -925,6 +967,7 @@ public: foreach (i; 0 .. tzInfos.length) { + import std.exception : enforce; auto tzName = tzInfos[i][0]; setTZEnvVar(tzName); immutable spring = tzInfos[i][3]; @@ -996,7 +1039,7 @@ public: 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. + // https://issues.dlang.org/show_bug.cgi?id=3659 makes this necessary. auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1); foreach (hour; -24 .. 25) @@ -1070,13 +1113,14 @@ private: { long tm_gmtoff(long stdTime) @trusted const nothrow { - import core.stdc.time : localtime, gmtime, tm; + import core.stdc.time : tm; + import core.sys.posix.time : localtime_r, gmtime_r; time_t unixTime = stdTimeToUnixTime(stdTime); - tm* buf = localtime(&unixTime); - tm timeInfo = *buf; - buf = gmtime(&unixTime); - tm timeInfoGmt = *buf; + tm timeInfo = void; + localtime_r(&unixTime, &timeInfo); + tm timeInfoGmt = void; + gmtime_r(&unixTime, &timeInfoGmt); return timeInfo.tm_sec - timeInfoGmt.tm_sec + convert!("minutes", "seconds")(timeInfo.tm_min - timeInfoGmt.tm_min) + @@ -1094,7 +1138,7 @@ final class UTC : TimeZone public: /++ - $(D UTC) is a singleton class. $(D UTC) returns its only instance. + `UTC` is a singleton class. `UTC` returns its only instance. +/ static immutable(UTC) opCall() @safe pure nothrow { @@ -1128,7 +1172,7 @@ public: time. See_Also: - $(D TimeZone.utcToTZ) + `TimeZone.utcToTZ` +/ override long utcToTZ(long stdTime) @safe const nothrow { @@ -1144,6 +1188,7 @@ public: scope(exit) clearTZEnvVar(); setTZEnvVar("UTC"); + import std.datetime.date : Date; auto std = SysTime(Date(2010, 1, 1)); auto dst = SysTime(Date(2010, 7, 1)); assert(UTC().utcToTZ(std.stdTime) == std.stdTime); @@ -1156,7 +1201,7 @@ public: Returns the given hnsecs without changing them at all. See_Also: - $(D TimeZone.tzToUTC) + `TimeZone.tzToUTC` Params: adjTime = The time in this time zone that needs to be adjusted to @@ -1176,6 +1221,7 @@ public: scope(exit) clearTZEnvVar(); setTZEnvVar("UTC"); + import std.datetime.date : Date; auto std = SysTime(Date(2010, 1, 1)); auto dst = SysTime(Date(2010, 7, 1)); assert(UTC().tzToUTC(std.stdTime) == std.stdTime); @@ -1214,10 +1260,10 @@ private: 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). + $(REF SysTime,std,datetime,systime)'s `fromISOString`, + `fromISOExtString`, and `fromSimpleString`. - $(D name) and $(D dstName) are always the empty string since this time zone + `name` and `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. @@ -1315,11 +1361,13 @@ public: 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. + stdName = The `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). + import std.datetime.date : DateTimeException; + import std.exception : enforce; enforce!DateTimeException(abs(utcOffset) < dur!"minutes"(1440), "Offset from UTC must be within range (-24:00 - 24:00)."); super("", stdName, ""); @@ -1359,14 +1407,32 @@ package: +/ static string toISOString(Duration utcOffset) @safe pure { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(5); + toISOString(w, utcOffset); + return w.data; + } + + // ditto + static void toISOString(W)(ref W writer, Duration utcOffset) + if (isOutputRange!(W, char)) + { + import std.datetime.date : DateTimeException; + import std.exception : enforce; + import std.format.write : formattedWrite; 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); + formattedWrite( + writer, + utcOffset < Duration.zero ? "-%02d%02d" : "+%02d%02d", + hours, + minutes + ); } @safe unittest @@ -1376,6 +1442,7 @@ package: return SimpleTimeZone.toISOString(offset); } + import std.datetime.date : DateTimeException; assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440))); assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440))); @@ -1411,7 +1478,19 @@ package: +/ static string toISOExtString(Duration utcOffset) @safe pure { - import std.format : format; + import std.array : appender; + auto w = appender!string(); + w.reserve(6); + toISOExtString(w, utcOffset); + return w.data; + } + + // ditto + static void toISOExtString(W)(ref W writer, Duration utcOffset) + { + import std.datetime.date : DateTimeException; + import std.format.write : formattedWrite; + import std.exception : enforce; immutable absOffset = abs(utcOffset); enforce!DateTimeException(absOffset < dur!"minutes"(1440), @@ -1419,7 +1498,12 @@ package: int hours; int minutes; absOffset.split!("hours", "minutes")(hours, minutes); - return format(utcOffset < Duration.zero ? "-%02d:%02d" : "+%02d:%02d", hours, minutes); + formattedWrite( + writer, + utcOffset < Duration.zero ? "-%02d:%02d" : "+%02d:%02d", + hours, + minutes + ); } @safe unittest @@ -1429,6 +1513,7 @@ package: return SimpleTimeZone.toISOExtString(offset); } + import std.datetime.date : DateTimeException; assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440))); assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440))); @@ -1466,34 +1551,43 @@ package: 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"); + import std.algorithm.searching : startsWith; + import std.conv : text, to, ConvException; + import std.datetime.date : DateTimeException; + import std.exception : enforce; - auto sign = dstr.startsWith('-') ? -1 : 1; - - dstr.popFront(); - enforce!DateTimeException(all!isDigit(dstr), format("Invalid ISO String: %s", dstr)); + auto whichSign = isoString.startsWith('-', '+'); + enforce!DateTimeException(whichSign > 0, text("Invalid ISO String ", isoString)); + isoString = isoString[1 .. $]; + auto sign = whichSign == 1 ? -1 : 1; int hours; int minutes; - if (dstr.length == 2) - hours = to!int(dstr); - else if (dstr.length == 4) + try { - hours = to!int(dstr[0 .. 2]); - minutes = to!int(dstr[2 .. 4]); + // cast to int from uint is used because it checks for + // non digits without extra loops + if (isoString.length == 2) + { + hours = cast(int) to!uint(isoString); + } + else if (isoString.length == 4) + { + hours = cast(int) to!uint(isoString[0 .. 2]); + minutes = cast(int) to!uint(isoString[2 .. 4]); + } + else + { + throw new DateTimeException(text("Invalid ISO String ", isoString)); + } + } + catch (ConvException) + { + throw new DateTimeException(text("Invalid ISO String ", isoString)); } - else - throw new DateTimeException(format("Invalid ISO String: %s", dstr)); - enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", dstr)); + enforce!DateTimeException(hours < 24 && minutes < 60, text("Invalid ISO String ", isoString)); return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes))); } @@ -1517,6 +1611,7 @@ package: "-ab:cd", "+abcd", "-0Z:00", "-Z", "-00Z", "01:00", "12:00", "23:59"]) { + import std.datetime.date : DateTimeException; assertThrown!DateTimeException(SimpleTimeZone.fromISOString(str), format("[%s]", str)); } @@ -1563,7 +1658,7 @@ package: import core.exception : AssertError; import std.format : format; - static void test(in string isoString, int expectedOffset, size_t line = __LINE__) + static void test(scope const string isoString, int expectedOffset, size_t line = __LINE__) { auto stz = SimpleTimeZone.fromISOExtString(isoString); if (stz.utcOffset != dur!"minutes"(expectedOffset)) @@ -1607,44 +1702,54 @@ package: Params: isoExtString = A string which represents a time zone in the ISO format. +/ - static immutable(SimpleTimeZone) fromISOExtString(S)(S isoExtString) @safe pure + static immutable(SimpleTimeZone) fromISOExtString(S)(scope S isoExtString) @safe pure if (isSomeString!S) { - import std.algorithm.searching : startsWith, countUntil, all; - import std.ascii : isDigit; - import std.conv : to; + import std.algorithm.searching : startsWith; + import std.conv : ConvException, to; + import std.datetime.date : DateTimeException; + import std.exception : enforce; import std.format : format; + import std.string : indexOf; - auto dstr = to!dstring(isoExtString); - - enforce!DateTimeException(dstr.startsWith('-', '+'), "Invalid ISO String"); + auto whichSign = isoExtString.startsWith('-', '+'); + enforce!DateTimeException(whichSign > 0, format("Invalid ISO String: %s", isoExtString)); + auto sign = whichSign == 1 ? -1 : 1; - auto sign = dstr.startsWith('-') ? -1 : 1; + isoExtString = isoExtString[1 .. $]; + enforce!DateTimeException(!isoExtString.empty, format("Invalid ISO String: %s", isoExtString)); - dstr.popFront(); - enforce!DateTimeException(!dstr.empty, "Invalid ISO String"); - - immutable colon = dstr.countUntil(':'); - - dstring hoursStr; - dstring minutesStr; + immutable colon = isoExtString.indexOf(':'); + S hoursStr; + S minutesStr; + int hours, minutes; if (colon != -1) { - hoursStr = dstr[0 .. colon]; - minutesStr = dstr[colon + 1 .. $]; - enforce!DateTimeException(minutesStr.length == 2, format("Invalid ISO String: %s", dstr)); + hoursStr = isoExtString[0 .. colon]; + minutesStr = isoExtString[colon + 1 .. $]; + enforce!DateTimeException(minutesStr.length == 2, format("Invalid ISO String: %s", isoExtString)); } else - hoursStr = dstr; + { + hoursStr = isoExtString; + } + + enforce!DateTimeException(hoursStr.length == 2, format("Invalid ISO String: %s", isoExtString)); - 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)); + try + { + // cast to int from uint is used because it checks for + // non digits without extra loops + hours = cast(int) to!uint(hoursStr); + minutes = cast(int) (minutesStr.empty ? 0 : to!uint(minutesStr)); + } + catch (ConvException) + { + throw new DateTimeException(format("Invalid ISO String: %s", isoExtString)); + } - 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)); + enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", isoExtString)); return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes))); } @@ -1668,6 +1773,7 @@ package: "-ab:cd", "abcd", "-0Z:00", "-Z", "-00Z", "0100", "1200", "2359"]) { + import std.datetime.date : DateTimeException; assertThrown!DateTimeException(SimpleTimeZone.fromISOExtString(str), format("[%s]", str)); } @@ -1714,7 +1820,7 @@ package: import core.exception : AssertError; import std.format : format; - static void test(in string isoExtString, int expectedOffset, size_t line = __LINE__) + static void test(scope const string isoExtString, int expectedOffset, size_t line = __LINE__) { auto stz = SimpleTimeZone.fromISOExtString(isoExtString); if (stz.utcOffset != dur!"minutes"(expectedOffset)) @@ -1758,21 +1864,19 @@ private: 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 + use `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. + `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). + To get a `PosixTimeZone`, call `PosixTimeZone.getTimeZone` + (which allows specifying the location the time zone files). 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 + takes leap seconds into account is to use `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 + include leap seconds, and `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). @@ -1796,7 +1900,7 @@ 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 + dates but will still return true for `hasDST` because the time zone did at some point have DST. +/ @property override bool hasDST() @safe const nothrow @@ -1865,7 +1969,7 @@ public: +/ override long tzToUTC(long adjTime) @safe const nothrow { - assert(!_transitions.empty); + assert(!_transitions.empty, "UTC offset's not available"); immutable leapSecs = calculateLeapSeconds(adjTime); time_t unixTime = stdTimeToUnixTime(adjTime); @@ -1901,32 +2005,37 @@ public: } - version (Android) + version (StdDdoc) + { + /++ + The default directory where the TZ Database files are stored. It's + empty for Windows, since Windows doesn't have them. You can also use + the TZDatabaseDir version to pass an arbitrary path at compile-time, + rather than hard-coding it here. Android concatenates all time zone + data into a single file called tzdata and stores it in the directory + below. + +/ + enum defaultTZDatabaseDir = ""; + } + else version (TZDatabaseDir) + { + import std.string : strip; + enum defaultTZDatabaseDir = strip(import("TZDatabaseDirFile")); + } + else version (Android) { - // Android concatenates all time zone data into a single file and stores it here. enum defaultTZDatabaseDir = "/system/usr/share/zoneinfo/"; } else version (Solaris) { - /++ - The default directory where the TZ Database files are. It's empty - for Windows, since Windows doesn't have them. - +/ enum defaultTZDatabaseDir = "/usr/share/lib/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 = ""; } @@ -1952,7 +2061,7 @@ public: 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 found or `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 @@ -1961,6 +2070,8 @@ public: { import std.algorithm.sorting : sort; import std.conv : to; + import std.datetime.date : DateTimeException; + import std.exception : enforce; import std.format : format; import std.path : asNormalizedPath, chainPath; import std.range : retro; @@ -1987,6 +2098,7 @@ public: version (Android) tzFile.seek(*tzfileOffset); immutable gmtZone = name.representation().canFind("GMT"); + import std.datetime.date : DateTimeException; try { _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif"); @@ -2303,12 +2415,13 @@ public: located. Throws: - $(D FileException) if it fails to read from disk. + `FileException` if it fails to read from disk. +/ - static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = defaultTZDatabaseDir) @trusted + static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = defaultTZDatabaseDir) @safe { import std.algorithm.sorting : sort; import std.array : appender; + import std.exception : enforce; import std.format : format; version (Posix) @@ -2320,6 +2433,7 @@ public: subName = replace(strip(subName), "/", dirSeparator); } + import std.datetime.date : DateTimeException; enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir))); @@ -2329,27 +2443,35 @@ public: { import std.algorithm.iteration : filter; import std.algorithm.mutation : copy; - tzdataIndex(tzDatabaseDir).byKey.filter!(a => a.startsWith(subName)).copy(timezones); + + const index = () @trusted { return tzdataIndex(tzDatabaseDir); }(); + index.byKey.filter!(a => a.startsWith(subName)).copy(timezones); } else { - foreach (DirEntry de; dirEntries(tzDatabaseDir, SpanMode.depth)) - { - if (de.isFile) + import std.path : baseName; + // dirEntries is @system because it uses a DirIterator with a + // RefCounted variable, but here, no references to the payload is + // escaped to the outside, so this should be @trusted + () @trusted { + foreach (DirEntry de; dirEntries(tzDatabaseDir, SpanMode.depth)) { - auto tzName = de.name[tzDatabaseDir.length .. $]; - - if (!tzName.extension().empty || - !tzName.startsWith(subName) || - tzName == "leapseconds" || - tzName == "+VERSION") + if (de.isFile) { - continue; - } + auto tzName = de.name[tzDatabaseDir.length .. $]; + + if (!tzName.extension().empty || + !tzName.startsWith(subName) || + baseName(tzName) == "leapseconds" || + tzName == "+VERSION") + { + continue; + } - timezones.put(tzName); + timezones.put(tzName); + } } - } + }(); } sort(timezones.data); @@ -2377,6 +2499,7 @@ public: auto tzNames = getInstalledTZNames(); + import std.datetime.date : DateTimeException; foreach (tzName; tzNames) assertNotThrown!DateTimeException(testPTZSuccess(tzName)); @@ -2403,7 +2526,7 @@ 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 + transition to or from DST) as well as a pointer to the `TTInfo` which holds information on the utc offset past the transition. +/ struct Transition @@ -2440,7 +2563,7 @@ private: +/ struct TTInfo { - this(in TempTTInfo tempTTInfo, string abbrev) @safe immutable pure + this(scope const TempTTInfo tempTTInfo, string abbrev) @safe immutable pure { utcOffset = tempTTInfo.tt_gmtoff; isDST = tempTTInfo.tt_isdst; @@ -2454,7 +2577,7 @@ private: /+ - Struct used to hold information relating to $(D TTInfo) while organizing + Struct used to hold information relating to `TTInfo` while organizing the time zone information prior to putting it in its final form. +/ struct TempTTInfo @@ -2473,7 +2596,7 @@ private: /+ - Struct used to hold information relating to $(D Transition) while + Struct used to hold information relating to `Transition` while organizing the time zone information prior to putting it in its final form. +/ @@ -2493,8 +2616,8 @@ private: /+ - Struct used to hold information relating to $(D Transition) and - $(D TTInfo) while organizing the time zone information prior to putting + Struct used to hold information relating to `Transition` and + `TTInfo` while organizing the time zone information prior to putting it in its final form. +/ struct TransitionType @@ -2517,7 +2640,7 @@ private: Reads an int from a TZ file. +/ static T readVal(T)(ref File tzFile) @trusted - if ((isIntegral!T || isSomeChar!T) || is(Unqual!T == bool)) + if ((isIntegral!T || isSomeChar!T) || is(immutable T == immutable bool)) { import std.bitmanip : bigEndianToNative; T[1] buff; @@ -2544,7 +2667,7 @@ private: /+ - Reads a $(D TempTTInfo) from a TZ file. + Reads a `TempTTInfo` from a TZ file. +/ static T readVal(T)(ref File tzFile) @safe if (is(T == TempTTInfo)) @@ -2557,10 +2680,11 @@ private: /+ Throws: - $(REF DateTimeException,std,datetime,date) if $(D result) is false. + $(REF DateTimeException,std,datetime,date) if `result` is false. +/ static void _enforceValidTZFile(bool result, size_t line = __LINE__) @safe pure { + import std.datetime.date : DateTimeException; if (!result) throw new DateTimeException("Not a valid tzdata file.", __FILE__, line); } @@ -2631,12 +2755,13 @@ private: { import std.concurrency : initOnce; - static __gshared uint[string] _tzIndex; + __gshared uint[string] _tzIndex; // _tzIndex is initialized once and then shared across all threads. initOnce!_tzIndex( { import std.conv : to; + import std.datetime.date : DateTimeException; import std.format : format; import std.path : asNormalizedPath, chainPath; @@ -2709,24 +2834,21 @@ version (StdDdoc) 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. + `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, + $(LREF PosixTimeZone) rather than `WindowsTimeZone`. However, because + `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. + `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). + To get a `WindowsTimeZone`, call `WindowsTimeZone.getTimeZone`. See_Also: $(HTTP www.iana.org/time-zones, Home of the TZ Database files) @@ -2738,7 +2860,7 @@ version (StdDdoc) /++ 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 + current dates but will still return true for `hasDST` because the time zone did at some point have DST. +/ @property override bool hasDST() @safe const nothrow; @@ -2810,7 +2932,7 @@ version (StdDdoc) 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 + `TimeZone.getinstalledTZNames` will return the TZ Database names which are equivalent to the Windows TZ names. +/ static string[] getInstalledTZNames() @safe; @@ -2887,7 +3009,8 @@ else version (Windows) scope tziVal = tzKey.getValue("TZI"); auto binVal = tziVal.value_BINARY; - assert(binVal.length == REG_TZI_FORMAT.sizeof); + assert(binVal.length == REG_TZI_FORMAT.sizeof, + "Unexpected size while getTimeZone with name " ~ name); auto tziFmt = cast(REG_TZI_FORMAT*) binVal.ptr; TIME_ZONE_INFORMATION tzInfo; @@ -2909,6 +3032,7 @@ else version (Windows) return new immutable WindowsTimeZone(name, tzInfo); } + import std.datetime.date : DateTimeException; throw new DateTimeException(format("Failed to find time zone: %s", name)); } @@ -2938,6 +3062,7 @@ else version (Windows) auto tzNames = getInstalledTZNames(); + import std.datetime.date : DateTimeException; foreach (tzName; tzNames) assertNotThrown!DateTimeException(testWTZSuccess(tzName)); } @@ -2952,11 +3077,13 @@ else version (Windows) if (tzInfo.DaylightDate.wMonth == 0) return false; + import std.datetime.date : DateTime, Month; auto utcDateTime = cast(DateTime) SysTime(stdTime, UTC()); //The limits of what SystemTimeToTzSpecificLocalTime will accept. if (utcDateTime.year < 1601) { + import std.datetime.date : Month; if (utcDateTime.month == Month.feb && utcDateTime.day == 29) utcDateTime.day = 28; utcDateTime.year = 1601; @@ -2994,7 +3121,7 @@ else version (Windows) immutable result = SystemTimeToTzSpecificLocalTime(cast(TIME_ZONE_INFORMATION*) tzInfo, &utcTime, &otherTime); - assert(result); + assert(result, "Failed to create SystemTimeToTzSpecificLocalTime"); immutable otherDateTime = DateTime(otherTime.wYear, otherTime.wMonth, @@ -3008,7 +3135,7 @@ else version (Windows) if (minutes == tzInfo.DaylightBias) return true; - assert(minutes == tzInfo.StandardBias); + assert(minutes == tzInfo.StandardBias, "Unexpected difference"); return false; } @@ -3021,6 +3148,7 @@ else version (Windows) TIME_ZONE_INFORMATION tzInfo; GetTimeZoneInformation(&tzInfo); + import std.datetime.date : DateTime; foreach (year; [1600, 1601, 30_827, 30_828]) WindowsTimeZone._dstInEffect(&tzInfo, SysTime(DateTime(year, 1, 1)).stdTime); } @@ -3041,6 +3169,7 @@ else version (Windows) { try { + import std.datetime.date : DateTime, Month; bool dstInEffectForLocalDateTime(DateTime localDateTime) { // The limits of what SystemTimeToTzSpecificLocalTime will accept. @@ -3086,6 +3215,7 @@ else version (Windows) &localTime, &utcTime); assert(result); + assert(result, "Failed to create _tzToUTC"); immutable utcDateTime = DateTime(utcTime.wYear, utcTime.wMonth, @@ -3100,11 +3230,12 @@ else version (Windows) if (minutes == tzInfo.DaylightBias) return true; - assert(minutes == tzInfo.StandardBias); + assert(minutes == tzInfo.StandardBias, "Unexpected difference"); return false; } + import std.datetime.date : DateTime; auto localDateTime = cast(DateTime) SysTime(adjTime, UTC()); auto localDateTimeBefore = localDateTime - dur!"hours"(1); auto localDateTimeAfter = localDateTime + dur!"hours"(1); @@ -3280,6 +3411,7 @@ TZConversions parseTZConversions(string windowsZonesXMLText) @safe pure foreach (line; windowsZonesXMLText.lineSplitter()) { + import std.exception : enforce; // Sample line: // @@ -3362,7 +3494,8 @@ For terms of use, see http://www.unicode.org/copyright.html - + `; diff --git a/libphobos/src/std/demangle.d b/libphobos/src/std/demangle.d index e49bb9f..a78d3d3 100644 --- a/libphobos/src/std/demangle.d +++ b/libphobos/src/std/demangle.d @@ -3,87 +3,67 @@ /** * Demangle D mangled names. * - * Copyright: Copyright Digital Mars 2000 - 2009. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/demangle.d) * $(SCRIPT inhibitQuickIndex = 1;) */ /* - * Copyright Digital Mars 2000 - 2009. + * Copyright The D Language Foundation 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 +/** +Demangle D mangled names. + +Params: + name = the mangled name +Returns: + A `string`. If it is not a D mangled name, it returns its argument name. + */ +string demangle(string name) @safe pure nothrow { - this() - { - super("MangleException"); - } + import core.demangle : demangle; + import std.exception : assumeUnique; + auto ret = demangle(name); + return () @trusted { return ret.assumeUnique; } (); } -+/ - -/***************************** - * 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) { } +/// +@safe pure unittest +{ + // int b in module a + assert(demangle("_D1a1bi") == "int a.b"); + // char array foo in module test + assert(demangle("_D4test3fooAa") == "char[] test.foo"); +} -int main() +/** +This program reads standard in and writes it to standard out, +pretty-printing any found D mangled names. + */ +@system unittest { - string buffer; - bool inword; - int c; + import std.ascii : isAlphaNum; + import std.algorithm.iteration : chunkBy, joiner, map; + import std.algorithm.mutation : copy; + import std.conv : to; + import std.demangle : demangle; + import std.functional : pipe; + import std.stdio : stdin, stdout; - writefln("Try typing in: %s", test.mangleof); - while ((c = fgetc(stdin)) != EOF) + void main() { - 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); - } + stdin.byLineCopy + .map!( + l => l.chunkBy!(a => isAlphaNum(a) || a == '_') + .map!(a => a[1].pipe!(to!string, demangle)).joiner + ) + .copy(stdout.lockingTextWriter); } - 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 index c606f4c..38563b1 100644 --- a/libphobos/src/std/digest/crc.d +++ b/libphobos/src/std/digest/crc.d @@ -18,7 +18,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF crcHexString) $(MYREF crc32Of) $(MYREF crc64EC ) * - * This module conforms to the APIs defined in $(D std.digest). To understand the + * This module conforms to the APIs defined in `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 @@ -38,7 +38,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF crcHexString) $(MYREF crc32Of) $(MYREF crc64EC * References: * $(LINK2 http://en.wikipedia.org/wiki/Cyclic_redundancy_check, Wikipedia on CRC) * - * Source: $(PHOBOSSRC std/digest/_crc.d) + * Source: $(PHOBOSSRC std/digest/crc.d) * * Standards: * Implements the 'common' IEEE CRC32 variant @@ -60,10 +60,6 @@ module std.digest.crc; public import std.digest; -version (unittest) - import std.exception; - - /// @safe unittest { @@ -133,19 +129,19 @@ private T[256][8] genTables(T)(T polynomial) /** * Template API CRC32 implementation. - * See $(D std.digest) for differences between template and OOP API. + * See `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. + * See `std.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. + * See `std.digest` for differences between template and OOP API. */ alias CRC64ISO = CRC!(64, 0xD800000000000000); @@ -158,9 +154,10 @@ alias CRC64ISO = CRC!(64, 0xD800000000000000); * 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. + * See `std.digest` for differences between template and OOP API. */ -struct CRC(uint N, ulong P) if (N == 32 || N == 64) +struct CRC(uint N, ulong P) +if (N == 32 || N == 64) { private: static if (N == 32) @@ -187,7 +184,7 @@ struct CRC(uint N, ulong P) if (N == 32 || N == 64) /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) - * interface for $(D ubyte) and $(D const(ubyte)[]). + * interface for `ubyte` and `const(ubyte)[]`. */ void put(scope const(ubyte)[] data...) @trusted pure nothrow @nogc { @@ -206,15 +203,19 @@ struct CRC(uint N, ulong P) if (N == 32 || N == 64) enum hasLittleEndianUnalignedReads = true; else enum hasLittleEndianUnalignedReads = false; // leave decision to optimizer - static if (hasLittleEndianUnalignedReads) + + uint one = void; + uint two = void; + + if (!__ctfe && hasLittleEndianUnalignedReads) { - uint one = (cast(uint*) data.ptr)[0]; - uint two = (cast(uint*) data.ptr)[1]; + one = (cast(uint*) data.ptr)[0]; + 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]); + one = (data.ptr[3] << 24 | data.ptr[2] << 16 | data.ptr[1] << 8 | data.ptr[0]); + two = (data.ptr[7] << 24 | data.ptr[6] << 16 | data.ptr[5] << 8 | data.ptr[4]); } static if (N == 32) @@ -271,7 +272,7 @@ struct CRC(uint N, ulong P) if (N == 32 || N == 64) } /** - * Works like $(D finish) but does not reset the internal state, so it's possible + * Works like `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 @@ -282,6 +283,22 @@ struct CRC(uint N, ulong P) if (N == 32 || N == 64) } } +@safe unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=13471 + static ubyte[4] foo(string str) + { + ubyte[4] result = str.crc32Of(); + if (result == (ubyte[4]).init) + throw new Exception("this should not be thrown"); + return result; + } + enum buggy1 = foo("Hello World!"); + enum buggy2 = crc32Of("Hello World!"); + assert(buggy1 == buggy2); + assert(buggy1 == "Hello World!".crc32Of()); +} + /// @safe unittest { @@ -347,117 +364,123 @@ struct CRC(uint N, ulong P) if (N == 32 || N == 64) @system unittest { + import std.conv : hexString; ubyte[4] digest; CRC32 crc; crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); - assert(crc.peek() == cast(ubyte[]) x"bd50274c"); + assert(crc.peek() == cast(ubyte[]) hexString!"bd50274c"); crc.start(); crc.put(cast(ubyte[])""); - assert(crc.finish() == cast(ubyte[]) x"00000000"); + assert(crc.finish() == cast(ubyte[]) hexString!"00000000"); digest = crc32Of(""); - assert(digest == cast(ubyte[]) x"00000000"); + assert(digest == cast(ubyte[]) hexString!"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"); + assert(digest == cast(ubyte[]) hexString!"43beb7e8"); digest = crc32Of("abc"); - assert(digest == cast(ubyte[]) x"c2412435"); + assert(digest == cast(ubyte[]) hexString!"c2412435"); digest = crc32Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); - assert(digest == cast(ubyte[]) x"5f3f1a17"); + assert(digest == cast(ubyte[]) hexString!"5f3f1a17"); digest = crc32Of("message digest"); - assert(digest == cast(ubyte[]) x"7f9d1520"); + assert(digest == cast(ubyte[]) hexString!"7f9d1520"); digest = crc32Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - assert(digest == cast(ubyte[]) x"d2e6c21f"); + assert(digest == cast(ubyte[]) hexString!"d2e6c21f"); digest = crc32Of("1234567890123456789012345678901234567890"~ "1234567890123456789012345678901234567890"); - assert(digest == cast(ubyte[]) x"724aa97c"); + assert(digest == cast(ubyte[]) hexString!"724aa97c"); - assert(crcHexString(cast(ubyte[4]) x"c3fcd3d7") == "D7D3FCC3"); + enum ubyte[4] input = cast(ubyte[4]) hexString!"c3fcd3d7"; + assert(crcHexString(input) == "D7D3FCC3"); } @system unittest { + import std.conv : hexString; ubyte[8] digest; CRC64ECMA crc; crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); - assert(crc.peek() == cast(ubyte[]) x"2f121b7575789626"); + assert(crc.peek() == cast(ubyte[]) hexString!"2f121b7575789626"); crc.start(); crc.put(cast(ubyte[])""); - assert(crc.finish() == cast(ubyte[]) x"0000000000000000"); + assert(crc.finish() == cast(ubyte[]) hexString!"0000000000000000"); digest = crc64ECMAOf(""); - assert(digest == cast(ubyte[]) x"0000000000000000"); + assert(digest == cast(ubyte[]) hexString!"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"); + assert(digest == cast(ubyte[]) hexString!"052b652e77840233"); digest = crc64ECMAOf("abc"); - assert(digest == cast(ubyte[]) x"2776271a4a09d82c"); + assert(digest == cast(ubyte[]) hexString!"2776271a4a09d82c"); digest = crc64ECMAOf("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); - assert(digest == cast(ubyte[]) x"4b7cdce3746c449f"); + assert(digest == cast(ubyte[]) hexString!"4b7cdce3746c449f"); digest = crc64ECMAOf("message digest"); - assert(digest == cast(ubyte[]) x"6f9b8a3156c9bc5d"); + assert(digest == cast(ubyte[]) hexString!"6f9b8a3156c9bc5d"); digest = crc64ECMAOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - assert(digest == cast(ubyte[]) x"2656b716e1bf0503"); + assert(digest == cast(ubyte[]) hexString!"2656b716e1bf0503"); digest = crc64ECMAOf("1234567890123456789012345678901234567890"~ "1234567890123456789012345678901234567890"); - assert(digest == cast(ubyte[]) x"bd3eb7765d0a22ae"); + assert(digest == cast(ubyte[]) hexString!"bd3eb7765d0a22ae"); - assert(crcHexString(cast(ubyte[8]) x"c3fcd3d7efbeadde") == "DEADBEEFD7D3FCC3"); + enum ubyte[8] input = cast(ubyte[8]) hexString!"c3fcd3d7efbeadde"; + assert(crcHexString(input) == "DEADBEEFD7D3FCC3"); } @system unittest { + import std.conv : hexString; ubyte[8] digest; CRC64ISO crc; crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); - assert(crc.peek() == cast(ubyte[]) x"f0494ab780989b42"); + assert(crc.peek() == cast(ubyte[]) hexString!"f0494ab780989b42"); crc.start(); crc.put(cast(ubyte[])""); - assert(crc.finish() == cast(ubyte[]) x"0000000000000000"); + assert(crc.finish() == cast(ubyte[]) hexString!"0000000000000000"); digest = crc64ISOOf(""); - assert(digest == cast(ubyte[]) x"0000000000000000"); + assert(digest == cast(ubyte[]) hexString!"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"); + assert(digest == cast(ubyte[]) hexString!"0000000000002034"); digest = crc64ISOOf("abc"); - assert(digest == cast(ubyte[]) x"0000000020c47637"); + assert(digest == cast(ubyte[]) hexString!"0000000020c47637"); digest = crc64ISOOf("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); - assert(digest == cast(ubyte[]) x"5173f717971365e5"); + assert(digest == cast(ubyte[]) hexString!"5173f717971365e5"); digest = crc64ISOOf("message digest"); - assert(digest == cast(ubyte[]) x"a2c355bbc0b93f86"); + assert(digest == cast(ubyte[]) hexString!"a2c355bbc0b93f86"); digest = crc64ISOOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - assert(digest == cast(ubyte[]) x"598B258292E40084"); + assert(digest == cast(ubyte[]) hexString!"598B258292E40084"); digest = crc64ISOOf("1234567890123456789012345678901234567890"~ "1234567890123456789012345678901234567890"); - assert(digest == cast(ubyte[]) x"760cd2d3588bf809"); + assert(digest == cast(ubyte[]) hexString!"760cd2d3588bf809"); - assert(crcHexString(cast(ubyte[8]) x"c3fcd3d7efbeadde") == "DEADBEEFD7D3FCC3"); + enum ubyte[8] input = cast(ubyte[8]) hexString!"c3fcd3d7efbeadde"; + assert(crcHexString(input) == "DEADBEEFD7D3FCC3"); } /** @@ -465,8 +488,8 @@ struct CRC(uint N, ulong P) if (N == 32 || N == 64) * 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 + * data = `InputRange` of `ElementType` implicitly convertible to + * `ubyte`, `ubyte[]` or `ubyte[num]` or one or more arrays * of any type. * * Returns: @@ -500,8 +523,8 @@ ubyte[4] crc32Of(T...)(T data) * 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 + * data = `InputRange` of `ElementType` implicitly convertible to + * `ubyte`, `ubyte[]` or `ubyte[num]` or one or more arrays * of any type. * * Returns: @@ -536,8 +559,8 @@ ubyte[8] crc64ECMAOf(T...)(T data) * 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 + * data = `InputRange` of `ElementType` implicitly convertible to + * `ubyte`, `ubyte[]` or `ubyte[num]` or one or more arrays * of any type. * * Returns: @@ -577,7 +600,7 @@ public alias crcHexString = toHexString!(Order.decreasing, 16); /** * OOP API CRC32 implementation. - * See $(D std.digest) for differences between template and OOP API. + * See `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. @@ -586,7 +609,7 @@ alias CRC32Digest = WrapperDigest!CRC32; /** * OOP API CRC64-ECMA implementation. - * See $(D std.digest.digest) for differences between template and OOP API. + * See `std.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. @@ -595,7 +618,7 @@ alias CRC64ECMADigest = WrapperDigest!CRC64ECMA; /** * OOP API CRC64-ISO implementation. - * See $(D std.digest.digest) for differences between template and OOP API. + * See `std.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. @@ -653,53 +676,55 @@ alias CRC64ISODigest = WrapperDigest!CRC64ISO; @system unittest { + import std.conv : hexString; import std.range; + import std.exception; auto crc = new CRC32Digest(); crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); - assert(crc.peek() == cast(ubyte[]) x"bd50274c"); + assert(crc.peek() == cast(ubyte[]) hexString!"bd50274c"); crc.reset(); crc.put(cast(ubyte[])""); - assert(crc.finish() == cast(ubyte[]) x"00000000"); + assert(crc.finish() == cast(ubyte[]) hexString!"00000000"); crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); ubyte[20] result; auto result2 = crc.finish(result[]); - assert(result[0 .. 4] == result2 && result2 == cast(ubyte[]) x"bd50274c"); + assert(result[0 .. 4] == result2 && result2 == cast(ubyte[]) hexString!"bd50274c"); debug assertThrown!Error(crc.finish(result[0 .. 3])); assert(crc.length == 4); - assert(crc.digest("") == cast(ubyte[]) x"00000000"); + assert(crc.digest("") == cast(ubyte[]) hexString!"00000000"); - assert(crc.digest("a") == cast(ubyte[]) x"43beb7e8"); + assert(crc.digest("a") == cast(ubyte[]) hexString!"43beb7e8"); - assert(crc.digest("abc") == cast(ubyte[]) x"c2412435"); + assert(crc.digest("abc") == cast(ubyte[]) hexString!"c2412435"); assert(crc.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") - == cast(ubyte[]) x"5f3f1a17"); + == cast(ubyte[]) hexString!"5f3f1a17"); - assert(crc.digest("message digest") == cast(ubyte[]) x"7f9d1520"); + assert(crc.digest("message digest") == cast(ubyte[]) hexString!"7f9d1520"); assert(crc.digest("abcdefghijklmnopqrstuvwxyz") - == cast(ubyte[]) x"bd50274c"); + == cast(ubyte[]) hexString!"bd50274c"); assert(crc.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") - == cast(ubyte[]) x"d2e6c21f"); + == cast(ubyte[]) hexString!"d2e6c21f"); assert(crc.digest("1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890") - == cast(ubyte[]) x"724aa97c"); + == cast(ubyte[]) hexString!"724aa97c"); ubyte[] onemilliona = new ubyte[1000000]; onemilliona[] = 'a'; auto digest = crc32Of(onemilliona); - assert(digest == cast(ubyte[]) x"BCBF25DC"); + assert(digest == cast(ubyte[]) hexString!"BCBF25DC"); auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); digest = crc32Of(oneMillionRange); - assert(digest == cast(ubyte[]) x"BCBF25DC"); + assert(digest == cast(ubyte[]) hexString!"BCBF25DC"); } diff --git a/libphobos/src/std/digest/digest.d b/libphobos/src/std/digest/digest.d index 325afd4..01fdbd7 100644 --- a/libphobos/src/std/digest/digest.d +++ b/libphobos/src/std/digest/digest.d @@ -1,21 +1,3 @@ +// @@@DEPRECATED_2.101@@@ +deprecated("import std.digest instead of std.digest.digest. std.digest.digest will be removed in 2.101") module std.digest.digest; - -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 index bf0cbf3..6864fc3 100644 --- a/libphobos/src/std/digest/hmac.d +++ b/libphobos/src/std/digest/hmac.d @@ -11,7 +11,7 @@ Macros: License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Source: $(PHOBOSSRC std/digest/_hmac.d) +Source: $(PHOBOSSRC std/digest/hmac.d) */ module std.digest.hmac; @@ -73,18 +73,20 @@ if (hashBlockSize % 8 == 0) { // if secret is too long, shorten it by computing its hash typeof(digest.finish()) buffer = void; + typeof(secret) secretBytes = secret; + if (secret.length > blockSize / 8) { digest.start(); digest.put(secret); buffer = digest.finish(); - secret = buffer[]; + secretBytes = 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[]); + secretBytes.copy(key[]); start(); } @@ -92,7 +94,7 @@ if (hashBlockSize % 8 == 0) /// @safe pure nothrow @nogc unittest { - import std.digest.hmac, std.digest.sha; + import std.digest.sha : SHA1; import std.string : representation; auto hmac = HMAC!SHA1("My s3cR3T keY".representation); hmac.put("Hello, world".representation); @@ -129,7 +131,7 @@ if (hashBlockSize % 8 == 0) /// @safe pure nothrow @nogc unittest { - import std.digest.hmac, std.digest.sha; + import std.digest.sha : SHA1; import std.string : representation; string data1 = "Hello, world", data2 = "Hola mundo"; auto hmac = HMAC!SHA1("My s3cR3T keY".representation); @@ -196,7 +198,7 @@ if (hashBlockSize % 8 == 0) /// @safe pure nothrow @nogc unittest { - import std.digest.hmac, std.digest.sha; + import std.digest.sha : SHA1; import std.string : representation; string data1 = "Hello, world", data2 = "Hola mundo"; auto hmac = HMAC!SHA1("My s3cR3T keY".representation); @@ -211,7 +213,7 @@ if (hashBlockSize % 8 == 0) } } -/// Convenience constructor for $(LREF HMAC). +/// ditto template hmac(H) if (isDigest!H && hasBlockSize!H) { @@ -237,7 +239,7 @@ if (isDigest!H) /// @safe pure nothrow @nogc unittest { - import std.digest.hmac, std.digest.sha; + import std.digest.sha : SHA1; import std.string : representation; string data1 = "Hello, world", data2 = "Hola mundo"; auto digest = hmac!SHA1("My s3cR3T keY".representation) @@ -272,7 +274,7 @@ if (isDigest!H) @safe pure nothrow @nogc unittest { import std.algorithm.iteration : map; - import std.digest.hmac, std.digest.sha; + import std.digest.sha : SHA1; import std.string : representation; string data = "Hello, world"; auto digest = data.representation @@ -287,10 +289,21 @@ if (isDigest!H) } } -version (unittest) +/// +@safe pure nothrow @nogc unittest { - import std.digest : toHexString, LetterCase; - alias hex = toHexString!(LetterCase.lower); + import std.digest.sha : SHA1; + 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); } @safe pure nothrow @nogc @@ -309,10 +322,15 @@ unittest import std.digest.md : MD5; import std.digest.sha : SHA1, SHA256; + // Note, can't be UFCS because we don't want to import inside + // version (StdUnittest). + import std.digest : toHexString, LetterCase; + alias hex = toHexString!(LetterCase.lower); + ubyte[] nada; - assert(hmac!MD5 (nada, nada).hex == "74e6f7298a9c2d168935f58c001bad88"); - assert(hmac!SHA1 (nada, nada).hex == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d"); - assert(hmac!SHA256(nada, nada).hex == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"); + assert(hex(hmac!MD5 (nada, nada)) == "74e6f7298a9c2d168935f58c001bad88"); + assert(hex(hmac!SHA1 (nada, nada)) == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d"); + assert(hex(hmac!SHA256(nada, nada)) == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"); import std.string : representation; auto key = "key".representation, @@ -322,13 +340,13 @@ unittest 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(hex(data.hmac!MD5 (key)) == "80070713463e7749b90c2dc24911e275"); + assert(hex(data.hmac!SHA1 (key)) == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"); + assert(hex(data.hmac!SHA256(key)) == "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(hex(data.hmac!MD5 (long_key)) == "e1728d68e05beae186ea768561963778"); + assert(hex(data.hmac!SHA1 (long_key)) == "560d3cd77316e57ab4bba0c186966200d2b37ba3"); + assert(hex(data.hmac!SHA256(long_key)) == "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)); diff --git a/libphobos/src/std/digest/md.d b/libphobos/src/std/digest/md.d index 1b621cf..0c4e42b 100644 --- a/libphobos/src/std/digest/md.d +++ b/libphobos/src/std/digest/md.d @@ -18,7 +18,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF md5Of)) ) ) - * This module conforms to the APIs defined in $(D std.digest). To understand the + * This module conforms to the APIs defined in `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 @@ -36,7 +36,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF md5Of)) * References: * $(LINK2 http://en.wikipedia.org/wiki/Md5, Wikipedia on MD5) * - * Source: $(PHOBOSSRC std/digest/_md.d) + * Source: $(PHOBOSSRC std/digest/md.d) * */ @@ -81,20 +81,13 @@ public import std.digest; 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. + * See `std.digest` for differences between template and OOP API. */ struct MD5 { + import core.bitop : rol; private: // magic initialization constants uint[4] _state = [0x67452301,0xefcdab89,0x98badcfe,0x10325476]; // state (ABCD) @@ -126,7 +119,7 @@ struct MD5 @safe pure nothrow @nogc { a += F (b, c, d) + x + ac; - a = rotateLeft(a, s); + a = rol(a, s); a += b; } @@ -134,7 +127,7 @@ struct MD5 @safe pure nothrow @nogc { a += G (b, c, d) + x + ac; - a = rotateLeft(a, s); + a = rol(a, s); a += b; } @@ -142,7 +135,7 @@ struct MD5 @safe pure nothrow @nogc { a += H (b, c, d) + x + ac; - a = rotateLeft(a, s); + a = rol(a, s); a += b; } @@ -150,7 +143,7 @@ struct MD5 @safe pure nothrow @nogc { a += I (b, c, d) + x + ac; - a = rotateLeft(a, s); + a = rol(a, s); a += b; } @@ -289,7 +282,7 @@ struct MD5 /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) - * interface for $(D ubyte) and $(D const(ubyte)[]). + * interface for `ubyte` and `const(ubyte)[]`. * * Example: * ---- @@ -445,6 +438,7 @@ struct MD5 @system unittest { import std.range; + import std.conv : hexString; ubyte[16] digest; @@ -452,44 +446,45 @@ struct MD5 md5.put(cast(ubyte[])"abcdef"); md5.start(); md5.put(cast(ubyte[])""); - assert(md5.finish() == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + assert(md5.finish() == cast(ubyte[]) hexString!"d41d8cd98f00b204e9800998ecf8427e"); digest = md5Of(""); - assert(digest == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + assert(digest == cast(ubyte[]) hexString!"d41d8cd98f00b204e9800998ecf8427e"); digest = md5Of("a"); - assert(digest == cast(ubyte[]) x"0cc175b9c0f1b6a831c399e269772661"); + assert(digest == cast(ubyte[]) hexString!"0cc175b9c0f1b6a831c399e269772661"); digest = md5Of("abc"); - assert(digest == cast(ubyte[]) x"900150983cd24fb0d6963f7d28e17f72"); + assert(digest == cast(ubyte[]) hexString!"900150983cd24fb0d6963f7d28e17f72"); digest = md5Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); - assert(digest == cast(ubyte[]) x"8215ef0796a20bcaaae116d3876c664a"); + assert(digest == cast(ubyte[]) hexString!"8215ef0796a20bcaaae116d3876c664a"); digest = md5Of("message digest"); - assert(digest == cast(ubyte[]) x"f96b697d7cb7938d525a2f31aaf161d0"); + assert(digest == cast(ubyte[]) hexString!"f96b697d7cb7938d525a2f31aaf161d0"); digest = md5Of("abcdefghijklmnopqrstuvwxyz"); - assert(digest == cast(ubyte[]) x"c3fcd3d76192e4007dfb496cca67e13b"); + assert(digest == cast(ubyte[]) hexString!"c3fcd3d76192e4007dfb496cca67e13b"); digest = md5Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - assert(digest == cast(ubyte[]) x"d174ab98d277d9f5a5611c2c9f419d9f"); + assert(digest == cast(ubyte[]) hexString!"d174ab98d277d9f5a5611c2c9f419d9f"); digest = md5Of("1234567890123456789012345678901234567890"~ "1234567890123456789012345678901234567890"); - assert(digest == cast(ubyte[]) x"57edf4a22be3c955ac49da2e2107b67a"); + assert(digest == cast(ubyte[]) hexString!"57edf4a22be3c955ac49da2e2107b67a"); - assert(toHexString(cast(ubyte[16]) x"c3fcd3d76192e4007dfb496cca67e13b") + enum ubyte[16] input = cast(ubyte[16]) hexString!"c3fcd3d76192e4007dfb496cca67e13b"; + assert(toHexString(input) == "C3FCD3D76192E4007DFB496CCA67E13B"); ubyte[] onemilliona = new ubyte[1000000]; onemilliona[] = 'a'; digest = md5Of(onemilliona); - assert(digest == cast(ubyte[]) x"7707D6AE4E027C70EEA2A935C2296F21"); + assert(digest == cast(ubyte[]) hexString!"7707D6AE4E027C70EEA2A935C2296F21"); auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); digest = md5Of(oneMillionRange); - assert(digest == cast(ubyte[]) x"7707D6AE4E027C70EEA2A935C2296F21"); + assert(digest == cast(ubyte[]) hexString!"7707D6AE4E027C70EEA2A935C2296F21"); } /** @@ -511,7 +506,7 @@ auto md5Of(T...)(T data) /** * OOP API MD5 implementation. - * See $(D std.digest) for differences between template and OOP API. + * See `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. @@ -547,17 +542,18 @@ alias MD5Digest = WrapperDigest!MD5; @system unittest { + import std.conv : hexString; auto md5 = new MD5Digest(); md5.put(cast(ubyte[])"abcdef"); md5.reset(); md5.put(cast(ubyte[])""); - assert(md5.finish() == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + assert(md5.finish() == cast(ubyte[]) hexString!"d41d8cd98f00b204e9800998ecf8427e"); md5.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); ubyte[20] result; auto result2 = md5.finish(result[]); - assert(result[0 .. 16] == result2 && result2 == cast(ubyte[]) x"c3fcd3d76192e4007dfb496cca67e13b"); + assert(result[0 .. 16] == result2 && result2 == cast(ubyte[]) hexString!"c3fcd3d76192e4007dfb496cca67e13b"); debug { @@ -567,24 +563,24 @@ alias MD5Digest = WrapperDigest!MD5; assert(md5.length == 16); - assert(md5.digest("") == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + assert(md5.digest("") == cast(ubyte[]) hexString!"d41d8cd98f00b204e9800998ecf8427e"); - assert(md5.digest("a") == cast(ubyte[]) x"0cc175b9c0f1b6a831c399e269772661"); + assert(md5.digest("a") == cast(ubyte[]) hexString!"0cc175b9c0f1b6a831c399e269772661"); - assert(md5.digest("abc") == cast(ubyte[]) x"900150983cd24fb0d6963f7d28e17f72"); + assert(md5.digest("abc") == cast(ubyte[]) hexString!"900150983cd24fb0d6963f7d28e17f72"); assert(md5.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") - == cast(ubyte[]) x"8215ef0796a20bcaaae116d3876c664a"); + == cast(ubyte[]) hexString!"8215ef0796a20bcaaae116d3876c664a"); - assert(md5.digest("message digest") == cast(ubyte[]) x"f96b697d7cb7938d525a2f31aaf161d0"); + assert(md5.digest("message digest") == cast(ubyte[]) hexString!"f96b697d7cb7938d525a2f31aaf161d0"); assert(md5.digest("abcdefghijklmnopqrstuvwxyz") - == cast(ubyte[]) x"c3fcd3d76192e4007dfb496cca67e13b"); + == cast(ubyte[]) hexString!"c3fcd3d76192e4007dfb496cca67e13b"); assert(md5.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") - == cast(ubyte[]) x"d174ab98d277d9f5a5611c2c9f419d9f"); + == cast(ubyte[]) hexString!"d174ab98d277d9f5a5611c2c9f419d9f"); assert(md5.digest("1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890") - == cast(ubyte[]) x"57edf4a22be3c955ac49da2e2107b67a"); + == cast(ubyte[]) hexString!"57edf4a22be3c955ac49da2e2107b67a"); } diff --git a/libphobos/src/std/digest/murmurhash.d b/libphobos/src/std/digest/murmurhash.d index b5b5bc7..533db92 100644 --- a/libphobos/src/std/digest/murmurhash.d +++ b/libphobos/src/std/digest/murmurhash.d @@ -685,7 +685,7 @@ the 'put' method. assert(hashed == [181, 151, 88, 252]); } -version (unittest) +version (StdUnittest) { private auto hash(H, Element = H.Element)(string data) { diff --git a/libphobos/src/std/digest/package.d b/libphobos/src/std/digest/package.d index f4646ae..ea3738b 100644 --- a/libphobos/src/std/digest/package.d +++ b/libphobos/src/std/digest/package.d @@ -1,7 +1,7 @@ /** - * This module describes the _digest APIs used in Phobos. All digests follow + * 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. + * can be used with every digest type. * $(SCRIPT inhibitQuickIndex = 1;) @@ -11,13 +11,13 @@ $(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) + $(MYREF ExampleDigest) $(MYREF digest) $(MYREF hexDigest) $(MYREF makeDigest) ) ) $(TR $(TDNW OOP API) $(TD $(MYREF Digest) ) ) -$(TR $(TDNW Helper functions) $(TD $(MYREF toHexString)) +$(TR $(TDNW Helper functions) $(TD $(MYREF toHexString) $(MYREF secureEqual)) ) $(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDigest)) ) @@ -28,18 +28,18 @@ $(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDi * 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. + * and the OOP API class is called "$(B x)Digest". For example we have `MD5` <--> `MD5Digest`, + * `CRC32` <--> `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. + * but the $(LREF Digest) classes still have to be created using `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 + * 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. + * 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. * @@ -47,7 +47,7 @@ $(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDi * Authors: * Johannes Pfau * - * Source: $(PHOBOSSRC std/_digest/_package.d) + * Source: $(PHOBOSSRC std/digest/package.d) * * CTFE: * Digests do not work in CTFE @@ -199,7 +199,7 @@ version (ExampleDigest) * 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)) + * $(LI A digest passing the $(LREF isDigest) test is always an `OutputRange`) * ) */ struct ExampleDigest @@ -208,8 +208,8 @@ version (ExampleDigest) /** * 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 + * interface for `ubyte` and `const(ubyte)[]`. + * The following usages of `put` must work for any type which * passes $(LREF isDigest): * Example: * ---- @@ -240,7 +240,7 @@ version (ExampleDigest) * * 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 + * `ubyte[16]` is just used as an example. It is guaranteed that the type is a * static array of ubytes. * * $(UL @@ -350,7 +350,7 @@ template DigestType(T) } /** - * Used to check if a digest supports the $(D peek) method. + * Used to check if a digest supports the `peek` method. * Peek has exactly the same function signatures as finish, but it doesn't reset * the digest's internal state. * @@ -392,7 +392,7 @@ template hasPeek(T) } /** - * Checks whether the digest has a $(D blockSize) member, which contains the + * Checks whether the digest has a `blockSize` member, which contains the * digest's internal block size in bits. It is primarily used by $(REF HMAC, std,digest,hmac). */ @@ -427,17 +427,68 @@ package template isDigestibleRange(Range) * 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]) + * range= an `InputRange` with `ElementType` `ubyte`, `ubyte[]` or `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(); + alias E = ElementType!Range; // Not necessarily ubyte. Could be ubyte[N] or ubyte[] or something w/alias this. + static if (!(__traits(isScalar, E) && E.sizeof == 1)) + { + foreach (e; range) + hash.put(e); + return hash.finish(); + } + else + { + static if (hasBlockSize!Hash) + enum bufferBytes = Hash.blockSize >= (8192 * 8) ? 8192 : Hash.blockSize <= 64 ? 8 : (Hash.blockSize / 8); + else + enum bufferBytes = 8; + ubyte[bufferBytes] buffer = void; + static if (isRandomAccessRange!Range && hasLength!Range) + { + const end = range.length; + size_t i = 0; + while (end - i >= buffer.length) + { + foreach (ref e; buffer) + e = range[i++]; + hash.put(buffer); + } + if (const remaining = end - i) + { + foreach (ref e; buffer[0 .. remaining]) + e = range[i++]; + hash.put(buffer[0 .. remaining]); + } + return hash.finish(); + } + else + { + for (;;) + { + size_t n = buffer.length; + foreach (i, ref e; buffer) + { + if (range.empty) + { + n = i; + break; + } + e = range.front; + range.popFront(); + } + if (n) + hash.put(buffer[0 .. n]); + if (n != buffer.length) + return hash.finish(); + } + } + } } /// @@ -490,7 +541,7 @@ if (allSatisfy!(isArray, typeof(data))) * * 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]) + * range= an `InputRange` with `ElementType` `ubyte`, `ubyte[]` or `ubyte[num]` */ char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, Range)(ref Range range) if (!isArray!Range && isDigestibleRange!Range) @@ -562,7 +613,7 @@ Hash makeDigest(Hash)() * The Digest interface is the base interface which is implemented by all digests. * * Note: - * A Digest implementation is always an $(D OutputRange) + * A Digest implementation is always an `OutputRange` */ interface Digest { @@ -570,7 +621,7 @@ interface Digest /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) - * interface for $(D ubyte) and $(D const(ubyte)[]). + * interface for `ubyte` and `const(ubyte)[]`. * * Example: * ---- @@ -589,7 +640,7 @@ interface Digest * 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). + * `reset` manually after a call to $(LREF finish). */ @trusted nothrow void reset(); @@ -606,7 +657,7 @@ interface Digest @trusted nothrow ubyte[] finish(); ///ditto nothrow ubyte[] finish(ubyte[] buf); - //@@@BUG@@@ http://d.puremagic.com/issues/show_bug.cgi?id=6549 + // https://issues.dlang.org/show_bug.cgi?id=6549 /*in { assert(buf.length >= this.length); @@ -686,6 +737,16 @@ enum Order : bool decreasing /// } +/// +@safe unittest +{ + import std.digest.crc : CRC32; + + auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog"); + assert(crc32.toHexString!(Order.decreasing) == "414FA339"); + assert(crc32.toHexString!(LetterCase.lower, Order.decreasing) == "414fa339"); +} + /** * Used to convert a hash value (a static or dynamic array of ubytes) to a string. @@ -705,39 +766,12 @@ enum Order : bool * 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) +(const 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--; - } - } + toHexStringImpl!(order, letterCase)(digest, result); return result; } @@ -751,35 +785,8 @@ char[num*2] toHexString(LetterCase letterCase, Order order = Order.increasing, s 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]; - } - } + toHexStringImpl!(order, letterCase)(digest, result); import std.exception : assumeUnique; // memory was just created, so casting to immutable is safe return () @trusted { return assumeUnique(result); }(); @@ -842,6 +849,42 @@ ref T[N] asArray(size_t N, T)(ref T[] source, string errorMsg = "") } /* + * Fill in a preallocated buffer with the ASCII hex representation from a byte buffer + */ +private void toHexStringImpl(Order order, LetterCase letterCase, BB, HB) +(scope const ref BB byteBuffer, ref HB hexBuffer){ + static if (letterCase == LetterCase.upper) + { + import std.ascii : hexDigits = hexDigits; + } + else + { + import std.ascii : hexDigits = lowerHexDigits; + } + + size_t i; + static if (order == Order.increasing) + { + foreach (u; byteBuffer) + { + hexBuffer[i++] = hexDigits[u >> 4]; + hexBuffer[i++] = hexDigits[u & 15]; + } + } + else + { + size_t j = byteBuffer.length -1; + while (i < byteBuffer.length*2) + { + hexBuffer[i++] = hexDigits[byteBuffer[j] >> 4]; + hexBuffer[i++] = hexDigits[byteBuffer[j] & 15]; + j--; + } + } +} + + +/* * Returns the length (in bytes) of the hash value produced by T. */ template digestLength(T) @@ -884,7 +927,7 @@ if (isDigest!T) : Digest /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) - * interface for $(D ubyte) and $(D const(ubyte)[]). + * interface for `ubyte` and `const(ubyte)[]`. */ @trusted nothrow void put(scope const(ubyte)[] data...) { @@ -895,7 +938,7 @@ if (isDigest!T) : Digest * 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). + * `reset` manually after a call to $(LREF finish). */ @trusted nothrow void reset() { @@ -931,9 +974,9 @@ if (isDigest!T) : Digest nothrow ubyte[] finish(ubyte[] buf) in { - assert(buf.length >= this.length); + assert(buf.length >= this.length, "Given buffer is smaller than the local buffer."); } - body + do { enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~ "big, check " ~ typeof(this).stringof ~ ".length!"; @@ -953,10 +996,10 @@ if (isDigest!T) : Digest version (StdDdoc) { /** - * Works like $(D finish) but does not reset the internal state, so it's possible + * Works like `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. + * These functions are only available if `hasPeek!T` is true. */ @trusted ubyte[] peek(ubyte[] buf) const; ///ditto @@ -967,9 +1010,9 @@ if (isDigest!T) : Digest @trusted ubyte[] peek(ubyte[] buf) const in { - assert(buf.length >= this.length); + assert(buf.length >= this.length, "Given buffer is smaller than the local buffer."); } - body + do { enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~ "big, check " ~ typeof(this).stringof ~ ".length!"; @@ -1123,12 +1166,12 @@ if (isInputRange!R1 && isInputRange!R2 && !isInfinite!R1 && !isInfinite!R2 && 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; + auto hex1 = data.hmac!SHA1(secret).toHexString; + auto hex2 = data.hmac!SHA1(secret).toHexString; + auto hex3 = "data1".representation.hmac!SHA1(secret).toHexString; - assert( secureEqual(hex1, hex2)); - assert(!secureEqual(hex1, hex3)); + assert( secureEqual(hex1[], hex2[])); + assert(!secureEqual(hex1[], hex3[])); } @system pure unittest diff --git a/libphobos/src/std/digest/ripemd.d b/libphobos/src/std/digest/ripemd.d index c47a741..4a5deca 100644 --- a/libphobos/src/std/digest/ripemd.d +++ b/libphobos/src/std/digest/ripemd.d @@ -21,7 +21,7 @@ $(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 + * This module publicly imports `std.digest` and can be used as a stand-alone * module. * * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). @@ -40,7 +40,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF ripemd160Of)) * $(LI $(LINK2 http://en.wikipedia.org/wiki/RIPEMD-160, Wikipedia on RIPEMD-160)) * ) * - * Source: $(PHOBOSSRC std/digest/_ripemd.d) + * Source: $(PHOBOSSRC std/digest/ripemd.d) * */ @@ -85,20 +85,13 @@ public import std.digest; 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. + * See `std.digest` for differences between template and OOP API. */ struct RIPEMD160 { + import core.bitop : rol; private: // magic initialization constants uint[5] _state = [0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0]; // state (ABCDE) @@ -132,40 +125,40 @@ struct RIPEMD160 @safe pure nothrow @nogc { a += F(b, c, d) + x; - a = rotateLeft(a, s) + e; - c = rotateLeft(c, 10); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(c, 10); } /* @@ -177,40 +170,40 @@ struct RIPEMD160 @safe pure nothrow @nogc { a += F(b, c, d) + x; - a = rotateLeft(a, s) + e; - c = rotateLeft(c, 10); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(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); + a = rol(a, s) + e; + c = rol(c, 10); } /* @@ -445,7 +438,7 @@ struct RIPEMD160 /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) - * interface for $(D ubyte) and $(D const(ubyte)[]). + * interface for `ubyte` and `const(ubyte)[]`. * * Example: * ---- @@ -613,6 +606,7 @@ struct RIPEMD160 @system unittest { + import std.conv : hexString; import std.range; ubyte[20] digest; @@ -621,44 +615,45 @@ struct RIPEMD160 md.put(cast(ubyte[])"abcdef"); md.start(); md.put(cast(ubyte[])""); - assert(md.finish() == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + assert(md.finish() == cast(ubyte[]) hexString!"9c1185a5c5e9fc54612808977ee8f548b2258d31"); digest = ripemd160Of(""); - assert(digest == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + assert(digest == cast(ubyte[]) hexString!"9c1185a5c5e9fc54612808977ee8f548b2258d31"); digest = ripemd160Of("a"); - assert(digest == cast(ubyte[]) x"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); + assert(digest == cast(ubyte[]) hexString!"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); digest = ripemd160Of("abc"); - assert(digest == cast(ubyte[]) x"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); + assert(digest == cast(ubyte[]) hexString!"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); digest = ripemd160Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); - assert(digest == cast(ubyte[]) x"12a053384a9c0c88e405a06c27dcf49ada62eb2b"); + assert(digest == cast(ubyte[]) hexString!"12a053384a9c0c88e405a06c27dcf49ada62eb2b"); digest = ripemd160Of("message digest"); - assert(digest == cast(ubyte[]) x"5d0689ef49d2fae572b881b123a85ffa21595f36"); + assert(digest == cast(ubyte[]) hexString!"5d0689ef49d2fae572b881b123a85ffa21595f36"); digest = ripemd160Of("abcdefghijklmnopqrstuvwxyz"); - assert(digest == cast(ubyte[]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); + assert(digest == cast(ubyte[]) hexString!"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); digest = ripemd160Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - assert(digest == cast(ubyte[]) x"b0e20b6e3116640286ed3a87a5713079b21f5189"); + assert(digest == cast(ubyte[]) hexString!"b0e20b6e3116640286ed3a87a5713079b21f5189"); digest = ripemd160Of("1234567890123456789012345678901234567890"~ "1234567890123456789012345678901234567890"); - assert(digest == cast(ubyte[]) x"9b752e45573d4b39f4dbd3323cab82bf63326bfb"); + assert(digest == cast(ubyte[]) hexString!"9b752e45573d4b39f4dbd3323cab82bf63326bfb"); - assert(toHexString(cast(ubyte[20]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc") + enum ubyte[20] input = cast(ubyte[20]) hexString!"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"; + assert(toHexString(input) == "F71C27109C692C1B56BBDCEB5B9D2865B3708DBC"); ubyte[] onemilliona = new ubyte[1000000]; onemilliona[] = 'a'; digest = ripemd160Of(onemilliona); - assert(digest == cast(ubyte[]) x"52783243c1697bdbe16d37f97f68f08325dc1528"); + assert(digest == cast(ubyte[]) hexString!"52783243c1697bdbe16d37f97f68f08325dc1528"); auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); digest = ripemd160Of(oneMillionRange); - assert(digest == cast(ubyte[]) x"52783243c1697bdbe16d37f97f68f08325dc1528"); + assert(digest == cast(ubyte[]) hexString!"52783243c1697bdbe16d37f97f68f08325dc1528"); } /** @@ -680,7 +675,7 @@ auto ripemd160Of(T...)(T data) /** * OOP API RIPEMD160 implementation. - * See $(D std.digest) for differences between template and OOP API. + * See `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. @@ -716,17 +711,18 @@ alias RIPEMD160Digest = WrapperDigest!RIPEMD160; @system unittest { + import std.conv : hexString; auto md = new RIPEMD160Digest(); md.put(cast(ubyte[])"abcdef"); md.reset(); md.put(cast(ubyte[])""); - assert(md.finish() == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + assert(md.finish() == cast(ubyte[]) hexString!"9c1185a5c5e9fc54612808977ee8f548b2258d31"); md.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); ubyte[20] result; auto result2 = md.finish(result[]); - assert(result[0 .. 20] == result2 && result2 == cast(ubyte[]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); + assert(result[0 .. 20] == result2 && result2 == cast(ubyte[]) hexString!"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); debug { @@ -736,27 +732,27 @@ alias RIPEMD160Digest = WrapperDigest!RIPEMD160; assert(md.length == 20); - assert(md.digest("") == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + assert(md.digest("") == cast(ubyte[]) hexString!"9c1185a5c5e9fc54612808977ee8f548b2258d31"); - assert(md.digest("a") == cast(ubyte[]) x"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); + assert(md.digest("a") == cast(ubyte[]) hexString!"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); - assert(md.digest("abc") == cast(ubyte[]) x"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); + assert(md.digest("abc") == cast(ubyte[]) hexString!"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); assert(md.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") - == cast(ubyte[]) x"12a053384a9c0c88e405a06c27dcf49ada62eb2b"); + == cast(ubyte[]) hexString!"12a053384a9c0c88e405a06c27dcf49ada62eb2b"); - assert(md.digest("message digest") == cast(ubyte[]) x"5d0689ef49d2fae572b881b123a85ffa21595f36"); + assert(md.digest("message digest") == cast(ubyte[]) hexString!"5d0689ef49d2fae572b881b123a85ffa21595f36"); assert(md.digest("abcdefghijklmnopqrstuvwxyz") - == cast(ubyte[]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); + == cast(ubyte[]) hexString!"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); assert(md.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") - == cast(ubyte[]) x"b0e20b6e3116640286ed3a87a5713079b21f5189"); + == cast(ubyte[]) hexString!"b0e20b6e3116640286ed3a87a5713079b21f5189"); assert(md.digest("1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890") - == cast(ubyte[]) x"9b752e45573d4b39f4dbd3323cab82bf63326bfb"); + == cast(ubyte[]) hexString!"9b752e45573d4b39f4dbd3323cab82bf63326bfb"); assert(md.digest(new ubyte[160/8]) // 160 zero bits - == cast(ubyte[]) x"5c00bd4aca04a9057c09b20b05f723f2e23deb65"); + == cast(ubyte[]) hexString!"5c00bd4aca04a9057c09b20b05f723f2e23deb65"); } diff --git a/libphobos/src/std/digest/sha.d b/libphobos/src/std/digest/sha.d index 671e07b..5bbf7ea 100644 --- a/libphobos/src/std/digest/sha.d +++ b/libphobos/src/std/digest/sha.d @@ -26,7 +26,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF sha1Of)) * 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 + * This module publicly imports `std.digest` and can be used as a stand-alone * module. * * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). @@ -46,7 +46,7 @@ $(TR $(TDNW Helpers) $(TD $(MYREF sha1Of)) * $(LI $(LINK2 http://en.wikipedia.org/wiki/Secure_Hash_Algorithm, Wikipedia article about SHA)) * ) * - * Source: $(PHOBOSSRC std/digest/_sha.d) + * Source: $(PHOBOSSRC std/digest/sha.d) * */ @@ -101,13 +101,9 @@ module std.digest.sha; hash1 = sha1.finish(); } -version (Win64) +version (D_InlineAsm_X86) { - // wrong calling convention -} -else version (D_InlineAsm_X86) -{ - version (D_PIC) {} // Bugzilla 9378 + version (D_PIC) {} // https://issues.dlang.org/show_bug.cgi?id=9378 else private version = USE_SSSE3; } else version (D_InlineAsm_X86_64) @@ -115,14 +111,7 @@ else version (D_InlineAsm_X86_64) private version = USE_SSSE3; } -version (LittleEndian) import core.bitop : bswap; - - -version (unittest) -{ - import std.exception; -} - +import core.bitop; public import std.digest; @@ -130,59 +119,16 @@ 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) { - version (LittleEndian) - immutable uint res = bswap(val); - else - immutable uint res = val; - return *cast(ubyte[4]*) &res; + private alias nativeToBigEndian = bswap; + private alias bigEndianToNative = bswap; } - -private ulong bigEndianToNative(ubyte[8] val) @trusted pure nothrow @nogc +else pragma(inline, true) private pure @nogc nothrow @safe { - 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)); + uint nativeToBigEndian(uint val) { return val; } + ulong nativeToBigEndian(ulong val) { return val; } + alias bigEndianToNative = nativeToBigEndian; } /** @@ -193,7 +139,7 @@ private ulong rotateRight(ulong x, uint n) @safe pure nothrow @nogc * 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. + * See `std.digest` for differences between template and OOP API. */ struct SHA(uint hashBlockSize, uint digestSize) { @@ -220,7 +166,8 @@ struct SHA(uint hashBlockSize, uint digestSize) if (ssse3) { version (D_InlineAsm_X86_64) - // constants as extra argument for PIC, see Bugzilla 9378 + // constants as extra argument for PIC + // see https://issues.dlang.org/show_bug.cgi?id=9378 transformSSSE3(state, block, &sse3_constants); else transformSSSE3(state, block); @@ -364,6 +311,7 @@ struct SHA(uint hashBlockSize, uint digestSize) /* * Basic SHA1/SHA2 functions. */ + pragma(inline, true) static @safe pure nothrow @nogc { /* All SHA1/SHA2 */ @@ -374,16 +322,16 @@ struct SHA(uint hashBlockSize, uint digestSize) 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; } + uint BigSigma0(uint x) { return core.bitop.ror(x, 2) ^ core.bitop.ror(x, 13) ^ core.bitop.ror(x, 22); } + uint BigSigma1(uint x) { return core.bitop.ror(x, 6) ^ core.bitop.ror(x, 11) ^ core.bitop.ror(x, 25); } + uint SmSigma0(uint x) { return core.bitop.ror(x, 7) ^ core.bitop.ror(x, 18) ^ x >> 3; } + uint SmSigma1(uint x) { return core.bitop.ror(x, 17) ^ core.bitop.ror(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; } + ulong BigSigma0(ulong x) { return core.bitop.ror(x, 28) ^ core.bitop.ror(x, 34) ^ core.bitop.ror(x, 39); } + ulong BigSigma1(ulong x) { return core.bitop.ror(x, 14) ^ core.bitop.ror(x, 18) ^ core.bitop.ror(x, 41); } + ulong SmSigma0(ulong x) { return core.bitop.ror(x, 1) ^ core.bitop.ror(x, 8) ^ x >> 7; } + ulong SmSigma1(ulong x) { return core.bitop.ror(x, 19) ^ core.bitop.ror(x, 61) ^ x >> 6; } } /* @@ -392,41 +340,41 @@ struct SHA(uint hashBlockSize, uint digestSize) 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); + uint Wi = W[i] = bigEndianToNative(*cast(uint*) &((*input)[i*4])); + T = Ch(B, C, D) + E + core.bitop.rol(A, 5) + Wi + 0x5a827999; + B = core.bitop.rol(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); + W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Ch(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0x5a827999; + B = core.bitop.rol(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); + W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Parity(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0x6ed9eba1; + B = core.bitop.rol(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); + W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Maj(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0x8f1bbcdc; + B = core.bitop.rol(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); + W[i&15] = core.bitop.rol(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Parity(B, C, D) + E + core.bitop.rol(A, 5) + W[i&15] + 0xca62c1d6; + B = core.bitop.rol(B, 30); } private static void transformX86(uint[5]* state, const(ubyte[64])* block) pure nothrow @nogc @@ -534,17 +482,20 @@ struct SHA(uint hashBlockSize, uint digestSize) /* * SHA2 basic transformation. Transforms state based on block. */ + pragma(inline, true) 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 Wi = W[i] = bigEndianToNative(*cast(Word*) &((*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; } + // Temporarily disable inlining because it increases build speed by 10x. + // pragma(inline, true) 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 @@ -694,7 +645,7 @@ struct SHA(uint hashBlockSize, uint digestSize) /** * Use this to feed the digest with data. * Also implements the $(REF isOutputRange, std,range,primitives) - * interface for $(D ubyte) and $(D const(ubyte)[]). + * interface for `ubyte` and `const(ubyte)[]`. */ void put(scope const(ubyte)[] input...) @trusted pure nothrow @nogc { @@ -758,11 +709,11 @@ struct SHA(uint hashBlockSize, uint digestSize) { static if (blockSize == 512) { - ubyte[32] data = void; + uint[8] data = void; uint index, padLen; /* Save number of bits */ - ubyte[8] bits = nativeToBigEndian(count[0]); + ulong bits = nativeToBigEndian(count[0]); /* Pad out to 56 mod 64. */ index = (cast(uint) count[0] >> 3) & (64 - 1); @@ -770,25 +721,23 @@ struct SHA(uint hashBlockSize, uint digestSize) put(padding[0 .. padLen]); /* Append length (before padding) */ - put(bits); + put((cast(ubyte*) &bits)[0 .. bits.sizeof]); /* Store state in digest */ - for (auto i = 0; i < ((digestSize == 160)? 5 : 8); i++) - data[i*4..(i+1)*4] = nativeToBigEndian(state[i])[]; + static foreach (i; 0 .. (digestSize == 160) ? 5 : 8) + data[i] = nativeToBigEndian(state[i]); /* Zeroize sensitive information. */ start(); - return data[0 .. digestSize/8]; + return (cast(ubyte*) data.ptr)[0 .. digestSize/8]; } else static if (blockSize == 1024) { - ubyte[64] data = void; + ulong[8] data = void; uint index, padLen; /* Save number of bits */ - ubyte[16] bits; - bits[ 0 .. 8] = nativeToBigEndian(count[1]); - bits[8 .. 16] = nativeToBigEndian(count[0]); + ulong[2] bits = [nativeToBigEndian(count[1]), nativeToBigEndian(count[0])]; /* Pad out to 112 mod 128. */ index = (cast(uint) count[0] >> 3) & (128 - 1); @@ -796,15 +745,15 @@ struct SHA(uint hashBlockSize, uint digestSize) put(padding[0 .. padLen]); /* Append length (before padding) */ - put(bits); + put((cast(ubyte*) &bits)[0 .. bits.sizeof]); /* Store state in digest */ - for (auto i = 0; i < 8; i++) - data[i*8..(i+1)*8] = nativeToBigEndian(state[i])[]; + static foreach (i; 0 .. 8) + data[i] = nativeToBigEndian(state[i]); /* Zeroize sensitive information. */ start(); - return data[0 .. digestSize/8]; + return (cast(ubyte*) data.ptr)[0 .. digestSize/8]; } else static assert(0); @@ -820,14 +769,6 @@ struct SHA(uint hashBlockSize, uint digestSize) } } -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 { @@ -869,6 +810,14 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte assert(toHexString(sha.finish()) == "5BA93C9DB0CFF93F52B521D7420E43F6EDA2784F"); } +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 { assert(isDigest!SHA1); @@ -897,19 +846,20 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte sha.put(cast(ubyte[])"abcdef"); sha.start(); sha.put(cast(ubyte[])""); - assert(sha.finish() == cast(ubyte[]) x"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + assert(sha.finish() == cast(ubyte[]) hexString!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); SHA224 sha224; sha224.put(cast(ubyte[])"abcdef"); sha224.start(); sha224.put(cast(ubyte[])""); - assert(sha224.finish() == cast(ubyte[]) x"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + assert(sha224.finish() == cast(ubyte[]) hexString!"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); SHA256 sha256; sha256.put(cast(ubyte[])"abcdef"); sha256.start(); sha256.put(cast(ubyte[])""); - assert(sha256.finish() == cast(ubyte[]) x"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + assert(sha256.finish() == cast(ubyte[]) + hexString!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); SHA384 sha384; sha384.put(cast(ubyte[])"abcdef"); @@ -922,20 +872,22 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte sha512.put(cast(ubyte[])"abcdef"); sha512.start(); sha512.put(cast(ubyte[])""); - assert(sha512.finish() == cast(ubyte[]) hexString!("cf83e1357eefb8bdf1542850d66d8007d620e4050b571" + 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"); + assert(sha512_224.finish() == cast(ubyte[]) hexString!"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"); + assert(sha512_256.finish() == cast(ubyte[]) + hexString!"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a"); digest = sha1Of (""); digest224 = sha224Of (""); @@ -944,15 +896,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + assert(digest224 == cast(ubyte[]) hexString!"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4"); + assert(digest512_256 == cast(ubyte[]) hexString!"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a"); digest = sha1Of ("a"); digest224 = sha224Of ("a"); @@ -961,15 +913,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); + assert(digest224 == cast(ubyte[]) hexString!"abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327"); + assert(digest512_256 == cast(ubyte[]) hexString!"455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8"); digest = sha1Of ("abc"); digest224 = sha224Of ("abc"); @@ -978,15 +930,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"a9993e364706816aba3e25717850c26c9cd0d89d"); + assert(digest224 == cast(ubyte[]) hexString!"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa"); + assert(digest512_256 == cast(ubyte[]) hexString!"53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23"); digest = sha1Of ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); digest224 = sha224Of ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); @@ -995,15 +947,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + assert(digest224 == cast(ubyte[]) hexString!"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"e5302d6d54bb242275d1e7622d68df6eb02dedd13f564c13dbda2174"); + assert(digest512_256 == cast(ubyte[]) hexString!"bde8e1f9f19bb9fd3406c90ec6bc47bd36d8ada9f11880dbc8a22a7078b6a461"); digest = sha1Of ("message digest"); digest224 = sha224Of ("message digest"); @@ -1012,15 +964,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"c12252ceda8be8994d5fa0290a47231c1d16aae3"); + assert(digest224 == cast(ubyte[]) hexString!"2cb21c83ae2f004de7e81c3c7019cbcb65b71ab656b22d6d0c39b8eb"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"ad1a4db188fe57064f4f24609d2a83cd0afb9b398eb2fcaeaae2c564"); + assert(digest512_256 == cast(ubyte[]) hexString!"0cf471fd17ed69d990daf3433c89b16d63dec1bb9cb42a6094604ee5d7b4e9fb"); digest = sha1Of ("abcdefghijklmnopqrstuvwxyz"); digest224 = sha224Of ("abcdefghijklmnopqrstuvwxyz"); @@ -1029,15 +981,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); + assert(digest224 == cast(ubyte[]) hexString!"45a5f72c39c5cff2522eb3429799e49e5f44b356ef926bcf390dccc2"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"ff83148aa07ec30655c1b40aff86141c0215fe2a54f767d3f38743d8"); + assert(digest512_256 == cast(ubyte[]) hexString!"fc3189443f9c268f626aea08a756abe7b726b05f701cb08222312ccfd6710a26"); digest = sha1Of ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); digest224 = sha224Of ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); @@ -1046,15 +998,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"761c457bf73b14d27e9e9265c46f4b4dda11f940"); + assert(digest224 == cast(ubyte[]) hexString!"bff72b4fcb7d75e5632900ac5f90d219e05e97a7bde72e740db393d9"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"a8b4b9174b99ffc67d6f49be9981587b96441051e16e6dd036b140d3"); + assert(digest512_256 == cast(ubyte[]) hexString!"cdf1cc0effe26ecc0c13758f7b4a48e000615df241284185c39eb05d355bb9c8"); digest = sha1Of ("1234567890123456789012345678901234567890"~ "1234567890123456789012345678901234567890"); @@ -1070,15 +1022,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte "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(digest == cast(ubyte[]) hexString!"50abf5706a150990a08b2c5ea40fa0e585554732"); + assert(digest224 == cast(ubyte[]) hexString!"b50aecbe4e9bb0b57bc5f3ae760a8e01db24f203fb3cdcd13148046e"); + assert(digest256 == cast(ubyte[]) hexString!"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"); + assert(digest512_224 == cast(ubyte[]) hexString!"ae988faaa47e401a45f704d1272d99702458fea2ddc6582827556dd2"); + assert(digest512_256 == cast(ubyte[]) hexString!"2c9fdbc0c90bdd87612ee8455474f9044850241dc105b1e8b94b8ddf5fac9148"); ubyte[] onemilliona = new ubyte[1000000]; onemilliona[] = 'a'; @@ -1089,15 +1041,15 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"34aa973cd4c4daa4f61eeb2bdbad27316534016f"); + assert(digest224 == cast(ubyte[]) hexString!"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67"); + assert(digest256 == cast(ubyte[]) hexString!"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(digest512_224 == cast(ubyte[]) hexString!"37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287"); + assert(digest512_256 == cast(ubyte[]) hexString!"9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21"); auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); digest = sha1Of(oneMillionRange); @@ -1107,17 +1059,18 @@ alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte 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(digest == cast(ubyte[]) hexString!"34aa973cd4c4daa4f61eeb2bdbad27316534016f"); + assert(digest224 == cast(ubyte[]) hexString!"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67"); + assert(digest256 == cast(ubyte[]) hexString!"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(digest512_224 == cast(ubyte[]) hexString!"37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287"); + assert(digest512_256 == cast(ubyte[]) hexString!"9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21"); - assert(toHexString(cast(ubyte[20]) x"a9993e364706816aba3e25717850c26c9cd0d89d") + enum ubyte[20] input = cast(ubyte[20]) hexString!"a9993e364706816aba3e25717850c26c9cd0d89d"; + assert(toHexString(input) == "A9993E364706816ABA3E25717850C26C9CD0D89D"); } @@ -1190,16 +1143,16 @@ auto sha512_256Of(T...)(T data) { string a = "Mary has ", b = "a little lamb"; int[] c = [ 1, 2, 3, 4, 5 ]; - string d = toHexString(sha1Of(a, b, c)); + auto d = toHexString(sha1Of(a, b, c)); version (LittleEndian) - assert(d == "CDBB611D00AC2387B642D3D7BDF4C3B342237110", d); + assert(d[] == "CDBB611D00AC2387B642D3D7BDF4C3B342237110", d.dup); else - assert(d == "A0F1196C7A379C09390476D9CA4AA11B71FD11C8", d); + assert(d[] == "A0F1196C7A379C09390476D9CA4AA11B71FD11C8", d.dup); } /** * OOP API SHA1 and SHA2 implementations. - * See $(D std.digest) for differences between template and OOP API. + * See `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. @@ -1247,45 +1200,47 @@ alias SHA512_256Digest = WrapperDigest!SHA512_256; ///ditto @system unittest { + import std.conv : hexString; + import std.exception; auto sha = new SHA1Digest(); sha.put(cast(ubyte[])"abcdef"); sha.reset(); sha.put(cast(ubyte[])""); - assert(sha.finish() == cast(ubyte[]) x"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + assert(sha.finish() == cast(ubyte[]) hexString!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); sha.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); ubyte[22] result; auto result2 = sha.finish(result[]); - assert(result[0 .. 20] == result2 && result2 == cast(ubyte[]) x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); + assert(result[0 .. 20] == result2 && result2 == cast(ubyte[]) hexString!"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); debug assertThrown!Error(sha.finish(result[0 .. 15])); assert(sha.length == 20); - assert(sha.digest("") == cast(ubyte[]) x"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + assert(sha.digest("") == cast(ubyte[]) hexString!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); - assert(sha.digest("a") == cast(ubyte[]) x"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); + assert(sha.digest("a") == cast(ubyte[]) hexString!"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); - assert(sha.digest("abc") == cast(ubyte[]) x"a9993e364706816aba3e25717850c26c9cd0d89d"); + assert(sha.digest("abc") == cast(ubyte[]) hexString!"a9993e364706816aba3e25717850c26c9cd0d89d"); assert(sha.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") - == cast(ubyte[]) x"84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + == cast(ubyte[]) hexString!"84983e441c3bd26ebaae4aa1f95129e5e54670f1"); - assert(sha.digest("message digest") == cast(ubyte[]) x"c12252ceda8be8994d5fa0290a47231c1d16aae3"); + assert(sha.digest("message digest") == cast(ubyte[]) hexString!"c12252ceda8be8994d5fa0290a47231c1d16aae3"); assert(sha.digest("abcdefghijklmnopqrstuvwxyz") - == cast(ubyte[]) x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); + == cast(ubyte[]) hexString!"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); assert(sha.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") - == cast(ubyte[]) x"761c457bf73b14d27e9e9265c46f4b4dda11f940"); + == cast(ubyte[]) hexString!"761c457bf73b14d27e9e9265c46f4b4dda11f940"); assert(sha.digest("1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890") - == cast(ubyte[]) x"50abf5706a150990a08b2c5ea40fa0e585554732"); + == cast(ubyte[]) hexString!"50abf5706a150990a08b2c5ea40fa0e585554732"); ubyte[] onemilliona = new ubyte[1000000]; onemilliona[] = 'a'; - assert(sha.digest(onemilliona) == cast(ubyte[]) x"34aa973cd4c4daa4f61eeb2bdbad27316534016f"); + assert(sha.digest(onemilliona) == cast(ubyte[]) hexString!"34aa973cd4c4daa4f61eeb2bdbad27316534016f"); } diff --git a/libphobos/src/std/encoding.d b/libphobos/src/std/encoding.d index 5bbd0d4..9334f31 100644 --- a/libphobos/src/std/encoding.d +++ b/libphobos/src/std/encoding.d @@ -3,14 +3,16 @@ /** 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 +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. +(also known as LATIN-1), ISO-8859-2 (LATIN-2), WINDOWS-1250, WINDOWS-1251 +and WINDOWS-1252. $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD Decode) $(TD @@ -53,6 +55,7 @@ $(TR $(TD Encoding schemes) $(TD $(LREF EncodingSchemeUtf32Native) $(LREF EncodingSchemeUtf8) $(LREF EncodingSchemeWindows1250) + $(LREF EncodingSchemeWindows1251) $(LREF EncodingSchemeWindows1252) )) $(TR $(TD Representation) $(TD @@ -64,6 +67,8 @@ $(TR $(TD Representation) $(TD $(LREF Latin2String) $(LREF Windows1250Char) $(LREF Windows1250String) + $(LREF Windows1251Char) + $(LREF Windows1251String) $(LREF Windows1252Char) $(LREF Windows1252String) )) @@ -71,9 +76,9 @@ $(TR $(TD Exceptions) $(TD $(LREF INVALID_SEQUENCE) $(LREF EncodingException) )) -) +)) -For cases where the _encoding is not known at compile-time, but is +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. @@ -84,16 +89,16 @@ 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. +WINDOWS-1251, 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. +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) +Source: $(PHOBOSSRC std/encoding.d) */ /* Copyright Janice Caron 2008 - 2009. @@ -277,6 +282,55 @@ import std.typecons; "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD", ]; + // HELPER FUNCTIONS + // we can probably do this better... + static char toHexDigit(int n) + { + return "0123456789ABCDEF"[n & 0xF]; + } + + static string makeReadable(string s) + { + string r = "\""; + foreach (char c;s) + { + if (c >= 0x20 && c < 0x80) + { + r ~= c; + } + else + { + r ~= "\\x"; + r ~= toHexDigit(c >> 4); + r ~= toHexDigit(c); + } + } + r ~= "\""; + return r; + } + + void transcodeReverse(Src,Dst)(immutable(Src)[] s, out immutable(Dst)[] r) + { + static if (is(Src == Dst)) + { + return s; + } + else static if (is(Src == AsciiChar)) + { + transcodeReverse!(char,Dst)(cast(string) s,r); + } + else + { + foreach_reverse (d;codePoints(s)) + { + foreach_reverse (c;codeUnits!(Dst)(d)) + { + r = c ~ r; + } + } + } + } + // Make sure everything that should be valid, is foreach (a;validStrings) { @@ -395,6 +449,10 @@ import std.typecons; Windows1250String y; transcode(s,y); assert(y == cast(Windows1250Char[])[0x8e, 'l', 'u', 0x9d, 'o', 'u', 0xe8, 'k', 0xfd, ' ', 'k', 0xf9, 0xf2]); + s = "\u0402lu\u0403ou\u201D\u045C k\u0414\u044F"; + Windows1251String s51; + transcode(s,s51); + assert(s51 == cast(Windows1251Char[])[0x80, 'l', 'u', 0x81, 'o', 'u', 0x94, 0x9d, ' ', 'k', 0xc4, 0xff]); } // Make sure we can count properly @@ -419,7 +477,7 @@ import std.typecons; //============================================================================= -/** Special value returned by $(D safeDecode) */ +/** Special value returned by `safeDecode` */ enum dchar INVALID_SEQUENCE = cast(dchar) 0xFFFFFFFF; template EncoderFunctions() @@ -595,7 +653,7 @@ struct CodePoints(E) { assert(isValid(s)); } - body + do { this.s = s; } @@ -663,7 +721,7 @@ struct CodeUnits(E) { assert(isValidCodePoint(d)); } - body + do { s = encode!(E)(d); } @@ -727,7 +785,7 @@ private template GenericEncoder() { assert(canEncode(c)); } - body + do { return 1; } @@ -790,7 +848,7 @@ private template GenericEncoder() //============================================================================= /** Defines various character sets. */ -enum AsciiChar : ubyte { init } +enum AsciiChar : ubyte { _init } /// Ditto alias AsciiString = immutable(AsciiChar)[]; @@ -819,7 +877,7 @@ template EncoderInstance(CharType : AsciiChar) { assert(canEncode(c)); } - body + do { return 1; } @@ -870,7 +928,7 @@ template EncoderInstance(CharType : AsciiChar) //============================================================================= /** Defines an Latin1-encoded character. */ -enum Latin1Char : ubyte { init } +enum Latin1Char : ubyte { _init } /** Defines an Latin1-encoded string (as an array of $(D immutable(Latin1Char))). @@ -902,7 +960,7 @@ template EncoderInstance(CharType : Latin1Char) { assert(canEncode(c)); } - body + do { return 1; } @@ -946,7 +1004,7 @@ template EncoderInstance(CharType : Latin1Char) //============================================================================= /// Defines a Latin2-encoded character. -enum Latin2Char : ubyte { init } +enum Latin2Char : ubyte { _init } /** * Defines an Latin2-encoded string (as an array of $(D @@ -1026,7 +1084,7 @@ private template EncoderInstance(CharType : Latin2Char) //============================================================================= /// Defines a Windows1250-encoded character. -enum Windows1250Char : ubyte { init } +enum Windows1250Char : ubyte { _init } /** * Defines an Windows1250-encoded string (as an array of $(D @@ -1115,11 +1173,106 @@ private template EncoderInstance(CharType : Windows1250Char) } //============================================================================= +// WINDOWS-1251 +//============================================================================= + +/// Defines a Windows1251-encoded character. +enum Windows1251Char : ubyte { _init } + +/** + * Defines an Windows1251-encoded string (as an array of $(D + * immutable(Windows1251Char))). + */ +alias Windows1251String = immutable(Windows1251Char)[]; + +private template EncoderInstance(CharType : Windows1251Char) +{ + import std.typecons : Tuple, tuple; + + alias E = Windows1251Char; + alias EString = Windows1251String; + + @property string encodingName() @safe pure nothrow @nogc + { + return "windows-1251"; + } + + private static immutable dchar m_charMapStart = 0x80; + private static immutable dchar m_charMapEnd = 0xff; + + private immutable wstring charMap = + "\u0402\u0403\u201A\u0453\u201E\u2026\u2020\u2021"~ + "\u20AC\u2030\u0409\u2039\u040A\u040C\u040B\u040F"~ + "\u0452\u2018\u2019\u201C\u201D\u2022\u2013\u2014"~ + "\uFFFD\u2122\u0459\u203A\u045A\u045C\u045B\u045F"~ + "\u00A0\u040E\u045E\u0408\u00A4\u0490\u00A6\u00A7"~ + "\u0401\u00A9\u0404\u00AB\u00AC\u00AD\u00AE\u0407"~ + "\u00B0\u00B1\u0406\u0456\u0491\u00B5\u00B6\u00B7"~ + "\u0451\u2116\u0454\u00BB\u0458\u0405\u0455\u0457"~ + "\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417"~ + "\u0418\u0419\u041A\u041B\u041C\u041D\u041E\u041F"~ + "\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427"~ + "\u0428\u0429\u042A\u042B\u042C\u042D\u042E\u042F"~ + "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437"~ + "\u0438\u0439\u043A\u043B\u043C\u043D\u043E\u043F"~ + "\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447"~ + "\u0448\u0449\u044A\u044B\u044C\u044D\u044E\u044F"; + + private immutable Tuple!(wchar, char)[] bstMap = [ + tuple('\u0432','\xE2'),tuple('\u0412','\xC2'),tuple('\u0453','\x83'), + tuple('\u0401','\xA8'),tuple('\u0422','\xD2'),tuple('\u0442','\xF2'), + tuple('\u2018','\x91'),tuple('\u00AD','\xAD'),tuple('\u0409','\x8A'), + tuple('\u041A','\xCA'),tuple('\u042A','\xDA'),tuple('\u043A','\xEA'), + tuple('\u044A','\xFA'),tuple('\u045B','\x9E'),tuple('\u2022','\x95'), + tuple('\u00A7','\xA7'),tuple('\u00B5','\xB5'),tuple('\u0405','\xBD'), + tuple('\u040E','\xA1'),tuple('\u0416','\xC6'),tuple('\u041E','\xCE'), + tuple('\u0426','\xD6'),tuple('\u042E','\xDE'),tuple('\u0436','\xE6'), + tuple('\u043E','\xEE'),tuple('\u0446','\xF6'),tuple('\u044E','\xFE'), + tuple('\u0457','\xBF'),tuple('\u0490','\xA5'),tuple('\u201D','\x94'), + tuple('\u203A','\x9B'),tuple('\u00A4','\xA4'),tuple('\u00AB','\xAB'), + tuple('\u00B0','\xB0'),tuple('\u00B7','\xB7'),tuple('\u0403','\x81'), + tuple('\u0407','\xAF'),tuple('\u040B','\x8E'),tuple('\u0410','\xC0'), + tuple('\u0414','\xC4'),tuple('\u0418','\xC8'),tuple('\u041C','\xCC'), + tuple('\u0420','\xD0'),tuple('\u0424','\xD4'),tuple('\u0428','\xD8'), + tuple('\u042C','\xDC'),tuple('\u0430','\xE0'),tuple('\u0434','\xE4'), + tuple('\u0438','\xE8'),tuple('\u043C','\xEC'),tuple('\u0440','\xF0'), + tuple('\u0444','\xF4'),tuple('\u0448','\xF8'),tuple('\u044C','\xFC'), + tuple('\u0451','\xB8'),tuple('\u0455','\xBE'),tuple('\u0459','\x9A'), + tuple('\u045E','\xA2'),tuple('\u2013','\x96'),tuple('\u201A','\x82'), + tuple('\u2020','\x86'),tuple('\u2030','\x89'),tuple('\u2116','\xB9'), + tuple('\u00A0','\xA0'),tuple('\u00A6','\xA6'),tuple('\u00A9','\xA9'), + tuple('\u00AC','\xAC'),tuple('\u00AE','\xAE'),tuple('\u00B1','\xB1'), + tuple('\u00B6','\xB6'),tuple('\u00BB','\xBB'),tuple('\u0402','\x80'), + tuple('\u0404','\xAA'),tuple('\u0406','\xB2'),tuple('\u0408','\xA3'), + tuple('\u040A','\x8C'),tuple('\u040C','\x8D'),tuple('\u040F','\x8F'), + tuple('\u0411','\xC1'),tuple('\u0413','\xC3'),tuple('\u0415','\xC5'), + tuple('\u0417','\xC7'),tuple('\u0419','\xC9'),tuple('\u041B','\xCB'), + tuple('\u041D','\xCD'),tuple('\u041F','\xCF'),tuple('\u0421','\xD1'), + tuple('\u0423','\xD3'),tuple('\u0425','\xD5'),tuple('\u0427','\xD7'), + tuple('\u0429','\xD9'),tuple('\u042B','\xDB'),tuple('\u042D','\xDD'), + tuple('\u042F','\xDF'),tuple('\u0431','\xE1'),tuple('\u0433','\xE3'), + tuple('\u0435','\xE5'),tuple('\u0437','\xE7'),tuple('\u0439','\xE9'), + tuple('\u043B','\xEB'),tuple('\u043D','\xED'),tuple('\u043F','\xEF'), + tuple('\u0441','\xF1'),tuple('\u0443','\xF3'),tuple('\u0445','\xF5'), + tuple('\u0447','\xF7'),tuple('\u0449','\xF9'),tuple('\u044B','\xFB'), + tuple('\u044D','\xFD'),tuple('\u044F','\xFF'),tuple('\u0452','\x90'), + tuple('\u0454','\xBA'),tuple('\u0456','\xB3'),tuple('\u0458','\xBC'), + tuple('\u045A','\x9C'),tuple('\u045C','\x9D'),tuple('\u045F','\x9F'), + tuple('\u0491','\xB4'),tuple('\u2014','\x97'),tuple('\u2019','\x92'), + tuple('\u201C','\x93'),tuple('\u201E','\x84'),tuple('\u2021','\x87'), + tuple('\u2026','\x85'),tuple('\u2039','\x8B'),tuple('\u20AC','\x88'), + tuple('\u2122','\x99') + ]; + + mixin GenericEncoder!(); +} + +//============================================================================= // WINDOWS-1252 //============================================================================= /// Defines a Windows1252-encoded character. -enum Windows1252Char : ubyte { init } +enum Windows1252Char : ubyte { _init } /** * Defines an Windows1252-encoded string (as an array of $(D @@ -1204,7 +1357,7 @@ template EncoderInstance(CharType : char) { assert(c >= 0x80); } - body + do { return tailTable[c-0x80]; } @@ -1214,7 +1367,7 @@ template EncoderInstance(CharType : char) { assert(canEncode(c)); } - body + do { if (c < 0x80) return 1; if (c < 0x800) return 2; @@ -1358,7 +1511,7 @@ template EncoderInstance(CharType : wchar) { assert(canEncode(c)); } - body + do { return (c < 0x10000) ? 1 : 2; } @@ -1455,7 +1608,7 @@ template EncoderInstance(CharType : dchar) { assert(canEncode(c)); } - body + do { return 1; } @@ -1505,10 +1658,10 @@ Returns true if c is a valid code point characters). Supersedes: - This function supersedes $(D std.utf.startsValidDchar()). + This function supersedes `std.utf.startsValidDchar()`. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: c = the code point to be tested @@ -1525,7 +1678,7 @@ bool isValidCodePoint(dchar c) @safe pure nothrow @nogc explicitly specify the encoding type. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 */ @property string encodingName(T)() { @@ -1542,6 +1695,7 @@ bool isValidCodePoint(dchar c) @safe pure nothrow @nogc assert(encodingName!(Latin1Char) == "ISO-8859-1"); assert(encodingName!(Latin2Char) == "ISO-8859-2"); assert(encodingName!(Windows1250Char) == "windows-1250"); + assert(encodingName!(Windows1251Char) == "windows-1251"); assert(encodingName!(Windows1252Char) == "windows-1252"); } @@ -1553,7 +1707,7 @@ bool isValidCodePoint(dchar c) @safe pure nothrow @nogc explicitly specify the encoding type. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 */ bool canEncode(E)(dchar c) { @@ -1571,6 +1725,9 @@ bool canEncode(E)(dchar c) assert( canEncode!(Windows1250Char)('\u20AC')); assert(!canEncode!(Windows1250Char)('\u20AD')); assert(!canEncode!(Windows1250Char)('\uFFFD')); + assert( canEncode!(Windows1251Char)('\u0402')); + assert(!canEncode!(Windows1251Char)('\u20AD')); + assert(!canEncode!(Windows1251Char)('\uFFFD')); assert( canEncode!(Windows1252Char)('\u20AC')); assert(!canEncode!(Windows1252Char)('\u20AD')); assert(!canEncode!(Windows1252Char)('\uFFFD')); @@ -1595,7 +1752,7 @@ bool canEncode(E)(dchar c) 0x00 to 0x7F. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: c = the code unit to be tested @@ -1615,6 +1772,8 @@ bool isValidCodeUnit(E)(E c) assert(!isValidCodeUnit(cast(AsciiChar) 0xA0)); assert( isValidCodeUnit(cast(Windows1250Char) 0x80)); assert(!isValidCodeUnit(cast(Windows1250Char) 0x81)); + assert( isValidCodeUnit(cast(Windows1251Char) 0x80)); + assert(!isValidCodeUnit(cast(Windows1251Char) 0x98)); assert( isValidCodeUnit(cast(Windows1252Char) 0x80)); assert(!isValidCodeUnit(cast(Windows1252Char) 0x81)); } @@ -1628,7 +1787,7 @@ bool isValidCodeUnit(E)(E c) whereas the older function would throw an exception. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: s = the string to be tested @@ -1650,7 +1809,7 @@ bool isValid(E)(const(E)[] s) the first code unit, which is validly encoded. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: s = the string to be tested @@ -1679,7 +1838,7 @@ size_t validLength(E)(const(E)[] s) replaced with '?'. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: s = the string to be sanitized @@ -1736,7 +1895,7 @@ immutable(E)[] sanitize(E)(immutable(E)[] s) This is enforced by the function's in-contract. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: s = the string to be sliced @@ -1748,7 +1907,7 @@ in const(E)[] u = s; assert(safeDecode(u) != INVALID_SEQUENCE); } -body +do { auto before = s.length; EncoderInstance!(E).skip(s); @@ -1769,7 +1928,7 @@ body This is enforced by the function's in-contract. Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: s = the string to be sliced @@ -1780,7 +1939,7 @@ in assert(s.length != 0); assert(isValid(s)); } -body +do { const(E)[] t = s; EncoderInstance!(E).decodeReverse(s); @@ -1804,7 +1963,7 @@ body This function supersedes std.utf.toUTFindex(). Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, - WINDOWS-1252 + WINDOWS-1251, WINDOWS-1252 Params: s = the string to be counted @@ -1816,7 +1975,7 @@ in assert(isValid(s)); assert(n >= 0); } -body +do { const(E)[] t = s; for (size_t i=0; i= 0); } - body + do { const(ubyte)[] t = s; for (size_t i=0; i " ~ schemeNames[i]); + assert(valid[i] == decStr,"Error encode/decode UTF8 <=> " ~ schemeNames[i]); + + if (schemeNames[i] == "ISO-8859-1" || schemeNames[i] == "ISO-8859-2") + { + assert(scheme.safeDecode(invalid) != INVALID_SEQUENCE); + } + else + { + assert(scheme.safeDecode(invalid) == INVALID_SEQUENCE); + } + assert(scheme.replacementSequence() == cast(immutable(ubyte)[])"?"); + } + assert(invalid.length == 0); +} + /** EncodingScheme to handle UTF-8 @@ -3242,7 +3539,7 @@ class EncodingSchemeUtf16Native : EncodingScheme { assert((s.length & 1) == 0); } - body + do { auto t = cast(const(wchar)[]) s; dchar c = std.encoding.decode(t); @@ -3255,7 +3552,7 @@ class EncodingSchemeUtf16Native : EncodingScheme { assert((s.length & 1) == 0); } - body + do { auto t = cast(const(wchar)[]) s; dchar c = std.encoding.safeDecode(t); @@ -3338,7 +3635,7 @@ class EncodingSchemeUtf32Native : EncodingScheme { assert((s.length & 3) == 0); } - body + do { auto t = cast(const(dchar)[]) s; dchar c = std.encoding.decode(t); @@ -3351,7 +3648,7 @@ class EncodingSchemeUtf32Native : EncodingScheme { assert((s.length & 3) == 0); } - body + do { auto t = cast(const(dchar)[]) s; dchar c = std.encoding.safeDecode(t); @@ -3386,121 +3683,16 @@ class EncodingSchemeUtf32Native : EncodingScheme //============================================================================= -// Helper functions -version (unittest) -{ - void transcodeReverse(Src,Dst)(immutable(Src)[] s, out immutable(Dst)[] r) - { - static if (is(Src == Dst)) - { - return s; - } - else static if (is(Src == AsciiChar)) - { - transcodeReverse!(char,Dst)(cast(string) s,r); - } - else - { - foreach_reverse (d;codePoints(s)) - { - foreach_reverse (c;codeUnits!(Dst)(d)) - { - r = c ~ r; - } - } - } - } - - string makeReadable(string s) - { - string r = "\""; - foreach (char c;s) - { - if (c >= 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). +The elements of the `enum` can used as indices into `bomTable` to get +matching `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] + utf7 = 3, /** [0x2B, 0x2F, 0x76, 0x38] [0x2B, 0x2F, 0x76, 0x39], [0x2B, 0x2F, 0x76, 0x2B], [0x2B, 0x2F, 0x76, 0x2F], @@ -3516,7 +3708,7 @@ enum BOM utf16le = 15 /// [0xFF, 0xFE] } -/// The type stored inside $(D bomTable). +/// The type stored inside `bomTable`. alias BOMSeq = Tuple!(BOM, "schema", ubyte[], "sequence"); /** Mapping of a byte sequence to $(B Byte Order Mark (BOM)) @@ -3540,20 +3732,20 @@ immutable bomTable = [ 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 +/** Returns a `BOMSeq` for a given `input`. +If no `BOM` is present the `BOMSeq` for `BOM.none` is +returned. The `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. +range make sure that `save` creates a deep copy. Params: - input = The sequence to check for the $(D BOM) + input = The sequence to check for the `BOM` Returns: - the found $(D BOMSeq) corresponding to the passed $(D input). + the found `BOMSeq` corresponding to the passed `input`. */ immutable(BOMSeq) getBOM(Range)(Range input) -if (isForwardRange!Range && is(Unqual!(ElementType!Range) == ubyte)) +if (isForwardRange!Range && is(immutable ElementType!Range == immutable ubyte)) { import std.algorithm.searching : startsWith; foreach (it; bomTable[1 .. $]) diff --git a/libphobos/src/std/exception.d b/libphobos/src/std/exception.d index 56133c9..5fcee2a 100644 --- a/libphobos/src/std/exception.d +++ b/libphobos/src/std/exception.d @@ -5,6 +5,7 @@ handling. It also defines functions intended to aid in unit testing. $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD Assumptions) $(TD @@ -17,7 +18,6 @@ $(TR $(TD Assumptions) $(TD $(TR $(TD Enforce) $(TD $(LREF doesPointTo) $(LREF enforce) - $(LREF enforceEx) $(LREF errnoEnforce) )) $(TR $(TD Handlers) $(TD @@ -32,68 +32,107 @@ $(TR $(TD Other) $(TD $(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 + 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 + $(HTTP jmdavisprog.com, Jonathan M Davis) + Source: $(PHOBOSSRC std/exception.d) - Exception e = collectException(write(g, readln(f))); - if (e) - { - ... an exception occurred... - ... We have the exception to play around with... - } + +/ +module std.exception; - string msg = collectExceptionMsg(write(g, readln(f))); - if (msg) - { - ... an exception occurred... - ... We have the message from the exception but not the exception... - } +/// Synopis +@system unittest +{ + import core.stdc.stdlib : malloc, free; + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map, splitter; + import std.algorithm.searching : endsWith; + import std.conv : ConvException, to; + import std.range : front, retro; + + // use enforce like assert + int a = 3; + enforce(a > 2, "a needs to be higher than 2."); + + // enforce can throw a custom exception + enforce!ConvException(a > 2, "a needs to be higher than 2."); + + // enforce will return it's input + enum size = 42; + auto memory = enforce(malloc(size), "malloc failed")[0 .. size]; + scope(exit) free(memory.ptr); + + // collectException can be used to test for exceptions + Exception e = collectException("abc".to!int); + assert(e.file.endsWith("conv.d")); + + // and just for the exception message + string msg = collectExceptionMsg("abc".to!int); + assert(msg == "Unexpected 'a' when converting from type string to type int"); + + // assertThrown can be used to assert that an exception is thrown + assertThrown!ConvException("abc".to!int); - char[] line; - enforce(readln(f, line)); - return assumeUnique(line); + // ifThrown can be used to provide a default value if an exception is thrown + assert("x".to!int().ifThrown(0) == 0); + + // handle is a more advanced version of ifThrown for ranges + auto r = "12,1337z32,54".splitter(',').map!(a => to!int(a)); + auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0); + assert(h.equal([12, 0, 54])); + assertThrown!ConvException(h.retro.equal([54, 0, 12])); + + // basicExceptionCtors avoids the boilerplate when creating custom exceptions + static class MeaCulpa : Exception + { + mixin basicExceptionCtors; } - -------------------- + e = collectException((){throw new MeaCulpa("diagnostic message");}()); + assert(e.msg == "diagnostic message"); + assert(e.file == __FILE__); + assert(e.line == __LINE__ - 3); - 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) + // assumeWontThrow can be used to cast throwing code into `nothrow` + void exceptionFreeCode() nothrow + { + // auto-decoding only throws if an invalid UTF char is given + assumeWontThrow("abc".front); + } - +/ -module std.exception; + // assumeUnique can be used to cast mutable instance to an `immutable` one + // use with care + char[] str = " mutable".dup; + str[0 .. 2] = "im"; + immutable res = assumeUnique(str); + assert(res == "immutable"); +} 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, + of `Throwable`. If a `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. + `AssertError` is thrown. However, any other `Throwable`s will escape. Params: - T = The $(D Throwable) to test for. + T = The `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__). + Defaults to `__FILE__`. line = The line where the error occurred. - Defaults to $(D __LINE__). + Defaults to `__LINE__`. Throws: - $(D AssertError) if the given $(D Throwable) is thrown. + `AssertError` if the given `Throwable` is thrown. Returns: the result of `expression`. @@ -226,22 +265,22 @@ auto assertNotThrown(T : Throwable = Exception, E) } /++ - 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. + Asserts that the given expression throws the given type of `Throwable`. + The `Throwable` is caught and does not escape assertThrown. However, + any other `Throwable`s $(I will) escape, and if no `Throwable` + of the given type is thrown, then an `AssertError` is thrown. Params: - T = The $(D Throwable) to test for. + T = The `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__). + Defaults to `__FILE__`. line = The line where the error occurred. - Defaults to $(D __LINE__). + Defaults to `__LINE__`. Throws: - $(D AssertError) if the given $(D Throwable) is not thrown. + `AssertError` if the given `Throwable` is not thrown. +/ void assertThrown(T : Throwable = Exception, E) (lazy E expression, @@ -355,55 +394,52 @@ void assertThrown(T : Throwable = Exception, E) /++ Enforces that the given value is true. + If the given value is false, an exception is thrown. + The + $(UL + $(LI `msg` - error message as a `string`) + $(LI `dg` - custom delegate that return a string and is only called if an exception occurred) + $(LI `ex` - custom exception to be thrown. It is `lazy` and is only created if an exception occurred) + ) Params: value = The value to test. - E = Exception type to throw if the value evalues to false. + E = Exception type to throw if the value evaluates to false. msg = The error message to put in the exception if it is thrown. + dg = The delegate to be called if the value evaluates to false. + ex = The exception to throw 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, - $(D new Exception(msg)) is thrown. + Returns: `value`, if `cast(bool) value` is true. Otherwise, + depending on the chosen overload, `new Exception(msg)`, `dg()` or `ex` is thrown. Note: - $(D enforce) is used to throw exceptions and is therefore intended to + `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."); - -------------------- + of your program. That is what `assert` is for. Also, do not use + `enforce` inside of contracts (i.e. inside of `in` and `out` + blocks and `invariant`s), because contracts are compiled out when + compiling with $(I -release). + + If a delegate is passed, the safety and purity of this function are inferred + from `Dg`'s safety and purity. +/ -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) {} }))) +template enforce(E : Throwable = Exception) +if (is(typeof(new E("", string.init, size_t.init)) : Throwable) || + is(typeof(new E(string.init, size_t.init)) : Throwable)) { - if (!value) bailOut!E(file, line, msg); - return value; + /// + T enforce(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. - +/ +/// ditto T enforce(T, Dg, string file = __FILE__, size_t line = __LINE__) (T value, scope Dg dg) if (isSomeFunction!Dg && is(typeof( dg() )) && @@ -413,23 +449,40 @@ if (isSomeFunction!Dg && is(typeof( dg() )) && return value; } -private void bailOut(E : Throwable = Exception)(string file, size_t line, in char[] msg) +/// ditto +T enforce(T)(T value, lazy Throwable ex) { - 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)); - } + if (!value) throw ex(); + return value; +} + +/// +@system unittest +{ + import core.stdc.stdlib : malloc, free; + import std.conv : ConvException, to; + + // use enforce like assert + int a = 3; + enforce(a > 2, "a needs to be higher than 2."); + + // enforce can throw a custom exception + enforce!ConvException(a > 2, "a needs to be higher than 2."); + + // enforce will return it's input + enum size = 42; + auto memory = enforce(malloc(size), "malloc failed")[0 .. size]; + scope(exit) free(memory.ptr); +} + +/// +@safe unittest +{ + assertNotThrown(enforce(true, new Exception("this should not be thrown"))); + assertThrown(enforce(false, new Exception("this should be thrown"))); } +/// @safe unittest { assert(enforce(123) == 123); @@ -447,9 +500,35 @@ private void bailOut(E : Throwable = Exception)(string file, size_t line, in cha } } +/// Alias your own enforce function +@safe unittest +{ + import std.conv : ConvException; + alias convEnforce = enforce!ConvException; + assertNotThrown(convEnforce(true)); + assertThrown!ConvException(convEnforce(false, "blah")); +} + +private noreturn bailOut(E : Throwable = Exception)(string file, size_t line, scope const(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)); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=10510 @safe unittest { - // Issue 10510 extern(C) void cFoo() { } enforce(false, &cFoo); } @@ -457,14 +536,12 @@ private void bailOut(E : Throwable = Exception)(string file, size_t line, in cha // purity and safety inference test @system unittest { - import std.meta : AliasSeq; - - foreach (EncloseSafe; AliasSeq!(false, true)) - foreach (EnclosePure; AliasSeq!(false, true)) + static foreach (EncloseSafe; [false, true]) + static foreach (EnclosePure; [false, true]) { - foreach (BodySafe; AliasSeq!(false, true)) - foreach (BodyPure; AliasSeq!(false, true)) - { + static foreach (BodySafe; [false, true]) + static foreach (BodyPure; [false, true]) + {{ enum code = "delegate void() " ~ (EncloseSafe ? "@safe " : "") ~ @@ -485,11 +562,11 @@ private void bailOut(E : Throwable = Exception)(string file, size_t line, in cha "code = ", code); static assert(__traits(compiles, mixin(code)()) == expect); - } + }} } } -// Test for bugzilla 8637 +// Test for https://issues.dlang.org/show_bug.cgi?id=8637 @system unittest { struct S @@ -523,10 +600,9 @@ private void bailOut(E : Throwable = Exception)(string file, size_t line, in cha enforce!E2(s); } +// https://issues.dlang.org/show_bug.cgi?id=14685 @safe unittest { - // Issue 14685 - class E : Exception { this() { super("Not found"); } @@ -535,35 +611,6 @@ private void bailOut(E : Throwable = Exception)(string file, size_t line, in cha } /++ - 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. @@ -571,125 +618,37 @@ T enforce(T)(T value, lazy Throwable ex) 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, + Returns: `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 + operation set `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; - } -} +alias errnoEnforce = enforce!ErrnoException; +/// @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); - } + import core.stdc.stdio : fclose, fgets, fopen; + import std.file : thisExePath; + import std.string : toStringz; - { - 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)))); + auto f = fopen(thisExePath.toStringz, "r").errnoEnforce; + scope(exit) fclose(f); + char[100] buf; + auto line = fgets(buf.ptr, buf.length, f); + enforce(line !is null); // expect a non-empty line } -@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 + If no exception is thrown, then null is returned and `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 + Note that while `collectException` $(I can) be used to collect any + `Throwable` and not just `Exception`s, it is generally ill-advised to + catch anything that is neither an `Exception` nor a type derived from + `Exception`. So, do not use `collectException` to collect + non-`Exception`s unless you're sure that that's what you really want to do. Params: @@ -723,14 +682,14 @@ T collectException(T = Exception, E)(lazy E expression, ref E result) /++ 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 + If no exception is thrown, then null is returned. `E` can be + `void`. + + Note that while `collectException` $(I can) be used to collect any + `Throwable` and not just `Exception`s, it is generally ill-advised to + catch anything that is neither an `Exception` nor a type derived from + `Exception`. So, do not use `collectException` to collect + non-`Exception`s unless you're sure that that's what you really want to do. Params: @@ -750,25 +709,26 @@ T collectException(T : Throwable = Exception, E)(lazy E expression) return null; } +/// @safe unittest { int foo() { throw new Exception("blah"); } - assert(collectException(foo())); + assert(collectException(foo()).msg == "blah"); } /++ 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). + returned. `E` can be `void`. If an exception is thrown but it has an empty message, then - $(D emptyExceptionMsg) is returned. + `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 + Note that while `collectExceptionMsg` $(I can) be used to collect any + `Throwable` and not just `Exception`s, it is generally ill-advised to + catch anything that is neither an `Exception` nor a type derived from + `Exception`. So, do not use `collectExceptionMsg` to collect + non-`Exception`s unless you're sure that that's what you really want to do. Params: @@ -808,18 +768,18 @@ enum emptyExceptionMsg = ""; /** * Casts a mutable array to an immutable array in an idiomatic - * manner. Technically, $(D assumeUnique) just inserts a cast, + * manner. Technically, `assumeUnique` just inserts a cast, * but its name documents assumptions on the part of the - * caller. $(D assumeUnique(arr)) should only be called when + * caller. `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 + * arr). To strengthen this assumption, `assumeUnique(arr)` + * also clears `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 + * is no more mutable access to any of `arr`'s elements * (transitively), and that all future accesses will be done through - * the immutable array returned by $(D assumeUnique). + * the immutable array returned by `assumeUnique`. * - * Typically, $(D assumeUnique) is used to return arrays from + * Typically, `assumeUnique` is used to return arrays from * functions that have allocated and built them. * * Params: @@ -829,6 +789,7 @@ enum emptyExceptionMsg = ""; * * Example: * + * $(RUNNABLE_EXAMPLE * ---- * string letters() * { @@ -840,14 +801,16 @@ enum emptyExceptionMsg = ""; * return assumeUnique(result); * } * ---- + * ) * - * The use in the example above is correct because $(D result) - * was private to $(D letters) and is inaccessible in writing + * The use in the example above is correct because `result` + * was private to `letters` and is inaccessible in writing * after the function returns. The following example shows an - * incorrect use of $(D assumeUnique). + * incorrect use of `assumeUnique`. * * Bad: * + * $(RUNNABLE_EXAMPLE * ---- * private char[] buffer; * string letters(char first, char last) @@ -862,11 +825,13 @@ enum emptyExceptionMsg = ""; * 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 + * immutable array from the writable array `buffer`, replace * the last line with: + * * ---- * return to!(string)(sneaky); // not that sneaky anymore * ---- @@ -878,6 +843,8 @@ enum emptyExceptionMsg = ""; * 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: + * + * $(RUNNABLE_EXAMPLE * ---- * string letters() pure * { @@ -889,16 +856,17 @@ enum emptyExceptionMsg = ""; * 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) + * $(HTTP www.cs.cmu.edu/~aldrich/papers/aldrich-dissertation.pdf, ArchJava) * language. * - * The downside of using $(D assumeUnique)'s + * The downside of using `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 + * on the upside, the idiomatic use of `assumeUnique` is * simple and rare enough to be tolerable. * */ @@ -921,34 +889,38 @@ immutable(T[U]) assumeUnique(T, U)(ref T[U] array) pure nothrow 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); + auto arr1 = arr.assumeUnique; + static assert(is(typeof(arr1) == immutable(int)[])); + assert(arr == null); + assert(arr1 == [0]); } -// @@@BUG@@@ -version (none) @system unittest +/// +@system unittest { int[string] arr = ["a":1]; - auto arr1 = assumeUnique(arr); - assert(is(typeof(arr1) == immutable(int[string])) && arr == null); + auto arr1 = arr.assumeUnique; + static assert(is(typeof(arr1) == immutable(int[string]))); + assert(arr == null); + assert(arr1.keys == ["a"]); } /** - * Wraps a possibly-throwing expression in a $(D nothrow) wrapper so that it - * can be called by a $(D nothrow) function. + * Wraps a possibly-throwing expression in a `nothrow` wrapper so that it + * can be called by a `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 + * trigger an exception during the evaluation of `expr`. If it turns out * that the expression $(I does) throw at runtime, the wrapper will throw an - * $(D AssertError). + * `AssertError`. * - * (Note that $(D Throwable) objects such as $(D AssertError) that do not - * subclass $(D Exception) may be thrown even from $(D nothrow) functions, + * (Note that `Throwable` objects such as `AssertError` that do not + * subclass `Exception` may be thrown even from `nothrow` functions, * since they are considered to be serious runtime problems that cannot be * recovered from.) * @@ -984,7 +956,7 @@ T assumeWontThrow(T)(lazy T expr, /// @safe unittest { - import std.math : sqrt; + import std.math.algebraic : sqrt; // This function may throw. int squareRoot(int x) @@ -1030,37 +1002,41 @@ 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 +Bugs: + The function is explicitly annotated `@nogc` because inference could fail, + see $(LINK2 https://issues.dlang.org/show_bug.cgi?id=17084, issue 17084). + +Returns: `true` if `source`'s representation embeds a pointer +that points to `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 `source` is or contains a dynamic array, then, then these functions will check +if there is overlap between the dynamic array and `target`'s representation. -If $(D source) is a class, then it will be handled as a pointer. +If `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 `target` is a pointer, a dynamic array or a class, then these functions will only +check if `source` points to `target`, $(I not) what `target` references. -If $(D source) is or contains a union, then there may be either false positives or +If `source` is or contains a union or `void[n]`, 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 +`doesPointTo` will return `true` if it is absolutely certain +`source` points to `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 +`mayPointTo` will return `false` if it is absolutely certain +`source` does not point to `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 +Note: Evaluating $(D doesPointTo(x, x)) checks whether `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 +bool doesPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) @nogc @trusted pure nothrow if (__traits(isRef, source) || isDynamicArray!S || isPointer!S || is(S == class)) { @@ -1080,8 +1056,11 @@ if (__traits(isRef, source) || isDynamicArray!S || } else static if (isStaticArray!S) { - foreach (size_t i; 0 .. S.length) - if (doesPointTo(source[i], target)) return true; + static if (!is(S == void[n], size_t n)) + { + foreach (ref s; source) + if (doesPointTo(s, target)) return true; + } return false; } else static if (isDynamicArray!S) @@ -1122,8 +1101,38 @@ if (__traits(isRef, source) || isDynamicArray!S || } else static if (isStaticArray!S) { - foreach (size_t i; 0 .. S.length) - if (mayPointTo(source[i], target)) return true; + static if (is(S == void[n], size_t n)) + { + static if (n >= (void[]).sizeof) + { + // could contain a slice, which could point at anything. + // But a void[N] that is all 0 cannot point anywhere + import std.algorithm.searching : any; + if (__ctfe || any(cast(ubyte[]) source[])) + return true; + } + else static if (n >= (void*).sizeof) + { + // Reinterpreting cast is impossible during ctfe + if (__ctfe) + return true; + + // Only check for properly aligned pointers + enum al = (void*).alignof - 1; + const base = cast(size_t) &source; + const alBase = (base + al) & ~al; + + if ((n - (alBase - base)) >= (void*).sizeof && + mayPointTo(*(cast(void**) alBase), target)) + return true; + } + } + else + { + foreach (size_t i; 0 .. S.length) + if (mayPointTo(source[i], target)) return true; + } + return false; } else static if (isDynamicArray!S) @@ -1179,9 +1188,12 @@ bool mayPointTo(S, T)(auto ref const shared S source, ref const shared T target) @system unittest { int i; + // trick the compiler when initializing slice + // https://issues.dlang.org/show_bug.cgi?id=18637 + int* p = &i; int[] slice = [0, 1, 2, 3, 4]; int[5] arr = [0, 1, 2, 3, 4]; - int*[] slicep = [&i]; + int*[] slicep = [p]; int*[1] arrp = [&i]; // A slice points to all of its members: @@ -1241,6 +1253,37 @@ bool mayPointTo(S, T)(auto ref const shared S source, ref const shared T target) assert(b.doesPointTo(*aLoc)); // b points to where a is pointing } + +version (StdUnittest) +{ + // https://issues.dlang.org/show_bug.cgi?id=17084 + // the bug doesn't happen if these declarations are in the unittest block + // (static or not). + private struct Page17084 + { + URL17084 url; + int opCmp(P)(P) { return 0; } + int opCmp(P)(shared(P)) shared { return 0; } + } + + private struct URL17084 + { + int[] queryParams; + string toString()() const { return ""; } + alias toString this; + } +} + +// https://issues.dlang.org/show_bug.cgi?id=17084 +@system unittest +{ + import std.algorithm.sorting : sort; + Page17084[] s; + sort(s); + shared(Page17084)[] p; + sort(p); +} + @system unittest { struct S1 { int a; S1 * b; } @@ -1337,6 +1380,57 @@ bool mayPointTo(S, T)(auto ref const shared S source, ref const shared T target) 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. + + // https://issues.dlang.org/show_bug.cgi?id=20426 + align((void*).alignof) void[32] voidArr = void; + (cast(void*[]) voidArr[])[] = null; // Ensure no false pointers + + // zeroed void ranges can't point at anything + assert(!mayPointTo(voidArr, a)); + assert(!mayPointTo(voidArr, b)); + + *cast(void**) &voidArr[16] = &a; // Pointers should be found + + alias SA = void[size_t.sizeof + 3]; + SA *smallArr1 = cast(SA*)&voidArr; + SA *smallArr2 = cast(SA*)&(voidArr[16]); + + // But it should only consider properly aligned pointers + // Write single bytes to avoid issues due to misaligned writes + void*[1] tmp = [&b]; + (cast(ubyte[]) voidArr[3 .. 3 + (void*).sizeof])[] = cast(ubyte[]) tmp[]; + + + assert( mayPointTo(*smallArr2, a)); + assert(!mayPointTo(*smallArr1, b)); + + assert(!doesPointTo(voidArr, a)); // Value might be a false pointer + assert(!doesPointTo(voidArr, b)); + + SA *smallArr3 = cast(SA *) &voidArr[13]; // Works for weird sizes/alignments + assert( mayPointTo(*smallArr3, a)); + assert(!mayPointTo(*smallArr3, b)); + + assert(!doesPointTo(*smallArr3, a)); + assert(!doesPointTo(*smallArr3, b)); + + auto v3 = cast(void[3]*) &voidArr[16]; // Arrays smaller than pointers are ignored + assert(!mayPointTo(*v3, a)); + assert(!mayPointTo(*v3, b)); + + assert(!doesPointTo(*v3, a)); + assert(!doesPointTo(*v3, b)); + + assert(mayPointTo(voidArr, a)); // slice-contiaining void[N] might point at anything + assert(mayPointTo(voidArr, b)); + + static assert(() { + void[16] arr1 = void; + void[size_t.sizeof] arr2 = void; + int var; + return mayPointTo(arr1, var) && !doesPointTo(arr1, var) && + mayPointTo(arr2, var) && !doesPointTo(arr2, var); + }()); } @@ -1425,7 +1519,7 @@ bool mayPointTo(S, T)(auto ref const shared S source, ref const shared T target) } /+ -Returns true if the field at index $(D i) in ($D T) shares its address with another field. +Returns true if the field at index `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. @@ -1510,53 +1604,57 @@ package string errnoString(int errno) nothrow @trusted } /********************* - * Thrown if errors that set $(D errno) occur. + * Thrown if errors that set `errno` occur. */ class ErrnoException : Exception { - final @property uint errno() { return _errno; } /// Operating system error code. + /// Operating system error code. + final @property uint errno() nothrow pure @nogc @safe { return _errno; } 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 + this(string msg, string file = null, size_t line = 0) @safe { 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 + this(string msg, int errno, string file = null, size_t line = 0) @safe { _errno = errno; super(msg ~ " (" ~ errnoString(errno) ~ ")", file, line); } +} - @system unittest - { - import core.stdc.errno : errno, EAGAIN; +/// +@safe unittest +{ + import core.stdc.errno : EAGAIN; + auto ex = new ErrnoException("oh no", EAGAIN); + assert(ex.errno == EAGAIN); +} - auto old = errno; - scope(exit) errno = old; +/// errno is used by default if no explicit error code is provided +@safe unittest +{ + import core.stdc.errno : errno, EAGAIN; - errno = EAGAIN; - auto ex = new ErrnoException("oh no"); - assert(ex.errno == EAGAIN); - } + auto old = errno; + scope(exit) errno = old; - @system unittest - { - import core.stdc.errno : EAGAIN; - auto ex = new ErrnoException("oh no", EAGAIN); - assert(ex.errno == EAGAIN); - } + // fake that errno got set by the callee + errno = EAGAIN; + auto ex = new ErrnoException("oh no"); + 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 + returns its result. If the expression throws a `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) + E = The type of `Throwable`s to catch. Defaults to `Exception` T1 = The type of the expression. T2 = The return type of the error handler. expression = The expression to run and return its result. @@ -1565,54 +1663,7 @@ class ErrnoException : Exception 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) { @@ -1675,6 +1726,59 @@ CommonType!(T1, T2) ifThrown(T1, T2)(lazy scope T1 expression, scope T2 delegate } } +/// Revert to a default value upon an error: +@safe unittest +{ + import std.conv : to; + assert("x".to!int.ifThrown(0) == 0); +} + +/** +Chain multiple calls to ifThrown, each capturing errors from the +entire preceding expression. +*/ +@safe unittest +{ + import std.conv : ConvException, to; + string s = "true"; + assert(s.to!int.ifThrown(cast(int) s.to!double) + .ifThrown(cast(int) s.to!bool) == 1); + + s = "2.0"; + assert(s.to!int.ifThrown(cast(int) s.to!double) + .ifThrown(cast(int) s.to!bool) == 2); + + // Respond differently to different types of errors + alias orFallback = (lazy a) => a.ifThrown!ConvException("not a number") + .ifThrown!Exception("number too small"); + + assert(orFallback(enforce("x".to!int < 1).to!string) == "not a number"); + assert(orFallback(enforce("2".to!int < 1).to!string) == "number too small"); +} + +/** +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. +*/ +@safe unittest +{ + // 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. +@system unittest +{ + import std.format : format; + assert("%s".format.ifThrown!Exception(e => e.classinfo.name) == "std.format.FormatException"); +} + //Verify Examples @system unittest { @@ -1745,22 +1849,22 @@ CommonType!(T1, T2) ifThrown(T1, T2)(lazy scope T1 expression, scope T2 delegate static assert(!__traits(compiles, (new Object()).ifThrown(e=>1))); } -version (unittest) package -@property void assertCTFEable(alias dg)() +version (StdUnittest) package +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 +/** This `enum` is used to select the primitives of the range to handle by the + $(LREF handle) range wrapper. The values of the `enum` can be `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). + `RangePrimitive.access` is a shortcut for the access primitives; `front`, + `back` and `opIndex`. - $(D RangePrimitive.pop) is a shortcut for the mutating primitives; - $(D popFront) and $(D popBack). + `RangePrimitive.pop` is a shortcut for the mutating primitives; + `popFront` and `popBack`. */ enum RangePrimitive { @@ -1778,31 +1882,65 @@ enum RangePrimitive pop = popFront | popBack, /// Ditto } +/// +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` +} + /** 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). +Multiple range primitives can be handled at once by using the `OR` operator +or the pseudo-primitives `RangePrimitive.access` and `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. + E = The type of `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 + `Throwable` of type `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 + return value whenever `E` is thrown. For `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). +Returns: A wrapper `struct` that preserves the range interface of `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 +bound (see $(REF hasSlicing, std,range,primitives)); `handle` deals with +this by `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) diff --git a/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d index cfeae0c..1e3df91 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d @@ -1,31 +1,34 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/affix_allocator.d) +*/ 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 +Allocator that adds some extra data before (of type `Prefix`) and/or after +(of type `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). +If `Prefix` is not `void`, `Allocator` must guarantee an alignment at +least as large as `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). +The following methods are defined if `Allocator` defines them, and forward to it: `deallocateAll`, `empty`, `owns`. */ struct AffixAllocator(Allocator, Prefix, Suffix = void) { import std.algorithm.comparison : min; - import std.conv : emplace; - import std.experimental.allocator : IAllocator, theAllocator; + import core.lifetime : emplace; + import std.experimental.allocator : RCIAllocator, theAllocator; import std.experimental.allocator.common : stateSize, forwardToMember, roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf, hasStaticallyKnownAlignment; - import std.math : isPowerOf2; + import std.math.traits : isPowerOf2; import std.traits : hasMember; import std.typecons : Ternary; @@ -40,7 +43,7 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) "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. + If `Prefix` is `void`, the alignment is that of the parent. Otherwise, the alignment is the same as the `Prefix`'s alignment. */ static if (hasStaticallyKnownAlignment!Allocator) { @@ -58,20 +61,45 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) } /** - 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 + If the parent allocator `Allocator` is stateful, an instance of it is + stored as a member. Otherwise, `AffixAllocator` uses + `Allocator.instance`. In either case, the name `_parent` is uniformly used for accessing the parent allocator. */ static if (stateSize!Allocator) { Allocator _parent; - static if (is(Allocator == IAllocator)) + static if (is(Allocator == RCIAllocator)) { + @nogc nothrow pure @safe Allocator parent() { - if (_parent is null) _parent = theAllocator; - assert(alignment <= _parent.alignment); + static @nogc nothrow + RCIAllocator wrapAllocatorObject() + { + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator : allocatorObject; + + return allocatorObject(GCAllocator.instance); + } + + if (_parent.isNull) + { + // If the `_parent` allocator is `null` we will assign + // an object that references the GC as the `parent`. + auto fn = (() @trusted => + cast(RCIAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))(); + _parent = fn(); + } + + // `RCIAllocator.alignment` currently doesn't have any attributes + // so we must cast; throughout the allocators module, `alignment` + // is defined as an `enum` for the existing allocators. + // `alignment` should always be `@nogc nothrow pure @safe`; once + // this is enforced by the interface we can remove the cast + auto pureAlign = (() @trusted => + cast(uint delegate() @nogc nothrow pure @safe)(&_parent.alignment))(); + assert(alignment <= pureAlign()); return _parent; } } @@ -119,10 +147,9 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) [0 .. actualAllocationSize(b.length)]; } - void[] allocate(size_t bytes) - { - if (!bytes) return null; - auto result = parent.allocate(actualAllocationSize(bytes)); + // Common code shared between allocate and allocateZeroed. + private enum _processAndReturnAllocateResult = + q{ if (result is null) return null; static if (stateSize!Prefix) { @@ -136,6 +163,21 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) emplace!Suffix(cast(Suffix*)(suffixP)); } return result[stateSize!Prefix .. stateSize!Prefix + bytes]; + }; + + void[] allocate(size_t bytes) + { + if (!bytes) return null; + auto result = parent.allocate(actualAllocationSize(bytes)); + mixin(_processAndReturnAllocateResult); + } + + static if (hasMember!(Allocator, "allocateZeroed")) + package(std) void[] allocateZeroed()(size_t bytes) + { + if (!bytes) return null; + auto result = parent.allocateZeroed(actualAllocationSize(bytes)); + mixin(_processAndReturnAllocateResult); } static if (hasMember!(Allocator, "allocateAll")) @@ -171,7 +213,7 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) Ternary owns(void[] b) { if (b is null) return Ternary.no; - return parent.owns(actualAllocation(b)); + return parent.owns((() @trusted => actualAllocation(b))()); } static if (hasMember!(Allocator, "resolveInternalPointer")) @@ -182,20 +224,22 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) 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]; + auto p2 = (() @trusted => (&p1[0] + p1.length - stateSize!Suffix) + .alignDownTo(Suffix.alignof))(); + result = p1[0 .. p2 - &p1[0]]; return Ternary.yes; } - static if (!stateSize!Suffix && hasMember!(Allocator, "expand")) + static if (!stateSize!Suffix && hasMember!(Allocator, "expand") + && hasMember!(Allocator, "owns")) bool expand(ref void[] b, size_t delta) { - if (!b.ptr) return delta == 0; - auto t = actualAllocation(b); + if (!b || delta == 0) return delta == 0; + if (owns(b) == Ternary.no) return false; + auto t = (() @trusted => actualAllocation(b))(); const result = parent.expand(t, delta); if (!result) return false; - b = b.ptr[0 .. b.length + delta]; + b = (() @trusted => b.ptr[0 .. b.length + delta])(); return true; } @@ -221,8 +265,8 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) return parent.deallocate(actualAllocation(b)); } - /* The following methods are defined if $(D ParentAllocator) defines - them, and forward to it: $(D deallocateAll), $(D empty).*/ + /* The following methods are defined if `ParentAllocator` defines + them, and forward to it: `deallocateAll`, `empty`.*/ mixin(forwardToMember("parent", "deallocateAll", "empty")); @@ -267,7 +311,7 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) { /** Standard allocator methods. Each is defined if and only if the parent - allocator defines the homonym method (except for $(D goodAllocSize), + allocator defines the homonym method (except for `goodAllocSize`, which may use the global default). Also, the methods will be $(D shared) if the parent allocator defines them as such. */ @@ -344,14 +388,20 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) } else static if (is(typeof(Allocator.instance) == shared)) { + static assert(stateSize!Allocator == 0); static shared AffixAllocator instance; shared { mixin Impl!(); } } + else static if (is(Allocator == shared)) + { + static assert(stateSize!Allocator != 0); + shared { mixin Impl!(); } + } else { mixin Impl!(); static if (stateSize!Allocator == 0) - static __gshared AffixAllocator instance; + __gshared AffixAllocator instance; } } @@ -371,10 +421,10 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) @system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; - import std.experimental.allocator : theAllocator, IAllocator; + import std.experimental.allocator : theAllocator, RCIAllocator; // One word before and after each allocation. - auto A = AffixAllocator!(IAllocator, size_t, size_t)(theAllocator); + auto A = AffixAllocator!(RCIAllocator, size_t, size_t)(theAllocator); auto a = A.allocate(11); A.prefix(a) = 0xCAFE_BABE; A.suffix(a) = 0xDEAD_BEEF; @@ -382,7 +432,7 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) && A.suffix(a) == 0xDEAD_BEEF); // One word before and after each allocation. - auto B = AffixAllocator!(IAllocator, size_t, size_t)(); + auto B = AffixAllocator!(RCIAllocator, size_t, size_t)(); auto b = B.allocate(11); B.prefix(b) = 0xCAFE_BABE; B.suffix(b) = 0xDEAD_BEEF; @@ -390,6 +440,7 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) && B.suffix(b) == 0xDEAD_BEEF); } +version (StdUnittest) @system unittest { import std.experimental.allocator.building_blocks.bitmapped_block @@ -402,6 +453,20 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) }); } +// Test empty +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.typecons : Ternary; + + auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong) + (BitmappedBlock!128(new ubyte[128 * 4096])); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); + auto b = a.allocate(42); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no); +} + @system unittest { import std.experimental.allocator.mallocator : Mallocator; @@ -435,7 +500,63 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) 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((() nothrow @safe @nogc => MyAllocator.instance.resolveInternalPointer(null, p))() == Ternary.no); + assert((() nothrow @safe => MyAllocator.instance.resolveInternalPointer(&d[0], p))() == Ternary.yes); assert(p.ptr is d.ptr && p.length >= d.length); } + +@system unittest +{ + import std.experimental.allocator.gc_allocator; + alias a = AffixAllocator!(GCAllocator, uint).instance; + + // Check that goodAllocSize inherits from parent, i.e. GCAllocator + assert(__traits(compiles, (() nothrow @safe @nogc => a.goodAllocSize(1))())); + + // Ensure deallocate inherits from parent + auto b = a.allocate(42); + assert(b.length == 42); + () nothrow @nogc { a.deallocate(b); }(); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + + auto a = AffixAllocator!(Region!(), uint)(Region!()(new ubyte[1024 * 64])); + auto b = a.allocate(42); + assert(b.length == 42); + // Test that expand infers from parent + assert((() pure nothrow @safe @nogc => a.expand(b, 58))()); + assert(b.length == 100); + // Test that deallocateAll infers from parent + assert((() nothrow @nogc => a.deallocateAll())()); +} + +// Test that reallocate infers from parent +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + alias a = AffixAllocator!(Mallocator, uint).instance; + auto b = a.allocate(42); + assert(b.length == 42); + assert((() nothrow @nogc => a.reallocate(b, 100))()); + assert(b.length == 100); + assert((() nothrow @nogc => a.deallocate(b))()); +} + +@system unittest +{ + import std.experimental.allocator : processAllocator, RCISharedAllocator; + import std.traits; + + alias SharedAllocT = shared AffixAllocator!(RCISharedAllocator, int); + static assert(is(RCISharedAllocator == shared)); + static assert(!is(SharedAllocT.instance)); + + SharedAllocT a = SharedAllocT(processAllocator); + auto buf = a.allocate(10); + static assert(is(typeof(a.allocate) == shared)); + assert(buf.length == 10); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/aligned_block_list.d b/libphobos/src/std/experimental/allocator/building_blocks/aligned_block_list.d new file mode 100644 index 0000000..14c6de4 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/aligned_block_list.d @@ -0,0 +1,699 @@ +// Written in the D programming language. +/** +`AlignedBlockList` represents a wrapper around a chain of allocators, allowing for fast deallocations +and preserving a low degree of fragmentation by means of aligned allocations. + +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/aligned_block_list.d) +*/ +module std.experimental.allocator.building_blocks.aligned_block_list; + +import std.experimental.allocator.common; +import std.experimental.allocator.building_blocks.null_allocator; + +// Common function implementation for thread local and shared AlignedBlockList +private mixin template AlignedBlockListImpl(bool isShared) +{ + import std.traits : hasMember; + import std.typecons : Ternary; + + static if (isShared) + import core.internal.spinlock : SpinLock; + +private: + // Doubly linked list of 'AlignedBlockNode' + // Each node contains an `Allocator` followed by its payload + static struct AlignedBlockNode + { + AlignedBlockNode* next, prev; + Allocator bAlloc; + + static if (isShared) + { + shared(size_t) bytesUsed; + // Since the lock is not taken when allocating, this acts like a refcount + // keeping the node alive + uint keepAlive; + } + else + { + size_t bytesUsed; + } + } + + // Root of the internal doubly linked list + AlignedBlockNode* root; + + // Number of active nodes + uint numNodes; + + // If the numNodes exceeds this limit, we will start deallocating nodes + enum uint maxNodes = 64; + + // This lock is always taken when changing the list + // To improve performance, the lock is not taken when the allocation logic is called + static if (isShared) + SpinLock lock = SpinLock(SpinLock.Contention.brief); + + // Moves a node to the front of the list, allowing for quick allocations + void moveToFront(AlignedBlockNode* tmp) + { + auto localRoot = cast(AlignedBlockNode*) root; + if (tmp == localRoot) + return; + + if (tmp.prev) tmp.prev.next = tmp.next; + if (tmp.next) tmp.next.prev = tmp.prev; + if (localRoot) localRoot.prev = tmp; + tmp.next = localRoot; + tmp.prev = null; + + root = cast(typeof(root)) tmp; + } + + // Removes a node from the list, including its payload + // The payload is deallocated by calling 'parent.deallocate' + void removeNode(AlignedBlockNode* tmp) + { + auto next = tmp.next; + if (tmp.prev) tmp.prev.next = tmp.next; + if (tmp.next) tmp.next.prev = tmp.prev; + parent.deallocate((cast(void*) tmp)[0 .. theAlignment]); + + if (tmp == cast(AlignedBlockNode*) root) + root = cast(typeof(root)) next; + + static if (isShared) + { + import core.atomic : atomicOp; + atomicOp!"-="(numNodes, 1); + } + else + { + numNodes--; + } + } + + // If the nodes do not have available space, a new node is created + // by drawing memory from the parent allocator with aligned allocations. + // The new node is inserted at the front of the list + bool insertNewNode() + { + void[] buf = parent.alignedAllocate(theAlignment, theAlignment); + if (buf is null) + return false; + + auto localRoot = cast(AlignedBlockNode*) root; + auto newNode = cast(AlignedBlockNode*) buf; + + // The first part of the allocation represent the node contents + // followed by the actual payload + ubyte[] payload = cast(ubyte[]) buf[AlignedBlockNode.sizeof .. $]; + newNode.bAlloc = Allocator(payload); + + newNode.next = localRoot; + newNode.prev = null; + if (localRoot) + localRoot.prev = newNode; + root = cast(typeof(root)) newNode; + + static if (isShared) + { + import core.atomic : atomicOp; + atomicOp!"+="(numNodes, 1); + } + else + { + numNodes++; + } + + return true; + } + +public: + static if (stateSize!ParentAllocator) ParentAllocator parent; + else alias parent = ParentAllocator.instance; + + enum ulong alignment = Allocator.alignment; + + // Since all memory is drawn from ParentAllocator, we can + // forward this to the parent + static if (hasMember!(ParentAllocator, "owns")) + Ternary owns(void[] b) + { + return parent.owns(b); + } + + // Use `theAlignment` to find the node which allocated this block + bool deallocate(void[] b) + { + if (b is null) + return true; + + // Round buffer to nearest `theAlignment` multiple to quickly find + // the `parent` `AlignedBlockNode` + enum ulong mask = ~(theAlignment - 1); + ulong ptr = ((cast(ulong) b.ptr) & mask); + AlignedBlockNode *node = cast(AlignedBlockNode*) ptr; + if (node.bAlloc.deallocate(b)) + { + static if (isShared) + { + import core.atomic : atomicOp; + atomicOp!"-="(node.bytesUsed, b.length); + } + else + { + node.bytesUsed -= b.length; + } + return true; + } + return false; + } + + // Allocate works only if memory can be provided via `alignedAllocate` from the parent + static if (hasMember!(ParentAllocator, "alignedAllocate")) + void[] allocate(size_t n) + { + static if (isShared) + import core.atomic : atomicOp, atomicLoad; + + if (n == 0 || n > theAlignment) + return null; + + static if (isShared) + { + lock.lock(); + scope(exit) lock.unlock(); + } + + auto tmp = cast(AlignedBlockNode*) root; + + // Iterate through list and find first node which has memory available + while (tmp) + { + auto next = tmp.next; + static if (isShared) + { + // Allocations can happen outside the lock + // Make sure nobody deletes this node while using it + tmp.keepAlive++; + if (next) next.keepAlive++; + lock.unlock(); + } + + auto result = tmp.bAlloc.allocate(n); + if (result.length == n) + { + // Success + static if (isShared) + { + atomicOp!"+="(tmp.bytesUsed, n); + lock.lock(); + } + else + { + tmp.bytesUsed += n; + } + + // Most likely this node has memory for more allocations + // Move it to the front + moveToFront(tmp); + + static if (isShared) + { + tmp.keepAlive--; + if (next) next.keepAlive--; + } + + return result; + } + + // This node can now be removed if necessary + static if (isShared) + { + lock.lock(); + tmp.keepAlive--; + if (next) next.keepAlive--; + } + + if (!next) + break; + + tmp = next; + next = tmp.next; + + // If there are too many nodes, free memory by removing empty nodes + static if (isShared) + { + if (atomicLoad(numNodes) > maxNodes && + atomicLoad(tmp.bytesUsed) == 0 && + tmp.keepAlive == 0) + { + removeNode(tmp); + } + } + else + { + if (numNodes > maxNodes && tmp.bytesUsed == 0) + { + removeNode(tmp); + } + } + + tmp = next; + } + + // Cannot create new AlignedBlockNode. Most likely the ParentAllocator ran out of resources + if (!insertNewNode()) + return null; + + tmp = cast(typeof(tmp)) root; + void[] result = tmp.bAlloc.allocate(n); + + static if (isShared) + { + atomicOp!"+="(root.bytesUsed, result.length); + } + else + { + root.bytesUsed += result.length; + } + + return result; + } + + // goodAllocSize should not use state + size_t goodAllocSize(const size_t n) + { + Allocator a = null; + return a.goodAllocSize(n); + } +} + +/** +`AlignedBlockList` represents a wrapper around a chain of allocators, allowing for fast deallocations +and preserving a low degree of fragmentation. +The allocator holds internally a doubly linked list of `Allocator` objects, which will serve allocations +in a most-recently-used fashion. Most recent allocators used for `allocate` calls, will be +moved to the front of the list. + +Although allocations are in theory served in linear searching time, `deallocate` calls take +$(BIGOH 1) time, by using aligned allocations. `ParentAllocator` must implement `alignedAllocate` +and it must be able to allocate `theAlignment` bytes at the same alignment. Each aligned allocation +done by `ParentAllocator` will contain metadata for an `Allocator`, followed by its payload. + +Params: + Allocator = the allocator which is used to manage each node; it must have a constructor which receives + `ubyte[]` and it must not have any parent allocators, except for the `NullAllocator` + ParentAllocator = each node draws memory from the parent allocator; it must support `alignedAllocate` + theAlignment = alignment of each block and at the same time length of each node +*/ +struct AlignedBlockList(Allocator, ParentAllocator, ulong theAlignment = (1 << 21)) +{ + version (StdDdoc) + { + import std.typecons : Ternary; + import std.traits : hasMember; + + /** + Returns a chunk of memory of size `n` + It finds the first node in the `AlignedBlockNode` list which has available memory, + and moves it to the front of the list. + + All empty nodes which cannot return new memory, are removed from the list. + + Params: + n = bytes to allocate + Returns: + A chunk of memory of the required length or `null` on failure or + */ + static if (hasMember!(ParentAllocator, "alignedAllocate")) + void[] allocate(size_t n); + + /** + Deallocates the buffer `b` given as parameter. Deallocations take place in constant + time, regardless of the number of nodes in the list. `b.ptr` is rounded down + to the nearest multiple of the `alignment` to quickly find the corresponding + `AlignedBlockNode`. + + Params: + b = buffer candidate for deallocation + Returns: + `true` on success and `false` on failure + */ + bool deallocate(void[] b); + + /** + Returns `Ternary.yes` if the buffer belongs to the parent allocator and + `Ternary.no` otherwise. + + Params: + b = buffer tested if owned by this allocator + Returns: + `Ternary.yes` if owned by this allocator and `Ternary.no` otherwise + */ + static if (hasMember!(ParentAllocator, "owns")) + Ternary owns(void[] b); + } + else + { + import std.math.traits : isPowerOf2; + static assert(isPowerOf2(alignment)); + mixin AlignedBlockListImpl!false; + } +} + +/// +@system unittest +{ + import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.experimental.allocator.building_blocks.segregator : Segregator; + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.typecons : Ternary; + + /* + In this example we use 'AlignedBlockList' in conjunction with other allocators + in order to create a more complex allocator. + + The 'SuperAllocator' uses a 'Segregator' to distribute allocations to sub-allocators, + based on the requested size. + + Each sub-allocator is represented by an 'AlignedBlockList' of 'BitmappedBlocks'. + Each 'AlignedBlockList' draws memory from a root allocator which in this case is an 'AscendingPageAllocator' + + Such an allocator not only provides good performance, but also a low degree of memory fragmentation. + */ + alias SuperAllocator = Segregator!( + 32, + AlignedBlockList!(BitmappedBlock!32, AscendingPageAllocator*, 1 << 12), + Segregator!( + + 64, + AlignedBlockList!(BitmappedBlock!64, AscendingPageAllocator*, 1 << 12), + Segregator!( + + 128, + AlignedBlockList!(BitmappedBlock!128, AscendingPageAllocator*, 1 << 12), + AscendingPageAllocator* + ))); + + SuperAllocator a; + auto pageAlloc = AscendingPageAllocator(128 * 4096); + + // Set the parent allocator for all the sub allocators + a.allocatorForSize!256 = &pageAlloc; + a.allocatorForSize!128.parent = &pageAlloc; + a.allocatorForSize!64.parent = &pageAlloc; + a.allocatorForSize!32.parent = &pageAlloc; + + enum testNum = 10; + void[][testNum] buf; + + // Allocations of size 32 will go to the first 'AlignedBlockList' + foreach (j; 0 .. testNum) + { + buf[j] = a.allocate(32); + assert(buf[j].length == 32); + + // This is owned by the first 'AlignedBlockList' + assert(a.allocatorForSize!32.owns(buf[j]) == Ternary.yes); + } + + // Free the memory + foreach (j; 0 .. testNum) + assert(a.deallocate(buf[j])); + + // Allocations of size 64 will go to the second 'AlignedBlockList' + foreach (j; 0 .. testNum) + { + buf[j] = a.allocate(64); + assert(buf[j].length == 64); + + // This is owned by the second 'AlignedBlockList' + assert(a.allocatorForSize!64.owns(buf[j]) == Ternary.yes); + } + + // Free the memory + foreach (j; 0 .. testNum) + assert(a.deallocate(buf[j])); + + // Allocations of size 128 will go to the third 'AlignedBlockList' + foreach (j; 0 .. testNum) + { + buf[j] = a.allocate(128); + assert(buf[j].length == 128); + + // This is owned by the third 'AlignedBlockList' + assert(a.allocatorForSize!128.owns(buf[j]) == Ternary.yes); + } + + // Free the memory + foreach (j; 0 .. testNum) + assert(a.deallocate(buf[j])); + + // Allocations which exceed 128, will go to the 'AscendingPageAllocator*' + void[] b = a.allocate(256); + assert(b.length == 256); + a.deallocate(b); +} + +/** +`SharedAlignedBlockList` is the threadsafe version of `AlignedBlockList`. +The `Allocator` template parameter must refer a shared allocator. +Also, `ParentAllocator` must be a shared allocator, supporting `alignedAllocate`. + +Params: + Allocator = the shared allocator which is used to manage each node; it must have a constructor which receives + `ubyte[]` and it must not have any parent allocators, except for the `NullAllocator` + ParentAllocator = each node draws memory from the parent allocator; it must be shared and support `alignedAllocate` + theAlignment = alignment of each block and at the same time length of each node +*/ +shared struct SharedAlignedBlockList(Allocator, ParentAllocator, ulong theAlignment = (1 << 21)) +{ + version (StdDdoc) + { + import std.typecons : Ternary; + import std.traits : hasMember; + + /** + Returns a chunk of memory of size `n` + It finds the first node in the `AlignedBlockNode` list which has available memory, + and moves it to the front of the list. + + All empty nodes which cannot return new memory, are removed from the list. + + Params: + n = bytes to allocate + Returns: + A chunk of memory of the required length or `null` on failure or + */ + static if (hasMember!(ParentAllocator, "alignedAllocate")) + void[] allocate(size_t n); + + /** + Deallocates the buffer `b` given as parameter. Deallocations take place in constant + time, regardless of the number of nodes in the list. `b.ptr` is rounded down + to the nearest multiple of the `alignment` to quickly find the corresponding + `AlignedBlockNode`. + + Params: + b = buffer candidate for deallocation + Returns: + `true` on success and `false` on failure + */ + bool deallocate(void[] b); + + /** + Returns `Ternary.yes` if the buffer belongs to the parent allocator and + `Ternary.no` otherwise. + + Params: + b = buffer tested if owned by this allocator + Returns: + `Ternary.yes` if owned by this allocator and `Ternary.no` otherwise + */ + static if (hasMember!(ParentAllocator, "owns")) + Ternary owns(void[] b); + } + else + { + import std.math.traits : isPowerOf2; + static assert(isPowerOf2(alignment)); + mixin AlignedBlockListImpl!true; + } +} + +/// +@system unittest +{ + import std.experimental.allocator.building_blocks.region : SharedRegion; + import std.experimental.allocator.building_blocks.ascending_page_allocator : SharedAscendingPageAllocator; + import std.experimental.allocator.building_blocks.null_allocator : NullAllocator; + import core.thread : ThreadGroup; + + enum numThreads = 8; + enum size = 2048; + enum maxIter = 10; + + /* + In this example we use 'SharedAlignedBlockList' together with 'SharedRegion', + in order to create a fast, thread-safe allocator. + */ + alias SuperAllocator = SharedAlignedBlockList!( + SharedRegion!(NullAllocator, 1), + SharedAscendingPageAllocator, + 4096); + + SuperAllocator a; + // The 'SuperAllocator' will draw memory from a 'SharedAscendingPageAllocator' + a.parent = SharedAscendingPageAllocator(4096 * 1024); + + // Launch 'numThreads', each performing allocations + void fun() + { + foreach (i; 0 .. maxIter) + { + void[] b = a.allocate(size); + assert(b.length == size); + } + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); +} + +version (StdUnittest) +{ + static void testrw(void[] b) + { + ubyte* buf = cast(ubyte*) b.ptr; + size_t len = (b.length).roundUpToMultipleOf(4096); + for (int i = 0; i < len; i += 4096) + { + buf[i] = (cast(ubyte) i % 256); + assert(buf[i] == (cast(ubyte) i % 256)); + } + } +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region; + import std.experimental.allocator.building_blocks.ascending_page_allocator; + import std.random; + import std.algorithm.sorting : sort; + import core.thread : ThreadGroup; + import core.internal.spinlock : SpinLock; + + enum pageSize = 4096; + enum numThreads = 10; + enum maxIter = 20; + enum totalAllocs = maxIter * numThreads; + size_t count = 0; + SpinLock lock = SpinLock(SpinLock.Contention.brief); + + alias SuperAllocator = SharedAlignedBlockList!( + SharedRegion!(NullAllocator, 1), + SharedAscendingPageAllocator, + 1 << 16); + void[][totalAllocs] buf; + + SuperAllocator a; + a.parent = SharedAscendingPageAllocator(4096 * 1024); + + void fun() + { + auto rnd = Random(1000); + + foreach (i; 0 .. maxIter) + { + auto size = uniform(1, pageSize + 1, rnd); + void[] b = a.allocate(size); + assert(b.length == size); + testrw(b); + + lock.lock(); + buf[count++] = b; + lock.unlock(); + } + } + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); + + sort!((a, b) => a.ptr < b.ptr)(buf[0 .. totalAllocs]); + foreach (i; 0 .. totalAllocs - 1) + { + assert(buf[i].ptr + a.goodAllocSize(buf[i].length) <= buf[i + 1].ptr); + } + + foreach (i; 0 .. totalAllocs) + { + assert(a.deallocate(buf[totalAllocs - 1 - i])); + } +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.experimental.allocator.building_blocks.segregator : Segregator; + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.random; + + alias SuperAllocator = Segregator!( + 256, + AlignedBlockList!(BitmappedBlock!256, AscendingPageAllocator*, 1 << 16), + Segregator!( + + 512, + AlignedBlockList!(BitmappedBlock!512, AscendingPageAllocator*, 1 << 16), + Segregator!( + + 1024, + AlignedBlockList!(BitmappedBlock!1024, AscendingPageAllocator*, 1 << 16), + Segregator!( + + 2048, + AlignedBlockList!(BitmappedBlock!2048, AscendingPageAllocator*, 1 << 16), + AscendingPageAllocator* + )))); + + SuperAllocator a; + auto pageAlloc = AscendingPageAllocator(4096 * 4096); + a.allocatorForSize!4096 = &pageAlloc; + a.allocatorForSize!2048.parent = &pageAlloc; + a.allocatorForSize!1024.parent = &pageAlloc; + a.allocatorForSize!512.parent = &pageAlloc; + a.allocatorForSize!256.parent = &pageAlloc; + + auto rnd = Random(1000); + + size_t maxIter = 10; + enum testNum = 10; + void[][testNum] buf; + int maxSize = 8192; + foreach (i; 0 .. maxIter) + { + foreach (j; 0 .. testNum) + { + auto size = uniform(1, maxSize + 1, rnd); + buf[j] = a.allocate(size); + assert(buf[j].length == size); + testrw(buf[j]); + } + + randomShuffle(buf[]); + + foreach (j; 0 .. testNum) + { + assert(a.deallocate(buf[j])); + } + } +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d index 2d0e670..bcab16d 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d @@ -1,10 +1,14 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/allocator_list.d) +*/ module std.experimental.allocator.building_blocks.allocator_list; +import core.memory : pageSize; + 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; @@ -21,7 +25,7 @@ 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. +`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 @@ -45,18 +49,18 @@ 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 +BookkeepingAllocator) is `NullAllocator`, then `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 +`n` passed to `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 +basis. For an object `sweatshop` of type `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 +allocators created should be much larger than `n` such that an allocator can +be used for many subsequent allocations. `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`. @@ -64,7 +68,7 @@ called `factory`. */ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) { - import std.conv : emplace; + import core.lifetime : emplace; import std.experimental.allocator.building_blocks.stats_collector : StatsCollector, Options; import std.traits : hasMember; @@ -97,7 +101,7 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) } /** - If $(D BookkeepingAllocator) is not $(D NullAllocator), $(D bkalloc) is + If `BookkeepingAllocator` is not `NullAllocator`, `bkalloc` is defined and accessible. */ @@ -155,11 +159,11 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) enum uint alignment = Allocator.alignment; /** - Allocate a block of size $(D s). First tries to allocate from the existing + Allocate a block of size `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 + creates a new allocator by calling `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 + fails, subsequent calls to `allocate` will not cause more calls to $(D make). */ void[] allocate(size_t s) @@ -177,17 +181,85 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) } 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) + + // Add a new allocator + if (auto a = addAllocator(s)) { - // no can do - return null; + auto result = a.allocate(s); + assert(owns(result) == Ternary.yes || !result.ptr); + return result; } + return null; + } + + static if (hasMember!(Allocator, "allocateZeroed")) + package(std) void[] allocateZeroed()(size_t s) + { + for (auto p = &root, n = *p; n; p = &n.next, n = *p) + { + auto result = n.allocateZeroed(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; + } + // Add a new allocator if (auto a = addAllocator(s)) { - auto result = a.allocate(s); + auto result = a.allocateZeroed(s); + assert(owns(result) == Ternary.yes || !result.ptr); + return result; + } + return null; + } + + /** + Allocate a block of size `s` with alignment `a`. First tries to allocate + from the existing list of already-created allocators. If neither can + satisfy the request, creates a new allocator by calling `make(s + a - 1)` + and delegates the request to it. However, if the allocation fresh off a + newly created allocator fails, subsequent calls to `alignedAllocate` + will not cause more calls to `make`. + */ + static if (hasMember!(Allocator, "alignedAllocate")) + void[] alignedAllocate(size_t s, uint theAlignment) + { + import std.algorithm.comparison : max; + import core.checkedint : addu; + + if (theAlignment == 0 || s == 0) + return null; + + for (auto p = &root, n = *p; n; p = &n.next, n = *p) + { + auto result = n.alignedAllocate(s, theAlignment); + if (result.length != s) continue; + // Bring to front if not already + if (root != n) + { + *p = n.next; + n.next = root; + root = n; + } + return result; + } + + bool overflow = false; + size_t maxSize = addu(s - 1, cast(size_t) theAlignment, overflow); + assert(!overflow, "Requested size is too large"); + if (overflow) + return null; + + // Add a new allocator + if (auto a = addAllocator(maxSize)) + { + auto result = a.alignedAllocate(s, theAlignment); assert(owns(result) == Ternary.yes || !result.ptr); return result; } @@ -373,15 +445,15 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) } /** - 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 + Defined only if `Allocator.expand` is defined. Finds the owner of `b` + and calls `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; + if (!b) 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); @@ -390,9 +462,9 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) } /** - 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. + Defined only if `Allocator.reallocate` is defined. Finds the owner of + `b` and calls `reallocate` for it. If that fails, calls the global + `reallocate`, which allocates a new block and moves memory. */ static if (hasMember!(Allocator, "reallocate")) bool reallocate(ref void[] b, size_t s) @@ -412,7 +484,7 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) } /** - Defined if $(D Allocator.deallocate) and $(D Allocator.owns) are defined. + Defined if `Allocator.deallocate` and `Allocator.owns` are defined. */ static if (hasMember!(Allocator, "deallocate") && hasMember!(Allocator, "owns")) @@ -454,7 +526,7 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) } /** - Defined only if $(D Allocator.owns) and $(D Allocator.deallocateAll) are + Defined only if `Allocator.owns` and `Allocator.deallocateAll` are defined. */ static if (ouroboros && hasMember!(Allocator, "deallocateAll") @@ -476,7 +548,19 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) assert(special || !allocators.ptr); if (special) { - special.deallocate(allocators); + static if (stateSize!SAllocator) + { + import core.stdc.string : memcpy; + SAllocator specialCopy; + assert(special.a.sizeof == specialCopy.sizeof); + memcpy(&specialCopy, &special.a, specialCopy.sizeof); + emplace(&special.a); + specialCopy.deallocateAll(); + } + else + { + special.deallocateAll(); + } } allocators = null; root = null; @@ -503,6 +587,7 @@ struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) Returns `Ternary.yes` if no allocators are currently active, `Ternary.no` otherwise. This methods never returns `Ternary.unknown`. */ + pure nothrow @safe @nogc Ternary empty() const { return Ternary(!allocators.length); @@ -596,26 +681,119 @@ version (Posix) @system unittest 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); + auto b1 = a.alignedAllocate(1024 * 8192, 1024); assert(b1 !is null); // still works due to overdimensioning + assert(b1.length == 1024 * 8192); + assert(b1.ptr.alignedAt(1024)); + assert(a.allocators.length == 1); + + b1 = a.alignedAllocate(0, 1024); + assert(b1.length == 0); + assert(a.allocators.length == 1); + b1 = a.allocate(1024 * 10); assert(b1.length == 1024 * 10); + + assert(a.reallocate(b1, 1024)); + assert(b1.length == 1024); + a.deallocateAll(); } @system unittest { + import core.exception : AssertError; + import std.exception : assertThrown; + + // 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.alignedAllocate(0, 1); + assert(b1 is null); + + b1 = a.alignedAllocate(1, 0); + assert(b1 is null); + + b1 = a.alignedAllocate(0, 0); + assert(b1 is null); + + assertThrown!AssertError(a.alignedAllocate(size_t.max, 1024)); + a.deallocateAll(); +} + +@system unittest +{ import std.typecons : Ternary; + + // 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 b0 = a.alignedAllocate(1, 1024); + assert(b0.length == 1); + assert(b0.ptr.alignedAt(1024)); + assert(a.allocators.length == 1); + + auto b1 = a.alignedAllocate(1024 * 4096, 1024); + assert(b1.length == 1024 * 4096); + assert(b1.ptr.alignedAt(1024)); + assert(a.allocators.length == 2); + + auto b2 = a.alignedAllocate(1024, 128); + assert(b2.length == 1024); + assert(b2.ptr.alignedAt(128)); + assert(a.allocators.length == 2); + + auto b3 = a.allocate(1024); + assert(b3.length == 1024); + assert(a.allocators.length == 2); + + auto b4 = a.allocate(1024 * 4096); + assert(b4.length == 1024 * 4096); + assert(a.allocators.length == 3); + + assert(a.root.empty == Ternary.no); + assert(a.deallocate(b4)); + assert(a.root.empty == Ternary.yes); + + assert(a.deallocate(b1)); + 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); + assert(a.reallocate(b1, 1024)); + assert(b1.length == 1024); + a.deallocateAll(); +} + +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)]), Mallocator) a; + auto b1 = a.allocate(1024 * 8192); assert(b1 !is null); b1 = a.allocate(1024 * 10); assert(b1.length == 1024 * 10); + assert((() pure nothrow @safe @nogc => a.expand(b1, 10))()); + assert(b1.length == 1025 * 10); a.allocate(1024 * 4095); - a.deallocateAll(); - assert(a.empty == Ternary.yes); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no); + // Ensure deallocateAll infers from parent + assert((() nothrow @nogc => a.deallocateAll())()); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); } @system unittest @@ -632,9 +810,191 @@ version (Posix) @system unittest auto b3 = a.allocate(192 * bs); assert(b3.length == 192 * bs); assert(a.allocators.length == 2); - a.deallocate(b1); + // Ensure deallocate inherits from parent allocators + () nothrow @nogc { a.deallocate(b1); }(); b1 = a.allocate(64 * bs); assert(b1.length == 64 * bs); assert(a.allocators.length == 2); a.deallocateAll(); } + +@system unittest +{ + import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.experimental.allocator.mallocator : Mallocator; + import std.algorithm.comparison : max; + import std.typecons : Ternary; + + static void testrw(void[] b) + { + ubyte* buf = cast(ubyte*) b.ptr; + for (int i = 0; i < b.length; i += pageSize) + { + buf[i] = cast(ubyte) (i % 256); + assert(buf[i] == cast(ubyte) (i % 256)); + } + } + + enum numPages = 2; + AllocatorList!((n) => AscendingPageAllocator(max(n, numPages * pageSize)), Mallocator) a; + + void[] b1 = a.allocate(1); + assert(b1.length == 1); + b1 = a.allocate(2); + assert(b1.length == 2); + testrw(b1); + assert(a.root.a.parent.getAvailableSize() == 0); + + void[] b2 = a.allocate((numPages + 1) * pageSize); + assert(b2.length == (numPages + 1) * pageSize); + testrw(b2); + + void[] b3 = a.allocate(3); + assert(b3.length == 3); + testrw(b3); + + void[] b4 = a.allocate(0); + assert(b4.length == 0); + + assert(a.allocators.length == 3); + assert(a.owns(b1) == Ternary.yes); + assert(a.owns(b2) == Ternary.yes); + assert(a.owns(b3) == Ternary.yes); + + assert(a.expand(b1, pageSize - b1.length)); + assert(b1.length == pageSize); + assert(!a.expand(b1, 1)); + assert(!a.expand(b2, 1)); + + testrw(b1); + testrw(b2); + testrw(b3); + + assert(a.deallocate(b1)); + assert(a.deallocate(b2)); + + assert(a.deallocateAll()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.experimental.allocator.mallocator : Mallocator; + import std.algorithm.comparison : max; + import std.typecons : Ternary; + + static void testrw(void[] b) + { + ubyte* buf = cast(ubyte*) b.ptr; + for (int i = 0; i < b.length; i += pageSize) + { + buf[i] = cast(ubyte) (i % 256); + assert(buf[i] == cast(ubyte) (i % 256)); + } + } + + enum numPages = 2; + AllocatorList!((n) => AscendingPageAllocator(max(n, numPages * pageSize)), NullAllocator) a; + + void[] b1 = a.allocate(1); + assert(b1.length == 1); + b1 = a.allocate(2); + assert(b1.length == 2); + testrw(b1); + + void[] b2 = a.allocate((numPages + 1) * pageSize); + assert(b2.length == (numPages + 1) * pageSize); + testrw(b2); + + void[] b3 = a.allocate(3); + assert(b3.length == 3); + testrw(b3); + + void[] b4 = a.allocate(0); + assert(b4.length == 0); + + assert(a.allocators.length == 3); + assert(a.owns(b1) == Ternary.yes); + assert(a.owns(b2) == Ternary.yes); + assert(a.owns(b3) == Ternary.yes); + + assert(a.expand(b1, pageSize - b1.length)); + assert(b1.length == pageSize); + assert(!a.expand(b1, 1)); + assert(!a.expand(b2, 1)); + + testrw(b1); + testrw(b2); + testrw(b3); + + assert(a.deallocate(b1)); + assert(a.deallocate(b2)); + + const alignment = cast(uint) (70 * pageSize); + b3 = a.alignedAllocate(70 * pageSize, alignment); + assert(b3.length == 70 * pageSize); + assert(b3.ptr.alignedAt(alignment)); + testrw(b3); + assert(a.allocators.length == 4); + assert(a.deallocate(b3)); + + + assert(a.deallocateAll()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.experimental.allocator.mallocator : Mallocator; + import std.algorithm.comparison : max; + import std.typecons : Ternary; + + static void testrw(void[] b) + { + ubyte* buf = cast(ubyte*) b.ptr; + for (int i = 0; i < b.length; i += pageSize) + { + buf[i] = cast(ubyte) (i % 256); + assert(buf[i] == cast(ubyte) (i % 256)); + } + } + + enum numPages = 5; + AllocatorList!((n) => AscendingPageAllocator(max(n, numPages * pageSize)), NullAllocator) a; + const alignment = cast(uint) (2 * pageSize); + auto b = a.alignedAllocate(1, alignment); + assert(b.length == 1); + assert(a.expand(b, pageSize - 1)); + assert(b.ptr.alignedAt(alignment)); + assert(b.length == pageSize); + + b = a.allocate(pageSize); + assert(b.length == pageSize); + assert(a.allocators.length == 1); + + assert(a.allocate(pageSize * 5).length == pageSize * 5); + assert(a.allocators.length == 2); + + assert(a.deallocateAll()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.ascending_page_allocator : AscendingPageAllocator; + import std.algorithm.comparison : max; + + enum maxIter = 100; + enum numPages = 10; + const chunkSize = pageSize / 8; + + AllocatorList!((n) => AscendingPageAllocator(max(n, numPages * pageSize)), NullAllocator) a; + foreach (i; 0 .. maxIter) + { + auto b1 = a.allocate(chunkSize); + assert(b1.length == chunkSize); + + assert(a.deallocate(b1)); + } + + assert(a.deallocateAll()); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/ascending_page_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/ascending_page_allocator.d new file mode 100644 index 0000000..bfb78c5 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/ascending_page_allocator.d @@ -0,0 +1,1007 @@ +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/ascending_page_allocator.d) +*/ +module std.experimental.allocator.building_blocks.ascending_page_allocator; + +import core.memory : pageSize; + +import std.experimental.allocator.common; + +// Common implementations for shared and thread local AscendingPageAllocator +private mixin template AscendingPageAllocatorImpl(bool isShared) +{ + bool deallocate(void[] buf) nothrow @nogc + { + size_t goodSize = goodAllocSize(buf.length); + version (Posix) + { + import core.sys.posix.sys.mman : mmap, MAP_FAILED, MAP_PRIVATE, + MAP_ANON, MAP_FIXED, PROT_NONE, munmap; + + auto ptr = mmap(buf.ptr, goodSize, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (ptr == MAP_FAILED) + return false; + } + else version (Windows) + { + import core.sys.windows.winbase : VirtualFree; + import core.sys.windows.winnt : MEM_DECOMMIT; + + auto ret = VirtualFree(buf.ptr, goodSize, MEM_DECOMMIT); + if (ret == 0) + return false; + } + else + { + static assert(0, "Unsupported OS"); + } + + static if (!isShared) + { + pagesUsed -= goodSize / pageSize; + } + + return true; + } + + Ternary owns(void[] buf) nothrow @nogc + { + if (!data) + return Ternary.no; + return Ternary(buf.ptr >= data && buf.ptr < buf.ptr + numPages * pageSize); + } + + bool deallocateAll() nothrow @nogc + { + version (Posix) + { + import core.sys.posix.sys.mman : munmap; + auto ret = munmap(cast(void*) data, numPages * pageSize); + if (ret != 0) + assert(0, "Failed to unmap memory, munmap failure"); + } + else version (Windows) + { + import core.sys.windows.winbase : VirtualFree; + import core.sys.windows.winnt : MEM_RELEASE; + auto ret = VirtualFree(cast(void*) data, 0, MEM_RELEASE); + if (ret == 0) + assert(0, "Failed to unmap memory, VirtualFree failure"); + } + else + { + static assert(0, "Unsupported OS version"); + } + data = null; + offset = null; + return true; + } + + size_t goodAllocSize(size_t n) nothrow @nogc + { + return n.roundUpToMultipleOf(cast(uint) pageSize); + } + + this(size_t n) nothrow @nogc + { + static if (isShared) + { + lock = SpinLock(SpinLock.Contention.brief); + } + + pageSize = .pageSize; + numPages = n.roundUpToMultipleOf(cast(uint) pageSize) / pageSize; + + version (Posix) + { + import core.sys.posix.sys.mman : mmap, MAP_ANON, PROT_NONE, + MAP_PRIVATE, MAP_FAILED; + + data = cast(typeof(data)) mmap(null, pageSize * numPages, + PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); + if (data == MAP_FAILED) + assert(0, "Failed to mmap memory"); + } + else version (Windows) + { + import core.sys.windows.winbase : VirtualAlloc; + import core.sys.windows.winnt : MEM_RESERVE, PAGE_NOACCESS; + + data = cast(typeof(data)) VirtualAlloc(null, pageSize * numPages, + MEM_RESERVE, PAGE_NOACCESS); + if (!data) + assert(0, "Failed to VirtualAlloc memory"); + } + else + { + static assert(0, "Unsupported OS version"); + } + + offset = data; + readWriteLimit = data; + } + + size_t getAvailableSize() nothrow @nogc + { + static if (isShared) + { + lock.lock(); + } + + auto size = numPages * pageSize + data - offset; + static if (isShared) + { + lock.unlock(); + } + return size; + } + + // Sets the protection of a memory range to read/write + private bool extendMemoryProtection(void* start, size_t size) nothrow @nogc + { + version (Posix) + { + import core.sys.posix.sys.mman : mprotect, PROT_WRITE, PROT_READ; + + auto ret = mprotect(start, size, PROT_WRITE | PROT_READ); + return ret == 0; + } + else version (Windows) + { + import core.sys.windows.winbase : VirtualAlloc; + import core.sys.windows.winnt : MEM_COMMIT, PAGE_READWRITE; + + auto ret = VirtualAlloc(start, size, MEM_COMMIT, PAGE_READWRITE); + return ret != null; + } + else + { + static assert(0, "Unsupported OS"); + } + } +} + +/** +`AscendingPageAllocator` is a fast and safe allocator that rounds all allocations +to multiples of the system's page size. It reserves a range of virtual addresses +(using `mmap` on Posix and `VirtualAlloc` on Windows) and allocates memory at consecutive virtual +addresses. + +When a chunk of memory is requested, the allocator finds a range of +virtual pages that satisfy the requested size, changing their protection to +read/write using OS primitives (`mprotect` and `VirtualProtect`, respectively). +The physical memory is allocated on demand, when the pages are accessed. + +Deallocation removes any read/write permissions from the target pages +and notifies the OS to reclaim the physical memory, while keeping the virtual +memory. + +Because the allocator does not reuse memory, any dangling references to +deallocated memory will always result in deterministically crashing the process. + +See_Also: +$(HTTPS microsoft.com/en-us/research/wp-content/uploads/2017/03/kedia2017mem.pdf, Simple Fast and Safe Manual Memory Management) for the general approach. +*/ +struct AscendingPageAllocator +{ + import std.typecons : Ternary; + + // Docs for mixin functions + version (StdDdoc) + { + /** + Rounds the mapping size to the next multiple of the page size and calls + the OS primitive responsible for creating memory mappings: `mmap` on POSIX and + `VirtualAlloc` on Windows. + + Params: + n = mapping size in bytes + */ + this(size_t n) nothrow @nogc; + + /** + Rounds the requested size to the next multiple of the page size. + */ + size_t goodAllocSize(size_t n) nothrow @nogc; + + /** + Decommit all physical memory associated with the buffer given as parameter, + but keep the range of virtual addresses. + + On POSIX systems `deallocate` calls `mmap` with `MAP_FIXED' a second time to decommit the memory. + On Windows, it uses `VirtualFree` with `MEM_DECOMMIT`. + */ + void deallocate(void[] b) nothrow @nogc; + + /** + Returns `Ternary.yes` if the passed buffer is inside the range of virtual adresses. + Does not guarantee that the passed buffer is still valid. + */ + Ternary owns(void[] buf) nothrow @nogc; + + /** + Removes the memory mapping causing all physical memory to be decommited and + the virtual address space to be reclaimed. + */ + bool deallocateAll() nothrow @nogc; + + /** + Returns the available size for further allocations in bytes. + */ + size_t getAvailableSize() nothrow @nogc; + } + +private: + size_t pageSize; + size_t numPages; + + // The start of the virtual address range + void* data; + + // Keeps track of there the next allocation should start + void* offset; + + // Number of pages which contain alive objects + size_t pagesUsed; + + // On allocation requests, we allocate an extra 'extraAllocPages' pages + // The address up to which we have permissions is stored in 'readWriteLimit' + void* readWriteLimit; + enum extraAllocPages = 1000; + +public: + enum uint alignment = 4096; + + // Inject common function implementations + mixin AscendingPageAllocatorImpl!false; + + /** + Rounds the allocation size to the next multiple of the page size. + The allocation only reserves a range of virtual pages but the actual + physical memory is allocated on demand, when accessing the memory. + + Params: + n = Bytes to allocate + + Returns: + `null` on failure or if the requested size exceeds the remaining capacity. + */ + void[] allocate(size_t n) nothrow @nogc + { + import std.algorithm.comparison : min; + + immutable pagedBytes = numPages * pageSize; + size_t goodSize = goodAllocSize(n); + + // Requested exceeds the virtual memory range + if (goodSize > pagedBytes || offset - data > pagedBytes - goodSize) + return null; + + // Current allocation exceeds readable/writable memory area + if (offset + goodSize > readWriteLimit) + { + // Extend r/w memory range to new limit + void* newReadWriteLimit = min(data + pagedBytes, + offset + goodSize + extraAllocPages * pageSize); + if (newReadWriteLimit != readWriteLimit) + { + assert(newReadWriteLimit > readWriteLimit); + if (!extendMemoryProtection(readWriteLimit, newReadWriteLimit - readWriteLimit)) + return null; + + readWriteLimit = newReadWriteLimit; + } + } + + void* result = offset; + offset += goodSize; + pagesUsed += goodSize / pageSize; + + return cast(void[]) result[0 .. n]; + } + + /** + Rounds the allocation size to the next multiple of the page size. + The allocation only reserves a range of virtual pages but the actual + physical memory is allocated on demand, when accessing the memory. + + The allocated memory is aligned to the specified alignment `a`. + + Params: + n = Bytes to allocate + a = Alignment + + Returns: + `null` on failure or if the requested size exceeds the remaining capacity. + */ + void[] alignedAllocate(size_t n, uint a) nothrow @nogc + { + void* alignedStart = cast(void*) roundUpToMultipleOf(cast(size_t) offset, a); + assert(alignedStart.alignedAt(a)); + immutable pagedBytes = numPages * pageSize; + size_t goodSize = goodAllocSize(n); + if (goodSize > pagedBytes || + alignedStart - data > pagedBytes - goodSize) + return null; + + // Same logic as allocate, only that the buffer must be properly aligned + auto oldOffset = offset; + offset = alignedStart; + auto result = allocate(n); + if (!result) + offset = oldOffset; + return result; + } + + /** + If the passed buffer is not the last allocation, then `delta` can be + at most the number of bytes left on the last page. + Otherwise, we can expand the last allocation until the end of the virtual + address range. + */ + bool expand(ref void[] b, size_t delta) nothrow @nogc + { + import std.algorithm.comparison : min; + + if (!delta) return true; + if (b is null) return false; + + size_t goodSize = goodAllocSize(b.length); + size_t bytesLeftOnPage = goodSize - b.length; + + // If this is not the last allocation, we can only expand until + // completely filling the last page covered by this buffer + if (b.ptr + goodSize != offset && delta > bytesLeftOnPage) + return false; + + size_t extraPages = 0; + + // If the extra `delta` bytes requested do not fit the last page + // compute how many extra pages are neeeded + if (delta > bytesLeftOnPage) + { + extraPages = goodAllocSize(delta - bytesLeftOnPage) / pageSize; + } + else + { + b = cast(void[]) b.ptr[0 .. b.length + delta]; + return true; + } + + if (extraPages > numPages || offset - data > pageSize * (numPages - extraPages)) + return false; + + void* newPtrEnd = b.ptr + goodSize + extraPages * pageSize; + if (newPtrEnd > readWriteLimit) + { + void* newReadWriteLimit = min(data + numPages * pageSize, + newPtrEnd + extraAllocPages * pageSize); + if (newReadWriteLimit > readWriteLimit) + { + if (!extendMemoryProtection(readWriteLimit, newReadWriteLimit - readWriteLimit)) + return false; + + readWriteLimit = newReadWriteLimit; + } + } + + pagesUsed += extraPages; + offset += extraPages * pageSize; + b = cast(void[]) b.ptr[0 .. b.length + delta]; + return true; + } + + /** + Returns `Ternary.yes` if the allocator does not contain any alive objects + and `Ternary.no` otherwise. + */ + Ternary empty() nothrow @nogc + { + return Ternary(pagesUsed == 0); + } + + /** + Unmaps the whole virtual address range on destruction. + */ + ~this() nothrow @nogc + { + if (data) + deallocateAll(); + } +} + +/// +@system @nogc nothrow unittest +{ + import core.memory : pageSize; + + size_t numPages = 100; + void[] buf; + void[] prevBuf = null; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + + foreach (i; 0 .. numPages) + { + // Allocation is rounded up to page size + buf = a.allocate(pageSize - 100); + assert(buf.length == pageSize - 100); + + // Allocations are served at increasing addresses + if (prevBuf) + assert(prevBuf.ptr + pageSize == buf.ptr); + + assert(a.deallocate(buf)); + prevBuf = buf; + } +} + +/** +`SharedAscendingPageAllocator` is the threadsafe version of `AscendingPageAllocator`. +*/ +shared struct SharedAscendingPageAllocator +{ + import std.typecons : Ternary; + import core.internal.spinlock : SpinLock; + + // Docs for mixin functions + version (StdDdoc) + { + /** + Rounds the mapping size to the next multiple of the page size and calls + the OS primitive responsible for creating memory mappings: `mmap` on POSIX and + `VirtualAlloc` on Windows. + + Params: + n = mapping size in bytes + */ + this(size_t n) nothrow @nogc; + + /** + Rounds the requested size to the next multiple of the page size. + */ + size_t goodAllocSize(size_t n) nothrow @nogc; + + /** + Decommit all physical memory associated with the buffer given as parameter, + but keep the range of virtual addresses. + + On POSIX systems `deallocate` calls `mmap` with `MAP_FIXED' a second time to decommit the memory. + On Windows, it uses `VirtualFree` with `MEM_DECOMMIT`. + */ + void deallocate(void[] b) nothrow @nogc; + + /** + Returns `Ternary.yes` if the passed buffer is inside the range of virtual adresses. + Does not guarantee that the passed buffer is still valid. + */ + Ternary owns(void[] buf) nothrow @nogc; + + /** + Removes the memory mapping causing all physical memory to be decommited and + the virtual address space to be reclaimed. + */ + bool deallocateAll() nothrow @nogc; + + /** + Returns the available size for further allocations in bytes. + */ + size_t getAvailableSize() nothrow @nogc; + } + +private: + size_t pageSize; + size_t numPages; + + // The start of the virtual address range + shared void* data; + + // Keeps track of there the next allocation should start + shared void* offset; + + // On allocation requests, we allocate an extra 'extraAllocPages' pages + // The address up to which we have permissions is stored in 'readWriteLimit' + shared void* readWriteLimit; + enum extraAllocPages = 1000; + SpinLock lock; + +public: + enum uint alignment = 4096; + + // Inject common function implementations + mixin AscendingPageAllocatorImpl!true; + + /** + Rounds the allocation size to the next multiple of the page size. + The allocation only reserves a range of virtual pages but the actual + physical memory is allocated on demand, when accessing the memory. + + Params: + n = Bytes to allocate + + Returns: + `null` on failure or if the requested size exceeds the remaining capacity. + */ + void[] allocate(size_t n) nothrow @nogc + { + return allocateImpl(n, 1); + } + + /** + Rounds the allocation size to the next multiple of the page size. + The allocation only reserves a range of virtual pages but the actual + physical memory is allocated on demand, when accessing the memory. + + The allocated memory is aligned to the specified alignment `a`. + + Params: + n = Bytes to allocate + a = Alignment + + Returns: + `null` on failure or if the requested size exceeds the remaining capacity. + */ + void[] alignedAllocate(size_t n, uint a) nothrow @nogc + { + // For regular `allocate` calls, `a` will be set to 1 + return allocateImpl(n, a); + } + + private void[] allocateImpl(size_t n, uint a) nothrow @nogc + { + import std.algorithm.comparison : min; + + size_t localExtraAlloc; + void* localOffset; + immutable pagedBytes = numPages * pageSize; + size_t goodSize = goodAllocSize(n); + + if (goodSize > pagedBytes) + return null; + + lock.lock(); + scope(exit) lock.unlock(); + + localOffset = cast(void*) offset; + void* alignedStart = cast(void*) roundUpToMultipleOf(cast(size_t) localOffset, a); + assert(alignedStart.alignedAt(a)); + if (alignedStart - data > pagedBytes - goodSize) + return null; + + localOffset = alignedStart + goodSize; + if (localOffset > readWriteLimit) + { + void* newReadWriteLimit = min(cast(void*) data + pagedBytes, + cast(void*) localOffset + extraAllocPages * pageSize); + assert(newReadWriteLimit > readWriteLimit); + localExtraAlloc = newReadWriteLimit - readWriteLimit; + if (!extendMemoryProtection(cast(void*) readWriteLimit, localExtraAlloc)) + return null; + readWriteLimit = cast(shared(void*)) newReadWriteLimit; + } + + offset = cast(typeof(offset)) localOffset; + return cast(void[]) alignedStart[0 .. n]; + } + + /** + If the passed buffer is not the last allocation, then `delta` can be + at most the number of bytes left on the last page. + Otherwise, we can expand the last allocation until the end of the virtual + address range. + */ + bool expand(ref void[] b, size_t delta) nothrow @nogc + { + import std.algorithm.comparison : min; + + if (!delta) return true; + if (b is null) return false; + + void* localOffset; + size_t localExtraAlloc; + size_t goodSize = goodAllocSize(b.length); + size_t bytesLeftOnPage = goodSize - b.length; + + if (bytesLeftOnPage >= delta) + { + b = cast(void[]) b.ptr[0 .. b.length + delta]; + return true; + } + + lock.lock(); + scope(exit) lock.unlock(); + + localOffset = cast(void*) offset; + if (b.ptr + goodSize != localOffset) + return false; + + size_t extraPages = goodAllocSize(delta - bytesLeftOnPage) / pageSize; + if (extraPages > numPages || localOffset - data > pageSize * (numPages - extraPages)) + return false; + + + localOffset = b.ptr + goodSize + extraPages * pageSize; + if (localOffset > readWriteLimit) + { + void* newReadWriteLimit = min(cast(void*) data + numPages * pageSize, + localOffset + extraAllocPages * pageSize); + assert(newReadWriteLimit > readWriteLimit); + localExtraAlloc = newReadWriteLimit - readWriteLimit; + if (!extendMemoryProtection(cast(void*) readWriteLimit, localExtraAlloc)) + return false; + readWriteLimit = cast(shared(void*)) newReadWriteLimit; + } + + offset = cast(typeof(offset)) localOffset; + b = cast(void[]) b.ptr[0 .. b.length + delta]; + return true; + } +} + +/// +@system unittest +{ + import core.memory : pageSize; + import core.thread : ThreadGroup; + + enum numThreads = 100; + shared SharedAscendingPageAllocator a = SharedAscendingPageAllocator(pageSize * numThreads); + + void fun() + { + void[] b = a.allocate(pageSize); + assert(b.length == pageSize); + + assert(a.deallocate(b)); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); +} + +version (StdUnittest) +{ + private static void testrw(void[] b) @nogc nothrow + { + ubyte* buf = cast(ubyte*) b.ptr; + buf[0] = 100; + assert(buf[0] == 100); + buf[b.length - 1] = 101; + assert(buf[b.length - 1] == 101); + } +} + +@system @nogc nothrow unittest +{ + static void testAlloc(Allocator)(ref Allocator a) @nogc nothrow + { + void[] b1 = a.allocate(1); + assert(a.getAvailableSize() == 3 * pageSize); + testrw(b1); + void[] b2 = a.allocate(2); + assert(a.getAvailableSize() == 2 * pageSize); + testrw(b2); + void[] b3 = a.allocate(pageSize + 1); + assert(a.getAvailableSize() == 0); + + testrw(b3); + assert(b1.length == 1); + assert(b2.length == 2); + assert(b3.length == pageSize + 1); + + assert(a.offset - a.data == 4 * pageSize); + void[] b4 = a.allocate(4); + assert(!b4); + + a.deallocate(b1); + assert(a.data); + a.deallocate(b2); + assert(a.data); + a.deallocate(b3); + } + + AscendingPageAllocator a = AscendingPageAllocator(4 * pageSize); + shared SharedAscendingPageAllocator aa = SharedAscendingPageAllocator(4 * pageSize); + + testAlloc(a); + testAlloc(aa); +} + +@system @nogc nothrow unittest +{ + size_t numPages = 26214; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + foreach (i; 0 .. numPages) + { + void[] buf = a.allocate(pageSize); + assert(buf.length == pageSize); + testrw(buf); + a.deallocate(buf); + } + + assert(!a.allocate(1)); + assert(a.getAvailableSize() == 0); +} + +@system @nogc nothrow unittest +{ + size_t numPages = 26214; + uint alignment = cast(uint) pageSize; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + + foreach (i; 0 .. numPages) + { + void[] buf = a.alignedAllocate(pageSize, alignment); + assert(buf.length == pageSize); + testrw(buf); + a.deallocate(buf); + } + + assert(!a.allocate(1)); + assert(a.getAvailableSize() == 0); +} + +@system @nogc nothrow unittest +{ + static void testAlloc(Allocator)(ref Allocator a) @nogc nothrow + { + import std.traits : hasMember; + + size_t numPages = 5; + uint alignment = cast(uint) pageSize; + + void[] b1 = a.allocate(pageSize / 2); + assert(b1.length == pageSize / 2); + + void[] b2 = a.alignedAllocate(pageSize / 2, alignment); + assert(a.expand(b1, pageSize / 2)); + assert(a.expand(b1, 0)); + assert(!a.expand(b1, 1)); + testrw(b1); + + assert(a.expand(b2, pageSize / 2)); + testrw(b2); + assert(b2.length == pageSize); + + assert(a.getAvailableSize() == pageSize * 3); + + void[] b3 = a.allocate(pageSize / 2); + assert(a.reallocate(b1, b1.length)); + assert(a.reallocate(b2, b2.length)); + assert(a.reallocate(b3, b3.length)); + + assert(b3.length == pageSize / 2); + testrw(b3); + assert(a.expand(b3, pageSize / 4)); + testrw(b3); + assert(a.expand(b3, 0)); + assert(b3.length == pageSize / 2 + pageSize / 4); + assert(a.expand(b3, pageSize / 4 - 1)); + testrw(b3); + assert(a.expand(b3, 0)); + assert(b3.length == pageSize - 1); + assert(a.expand(b3, 2)); + assert(a.expand(b3, 0)); + assert(a.getAvailableSize() == pageSize); + assert(b3.length == pageSize + 1); + testrw(b3); + + assert(a.reallocate(b1, b1.length)); + assert(a.reallocate(b2, b2.length)); + assert(a.reallocate(b3, b3.length)); + + assert(a.reallocate(b3, 2 * pageSize)); + testrw(b3); + assert(a.reallocate(b1, pageSize - 1)); + testrw(b1); + assert(a.expand(b1, 1)); + testrw(b1); + assert(!a.expand(b1, 1)); + + a.deallocate(b1); + a.deallocate(b2); + a.deallocate(b3); + } + + size_t numPages = 5; + uint alignment = cast(uint) pageSize; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + shared SharedAscendingPageAllocator aa = SharedAscendingPageAllocator(numPages * pageSize); + + testAlloc(a); + testAlloc(aa); +} + +@system @nogc nothrow unittest +{ + size_t numPages = 21000; + enum testNum = 100; + enum allocPages = 10; + void[][testNum] buf; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + + for (int i = 0; i < numPages; i += testNum * allocPages) + { + foreach (j; 0 .. testNum) + { + buf[j] = a.allocate(pageSize * allocPages); + testrw(buf[j]); + } + + foreach (j; 0 .. testNum) + { + a.deallocate(buf[j]); + } + } +} + +@system @nogc nothrow unittest +{ + size_t numPages = 21000; + enum testNum = 100; + enum allocPages = 10; + void[][testNum] buf; + shared SharedAscendingPageAllocator a = SharedAscendingPageAllocator(numPages * pageSize); + + for (int i = 0; i < numPages; i += testNum * allocPages) + { + foreach (j; 0 .. testNum) + { + buf[j] = a.allocate(pageSize * allocPages); + testrw(buf[j]); + } + + foreach (j; 0 .. testNum) + { + a.deallocate(buf[j]); + } + } +} + +@system @nogc nothrow unittest +{ + enum numPages = 2; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + void[] b = a.allocate((numPages + 1) * pageSize); + assert(b is null); + b = a.allocate(1); + assert(b.length == 1); + assert(a.getAvailableSize() == pageSize); + a.deallocateAll(); + assert(!a.data && !a.offset); +} + +@system @nogc nothrow unittest +{ + enum numPages = 26; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + uint alignment = cast(uint) ((numPages / 2) * pageSize); + void[] b = a.alignedAllocate(pageSize, alignment); + assert(b.length == pageSize); + testrw(b); + assert(b.ptr.alignedAt(alignment)); + a.deallocateAll(); + assert(!a.data && !a.offset); +} + +@system @nogc nothrow unittest +{ + enum numPages = 10; + AscendingPageAllocator a = AscendingPageAllocator(numPages * pageSize); + uint alignment = cast(uint) (2 * pageSize); + + void[] b1 = a.alignedAllocate(pageSize, alignment); + assert(b1.length == pageSize); + testrw(b1); + assert(b1.ptr.alignedAt(alignment)); + + void[] b2 = a.alignedAllocate(pageSize, alignment); + assert(b2.length == pageSize); + testrw(b2); + assert(b2.ptr.alignedAt(alignment)); + + void[] b3 = a.alignedAllocate(pageSize, alignment); + assert(b3.length == pageSize); + testrw(b3); + assert(b3.ptr.alignedAt(alignment)); + + void[] b4 = a.allocate(pageSize); + assert(b4.length == pageSize); + testrw(b4); + + assert(a.deallocate(b1)); + assert(a.deallocate(b2)); + assert(a.deallocate(b3)); + assert(a.deallocate(b4)); + + a.deallocateAll(); + assert(!a.data && !a.offset); +} + +@system unittest +{ + import core.thread : ThreadGroup; + import std.algorithm.sorting : sort; + import core.internal.spinlock : SpinLock; + + enum numThreads = 100; + SpinLock lock = SpinLock(SpinLock.Contention.brief); + ulong[numThreads] ptrVals; + size_t count = 0; + shared SharedAscendingPageAllocator a = SharedAscendingPageAllocator(pageSize * numThreads); + + void fun() + { + void[] b = a.allocate(4000); + assert(b.length == 4000); + + assert(a.expand(b, 96)); + assert(b.length == 4096); + + lock.lock(); + ptrVals[count] = cast(ulong) b.ptr; + count++; + lock.unlock(); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); + + ptrVals[].sort(); + foreach (i; 0 .. numThreads - 1) + { + assert(ptrVals[i] + pageSize == ptrVals[i + 1]); + } +} + +@system unittest +{ + import core.thread : ThreadGroup; + import std.algorithm.sorting : sort; + import core.internal.spinlock : SpinLock; + + SpinLock lock = SpinLock(SpinLock.Contention.brief); + enum numThreads = 100; + void[][numThreads] buf; + size_t count = 0; + shared SharedAscendingPageAllocator a = SharedAscendingPageAllocator(2 * pageSize * numThreads); + + void fun() + { + enum expand = 96; + void[] b = a.allocate(pageSize - expand); + assert(b.length == pageSize - expand); + + assert(a.expand(b, expand)); + assert(b.length == pageSize); + + a.expand(b, pageSize); + assert(b.length == pageSize || b.length == pageSize * 2); + + lock.lock(); + buf[count] = b; + count++; + lock.unlock(); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); + + sort!((a, b) => a.ptr < b.ptr)(buf[0 .. 100]); + foreach (i; 0 .. numThreads - 1) + { + assert(buf[i].ptr + buf[i].length == buf[i + 1].ptr); + } +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d b/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d index 24e27a9..571e4e6 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d @@ -1,87 +1,48 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/bitmapped_block.d) +*/ module std.experimental.allocator.building_blocks.bitmapped_block; import std.experimental.allocator.building_blocks.null_allocator; import std.experimental.allocator.common; +import std.typecons : Flag, Yes, No; -/** - -$(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) +// Common implementation for shared and non-shared versions of the BitmappedBlock +private mixin template BitmappedBlockImpl(bool isShared, bool multiBlock) { 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 if (isShared && multiBlock) + import core.internal.spinlock : SpinLock; + static assert(theBlockSize > 0 && theAlignment.isGoodStaticAlignment); - static assert(theBlockSize == chooseAtRuntime - || theBlockSize % theAlignment == 0, - "Block size must be a multiple of the alignment"); + 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 { + // It is the caller's responsibilty to synchronize this with + // allocate/deallocate in shared environments @property uint blockSize() { return _blockSize; } @property void blockSize(uint s) { - assert(!_control && s % alignment == 0); + static if (multiBlock) + { + assert((cast(BitVector) _control).length == 0 && s % alignment == 0); + } + else + { + assert(_control.length == 0 && s % alignment == 0); + } _blockSize = s; } private uint _blockSize; @@ -97,18 +58,8 @@ struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment 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; @@ -117,42 +68,44 @@ struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment { alias parent = ParentAllocator.instance; } - private uint _blocks; - private BitVector _control; + + private size_t _blocks; private void[] _payload; private size_t _startIdx; - // } + // For multiblock, '_control' is a BitVector, otherwise just a regular ulong[] + static if (multiBlock) + { + // Keeps track of first block which has never been used in an allocation. + // All blocks which are located right to the '_freshBit', should have never been + // allocated + private ulong _freshBit; + private BitVector _control; + } + else + { + private ulong[] _control; + } + + static if (multiBlock && isShared) + { + SpinLock lock = SpinLock(SpinLock.Contention.brief); + } + + pure nothrow @safe @nogc 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)); + 1U << min(31U, 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; @@ -161,31 +114,49 @@ struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment 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); + _blocks = totalBits / bitsPerBlock; // Reality is a bit more complicated, iterate until a good number of // blocks found. - for (; _blocks; --_blocks) + size_t localBlocks; + for (localBlocks = _blocks; localBlocks; --localBlocks) { - immutable controlWords = _blocks.divideRoundUp(64); + immutable controlWords = localBlocks.divideRoundUp(64); auto payload = data[controlWords * 8 .. $].roundStartToMultipleOf( alignment); - if (payload.length < _blocks * blockSize) + if (payload.length < localBlocks * blockSize) { // Overestimated continue; } - _control = BitVector((cast(ulong*) data.ptr)[0 .. controlWords]); - _control[] = 0; - _payload = payload; + + // Need the casts for shared versions + static if (multiBlock) + { + _control = cast(typeof(_control)) BitVector((cast(ulong*) data.ptr)[0 .. controlWords]); + (cast(BitVector) _control)[] = 0; + } + else + { + _control = (cast(typeof(_control.ptr)) data.ptr)[0 .. controlWords]; + _control[] = 0; + } + + _payload = cast(typeof(_payload)) payload; break; } + + _blocks = cast(typeof(_blocks)) localBlocks; } - /// Ditto - static if (!is(ParentAllocator == NullAllocator)) + static if (chooseAtRuntime == theBlockSize) + this(ubyte[] data, uint blockSize) + { + this._blockSize = blockSize; + this(data); + } + + static if (!is(ParentAllocator == NullAllocator) && !stateSize!ParentAllocator) this(size_t capacity) { size_t toAllocate = totalAllocation(capacity); @@ -194,585 +165,1728 @@ struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment 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() + static if (!is(ParentAllocator == NullAllocator) && stateSize!ParentAllocator) + this(ParentAllocator parent, size_t capacity) { - auto start = _control.rep.ptr, end = _payload.ptr + _payload.length; - parent.deallocate(start[0 .. end - start]); + this.parent = parent; + size_t toAllocate = totalAllocation(capacity); + auto data = cast(ubyte[])(parent.allocate(toAllocate)); + this(data); } - /* - 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() + static if (!is(ParentAllocator == NullAllocator) && + chooseAtRuntime == theBlockSize && + !stateSize!ParentAllocator) + this(size_t capacity, uint blockSize) { - while (_startIdx < _control.rep.length - && _control.rep[_startIdx] == ulong.max) - { - ++_startIdx; - } + this._blockSize = blockSize; + this(capacity); } - /* - 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) + static if (!is(ParentAllocator == NullAllocator) && + chooseAtRuntime == theBlockSize && + stateSize!ParentAllocator) + this(ParentAllocator parent, size_t capacity, uint blockSize) { - 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; + this._blockSize = blockSize; + this(parent, capacity); } - /** - Returns the actual bytes allocated when $(D n) bytes are requested, i.e. - $(D n.roundUpToMultipleOf(blockSize)). - */ + static if (!is(ParentAllocator == NullAllocator) + && hasMember!(ParentAllocator, "deallocate")) + ~this() + { + // multiblock bitmapped blocks use a BitVector + static if (multiBlock) + { + void* start = cast(void*) _control.rep.ptr; + } + else + { + void* start = cast(void*) _control.ptr; + } + void* end = cast(void*) (_payload.ptr + _payload.length); + parent.deallocate(start[0 .. end - start]); + } + + pure nothrow @safe @nogc 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) + // Implementation of the 'multiBlock' BitmappedBlock + // For the shared version, the methods are protected by a common lock + static if (multiBlock) { - const blocks = s.divideRoundUp(blockSize); - void[] result = void; - - switcharoo: - switch (blocks) + /* + 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() { - case 1: - // inline code here for speed - // find the next available block - foreach (i; _startIdx .. _control.rep.length) + while (_startIdx < _control.rep.length && _control.rep[_startIdx] == ulong.max) { - 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) + static if (isShared) + { + // Shared demands atomic increment, however this is protected + // by a lock. Regular increment is fine + auto localStart = _startIdx + 1; + _startIdx = localStart; + } + else { - adjustStartIdx(); + ++_startIdx; } - result = blocksFor(i, j, 1); - break switcharoo; } - goto case 0; // fall through - case 0: + } + + /* + Based on the latest allocated bit, 'newBit', it adjusts '_freshBit' + */ + pure nothrow @safe @nogc + private void adjustFreshBit(const ulong newBit) + { + import std.algorithm.comparison : max; + static if (isShared) + { + auto localFreshBit = max(newBit, _freshBit); + _freshBit = localFreshBit; + } + else + { + _freshBit = max(newBit, _freshBit); + } + } + + /* + Returns the blocks corresponding to the control bits starting at word index + wordIdx and bit index msbIdx (MSB=0) for a total of howManyBlocks. + */ + @trusted + private void[] blocksFor(this _)(size_t wordIdx, uint msbIdx, size_t howManyBlocks) + { + assert(msbIdx <= 63); + const start = (wordIdx * 64 + msbIdx) * blockSize; + const end = start + blockSize * howManyBlocks; + if (start == end) return null; + if (end <= _payload.length) return cast(void[]) _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; - 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); + static if (isShared) + nothrow @safe @nogc + void[] allocate(const size_t s) + { + lock.lock(); + scope(exit) lock.unlock(); - // 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 + return allocateImpl(s); + } - // Free any blocks that might be slack at the beginning - auto slackHeadingBlocks = (result.ptr - b.ptr) / blockSize; - if (slackHeadingBlocks) + static if (!isShared) + pure nothrow @safe @nogc + void[] allocate(const size_t s) { - deallocate(b[0 .. slackHeadingBlocks * blockSize]); + return allocateImpl(s); } - // Free any blocks that might be slack at the end - auto slackTrailingBlocks = ((b.ptr + b.length) - - (result.ptr + result.length)) / blockSize; - if (slackTrailingBlocks) + + // If shared, this is protected by a lock inside 'allocate' + pure nothrow @trusted @nogc + private void[] allocateImpl(const size_t s) { - deallocate(b[$ - slackTrailingBlocks * blockSize .. $]); + const blocks = s.divideRoundUp(blockSize); + void[] result; + + Lswitch: + 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, "Invalid number of blocks"); + assert((_control.rep[i] & ((1UL << 63) >> j)) == 0, "Corrupted bitmap"); + static if (isShared) + { + // Need the cast because shared does not recognize the lock + *(cast(ulong*) &_control._rep[i]) |= (1UL << 63) >> j; + } + else + { + _control.rep[i] |= (1UL << 63) >> j; + } + if (i == _startIdx) + { + adjustStartIdx(); + } + result = blocksFor(i, j, 1); + break Lswitch; + } + goto case 0; // fall through + case 0: + return null; + case 2: .. case 64: + result = smallAlloc(cast(uint) blocks); + break; + default: + result = hugeAlloc(blocks); + break; + } + if (result) + { + adjustFreshBit((result.ptr - _payload.ptr) / blockSize + blocks); + } + return result.ptr ? result.ptr[0 .. s] : null; } - return result; - } + @trusted void[] allocateFresh(const size_t s) + { + static if (isShared) + { + lock.lock(); + scope(exit) lock.unlock(); + } - /** - 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; - } + const blocks = s.divideRoundUp(blockSize); - /** - 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); - } + void[] result = blocksFor(cast(size_t) (_freshBit / 64), + cast(uint) (_freshBit % 64), blocks); + if (result) + { + (cast(BitVector) _control)[_freshBit .. _freshBit + blocks] = 1; + static if (isShared) + { + ulong localFreshBit = _freshBit; + localFreshBit += blocks; + _freshBit = localFreshBit; + } + else + { + _freshBit += blocks; + } + } + return result; + } - /* - 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) + void[] alignedAllocate(size_t n, uint a) { - // Allocation should fit this control word - if (setBitsIfZero(_control.rep[wordIdx], - cast(uint) (64 - msbIdx - blocks), 63 - msbIdx)) + static if (isShared) { - // Success - result = blocksFor(wordIdx, msbIdx, blocks); + lock.lock(); + scope(exit) lock.unlock(); + } + + return alignedAllocateImpl(n, a); + } + + // If shared, this is protected by a lock inside 'alignedAllocate' + private void[] alignedAllocateImpl(size_t n, uint a) + { + import std.math.traits : isPowerOf2; + assert(a.isPowerOf2); + if (a <= alignment) return allocate(n); + + // Overallocate to make sure we can get an aligned block + auto b = allocateImpl((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) + { + deallocateImpl(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) + { + deallocateImpl(b[$ - slackTrailingBlocks * blockSize .. $]); + } + + return result; + } + + /* + 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); + void[] tmpResult; + result = null; + if (msbIdx + blocks <= 64) + { + // Allocation should fit this control word + static if (isShared) + { + ulong localControl = _control.rep[wordIdx]; + bool didSetBit = setBitsIfZero(localControl, + cast(uint) (64 - msbIdx - blocks), 63 - msbIdx); + _control.rep[wordIdx] = localControl; + } + else + { + bool didSetBit = setBitsIfZero(_control.rep[wordIdx], + cast(uint) (64 - msbIdx - blocks), 63 - msbIdx); + } + if (didSetBit) + { + tmpResult = blocksFor(wordIdx, msbIdx, blocks); + if (!tmpResult) + { + static if (isShared) + { + localControl = _control.rep[wordIdx]; + resetBits(localControl, + cast(uint) (64 - msbIdx - blocks), 63 - msbIdx); + _control.rep[wordIdx] = localControl; + } + else + { + resetBits(_control.rep[wordIdx], + cast(uint) (64 - msbIdx - blocks), 63 - msbIdx); + } + return tuple(size_t.max - 1, 0u); + } + result = tmpResult; + tmpResult = null; + 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) + { + tmpResult = blocksFor(wordIdx, msbIdx, blocks); + if (!tmpResult) + { + return tuple(size_t.max - 1, 0u); + } + static if (isShared) + { + // Dont want atomics, because this is protected by 'lock' + ulong localControl = _control.rep[wordIdx]; + localControl |= mask; + _control.rep[wordIdx] = localControl; + } + else + { + _control.rep[wordIdx] |= mask; + } + result = tmpResult; + tmpResult = null; 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)); + // Failed, return a suggestion that skips this whole run. + return hint; } - // Allocation spans two control words or more - immutable mask = ulong.max >> msbIdx; - if (_control.rep[wordIdx] & mask) + + /* 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) { - // We can't allocate the rest of this control word, - // return a suggestion. - return tuple(wordIdx + 1, 0u); + assert(wordIdx < _control.rep.length); + const available = trailingZeros(_control.rep[wordIdx]); + static if (isShared) + { + ulong localControl = _control.rep[wordIdx]; + localControl |= ulong.max >> available; + _control.rep[wordIdx] = localControl; + } + else + { + _control.rep[wordIdx] |= ulong.max >> available; + } + return available; } - // 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) + + pure nothrow @safe @nogc + private void[] smallAlloc(uint blocks) return scope { - // No more memory - return tuple(_control.rep.length, 0u); + assert(blocks >= 2 && blocks <= 64); + void[] result; + 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 + result = blocksFor(i, j, blocks); + if (result) + { + static if (isShared) + { + ulong localControl = _control.rep[i]; + setBits(localControl, 64 - j - blocks, 63 - j); + _control.rep[i] = localControl; + } + else + { + setBits(_control.rep[i], 64 - j - blocks, 63 - j); + } + } + return result; + } + // 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); + result = blocksFor(i, 64 - available, blocks); + if (result && allocateAtFront(i + 1, needed)) + { + static if (isShared) + { + ulong localControl = _control.rep[i]; + localControl |= (1UL << available) - 1; + _control.rep[i] = localControl; + } + else + { + _control.rep[i] |= (1UL << available) - 1; + } + return result; + } + } + return null; } - auto hint = allocateAt(wordIdx + 1, 0, blocks - 64 + msbIdx, result); - if (hint[0] == size_t.max) + + pure nothrow @trusted @nogc + private void[] hugeAlloc(size_t blocks) return scope { - // We did it! - _control.rep[wordIdx] |= mask; - result = blocksFor(wordIdx, msbIdx, blocks); - return tuple(size_t.max, 0u); + assert(blocks > 64); + if (_startIdx == _control._rep.length) + { + assert((cast(BitVector) _control).allAre1); + return null; + } + + auto i = (cast(BitVector)_control).findZeros(blocks, _startIdx * 64); + if (i == i.max || i + blocks > _blocks) return null; + // Allocate those bits + (cast(BitVector) _control)[i .. i + blocks] = 1; + return cast(void[]) _payload[cast(size_t) (i * blockSize) + .. cast(size_t) ((i + blocks) * blockSize)]; } - // 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; - } + // Rounds sizeInBytes to a multiple of blockSize. + private size_t bytes2blocks(size_t sizeInBytes) + { + return (sizeInBytes + blockSize - 1) / blockSize; + } - 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) + /* 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; + static if (isShared) { - // yay, found stuff - setBits(_control.rep[i], 64 - j - blocks, 63 - j); - return blocksFor(i, j, blocks); + ulong localControl = _control.rep[wordIdx]; + localControl |= ~mask; + _control.rep[wordIdx] = localControl; } - // 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)) + else { - // yay, found a block crossing two words - _control.rep[i] |= (1UL << available) - 1; - return blocksFor(i, 64 - available, blocks); + _control.rep[wordIdx] |= ~mask; } + return true; } - 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)]; - } + // Since the lock is not pure, only the single threaded 'expand' is pure + static if (isShared) + { + nothrow @trusted @nogc + bool expand(ref void[] b, immutable size_t delta) + { + lock.lock(); + scope(exit) lock.unlock(); + + return expandImpl(b, delta); + } + } + else + { + pure nothrow @trusted @nogc + bool expand(ref void[] b, immutable size_t delta) + { + return expandImpl(b, delta); + } + } + + // If shared, this is protected by a lock inside 'expand' + pure nothrow @trusted @nogc + private bool expandImpl(ref void[] b, immutable size_t delta) + { + // Dispose with trivial corner cases + if (b is null || delta == 0) return delta == 0; + + /* 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); + b = b.ptr[0 .. b.length + delta]; + adjustFreshBit(blockIdx + blocksNew); + return true; + } + + @system bool reallocate(ref void[] b, size_t newSize) + { + static if (isShared) + { + lock.lock(); + scope(exit) lock.unlock(); + } + + return reallocateImpl(b, newSize); + } + + // If shared, this is protected by a lock inside 'reallocate' + private @system bool reallocateImpl(ref void[] b, size_t newSize) + { + static bool slowReallocate(Allocator)(ref Allocator a, ref void[] b, size_t s) + { + if (b.length == s) return true; + if (b.length <= s && a.expandImpl(b, s - b.length)) return true; + auto newB = a.allocateImpl(s); + if (newB.length != s) return false; + if (newB.length <= b.length) newB[] = b[0 .. newB.length]; + else newB[0 .. b.length] = b[]; + a.deallocateImpl(b); + b = newB; + return true; + } + + if (!b.ptr) + { + b = allocateImpl(newSize); + return b.length == newSize; + } + if (newSize == 0) + { + deallocateImpl(b); + b = null; + return true; + } + if (newSize < b.length) + { + // Shrink. Will shrink in place by deallocating the trailing part. + auto newCapacity = bytes2blocks(newSize) * blockSize; + deallocateImpl(b[newCapacity .. $]); + b = b[0 .. newSize]; + return true; + } + // Go the slow route + return slowReallocate(this, b, newSize); + } + + @system bool alignedReallocate(ref void[] b, size_t newSize, uint a) + { + static if (isShared) + { + lock.lock(); + scope(exit) lock.unlock(); + } + + return alignedReallocateImpl(b, newSize, a); + } + + // If shared, this is protected by a lock inside 'alignedReallocate' + private @system bool alignedReallocateImpl(ref void[] b, size_t newSize, uint a) + { + static bool slowAlignedReallocate(Allocator)(ref Allocator alloc, + ref void[] b, size_t s, uint a) + { + if (b.length <= s && b.ptr.alignedAt(a) + && alloc.expandImpl(b, s - b.length)) return true; + + auto newB = alloc.alignedAllocateImpl(s, a); + if (newB.length != s) return false; + if (newB.length <= b.length) newB[] = b[0 .. newB.length]; + else newB[0 .. b.length] = b[]; + alloc.deallocateImpl(b); + b = newB; + return true; + } + + if (newSize == 0) + { + deallocateImpl(b); + b = null; + return true; + } + // Go the slow route + return slowAlignedReallocate(this, b, newSize, a); + } + + nothrow @nogc + bool deallocate(void[] b) + { + static if (isShared) + { + lock.lock(); + scope(exit) lock.unlock(); + } + + return deallocateImpl(b); + } + + // If shared, this is protected by a lock inside 'deallocate' + nothrow @nogc + private bool deallocateImpl(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 + void* begin = cast(void*) (_payload.ptr + blockIdx * blockSize), + end = cast(void*) (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) + { + static if (isShared) + { + ulong localControl = _control.rep[wordIdx]; + resetBits(localControl, + cast(uint) (64 - msbIdx - blocks), + 63 - msbIdx); + _control.rep[wordIdx] = localControl; + } + else + { + resetBits(_control.rep[wordIdx], + cast(uint) (64 - msbIdx - blocks), + 63 - msbIdx); + } + return true; + } + else + { + static if (isShared) + { + ulong localControl = _control.rep[wordIdx]; + localControl &= ulong.max << (64 - msbIdx); + _control.rep[wordIdx] = localControl; + } + 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) + { + static if (isShared) + { + ulong localControl = _control.rep[wordIdx]; + localControl &= ulong.max >> blocks; + _control.rep[wordIdx] = localControl; + } + else + { + _control.rep[wordIdx] &= ulong.max >> blocks; + } + } + return true; + } + + // Since the lock is not pure, only the single threaded version is pure + static if (isShared) + { + nothrow @nogc + bool deallocateAll() + { + lock.lock(); + scope(exit) lock.unlock(); + + (cast(BitVector) _control)[] = 0; + _startIdx = 0; + return true; + } + } + else + { + pure nothrow @nogc + bool deallocateAll() + { + _control[] = 0; + _startIdx = 0; + return true; + } + } + + // Since the lock is not pure, only the single threaded version is pure + static if (isShared) + { + nothrow @safe @nogc + Ternary empty() + { + lock.lock(); + scope(exit) lock.unlock(); + + return emptyImpl(); + } + } + else + { + pure nothrow @safe @nogc + Ternary empty() + { + return Ternary(_control.allAre0()); + } + } + + pure nothrow @trusted @nogc + private Ternary emptyImpl() + { + return Ternary((cast(BitVector) _control).allAre0()); + } + + // Debug helper + debug(StdBitmapped) + private void dump() + { + import std.stdio : writefln, writeln; + + ulong controlLen = (cast(BitVector) _control).length; + writefln("%s @ %s {", typeid(this), cast(void*) (cast(BitVector) _control)._rep.ptr); + scope(exit) writeln("}"); + assert(_payload.length >= blockSize * _blocks); + assert(controlLen >= _blocks); + writefln(" _startIdx=%s; blockSize=%s; blocks=%s", + _startIdx, blockSize, _blocks); + if (!controlLen) return; + uint blockCount = 1; + bool inAllocatedStore = (cast(BitVector) _control)[0]; + void* start = cast(void*) _payload.ptr; + for (size_t i = 1;; ++i) + { + if (i >= _blocks || (cast(BitVector) _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 < controlLen); + inAllocatedStore = (cast(BitVector) _control)[i]; + start = cast(void*) (_payload.ptr + blockCount * blockSize); + blockCount = 1; + } + else + { + ++blockCount; + } + } + } + + void[] allocateAll() return scope + { + static if (isShared) + { + lock.lock(); + scope(exit) lock.unlock(); + } + + if (emptyImpl != Ternary.yes) return null; + (cast(BitVector) _control)[] = 1; + return cast(void[]) _payload; + } + } // Finish Yes.multiblock implementation specifics + else + { + static if (isShared) + pure nothrow @trusted @nogc + void[] allocate(const size_t s) + { + import core.atomic : cas, atomicLoad, atomicOp; + import core.bitop : bsr; + import std.range : iota; + import std.algorithm.iteration : map; + import std.array : array; + + if (s.divideRoundUp(blockSize) != 1) + return null; + + // First zero bit position for all values in the 0 - 255 range + // for fast lookup + static immutable ubyte[255] firstZero = iota(255U).map! + (x => (7 - (bsr((~x) & 0x000000ff)))).array; + + foreach (size_t i; 0 .. _control.length) + { + ulong controlVal, newControlVal, bitIndex; + do + { + bitIndex = 0; + newControlVal = 0; + controlVal = atomicLoad(_control[i]); + + // skip all control words which have all bits set + if (controlVal == ulong.max) + break; + + // fast lookup of first byte which has at least one zero bit + foreach (byteIndex; 0 .. 8) + { + ulong mask = (0xFFUL << (8 * (7 - byteIndex))); + if ((mask & controlVal) != mask) + { + ubyte byteVal = cast(ubyte) ((mask & controlVal) >> (8 * (7 - byteIndex))); + bitIndex += firstZero[byteVal]; + newControlVal = controlVal | (1UL << (63 - bitIndex)); + break; + } + bitIndex += 8; + } + } while (!cas(&_control[i], controlVal, newControlVal)); + + auto blockIndex = bitIndex + 64 * i; + if (controlVal != ulong.max && blockIndex < _blocks) + { + size_t payloadBlockStart = cast(size_t) blockIndex * blockSize; + return cast(void[]) _payload[payloadBlockStart .. payloadBlockStart + s]; + } + } + + return null; + } + + static if (!isShared) + pure nothrow @trusted @nogc + void[] allocate(const size_t s) + { + import core.bitop : bsr; + import std.range : iota; + import std.algorithm.iteration : map; + import std.array : array; + + if (s.divideRoundUp(blockSize) != 1) + return null; + + // First zero bit position for all values in the 0 - 255 range + // for fast lookup + static immutable ubyte[255] firstZero = iota(255U).map! + (x => (7 - (bsr((~x) & 0x000000ff)))).array; + + _startIdx = (_startIdx + 1) % _control.length; + foreach (size_t idx; 0 .. _control.length) + { + size_t i = (idx + _startIdx) % _control.length; + size_t bitIndex = 0; + // skip all control words which have all bits set + if (_control[i] == ulong.max) + continue; + + // fast lookup of first byte which has at least one zero bit + foreach (byteIndex; 0 .. 8) + { + ulong mask = (0xFFUL << (8 * (7 - byteIndex))); + if ((mask & _control[i]) != mask) + { + ubyte byteVal = cast(ubyte) ((mask & _control[i]) >> (8 * (7 - byteIndex))); + bitIndex += firstZero[byteVal]; + _control[i] |= (1UL << (63 - bitIndex)); + break; + } + bitIndex += 8; + } + + auto blockIndex = bitIndex + 64 * i; + if (blockIndex < _blocks) + { + size_t payloadBlockStart = cast(size_t) blockIndex * blockSize; + return cast(void[]) _payload[payloadBlockStart .. payloadBlockStart + s]; + } + } + + return null; + } + + nothrow @nogc + bool deallocate(void[] b) + { + static if (isShared) + import core.atomic : atomicOp; + + if (b is null) + return true; + + auto blockIndex = (b.ptr - _payload.ptr) / blockSize; + auto controlIndex = blockIndex / 64; + auto bitIndex = blockIndex % 64; + static if (isShared) + { + atomicOp!"&="(_control[controlIndex], ~(1UL << (63 - bitIndex))); + } + else + { + _control[controlIndex] &= ~(1UL << (63 - bitIndex)); + } + + return true; + } + + pure nothrow @trusted @nogc + bool expand(ref void[] b, immutable size_t delta) + { + if (delta == 0) + return true; + + immutable newLength = delta + b.length; + if (b is null || newLength > blockSize) + return false; + + b = b.ptr[0 .. newLength]; + return true; + } + } // Finish No.multiblock implementation specifics + + pure nothrow @trusted @nogc + Ternary owns(const void[] b) const + { + assert(b || b.length == 0, "Corrupt block."); + return Ternary(b && _payload && (&b[0] >= &_payload[0]) + && (&b[0] + b.length) <= (&_payload[0] + _payload.length)); + } +} + +/** +`BitmappedBlock` implements a simple heap consisting of one contiguous area +of memory organized in blocks, each of size `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 `NullAllocator` as `ParentAllocator` (the default) means user code +manages allocation of the memory block from the outside; in that case +`BitmappedBlock` must be constructed with a `ubyte[]` preallocated block and +has no responsibility regarding the lifetime of its support underlying storage. +If another allocator type is passed, `BitmappedBlock` defines a destructor that +uses the parent allocator to release the memory block. That makes the combination of `AllocatorList`, +`BitmappedBlock`, and a back-end allocator such as `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 `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 `BitmappedBlock` does +not need to maintain the allocated size, freeing memory implicitly coalesces +free blocks together. Also, tuning `blockSize` has a considerable impact on +both internal and external fragmentation. + +If the last template parameter is set to `No.multiblock`, the allocator will only serve +allocations which require at most `theBlockSize`. The `BitmappedBlock` has a specialized +implementation for single-block allocations which allows for greater performance, +at the cost of not being able to allocate more than one block at a time. + +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 `blockSize` +parameter as in `BitmappedBlock!(4096)`. To choose a block +size parameter, use `BitmappedBlock!(chooseAtRuntime)` and pass the +block size to the constructor. + +Params: + theBlockSize = the length of a block, which must be a multiple of `theAlignment` + + theAlignment = alignment of each block + + ParentAllocator = allocator from which the `BitmappedBlock` will draw memory. + If set to `NullAllocator`, the storage must be passed via the constructor + + f = `Yes.multiblock` to support allocations spanning across multiple blocks and + `No.multiblock` to support single block allocations. + Although limited by single block allocations, `No.multiblock` will generally + provide higher performance. +*/ +struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment, + ParentAllocator = NullAllocator, Flag!"multiblock" f = Yes.multiblock) +{ + version (StdDdoc) + { + /** + Constructs a block allocator given a hunk of memory, or a desired capacity + in bytes. + $(UL + $(LI If `ParentAllocator` is $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), + only the constructor taking `data` is defined and the user is responsible for freeing `data` if desired.) + $(LI Otherwise, both constructors are defined. The `data`-based + constructor assumes memory has been allocated with the parent allocator. + The `capacity`-based constructor uses `ParentAllocator` to allocate + an appropriate contiguous hunk of memory. Regardless of the constructor + used, the destructor releases the memory by using `ParentAllocator.deallocate`.) + ) + */ + this(ubyte[] data); + + /// Ditto + this(ubyte[] data, uint blockSize); + + /// Ditto + this(size_t capacity); + + /// Ditto + this(ParentAllocator parent, size_t capacity); + + /// Ditto + this(size_t capacity, uint blockSize); + + /// Ditto + this(ParentAllocator parent, size_t capacity, uint blockSize); + + /** + If `blockSize == chooseAtRuntime`, `BitmappedBlock` offers a read/write + property `blockSize`. It must be set before any use of the allocator. + Otherwise (i.e. `theBlockSize` is a legit constant), `blockSize` is + an alias for `theBlockSize`. Whether constant or variable, must also be + a multiple of `alignment`. This constraint is `assert`ed statically + and dynamically. + */ + alias blockSize = theBlockSize; + + /** + The _alignment offered is user-configurable statically through parameter + `theAlignment`, defaulted to `platformAlignment`. + */ + alias alignment = theAlignment; + + /** + The _parent allocator. Depending on whether `ParentAllocator` holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + ParentAllocator parent; + + /** + Returns the actual bytes allocated when `n` bytes are requested, i.e. + `n.roundUpToMultipleOf(blockSize)`. + */ + pure nothrow @safe @nogc + size_t goodAllocSize(size_t n); + + /** + 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.) + */ + pure nothrow @trusted @nogc + Ternary owns(const void[] b) const; + + /** + Expands in place a buffer previously allocated by `BitmappedBlock`. + If instantiated with `No.multiblock`, the expansion fails if the new length + exceeds `theBlockSize`. + */ + pure nothrow @trusted @nogc + bool expand(ref void[] b, immutable size_t delta); + + /** + Deallocates a block previously allocated with this allocator. + */ + nothrow @nogc + bool deallocate(void[] b); + + /** + Allocates `s` bytes of memory and returns it, or `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 `ulong` in the metadata. + Allocations greater than 64 blocks require a multiword search through the + metadata. + + If instantiated with `No.multiblock`, it performs a search for the first zero + bit in the bitmap and sets it. + */ + pure nothrow @trusted @nogc + void[] allocate(const size_t s); + + /** + Allocates s bytes of memory and returns it, or `null` if memory could not be allocated. + `allocateFresh` behaves just like allocate, the only difference being that this always + returns unused(fresh) memory. Although there may still be available space in the `BitmappedBlock`, + `allocateFresh` could still return null, because all the available blocks have been previously deallocated. + */ + @trusted void[] allocateFresh(const size_t s); + + /** + If the `BitmappedBlock` object is empty (has no active allocation), allocates + all memory within and returns a slice to it. Otherwise, returns `null` + (i.e. no attempt is made to allocate the largest available block). + */ + void[] allocateAll(); + + /** + Returns `Ternary.yes` if no memory is currently allocated with this + allocator, otherwise `Ternary.no`. This method never returns + `Ternary.unknown`. + */ + pure nothrow @safe @nogc + Ternary empty(); + + /** + Forcibly deallocates all memory allocated by this allocator, making it + available for further allocations. Does not return memory to `ParentAllocator`. + */ + pure nothrow @nogc + bool deallocateAll(); + + /** + Reallocates a block previously allocated with `alignedAllocate`. Contractions do not occur in place. + */ + @system bool alignedReallocate(ref void[] b, size_t newSize, uint a); + + /** + Reallocates a previously-allocated block. Contractions occur in place. + */ + @system bool reallocate(ref void[] b, size_t newSize); + + /** + Allocates a block with specified alignment `a`. The alignment must be a + power of 2. If `a <= alignment`, function forwards to `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); + + /** + If `ParentAllocator` is not `NullAllocator` and defines `deallocate`, + the destructor is defined to deallocate the block held. + */ + ~this(); + } + else + { + version (StdUnittest) + @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) () nothrow @nogc { AlignedMallocator.instance.deallocate(m); }(); + static if (theBlockSize == chooseAtRuntime) + { + testAllocator!(() => BitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m, 64)); + } + else + { + testAllocator!(() => BitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m)); + } + } + mixin BitmappedBlockImpl!(false, f == Yes.multiblock); + } +} + +/// +@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.mallocator : Mallocator; + import std.typecons : Flag, Yes; + + enum blockSize = 64; + enum numBlocks = 10; + + // The 'BitmappedBlock' is implicitly instantiated with Yes.multiblock + auto a = BitmappedBlock!(blockSize, 8, Mallocator, Yes.multiblock)(numBlocks * blockSize); + + // Instantiated with Yes.multiblock, can allocate more than one block at a time + void[] buf = a.allocate(2 * blockSize); + assert(buf.length == 2 * blockSize); + assert(a.deallocate(buf)); + + // Can also allocate less than one block + buf = a.allocate(blockSize / 2); + assert(buf.length == blockSize / 2); + + // Expands inside the same block + assert(a.expand(buf, blockSize / 2)); + assert(buf.length == blockSize); + + // If Yes.multiblock, can expand past the size of a single block + assert(a.expand(buf, 3 * blockSize)); + assert(buf.length == 4 * blockSize); + assert(a.deallocate(buf)); +} + +/// +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Flag, No; + + enum blockSize = 64; + auto a = BitmappedBlock!(blockSize, 8, Mallocator, No.multiblock)(1024 * blockSize); + + // Since instantiated with No.multiblock, can only allocate at most the block size + void[] buf = a.allocate(blockSize + 1); + assert(buf is null); + + buf = a.allocate(blockSize); + assert(buf.length == blockSize); + assert(a.deallocate(buf)); + + // This is also fine, because it's less than the block size + buf = a.allocate(blockSize / 2); + assert(buf.length == blockSize / 2); + + // Can expand the buffer until its length is at most 64 + assert(a.expand(buf, blockSize / 2)); + assert(buf.length == blockSize); + + // Cannot expand anymore + assert(!a.expand(buf, 1)); + assert(a.deallocate(buf)); +} + +// Test instantiation with stateful allocators +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.experimental.allocator.building_blocks.region : Region; + auto r = Region!Mallocator(1024 * 96); + auto a = BitmappedBlock!(chooseAtRuntime, 8, Region!Mallocator*, No.multiblock)(&r, 1024 * 64, 1024); +} + +/** +The threadsafe version of the $(LREF BitmappedBlock). +The semantics of the `SharedBitmappedBlock` are identical to the regular $(LREF BitmappedBlock). + +Params: + theBlockSize = the length of a block, which must be a multiple of `theAlignment` + + theAlignment = alignment of each block + + ParentAllocator = allocator from which the `BitmappedBlock` will draw memory. + If set to `NullAllocator`, the storage must be passed via the constructor + + f = `Yes.multiblock` to support allocations spanning across multiple blocks and + `No.multiblock` to support single block allocations. + Although limited by single block allocations, `No.multiblock` will generally + provide higher performance. +*/ +shared struct SharedBitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment, + ParentAllocator = NullAllocator, Flag!"multiblock" f = Yes.multiblock) +{ + version (StdDdoc) + { + /** + Constructs a block allocator given a hunk of memory, or a desired capacity + in bytes. + $(UL + $(LI If `ParentAllocator` is $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), + only the constructor taking `data` is defined and the user is responsible for freeing `data` if desired.) + $(LI Otherwise, both constructors are defined. The `data`-based + constructor assumes memory has been allocated with the parent allocator. + The `capacity`-based constructor uses `ParentAllocator` to allocate + an appropriate contiguous hunk of memory. Regardless of the constructor + used, the destructor releases the memory by using `ParentAllocator.deallocate`.) + ) + */ + this(ubyte[] data); + + /// Ditto + this(ubyte[] data, uint blockSize); + + /// Ditto + this(size_t capacity); + + /// Ditto + this(ParentAllocator parent, size_t capacity); + + /// Ditto + this(size_t capacity, uint blockSize); + + /// Ditto + this(ParentAllocator parent, size_t capacity, uint blockSize); + + /** + If `blockSize == chooseAtRuntime`, `SharedBitmappedBlock` offers a read/write + property `blockSize`. It must be set before any use of the allocator. + Otherwise (i.e. `theBlockSize` is a legit constant), `blockSize` is + an alias for `theBlockSize`. Whether constant or variable, must also be + a multiple of `alignment`. This constraint is `assert`ed statically + and dynamically. + */ + alias blockSize = theBlockSize; - // Rounds sizeInBytes to a multiple of blockSize. - private size_t bytes2blocks(size_t sizeInBytes) - { - return (sizeInBytes + blockSize - 1) / blockSize; - } + /** + The _alignment offered is user-configurable statically through parameter + `theAlignment`, defaulted to `platformAlignment`. + */ + alias alignment = theAlignment; - /* 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; - } + /** + The _parent allocator. Depending on whether `ParentAllocator` holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + ParentAllocator parent; - /** - 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; + /** + Returns the actual bytes allocated when `n` bytes are requested, i.e. + `n.roundUpToMultipleOf(blockSize)`. + */ + pure nothrow @safe @nogc + size_t goodAllocSize(size_t n); - /* 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). + /** + Returns `Ternary.yes` if `b` belongs to the `SharedBitmappedBlock` object, + `Ternary.no` otherwise. Never returns `Ternary.unkown`. (This + method is somewhat tolerant in that accepts an interior slice.) */ - if ((b.ptr - _payload.ptr) % blockSize) return false; + pure nothrow @trusted @nogc + Ternary owns(const void[] b) const; - const blocksOld = bytes2blocks(b.length); - const blocksNew = bytes2blocks(b.length + delta); - assert(blocksOld <= blocksNew); + /** + Expands in place a buffer previously allocated by `SharedBitmappedBlock`. + Expansion fails if the new length exceeds the block size. + */ + bool expand(ref void[] b, immutable size_t delta); - // Possibly we have enough slack at the end of the block! - if (blocksOld == blocksNew) - { - b = b.ptr[0 .. b.length + delta]; - return true; - } + /** + Deallocates the given buffer `b`, by atomically setting the corresponding + bit to `0`. `b` must be valid, and cannot contain multiple adjacent `blocks`. + */ + nothrow @nogc + bool deallocate(void[] b); - assert((b.ptr - _payload.ptr) % blockSize == 0); - const blockIdx = (b.ptr - _payload.ptr) / blockSize; - const blockIdxAfter = blockIdx + blocksOld; + /** + Allocates `s` bytes of memory and returns it, or `null` if memory + could not be allocated. - // 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; - } + The `SharedBitmappedBlock` cannot allocate more than the given block size. + Allocations are satisfied by searching the first unset bit in the bitmap, + and atomically setting it. + In rare memory pressure scenarios, the allocation could fail. + */ + nothrow @trusted @nogc + void[] allocate(const size_t s); + + /** + Allocates s bytes of memory and returns it, or `null` if memory could not be allocated. + `allocateFresh` behaves just like allocate, the only difference being that this always + returns unused(fresh) memory. Although there may still be available space in the `SharedBitmappedBlock`, + `allocateFresh` could still return null, because all the available blocks have been previously deallocated. + */ + @trusted void[] allocateFresh(const size_t s); - /** - 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); - } + /** + If the `SharedBitmappedBlock` object is empty (has no active allocation), allocates + all memory within and returns a slice to it. Otherwise, returns `null` + (i.e. no attempt is made to allocate the largest available block). + */ + void[] allocateAll(); - /** - 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); - } + /** + Returns `Ternary.yes` if no memory is currently allocated with this + allocator, otherwise `Ternary.no`. This method never returns + `Ternary.unknown`. + */ + nothrow @safe @nogc + Ternary empty(); - /** - Deallocates a block previously allocated with this allocator. - */ - bool deallocate(void[] b) - { - if (b is null) return true; + /** + Forcibly deallocates all memory allocated by this allocator, making it + available for further allocations. Does not return memory to `ParentAllocator`. + */ + nothrow @nogc + bool deallocateAll(); - // Locate position - immutable pos = b.ptr - _payload.ptr; - immutable blockIdx = pos / blockSize; + /** + Reallocates a block previously allocated with `alignedAllocate`. Contractions do not occur in place. + */ + @system bool alignedReallocate(ref void[] b, size_t newSize, uint a); - // 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); + /** + Reallocates a previously-allocated block. Contractions occur in place. + */ + @system bool reallocate(ref void[] b, size_t newSize); - // Get into details - auto wordIdx = blockIdx / 64, msbIdx = cast(uint) (blockIdx % 64); - if (_startIdx > wordIdx) _startIdx = wordIdx; + /** + Allocates a block with specified alignment `a`. The alignment must be a + power of 2. If `a <= alignment`, function forwards to `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); - // Three stages: heading bits, full words, leftover bits - if (msbIdx) + /** + If `ParentAllocator` is not `NullAllocator` and defines `deallocate`, + the destructor is defined to deallocate the block held. + */ + ~this(); + } + else + { + version (StdUnittest) + @system unittest { - if (blocks + msbIdx <= 64) + 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) () nothrow @nogc { AlignedMallocator.instance.deallocate(m); }(); + static if (theBlockSize == chooseAtRuntime) { - resetBits(_control.rep[wordIdx], - cast(uint) (64 - msbIdx - blocks), - 63 - msbIdx); - return true; + testAllocator!(() => SharedBitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m, 64)); } else { - _control.rep[wordIdx] &= ulong.max << 64 - msbIdx; - blocks -= 64 - msbIdx; - ++wordIdx; - msbIdx = 0; + testAllocator!(() => SharedBitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m)); } } + mixin BitmappedBlockImpl!(true, f == Yes.multiblock); + } +} + +/// +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.experimental.allocator.common : platformAlignment; + import std.typecons : Flag, Yes, No; + + // Create 'numThreads' threads, each allocating in parallel a chunk of memory + static void testAlloc(Allocator)(ref Allocator a, size_t allocSize) + { + import core.thread : ThreadGroup; + import std.algorithm.sorting : sort; + import core.internal.spinlock : SpinLock; + + SpinLock lock = SpinLock(SpinLock.Contention.brief); + enum numThreads = 10; + void[][numThreads] buf; + size_t count = 0; - // Stage 2: reset one word at a time - for (; blocks >= 64; blocks -= 64) + // Each threads allocates 'allocSize' + void fun() { - _control.rep[wordIdx++] = 0; + void[] b = a.allocate(allocSize); + assert(b.length == allocSize); + + lock.lock(); + scope(exit) lock.unlock(); + + buf[count] = b; + count++; } - // Stage 3: deal with leftover bits, if any - assert(wordIdx <= _control.rep.length); - if (blocks) + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) { - _control.rep[wordIdx] &= ulong.max >> blocks; + tg.create(&fun); } - 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; - } + tg.joinAll(); - /** - 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()); - } + // Sorting the allocations made by each thread, we expect the buffers to be + // adjacent inside the SharedBitmappedBlock + sort!((a, b) => a.ptr < b.ptr)(buf[0 .. numThreads]); + foreach (i; 0 .. numThreads - 1) + { + assert(buf[i].ptr + a.goodAllocSize(buf[i].length) <= buf[i + 1].ptr); + } - 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; - } + // Deallocate everything + foreach (i; 0 .. numThreads) + { + assert(a.deallocate(buf[i])); } } + + enum blockSize = 64; + auto alloc1 = SharedBitmappedBlock!(blockSize, platformAlignment, Mallocator, Yes.multiblock)(1024 * 1024); + auto alloc2 = SharedBitmappedBlock!(blockSize, platformAlignment, Mallocator, No.multiblock)(1024 * 1024); + testAlloc(alloc1, 2 * blockSize); + testAlloc(alloc2, blockSize); } -/// @system unittest { + // Test chooseAtRuntime // 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())); + uint blockSize = 64; + auto a = BitmappedBlock!(chooseAtRuntime, 64)(cast(ubyte[])(r.allocateAll()), blockSize); static assert(hasMember!(InSituRegion!(10_240, 64), "allocateAll")); + const b = (() pure nothrow @safe @nogc => a.allocate(100))(); + assert(b.length == 100); +} + +pure @safe unittest +{ + import std.typecons : Ternary; + + auto a = (() @trusted => BitmappedBlock!(64, 64, NullAllocator, Yes.multiblock)(new ubyte[10_240]))(); + () nothrow @nogc { + assert(a.empty == Ternary.yes); + const b = a.allocate(100); + assert(b.length == 100); + assert(a.empty == Ternary.no); + }(); +} + +@safe unittest +{ + import std.typecons : Ternary; + + auto a = (() @trusted => SharedBitmappedBlock!(64, 64, NullAllocator, Yes.multiblock)(new ubyte[10_240]))(); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); const b = a.allocate(100); assert(b.length == 100); } +version (StdUnittest) @system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; testAllocator!(() => BitmappedBlock!(64, 8, GCAllocator)(1024 * 64)); } +version (StdUnittest) +@system unittest +{ + // Test chooseAtRuntime + import std.experimental.allocator.gc_allocator : GCAllocator; + uint blockSize = 64; + testAllocator!(() => BitmappedBlock!(chooseAtRuntime, 8, GCAllocator, Yes.multiblock)(1024 * 64, blockSize)); + testAllocator!(() => BitmappedBlock!(chooseAtRuntime, 8, GCAllocator, No.multiblock)(1024 * 64, blockSize)); +} + +version (StdUnittest) +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + testAllocator!(() => SharedBitmappedBlock!(64, 8, Mallocator, Yes.multiblock)(1024 * 64)); + testAllocator!(() => SharedBitmappedBlock!(64, 8, Mallocator, No.multiblock)(1024 * 64)); +} + +version (StdUnittest) +@system unittest +{ + // Test chooseAtRuntime + import std.experimental.allocator.mallocator : Mallocator; + uint blockSize = 64; + testAllocator!(() => SharedBitmappedBlock!(chooseAtRuntime, 8, Mallocator, Yes.multiblock)(1024 * 64, blockSize)); + testAllocator!(() => SharedBitmappedBlock!(chooseAtRuntime, 8, Mallocator, No.multiblock)(1024 * 64, blockSize)); +} + @system unittest { - static void testAllocateAll(size_t bs)(uint blocks, uint blocksAtATime) + static void testAllocateAll(size_t bs, bool isShared = true)(size_t blocks, uint blocksAtATime) { - import std.algorithm.comparison : min; + template attribAllocate(string size) + { + static if (isShared) + { + const char[] attribAllocate = "(() nothrow @safe @nogc => a.allocate(" ~ size ~ "))()"; + } + else + { + const char[] attribAllocate = "(() pure nothrow @safe @nogc => a.allocate(" ~ size ~ "))()"; + } + } + assert(bs); + import std.typecons : Ternary; + import std.algorithm.comparison : min; import std.experimental.allocator.gc_allocator : GCAllocator; - auto a = BitmappedBlock!(bs, min(bs, platformAlignment))( - cast(ubyte[])(GCAllocator.instance.allocate((blocks * bs * 8 + - blocks) / 8)) - ); + + static if (isShared) + { + auto a = SharedBitmappedBlock!(bs, min(bs, platformAlignment), NullAllocator)( + cast(ubyte[])(GCAllocator.instance.allocate((blocks * bs * 8 + blocks) / 8))); + } + else + { + auto a = BitmappedBlock!(bs, min(bs, platformAlignment), NullAllocator)( + 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); + auto x = mixin(attribAllocate!("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(); - + x = mixin(attribAllocate!("1")); + assert(x.length == 1 || blocks == 0); + assert((() nothrow @nogc => a.deallocateAll())()); + assert(a.empty() == Ternary.yes); bool twice = true; begin: foreach (i; 0 .. blocks / blocksAtATime) { - auto b = a.allocate(bs * blocksAtATime); + auto b = mixin(attribAllocate!("bs * blocksAtATime")); assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); } - assert(a.allocate(bs * blocksAtATime) is null); - assert(a.allocate(1) is null); + + assert(mixin(attribAllocate!("bs * blocksAtATime")) is null); + if (a._blocks % blocksAtATime == 0) + { + assert(mixin(attribAllocate!("1")) is null); + } // Now deallocate all and do it again! - a.deallocateAll(); + assert((() nothrow @nogc => a.deallocateAll())()); // Test deallocation auto v = new void[][blocks / blocksAtATime]; foreach (i; 0 .. blocks / blocksAtATime) { - auto b = a.allocate(bs * blocksAtATime); + auto b = mixin(attribAllocate!("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); + assert(mixin(attribAllocate!("bs * blocksAtATime")) is null); + if (a._blocks % blocksAtATime == 0) + { + assert(mixin(attribAllocate!("1")) is null); + } foreach (i; 0 .. blocks / blocksAtATime) { - a.deallocate(v[i]); + () nothrow @nogc { a.deallocate(v[i]); }(); } foreach (i; 0 .. blocks / blocksAtATime) { - auto b = a.allocate(bs * blocksAtATime); + auto b = mixin(attribAllocate!("bs * blocksAtATime")); assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); v[i] = b; } foreach (i; 0 .. v.length) { - a.deallocate(v[i]); + () nothrow @nogc { a.deallocate(v[i]); }(); } if (twice) @@ -781,18 +1895,26 @@ struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment goto begin; } - a.deallocateAll; + assert((() nothrow @nogc => a.deallocateAll())()); // test expansion if (blocks >= blocksAtATime) { foreach (i; 0 .. blocks / blocksAtATime - 1) { - auto b = a.allocate(bs * blocksAtATime); + auto b = mixin(attribAllocate!("bs * blocksAtATime")); assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); (cast(ubyte[]) b)[] = 0xff; - a.expand(b, blocksAtATime * bs) - || assert(0, text(i)); + static if (isShared) + { + assert((() nothrow @safe @nogc => a.expand(b, blocksAtATime * bs))() + , text(i)); + } + else + { + assert((() pure nothrow @safe @nogc => a.expand(b, blocksAtATime * bs))() + , text(i)); + } (cast(ubyte[]) b)[] = 0xfe; assert(b.length == bs * blocksAtATime * 2, text(i, ": ", b.length)); a.reallocate(b, blocksAtATime * bs) || assert(0); @@ -802,31 +1924,203 @@ struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment } testAllocateAll!(1)(0, 1); + testAllocateAll!(1, false)(0, 1); testAllocateAll!(1)(8, 1); + testAllocateAll!(1, false)(8, 1); + testAllocateAll!(4096)(128, 1); + testAllocateAll!(4096, false)(128, 1); testAllocateAll!(1)(0, 2); testAllocateAll!(1)(128, 2); testAllocateAll!(4096)(128, 2); + testAllocateAll!(1, false)(0, 2); + testAllocateAll!(1, false)(128, 2); + testAllocateAll!(4096, false)(128, 2); + testAllocateAll!(1)(0, 4); testAllocateAll!(1)(128, 4); testAllocateAll!(4096)(128, 4); + testAllocateAll!(1, false)(0, 4); + testAllocateAll!(1, false)(128, 4); + testAllocateAll!(4096, false)(128, 4); + testAllocateAll!(1)(0, 3); testAllocateAll!(1)(24, 3); testAllocateAll!(3008)(100, 1); testAllocateAll!(3008)(100, 3); + testAllocateAll!(1, false)(0, 3); + testAllocateAll!(1, false)(24, 3); + testAllocateAll!(3008, false)(100, 1); + testAllocateAll!(3008, false)(100, 3); + testAllocateAll!(1)(0, 128); testAllocateAll!(1)(128 * 1, 128); testAllocateAll!(128 * 20)(13 * 128, 128); + + testAllocateAll!(1, false)(0, 128); + testAllocateAll!(1, false)(128 * 1, 128); + testAllocateAll!(128 * 20, false)(13 * 128, 128); } -// Test totalAllocation -@safe unittest +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + enum blocks = 10000; + int count = 0; + + ubyte[] payload = cast(ubyte[]) Mallocator.instance.allocate(blocks * 16); + auto a = BitmappedBlock!(16, 16)(payload); + void[][] buf = cast(void[][]) Mallocator.instance.allocate((void[]).sizeof * blocks); + + assert(!a.allocateFresh(0)); + assert(!a._control[0]); + + void[] b = a.allocate(256 * 16); + assert(b.length == 256 * 16); + count += 256; + + assert(!a._control[count]); + b = a.allocateFresh(16); + assert(b.length == 16); + count++; + assert(a._control[count - 1]); + + b = a.allocateFresh(16 * 300); + assert(b.length == 16 * 300); + count += 300; + + for (int i = 0; i < count; i++) + assert(a._control[i]); + assert(!a._control[count]); + + assert(a.expand(b, 313 * 16)); + count += 313; + + for (int i = 0; i < count; i++) + assert(a._control[i]); + assert(!a._control[count]); + + b = a.allocate(64 * 16); + assert(b.length == 64 * 16); + count += 64; + + b = a.allocateFresh(16); + assert(b.length == 16); + count++; + + for (int i = 0; i < count; i++) + assert(a._control[i]); + assert(!a._control[count]); + + assert(a.deallocateAll()); + for (int i = 0; i < a._blocks; i++) + assert(!a._control[i]); + + b = a.allocateFresh(257 * 16); + assert(b.length == 257 * 16); + for (int i = 0; i < count; i++) + assert(!a._control[i]); + for (int i = count; i < count + 257; i++) + assert(a._control[i]); + count += 257; + assert(!a._control[count]); + + while (true) + { + b = a.allocate(16); + if (!b) + break; + assert(b.length == 16); + } + + assert(!a.allocateFresh(16)); + assert(a.deallocateAll()); + + assert(a.allocate(16).length == 16); + assert(!a.allocateFresh(16)); +} + + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.random; + + static void testAlloc(Allocator)() + { + auto numBlocks = [1, 64, 256]; + enum blocks = 10000; + int iter = 0; + + ubyte[] payload = cast(ubyte[]) Mallocator.instance.allocate(blocks * 16); + auto a = Allocator(payload); + void[][] buf = cast(void[][]) Mallocator.instance.allocate((void[]).sizeof * blocks); + + auto rnd = Random(); + while (iter < blocks) + { + int event = uniform(0, 2, rnd); + int doExpand = uniform(0, 2, rnd); + int allocSize = numBlocks[uniform(0, 3, rnd)] * 16; + int expandSize = numBlocks[uniform(0, 3, rnd)] * 16; + int doDeallocate = uniform(0, 2, rnd); + + if (event) buf[iter] = a.allocate(allocSize); + else buf[iter] = a.allocateFresh(allocSize); + + if (!buf[iter]) + break; + assert(buf[iter].length == allocSize); + + auto oldSize = buf[iter].length; + if (doExpand && a.expand(buf[iter], expandSize)) + assert(buf[iter].length == expandSize + oldSize); + + if (doDeallocate) + { + assert(a.deallocate(buf[iter])); + buf[iter] = null; + } + + iter++; + } + + while (iter < blocks) + { + buf[iter++] = a.allocate(16); + if (!buf[iter - 1]) + break; + assert(buf[iter - 1].length == 16); + } + + for (size_t i = 0; i < a._blocks; i++) + assert((cast(BitVector) a._control)[i]); + + assert(!a.allocate(16)); + for (size_t i = 0; i < iter; i++) + { + if (buf[i]) + assert(a.deallocate(buf[i])); + } + + for (size_t i = 0; i < a._blocks; i++) + assert(!(cast(BitVector) a._control)[i]); + } + + testAlloc!(BitmappedBlock!(16, 16))(); + testAlloc!(SharedBitmappedBlock!(16, 16))(); +} + +// Test totalAllocation and goodAllocSize +nothrow @safe @nogc unittest { BitmappedBlock!(8, 8, NullAllocator) h1; + assert(h1.goodAllocSize(1) == 8); assert(h1.totalAllocation(1) >= 8); assert(h1.totalAllocation(64) >= 64); assert(h1.totalAllocation(8 * 64) >= 8 * 64); @@ -834,24 +2128,39 @@ struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment assert(h1.totalAllocation(8 * 64 + 1) >= 8 * 65); BitmappedBlock!(64, 8, NullAllocator) h2; + assert(h2.goodAllocSize(1) == 64); assert(h2.totalAllocation(1) >= 64); assert(h2.totalAllocation(64 * 64) >= 64 * 64); BitmappedBlock!(4096, 4096, NullAllocator) h3; + assert(h3.goodAllocSize(1) == 4096); assert(h3.totalAllocation(1) >= 4096); assert(h3.totalAllocation(64 * 4096) >= 64 * 4096); assert(h3.totalAllocation(64 * 4096 + 1) >= 65 * 4096); } +// Test owns +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + + auto a = BitmappedBlock!(64, 8, GCAllocator)(1024 * 64); + const void[] buff = (() pure nothrow @safe @nogc => a.allocate(42))(); + + assert((() nothrow @safe @nogc => a.owns(buff))() == Ternary.yes); + assert((() nothrow @safe @nogc => a.owns(null))() == Ternary.no); +} + // BitmappedBlockWithInternalPointers /** -A $(D BitmappedBlock) with additional structure for supporting $(D -resolveInternalPointer). To that end, $(D BitmappedBlockWithInternalPointers) adds a +A `BitmappedBlock` with additional structure for supporting `resolveInternalPointer`. +To that end, `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) +The time complexity of `resolveInternalPointer` is $(BIGOH k), where `k` is the size of the object within which the internal pointer is looked up. */ @@ -861,31 +2170,42 @@ struct BitmappedBlockWithInternalPointers( { import std.conv : text; import std.typecons : Ternary; + + static if (!stateSize!ParentAllocator) + version (StdUnittest) @system unittest { import std.experimental.allocator.mallocator : AlignedMallocator; auto m = cast(ubyte[])(AlignedMallocator.instance.alignedAllocate(1024 * 64, theAlignment)); - scope(exit) AlignedMallocator.instance.deallocate(m); + scope(exit) () nothrow @nogc { AlignedMallocator.instance.deallocate(m); }(); testAllocator!(() => BitmappedBlockWithInternalPointers(m)); } // state { - private BitmappedBlock!(theBlockSize, theAlignment, NullAllocator) _heap; + private BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator) _heap; private BitVector _allocStart; // } /** Constructors accepting desired capacity or a preallocated buffer, similar - in semantics to those of $(D BitmappedBlock). + in semantics to those of `BitmappedBlock`. */ + static if (!stateSize!ParentAllocator) this(ubyte[] data) { _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator)(data); } + static if (stateSize!ParentAllocator) + this(ParentAllocator parent, ubyte[] data) + { + _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator)(data); + _heap.parent = parent; + } + /// Ditto - static if (!is(ParentAllocator == NullAllocator)) + static if (!is(ParentAllocator == NullAllocator) && !stateSize!ParentAllocator) this(size_t capacity) { // Add room for the _allocStart vector @@ -893,7 +2213,17 @@ struct BitmappedBlockWithInternalPointers( (capacity + capacity.divideRoundUp(64)); } + /// Ditto + static if (!is(ParentAllocator == NullAllocator) && stateSize!ParentAllocator) + this(ParentAllocator parent, size_t capacity) + { + // Add room for the _allocStart vector + _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator) + (parent, capacity + capacity.divideRoundUp(64)); + } + // Makes sure there's enough room for _allocStart + @safe private bool ensureRoomForAllocStart(size_t len) { if (_allocStart.length >= len) return true; @@ -901,9 +2231,9 @@ struct BitmappedBlockWithInternalPointers( 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); + if ((() @trusted => !_heap.reallocate(b, bits / 8))()) return false; + assert(b.length * 8 == bits); + _allocStart = BitVector((() @trusted => cast(ulong[]) b)()); assert(_allocStart.rep.length * 64 == bits); _allocStart.rep[oldLength .. $] = ulong.max; return true; @@ -915,6 +2245,7 @@ struct BitmappedBlockWithInternalPointers( alias alignment = theAlignment; /// Ditto + pure nothrow @safe @nogc size_t goodAllocSize(size_t n) { return n.roundUpToMultipleOf(_heap.blockSize); @@ -925,13 +2256,13 @@ struct BitmappedBlockWithInternalPointers( { auto r = _heap.allocate(bytes); if (!r.ptr) return r; - immutable block = (r.ptr - _heap._payload.ptr) / _heap.blockSize; + immutable block = (() @trusted => (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); + () @trusted { _heap.deallocate(r); r = null; }(); return null; } assert(block < _allocStart.length); @@ -973,7 +2304,7 @@ struct BitmappedBlockWithInternalPointers( immutable newBlocks = (b.length + bytes + _heap.blockSize - 1) / _heap.blockSize; assert(newBlocks >= oldBlocks); - immutable block = (b.ptr - _heap._payload.ptr) / _heap.blockSize; + immutable block = (() @trusted => (b.ptr - _heap._payload.ptr) / _heap.blockSize)(); assert(_allocStart[block]); if (!ensureRoomForAllocStart(block + newBlocks) || !_heap.expand(b, bytes)) @@ -997,22 +2328,24 @@ struct BitmappedBlockWithInternalPointers( } /// Ditto + nothrow @safe @nogc Ternary resolveInternalPointer(const void* p, ref void[] result) { - if (p < _heap._payload.ptr - || p >= _heap._payload.ptr + _heap._payload.length) + if ((() @trusted => _heap._payload + && (p < &_heap._payload[0] + || p >= &_heap._payload[0] + _heap._payload.length))()) { return Ternary.no; } // Find block start - auto block = (p - _heap._payload.ptr) / _heap.blockSize; + auto block = (() @trusted => (p - &_heap._payload[0]) / _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)]; + result = (() @trusted => _heap._payload.ptr[cast(size_t) (_heap.blockSize * i) + .. cast(size_t) (_heap.blockSize * j)])(); return Ternary.yes; } @@ -1066,47 +2399,69 @@ struct BitmappedBlockWithInternalPointers( import std.typecons : Ternary; auto h = BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]); - auto b = h.allocate(123); + assert((() nothrow @safe @nogc => h.empty)() == Ternary.yes); + auto b = (() pure nothrow @safe @nogc => h.allocate(123))(); assert(b.length == 123); + assert((() nothrow @safe @nogc => h.empty)() == Ternary.no); void[] p; - Ternary r = h.resolveInternalPointer(b.ptr + 17, p); + void* offset = &b[0] + 17; + assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes); assert(p.ptr is b.ptr); assert(p.length >= b.length); - b = h.allocate(4096); + b = (() pure nothrow @safe @nogc => h.allocate(4096))(); - h.resolveInternalPointer(b.ptr, p); + offset = &b[0]; + assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes); assert(p is b); - h.resolveInternalPointer(b.ptr + 11, p); + offset = &b[0] + 11; + assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes); assert(p is b); void[] unchanged = p; - h.resolveInternalPointer(b.ptr - 40_970, p); + offset = &b[0] - 40_970; + assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.no); assert(p is unchanged); - assert(h.expand(b, 1)); + assert((() @safe => h.expand(b, 1))()); assert(b.length == 4097); - h.resolveInternalPointer(b.ptr + 4096, p); + offset = &b[0] + 4096; + assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes); assert(p.ptr is b.ptr); + + // Ensure deallocate inherits from parent + () nothrow @nogc { h.deallocate(b); }(); +} + +@system unittest +{ + auto h = BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]); + assert((() pure nothrow @safe @nogc => h.goodAllocSize(1))() == 4096); +} + +// Test instantiation with stateful allocators +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.experimental.allocator.building_blocks.region : Region; + auto r = Region!Mallocator(1024 * 1024); + auto h = BitmappedBlockWithInternalPointers!(4096, 8, Region!Mallocator*)(&r, 4096 * 1024); } /** -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. +Returns the number of most significant ones before a zero can be found in `x`. +If `x` contains no zeros (i.e. is equal to `ulong.max`), returns 64. */ +pure nothrow @safe @nogc private uint leadingOnes(ulong x) { - uint result = 0; - while (cast(long) x < 0) - { - ++result; - x <<= 1; - } - return result; + import core.bitop : bsr; + const x_ = ~x; + return x_ == 0 ? 64 : (63 - bsr(x_)); } -@system unittest +@safe unittest { assert(leadingOnes(0) == 0); assert(leadingOnes(~0UL) == 64); @@ -1118,8 +2473,9 @@ private uint leadingOnes(ulong x) } /** -Finds a run of contiguous ones in $(D x) of length at least $(D n). +Finds a run of contiguous ones in `x` of length at least `n`. */ +pure nothrow @safe @nogc private uint findContigOnes(ulong x, uint n) { while (n > 1) @@ -1131,7 +2487,7 @@ private uint findContigOnes(ulong x, uint n) return leadingOnes(~x); } -@system unittest +@safe unittest { assert(findContigOnes(0x0000_0000_0000_0300, 2) == 54); @@ -1148,6 +2504,7 @@ private uint findContigOnes(ulong x, uint n) /* Unconditionally sets the bits from lsb through msb in w to zero. */ +pure nothrow @safe @nogc private void setBits(ref ulong w, uint lsb, uint msb) { assert(lsb <= msb && msb < 64); @@ -1155,7 +2512,7 @@ private void setBits(ref ulong w, uint lsb, uint msb) w |= mask; } -@system unittest +@safe unittest { ulong w; w = 0; setBits(w, 0, 63); assert(w == ulong.max); @@ -1167,6 +2524,7 @@ private void setBits(ref ulong w, uint lsb, uint msb) /* Are bits from lsb through msb in w zero? If so, make then 1 and return the resulting w. Otherwise, just return 0. */ +pure nothrow @safe @nogc private bool setBitsIfZero(ref ulong w, uint lsb, uint msb) { assert(lsb <= msb && msb < 64); @@ -1177,6 +2535,7 @@ private bool setBitsIfZero(ref ulong w, uint lsb, uint msb) } // Assigns bits in w from lsb through msb to zero. +pure nothrow @safe @nogc private void resetBits(ref ulong w, uint lsb, uint msb) { assert(lsb <= msb && msb < 64); @@ -1191,12 +2550,15 @@ private struct BitVector { ulong[] _rep; - auto rep() { return _rep; } + auto rep(this _)() { return _rep; } + pure nothrow @safe @nogc this(ulong[] data) { _rep = data; } + pure nothrow @safe @nogc void opSliceAssign(bool b) { _rep[] = b ? ulong.max : 0; } + pure nothrow @safe @nogc void opSliceAssign(bool b, ulong x, ulong y) { assert(x <= y && y <= _rep.length * 64); @@ -1222,12 +2584,13 @@ private struct BitVector assert(i1 < i2); if (b) setBits(_rep[i1], 0, b1); else resetBits(_rep[i1], 0, b1); - _rep[i1 + 1 .. i2] = b; + _rep[i1 + 1 .. i2] = (b ? ulong.max : 0); if (b) setBits(_rep[i2], b2, 63); else resetBits(_rep[i2], b2, 63); } } + pure nothrow @safe @nogc bool opIndex(ulong x) { assert(x < length); @@ -1235,6 +2598,7 @@ private struct BitVector & (0x8000_0000_0000_0000UL >> (x % 64))) != 0; } + pure nothrow @safe @nogc void opIndexAssign(bool b, ulong x) { assert(x / 64 <= size_t.max); @@ -1244,6 +2608,7 @@ private struct BitVector else _rep[i] &= ~j; } + pure nothrow @safe @nogc ulong length() const { return _rep.length * 64; @@ -1252,6 +2617,7 @@ private struct BitVector /* Returns the index of the first 1 to the right of i (including i itself), or length if not found. */ + pure nothrow @safe @nogc ulong find1(ulong i) { assert(i < length); @@ -1279,6 +2645,7 @@ private struct BitVector /* Returns the index of the first 1 to the left of i (including i itself), or ulong.max if not found. */ + pure nothrow @safe @nogc ulong find1Backward(ulong i) { assert(i < length); @@ -1304,6 +2671,7 @@ private struct BitVector } /// Are all bits zero? + pure nothrow @safe @nogc bool allAre0() const { foreach (w; _rep) if (w) return false; @@ -1311,12 +2679,14 @@ private struct BitVector } /// Are all bits one? + pure nothrow @safe @nogc bool allAre1() const { foreach (w; _rep) if (w != ulong.max) return false; return true; } + pure nothrow @safe @nogc ulong findZeros(immutable size_t howMany, ulong start) { assert(start < length); @@ -1353,7 +2723,7 @@ private struct BitVector } } -@system unittest +@safe unittest { auto v = BitVector(new ulong[10]); assert(v.length == 640); diff --git a/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d b/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d index 64067dd..aab1f60 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d @@ -1,23 +1,27 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/bucketizer.d) +*/ module std.experimental.allocator.building_blocks.bucketizer; /** -A $(D Bucketizer) uses distinct allocators for handling allocations of sizes in +A `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 [min + 2 * step, min + 3 * step - 1]), `...`, $(D [max - step + 1, max]). -$(D Bucketizer) holds a fixed-size array of allocators and dispatches calls to +`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. +Allocations for sizes smaller than `min` or larger than `max` are illegal +for `Bucketizer`. To handle them separately, `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 common = std.experimental.allocator.common : roundUpToMultipleOf, + alignedAt; import std.traits : hasMember; import std.typecons : Ternary; @@ -31,20 +35,22 @@ struct Bucketizer(Allocator, size_t min, size_t max, size_t step) */ Allocator[(max + 1 - min) / step] buckets; + pure nothrow @safe @nogc private Allocator* allocatorFor(size_t n) { const i = (n - min) / step; - return i < buckets.length ? buckets.ptr + i : null; + return i < buckets.length ? &buckets[i] : null; } /** - The alignment offered is the same as $(D Allocator.alignment). + The alignment offered is the same as `Allocator.alignment`. */ enum uint alignment = Allocator.alignment; /** - Rounds up to the maximum size of the bucket in which $(D bytes) falls. + Rounds up to the maximum size of the bucket in which `bytes` falls. */ + pure nothrow @safe @nogc size_t goodAllocSize(size_t bytes) const { // round up bytes such that bytes - min + 1 is a multiple of step @@ -54,7 +60,7 @@ struct Bucketizer(Allocator, size_t min, size_t max, size_t step) } /** - Directs the call to either one of the $(D buckets) allocators. + Directs the call to either one of the `buckets` allocators. */ void[] allocate(size_t bytes) { @@ -68,42 +74,56 @@ struct Bucketizer(Allocator, size_t min, size_t max, size_t step) return null; } + static if (hasMember!(Allocator, "allocateZeroed")) + package(std) void[] allocateZeroed()(size_t bytes) + { + if (!bytes) return null; + if (auto a = allocatorFor(bytes)) + { + const actual = goodAllocSize(bytes); + auto result = a.allocateZeroed(actual); + return result.ptr ? result.ptr[0 .. bytes] : null; + } + return null; + } + /** - Directs the call to either one of the $(D buckets) allocators. Defined only + Allocates the requested `bytes` of memory with specified `alignment`. + Directs the call to either one of the `buckets` allocators. Defined only if `Allocator` defines `alignedAllocate`. */ static if (hasMember!(Allocator, "alignedAllocate")) - void[] alignedAllocate(size_t bytes, uint a) + void[] alignedAllocate(size_t bytes, uint alignment) { if (!bytes) return null; - if (auto a = allocatorFor(b.length)) + if (auto a = allocatorFor(bytes)) { const actual = goodAllocSize(bytes); - auto result = a.alignedAllocate(actual); - return result.ptr ? result.ptr[0 .. bytes] : null; + auto result = a.alignedAllocate(actual, alignment); + return result !is null ? (() @trusted => (&result[0])[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 + if both `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; + if (!b || delta == 0) 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]; + b = (() @trusted => 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 * + `b.length` and `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. */ @@ -115,9 +135,9 @@ struct Bucketizer(Allocator, size_t min, size_t max, size_t step) b = null; return true; } - if (size >= b.length) + if (size >= b.length && expand(b, size - b.length)) { - return expand(b, size - b.length); + return true; } assert(b.length >= min && b.length <= max); if (goodAllocSize(size) == goodAllocSize(b.length)) @@ -142,18 +162,18 @@ struct Bucketizer(Allocator, size_t min, size_t max, size_t step) b = null; return true; } - if (size >= b.length) + if (size >= b.length && b.ptr.alignedAt(a) && expand(b, size - b.length)) { - return expand(b, size - b.length); + return true; } assert(b.length >= min && b.length <= max); - if (goodAllocSize(size) == goodAllocSize(b.length)) + if (goodAllocSize(size) == goodAllocSize(b.length) && b.ptr.alignedAt(a)) { b = b.ptr[0 .. size]; return true; } // Move cross buckets - return .alignedReallocate(this, b, size, a); + return common.alignedReallocate(this, b, size, a); } /** @@ -172,7 +192,7 @@ struct Bucketizer(Allocator, size_t min, size_t max, size_t step) } /** - This method is only defined if $(D Allocator) defines $(D deallocate). + This method is only defined if `Allocator` defines `deallocate`. */ static if (hasMember!(Allocator, "deallocate")) bool deallocate(void[] b) @@ -236,6 +256,93 @@ struct Bucketizer(Allocator, size_t min, size_t max, size_t step) auto b = a.allocate(400); assert(b.length == 400); assert(a.owns(b) == Ternary.yes); - void[] p; a.deallocate(b); } + +@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)), Mallocator), + 0, unbounded), + 65, 512, 64) a; + + assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() == 128); + + auto b = a.allocate(100); + assert(b.length == 100); + // Make reallocate use extend + assert((() nothrow @nogc => a.reallocate(b, 101))()); + assert(b.length == 101); + // Move cross buckets + assert((() nothrow @nogc => a.reallocate(b, 200))()); + assert(b.length == 200); + // Free through realloc + assert((() nothrow @nogc => a.reallocate(b, 0))()); + assert(b is null); + // Ensure deallocate inherits from parent allocators + assert((() nothrow @nogc => a.deallocate(b))()); + assert((() nothrow @nogc => a.deallocateAll())()); +} + +// Test alignedAllocate +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.experimental.allocator.gc_allocator : GCAllocator; + + Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 65, 512, 64) a; + foreach (ref bucket; a.buckets) + { + bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]); + } + + auto b = a.alignedAllocate(100, 16); + assert(b.length == 100); + assert(a.alignedAllocate(42, 16) is null); + assert(a.alignedAllocate(0, 16) is null); + assert((() pure nothrow @safe @nogc => a.expand(b, 0))()); + assert(b.length == 100); + assert((() pure nothrow @safe @nogc => a.expand(b, 28))()); + assert(b.length == 128); + assert((() pure nothrow @safe @nogc => !a.expand(b, 1))()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.experimental.allocator.gc_allocator : GCAllocator; + + Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 1, 512, 64) a; + foreach (ref bucket; a.buckets) + { + bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]); + } + + auto b = a.alignedAllocate(1, 4); + assert(b.length == 1); + // Make reallocate use extend + assert(a.alignedReallocate(b, 11, 4)); + assert(b.length == 11); + // Make reallocate use use realloc because of alignment change + assert(a.alignedReallocate(b, 21, 16)); + assert(b.length == 21); + // Make reallocate use extend + assert(a.alignedReallocate(b, 22, 16)); + assert(b.length == 22); + // Move cross buckets + assert(a.alignedReallocate(b, 101, 16)); + assert(b.length == 101); + // Free through realloc + assert(a.alignedReallocate(b, 0, 16)); + assert(b is null); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d index ca7961b..b413d73 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d @@ -1,22 +1,25 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/fallback_allocator.d) +*/ 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 +`FallbackAllocator` is the allocator equivalent of an "or" operator in +algebra. An allocation request is first attempted with the `Primary` +allocator. If that returns `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 +In order to work, `FallbackAllocator` requires that `Primary` defines the +`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 +`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). +up by the `GCAllocator`. */ struct FallbackAllocator(Primary, Fallback) { @@ -24,6 +27,10 @@ struct FallbackAllocator(Primary, Fallback) import std.traits : hasMember; import std.typecons : Ternary; + // Need both allocators to be stateless + // This is to avoid using default initialized stateful allocators + static if (!stateSize!Primary && !stateSize!Fallback) + version (StdUnittest) @system unittest { testAllocator!(() => FallbackAllocator()); @@ -38,7 +45,7 @@ struct FallbackAllocator(Primary, Fallback) else alias fallback = Fallback.instance; /** - If both $(D Primary) and $(D Fallback) are stateless, $(D FallbackAllocator) + If both `Primary` and `Fallback` are stateless, `FallbackAllocator` defines a static instance called `instance`. */ static if (!stateSize!Primary && !stateSize!Fallback) @@ -61,8 +68,40 @@ struct FallbackAllocator(Primary, Fallback) return result.length == s ? result : fallback.allocate(s); } + static if (hasMember!(Primary, "allocateZeroed") + || (hasMember!(Fallback, "allocateZeroed"))) + package(std) void[] allocateZeroed()(size_t s) + { + // Try to allocate with primary. + static if (hasMember!(Primary, "allocateZeroed")) + { + void[] result = primary.allocateZeroed(s); + if (result.length == s) return result; + } + else + { + void[] result = primary.allocate(s); + if (result.length == s) + { + (() @trusted => (cast(ubyte[]) result)[] = 0)(); + return result; + } + } + // Allocate with fallback. + static if (hasMember!(Fallback, "allocateZeroed")) + { + return fallback.allocateZeroed(s); + } + else + { + result = fallback.allocate(s); + (() @trusted => (cast(ubyte[]) result)[] = 0)(); // OK even if result is null. + return result; + } + } + /** - $(D FallbackAllocator) offers $(D alignedAllocate) iff at least one of the + `FallbackAllocator` offers `alignedAllocate` iff at least one of the allocators also offers it. It attempts to allocate using either or both. */ static if (hasMember!(Primary, "alignedAllocate") @@ -84,12 +123,12 @@ struct FallbackAllocator(Primary, Fallback) /** - $(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. + `expand` is defined if and only if at least one of the allocators + defines `expand`. It works as follows. If `primary.owns(b)`, then the + request is forwarded to `primary.expand` if it is defined, or fails + (returning `false`) otherwise. If `primary` does not own `b`, then + the request is forwarded to `fallback.expand` if it is defined, or fails + (returning `false`) otherwise. */ static if (hasMember!(Primary, "owns") @@ -113,13 +152,13 @@ struct FallbackAllocator(Primary, Fallback) /** - $(D reallocate) works as follows. If $(D primary.owns(b)), then $(D + `reallocate` works as follows. If `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). + made to move the allocation from `primary` to `fallback`. - If $(D primary) does not own $(D b), then $(D fallback.reallocate(b, + If `primary` does not own `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). + allocation from `fallback` to `primary`. */ static if (hasMember!(Primary, "owns")) @@ -192,7 +231,7 @@ struct FallbackAllocator(Primary, Fallback) } /** - $(D owns) is defined if and only if both allocators define $(D owns). + `owns` is defined if and only if both allocators define `owns`. Returns $(D primary.owns(b) | fallback.owns(b)). */ static if (hasMember!(Primary, "owns") && hasMember!(Fallback, "owns")) @@ -202,7 +241,7 @@ struct FallbackAllocator(Primary, Fallback) } /** - $(D resolveInternalPointer) is defined if and only if both allocators + `resolveInternalPointer` is defined if and only if both allocators define it. */ static if (hasMember!(Primary, "resolveInternalPointer") @@ -214,11 +253,11 @@ struct FallbackAllocator(Primary, Fallback) } /** - $(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 + `deallocate` is defined if and only if at least one of the allocators + define `deallocate`. It works as follows. If `primary.owns(b)`, + then the request is forwarded to `primary.deallocate` if it is defined, + or is a no-op otherwise. If `primary` does not own `b`, then the + request is forwarded to `fallback.deallocate` if it is defined, or is a no-op otherwise. */ static if (hasMember!(Primary, "owns") && @@ -243,11 +282,12 @@ struct FallbackAllocator(Primary, Fallback) } /** - $(D empty) is defined if both allocators also define it. + `empty` is defined if both allocators also define it. Returns: $(D primary.empty & fallback.empty) */ - static if (hasMember!(Primary, "empty") && hasMember!(Fallback, "empty")) + static if (hasMember!(Primary, "empty") + && hasMember!(Fallback, "empty")) Ternary empty() { return primary.empty & fallback.empty; @@ -264,12 +304,89 @@ struct FallbackAllocator(Primary, Fallback) // 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 + assert((() pure nothrow @safe @nogc => a.primary.owns(b1))() == Ternary.yes); + assert((() nothrow => a.reallocate(b1, 2048))()); + assert(b1.length == 2048, text(b1.length)); + assert((() pure nothrow @safe @nogc => a.primary.owns(b1))() == Ternary.yes); + // This large allocation will go to the GCAllocator auto b2 = a.allocate(1024 * 1024); - assert(a.primary.owns(b2) == Ternary.no); - a.deallocate(b1); - a.deallocate(b2); + assert((() pure nothrow @safe @nogc => a.primary.owns(b2))() == Ternary.no); + // Ensure deallocate inherits from parent allocators + () nothrow @nogc { a.deallocate(b1); }(); + () nothrow @nogc { a.deallocate(b2); }(); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlockWithInternalPointers; + import std.typecons : Ternary; + + alias A = + FallbackAllocator!( + BitmappedBlockWithInternalPointers!(4096), + BitmappedBlockWithInternalPointers!(4096) + ); + + A a = A( + BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]), + BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]) + ); + + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); + auto b = a.allocate(201); + assert(b.length == 201); + assert(a.reallocate(b, 202)); + assert(b.length == 202); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.typecons : Ternary; + + auto a = FallbackAllocator!(Region!(), Region!())( + Region!()(new ubyte[4096 * 1024]), + Region!()(new ubyte[4096 * 1024])); + + auto b = a.alignedAllocate(42, 8); + assert(b.length == 42); + assert((() nothrow @nogc => a.alignedReallocate(b, 100, 8))()); + assert(b.length == 100); +} + +version (StdUnittest) +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlockWithInternalPointers; + import std.typecons : Ternary; + + alias A = + FallbackAllocator!( + BitmappedBlockWithInternalPointers!(4096), + BitmappedBlockWithInternalPointers!(4096) + ); + + // Run testAllocator here since both allocators stateful + testAllocator!( + () => A( + BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]), + BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]) + ) + ); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + + alias a = FallbackAllocator!(Mallocator, Mallocator).instance; + + auto b = a.allocate(42); + assert(b.length == 42); + assert((() nothrow @nogc => a.reallocate(b, 100))()); + assert(b.length == 100); } /* @@ -319,8 +436,8 @@ private auto ref forward(alias arg)() /** 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` instance. To initialize with allocators that don't have +state, use their `it` static member. */ FallbackAllocator!(Primary, Fallback) fallbackAllocator(Primary, Fallback)(auto ref Primary p, auto ref Fallback f) @@ -353,3 +470,51 @@ fallbackAllocator(Primary, Fallback)(auto ref Primary p, auto ref Fallback f) assert(b2.length == 10); assert(a.primary.owns(b2) == Ternary.no); } + +version (StdUnittest) +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.gc_allocator : GCAllocator; + testAllocator!(() => fallbackAllocator(Region!GCAllocator(1024), GCAllocator.instance)); +} + +// Ensure `owns` inherits function attributes +@system unittest +{ + import std.experimental.allocator.building_blocks.region : InSituRegion; + import std.typecons : Ternary; + + FallbackAllocator!(InSituRegion!16_384, InSituRegion!16_384) a; + auto buff = a.allocate(42); + assert((() pure nothrow @safe @nogc => a.owns(buff))() == Ternary.yes); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + + auto a = fallbackAllocator(GCAllocator.instance, GCAllocator.instance); + auto b = a.allocate(1020); + assert(b.length == 1020); + + void[] p; + assert((() nothrow @safe @nogc => a.resolveInternalPointer(null, p))() == Ternary.no); + assert((() nothrow @safe @nogc => a.resolveInternalPointer(&b[0], p))() == Ternary.yes); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.typecons : Ternary; + + alias A = FallbackAllocator!(Region!(), Region!()); + auto a = A(Region!()(new ubyte[16_384]), Region!()(new ubyte[16_384])); + + auto b = a.allocate(42); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + assert((() nothrow @safe @nogc => a.expand(b, 58))()); + assert(b.length == 100); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/free_list.d b/libphobos/src/std/experimental/allocator/building_blocks/free_list.d index 8860806..7055d66 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/free_list.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/free_list.d @@ -1,4 +1,7 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/free_list.d) +*/ module std.experimental.allocator.building_blocks.free_list; import std.experimental.allocator.common; @@ -7,21 +10,23 @@ 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 +another allocator. Allocation requests between `min` and `max` bytes are +rounded up to `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. +from the free list are fast. If `adaptive` is set to `Yes.adaptive`, +the free list gradually reduces its size if allocations tend to use the parent +allocator much more than the lists' available nodes. 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 +owning allocator on top of the free list allocator (such as `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). +The following methods are defined if `ParentAllocator` defines them, and +forward to it: `expand`, `owns`, `reallocate`. */ struct FreeList(ParentAllocator, @@ -32,6 +37,7 @@ struct FreeList(ParentAllocator, import std.exception : enforce; import std.traits : hasMember; import std.typecons : Ternary; + import std.experimental.allocator.building_blocks.null_allocator : NullAllocator; static assert(minSize != unbounded, "Use minSize = 0 for no low bound."); static assert(maxSize >= (void*).sizeof, @@ -47,7 +53,7 @@ struct FreeList(ParentAllocator, /** Returns the smallest allocation size eligible for allocation from the freelist. (If $(D minSize != chooseAtRuntime), this is simply an alias - for $(D minSize).) + for `minSize`.) */ @property size_t min() const { @@ -55,15 +61,15 @@ struct FreeList(ParentAllocator, return _min; } /** - If $(D FreeList) has been instantiated with $(D minSize == - chooseAtRuntime), then the $(D min) property is writable. Setting it + If `FreeList` has been instantiated with $(D minSize == + chooseAtRuntime), then the `min` property is writable. Setting it must precede any allocation. Params: - low = new value for $(D min) + low = new value for `min` Precondition: $(D low <= max), or $(D maxSize == chooseAtRuntime) and - $(D max) has not yet been initialized. Also, no allocation has been + `max` has not yet been initialized. Also, no allocation has been yet done with this allocator. Postcondition: $(D min == low) @@ -85,24 +91,24 @@ struct FreeList(ParentAllocator, /** 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 + for `maxSize`.) All allocation requests for sizes greater than or + equal to `min` and less than or equal to `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). + allocated size assumed to be `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 + If `FreeList` has been instantiated with $(D maxSize == + chooseAtRuntime), then the `max` property is writable. Setting it must precede any allocation. Params: - high = new value for $(D max) + high = new value for `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. + `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) */ @@ -114,8 +120,7 @@ struct FreeList(ParentAllocator, _max = high; } - /// - @safe unittest + @system unittest { import std.experimental.allocator.common : chooseAtRuntime; import std.experimental.allocator.mallocator : Mallocator; @@ -209,7 +214,7 @@ struct FreeList(ParentAllocator, // state /** - The parent allocator. Depending on whether $(D ParentAllocator) holds state + The parent allocator. Depending on whether `ParentAllocator` holds state or not, this is a member variable or an alias for `ParentAllocator.instance`. */ @@ -225,12 +230,12 @@ struct FreeList(ParentAllocator, 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. + If $(D maxSize == unbounded), returns `parent.goodAllocSize(bytes)`. + Otherwise, returns `max` for sizes in the interval $(D [min, max]), and + `parent.goodAllocSize(bytes)` otherwise. Precondition: - If set at runtime, $(D min) and/or $(D max) must be initialized + If set at runtime, `min` and/or `max` must be initialized appropriately. Postcondition: @@ -252,14 +257,17 @@ struct FreeList(ParentAllocator, return parent.goodAllocSize(bytes); } - private void[] allocateEligible(size_t bytes) + private void[] allocateEligible(string fillMode)(size_t bytes) + if (fillMode == "void" || fillMode == "zero") { + enum bool isFillZero = fillMode == "zero"; assert(bytes); if (root) { // faster auto result = (cast(ubyte*) root)[0 .. bytes]; root = root.next; + static if (isFillZero) result[0 .. bytes] = 0; return result; } // slower @@ -272,7 +280,10 @@ struct FreeList(ParentAllocator, alias toAllocate = bytes; } assert(toAllocate == max || max == unbounded); - auto result = parent.allocate(toAllocate); + static if (isFillZero) + auto result = parent.allocateZeroed(toAllocate); + else + auto result = parent.allocate(toAllocate); static if (hasTolerance) { if (result) result = result.ptr[0 .. bytes]; @@ -287,20 +298,20 @@ struct FreeList(ParentAllocator, /** 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 + If `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. + of `max` bytes is allocated, truncated to `n` bytes, and returned. Params: n = number of bytes to allocate Returns: - The allocated block, or $(D null). + The allocated block, or `null`. Precondition: - If set at runtime, $(D min) and/or $(D max) must be initialized + If set at runtime, `min` and/or `max` must be initialized appropriately. Postcondition: $(D result.length == bytes || result is null) @@ -312,7 +323,7 @@ struct FreeList(ParentAllocator, // fast path if (freeListEligible(n)) { - return allocateEligible(n); + return allocateEligible!"void"(n); } // slower static if (adaptive == Yes.adaptive) @@ -322,23 +333,41 @@ struct FreeList(ParentAllocator, return parent.allocate(n); } + static if (hasMember!(ParentAllocator, "allocateZeroed")) + package(std) void[] allocateZeroed()(size_t n) + { + static if (adaptive == Yes.adaptive) ++accumSamples; + assert(n < size_t.max / 2); + // fast path + if (freeListEligible(n)) + { + return allocateEligible!"zero"(n); + } + // slower + static if (adaptive == Yes.adaptive) + { + updateStats; + } + return parent.allocateZeroed(n); + } + // Forwarding methods mixin(forwardToMember("parent", "expand", "owns", "reallocate")); /** - If $(D block.length) is within $(D [min, max]) or if the free list is + If `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. + parent.deallocate) if `Parent.deallocate` is defined. Params: block = Block to deallocate. Precondition: - If set at runtime, $(D min) and/or $(D max) must be initialized + If set at runtime, `min` and/or `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 + freelist, and no dynamic changing of `min` or `max` is allowed to occur between allocation and deallocation. */ bool deallocate(void[] block) @@ -362,7 +391,7 @@ struct FreeList(ParentAllocator, } /** - Defined only if $(D ParentAllocator) defines $(D deallocateAll). If so, + Defined only if `ParentAllocator` defines `deallocateAll`. If so, forwards to it and resets the freelist. */ static if (hasMember!(ParentAllocator, "deallocateAll")) @@ -374,8 +403,8 @@ struct FreeList(ParentAllocator, /** Nonstandard function that minimizes the memory usage of the freelist by - freeing each element in turn. Defined only if $(D ParentAllocator) defines - $(D deallocate). + freeing each element in turn. Defined only if `ParentAllocator` defines + `deallocate`. $(D FreeList!(0, unbounded)) does not have this function. */ static if (hasMember!(ParentAllocator, "deallocate") && !unchecked) void minimize() @@ -387,6 +416,48 @@ struct FreeList(ParentAllocator, parent.deallocate(nuke); } } + + /** + If `ParentAllocator` defines `deallocate`, the list frees all nodes + on destruction. $(D FreeList!(0, unbounded)) does not deallocate the memory + on destruction. + */ + static if (!is(ParentAllocator == NullAllocator) && + hasMember!(ParentAllocator, "deallocate") && !unchecked) + ~this() + { + minimize(); + } +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.experimental.allocator.building_blocks.stats_collector + : StatsCollector, Options; + + struct StatsCollectorWrapper { + ~this() + { + // buf2 should still be around and buf1 deallocated + assert(parent.numDeallocate == 1); + assert(parent.bytesUsed == 16); + } + static StatsCollector!(Mallocator, Options.all) parent; + alias parent this; + } + + FreeList!(StatsCollectorWrapper, 16, 16) fl; + auto buf1 = fl.allocate(16); + auto buf2 = fl.allocate(16); + assert(fl.parent.bytesUsed == 32); + + // After this, the list has 1 node, so no actual deallocation by Mallocator + fl.deallocate(buf1); + assert(fl.parent.bytesUsed == 32); + + // Destruction should only deallocate the node + destroy(fl); } @system unittest @@ -397,31 +468,55 @@ struct FreeList(ParentAllocator, auto b1 = fl.allocate(7); fl.allocate(8); assert(fl.root is null); - fl.deallocate(b1); + // Ensure deallocate inherits from parent + () nothrow @nogc { fl.deallocate(b1); }(); assert(fl.root !is null); fl.allocate(8); assert(fl.root is null); } +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + FreeList!(GCAllocator, 0, 16) fl; + // Not @nogc because of std.conv.text + assert((() nothrow @safe /*@nogc*/ => fl.goodAllocSize(1))() == 16); +} + +// Test that deallocateAll infers from parent +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + + auto fl = FreeList!(Region!(), 0, 16)(Region!()(new ubyte[1024 * 64])); + auto b = fl.allocate(42); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => fl.expand(b, 48))()); + assert(b.length == 90); + assert((() nothrow @nogc => fl.reallocate(b, 100))()); + assert(b.length == 100); + assert((() nothrow @nogc => fl.deallocateAll())()); +} + /** 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 +assumed to have been allocated with `ParentAllocator`, and is released in +`ContiguousFreeList`'s destructor (unless `ParentAllocator` is $(D NullAllocator)). -$(D ContiguousFreeList) has most advantages of $(D FreeList) but fewer +`ContiguousFreeList` has most advantages of `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 +The disadvantages of `ContiguousFreeList` over `FreeList` are its pay +upfront model (as opposed to `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). +available for `ContiguousFreeList`. */ struct ContiguousFreeList(ParentAllocator, size_t minSize, size_t maxSize = minSize) @@ -441,7 +536,7 @@ struct ContiguousFreeList(ParentAllocator, // state /** - The parent allocator. Depending on whether $(D ParentAllocator) holds state + The parent allocator. Depending on whether `ParentAllocator` holds state or not, this is a member variable or an alias for `ParentAllocator.instance`. */ @@ -481,8 +576,8 @@ struct ContiguousFreeList(ParentAllocator, 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) + buffer = Buffer to structure as a free list. If `ParentAllocator` is not + `NullAllocator`, the buffer is assumed to be allocated by `parent` and will be freed in the destructor. parent = Parent allocator. For construction from stateless allocators, use their `instance` static member. @@ -493,8 +588,8 @@ struct ContiguousFreeList(ParentAllocator, == 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). + condition is met and no `min` parameter is present, `min` is + initialized with `max`. */ static if (!stateSize!ParentAllocator) this(ubyte[] buffer) @@ -573,11 +668,11 @@ struct ContiguousFreeList(ParentAllocator, } /** - If $(D n) is eligible for freelisting, returns $(D max). Otherwise, returns - $(D parent.goodAllocSize(n)). + If `n` is eligible for freelisting, returns `max`. Otherwise, returns + `parent.goodAllocSize(n)`. Precondition: - If set at runtime, $(D min) and/or $(D max) must be initialized + If set at runtime, `min` and/or `max` must be initialized appropriately. Postcondition: @@ -590,7 +685,7 @@ struct ContiguousFreeList(ParentAllocator, } /** - Allocate $(D n) bytes of memory. If $(D n) is eligible for freelist and the + Allocate `n` bytes of memory. If `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. */ @@ -612,9 +707,13 @@ struct ContiguousFreeList(ParentAllocator, belongs to this allocator. */ static if (hasMember!(SParent, "owns") || unchecked) + // Ternary owns(const void[] b) const ? Ternary owns(void[] b) { - if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length) + if ((() @trusted => support && b + && (&support[0] <= &b[0]) + && (&b[0] < &support[0] + support.length) + )()) return Ternary.yes; static if (unchecked) return Ternary.no; @@ -623,10 +722,10 @@ struct ContiguousFreeList(ParentAllocator, } /** - Deallocates $(D b). If it's of eligible size, it's put on the free list. - Otherwise, it's returned to $(D parent). + Deallocates `b`. If it's of eligible size, it's put on the free list. + Otherwise, it's returned to `parent`. - Precondition: $(D b) has been allocated with this allocator, or is $(D + Precondition: `b` has been allocated with this allocator, or is $(D null). */ bool deallocate(void[] b) @@ -634,8 +733,7 @@ struct ContiguousFreeList(ParentAllocator, 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(fl.freeListEligible(b.length)); assert(allocated); --allocated; // Put manually in the freelist @@ -692,21 +790,23 @@ struct ContiguousFreeList(ParentAllocator, alias A = ContiguousFreeList!(NullAllocator, 0, 64); auto a = A(new ubyte[1024]); - assert(a.empty == Ternary.yes); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); - assert(a.goodAllocSize(15) == 64); - assert(a.goodAllocSize(65) == NullAllocator.instance.goodAllocSize(65)); + assert((() pure nothrow @safe @nogc => a.goodAllocSize(15))() == 64); + assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() + == (() nothrow @safe @nogc => NullAllocator.instance.goodAllocSize(65))()); auto b = a.allocate(100); - assert(a.empty == Ternary.yes); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); assert(b.length == 0); - a.deallocate(b); + // Ensure deallocate inherits from parent + () nothrow @nogc { a.deallocate(b); }(); b = a.allocate(64); - assert(a.empty == Ternary.no); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); assert(b.length == 64); - assert(a.owns(b) == Ternary.yes); - assert(a.owns(null) == Ternary.no); - a.deallocate(b); + assert((() nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + assert((() nothrow @safe @nogc => a.owns(null))() == Ternary.no); + () nothrow @nogc { a.deallocate(b); }(); } @system unittest @@ -717,23 +817,29 @@ struct ContiguousFreeList(ParentAllocator, alias A = ContiguousFreeList!(Region!GCAllocator, 0, 64); auto a = A(Region!GCAllocator(1024 * 4), 1024); - assert(a.empty == Ternary.yes); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); - assert(a.goodAllocSize(15) == 64); - assert(a.goodAllocSize(65) == a.parent.goodAllocSize(65)); + assert((() pure nothrow @safe @nogc => a.goodAllocSize(15))() == 64); + assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() + == (() pure nothrow @safe @nogc => a.parent.goodAllocSize(65))()); auto b = a.allocate(100); - assert(a.empty == Ternary.no); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); assert(a.allocated == 0); assert(b.length == 100); - a.deallocate(b); - assert(a.empty == Ternary.yes); + // Ensure deallocate inherits from parent + assert((() nothrow @nogc => a.deallocate(b))()); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); b = a.allocate(64); - assert(a.empty == Ternary.no); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); assert(b.length == 64); - assert(a.owns(b) == Ternary.yes); - assert(a.owns(null) == Ternary.no); - a.deallocate(b); + assert(a.reallocate(b, 100)); + assert(b.length == 100); + assert((() nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + assert((() nothrow @safe @nogc => a.owns(null))() == Ternary.no); + // Test deallocate infers from parent + assert((() nothrow @nogc => a.deallocate(b))()); + assert((() nothrow @nogc => a.deallocateAll())()); } @system unittest @@ -747,10 +853,10 @@ struct ContiguousFreeList(ParentAllocator, /** FreeList shared across threads. Allocation and deallocation are lock-free. The -parameters have the same semantics as for $(D FreeList). +parameters have the same semantics as for `FreeList`. -$(D expand) is defined to forward to $(D ParentAllocator.expand) -(it must be also $(D shared)). +`expand` is defined to forward to `ParentAllocator.expand` +(it must be also `shared`). */ struct SharedFreeList(ParentAllocator, size_t minSize, size_t maxSize = minSize, size_t approxMaxNodes = unbounded) @@ -759,6 +865,11 @@ struct SharedFreeList(ParentAllocator, import std.exception : enforce; import std.traits : hasMember; + static if (hasMember!(ParentAllocator, "owns")) + { + import std.typecons : Ternary; + } + static assert(approxMaxNodes, "approxMaxNodes must not be null."); static assert(minSize != unbounded, "Use minSize = 0 for no low bound."); static assert(maxSize >= (void*).sizeof, @@ -884,7 +995,7 @@ struct SharedFreeList(ParentAllocator, /** 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). + the primitives have the same semantics as those of `FreeList`. */ @property size_t min(); /// Ditto @@ -895,20 +1006,6 @@ struct SharedFreeList(ParentAllocator, @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. @@ -916,25 +1013,10 @@ struct SharedFreeList(ParentAllocator, @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 + The parent allocator. Depending on whether `ParentAllocator` holds state or not, this is a member variable or an alias for `ParentAllocator.instance`. */ @@ -961,7 +1043,7 @@ struct SharedFreeList(ParentAllocator, /// Ditto static if (hasMember!(ParentAllocator, "owns")) - Ternary owns(void[] b) shared const + Ternary owns(const void[] b) shared const { return parent.owns(b); } @@ -1050,8 +1132,8 @@ struct SharedFreeList(ParentAllocator, /** Nonstandard function that minimizes the memory usage of the freelist by - freeing each element in turn. Defined only if $(D ParentAllocator) defines - $(D deallocate). + freeing each element in turn. Defined only if `ParentAllocator` defines + `deallocate`. */ static if (hasMember!(ParentAllocator, "deallocate") && !unchecked) void minimize() shared @@ -1071,6 +1153,34 @@ struct SharedFreeList(ParentAllocator, } } +/// +@safe unittest +{ + import std.experimental.allocator.common : chooseAtRuntime; + import std.experimental.allocator.mallocator : Mallocator; + + shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; + a.setBounds(64, 128); + assert(a.max == 128); + assert(a.min == 64); +} + +/// +@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); +} + @system unittest { import core.thread : ThreadGroup; @@ -1080,10 +1190,11 @@ struct SharedFreeList(ParentAllocator, static shared SharedFreeList!(Mallocator, 64, 128, 10) a; - assert(a.goodAllocSize(1) == platformAlignment); + assert((() nothrow @safe @nogc => a.goodAllocSize(1))() == platformAlignment); auto b = a.allocate(96); - a.deallocate(b); + // Ensure deallocate inherits from parent + () nothrow @nogc { a.deallocate(b); }(); void fun() { @@ -1091,7 +1202,7 @@ struct SharedFreeList(ParentAllocator, b[] = cast(size_t) &b; assert(b.equal(repeat(cast(size_t) &b, b.length))); - a.deallocate(b); + () nothrow @nogc { a.deallocate(b); }(); } auto tg = new ThreadGroup; @@ -1108,10 +1219,11 @@ struct SharedFreeList(ParentAllocator, import std.experimental.allocator.mallocator : Mallocator; static shared SharedFreeList!(Mallocator, 64, 128, 10) a; auto b = a.allocate(100); - a.deallocate(b); + // Ensure deallocate inherits from parent + () nothrow @nogc { a.deallocate(b); }(); assert(a.nodes == 1); b = []; - a.deallocateAll(); + assert((() nothrow @nogc => a.deallocateAll())()); assert(a.nodes == 0); } @@ -1121,12 +1233,13 @@ struct SharedFreeList(ParentAllocator, static shared SharedFreeList!(Mallocator, 64, 128, 10) a; auto b = a.allocate(100); auto c = a.allocate(100); - a.deallocate(c); + // Ensure deallocate inherits from parent + () nothrow @nogc { a.deallocate(c); }(); assert(a.nodes == 1); c = []; a.minimize(); assert(a.nodes == 0); - a.deallocate(b); + () nothrow @nogc { a.deallocate(b); }(); assert(a.nodes == 1); b = []; a.minimize(); @@ -1140,8 +1253,9 @@ struct SharedFreeList(ParentAllocator, auto b = a.allocate(100); auto c = a.allocate(100); assert(a.nodes == 0); - a.deallocate(b); - a.deallocate(c); + // Ensure deallocate inherits from parent + () nothrow @nogc { a.deallocate(b); }(); + () nothrow @nogc { a.deallocate(c); }(); assert(a.nodes == 2); b = []; c = []; @@ -1153,18 +1267,19 @@ struct SharedFreeList(ParentAllocator, { import std.experimental.allocator.mallocator : Mallocator; shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; - scope(exit) a.deallocateAll(); + scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); auto c = a.allocate(64); - assert(a.reallocate(c, 96)); + assert((() nothrow @nogc => a.reallocate(c, 96))()); assert(c.length == 96); - a.deallocate(c); + // Ensure deallocate inherits from parent + () nothrow @nogc { a.deallocate(c); }(); } @system unittest { import std.experimental.allocator.mallocator : Mallocator; shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime, chooseAtRuntime) a; - scope(exit) a.deallocateAll; + scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); a.allocate(64); } @@ -1172,7 +1287,7 @@ struct SharedFreeList(ParentAllocator, { import std.experimental.allocator.mallocator : Mallocator; shared SharedFreeList!(Mallocator, 30, 40) a; - scope(exit) a.deallocateAll; + scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); a.allocate(64); } @@ -1180,7 +1295,7 @@ struct SharedFreeList(ParentAllocator, { import std.experimental.allocator.mallocator : Mallocator; shared SharedFreeList!(Mallocator, 30, 40, chooseAtRuntime) a; - scope(exit) a.deallocateAll; + scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); a.allocate(64); } @@ -1189,7 +1304,7 @@ struct SharedFreeList(ParentAllocator, // Pull request #5556 import std.experimental.allocator.mallocator : Mallocator; shared SharedFreeList!(Mallocator, 0, chooseAtRuntime) a; - scope(exit) a.deallocateAll; + scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); a.max = 64; a.allocate(64); } @@ -1199,7 +1314,7 @@ struct SharedFreeList(ParentAllocator, // Pull request #5556 import std.experimental.allocator.mallocator : Mallocator; shared SharedFreeList!(Mallocator, chooseAtRuntime, 64) a; - scope(exit) a.deallocateAll; + scope(exit) assert((() nothrow @nogc => 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 index 6b64659..bd4bb95 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d @@ -1,4 +1,7 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/_free_tree.d) +*/ module std.experimental.allocator.building_blocks.free_tree; import std.experimental.allocator.common; @@ -13,10 +16,10 @@ 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: +Common uses of `FreeTree` include: $(UL -$(LI Adding $(D deallocate) capability to an allocator that lacks it (such as simple regions).) +$(LI Adding `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.) @@ -24,14 +27,14 @@ 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 +free tree is expected to be $(BIGOH log n) where `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. +ParentAllocator). If at this point `ParentAllocator` also fails to allocate, +`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 @@ -40,12 +43,12 @@ 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), +`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 +be efficiently allocated, the `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). +The following methods are defined if `ParentAllocator` defines them, and forward to it: `allocateAll`, `expand`, `owns`, `reallocate`. */ struct FreeTree(ParentAllocator) { @@ -240,17 +243,17 @@ struct FreeTree(ParentAllocator) } /** - The $(D FreeTree) is word aligned. + The `FreeTree` is word aligned. */ enum uint alignment = size_t.alignof; /** - The $(D FreeTree) allocator is noncopyable. + The `FreeTree` allocator is noncopyable. */ this(this) @disable; /** - The destructor of $(D FreeTree) releases all memory back to the parent + The destructor of `FreeTree` releases all memory back to the parent allocator. */ static if (hasMember!(ParentAllocator, "deallocate")) @@ -275,14 +278,14 @@ struct FreeTree(ParentAllocator) /** - Allocates $(D n) bytes of memory. First consults the free tree, and returns + Allocates `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 + ParentAllocator) defines `deallocate`, `FreeTree` releases all of its contents and tries again. - TODO: Splitting and coalescing should be implemented if $(D ParentAllocator) does not defined $(D deallocate). + TODO: Splitting and coalescing should be implemented if `ParentAllocator` does not defined `deallocate`. */ void[] allocate(size_t n) @@ -320,7 +323,7 @@ struct FreeTree(ParentAllocator) mixin(forwardToMember("parent", "allocateAll", "expand", "owns", "reallocate")); - /** Places $(D b) into the free tree. */ + /** Places `b` into the free tree. */ bool deallocate(void[] b) { if (!b.ptr) return true; @@ -340,9 +343,9 @@ struct FreeTree(ParentAllocator) 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); + () nothrow @nogc { a.deallocate(b1); }(); + () nothrow @nogc { a.deallocate(b3); }(); + () nothrow @nogc { a.deallocate(b2); }(); assert(a.formatSizes == "(20480 (12288 32768))", a.formatSizes); b1 = a.allocate(10000); @@ -365,7 +368,7 @@ struct FreeTree(ParentAllocator) foreach_reverse (b; allocs) { assert(b.ptr); - a.deallocate(b); + () nothrow @nogc { a.deallocate(b); }(); } a.assertValid; allocs = null; @@ -375,7 +378,7 @@ struct FreeTree(ParentAllocator) a.assertValid; } - /** Defined if $(D ParentAllocator.deallocate) exists, and returns to it + /** Defined if `ParentAllocator.deallocate` exists, and returns to it all memory held in the free tree. */ static if (hasMember!(ParentAllocator, "deallocate")) void clear() @@ -393,7 +396,7 @@ struct FreeTree(ParentAllocator) /** - Defined if $(D ParentAllocator.deallocateAll) exists, and forwards to it. + Defined if `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). @@ -408,13 +411,15 @@ struct FreeTree(ParentAllocator) } } +version (StdUnittest) @system unittest { import std.experimental.allocator.gc_allocator; testAllocator!(() => FreeTree!GCAllocator()); } -@system unittest // issue 16506 +// https://issues.dlang.org/show_bug.cgi?id=16506 +@system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; import std.experimental.allocator.mallocator : Mallocator; @@ -425,7 +430,7 @@ struct FreeTree(ParentAllocator) byte[] _payload = cast(byte[]) myAlloc.allocate(sz); assert(_payload, "_payload is null"); _payload[] = 0; - myAlloc.deallocate(_payload); + () nothrow @nogc { myAlloc.deallocate(_payload); }(); } f!Mallocator(33); @@ -433,7 +438,8 @@ struct FreeTree(ParentAllocator) f!GCAllocator(1); } -@system unittest // issue 16507 +// https://issues.dlang.org/show_bug.cgi?id=16507 +@system unittest { static struct MyAllocator { @@ -446,7 +452,7 @@ struct FreeTree(ParentAllocator) FreeTree!MyAllocator ft; void[] x = ft.allocate(1); - ft.deallocate(x); + () nothrow @nogc { ft.deallocate(x); }(); ft.allocate(1000); MyAllocator.alive = false; } @@ -475,7 +481,7 @@ struct FreeTree(ParentAllocator) FreeTree!MyAllocator ft; void[] x = ft.allocate(1); - ft.deallocate(x); + () nothrow @nogc { ft.deallocate(x); }(); assert(myDeallocCounter == 0); x = ft.allocate(1000); // Triggers "desperation mode". assert(myDeallocCounter == 1); @@ -485,3 +491,25 @@ struct FreeTree(ParentAllocator) assert(myDeallocCounter == 1); assert(y.ptr is null); } + +@system unittest +{ + import std.experimental.allocator.gc_allocator; + FreeTree!GCAllocator a; + + assert((() nothrow @safe @nogc => a.goodAllocSize(1))() == typeof(*a.root).sizeof); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + + auto a = FreeTree!(Region!())(Region!()(new ubyte[1024 * 64])); + auto b = a.allocate(42); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => a.expand(b, 22))()); + assert(b.length == 64); + assert((() nothrow @nogc => a.reallocate(b, 100))()); + assert(b.length == 100); + assert((() nothrow @nogc => a.deallocateAll())()); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d b/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d index 555daba..65226e7 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d @@ -1,14 +1,17 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/kernighan_ritchie.d) +*/ module std.experimental.allocator.building_blocks.kernighan_ritchie; -import std.experimental.allocator.building_blocks.null_allocator; +import std.experimental.allocator.building_blocks.null_allocator : + NullAllocator; //debug = KRRegion; -version (unittest) import std.conv : text; debug(KRRegion) import std.stdio; // KRRegion /** -$(D KRRegion) draws inspiration from the $(MREF_ALTTEXT region allocation +`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 @@ -19,11 +22,11 @@ $(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. +left, `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 +Once the region cannot serve an `allocate` request, `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. @@ -47,17 +50,17 @@ 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) +The `ParentAllocator` type parameter is the type of the allocator used to +allocate the memory chunk underlying the `KRRegion` object. Choosing the +default (`NullAllocator`) means the user is responsible for passing a buffer +at construction (and for deallocating it if necessary). Otherwise, `KRRegion` automatically deallocates the buffer during destruction. For that reason, if -$(D ParentAllocator) is not $(D NullAllocator), then $(D KRRegion) is not +`ParentAllocator` is not `NullAllocator`, then `KRRegion` is not copyable. $(H4 Implementation Details) -In free list mode, $(D KRRegion) embeds a free blocks list onto the chunk of +In free list mode, `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 @@ -84,9 +87,9 @@ 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 +KRRegion) just gets full (returns `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 +example, see the example below using `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.) @@ -163,15 +166,17 @@ struct KRRegion(ParentAllocator = NullAllocator) // state /** - If $(D ParentAllocator) holds state, $(D parent) is a public member of type - $(D KRRegion). Otherwise, $(D parent) is an $(D alias) for + If `ParentAllocator` holds state, `parent` is a public member of type + `KRRegion`. Otherwise, `parent` is an `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; + private bool regionMode() const { return bytesUsedRegionMode != size_t.max; } + private void cancelRegionMode() { bytesUsedRegionMode = size_t.max; } + private size_t bytesUsedRegionMode = 0; auto byNodePtr() { @@ -302,14 +307,14 @@ struct KRRegion(ParentAllocator = NullAllocator) } /** - Create a $(D KRRegion). If $(D ParentAllocator) is not $(D NullAllocator), - $(D KRRegion)'s destructor will call $(D parent.deallocate). + Create a `KRRegion`. If `ParentAllocator` is not `NullAllocator`, + `KRRegion`'s destructor will call `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). + ParentAllocator) is not `NullAllocator`. */ this(ubyte[] b) { @@ -334,7 +339,7 @@ struct KRRegion(ParentAllocator = NullAllocator) } /// Ditto - static if (!is(ParentAllocator == NullAllocator)) + static if (!is(ParentAllocator == NullAllocator) && !stateSize!ParentAllocator) this(size_t n) { assert(n > Node.sizeof); @@ -342,6 +347,15 @@ struct KRRegion(ParentAllocator = NullAllocator) } /// Ditto + static if (!is(ParentAllocator == NullAllocator) && stateSize!ParentAllocator) + this(ParentAllocator parent, size_t n) + { + assert(n > Node.sizeof); + this.parent = parent; + this(cast(ubyte[])(parent.allocate(n))); + } + + /// Ditto static if (!is(ParentAllocator == NullAllocator) && hasMember!(ParentAllocator, "deallocate")) ~this() @@ -357,7 +371,7 @@ struct KRRegion(ParentAllocator = NullAllocator) void switchToFreeList() { if (!regionMode) return; - regionMode = false; + cancelRegionMode; if (!root) return; root = sortFreelist(root); coalesceAndMakeCircular; @@ -374,13 +388,13 @@ struct KRRegion(ParentAllocator = NullAllocator) 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). + Allocates `n` bytes. Allocation searches the list of available blocks + until a free block with `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). + Returns: A word-aligned buffer of `n` bytes, or `null`. */ void[] allocate(size_t n) { @@ -394,6 +408,7 @@ struct KRRegion(ParentAllocator = NullAllocator) if (root.size >= actualBytes) { // Enough room for allocation + bytesUsedRegionMode += actualBytes; void* result = root; immutable balance = root.size - actualBytes; if (balance >= Node.sizeof) @@ -436,13 +451,14 @@ struct KRRegion(ParentAllocator = NullAllocator) } /** - Deallocates $(D b), which is assumed to have been previously allocated with + Deallocates `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 */ + nothrow @nogc bool deallocate(void[] b) { debug(KRRegion) writefln("KRRegion@%s: deallocate(%s[%s])", &this, @@ -461,6 +477,7 @@ struct KRRegion(ParentAllocator = NullAllocator) { assert(root); // Insert right after root + bytesUsedRegionMode -= n.size; n.next = root.next; root.next = n; return true; @@ -489,9 +506,10 @@ struct KRRegion(ParentAllocator = NullAllocator) assert(pnode && pnode.next); assert(pnode != n); assert(pnode.next != n); + if (pnode < pnode.next) { - if (pnode >= n || n >= pnode.next) continue; + if (pnode > n || n > pnode.next) continue; // Insert in between pnode and pnode.next n.next = pnode.next; pnode.next = n; @@ -528,13 +546,13 @@ struct KRRegion(ParentAllocator = NullAllocator) /** 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 + a best-effort allocation: if there is no fragmentation (e.g. `allocate` + has been used but not `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. + `allocateAll` succeeds or fails due to fragmentation. */ void[] allocateAll() { @@ -559,37 +577,42 @@ struct KRRegion(ParentAllocator = NullAllocator) Deallocates all memory currently allocated, making the allocator ready for other allocations. This is a $(BIGOH 1) operation. */ + pure nothrow @nogc bool deallocateAll() { debug(KRRegion) assertValid("deallocateAll"); debug(KRRegion) scope(exit) assertValid("deallocateAll"); root = cast(Node*) payload.ptr; - // Initialize the free list with all list + + // Reset to regionMode + bytesUsedRegionMode = 0; if (root) { - root.next = root; + root.next = null; 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. + Checks whether the allocator is responsible for the allocation of `b`. + It does a simple $(BIGOH 1) range check. `b` should be a buffer either + allocated with `this` or obtained through other means. */ + pure nothrow @trusted @nogc 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); + return Ternary(b && payload && (&b[0] >= &payload[0]) + && (&b[0] < &payload[0] + payload.length)); } /** - Adjusts $(D n) to a size suitable for allocation (two words or larger, + Adjusts `n` to a size suitable for allocation (two words or larger, word-aligned). */ + pure nothrow @safe @nogc static size_t goodAllocSize(size_t n) { import std.experimental.allocator.common : roundUpToMultipleOf; @@ -601,16 +624,20 @@ struct KRRegion(ParentAllocator = NullAllocator) Returns: `Ternary.yes` if the allocator is empty, `Ternary.no` otherwise. Never returns `Ternary.unknown`. */ + pure nothrow @safe @nogc Ternary empty() { + if (regionMode) + return Ternary(bytesUsedRegionMode == 0); + 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 +`KRRegion` is preferable to `Region` as a front for a general-purpose +allocator if `deallocate` is needed, yet the actual deallocation traffic is +relatively low. The example below shows a `KRRegion` using stack storage fronting the GC allocator. */ @system unittest @@ -624,7 +651,7 @@ fronting the GC allocator. auto alloc = fallbackAllocator(KRRegion!()(buf), GCAllocator.instance); auto b = alloc.allocate(100); assert(b.length == 100); - assert(alloc.primary.owns(b) == Ternary.yes); + assert((() pure nothrow @safe @nogc => alloc.primary.owns(b))() == Ternary.yes); } /** @@ -642,7 +669,6 @@ it actually returns memory to the operating system when possible. 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; } @@ -652,7 +678,6 @@ it actually returns memory to the operating system when possible. 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; /* @@ -675,8 +700,8 @@ it actually returns memory to the operating system when possible. foreach (i; 0 .. array.length) { assert(array[i].ptr); - assert(alloc.owns(array[i]) == Ternary.yes); - alloc.deallocate(array[i]); + assert((() pure nothrow @safe @nogc => alloc.owns(array[i]))() == Ternary.yes); + () nothrow @nogc { alloc.deallocate(array[i]); }(); } } @@ -685,7 +710,6 @@ it actually returns memory to the operating system when possible. 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; /* @@ -713,11 +737,12 @@ it actually returns memory to the operating system when possible. randomShuffle(array[]); foreach (i; 0 .. array.length) { - assert(alloc.owns(array[i]) == Ternary.yes); - alloc.deallocate(array[i]); + assert((() pure nothrow @safe @nogc => alloc.owns(array[i]))() == Ternary.yes); + () nothrow @nogc { alloc.deallocate(array[i]); }(); } } +version (StdUnittest) @system unittest { import std.algorithm.comparison : max; @@ -741,9 +766,9 @@ it actually returns memory to the operating system when possible. array ~= alloc.allocate(i); assert(array[$ - 1].length == i); } - alloc.deallocate(array[1]); - alloc.deallocate(array[0]); - alloc.deallocate(array[2]); + () nothrow @nogc { alloc.deallocate(array[1]); }(); + () nothrow @nogc { alloc.deallocate(array[0]); }(); + () nothrow @nogc { alloc.deallocate(array[2]); }(); assert(alloc.allocateAll().length == 1024 * 1024); } @@ -755,9 +780,9 @@ it actually returns memory to the operating system when possible. cast(ubyte[])(GCAllocator.instance.allocate(1024 * 1024))); const store = alloc.allocate(KRRegion!().sizeof); auto p = cast(KRRegion!()* ) store.ptr; + import core.lifetime : emplace; import core.stdc.string : memcpy; - import std.algorithm.mutation : move; - import std.conv : emplace; + import std.conv : text; memcpy(p, &alloc, alloc.sizeof); emplace(&alloc); @@ -768,14 +793,14 @@ it actually returns memory to the operating system when possible. 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); + assert((() pure nothrow @safe @nogc => 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]); + assert((() pure nothrow @safe @nogc => p.owns(array[i]))() == Ternary.yes); + () nothrow @nogc { p.deallocate(array[i]); }(); } auto b = p.allocateAll(); assert(b.length == 1024 * 1024 - KRRegion!().sizeof, text(b.length)); @@ -783,20 +808,21 @@ it actually returns memory to the operating system when possible. @system unittest { + import std.typecons : Ternary; 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(); + assert((() nothrow @nogc => alloc.deallocateAll())()); + assert(alloc.empty() == Ternary.yes); p = alloc.allocateAll(); assert(p.length == 1024 * 1024); } @system unittest { - import std.experimental.allocator.building_blocks; - import std.random; + import std.random : randomCover; import std.typecons : Ternary; // Both sequences must work on either system @@ -825,10 +851,10 @@ it actually returns memory to the operating system when possible. foreach (b; bufs.randomCover) { - a.deallocate(b); + () nothrow @nogc { a.deallocate(b); }(); } - assert(a.empty == Ternary.yes); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); } test(sizes64); @@ -837,8 +863,6 @@ it actually returns memory to the operating system when possible. @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. @@ -865,18 +889,51 @@ it actually returns memory to the operating system when possible. bufs ~= a.allocate(size); } - a.deallocate(bufs[1]); + () nothrow @nogc { a.deallocate(bufs[1]); }(); bufs ~= a.allocate(sizes[1] - word); - a.deallocate(bufs[0]); + () nothrow @nogc { a.deallocate(bufs[0]); }(); foreach (i; 2 .. bufs.length) { - a.deallocate(bufs[i]); + () nothrow @nogc { a.deallocate(bufs[i]); }(); } - assert(a.empty == Ternary.yes); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); } test(sizes64, word64); test(sizes32, word32); } + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + + auto a = KRRegion!GCAllocator(1024 * 1024); + assert((() pure nothrow @safe @nogc => a.goodAllocSize(1))() == typeof(*a.root).sizeof); +} + +@system unittest +{ import std.typecons : Ternary; + + ubyte[1024] b; + auto alloc = KRRegion!()(b); + + auto k = alloc.allocate(128); + assert(k.length == 128); + assert(alloc.empty == Ternary.no); + assert(alloc.deallocate(k)); + assert(alloc.empty == Ternary.yes); + + k = alloc.allocate(512); + assert(k.length == 512); + assert(alloc.empty == Ternary.no); + assert(alloc.deallocate(k)); + assert(alloc.empty == Ternary.yes); + + k = alloc.allocate(1024); + assert(k.length == 1024); + assert(alloc.empty == Ternary.no); + assert(alloc.deallocate(k)); + assert(alloc.empty == Ternary.yes); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d index 68bab70..06b4e15 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d @@ -1,32 +1,37 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/null_allocator.d) +*/ module std.experimental.allocator.building_blocks.null_allocator; /** -$(D NullAllocator) is an emphatically empty implementation of the allocator +`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; + + nothrow @nogc pure @safe: /** - $(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) + `NullAllocator` advertises a relatively large _alignment equal to 64 KB. + This is because `NullAllocator` never actually needs to honor this + alignment and because composite allocators using `NullAllocator` shouldn't be unnecessarily constrained. */ enum uint alignment = 64 * 1024; - // /// Returns $(D n). + // /// Returns `n`. //size_t goodAllocSize(size_t n) shared const //{ return .goodAllocSize(this, n); } - /// Always returns $(D null). + /// Always returns `null`. void[] allocate(size_t) shared { return null; } - /// Always returns $(D null). + /// Always returns `null`. void[] alignedAllocate(size_t, uint) shared { return null; } - /// Always returns $(D null). + /// Always returns `null`. void[] allocateAll() shared { return null; } /** - These methods return $(D false). + These methods return `false`. Precondition: $(D b is null). This is because there is no other possible legitimate input. */ @@ -38,10 +43,10 @@ struct NullAllocator /// 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 `Ternary.no`. + Ternary owns(const void[]) shared const { return Ternary.no; } /** - Returns $(D Ternary.no). + Returns `Ternary.no`. */ Ternary resolveInternalPointer(const void*, ref void[]) shared const { return Ternary.no; } @@ -55,31 +60,34 @@ struct NullAllocator */ bool deallocateAll() shared { return true; } /** - Returns $(D Ternary.yes). + Returns `Ternary.yes`. */ Ternary empty() shared const { return Ternary.yes; } /** - Returns the $(D shared) global instance of the $(D NullAllocator). + Returns the `shared` global instance of the `NullAllocator`. */ static shared NullAllocator instance; } -@system unittest +nothrow @nogc pure @safe unittest { - assert(NullAllocator.instance.alignedAllocate(100, 0) is null); - assert(NullAllocator.instance.allocateAll() is null); - auto b = NullAllocator.instance.allocate(100); + alias a = NullAllocator.instance; + + assert(a.alignedAllocate(100, 0) is null); + assert(a.allocateAll() is null); + auto b = a.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(); + assert(a.expand(b, 0)); + assert(!a.expand(b, 42)); + assert(!a.reallocate(b, 42)); + assert(!a.alignedReallocate(b, 42, 0)); + assert(a.deallocate(b)); + assert(a.deallocateAll()); import std.typecons : Ternary; - assert(NullAllocator.instance.empty() == Ternary.yes); - assert(NullAllocator.instance.owns(null) == Ternary.no); + assert(a.empty == Ternary.yes); + assert(a.owns(null) == Ternary.no); + void[] p; - assert(NullAllocator.instance.resolveInternalPointer(null, p) == Ternary.no); + assert(a.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 index d55a16b..962ed91 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/package.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/package.d @@ -1,9 +1,10 @@ +// Written in the D programming language. /** $(H2 Assembling Your Own Allocator) -In addition to defining the interfaces above, this package also implements +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 +exclusively in `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. @@ -16,11 +17,11 @@ 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*).) +deallocation in C++.) For this reason, allocators herein traffic in `void[]` +as opposed to `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 +following methods with their respective semantics. Only `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 @@ -35,23 +36,23 @@ $(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 +alignment) as a statically-known `enum` value only. Applications that need +dynamically-chosen alignment values should use the `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 +`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 +The function `goodAllocSize(n)` returns the actual number of bytes that would +be allocated upon a request for `n` bytes. This module defines a default +implementation that returns `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.)) +null)). Otherwise, the call allocates `s` bytes of memory and returns the +allocated block, or `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 @@ -60,41 +61,41 @@ 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 +currently NOT managing any memory, then `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 +allocation primitives should not succeed (e.g. `allocate` shall return $(D +null) etc). Otherwise, `allocateAll` only works on a best-effort basis, and +the allocator is allowed to return `null` even if does have available memory. +Memory allocated with `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 +== $(I old)(b).length + delta)) $(TD Expands `b` by `delta` bytes. If $(D +delta == 0), succeeds without changing `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).)) +was successful, `expand` changes `b`'s length to $(D b.length + delta) and +returns `true`. Upon failure, the call effects no change upon the allocator +object, leaves `b` unchanged, and returns `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).)) +== s)) $(TD Reallocates `b` to size `s`, possibly moving memory around. +`b` must be `null` or a buffer allocated with the same allocator. If +reallocation was successful, `reallocate` changes `b` appropriately and +returns `true`. Upon failure, the call effects no change upon the allocator +object, leaves `b` unchanged, and returns `false`. An allocator should +implement `reallocate` if it can derive some advantage from doing so; +otherwise, this module defines a `reallocate` free function implemented in +terms of `expand`, `allocate`, and `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 +!$(RES) || b.length == s)) $(TD Similar to `reallocate`, but guarantees the +reallocated memory is aligned at `a` bytes. The buffer must have been +originated with a call to `alignedAllocate`. `a` must be a power of 2 +greater than `(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).)) +this module defines a `alignedReallocate` free function implemented in terms +of `expand`, `alignedAllocate`, and `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 @@ -128,23 +129,23 @@ $(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 +define a static `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).)) +thread-safe or not, this instance may be `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 +internal fragmentation to a minimum. The `FList` definitions specify no +bounds for the freelist because the `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). +from 3585 bytes through 4072 KB are handled by a `BitmappedBlock` with a +block size of 4 KB. Sizes above that are passed direct to the `GCAllocator`. ---- alias FList = FreeList!(GCAllocator, 0, unbounded); @@ -176,23 +177,23 @@ 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 +All allocators in this module accept and return `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 +reallocation, the memory is effectively not `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) +The issue remains of calling `a.deallocate(b)` from a different thread than +the one that allocated `b`. It follows that both threads must have access to +the same instance `a` of the respective allocator type. By definition of D, +this is possible only if `a` has the `shared` qualifier. It follows that +the allocator type must implement `allocate` and `deallocate` as $(D +shared) methods. That way, the allocator commits to allowing usable `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 +Conversely, allocating memory with one non-`shared` allocator, passing it +across threads (by casting the obtained buffer to `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. +or with the same allocator object after casting it to `shared`) is illegal. $(H2 Building Blocks) @@ -212,17 +213,20 @@ 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.)) +offers manual `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 +malloc)/`realloc`/`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)) +$(HTTP man7.org/linux/man-pages/man3/posix_memalign.3.html, `posix_memalign`) on Posix and $(HTTP msdn.microsoft.com/en-us/library/fs9stz4e(v=vs.80).aspx, -$(D __aligned_xxx)) on Windows.)) +`__aligned_xxx`) on Windows.)) + +$(TR $(TDC2 AlignedBlockList, aligned_block_list) $(TD A wrapper around a list of allocators +which allow for very fast deallocations.)) $(TR $(TDC2 AffixAllocator, affix_allocator) $(TD Allocator that allows and manages allocating extra prefix and/or a suffix bytes for each block allocated.)) @@ -231,8 +235,8 @@ $(TR $(TDC2 BitmappedBlock, bitmapped_block) $(TD Organizes one contiguous chunk 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 +$(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.)) @@ -241,10 +245,10 @@ 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 SharedFreeList, free_list) $(TD Same features as `FreeList`, but packaged as +a `shared` structure that is accessible to several threads.)) -$(TR $(TDC2 FreeTree, free_tree) $(TD Allocator similar to $(D FreeList) that uses a +$(TR $(TDC2 FreeTree, free_tree) $(TD Allocator similar to `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 @@ -276,27 +280,34 @@ 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.)) +$(TR $(TDC2 AscendingPageAllocator, ascending_page_allocator) $(TD A memory safe allocator +where sizes are rounded to a multiple of the page size and allocations are satisfied at increasing addresses.)) + $(COMMENT $(TR $(TDC2 InternalPointersTree) $(TD Adds support for resolving internal pointers on top of another allocator.))) ) +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/package.d) + Macros: MYREF2 = $(REF_SHORT $1, std,experimental,allocator,building_blocks,$2) MYREF3 = $(REF_SHORT $1, std,experimental,allocator,$2) -TDC = $(TDNW $(D $1)$+) +TDC = $(TDNW `$1`$+) TDC2 = $(TDNW $(D $(MYREF2 $1,$+))$(BR)$(SMALL -$(D std.experimental.allocator.building_blocks.$2))) +`std.experimental.allocator.building_blocks.$2`)) TDC3 = $(TDNW $(D $(MYREF3 $1,$+))$(BR)$(SMALL -$(D std.experimental.allocator.$2))) +`std.experimental.allocator.$2`)) RES = $(I result) -POST = $(BR)$(SMALL $(I Post:) $(BLUE $(D $0))) +POST = $(BR)$(SMALL $(I Post:) $(BLUE `$0`)) */ module std.experimental.allocator.building_blocks; public import std.experimental.allocator.building_blocks.affix_allocator, + std.experimental.allocator.building_blocks.aligned_block_list, std.experimental.allocator.building_blocks.allocator_list, + std.experimental.allocator.building_blocks.ascending_page_allocator, std.experimental.allocator.building_blocks.bucketizer, std.experimental.allocator.building_blocks.fallback_allocator, std.experimental.allocator.building_blocks.free_list, diff --git a/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d b/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d index b3f205d..762b379 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d @@ -1,37 +1,40 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/quantizer.d) +*/ 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: +This allocator sits on top of `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 +reallocation-heavy can therefore benefit from fronting a generic allocator with +a `Quantizer`. These advantages are present even if `ParentAllocator` does not +support reallocation at all.) +$(LI Improve behavior of allocators sensitive to allocation sizes, such as +`FreeList` and `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). +`allocateAll`, `owns`, `deallocateAll`, `empty`. -Preconditions: $(D roundingFunction) must satisfy three constraints. These are -not enforced (save for the use of $(D assert)) for the sake of efficiency. +Preconditions: `roundingFunction` must satisfy three constraints. These are +not enforced (save for the use of `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 +$(LI $(D roundingFunction(n) >= n) for all `n` of type `size_t`;) +$(LI `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).) +$(LI `roundingFunction` must be `nothrow`, `@safe`, `@nogc` and `pure`, i.e. +always return the same value for a given `n`.) ) */ struct Quantizer(ParentAllocator, alias roundingFunction) @@ -39,7 +42,7 @@ struct Quantizer(ParentAllocator, alias roundingFunction) import std.traits : hasMember; /** - The parent allocator. Depending on whether $(D ParentAllocator) holds state + The parent allocator. Depending on whether `ParentAllocator` holds state or not, this is a member variable or an alias for `ParentAllocator.instance`. */ @@ -50,11 +53,11 @@ struct Quantizer(ParentAllocator, alias roundingFunction) else { alias parent = ParentAllocator.instance; - static __gshared Quantizer instance; + __gshared Quantizer instance; } /** - Returns $(D roundingFunction(n)). + Returns `roundingFunction(n)`. */ size_t goodAllocSize(size_t n) { @@ -69,9 +72,9 @@ struct Quantizer(ParentAllocator, alias roundingFunction) 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]). + Gets a larger buffer `buf` by calling + `parent.allocate(goodAllocSize(n))`. If `buf` is `null`, returns + `null`. Otherwise, returns $(D buf[0 .. n]). */ void[] allocate(size_t n) { @@ -79,27 +82,34 @@ struct Quantizer(ParentAllocator, alias roundingFunction) return result.ptr ? result.ptr[0 .. n] : null; } + static if (hasMember!(ParentAllocator, "allocateZeroed")) + package(std) void[] allocateZeroed()(size_t n) + { + auto result = parent.allocateZeroed(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 + Defined only if `parent.alignedAllocate` exists and works similarly to + `allocate` by forwarding to $(D parent.alignedAllocate(goodAllocSize(n), a)). */ static if (hasMember!(ParentAllocator, "alignedAllocate")) - void[] alignedAllocate(size_t n, uint) + void[] alignedAllocate(size_t n, uint a) { - auto result = parent.alignedAllocate(goodAllocSize(n)); + auto result = parent.alignedAllocate(goodAllocSize(n), a); return result.ptr ? result.ptr[0 .. n] : null; } /** - First checks whether there's enough slack memory preallocated for $(D b) + First checks whether there's enough slack memory preallocated for `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. + the case, expands `b` in place. Otherwise, attempts to use + `parent.expand` appropriately if present. */ bool expand(ref void[] b, size_t delta) { - if (!b.ptr) return delta == 0; + if (!b || delta == 0) return delta == 0; immutable allocated = goodAllocSize(b.length), needed = b.length + delta, neededAllocation = goodAllocSize(needed); @@ -110,19 +120,19 @@ struct Quantizer(ParentAllocator, alias roundingFunction) if (allocated == neededAllocation) { // Nice! - b = b.ptr[0 .. needed]; + b = (() @trusted => b.ptr[0 .. needed])(); return true; } // Hail Mary static if (hasMember!(ParentAllocator, "expand")) { // Expand to the appropriate quantum - auto original = b.ptr[0 .. allocated]; + auto original = (() @trusted => 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]; + b = (() @trusted => original.ptr[0 .. needed])(); return true; } else @@ -134,7 +144,7 @@ struct Quantizer(ParentAllocator, alias roundingFunction) /** 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) + by `expand`. Shrinking occurs in place if $(D goodAllocSize(b.length) == goodAllocSize(s)). */ bool reallocate(ref void[] b, size_t s) @@ -162,8 +172,8 @@ struct Quantizer(ParentAllocator, alias roundingFunction) } /** - Defined only if $(D ParentAllocator.alignedAllocate) exists. Expansion - occurs in place under the conditions required by $(D expand). Shrinking + Defined only if `ParentAllocator.alignedAllocate` exists. Expansion + occurs in place under the conditions required by `expand`. Shrinking occurs in place if $(D goodAllocSize(b.length) == goodAllocSize(s)). */ static if (hasMember!(ParentAllocator, "alignedAllocate")) @@ -171,14 +181,14 @@ struct Quantizer(ParentAllocator, alias roundingFunction) { if (!b.ptr) { - b = alignedAllocate(s); + b = alignedAllocate(s, a); return b.length == s; } - if (s >= b.length && expand(b, s - b.length)) return true; + if (s >= b.length && b.ptr.alignedAt(a) && 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) + if (allocated == toAllocate && b.ptr.alignedAt(a)) { assert(b.ptr); // code above must have caught this // Reallocation (whether up or down) will be done in place @@ -193,7 +203,7 @@ struct Quantizer(ParentAllocator, alias roundingFunction) } /** - Defined if $(D ParentAllocator.deallocate) exists and forwards to + Defined if `ParentAllocator.deallocate` exists and forwards to $(D parent.deallocate(b.ptr[0 .. goodAllocSize(b.length)])). */ static if (hasMember!(ParentAllocator, "deallocate")) @@ -212,23 +222,113 @@ struct Quantizer(ParentAllocator, alias roundingFunction) @system unittest { import std.experimental.allocator.building_blocks.free_tree : FreeTree; - import std.experimental.allocator.common : roundUpToMultipleOf; import std.experimental.allocator.gc_allocator : GCAllocator; + size_t roundUpToMultipleOf(size_t s, uint base) + { + auto rem = s % base; + return rem ? s + base - rem : s; + } + // 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)); + n => roundUpToMultipleOf(n, n <= 16_384 ? 64 : 4096)); MyAlloc alloc; const buf = alloc.allocate(256); assert(buf.ptr); } +version (StdUnittest) @system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; alias MyAlloc = Quantizer!(GCAllocator, (size_t n) => n.roundUpToMultipleOf(64)); testAllocator!(() => MyAlloc()); + + assert((() pure nothrow @safe @nogc => MyAlloc().goodAllocSize(1))() == 64); + + auto a = MyAlloc(); + auto b = a.allocate(42); + assert(b.length == 42); + // Inplace expand, since goodAllocSize is 64 + assert((() @safe => a.expand(b, 22))()); + //assert((() nothrow @safe => a.expand(b, 22))()); + assert(b.length == 64); + // Trigger parent.expand, which may or may not succed + //() nothrow @safe { a.expand(b, 1); }(); + () @safe { a.expand(b, 1); }(); + assert(a.reallocate(b, 100)); + assert(b.length == 100); + // Ensure deallocate inherits from parent + () nothrow @nogc { a.deallocate(b); }(); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + + alias Alloc = Quantizer!(Region!(Mallocator), + (size_t n) => n.roundUpToMultipleOf(64)); + auto a = Alloc(Region!Mallocator(1024 * 64)); + const b = a.allocate(42); + assert(b.length == 42); + // Check that owns inherits from parent, i.e. Region + assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no); + + auto c = a.allocate(42); + assert(c.length == 42); + assert((() pure nothrow @safe @nogc => a.owns(c))() == Ternary.yes); + // Inplace expand, since goodAllocSize is 64 + assert((() nothrow @safe => a.expand(c, 22))()); + assert(c.length == 64); + // Trigger parent.expand + assert((() nothrow @safe => a.expand(c, 1))()); + assert(c.length == 65); + // Check that reallocate inherits from parent + assert((() nothrow @nogc => a.reallocate(c, 100))()); + assert(c.length == 100); +} + +version (StdUnittest) +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.mallocator : Mallocator; + + alias MyAlloc = Quantizer!(Region!(Mallocator), + (size_t n) => n.roundUpToMultipleOf(64)); + testAllocator!(() => MyAlloc(Region!Mallocator(1024 * 64))); + + auto a = MyAlloc(Region!Mallocator(1024 * 64)); + void[] b; + assert((() nothrow @nogc => a.alignedReallocate(b, 42, 16))()); + assert(b.length == 42); + assert(alignedAt(&b[0], 16)); +} + +version (StdUnittest) +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.typecons : Ternary; + + alias MyAlloc = Quantizer!(Region!(), + (size_t n) => n.roundUpToMultipleOf(64)); + testAllocator!(() => MyAlloc(Region!()(new ubyte[1024 * 64]))); + + auto a = MyAlloc(Region!()(new ubyte[1024 * 64])); + // Check that empty inherits from parent + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); + auto b = a.allocate(42); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no); + // Check that deallocateAll inherits from parent + assert((() nothrow @nogc => a.deallocateAll())()); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); } diff --git a/libphobos/src/std/experimental/allocator/building_blocks/region.d b/libphobos/src/std/experimental/allocator/building_blocks/region.d index 53f5ef9..be0d274 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/region.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/region.d @@ -1,4 +1,7 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/region.d) +*/ module std.experimental.allocator.building_blocks.region; import std.experimental.allocator.building_blocks.null_allocator; @@ -15,9 +18,9 @@ else version (WatchOS) version = Darwin; /** -A $(D Region) allocator allocates memory straight from one contiguous chunk. +A `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 +return `null`. Therefore, `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. @@ -26,11 +29,11 @@ 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) +If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), `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). +The `minAlign` parameter establishes alignment. If $(D minAlign > 1), the +sizes of all allocation requests are rounded up to a multiple of `minAlign`. Applications aiming at maximum speed may want to choose $(D minAlign = 1) and control alignment externally. @@ -47,7 +50,7 @@ struct Region(ParentAllocator = NullAllocator, // state /** - The _parent allocator. Depending on whether $(D ParentAllocator) holds state + The _parent allocator. Depending on whether `ParentAllocator` holds state or not, this is a member variable or an alias for `ParentAllocator.instance`. */ @@ -59,51 +62,65 @@ struct Region(ParentAllocator = NullAllocator, { alias parent = ParentAllocator.instance; } + private void* _current, _begin, _end; + private void* roundedBegin() const pure nothrow @trusted @nogc + { + return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); + } + + private void* roundedEnd() const pure nothrow @trusted @nogc + { + return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); + } /** - 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)). + Constructs a region backed by a user-provided store. + Assumes the memory was allocated with `ParentAllocator` + (if different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator)). 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); + store = User-provided store backing up the region. If $(D + ParentAllocator) is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), memory is assumed to + have been allocated with `ParentAllocator`. + n = Bytes to allocate using `ParentAllocator`. This constructor is only + defined If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator). If + `parent.allocate(n)` returns `null`, the region will be initialized + as empty (correctly initialized but unable to allocate). + */ + this(ubyte[] store) pure nothrow @nogc + { _begin = store.ptr; _end = store.ptr + store.length; static if (growDownwards) - _current = _end; + _current = roundedEnd(); else - _current = store.ptr; + _current = roundedBegin(); } /// Ditto - static if (!is(ParentAllocator == NullAllocator)) + static if (!is(ParentAllocator == NullAllocator) && !stateSize!ParentAllocator) this(size_t n) { - this(cast(ubyte[])(parent.allocate(n.roundUpToAlignment(alignment)))); + this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); + } + + /// Ditto + static if (!is(ParentAllocator == NullAllocator) && stateSize!ParentAllocator) + this(ParentAllocator parent, size_t n) + { + this.parent = parent; + this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); } /* - TODO: The postblit of $(D BasicRegion) should be disabled because such objects + TODO: The postblit of `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 + If `ParentAllocator` is not $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator) and defines `deallocate`, + the region defines a destructor that uses `ParentAllocator.deallocate` to free the memory chunk. */ static if (!is(ParentAllocator == NullAllocator) @@ -113,6 +130,13 @@ struct Region(ParentAllocator = NullAllocator, parent.deallocate(_begin[0 .. _end - _begin]); } + /** + Rounds the given size to a multiple of the `alignment` + */ + size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc + { + return n.roundUpToAlignment(alignment); + } /** Alignment offered. @@ -120,77 +144,75 @@ struct Region(ParentAllocator = NullAllocator, alias alignment = minAlign; /** - Allocates $(D n) bytes of memory. The shortest path involves an alignment + Allocates `n` bytes of memory. The shortest path involves an alignment adjustment (if $(D alignment > 1)), an increment, and a comparison. Params: - n = number of bytes to allocate + n = number of bytes to allocate Returns: - A properly-aligned buffer of size $(D n) or $(D null) if request could not - be satisfied. + A properly-aligned buffer of size `n` or `null` if request could not + be satisfied. */ - void[] allocate(size_t n) + void[] allocate(size_t n) pure nothrow @trusted @nogc { + const rounded = goodAllocSize(n); + if (n == 0 || rounded < n || available < rounded) return null; + static if (growDownwards) { - 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; } + + return result; } /** - Allocates $(D n) bytes of memory aligned at alignment $(D a). + Allocates `n` bytes of memory aligned at alignment `a`. Params: - n = number of bytes to allocate - a = alignment for the allocated block + 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). + Either a suitable block of `n` bytes aligned at `a`, or `null`. */ - void[] alignedAllocate(size_t n, uint a) + void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc { - import std.math : isPowerOf2; + import std.math.traits : isPowerOf2; assert(a.isPowerOf2); + + const rounded = goodAllocSize(n); + if (n == 0 || rounded < n || available < rounded) return null; + static if (growDownwards) { - const available = _current - _begin; - if (available < n) return null; - auto result = (_current - n).alignDownTo(a)[0 .. n]; - if (result.ptr >= _begin) + auto tmpCurrent = _current - rounded; + auto result = tmpCurrent.alignDownTo(a); + if (result <= tmpCurrent && result >= _begin) { - _current = result.ptr; - return result; + _current = result; + return cast(void[]) result[0 .. n]; } } else { // Just bump the pointer to the next good allocation + auto newCurrent = _current.alignUpTo(a); + if (newCurrent < _current || newCurrent > _end) + return null; + auto save = _current; - _current = _current.alignUpTo(a); + _current = newCurrent; auto result = allocate(n); if (result.ptr) { @@ -204,7 +226,7 @@ struct Region(ParentAllocator = NullAllocator, } /// Allocates and returns all memory available to this region. - void[] allocateAll() + void[] allocateAll() pure nothrow @trusted @nogc { static if (growDownwards) { @@ -225,20 +247,23 @@ struct Region(ParentAllocator = NullAllocator, `No.growDownwards`. */ static if (growDownwards == No.growDownwards) - bool expand(ref void[] b, size_t delta) + bool expand(ref void[] b, size_t delta) pure nothrow @safe @nogc { - 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; + assert(owns(b) == Ternary.yes || b is null); + assert((() @trusted => b.ptr + b.length <= _current)() || b is null); + if (b is null || delta == 0) return delta == 0; auto newLength = b.length + delta; - if (_current < b.ptr + b.length + alignment) + if ((() @trusted => _current < b.ptr + b.length + alignment)()) { + immutable currentGoodSize = this.goodAllocSize(b.length); + immutable newGoodSize = this.goodAllocSize(newLength); + immutable goodDelta = newGoodSize - currentGoodSize; // This was the last allocation! Allocate some more and we're done. - if (this.goodAllocSize(b.length) == this.goodAllocSize(newLength) - || allocate(delta).length == delta) + if (goodDelta == 0 + || (() @trusted => allocate(goodDelta).length == goodDelta)()) { - b = b.ptr[0 .. newLength]; - assert(_current < b.ptr + b.length + alignment); + b = (() @trusted => b.ptr[0 .. newLength])(); + assert((() @trusted => _current < b.ptr + b.length + alignment)()); return true; } } @@ -246,30 +271,29 @@ struct Region(ParentAllocator = NullAllocator, } /** - 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. + Deallocates `b`. This works only if `b` was obtained as the last call + to `allocate`; otherwise (i.e. another allocation has occurred since) it + does nothing. Params: - b = Block previously obtained by a call to $(D allocate) against this - allocator ($(D null) is allowed). + b = Block previously obtained by a call to `allocate` against this + allocator (`null` is allowed). */ - bool deallocate(void[] b) + bool deallocate(void[] b) pure nothrow @nogc { assert(owns(b) == Ternary.yes || b.ptr is null); + auto rounded = goodAllocSize(b.length); static if (growDownwards) { if (b.ptr == _current) { - _current += this.goodAllocSize(b.length); + _current += rounded; return true; } } else { - if (b.ptr + this.goodAllocSize(b.length) == _current) + if (b.ptr + rounded == _current) { assert(b.ptr !is null || _current is null); _current = b.ptr; @@ -283,46 +307,48 @@ struct Region(ParentAllocator = NullAllocator, Deallocates all memory allocated by this region, which can be subsequently reused for new allocations. */ - bool deallocateAll() + bool deallocateAll() pure nothrow @nogc { static if (growDownwards) { - _current = _end; + _current = roundedEnd(); } else { - _current = _begin; + _current = roundedBegin(); } return true; } /** - Queries whether $(D b) has been allocated with this region. + Queries whether `b` has been allocated with this region. Params: - b = Arbitrary block of memory ($(D null) is allowed; $(D owns(null)) - returns $(D false)). + b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns + `false`). Returns: - $(D true) if $(D b) has been allocated with this region, $(D false) - otherwise. + `true` if `b` has been allocated with this region, `false` otherwise. */ - Ternary owns(void[] b) const + Ternary owns(const void[] b) const pure nothrow @trusted @nogc { - return Ternary(b.ptr >= _begin && b.ptr + b.length <= _end); + return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); } /** Returns `Ternary.yes` if no memory has been allocated in this region, `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) */ - Ternary empty() const + Ternary empty() const pure nothrow @safe @nogc { - return Ternary(_current == _begin); + static if (growDownwards) + return Ternary(_current == roundedEnd()); + else + return Ternary(_current == roundedBegin()); } /// Nonstandard property that returns bytes available for allocation. - size_t available() const + size_t available() const @safe pure nothrow @nogc { static if (growDownwards) { @@ -336,45 +362,143 @@ struct Region(ParentAllocator = NullAllocator, } /// -@system unittest +@system nothrow unittest { import std.algorithm.comparison : max; import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; // 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)) )(); + assert(batchAllocator.empty == Ternary.yes); auto b = batchAllocator.allocate(101); assert(b.length == 101); + assert(batchAllocator.empty == Ternary.no); // 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 +@system nothrow @nogc unittest { import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + + static void testAlloc(Allocator)(ref Allocator a) + { + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); + const b = a.allocate(101); + assert(b.length == 101); + assert((() nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + + // Ensure deallocate inherits from parent allocators + auto c = a.allocate(42); + assert(c.length == 42); + assert((() nothrow @nogc => a.deallocate(c))()); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no); + } + // Create a 64 KB region allocated with malloc auto reg = Region!(Mallocator, Mallocator.alignment, Yes.growDownwards)(1024 * 64); - const b = reg.allocate(101); + testAlloc(reg); + + // Create a 64 KB shared region allocated with malloc + auto sharedReg = SharedRegion!(Mallocator, Mallocator.alignment, + Yes.growDownwards)(1024 * 64); + testAlloc(sharedReg); +} + +@system nothrow @nogc unittest +{ + import std.experimental.allocator.mallocator : AlignedMallocator; + import std.typecons : Ternary; + + ubyte[] buf = cast(ubyte[]) AlignedMallocator.instance.alignedAllocate(64, 64); + auto reg = Region!(NullAllocator, 64, Yes.growDownwards)(buf); + assert(reg.alignedAllocate(10, 32).length == 10); + assert(!reg.available); +} + +@system nothrow @nogc unittest +{ + // test 'this(ubyte[] store)' constructed regions properly clean up + // their inner storage after destruction + import std.experimental.allocator.mallocator : Mallocator; + + static shared struct LocalAllocator + { + nothrow @nogc: + enum alignment = Mallocator.alignment; + void[] buf; + bool deallocate(void[] b) + { + assert(buf.ptr == b.ptr && buf.length == b.length); + return true; + } + + void[] allocate(size_t n) + { + return null; + } + + } + + enum bufLen = 10 * Mallocator.alignment; + void[] tmp = Mallocator.instance.allocate(bufLen); + + LocalAllocator a; + a.buf = cast(typeof(a.buf)) tmp[1 .. $]; + + auto reg = Region!(LocalAllocator, Mallocator.alignment, + Yes.growDownwards)(cast(ubyte[]) a.buf); + auto sharedReg = SharedRegion!(LocalAllocator, Mallocator.alignment, + Yes.growDownwards)(cast(ubyte[]) a.buf); + reg.parent = a; + sharedReg.parent = a; + + Mallocator.instance.deallocate(tmp); +} + +version (StdUnittest) +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + testAllocator!(() => Region!(Mallocator)(1024 * 64)); + testAllocator!(() => Region!(Mallocator, Mallocator.alignment, Yes.growDownwards)(1024 * 64)); + + testAllocator!(() => SharedRegion!(Mallocator)(1024 * 64)); + testAllocator!(() => SharedRegion!(Mallocator, Mallocator.alignment, Yes.growDownwards)(1024 * 64)); +} + +@system nothrow @nogc unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + auto reg = Region!(Mallocator)(1024 * 64); + auto b = reg.allocate(101); assert(b.length == 101); - // Destructor will free the memory + assert((() pure nothrow @safe @nogc => reg.expand(b, 20))()); + assert((() pure nothrow @safe @nogc => reg.expand(b, 73))()); + assert((() pure nothrow @safe @nogc => !reg.expand(b, 1024 * 64))()); + assert((() nothrow @nogc => reg.deallocateAll())()); } /** -$(D InSituRegion) is a convenient region that carries its storage within itself +`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 +make sure that at least `n` bytes are available in the region, use $(D InSituRegion!(n + a - 1, a)). Given that the most frequent use of `InSituRegion` is as a stack allocator, it @@ -399,10 +523,10 @@ struct InSituRegion(size_t size, size_t minAlign = platformAlignment) else version (HPPA) enum growDownwards = No.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 (RISCV32) enum growDownwards = Yes.growDownwards; else version (RISCV64) enum growDownwards = Yes.growDownwards; + else version (MIPS32) enum growDownwards = Yes.growDownwards; + else version (MIPS64) enum growDownwards = Yes.growDownwards; else version (SPARC) enum growDownwards = Yes.growDownwards; else version (SPARC64) enum growDownwards = Yes.growDownwards; else version (SystemZ) enum growDownwards = Yes.growDownwards; @@ -415,12 +539,12 @@ struct InSituRegion(size_t size, size_t minAlign = platformAlignment) union { private ubyte[size] _store = void; - private double _forAlignmentOnly1 = void; + private double _forAlignmentOnly1; } // } /** - An alias for $(D minAlign), which must be a valid alignment (nonzero power + An alias for `minAlign`, which must be a valid alignment (nonzero power of 2). The start of the region and all allocation requests will be rounded up to a multiple of the alignment. @@ -441,7 +565,7 @@ struct InSituRegion(size_t size, size_t minAlign = platformAlignment) } /** - Allocates $(D bytes) and returns them, or $(D null) if the region cannot + Allocates `bytes` and returns them, or `null` if the region cannot accommodate the request. For efficiency reasons, if $(D bytes == 0) the function returns an empty non-null slice. */ @@ -459,7 +583,7 @@ struct InSituRegion(size_t size, size_t minAlign = platformAlignment) } /** - As above, but the memory allocated is aligned at $(D a) bytes. + As above, but the memory allocated is aligned at `a` bytes. */ void[] alignedAllocate(size_t n, uint a) { @@ -475,15 +599,15 @@ struct InSituRegion(size_t size, size_t minAlign = platformAlignment) } /** - 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) + Deallocates `b`. This works only if `b` was obtained as the last call + to `allocate`; otherwise (i.e. another allocation has occurred since) it + does nothing. This semantics is tricky and therefore `deallocate` is + defined only if `Region` is instantiated with `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). + b = Block previously obtained by a call to `allocate` against this + allocator (`null` is allowed). */ bool deallocate(void[] b) { @@ -495,7 +619,7 @@ struct InSituRegion(size_t size, size_t minAlign = platformAlignment) Returns `Ternary.yes` if `b` is the result of a previous allocation, `Ternary.no` otherwise. */ - Ternary owns(void[] b) + Ternary owns(const void[] b) pure nothrow @safe @nogc { if (!_impl._current) return Ternary.no; return _impl.owns(b); @@ -563,30 +687,38 @@ struct InSituRegion(size_t size, size_t minAlign = platformAlignment) // Reap with GC fallback. InSituRegion!(128 * 1024, 8) tmp3; FallbackAllocator!(BitmappedBlock!(64, 8), GCAllocator) r3; - r3.primary = BitmappedBlock!(64, 8)(cast(ubyte[])(tmp3.allocateAll())); + 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())); + r4.parent.primary = BitmappedBlock!(64, 64)(cast(ubyte[]) (tmp4.allocateAll())); const a4 = r4.allocate(104); assert(a4.length == 104); } -@system unittest +@system pure nothrow unittest { + import std.typecons : Ternary; + InSituRegion!(4096, 1) r1; auto a = r1.allocate(2001); assert(a.length == 2001); import std.conv : text; assert(r1.available == 2095, text(r1.available)); + // Ensure deallocate inherits from parent + assert((() nothrow @nogc => r1.deallocate(a))()); + assert((() nothrow @nogc => r1.deallocateAll())()); InSituRegion!(65_536, 1024*4) r2; assert(r2.available <= 65_536); a = r2.allocate(2001); assert(a.length == 2001); + const void[] buff = r2.allocate(42); + assert((() nothrow @safe @nogc => r2.owns(buff))() == Ternary.yes); + assert((() nothrow @nogc => r2.deallocateAll())()); } version (CRuntime_Musl) @@ -613,10 +745,10 @@ else /** 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 +for Posix systems. Due to the fact that `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` uses a mutex internally. This implies +that uncontrolled calls to `brk` and `sbrk` may affect the workings of $(D SbrkRegion) adversely. */ @@ -645,13 +777,20 @@ version (Posix) struct SbrkRegion(uint minAlign = platformAlignment) */ enum uint alignment = minAlign; + /** + Rounds the given size to a multiple of thew `alignment` + */ + size_t goodAllocSize(size_t n) shared const pure nothrow @safe @nogc + { + return n.roundUpToMultipleOf(alignment); + } + /// Ditto - void[] allocate(size_t bytes) shared + void[] allocate(size_t bytes) shared @trusted nothrow @nogc { - static if (minAlign > 1) - const rounded = bytes.roundUpToMultipleOf(alignment); - else - alias rounded = bytes; + // Take alignment rounding into account + const rounded = goodAllocSize(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); @@ -674,7 +813,7 @@ version (Posix) struct SbrkRegion(uint minAlign = platformAlignment) } /// Ditto - void[] alignedAllocate(size_t bytes, uint a) shared + void[] alignedAllocate(size_t bytes, uint a) shared @trusted nothrow @nogc { pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 @@ -705,56 +844,64 @@ version (Posix) struct SbrkRegion(uint minAlign = platformAlignment) /** - 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 `expand` method may only succeed if the argument is the last block + allocated. In that case, `expand` attempts to push the break pointer to the right. */ - bool expand(ref void[] b, size_t delta) shared + bool expand(ref void[] b, size_t delta) shared nothrow @trusted @nogc { - if (b is null) return delta == 0; + if (b is null || delta == 0) 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; + + // Take alignment rounding into account + const rounded = goodAllocSize(b.length); + + const slack = rounded - b.length; + if (delta <= slack) + { + b = b.ptr[0 .. b.length + delta]; + return true; + } + + if (_brkCurrent != b.ptr + rounded) 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); + delta -= slack; + + const roundedDelta = goodAllocSize(delta); + auto p = sbrk(roundedDelta); if (p == cast(void*) -1) { return false; } - _brkCurrent = cast(shared) (p + rounded); - b = b.ptr[0 .. b.length + delta]; + _brkCurrent = cast(shared) (p + roundedDelta); + b = b.ptr[0 .. b.length + slack + delta]; return true; } /// Ditto - Ternary owns(void[] b) shared + Ternary owns(const void[] b) shared pure nothrow @trusted @nogc { // No need to lock here. - assert(!_brkCurrent || b.ptr + b.length <= _brkCurrent); - return Ternary(_brkInitial && b.ptr >= _brkInitial); + assert(!_brkCurrent || !b || &b[0] + b.length <= _brkCurrent); + return Ternary(_brkInitial && b && (&b[0] >= _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) + The `deallocate` method only works (and returns `true`) on systems + that support reducing the break address (i.e. accept calls to `sbrk` with negative offsets). OSX does not accept such. In addition the argument must be the last block allocated. */ - bool deallocate(void[] b) shared + bool deallocate(void[] b) shared nothrow @nogc { - static if (minAlign > 1) - const rounded = b.length.roundUpToMultipleOf(alignment); - else - const rounded = b.length; + // Take alignment rounding into account + const rounded = goodAllocSize(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); @@ -767,10 +914,11 @@ version (Posix) struct SbrkRegion(uint minAlign = platformAlignment) } /** - 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) + The `deallocateAll` method only works (and returns `true`) on systems + that support reducing the break address (i.e. accept calls to `sbrk` with negative offsets). OSX does not accept such. */ + nothrow @nogc bool deallocateAll() shared { pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); @@ -780,7 +928,7 @@ version (Posix) struct SbrkRegion(uint minAlign = platformAlignment) } /// Standard allocator API. - Ternary empty() + Ternary empty() shared pure nothrow @safe @nogc { // Also works when they're both null. return Ternary(_brkCurrent == _brkInitial); @@ -806,17 +954,446 @@ version (DragonFlyBSD) {} else version (Posix) @system nothrow @nogc unittest { import std.typecons : Ternary; - alias alloc = SbrkRegion!(8).instance; + import std.algorithm.comparison : min; + alias alloc = SbrkRegion!(min(8, platformAlignment)).instance; + assert((() nothrow @safe @nogc => alloc.empty)() == Ternary.yes); auto a = alloc.alignedAllocate(2001, 4096); assert(a.length == 2001); + assert((() nothrow @safe @nogc => alloc.empty)() == Ternary.no); + auto oldBrkCurr = alloc._brkCurrent; auto b = alloc.allocate(2001); assert(b.length == 2001); - assert(alloc.owns(a) == Ternary.yes); - assert(alloc.owns(b) == Ternary.yes); + assert((() nothrow @safe @nogc => alloc.expand(b, 0))()); + assert(b.length == 2001); + // Expand with a small size to fit the rounded slack due to alignment + assert((() nothrow @safe @nogc => alloc.expand(b, 1))()); + assert(b.length == 2002); + // Exceed the rounded slack due to alignment + assert((() nothrow @safe @nogc => alloc.expand(b, 10))()); + assert(b.length == 2012); + assert((() nothrow @safe @nogc => alloc.owns(a))() == Ternary.yes); + assert((() nothrow @safe @nogc => alloc.owns(b))() == Ternary.yes); // reducing the brk does not work on OSX version (Darwin) {} else { - assert(alloc.deallocate(b)); - assert(alloc.deallocateAll); + assert((() nothrow @nogc => alloc.deallocate(b))()); + // Check that expand and deallocate work well + assert(oldBrkCurr == alloc._brkCurrent); + assert((() nothrow @nogc => alloc.deallocate(a))()); + assert((() nothrow @nogc => alloc.deallocateAll())()); + } + const void[] c = alloc.allocate(2001); + assert(c.length == 2001); + assert((() nothrow @safe @nogc => alloc.owns(c))() == Ternary.yes); + assert((() nothrow @safe @nogc => alloc.owns(null))() == Ternary.no); +} + +/** +The threadsafe version of the `Region` allocator. +Allocations and deallocations are lock-free based using $(REF cas, core,atomic). +*/ +shared struct SharedRegion(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 `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 shared void* _current, _begin, _end; + + private void* roundedBegin() const pure nothrow @trusted @nogc + { + return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); + } + + private void* roundedEnd() const pure nothrow @trusted @nogc + { + return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); + } + + + /** + Constructs a region backed by a user-provided store. + Assumes the memory was allocated with `ParentAllocator` + (if different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator)). + + Params: + store = User-provided store backing up the region. If `ParentAllocator` + is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), memory is assumed to + have been allocated with `ParentAllocator`. + n = Bytes to allocate using `ParentAllocator`. This constructor is only + defined If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator). If + `parent.allocate(n)` returns `null`, the region will be initialized + as empty (correctly initialized but unable to allocate). + */ + this(ubyte[] store) pure nothrow @nogc + { + _begin = cast(typeof(_begin)) store.ptr; + _end = cast(typeof(_end)) (store.ptr + store.length); + static if (growDownwards) + _current = cast(typeof(_current)) roundedEnd(); + else + _current = cast(typeof(_current)) roundedBegin(); + } + + /// Ditto + static if (!is(ParentAllocator == NullAllocator)) + this(size_t n) + { + this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); + } + + /** + Rounds the given size to a multiple of the `alignment` + */ + size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc + { + return n.roundUpToAlignment(alignment); + } + + /** + Alignment offered. + */ + alias alignment = minAlign; + + /** + Allocates `n` bytes of memory. The allocation is served by atomically incrementing + a pointer which keeps track of the current used space. + + Params: + n = number of bytes to allocate + + Returns: + A properly-aligned buffer of size `n`, or `null` if request could not + be satisfied. + */ + void[] allocate(size_t n) pure nothrow @trusted @nogc + { + import core.atomic : cas, atomicLoad; + + if (n == 0) return null; + const rounded = goodAllocSize(n); + + shared void* localCurrent, localNewCurrent; + static if (growDownwards) + { + do + { + localCurrent = atomicLoad(_current); + localNewCurrent = localCurrent - rounded; + if (localNewCurrent > localCurrent || localNewCurrent < _begin) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) localNewCurrent[0 .. n]; + } + else + { + do + { + localCurrent = atomicLoad(_current); + localNewCurrent = localCurrent + rounded; + if (localNewCurrent < localCurrent || localNewCurrent > _end) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) localCurrent[0 .. n]; + } + + assert(0, "Unexpected error in SharedRegion.allocate"); + } + + /** + Deallocates `b`. This works only if `b` was obtained as the last call + to `allocate`; otherwise (i.e. another allocation has occurred since) it + does nothing. + + Params: + b = Block previously obtained by a call to `allocate` against this + allocator (`null` is allowed). + */ + bool deallocate(void[] b) pure nothrow @nogc + { + import core.atomic : cas, atomicLoad; + + const rounded = goodAllocSize(b.length); + shared void* localCurrent, localNewCurrent; + + // The cas is done only once, because only the last allocation can be reverted + localCurrent = atomicLoad(_current); + static if (growDownwards) + { + localNewCurrent = localCurrent + rounded; + if (b.ptr == localCurrent) + return cas(&_current, localCurrent, localNewCurrent); + } + else + { + localNewCurrent = localCurrent - rounded; + if (b.ptr == localNewCurrent) + return cas(&_current, localCurrent, localNewCurrent); + } + + return false; + } + + /** + Deallocates all memory allocated by this region, which can be subsequently + reused for new allocations. + */ + bool deallocateAll() pure nothrow @nogc + { + import core.atomic : atomicStore; + static if (growDownwards) + { + atomicStore(_current, cast(shared(void*)) roundedEnd()); + } + else + { + atomicStore(_current, cast(shared(void*)) roundedBegin()); + } + return true; + } + + /** + Allocates `n` bytes of memory aligned at alignment `a`. + Params: + n = number of bytes to allocate + a = alignment for the allocated block + + Returns: + Either a suitable block of `n` bytes aligned at `a`, or `null`. + */ + void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc + { + import core.atomic : cas, atomicLoad; + import std.math.traits : isPowerOf2; + + assert(a.isPowerOf2); + if (n == 0) return null; + + const rounded = goodAllocSize(n); + shared void* localCurrent, localNewCurrent; + + static if (growDownwards) + { + do + { + localCurrent = atomicLoad(_current); + auto alignedCurrent = cast(void*)(localCurrent - rounded); + localNewCurrent = cast(shared(void*)) alignedCurrent.alignDownTo(a); + if (alignedCurrent > localCurrent || localNewCurrent > alignedCurrent || + localNewCurrent < _begin) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) localNewCurrent[0 .. n]; + } + else + { + do + { + localCurrent = atomicLoad(_current); + auto alignedCurrent = alignUpTo(cast(void*) localCurrent, a); + localNewCurrent = cast(shared(void*)) (alignedCurrent + rounded); + if (alignedCurrent < localCurrent || localNewCurrent < alignedCurrent || + localNewCurrent > _end) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) (localNewCurrent - rounded)[0 .. n]; + } + + assert(0, "Unexpected error in SharedRegion.alignedAllocate"); + } + + /** + Queries whether `b` has been allocated with this region. + + Params: + b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns + `false`). + + Returns: + `true` if `b` has been allocated with this region, `false` otherwise. + */ + Ternary owns(const void[] b) const pure nothrow @trusted @nogc + { + return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); + } + + /** + Returns `Ternary.yes` if no memory has been allocated in this region, + `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) + */ + Ternary empty() const pure nothrow @safe @nogc + { + import core.atomic : atomicLoad; + + auto localCurrent = atomicLoad(_current); + static if (growDownwards) + return Ternary(localCurrent == roundedEnd()); + else + return Ternary(localCurrent == roundedBegin()); + } + + /** + If `ParentAllocator` is not $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator) and defines `deallocate`, + the region defines a destructor that uses `ParentAllocator.deallocate` to free the + memory chunk. + */ + static if (!is(ParentAllocator == NullAllocator) + && hasMember!(ParentAllocator, "deallocate")) + ~this() + { + parent.deallocate(cast(void[]) _begin[0 .. _end - _begin]); + } +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + static void testAlloc(Allocator)(ref Allocator a, bool growDownwards) + { + import core.thread : ThreadGroup; + import std.algorithm.sorting : sort; + import core.internal.spinlock : SpinLock; + + SpinLock lock = SpinLock(SpinLock.Contention.brief); + enum numThreads = 100; + void[][numThreads] buf; + size_t count = 0; + + void fun() + { + void[] b = a.allocate(63); + assert(b.length == 63); + + lock.lock(); + buf[count] = b; + count++; + lock.unlock(); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); + + sort!((a, b) => a.ptr < b.ptr)(buf[0 .. numThreads]); + foreach (i; 0 .. numThreads - 1) + { + assert(buf[i].ptr + a.goodAllocSize(buf[i].length) == buf[i + 1].ptr); + } + + assert(!a.deallocate(buf[1])); + + foreach (i; 0 .. numThreads) + { + if (!growDownwards) + assert(a.deallocate(buf[numThreads - 1 - i])); + else + assert(a.deallocate(buf[i])); + } + + assert(a.deallocateAll()); + void[] b = a.allocate(63); + assert(b.length == 63); + assert(a.deallocate(b)); + } + + auto a1 = SharedRegion!(Mallocator, Mallocator.alignment, + Yes.growDownwards)(1024 * 64); + + auto a2 = SharedRegion!(Mallocator, Mallocator.alignment, + No.growDownwards)(1024 * 64); + + testAlloc(a1, true); + testAlloc(a2, false); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + static void testAlloc(Allocator)(ref Allocator a, bool growDownwards) + { + import core.thread : ThreadGroup; + import std.algorithm.sorting : sort; + import core.internal.spinlock : SpinLock; + + SpinLock lock = SpinLock(SpinLock.Contention.brief); + enum numThreads = 100; + void[][2 * numThreads] buf; + size_t count = 0; + + void fun() + { + void[] b = a.allocate(63); + assert(b.length == 63); + + lock.lock(); + buf[count] = b; + count++; + lock.unlock(); + + b = a.alignedAllocate(63, 32); + assert(b.length == 63); + assert(cast(size_t) b.ptr % 32 == 0); + + lock.lock(); + buf[count] = b; + count++; + lock.unlock(); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. numThreads) + { + tg.create(&fun); + } + tg.joinAll(); + + sort!((a, b) => a.ptr < b.ptr)(buf[0 .. 2 * numThreads]); + foreach (i; 0 .. 2 * numThreads - 1) + { + assert(buf[i].ptr + buf[i].length <= buf[i + 1].ptr); + } + + assert(!a.deallocate(buf[1])); + assert(a.deallocateAll()); + + void[] b = a.allocate(13); + assert(b.length == 13); + assert(a.deallocate(b)); + } + + auto a1 = SharedRegion!(Mallocator, Mallocator.alignment, + Yes.growDownwards)(1024 * 64); + + auto a2 = SharedRegion!(Mallocator, Mallocator.alignment, + No.growDownwards)(1024 * 64); + + testAlloc(a1, true); + testAlloc(a2, false); } diff --git a/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d index ff3261f..8fc7584 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d @@ -1,25 +1,33 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/scoped_allocator.d) +*/ 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 +`ScopedAllocator` delegates all allocation requests to `ParentAllocator`. +When destroyed, the `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. +`deallocate` is also supported, which is where most implementation effort +and overhead of `ScopedAllocator` go. If `deallocate` is not needed, a +simpler design combining `AllocatorList` with `Region` is recommended. */ struct ScopedAllocator(ParentAllocator) { - @system unittest + static if (!stateSize!ParentAllocator) { - testAllocator!(() => ScopedAllocator()); + // This test is available only for stateless allocators + version (StdUnittest) + @system unittest + { + testAllocator!(() => ScopedAllocator()); + } } import std.experimental.allocator.building_blocks.affix_allocator @@ -38,8 +46,8 @@ struct ScopedAllocator(ParentAllocator) // 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`. + If `ParentAllocator` is stateful, `parent` is a property giving access + to an `AffixAllocator!ParentAllocator`. Otherwise, `parent` is an alias for `AffixAllocator!ParentAllocator.instance`. */ static if (stateSize!ParentAllocator) { @@ -52,12 +60,12 @@ struct ScopedAllocator(ParentAllocator) private Node* root; /** - $(D ScopedAllocator) is not copyable. + `ScopedAllocator` is not copyable. */ @disable this(this); /** - $(D ScopedAllocator)'s destructor releases all memory allocated during its + `ScopedAllocator`'s destructor releases all memory allocated during its lifetime. */ ~this() @@ -69,7 +77,7 @@ struct ScopedAllocator(ParentAllocator) enum alignment = Allocator.alignment; /** - Forwards to $(D parent.goodAllocSize) (which accounts for the management + Forwards to `parent.goodAllocSize` (which accounts for the management overhead). */ size_t goodAllocSize(size_t n) @@ -77,14 +85,10 @@ struct ScopedAllocator(ParentAllocator) 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; + // Common code shared between allocate and allocateZeroed. + private enum _processAndReturnAllocateResult = + q{ + if (!b.ptr) return b; Node* toInsert = & parent.prefix(b); toInsert.prev = null; toInsert.next = root; @@ -93,6 +97,23 @@ struct ScopedAllocator(ParentAllocator) if (root) root.prev = toInsert; root = toInsert; return b; + }; + + /** + Allocates memory. For management it actually allocates extra memory from + the parent. + */ + void[] allocate(size_t n) + { + auto b = parent.allocate(n); + mixin(_processAndReturnAllocateResult); + } + + static if (hasMember!(Allocator, "allocateZeroed")) + package(std) void[] allocateZeroed()(size_t n) + { + auto b = parent.allocateZeroed(n); + mixin(_processAndReturnAllocateResult); } /** @@ -102,15 +123,15 @@ struct ScopedAllocator(ParentAllocator) bool expand(ref void[] b, size_t delta) { auto result = parent.expand(b, delta); - if (result && b.ptr) + if (result && b) { - parent.prefix(b).length = b.length; + () @trusted { parent.prefix(b).length = b.length; }(); } return result; } /** - Reallocates $(D b) to new size $(D s). + Reallocates `b` to new size `s`. */ bool reallocate(ref void[] b, size_t s) { @@ -137,7 +158,7 @@ struct ScopedAllocator(ParentAllocator) } /** - Forwards to $(D parent.owns(b)). + Forwards to `parent.owns(b)`. */ static if (hasMember!(Allocator, "owns")) Ternary owns(void[] b) @@ -146,7 +167,7 @@ struct ScopedAllocator(ParentAllocator) } /** - Deallocates $(D b). + Deallocates `b`. */ static if (hasMember!(Allocator, "deallocate")) bool deallocate(void[] b) @@ -184,6 +205,7 @@ struct ScopedAllocator(ParentAllocator) Returns `Ternary.yes` if this allocator is not responsible for any memory, `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) */ + pure nothrow @safe @nogc Ternary empty() const { return Ternary(root is null); @@ -202,6 +224,7 @@ struct ScopedAllocator(ParentAllocator) assert(alloc.empty == Ternary.no); } +version (StdUnittest) @system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; @@ -219,3 +242,62 @@ struct ScopedAllocator(ParentAllocator) alloc.dispose(foo); alloc.dispose(bar); // segfault here } + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + ScopedAllocator!GCAllocator a; + + assert(__traits(compiles, (() nothrow @safe @nogc => a.goodAllocSize(0))())); + + // Ensure deallocate inherits from parent allocators + auto b = a.allocate(42); + assert(b.length == 42); + () nothrow @nogc { a.deallocate(b); }(); +} + +// Test that deallocateAll infers from parent +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + + ScopedAllocator!(Region!()) a; + a.parent.parent = Region!()(new ubyte[1024 * 64]); + auto b = a.allocate(42); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => a.expand(b, 22))()); + assert(b.length == 64); + assert((() nothrow @nogc => a.reallocate(b, 100))()); + assert(b.length == 100); + assert((() nothrow @nogc => a.deallocateAll())()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + + auto a = Region!(Mallocator)(1024 * 64); + auto b = a.allocate(42); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => a.expand(b, 22))()); + assert(b.length == 64); + assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + assert((() nothrow @nogc => a.reallocate(b, 100))()); + assert(b.length == 100); + assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no); +} + +// Test empty +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + ScopedAllocator!Mallocator alloc; + + assert((() pure nothrow @safe @nogc => alloc.empty)() == Ternary.yes); + const b = alloc.allocate(10); + assert((() pure nothrow @safe @nogc => alloc.empty)() == Ternary.no); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/segregator.d b/libphobos/src/std/experimental/allocator/building_blocks/segregator.d index 76ca6f4..655db456 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/segregator.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/segregator.d @@ -1,21 +1,24 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/segregator.d) +*/ 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). +SmallAllocator) and `LargeAllocator`) depending on the size allocated, as +follows. All allocations smaller than or equal to `threshold` will be +dispatched to `SmallAllocator`. The others will go to `LargeAllocator`. -If both allocators are $(D shared), the $(D Segregator) will also offer $(D +If both allocators are `shared`, the `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.traits : hasMember, ReturnType; import std.typecons : Ternary; static if (stateSize!SmallAllocator) private SmallAllocator _small; @@ -31,73 +34,73 @@ struct Segregator(size_t threshold, SmallAllocator, LargeAllocator) 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 + it. The good allocation size is obtained from `SmallAllocator` if $(D + s <= threshold), or `LargeAllocator` otherwise. (If one of the + allocators does not define `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. + The memory is obtained from `SmallAllocator` if $(D s <= threshold), + or `LargeAllocator` otherwise. */ void[] allocate(size_t); /** This method is defined if both allocators define it, and forwards to - $(D SmallAllocator) or $(D LargeAllocator) appropriately. + `SmallAllocator` or `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). + it. If `SmallAllocator` defines `expand` and $(D b.length + + delta <= threshold), the call is forwarded to `SmallAllocator`. If $(D + LargeAllocator) defines `expand` and $(D b.length > threshold), the + call is forwarded to `LargeAllocator`. Otherwise, the call returns + `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 <= + it. If `SmallAllocator` defines `reallocate` and $(D b.length <= threshold && s <= threshold), the call is forwarded to $(D - SmallAllocator). If $(D LargeAllocator) defines $(D expand) and $(D + SmallAllocator). If `LargeAllocator` defines `expand` and $(D b.length > threshold && s > threshold), the call is forwarded to $(D - LargeAllocator). Otherwise, the call returns $(D false). + LargeAllocator). Otherwise, the call returns `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). + it, and work similarly to `reallocate`. */ - bool alignedReallocate(ref void[] b, size_t s); + bool alignedReallocate(ref void[] b, size_t s, uint a); /** This method is defined only if both allocators define it. The call is - forwarded to $(D SmallAllocator) if $(D b.length <= threshold), or $(D + forwarded to `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). + appropriately depending on `b.length`. */ bool deallocate(void[] b); /** This function is defined only if both allocators define it, and calls - $(D deallocateAll) for them in turn. + `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. + the conjunction of `empty` calls for the two. */ Ternary empty(); } /** - Composite allocators involving nested instantiations of $(D Segregator) make + Composite allocators involving nested instantiations of `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). + inside a `Segregator` that is responsible for a specific size `s`. Example: ---- @@ -195,22 +198,50 @@ struct Segregator(size_t threshold, SmallAllocator, LargeAllocator) static if (hasMember!(SmallAllocator, "alignedReallocate") || hasMember!(LargeAllocator, "alignedReallocate")) - bool reallocate(ref void[] b, size_t s) + bool alignedReallocate(ref void[] b, size_t s, uint a) { static if (hasMember!(SmallAllocator, "alignedReallocate")) if (b.length <= threshold && s <= threshold) { // Old and new allocations handled by _small - return _small.alignedReallocate(b, s); + return _small.alignedReallocate(b, s, a); } static if (hasMember!(LargeAllocator, "alignedReallocate")) if (b.length > threshold && s > threshold) { // Old and new allocations handled by _large - return _large.alignedReallocate(b, s); + return _large.alignedReallocate(b, s, a); } // Cross-allocator transgression - return .alignedReallocate(this, b, s); + return .alignedReallocate(this, b, s, a); + } + + static if (hasMember!(SmallAllocator, "allocateZeroed") + || hasMember!(LargeAllocator, "allocateZeroed")) + package(std) void[] allocateZeroed()(size_t s) + { + if (s <= threshold) + { + static if (hasMember!(SmallAllocator, "allocateZeroed")) + return _small.allocateZeroed(s); + else + { + auto b = _small.allocate(s); + (() @trusted => (cast(ubyte[]) b)[] = 0)(); // OK even if b is null. + return b; + } + } + else + { + static if (hasMember!(LargeAllocator, "allocateZeroed")) + return _large.allocateZeroed(s); + else + { + auto b = _large.allocate(s); + (() @trusted => (cast(ubyte[]) b)[] = 0)(); // OK even if b is null. + return b; + } + } } static if (hasMember!(SmallAllocator, "owns") @@ -242,7 +273,7 @@ struct Segregator(size_t threshold, SmallAllocator, LargeAllocator) && hasMember!(LargeAllocator, "empty")) Ternary empty() { - return _small.empty && _large.empty; + return _small.empty & _large.empty; } static if (hasMember!(SmallAllocator, "resolveInternalPointer") @@ -268,7 +299,7 @@ struct Segregator(size_t threshold, SmallAllocator, LargeAllocator) else { static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator) - static __gshared Segregator instance; + __gshared Segregator instance; mixin Impl!(); } } @@ -296,8 +327,8 @@ struct Segregator(size_t threshold, SmallAllocator, LargeAllocator) } /** -A $(D Segregator) with more than three arguments expands to a composition of -elemental $(D Segregator)s, as illustrated by the following example: +A `Segregator` with more than three arguments expands to a composition of +elemental `Segregator`s, as illustrated by the following example: ---- alias A = @@ -309,11 +340,11 @@ alias A = ); ---- -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) +With this definition, allocation requests for `n1` bytes or less are directed +to `A1`; requests between $(D n1 + 1) and `n2` bytes (inclusive) are +directed to `A2`; requests between $(D n2 + 1) and `n3` bytes (inclusive) +are directed to `A3`; and requests for more than `n3` bytes are directed +to `A4`. If some particular range should not be handled, `NullAllocator` may be used appropriately. */ @@ -359,3 +390,131 @@ if (Args.length > 3) assert(b.length == 201); a.deallocate(b); } + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.building_blocks.kernighan_ritchie : KRRegion; + Segregator!(128, GCAllocator, KRRegion!GCAllocator) alloc; + assert((() nothrow @safe @nogc => alloc.goodAllocSize(1))() + == GCAllocator.instance.goodAllocSize(1)); + + // Note: we infer `shared` from GCAllocator.goodAllocSize so we need a + // shared object in order to be able to use the function + shared Segregator!(128, GCAllocator, GCAllocator) sharedAlloc; + assert((() nothrow @safe @nogc => sharedAlloc.goodAllocSize(1))() + == GCAllocator.instance.goodAllocSize(1)); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.typecons : Ternary; + + alias A = + Segregator!( + 128, BitmappedBlock!(4096), + BitmappedBlock!(4096) + ); + + A a = A( + BitmappedBlock!(4096)(new ubyte[4096 * 1024]), + BitmappedBlock!(4096)(new ubyte[4096 * 1024]) + ); + + assert(a.empty == Ternary.yes); + auto b = a.allocate(42); + assert(b.length == 42); + assert(a.empty == Ternary.no); + assert(a.alignedReallocate(b, 256, 512)); + assert(b.length == 256); + assert(a.alignedReallocate(b, 42, 512)); + assert(b.length == 42); + assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes); + assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no); + // Ensure deallocate inherits from parent allocators + assert((() nothrow @nogc => a.deallocate(b))()); + assert(a.empty == Ternary.yes); + + // Test that deallocateAll inherits from parents + auto c = a.allocate(42); + assert(c.length == 42); + assert((() pure nothrow @safe @nogc => a.expand(c, 58))()); + assert(c.length == 100); + assert(a.empty == Ternary.no); + assert((() nothrow @nogc => a.deallocateAll())()); + assert(a.empty == Ternary.yes); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + + shared Segregator!(1024 * 4, GCAllocator, GCAllocator) a; + + auto b = a.allocate(201); + assert(b.length == 201); + + void[] p; + assert((() nothrow @safe @nogc => a.resolveInternalPointer(&b[0], p))() == Ternary.yes); + assert((() nothrow @safe @nogc => a.resolveInternalPointer(null, p))() == Ternary.no); + + // Ensure deallocate inherits from parent allocators + assert((() nothrow @nogc => a.deallocate(b))()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlockWithInternalPointers; + import std.typecons : Ternary; + + alias A = + Segregator!( + 10_240, BitmappedBlockWithInternalPointers!(4096), + BitmappedBlockWithInternalPointers!(4096) + ); + + A a = A( + BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]), + BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]) + ); + + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); + auto b = a.allocate(201); + assert(b.length == 201); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); + assert((() nothrow @nogc => a.deallocate(b))()); +} + +// Test that reallocate infers from parent +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + alias a = Segregator!(10_240, Mallocator, Mallocator).instance; + + auto b = a.allocate(42); + assert(b.length == 42); + assert((() nothrow @nogc => a.reallocate(b, 100))()); + assert(b.length == 100); + assert((() nothrow @nogc => a.deallocate(b))()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.typecons : Ternary; + + auto a = Segregator!(10_240, Region!(), Region!())( + Region!()(new ubyte[4096 * 1024]), + Region!()(new ubyte[4096 * 1024])); + + assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); + auto b = a.alignedAllocate(42, 8); + assert(b.length == 42); + assert((() nothrow @nogc => a.alignedReallocate(b, 100, 8))()); + assert(b.length == 100); + assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); + assert((() nothrow @nogc => 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 index aba084e..d57b3ed 100644 --- a/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d +++ b/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d @@ -4,60 +4,63 @@ 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); ----- +Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/stats_collector.d) */ module std.experimental.allocator.building_blocks.stats_collector; +/// +@safe unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.building_blocks.free_list : FreeList; + alias Allocator = StatsCollector!(GCAllocator, Options.bytesUsed); +} + import std.experimental.allocator.common; /** -_Options for $(D StatsCollector) defined below. Each enables during +_Options for `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). + Counts the number of calls to `owns`. */ numOwns = 1u << 0, /** - Counts the number of calls to $(D allocate). All calls are counted, + Counts the number of calls to `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 + Counts the number of calls to `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 + Counts the number of calls to `expand`, regardless of arguments or result. */ numExpand = 1u << 3, /** - Counts the number of calls to $(D expand) that resulted in a successful + Counts the number of calls to `expand` that resulted in a successful expansion. */ numExpandOK = 1u << 4, /** - Counts the number of calls to $(D reallocate), regardless of arguments or + Counts the number of calls to `reallocate`, regardless of arguments or result. */ numReallocate = 1u << 5, /** - Counts the number of calls to $(D reallocate) that succeeded. + Counts the number of calls to `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 + Counts the number of calls to `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 @@ -66,91 +69,102 @@ enum Options : ulong */ numReallocateInPlace = 1u << 7, /** - Counts the number of calls to $(D deallocate). + Counts the number of calls to `deallocate`. */ numDeallocate = 1u << 8, /** - Counts the number of calls to $(D deallocateAll). + Counts the number of calls to `deallocateAll`. */ numDeallocateAll = 1u << 9, /** - Chooses all $(D numXxx) flags. + Counts the number of calls to `alignedAllocate`. All calls are counted, + including requests for zero bytes or failed requests. + */ + numAlignedAllocate = 1u << 10, + /** + Counts the number of calls to `alignedAllocate` that succeeded, i.e. they + returned a block as large as requested. (N.B. requests for zero bytes count + as successful.) */ - numAll = (1u << 10) - 1, + numAlignedAllocateOk = 1u << 11, + /** + Chooses all `numXxx` flags. + */ + numAll = (1u << 12) - 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, + bytesUsed = 1u << 12, /** - Tracks total cumulative bytes allocated by means of $(D allocate), - $(D expand), and $(D reallocate) (when resulting in an expansion). This + Tracks total cumulative bytes allocated by means of `allocate`, + `expand`, and `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). + deallocated cumulatively, subtract `bytesUsed` from `bytesAllocated`. */ - bytesAllocated = 1u << 11, + bytesAllocated = 1u << 13, /** - Tracks the sum of all $(D delta) values in calls of the form - $(D expand(b, delta)) that succeed (return $(D true)). + Tracks the sum of all `delta` values in calls of the form + $(D expand(b, delta)) that succeed (return `true`). */ - bytesExpanded = 1u << 12, + bytesExpanded = 1u << 14, /** 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 + the form $(D realloc(b, s)) that succeed (return `true`). In per-call statistics, also unambiguously counts the bytes deallocated with - $(D deallocate). + `deallocate`. */ - bytesContracted = 1u << 13, + bytesContracted = 1u << 15, /** - Tracks the sum of all bytes moved as a result of calls to $(D realloc) that + Tracks the sum of all bytes moved as a result of calls to `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, + bytesMoved = 1u << 16, /** - Tracks the sum of all bytes NOT moved as result of calls to $(D realloc) + Tracks the sum of all bytes NOT moved as result of calls to `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 + small and `bytesSlack` is high, it means the application is overallocating for little benefit. */ - bytesNotMoved = 1u << 15, + bytesNotMoved = 1u << 17, /** 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, + bytesSlack = 1u << 18, /** Measures the maximum bytes allocated over the time. This is useful for dimensioning allocators. */ - bytesHighTide = 1u << 17, + bytesHighTide = 1u << 19, /** - Chooses all $(D byteXxx) flags. + Chooses all `byteXxx` flags. */ - bytesAll = ((1u << 18) - 1) & ~numAll, + bytesAll = ((1u << 20) - 1) & ~numAll, /** Combines all flags above. */ - all = (1u << 18) - 1 + all = (1u << 20) - 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). +or disabled through compile-time `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) +All stats of the form `numXxx` record counts of events occurring, such as +calls to functions and specific results. The stats of the form `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. +In addition, the data `callerSize`, `callerModule`, `callerFile`, $(D +callerLine), and `callerTime` is associated with each specific allocation. This data prefixes each allocation. */ @@ -188,7 +202,7 @@ private: version (StdDdoc) { /** - Read-only properties enabled by the homonym $(D flags) chosen by the + Read-only properties enabled by the homonym `flags` chosen by the user. Example: @@ -225,6 +239,10 @@ private: /// Ditto @property ulong numDeallocateAll() const; /// Ditto + @property ulong numAlignedAllocate() const; + /// Ditto + @property ulong numAlignedAllocateOk() const; + /// Ditto @property ulong bytesUsed() const; /// Ditto @property ulong bytesAllocated() const; @@ -264,6 +282,8 @@ private: "numReallocateInPlace", "numDeallocate", "numDeallocateAll", + "numAlignedAllocate", + "numAlignedAllocateOk", "bytesUsed", "bytesAllocated", "bytesExpanded", @@ -276,11 +296,11 @@ private: public: - /// Alignment offered is equal to $(D Allocator.alignment). + /// Alignment offered is equal to `Allocator.alignment`. alias alignment = Allocator.alignment; /** - Increments $(D numOwns) (per instance and and per call) and forwards to $(D + Increments `numOwns` (per instance and and per call) and forwards to $(D parent.owns(b)). */ static if (hasMember!(Allocator, "owns")) @@ -289,7 +309,7 @@ public: Ternary owns(void[] b) { return ownsImpl(b); } else - Ternary owns(string f = __FILE, uint n = line)(void[] b) + Ternary owns(string f = __FILE__, uint n = __LINE__)(void[] b) { return ownsImpl!(f, n)(b); } } @@ -301,10 +321,10 @@ public: } /** - 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). + Forwards to `parent.allocate`. Affects per instance: `numAllocate`, + `bytesUsed`, `bytesAllocated`, `bytesSlack`, `numAllocateOK`, + and `bytesHighTide`. Affects per call: `numAllocate`, $(D + numAllocateOK), and `bytesAllocated`. */ static if (!(perCallFlags & (Options.numAllocate | Options.numAllocateOK @@ -320,9 +340,9 @@ public: { return allocateImpl!(f, n)(bytes); } } - private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes) - { - auto result = parent.allocate(bytes); + // Common code currently shared between allocateImpl and allocateZeroedImpl. + private enum _updateStatsForAllocateResult = + q{ add!"bytesUsed"(result.length); add!"bytesAllocated"(result.length); immutable slack = this.goodAllocSize(result.length) - result.length; @@ -331,15 +351,92 @@ public: add!"numAllocateOK"(result.length == bytes); // allocating 0 bytes is OK addPerCall!(f, n, "numAllocate", "numAllocateOK", "bytesAllocated") (1, result.length == bytes, result.length); + }; + + private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes) + { + auto result = parent.allocate(bytes); + mixin(_updateStatsForAllocateResult); + return result; + } + + static if (hasMember!(Allocator, "allocateZeroed")) + { + static if (!(perCallFlags + & (Options.numAllocate | Options.numAllocateOK + | Options.bytesAllocated))) + { + package(std) void[] allocateZeroed()(size_t n) + { return allocateZeroedImpl(n); } + } + else + { + package(std) void[] allocateZeroed(string f = __FILE__, ulong n = __LINE__) + (size_t bytes) + { return allocateZeroedImpl!(f, n)(bytes); } + } + + private void[] allocateZeroedImpl(string f = null, ulong n = 0)(size_t bytes) + { + auto result = parent.allocateZeroed(bytes); + // Note: calls to `allocateZeroed` are counted for statistical purposes + // as if they were calls to `allocate`. If/when `allocateZeroed` is made + // public it might be of interest to count such calls separately. + mixin(_updateStatsForAllocateResult); + return result; + } + } + + /** + Forwards to `parent.alignedAllocate`. Affects per instance: `numAlignedAllocate`, + `bytesUsed`, `bytesAllocated`, `bytesSlack`, `numAlignedAllocateOk`, + and `bytesHighTide`. Affects per call: `numAlignedAllocate`, `numAlignedAllocateOk`, + and `bytesAllocated`. + */ + static if (!(perCallFlags + & (Options.numAlignedAllocate | Options.numAlignedAllocateOk + | Options.bytesAllocated))) + { + void[] alignedAllocate(size_t n, uint a) + { return alignedAllocateImpl(n, a); } + } + else + { + void[] alignedAllocate(string f = __FILE__, ulong n = __LINE__) + (size_t bytes, uint a) + { return alignedAllocateImpl!(f, n)(bytes, a); } + } + + private void[] alignedAllocateImpl(string f = null, ulong n = 0)(size_t bytes, uint a) + { + up!"numAlignedAllocate"; + static if (!hasMember!(Allocator, "alignedAllocate")) + { + if (bytes == 0) + up!"numAlignedAllocateOk"; + void[] result = null; + } + else + { + auto result = parent.alignedAllocate(bytes, a); + add!"bytesUsed"(result.length); + add!"bytesAllocated"(result.length); + immutable slack = this.goodAllocSize(result.length) - result.length; + add!"bytesSlack"(slack); + add!"numAlignedAllocateOk"(result.length == bytes); // allocating 0 bytes is OK + } + addPerCall!(f, n, "numAlignedAllocate", "numAlignedAllocateOk", "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). + Defined whether or not `Allocator.expand` is defined. Affects + per instance: `numExpand`, `numExpandOK`, `bytesExpanded`, + `bytesSlack`, `bytesAllocated`, and `bytesUsed`. Affects per call: + `numExpand`, `numExpandOK`, `bytesExpanded`, and + `bytesAllocated`. */ static if (!(perCallFlags & (Options.numExpand | Options.numExpandOK | Options.bytesExpanded))) @@ -385,13 +482,13 @@ public: } /** - 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). + Defined whether or not `Allocator.reallocate` is defined. Affects + per instance: `numReallocate`, `numReallocateOK`, $(D + numReallocateInPlace), `bytesNotMoved`, `bytesAllocated`, $(D + bytesSlack), `bytesExpanded`, and `bytesContracted`. Affects per call: + `numReallocate`, `numReallocateOK`, `numReallocateInPlace`, + `bytesNotMoved`, `bytesExpanded`, `bytesContracted`, and + `bytesMoved`. */ static if (!(perCallFlags & (Options.numReallocate | Options.numReallocateOK @@ -465,9 +562,9 @@ public: } /** - 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). + Defined whether or not `Allocator.deallocate` is defined. Affects + per instance: `numDeallocate`, `bytesUsed`, and `bytesSlack`. + Affects per call: `numDeallocate` and `bytesContracted`. */ static if (!(perCallFlags & (Options.numDeallocate | Options.bytesContracted))) @@ -492,8 +589,8 @@ public: static if (hasMember!(Allocator, "deallocateAll")) { /** - Defined only if $(D Allocator.deallocateAll) is defined. Affects - per instance and per call $(D numDeallocateAll). + Defined only if `Allocator.deallocateAll` is defined. Affects + per instance and per call `numDeallocateAll`. */ static if (!(perCallFlags & Options.numDeallocateAll)) bool deallocateAll() @@ -513,19 +610,20 @@ public: } /** - Defined only if $(D Options.bytesUsed) is defined. Returns $(D bytesUsed == + Defined only if `Options.bytesUsed` is defined. Returns $(D bytesUsed == 0). */ static if (flags & Options.bytesUsed) + pure nothrow @safe @nogc Ternary empty() { return Ternary(_bytesUsed == 0); } /** - Reports per instance statistics to $(D output) (e.g. $(D stdout)). The + Reports per instance statistics to `output` (e.g. `stdout`). The format is simple: one kind and value per line, separated by a colon, e.g. - $(D bytesAllocated:7395404) + `bytesAllocated:7395404` */ void reportStatistics(R)(auto ref R output) { @@ -542,7 +640,7 @@ public: static if (perCallFlags) { /** - Defined if $(D perCallFlags) is nonzero. + Defined if `perCallFlags` is nonzero. */ struct PerCallStatistics { @@ -552,7 +650,7 @@ public: uint line; /// The options corresponding to the statistics collected. Options[] opts; - /// The values of the statistics. Has the same length as $(D opts). + /// The values of the statistics. Has the same length as `opts`. ulong[] values; // Next in the chain. private PerCallStatistics* next; @@ -578,7 +676,7 @@ public: private static PerCallStatistics* root; /** - Defined if $(D perCallFlags) is nonzero. Iterates all monitored + Defined if `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. @@ -597,7 +695,7 @@ public: } /** - Defined if $(D perCallFlags) is nonzero. Outputs (e.g. to a $(D File)) + Defined if `perCallFlags` is nonzero. Outputs (e.g. to a `File`) a simple report of the collected per-call statistics. */ static void reportPerCallStatistics(R)(auto ref R output) @@ -631,7 +729,7 @@ public: private void addPerCall(string f, uint n, names...)(ulong[] values...) { import std.array : join; - enum uint mask = mixin("Options."~[names].join("|Options.")); + enum ulong mask = mixin("Options."~[names].join("|Options.")); static if (perCallFlags & mask) { // Per allocation info @@ -673,7 +771,7 @@ public: scope(exit) remove(f); Allocator.reportPerCallStatistics(File(f, "w")); alloc.reportStatistics(File(f, "a")); - assert(File(f).byLine.walkLength == 22); + assert(File(f).byLine.walkLength == 24); } @system unittest @@ -681,11 +779,13 @@ public: void test(Allocator)() { import std.range : walkLength; - import std.stdio : writeln; + import std.typecons : Ternary; + Allocator a; + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); auto b1 = a.allocate(100); assert(a.numAllocate == 1); - assert(a.expand(b1, 0)); + assert((() nothrow @safe => a.expand(b1, 0))()); assert(a.reallocate(b1, b1.length + 1)); auto b2 = a.allocate(101); assert(a.numAllocate == 2); @@ -694,12 +794,13 @@ public: auto b3 = a.allocate(202); assert(a.numAllocate == 3); assert(a.bytesAllocated == 404); + assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no); - a.deallocate(b2); + () nothrow @nogc { a.deallocate(b2); }(); assert(a.numDeallocate == 1); - a.deallocate(b1); + () nothrow @nogc { a.deallocate(b1); }(); assert(a.numDeallocate == 2); - a.deallocate(b3); + () nothrow @nogc { a.deallocate(b3); }(); assert(a.numDeallocate == 3); assert(a.numAllocate == a.numDeallocate); assert(a.bytesUsed == 0); @@ -717,19 +818,73 @@ public: void test(Allocator)() { import std.range : walkLength; - import std.stdio : writeln; Allocator a; auto b1 = a.allocate(100); - assert(a.expand(b1, 0)); + assert((() nothrow @safe => 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); + () nothrow @nogc { a.deallocate(b2); }(); + () nothrow @nogc { a.deallocate(b1); }(); + () nothrow @nogc { a.deallocate(b3); }(); } import std.experimental.allocator.building_blocks.free_list : FreeList; import std.experimental.allocator.gc_allocator : GCAllocator; test!(StatsCollector!(GCAllocator, 0, 0)); } + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + StatsCollector!(GCAllocator, 0, 0) a; + + // calls std.experimental.allocator.common.goodAllocSize + assert((() pure nothrow @safe @nogc => a.goodAllocSize(1))()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + + auto a = StatsCollector!(Region!(), Options.all, Options.all)(Region!()(new ubyte[1024 * 64])); + auto b = a.allocate(42); + assert(b.length == 42); + // Test that reallocate infers from parent + assert((() nothrow @nogc => a.reallocate(b, 100))()); + assert(b.length == 100); + // Test that deallocateAll infers from parent + assert((() nothrow @nogc => a.deallocateAll())()); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + + auto a = StatsCollector!(Region!(), Options.all)(Region!()(new ubyte[1024 * 64])); + auto b = a.alignedAllocate(42, 128); + assert(b.length == 42); + assert(b.ptr.alignedAt(128)); + assert(a.numAlignedAllocate == 1); + assert(a.numAlignedAllocateOk == 1); + assert(a.bytesUsed == 42); + + b = a.alignedAllocate(23, 256); + assert(b.length == 23); + assert(b.ptr.alignedAt(256)); + assert(a.numAlignedAllocate == 2); + assert(a.numAlignedAllocateOk == 2); + assert(a.bytesUsed == 65); + + b = a.alignedAllocate(0, 512); + assert(b.length == 0); + assert(a.numAlignedAllocate == 3); + assert(a.numAlignedAllocateOk == 3); + assert(a.bytesUsed == 65); + + b = a.alignedAllocate(1024 * 1024, 512); + assert(b is null); + assert(a.numAlignedAllocate == 4); + assert(a.numAlignedAllocateOk == 3); + assert(a.bytesUsed == 65); +} diff --git a/libphobos/src/std/experimental/allocator/common.d b/libphobos/src/std/experimental/allocator/common.d index 0eec0d3..8acd763 100644 --- a/libphobos/src/std/experimental/allocator/common.d +++ b/libphobos/src/std/experimental/allocator/common.d @@ -1,16 +1,19 @@ +// Written in the D programming language. /** 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`) + +Source: $(PHOBOSSRC std/experimental/allocator/common.d) */ 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 +object of type `T`. `stateSize!T` is zero for `struct`s that are not nested and have no nonstatic member variables. */ template stateSize(T) @@ -54,7 +57,7 @@ template hasStaticallyKnownAlignment(Allocator) } /** -$(D chooseAtRuntime) is a compile-time constant of type $(D size_t) that several +`chooseAtRuntime` is a compile-time constant of type `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 @@ -64,11 +67,11 @@ 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 +`unbounded` is a compile-time constant of type `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 +the parameter. For example, `Freelist` (described in detail below) accepts a +`maxNodes` parameter limiting the number of freelist items. If `unbounded` +is passed for `maxNodes`, then there is no limit and no checking for the number of nodes. */ enum unbounded = size_t.max; @@ -80,7 +83,7 @@ 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 +The default good size allocation is deduced as `n` rounded up to the allocator's alignment. */ size_t goodAllocSize(A)(auto ref A a, size_t n) @@ -88,7 +91,7 @@ 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 @@ -108,13 +111,13 @@ unittest 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; + import std.math.traits : isPowerOf2; assert(alignment.isPowerOf2); immutable uint slack = cast(uint) n & (alignment - 1); const result = slack @@ -133,13 +136,13 @@ unittest 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; + import std.math.traits : isPowerOf2; assert(alignment.isPowerOf2); return n & ~size_t(alignment - 1); } @@ -153,7 +156,7 @@ unittest 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. @@ -177,7 +180,7 @@ package void[] roundUpToAlignment(void[] b, uint a) assert(roundUpToAlignment(buf, 128) !is null); } -/** +/* Like `a / b` but rounds the result up, not down. */ @safe @nogc nothrow pure @@ -187,7 +190,7 @@ package size_t divideRoundUp(size_t a, size_t b) return (a + b - 1) / b; } -/** +/* Returns `s` rounded up to a multiple of `base`. */ @nogc nothrow pure @@ -209,8 +212,8 @@ nothrow pure assert(roundStartToMultipleOf(p, 16) is p); } -/** -Returns $(D s) rounded up to the nearest power of 2. +/* +Returns `s` rounded up to the nearest power of 2. */ @safe @nogc nothrow pure package size_t roundUpToPowerOf2(size_t s) @@ -246,18 +249,14 @@ unittest assert(((size_t.max >> 1) + 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1); } -/** -Returns the number of trailing zeros of $(D x). +/* +Returns the number of trailing zeros of `x`. */ @safe @nogc nothrow pure package uint trailingZeros(ulong x) { - uint result; - while (result < 64 && !(x & (1UL << result))) - { - ++result; - } - return result; + import core.bitop : bsf; + return x == 0 ? 64 : bsf(x); } @safe @nogc nothrow pure @@ -270,7 +269,7 @@ unittest assert(trailingZeros(4) == 2); } -/** +/* Returns `true` if `ptr` is aligned at `alignment`. */ @nogc nothrow pure @@ -279,14 +278,14 @@ 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) +package size_t effectiveAlignment(void* ptr) { - return 1U << trailingZeros(cast(size_t) ptr); + return (cast(size_t) 1) << trailingZeros(cast(size_t) ptr); } @nogc nothrow pure @@ -294,28 +293,31 @@ package uint effectiveAlignment(void* ptr) { int x; assert(effectiveAlignment(&x) >= int.alignof); + + const max = (cast(size_t) 1) << (size_t.sizeof * 8 - 1); + assert(effectiveAlignment(cast(void*) max) == max); } -/** +/* 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) +package void* alignDownTo(return scope void* ptr, uint alignment) { - import std.math : isPowerOf2; + import std.math.traits : 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) +package void* alignUpTo(return scope void* ptr, uint alignment) { - import std.math : isPowerOf2; + import std.math.traits : isPowerOf2; assert(alignment.isPowerOf2); immutable uint slack = cast(size_t) ptr & (alignment - 1U); return slack ? ptr + alignment - slack : ptr; @@ -324,27 +326,27 @@ package void* alignUpTo(void* ptr, uint alignment) @safe @nogc nothrow pure package bool isGoodStaticAlignment(uint x) { - import std.math : isPowerOf2; + import std.math.traits : isPowerOf2; return x.isPowerOf2; } @safe @nogc nothrow pure package bool isGoodDynamicAlignment(uint x) { - import std.math : isPowerOf2; + import std.math.traits : 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) +The default `reallocate` function first attempts to use `expand`. If $(D +Allocator.expand) is not defined or returns `false`, `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 +block to the new block. Finally, if `Allocator` defines `deallocate`, $(D reallocate) uses it to free the old memory block. -$(D reallocate) does not attempt to use $(D Allocator.reallocate) even if +`reallocate` does not attempt to use `Allocator.reallocate` even if defined. This is deliberate so allocators may use it internally within their own -implementation of $(D reallocate). +implementation of `reallocate`. */ bool reallocate(Allocator)(ref Allocator a, ref void[] b, size_t s) @@ -366,20 +368,21 @@ bool reallocate(Allocator)(ref Allocator a, ref void[] b, size_t s) /** -The default $(D alignedReallocate) function first attempts to use $(D expand). -If $(D Allocator.expand) is not defined or returns $(D false), $(D +The default `alignedReallocate` function first attempts to use `expand`. +If `Allocator.expand` is not defined or returns `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 +copies data from the old block to the new block. Finally, if `Allocator` +defines `deallocate`, `alignedReallocate` uses it to free the old memory block. -$(D alignedReallocate) does not attempt to use $(D Allocator.reallocate) even if +`alignedReallocate` does not attempt to use `Allocator.reallocate` even if defined. This is deliberate so allocators may use it internally within their own -implementation of $(D reallocate). +implementation of `reallocate`. */ bool alignedReallocate(Allocator)(ref Allocator alloc, ref void[] b, size_t s, uint a) +if (hasMember!(Allocator, "alignedAllocate")) { static if (hasMember!(Allocator, "expand")) { @@ -388,9 +391,10 @@ bool alignedReallocate(Allocator)(ref Allocator alloc, } else { - if (b.length == s) return true; + if (b.length == s && b.ptr.alignedAt(a)) return true; } auto newB = alloc.alignedAllocate(s, a); + 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")) @@ -399,6 +403,63 @@ bool alignedReallocate(Allocator)(ref Allocator alloc, return true; } +@system unittest +{ + bool called = false; + struct DummyAllocator + { + void[] alignedAllocate(size_t size, uint alignment) + { + called = true; + return null; + } + } + + struct DummyAllocatorExpand + { + void[] alignedAllocate(size_t size, uint alignment) + { + return null; + } + + bool expand(ref void[] b, size_t length) + { + called = true; + return true; + } + } + + char[128] buf; + uint alignment = 32; + auto alignedPtr = roundUpToMultipleOf(cast(size_t) buf.ptr, alignment); + auto diff = alignedPtr - cast(size_t) buf.ptr; + + // Align the buffer to 'alignment' + void[] b = cast(void[]) (buf.ptr + diff)[0 .. buf.length - diff]; + + DummyAllocator a1; + // Ask for same length and alignment, should not call 'alignedAllocate' + assert(alignedReallocate(a1, b, b.length, alignment)); + assert(!called); + + // Ask for same length, different alignment + // should call 'alignedAllocate' if not aligned to new value + alignedReallocate(a1, b, b.length, alignment + 1); + assert(b.ptr.alignedAt(alignment + 1) || called); + called = false; + + DummyAllocatorExpand a2; + // Ask for bigger length, same alignment, should call 'expand' + assert(alignedReallocate(a2, b, b.length + 1, alignment)); + assert(called); + called = false; + + // Ask for bigger length, different alignment + // should call 'alignedAllocate' if not aligned to new value + alignedReallocate(a2, b, b.length + 1, alignment + 1); + assert(b.ptr.alignedAt(alignment + 1) || !called); +} + /** Forwards each of the methods in `funs` (if defined) to `member`. */ @@ -417,14 +478,13 @@ Forwards each of the methods in `funs` (if defined) to `member`. return result; } -version (unittest) +version (StdUnittest) { - import std.experimental.allocator : IAllocator, ISharedAllocator; package void testAllocator(alias make)() { import std.conv : text; - import std.math : isPowerOf2; + import std.math.traits : isPowerOf2; import std.stdio : writeln, stderr; import std.typecons : Ternary; alias A = typeof(make()); @@ -450,6 +510,18 @@ version (unittest) assert(b2.length == 2); assert(b2.ptr + b2.length <= b1.ptr || b1.ptr + b1.length <= b2.ptr); + // Test allocateZeroed + static if (hasMember!(A, "allocateZeroed")) + {{ + auto b3 = a.allocateZeroed(8); + if (b3 !is null) + { + assert(b3.length == 8); + foreach (e; cast(ubyte[]) b3) + assert(e == 0); + } + }} + // Test alignedAllocate static if (hasMember!(A, "alignedAllocate")) {{ @@ -545,18 +617,22 @@ version (unittest) }} } - package void testAllocatorObject(AllocInterface)(AllocInterface a) - if (is(AllocInterface : IAllocator) - || is (AllocInterface : shared ISharedAllocator)) + package void testAllocatorObject(RCAllocInterface)(RCAllocInterface a) { + // this used to be a template constraint, but moving it inside prevents + // unnecessary import of std.experimental.allocator + import std.experimental.allocator : RCIAllocator, RCISharedAllocator; + static assert(is(RCAllocInterface == RCIAllocator) + || is (RCAllocInterface == RCISharedAllocator)); + import std.conv : text; - import std.math : isPowerOf2; + import std.math.traits : isPowerOf2; import std.stdio : writeln, stderr; import std.typecons : Ternary; scope(failure) stderr.writeln("testAllocatorObject failed for ", - AllocInterface.stringof); + RCAllocInterface.stringof); - assert(a); + assert(!a.isNull); // Test alignment assert(a.alignment.isPowerOf2); diff --git a/libphobos/src/std/experimental/allocator/gc_allocator.d b/libphobos/src/std/experimental/allocator/gc_allocator.d index 4189456..d356c73 100644 --- a/libphobos/src/std/experimental/allocator/gc_allocator.d +++ b/libphobos/src/std/experimental/allocator/gc_allocator.d @@ -1,28 +1,33 @@ -/// +// Written in the D programming language. +/** +D's built-in garbage-collected allocator. + +Source: $(PHOBOSSRC std/experimental/allocator/_gc_allocator.d) +*/ 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); } + version (StdUnittest) @system unittest { testAllocator!(() => GCAllocator.instance); } /** - The alignment is a static constant equal to $(D platformAlignment), which + The alignment is a static constant equal to `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 + deallocate) and `reallocate` methods are `@system` because they may move memory around, leaving dangling pointers in user code. */ - pure nothrow @trusted void[] allocate(size_t bytes) shared + pure nothrow @trusted void[] allocate(size_t bytes) shared const { if (!bytes) return null; auto p = GC.malloc(bytes); @@ -30,7 +35,7 @@ struct GCAllocator } /// Ditto - @system bool expand(ref void[] b, size_t delta) shared + pure nothrow @trusted bool expand(ref void[] b, size_t delta) shared const { if (delta == 0) return true; if (b is null) return false; @@ -53,7 +58,7 @@ struct GCAllocator } /// Ditto - pure nothrow @system bool reallocate(ref void[] b, size_t newSize) shared + pure nothrow @system bool reallocate(ref void[] b, size_t newSize) shared const { import core.exception : OutOfMemoryError; try @@ -70,8 +75,8 @@ struct GCAllocator } /// Ditto - pure nothrow - Ternary resolveInternalPointer(const void* p, ref void[] result) shared + pure nothrow @trusted @nogc + Ternary resolveInternalPointer(const void* p, ref void[] result) shared const { auto r = GC.addrOf(cast(void*) p); if (!r) return Ternary.no; @@ -80,14 +85,16 @@ struct GCAllocator } /// Ditto - pure nothrow @system bool deallocate(void[] b) shared + pure nothrow @system @nogc + bool deallocate(void[] b) shared const { GC.free(b.ptr); return true; } /// Ditto - size_t goodAllocSize(size_t n) shared + pure nothrow @safe @nogc + size_t goodAllocSize(size_t n) shared const { if (n == 0) return 0; @@ -104,23 +111,30 @@ struct GCAllocator return ((n + 4095) / 4096) * 4096; } + package pure nothrow @trusted void[] allocateZeroed()(size_t bytes) shared const + { + if (!bytes) return null; + auto p = GC.calloc(bytes); + return p ? p[0 .. bytes] : null; + } + /** 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). + are `shared`. */ - static shared GCAllocator instance; + static shared const GCAllocator instance; // Leave it undocummented for now. - nothrow @trusted void collect() shared + nothrow @trusted void collect() shared const { GC.collect(); } } /// -@system unittest +pure @system unittest { auto buffer = GCAllocator.instance.allocate(1024 * 1024 * 4); // deallocate upon scope's end (alternatively: leave it to collection) @@ -128,40 +142,58 @@ struct GCAllocator //... } -@system unittest +pure @safe unittest { auto b = GCAllocator.instance.allocate(10_000); assert(GCAllocator.instance.expand(b, 1)); } -@system unittest +pure @system unittest { import core.memory : GC; import std.typecons : Ternary; // test allocation sizes - assert(GCAllocator.instance.goodAllocSize(1) == 16); + assert((() nothrow @safe @nogc => 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); + assert((() nothrow @safe @nogc => GCAllocator.instance.goodAllocSize(s))() == s); + assert((() nothrow @safe @nogc => GCAllocator.instance.goodAllocSize(s - (s / 2) + 1))() == s); auto buffer = GCAllocator.instance.allocate(s); - scope(exit) GCAllocator.instance.deallocate(buffer); + scope(exit) () nothrow @nogc { GCAllocator.instance.deallocate(buffer); }(); void[] p; - assert(GCAllocator.instance.resolveInternalPointer(null, p) == Ternary.no); - Ternary r = GCAllocator.instance.resolveInternalPointer(buffer.ptr, p); + assert((() nothrow @safe => GCAllocator.instance.resolveInternalPointer(null, p))() == Ternary.no); + assert((() nothrow @safe => GCAllocator.instance.resolveInternalPointer(&buffer[0], p))() == Ternary.yes); 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); + // the GC should provide power of 2 as "good" sizes, but other sizes are allowed, too + version (none) + { + auto buffer2 = GCAllocator.instance.allocate(s - (s / 2) + 1); + scope(exit) () nothrow @nogc { GCAllocator.instance.deallocate(buffer2); }(); - assert(GC.sizeOf(buffer2.ptr) == s); + 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); + assert((() nothrow @safe @nogc => GCAllocator.instance.goodAllocSize(4096 * 4 + 1))() == 4096 * 5); +} + +pure nothrow @safe unittest +{ + import std.typecons : Ternary; + + void[] buffer = GCAllocator.instance.allocate(42); + void[] result; + Ternary found = GCAllocator.instance.resolveInternalPointer(&buffer[0], result); + + assert(found == Ternary.yes && &result[0] == &buffer[0] && result.length >= buffer.length); + assert(GCAllocator.instance.resolveInternalPointer(null, result) == Ternary.no); + void *badPtr = (() @trusted => cast(void*)(0xdeadbeef))(); + assert(GCAllocator.instance.resolveInternalPointer(badPtr, result) == Ternary.no); } diff --git a/libphobos/src/std/experimental/allocator/mallocator.d b/libphobos/src/std/experimental/allocator/mallocator.d index 2d1dec3..895d588 100644 --- a/libphobos/src/std/experimental/allocator/mallocator.d +++ b/libphobos/src/std/experimental/allocator/mallocator.d @@ -1,4 +1,9 @@ -/// +// Written in the D programming language. +/** +The C heap allocator. + +Source: $(PHOBOSSRC std/experimental/allocator/mallocator.d) +*/ module std.experimental.allocator.mallocator; import std.experimental.allocator.common; @@ -7,44 +12,44 @@ import std.experimental.allocator.common; */ struct Mallocator { - @system unittest { testAllocator!(() => Mallocator.instance); } + version (StdUnittest) @system unittest { testAllocator!(() => Mallocator.instance); } /** - The alignment is a static constant equal to $(D platformAlignment), which + The alignment is a static constant equal to `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 + `deallocate` and `reallocate` methods are `@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 + paradoxically, `malloc` is `@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 + @trusted @nogc nothrow pure + void[] allocate(size_t bytes) shared const { - import core.stdc.stdlib : malloc; + import core.memory : pureMalloc; if (!bytes) return null; - auto p = malloc(bytes); + auto p = pureMalloc(bytes); return p ? p[0 .. bytes] : null; } /// Ditto - @system @nogc nothrow - bool deallocate(void[] b) shared + @system @nogc nothrow pure + bool deallocate(void[] b) shared const { - import core.stdc.stdlib : free; - free(b.ptr); + import core.memory : pureFree; + pureFree(b.ptr); return true; } /// Ditto - @system @nogc nothrow - bool reallocate(ref void[] b, size_t s) shared + @system @nogc nothrow pure + bool reallocate(ref void[] b, size_t s) shared const { - import core.stdc.stdlib : realloc; + import core.memory : pureRealloc; if (!s) { // fuzzy area in the C standard, see http://goo.gl/ZpWeSE @@ -53,46 +58,52 @@ struct Mallocator b = null; return true; } - auto p = cast(ubyte*) realloc(b.ptr, s); + auto p = cast(ubyte*) pureRealloc(b.ptr, s); if (!p) return false; b = p[0 .. s]; return true; } + @trusted @nogc nothrow pure + package void[] allocateZeroed()(size_t bytes) shared const + { + import core.memory : pureCalloc; + if (!bytes) return null; + auto p = pureCalloc(1, bytes); + return p ? p[0 .. bytes] : null; + } + /** 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). + `shared`. */ static shared Mallocator instance; } /// -@nogc nothrow -@system unittest +@nogc @system nothrow unittest { auto buffer = Mallocator.instance.allocate(1024 * 1024 * 4); scope(exit) Mallocator.instance.deallocate(buffer); //... } -@nogc nothrow -@system unittest +@nogc @system nothrow pure unittest { - @nogc nothrow + @nogc nothrow pure static void test(A)() { int* p = null; p = cast(int*) A.instance.allocate(int.sizeof); - scope(exit) A.instance.deallocate(p[0 .. int.sizeof]); + scope(exit) () nothrow @nogc { A.instance.deallocate(p[0 .. int.sizeof]); }(); *p = 42; assert(*p == 42); } test!Mallocator(); } -@nogc nothrow -@system unittest +@nogc @system nothrow pure unittest { static void test(A)() { @@ -199,10 +210,10 @@ version (Windows) */ struct AlignedMallocator { - @system unittest { testAllocator!(() => typeof(this).instance); } + version (StdUnittest) @system unittest { testAllocator!(() => typeof(this).instance); } /** - The default alignment is $(D platformAlignment). + The default alignment is `platformAlignment`. */ enum uint alignment = platformAlignment; @@ -218,9 +229,9 @@ struct AlignedMallocator /** Uses $(HTTP man7.org/linux/man-pages/man3/posix_memalign.3.html, - $(D posix_memalign)) on Posix and + `posix_memalign`) on Posix and $(HTTP msdn.microsoft.com/en-us/library/8z34s9c6(v=vs.80).aspx, - $(D __aligned_malloc)) on Windows. + `__aligned_malloc`) on Windows. */ version (Posix) @trusted @nogc nothrow @@ -231,6 +242,15 @@ struct AlignedMallocator assert(a.isGoodDynamicAlignment); void* result; auto code = posix_memalign(&result, a, bytes); + +version (OSX) +version (LDC_AddressSanitizer) +{ + // The return value with AddressSanitizer may be -1 instead of ENOMEM + // or EINVAL. See https://bugs.llvm.org/show_bug.cgi?id=36510 + if (code == -1) + return null; +} if (code == ENOMEM) return null; @@ -255,9 +275,9 @@ struct AlignedMallocator else static assert(0); /** - Calls $(D free(b.ptr)) on Posix and + Calls `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. + `__aligned_free(b.ptr)`) on Windows. */ version (Posix) @system @nogc nothrow @@ -277,16 +297,10 @@ struct AlignedMallocator else static assert(0); /** - On Posix, forwards to $(D realloc). On Windows, forwards to - $(D alignedReallocate(b, newSize, platformAlignment)). + Forwards to $(D alignedReallocate(b, newSize, platformAlignment)). + Should be used with blocks obtained with `allocate` otherwise the custom + alignment passed with `alignedAllocate` can be lost. */ - 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 { @@ -294,9 +308,10 @@ struct AlignedMallocator } /** - 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, + On Posix there is no `realloc` for aligned memory, so `alignedReallocate` emulates + the needed behavior by using `alignedAllocate` to get a new block. The existing + block is copied to the new block and then freed. + On Windows, calls $(HTTPS msdn.microsoft.com/en-us/library/y69db7sx.aspx, $(D __aligned_realloc(b.ptr, newSize, a))). */ version (Windows) @@ -315,17 +330,40 @@ struct AlignedMallocator return true; } + /// ditto + version (Posix) + @system @nogc nothrow + bool alignedReallocate(ref void[] b, size_t s, uint a) shared + { + if (!s) + { + deallocate(b); + b = null; + return true; + } + auto p = alignedAllocate(s, a); + if (!p.ptr) + { + return false; + } + import std.algorithm.comparison : min; + const upTo = min(s, b.length); + p[0 .. upTo] = b[0 .. upTo]; + deallocate(b); + b = p; + 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). + `shared`. */ static shared AlignedMallocator instance; } /// -@nogc nothrow -@system unittest +@nogc @system nothrow unittest { auto buffer = AlignedMallocator.instance.alignedAllocate(1024 * 1024 * 4, 128); @@ -333,34 +371,59 @@ struct AlignedMallocator //... } -version (unittest) version (CRuntime_DigitalMars) -@nogc nothrow -size_t addr(ref void* ptr) { return cast(size_t) ptr; } +version (Posix) +@nogc @system nothrow unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=16398 + // test the "pseudo" alignedReallocate for Posix + void[] s = AlignedMallocator.instance.alignedAllocate(16, 32); + (cast(ubyte[]) s)[] = ubyte(1); + AlignedMallocator.instance.alignedReallocate(s, 32, 32); + ubyte[16] o; + o[] = 1; + assert((cast(ubyte[]) s)[0 .. 16] == o); + AlignedMallocator.instance.alignedReallocate(s, 4, 32); + assert((cast(ubyte[]) s)[0 .. 3] == o[0 .. 3]); + AlignedMallocator.instance.alignedReallocate(s, 128, 32); + assert((cast(ubyte[]) s)[0 .. 3] == o[0 .. 3]); + AlignedMallocator.instance.deallocate(s); + + void[] c; + AlignedMallocator.instance.alignedReallocate(c, 32, 32); + assert(c.ptr); + + version (DragonFlyBSD) {} else /* FIXME: Malloc on DragonFly does not return NULL when allocating more than UINTPTR_MAX + * $(LINK: https://bugs.dragonflybsd.org/issues/3114, dragonfly bug report) + * $(LINK: https://github.com/dlang/druntime/pull/1999#discussion_r157536030, PR Discussion) */ + assert(!AlignedMallocator.instance.alignedReallocate(c, size_t.max, 4096)); + AlignedMallocator.instance.deallocate(c); +} version (CRuntime_DigitalMars) -@nogc nothrow -@system unittest +@nogc @system nothrow unittest { void* m; + size_t m_addr() { return cast(size_t) m; } + m = _aligned_malloc(16, 0x10); if (m) { - assert((m.addr & 0xF) == 0); + assert((m_addr & 0xF) == 0); _aligned_free(m); } m = _aligned_malloc(16, 0x100); if (m) { - assert((m.addr & 0xFF) == 0); + assert((m_addr & 0xFF) == 0); _aligned_free(m); } m = _aligned_malloc(16, 0x1000); if (m) { - assert((m.addr & 0xFFF) == 0); + assert((m_addr & 0xFFF) == 0); _aligned_free(m); } @@ -369,7 +432,7 @@ version (CRuntime_DigitalMars) { assert((cast(size_t) m & 0xF) == 0); m = _aligned_realloc(m, 32, 0x10000); - if (m) assert((m.addr & 0xFFFF) == 0); + if (m) assert((m_addr & 0xFFFF) == 0); _aligned_free(m); } diff --git a/libphobos/src/std/experimental/allocator/mmap_allocator.d b/libphobos/src/std/experimental/allocator/mmap_allocator.d index e07d444..4151d0e 100644 --- a/libphobos/src/std/experimental/allocator/mmap_allocator.d +++ b/libphobos/src/std/experimental/allocator/mmap_allocator.d @@ -1,23 +1,23 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/allocator/_mmap_allocator.d) +*/ 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 +additional structure: each call to `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 +and each call to `deallocate(b)` issues $(D munmap(b.ptr, b.length)). +So `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; + static shared const MmapAllocator instance; /** Alignment is page-size and hardcoded to 4096 (even though on certain systems @@ -28,22 +28,29 @@ struct MmapAllocator version (Posix) { /// Allocator API. - void[] allocate(size_t bytes) shared + pure nothrow @nogc @safe + void[] allocate(size_t bytes) shared const { - import core.sys.posix.sys.mman : mmap, MAP_ANON, PROT_READ, + import core.sys.posix.sys.mman : 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]; + const errnosave = (() @trusted => fakePureErrno())(); // For purity revert changes to errno. + auto p = (() @trusted => fakePureMmap(null, bytes, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0))(); + if (p is MAP_FAILED) + { + (() @trusted => fakePureErrno() = errnosave)(); // errno only changed on MAP_FAILED. + return null; + } + return (() @trusted => p[0 .. bytes])(); } /// Ditto - bool deallocate(void[] b) shared + pure nothrow @nogc + bool deallocate(void[] b) shared const { - import core.sys.posix.sys.mman : munmap; - if (b.ptr) munmap(b.ptr, b.length) == 0 || assert(0); + // Because we assert(0) on error we don't need to reset errno for purity. + if (b.ptr) fakePureMunmap(b.ptr, b.length) == 0 || assert(0); return true; } @@ -64,22 +71,31 @@ struct MmapAllocator } else version (Windows) { - import core.sys.windows.windows : VirtualAlloc, VirtualFree, MEM_COMMIT, - PAGE_READWRITE, MEM_RELEASE; + import core.sys.windows.winnt : MEM_COMMIT, PAGE_READWRITE, MEM_RELEASE; /// Allocator API. - void[] allocate(size_t bytes) shared + pure nothrow @nogc @safe + void[] allocate(size_t bytes) shared const { if (!bytes) return null; - auto p = VirtualAlloc(null, bytes, MEM_COMMIT, PAGE_READWRITE); + // For purity ensure last-error does not visibly change. + const lastErrorSave = (() @trusted => GetLastError())(); + auto p = (() @trusted => VirtualAlloc(null, bytes, MEM_COMMIT, PAGE_READWRITE))(); if (p == null) + { + // Last-error only changed if allocation failed. + (() @trusted => SetLastError(lastErrorSave))(); return null; - return p[0 .. bytes]; + } + return (() @trusted => p[0 .. bytes])(); } /// Ditto - bool deallocate(void[] b) shared + pure nothrow @nogc + bool deallocate(void[] b) shared const { + const lastErrorSave = GetLastError(); // For purity ensure last-error does not visibly change. + scope(exit) SetLastError(lastErrorSave); return b.ptr is null || VirtualFree(b.ptr, 0, MEM_RELEASE) != 0; } @@ -87,10 +103,36 @@ struct MmapAllocator } } -@system unittest +// pure wrappers around `mmap` and `munmap` because they are used here locally +// solely to perform allocation and deallocation which in this case is `pure` +version (Posix) +extern (C) private pure @system @nogc nothrow +{ + import core.sys.posix.sys.types : off_t; + pragma(mangle, "fakePureErrnoImpl") ref int fakePureErrno(); + pragma(mangle, "mmap") void* fakePureMmap(void*, size_t, int, int, int, off_t); + pragma(mangle, "munmap") int fakePureMunmap(void*, size_t); +} + +// Pure wrappers around VirtualAlloc/VirtualFree for use here only. Their use is sound +// because when we call them we ensure that last-error is not visibly changed. +version (Windows) +extern (Windows) private pure @system @nogc nothrow +{ + import core.sys.windows.basetsd : SIZE_T; + import core.sys.windows.windef : BOOL, DWORD; + import core.sys.windows.winnt : LPVOID, PVOID; + + DWORD GetLastError(); + void SetLastError(DWORD); + PVOID VirtualAlloc(PVOID, SIZE_T, DWORD, DWORD); + BOOL VirtualFree(PVOID, SIZE_T, DWORD); +} + +pure nothrow @safe @nogc unittest { alias alloc = MmapAllocator.instance; auto p = alloc.allocate(100); assert(p.length == 100); - alloc.deallocate(p); + () @trusted { alloc.deallocate(p); p = null; }(); } diff --git a/libphobos/src/std/experimental/allocator/package.d b/libphobos/src/std/experimental/allocator/package.d index 11c8547..2804829 100644 --- a/libphobos/src/std/experimental/allocator/package.d +++ b/libphobos/src/std/experimental/allocator/package.d @@ -63,13 +63,13 @@ 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; +module). It consists of an interface called $(LREF IAllocator), which concrete 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 +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) @@ -82,7 +82,7 @@ $(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. +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.) @@ -91,27 +91,27 @@ 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- +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 +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 +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 +$(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)) +$(H2 Idiomatic Use of `std.experimental.allocator`) -As of this time, $(D std.experimental._allocator) is not integrated with D's +As of this time, `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 +array concatenation operators. That means `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 @@ -133,9 +133,9 @@ void fun(size_t n) 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. +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: ---- @@ -158,27 +158,27 @@ 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 +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; + private IAllocator allocator; this(size_t buckets, IAllocator allocator = theAllocator) { - this._allocator = allocator; + this.allocator = allocator; ... } // Getter and setter - IAllocator allocator() { return _allocator; } - void allocator(IAllocator a) { assert(empty); _allocator = a; } + 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 +`allocator` object for acquiring memory. Furthermore, setting +`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. @@ -217,7 +217,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/experimental/_allocator) +Source: $(PHOBOSSRC std/experimental/allocator) */ @@ -226,6 +226,19 @@ module std.experimental.allocator; public import std.experimental.allocator.common, std.experimental.allocator.typed; +// Fix https://issues.dlang.org/show_bug.cgi?id=17806 +// this should always be the first unittest in this module in order to ensure +// that we use the `processAllocator` setter before the getter +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.experimental.allocator.gc_allocator : GCAllocator; + auto newAlloc = sharedAllocatorObject(Mallocator.instance); + processAllocator = newAlloc; + assert(processAllocator is newAlloc); + processAllocator = sharedAllocatorObject(GCAllocator.instance); +} + // Example in the synopsis above @system unittest { @@ -276,16 +289,17 @@ 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 +in $(MREF 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 +Methods returning `Ternary` return `Ternary.yes` upon success, +`Ternary.no` upon failure, and `Ternary.unknown` if the primitive is not implemented by the allocator instance. */ interface IAllocator { +nothrow: /** Returns the alignment offered. */ @@ -329,8 +343,8 @@ interface IAllocator 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 + Returns `Ternary.yes` if the allocator owns `b`, `Ternary.no` if + the allocator doesn't own `b`, and `Ternary.unknown` if ownership cannot be determined. Implementations that don't support this primitive should always return `Ternary.unknown`. */ @@ -345,7 +359,7 @@ interface IAllocator /** 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)). + allocator supports deallocation is to call `deallocate(null)`. */ bool deallocate(void[] b); @@ -356,11 +370,257 @@ interface IAllocator 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. + Returns `Ternary.yes` if no memory is currently allocated from this + allocator, `Ternary.no` if some allocations are currently active, or + `Ternary.unknown` if not supported. */ Ternary empty(); + + /** + Increases the reference count of the concrete class that implements this + interface. + + For stateless allocators, this does nothing. + */ + @safe @nogc pure + void incRef(); + + /** + Decreases the reference count of the concrete class that implements this + interface. + When the reference count is `0`, the object self-destructs. + + Returns: `true` if the reference count is greater than `0` and `false` when + it hits `0`. For stateless allocators, it always returns `true`. + */ + @safe @nogc pure + bool decRef(); +} + +/** +A reference counted struct that wraps the dynamic allocator interface. +This should be used wherever a uniform type is required for encapsulating +various allocator implementations. + +Code that defines allocators ultimately implements the $(LREF IAllocator) +interface, possibly by using $(LREF CAllocatorImpl) below, and then build a +`RCIAllocator` out of this. + +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 `RCIAllocator` (possibly by using $(LREF allocatorObject) below). +*/ +struct RCIAllocator +{ + private IAllocator _alloc; + +nothrow: + private @nogc pure @safe + this(this _)(IAllocator alloc) + { + assert(alloc); + _alloc = alloc; + } + + @nogc pure @safe + this(this) + { + if (_alloc !is null) + { + _alloc.incRef(); + } + } + + @nogc pure @safe + ~this() + { + if (_alloc !is null) + { + bool isLast = !_alloc.decRef(); + if (isLast) _alloc = null; + } + } + + @nogc pure @safe + auto ref opAssign()(typeof(this) rhs) + { + if (_alloc is rhs._alloc) + { + return this; + } + // incRef was allready called by rhs posblit, so we're just moving + // calling dtor is the equivalent of decRef + __dtor(); + _alloc = rhs._alloc; + // move + rhs._alloc = null; + return this; + } + + @nogc pure @safe + bool isNull(this _)() + { + return _alloc is null; + } + + @property uint alignment() + { + assert(_alloc); + return _alloc.alignment(); + } + + size_t goodAllocSize(size_t s) + { + assert(_alloc); + return _alloc.goodAllocSize(s); + } + + void[] allocate(size_t n, TypeInfo ti = null) + { + assert(_alloc); + return _alloc.allocate(n, ti); + } + + void[] alignedAllocate(size_t n, uint a) + { + assert(_alloc); + return _alloc.alignedAllocate(n, a); + } + + void[] allocateAll() + { + assert(_alloc); + return _alloc.allocateAll(); + } + + bool expand(ref void[] b, size_t size) + { + assert(_alloc); + return _alloc.expand(b, size); + } + + bool reallocate(ref void[] b, size_t size) + { + assert(_alloc); + return _alloc.reallocate(b, size); + } + + bool alignedReallocate(ref void[] b, size_t size, uint alignment) + { + assert(_alloc); + return _alloc.alignedReallocate(b, size, alignment); + } + + Ternary owns(void[] b) + { + assert(_alloc); + return _alloc.owns(b); + } + + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + assert(_alloc); + return _alloc.resolveInternalPointer(p, result); + } + + bool deallocate(void[] b) + { + assert(_alloc); + return _alloc.deallocate(b); + } + + bool deallocateAll() + { + assert(_alloc); + return _alloc.deallocateAll(); + } + + Ternary empty() + { + assert(_alloc); + return _alloc.empty(); + } +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.conv : emplace; + + auto reg = Region!()(new ubyte[1024]); + auto state = reg.allocate(stateSize!(CAllocatorImpl!(Region!(), Yes.indirect))); + auto regObj = emplace!(CAllocatorImpl!(Region!(), Yes.indirect))(state, ®); + + auto rcalloc = RCIAllocator(regObj); + auto b = rcalloc.allocate(10); + assert(b.length == 10); + + // The reference counting is zero based + assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); + { + auto rca2 = rcalloc; + assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 2); + } + assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); +} + +@system unittest +{ + import std.conv; + import std.experimental.allocator.mallocator; + import std.experimental.allocator.building_blocks.stats_collector; + + alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); + SCAlloc statsCollectorAlloc; + + ulong bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == 0); + + { + auto _allocator = allocatorObject(&statsCollectorAlloc); + bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc, Yes.indirect))); + } + + bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == 0, "RCIAllocator leaks memory; leaked " + ~ to!string(bytesUsed) ~ " bytes"); +} + +@system unittest +{ + import std.conv; + import std.experimental.allocator.mallocator; + import std.experimental.allocator.building_blocks.stats_collector; + + alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); + SCAlloc statsCollectorAlloc; + + ulong bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == 0); + + { + auto _allocator = allocatorObject(statsCollectorAlloc); + + // Ensure that the allocator was passed through in CAllocatorImpl + // This allocator was used to allocate the chunk that holds the + // CAllocatorImpl object; which is it's own wrapper + bytesUsed = (cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)), + "RCIAllocator leaks memory; leaked " ~ to!string(bytesUsed) ~ " bytes"); + _allocator.allocate(1); + bytesUsed = (cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)) + 1, + "RCIAllocator leaks memory; leaked " ~ to!string(bytesUsed) ~ " bytes"); + } + + bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)), + "RCIAllocator leaks memory; leaked " + ~ to!string(bytesUsed) ~ " bytes"); } /** @@ -372,16 +632,17 @@ 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 +in $(MREF 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 +Methods returning `Ternary` return `Ternary.yes` upon success, +`Ternary.no` upon failure, and `Ternary.unknown` if the primitive is not implemented by the allocator instance. */ interface ISharedAllocator { +nothrow: /** Returns the alignment offered. */ @@ -425,8 +686,8 @@ interface ISharedAllocator 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 + Returns `Ternary.yes` if the allocator owns `b`, `Ternary.no` if + the allocator doesn't own `b`, and `Ternary.unknown` if ownership cannot be determined. Implementations that don't support this primitive should always return `Ternary.unknown`. */ @@ -441,7 +702,7 @@ interface ISharedAllocator /** 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)). + allocator supports deallocation is to call `deallocate(null)`. */ bool deallocate(void[] b) shared; @@ -452,113 +713,324 @@ interface ISharedAllocator 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. + Returns `Ternary.yes` if no memory is currently allocated from this + allocator, `Ternary.no` if some allocations are currently active, or + `Ternary.unknown` if not supported. */ Ternary empty() shared; + + /** + Increases the reference count of the concrete class that implements this + interface. + + For stateless allocators, this does nothing. + */ + @safe @nogc pure + void incRef() shared; + + /** + Decreases the reference count of the concrete class that implements this + interface. + When the reference count is `0`, the object self-destructs. + + For stateless allocators, this does nothing. + + Returns: `true` if the reference count is greater than `0` and `false` when + it hits `0`. For stateless allocators, it always returns `true`. + */ + @safe @nogc pure + bool decRef() shared; } -private shared ISharedAllocator _processAllocator; -private IAllocator _threadAllocator; +/** +A reference counted struct that wraps the dynamic shared allocator interface. +This should be used wherever a uniform type is required for encapsulating +various allocator implementations. -private IAllocator setupThreadAllocator() nothrow @nogc @safe +Code that defines allocators shareable across threads ultimately implements the +$(LREF ISharedAllocator) interface, possibly by using +$(LREF CSharedAllocatorImpl) below, and then build a `RCISharedAllocator` out +of this. + +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 `RCISharedAllocator` (possibly by using $(LREF sharedAllocatorObject) below). +*/ +shared struct RCISharedAllocator +{ + private ISharedAllocator _alloc; + +nothrow: + private @nogc pure @safe + this(shared ISharedAllocator alloc) + { + assert(alloc); + _alloc = alloc; + } + + @nogc pure @safe + this(this) + { + if (_alloc !is null) + { + _alloc.incRef(); + } + } + + @nogc pure @safe + ~this() + { + if (_alloc !is null) + { + bool isLast = !_alloc.decRef(); + if (isLast) _alloc = null; + } + } + + @nogc pure @safe + auto ref opAssign()(RCISharedAllocator rhs) + { + if (_alloc is rhs._alloc) + { + return this; + } + // incRef was allready called by rhs posblit, so we're just moving + if (_alloc !is null) + { + _alloc.decRef(); + } + _alloc = rhs._alloc; + // move + rhs._alloc = null; + return this; + } + + @nogc pure @safe + bool isNull(this _)() + { + return _alloc is null; + } + + @property uint alignment() + { + assert(_alloc); + return _alloc.alignment(); + } + + size_t goodAllocSize(size_t s) + { + assert(_alloc); + return _alloc.goodAllocSize(s); + } + + void[] allocate(size_t n, TypeInfo ti = null) + { + assert(_alloc); + return _alloc.allocate(n, ti); + } + + void[] alignedAllocate(size_t n, uint a) + { + assert(_alloc); + return _alloc.alignedAllocate(n, a); + } + + void[] allocateAll() + { + assert(_alloc); + return _alloc.allocateAll(); + } + + bool expand(ref void[] b, size_t size) + { + assert(_alloc); + return _alloc.expand(b, size); + } + + bool reallocate(ref void[] b, size_t size) + { + assert(_alloc); + return _alloc.reallocate(b, size); + } + + bool alignedReallocate(ref void[] b, size_t size, uint alignment) + { + assert(_alloc); + return _alloc.alignedReallocate(b, size, alignment); + } + + Ternary owns(void[] b) + { + assert(_alloc); + return _alloc.owns(b); + } + + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + assert(_alloc); + return _alloc.resolveInternalPointer(p, result); + } + + bool deallocate(void[] b) + { + assert(_alloc); + return _alloc.deallocate(b); + } + + bool deallocateAll() + { + assert(_alloc); + return _alloc.deallocateAll(); + } + + Ternary empty() + { + assert(_alloc); + return _alloc.empty(); + } +} + +private RCISharedAllocator _processAllocator; +private RCIAllocator _threadAllocator; + +@nogc nothrow @safe +private ref RCIAllocator setupThreadAllocator() { /* Forwards the `_threadAllocator` calls to the `processAllocator` */ static class ThreadAllocator : IAllocator { + nothrow: + private RCISharedAllocator _allocator; + + @nogc @safe + this(ref RCISharedAllocator procAlloc) + { + _allocator = procAlloc; + } + override @property uint alignment() { - return processAllocator.alignment(); + return _allocator.alignment(); } override size_t goodAllocSize(size_t s) { - return processAllocator.goodAllocSize(s); + return _allocator.goodAllocSize(s); } override void[] allocate(size_t n, TypeInfo ti = null) { - return processAllocator.allocate(n, ti); + return _allocator.allocate(n, ti); } override void[] alignedAllocate(size_t n, uint a) { - return processAllocator.alignedAllocate(n, a); + return _allocator.alignedAllocate(n, a); } override void[] allocateAll() { - return processAllocator.allocateAll(); + return _allocator.allocateAll(); } override bool expand(ref void[] b, size_t size) { - return processAllocator.expand(b, size); + return _allocator.expand(b, size); } override bool reallocate(ref void[] b, size_t size) { - return processAllocator.reallocate(b, size); + return _allocator.reallocate(b, size); } override bool alignedReallocate(ref void[] b, size_t size, uint alignment) { - return processAllocator.alignedReallocate(b, size, alignment); + return _allocator.alignedReallocate(b, size, alignment); } override Ternary owns(void[] b) { - return processAllocator.owns(b); + return _allocator.owns(b); } override Ternary resolveInternalPointer(const void* p, ref void[] result) { - return processAllocator.resolveInternalPointer(p, result); + return _allocator.resolveInternalPointer(p, result); } override bool deallocate(void[] b) { - return processAllocator.deallocate(b); + return _allocator.deallocate(b); } override bool deallocateAll() { - return processAllocator.deallocateAll(); + return _allocator.deallocateAll(); } override Ternary empty() { - return processAllocator.empty(); + return _allocator.empty(); + } + + @nogc pure @safe + override void incRef() + { + _allocator._alloc.incRef(); + } + + @nogc pure @safe + override bool decRef() + { + return _allocator._alloc.decRef(); } } - assert(!_threadAllocator); - import std.conv : emplace; + assert(_threadAllocator.isNull); + import core.lifetime : emplace; static ulong[stateSize!(ThreadAllocator).divideRoundUp(ulong.sizeof)] _threadAllocatorState; - _threadAllocator = () @trusted { return emplace!(ThreadAllocator)(_threadAllocatorState[]); } (); + () @trusted { + _threadAllocator = RCIAllocator(emplace!(ThreadAllocator)(_threadAllocatorState[], processAllocator())); + }(); return _threadAllocator; } +// Fix threadAllocator bug: the threadAllocator should hold an internal reference +// to the processAllocator that it's using +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + auto a = sharedAllocatorObject(Mallocator.instance); + auto buf = theAllocator.allocate(42); + processAllocator = a; + theAllocator.deallocate(buf); +} + /** 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 +to be shared across threads, use `processAllocator` (below). By default, +`theAllocator` ultimately fetches memory from `processAllocator`, which in turn uses the garbage collected heap. */ -nothrow @safe @nogc @property IAllocator theAllocator() +@nogc nothrow @safe +@property ref RCIAllocator theAllocator() { - auto p = _threadAllocator; - return p !is null ? p : setupThreadAllocator(); + alias p = _threadAllocator; + return !p.isNull() ? p : setupThreadAllocator(); } /// Ditto -nothrow @safe @nogc @property void theAllocator(IAllocator a) +nothrow @system @nogc +@property void theAllocator(RCIAllocator a) { - assert(a); + assert(!a.isNull); _threadAllocator = a; } @@ -580,21 +1052,29 @@ nothrow @safe @nogc @property void theAllocator(IAllocator a) /** 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). +allocator can be cast to `shared`. */ -@property shared(ISharedAllocator) processAllocator() +@nogc nothrow @trusted +@property ref RCISharedAllocator processAllocator() { import std.experimental.allocator.gc_allocator : GCAllocator; import std.concurrency : initOnce; - return initOnce!_processAllocator( - sharedAllocatorObject(GCAllocator.instance)); + + static RCISharedAllocator* forceAttributes() + { + return &initOnce!_processAllocator( + sharedAllocatorObject(GCAllocator.instance)); + } + + return *(cast(RCISharedAllocator* function() @nogc nothrow)(&forceAttributes))(); } /// Ditto -@property void processAllocator(shared ISharedAllocator a) +@nogc nothrow @system +@property void processAllocator(ref RCISharedAllocator a) { - assert(a); - _processAllocator = a; + assert(!a.isNull); + processAllocator() = a; } @system unittest @@ -604,97 +1084,122 @@ allocator can be cast to $(D shared). import std.experimental.allocator.building_blocks.free_list : SharedFreeList; import std.experimental.allocator.mallocator : Mallocator; - assert(processAllocator); - assert(theAllocator); + assert(!processAllocator.isNull); + assert(!theAllocator.isNull); testAllocatorObject(processAllocator); testAllocatorObject(theAllocator); shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) sharedFL; - shared ISharedAllocator sharedFLObj = sharedAllocatorObject(sharedFL); - assert(sharedFLObj); + RCISharedAllocator sharedFLObj = sharedAllocatorObject(sharedFL); + alias SharedAllocT = CSharedAllocatorImpl!( + shared SharedFreeList!( + Mallocator, chooseAtRuntime, chooseAtRuntime)); + + assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 1); + assert(!sharedFLObj.isNull); testAllocatorObject(sharedFLObj); // Test processAllocator setter - shared ISharedAllocator oldProcessAllocator = processAllocator; + RCISharedAllocator oldProcessAllocator = processAllocator; processAllocator = sharedFLObj; - assert(processAllocator is sharedFLObj); + assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 2); + assert(processAllocator._alloc is sharedFLObj._alloc); testAllocatorObject(processAllocator); testAllocatorObject(theAllocator); - assertThrown!AssertError(processAllocator = null); + assertThrown!AssertError(processAllocator = RCISharedAllocator(null)); // Restore initial processAllocator state processAllocator = oldProcessAllocator; + assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 1); assert(processAllocator is oldProcessAllocator); - shared ISharedAllocator indirectShFLObj = sharedAllocatorObject(&sharedFL); + RCISharedAllocator indirectShFLObj = sharedAllocatorObject(&sharedFL); testAllocatorObject(indirectShFLObj); + alias IndirectSharedAllocT = CSharedAllocatorImpl!( + shared SharedFreeList!( + Mallocator, chooseAtRuntime, chooseAtRuntime) + , Yes.indirect); - IAllocator indirectMallocator = allocatorObject(&Mallocator.instance); + assert((cast(IndirectSharedAllocT)(indirectShFLObj._alloc)).rc == 1); + + RCIAllocator 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 +Dynamically allocates (using `alloc`) 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 $(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.) +semantically the same as `T(args)`. +(Note that using `alloc.make!(T[])` creates a pointer to an (empty) array +of `T`s, not an array. To use an allocator to allocate and initialize an +array, use `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) +implementing the static interface for allocators, or an `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. +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 $(D T)'s constructor throws, deallocates the allocated memory and +Throws: If `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) && !is(T == interface) && A.length == 0 + && __traits(compiles, {T t;}) && __traits(isZeroInit, T) + && is(typeof(alloc.allocateZeroed(size_t.max)))) { - 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; - } + auto m = alloc.allocateZeroed(max(T.sizeof, 1)); + return (() @trusted => cast(T*) m.ptr)(); } - - scope(failure) + else { - static if (is(typeof(() pure { return construct(); }))) + import core.internal.lifetime : emplaceRef; + import core.lifetime : emplace; + + 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() { - // 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); }(); + 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!T(*p, args); + return p; + } } - else + + scope(failure) { - alloc.deallocate(m); + 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(); + return construct(); + } } /// @@ -744,7 +1249,9 @@ auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args) assert(outer.x == inner.getX); } -@system unittest // bugzilla 15639 & 15772 +// https://issues.dlang.org/show_bug.cgi?id=15639 +// https://issues.dlang.org/show_bug.cgi?id=15772 +@system unittest { abstract class Foo {} class Bar: Foo {} @@ -769,7 +1276,7 @@ auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args) A* b = alloc.make!A(42); assert(b.x == 42); assert(b.y is null); - import std.math : isNaN; + import std.math.traits : isNaN; assert(b.z.isNaN); b = alloc.make!A(43, "44", 45); @@ -895,7 +1402,47 @@ nothrow @safe @nogc unittest assertThrown(make!InvalidImpureStruct(Mallocator.instance, 42)); } -private void fillWithMemcpy(T)(void[] array, auto ref T filler) nothrow +// Don't allow zero-ctor-args `make` for structs with `@disable this();` +@system unittest +{ + struct NoDefaultCtor + { + int i; + @disable this(); + } + import std.experimental.allocator.mallocator : Mallocator; + static assert(!__traits(compiles, make!NoDefaultCtor(Mallocator.instance)), + "Don't allow zero-ctor-args `make` for structs with `@disable this();`"); +} + +// https://issues.dlang.org/show_bug.cgi?id=18937 +@safe unittest +{ + static struct S + { + ubyte[16 * 1024] data; + } + + static struct SomeAllocator + { + ubyte[] allocate(size_t) { return []; } + void deallocate(void[]) {} + } + + auto x = SomeAllocator().make!S(); +} + +private void fillWithMemcpy(T)(scope void[] array, auto ref T filler) nothrow +if (T.sizeof == 1) +{ + import core.stdc.string : memset; + import std.traits : CopyConstness; + if (!array.length) return; + memset(array.ptr, *cast(CopyConstness!(T*, ubyte*)) &filler, array.length); +} + +private void fillWithMemcpy(T)(scope void[] array, auto ref T filler) nothrow +if (T.sizeof != 1) { import core.stdc.string : memcpy; import std.algorithm.comparison : min; @@ -912,6 +1459,17 @@ private void fillWithMemcpy(T)(void[] array, auto ref T filler) nothrow @system unittest { + // Test T.sizeof == 1 path of fillWithMemcpy. + ubyte[] a; + fillWithMemcpy(a, ubyte(42)); + assert(a.length == 0); + a = [ 1, 2, 3, 4, 5 ]; + fillWithMemcpy(a, ubyte(42)); + assert(a == [ 42, 42, 42, 42, 42]); +} + +@system unittest +{ int[] a; fillWithMemcpy(a, 42); assert(a.length == 0); @@ -920,11 +1478,36 @@ private void fillWithMemcpy(T)(void[] array, auto ref T filler) nothrow assert(a == [ 42, 42, 42, 42, 42]); } +//Make shared object +@system unittest +{ + import core.atomic : atomicLoad; + auto psi = theAllocator.make!(shared(int))(10); + assert(10 == (*psi).atomicLoad()); +} + private T[] uninitializedFillDefault(T)(T[] array) nothrow { - T t = T.init; - fillWithMemcpy(array, t); - return array; + static if (__traits(isZeroInit, T)) + { + import core.stdc.string : memset; + if (array !is null) + memset(array.ptr, 0, T.sizeof * array.length); + return array; + } + else static if (is(immutable T == immutable char) || is(immutable T == immutable wchar)) + { + import core.stdc.string : memset; + if (array !is null) + memset(array.ptr, 0xff, T.sizeof * array.length); + return array; + } + else + { + T t = T.init; + fillWithMemcpy(array, t); + return array; + } } pure nothrow @nogc @@ -943,10 +1526,28 @@ pure nothrow @nogc int[] a = [1, 2, 4]; uninitializedFillDefault(a); assert(a == [0, 0, 0]); + + char[] b = [1, 2, 4]; + uninitializedFillDefault(b); + assert(b == [0xff, 0xff, 0xff]); + + wchar[] c = [1, 2, 4]; + uninitializedFillDefault(c); + assert(c == [0xffff, 0xffff, 0xffff]); +} + +@system unittest +{ + static struct P { float x = 0; float y = 0; } + + static assert(__traits(isZeroInit, P)); + P[] a = [P(10, 11), P(20, 21), P(40, 41)]; + uninitializedFillDefault(a); + assert(a == [P.init, P.init, P.init]); } /** -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`. +Create an array of `T` with `length` elements using `alloc`. 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 @@ -956,7 +1557,7 @@ 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 +The newly-created array, or `null` if either `length` was `0` or allocation failed. Throws: @@ -967,10 +1568,30 @@ 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); }(); + static if (T.sizeof <= 1) + { + const nAlloc = length * T.sizeof; + } + else + { + import core.checkedint : mulu; + bool overflow; + const nAlloc = mulu(length, T.sizeof, overflow); + if (overflow) return null; + } + + static if (__traits(isZeroInit, T) && hasMember!(Allocator, "allocateZeroed")) + { + auto m = alloc.allocateZeroed(nAlloc); + return (() @trusted => cast(T[]) m)(); + } + else + { + auto m = alloc.allocate(nAlloc); + if (!m.ptr) return null; + alias U = Unqual!T; + return () @trusted { return cast(T[]) uninitializedFillDefault(cast(U[]) m); }(); + } } @system unittest @@ -1021,6 +1642,16 @@ T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length) assert(c.equal([0, 0, 0, 0, 0])); } +// https://issues.dlang.org/show_bug.cgi?id=19085 - makeArray with void +@system unittest +{ + auto b = theAllocator.makeArray!void(5); + scope(exit) theAllocator.dispose(b); + auto c = cast(ubyte[]) b; + assert(c.length == 5); + assert(c == [0, 0, 0, 0, 0]); // default initialization +} + private enum hasPurePostblit(T) = !hasElaborateCopyConstructor!T || is(typeof(() pure { T.init.__xpostblit(); })); @@ -1031,8 +1662,7 @@ private enum hasPureDtor(T) = !hasElaborateDestructor!T || private enum canSafelyDeallocPostRewind(T) = hasPurePostblit!T && hasPureDtor!T; /// Ditto -T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, - auto ref T init) +T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, T init) { if (!length) return null; auto m = alloc.allocate(T.sizeof * length); @@ -1060,7 +1690,7 @@ T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, } } } - import std.conv : emplace; + import core.lifetime : emplace; for (; i < length; ++i) { emplace!T(&result[i], init); @@ -1096,6 +1726,18 @@ T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, @system unittest { + void test(T)(in T initialValue) + { + auto t = theAllocator.makeArray!T(100, initialValue); + //auto t = theAllocator.makeArray(100, initialValue); // works well with the old code + } + + const int init = 3; + test(init); +} + +@system unittest +{ void test(A)(auto ref A alloc) { long[] a = alloc.makeArray!long(0, 42); @@ -1196,7 +1838,7 @@ if (isInputRange!R && !isInfinite!R) alloc.deallocate(m); } - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; static if (isNarrowString!R || isRandomAccessRange!R) { foreach (j; 0 .. range.length) @@ -1253,7 +1895,7 @@ if (isInputRange!R && !isInfinite!R) } result = () @trusted { return cast(T[]) m; } (); } - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; emplaceRef(result[initialized], range.front); } @@ -1377,7 +2019,7 @@ if (isInputRange!R && !isInfinite!R) j++; } } - assertThrown(makeArray!NoCopy(Mallocator.instance, NoCopyRange())); + makeArray!NoCopy(Mallocator.instance, NoCopyRange()); // rvalue elements are forwarded/moved } // test failure with an impure, failing struct @@ -1407,9 +2049,8 @@ if (isInputRange!R && !isInfinite!R) auto arr = [NoCopy(1), NoCopy(2)]; assertThrown(makeArray!NoCopy(Mallocator.instance, arr)); - // allow more copies and thus force reallocation i = 0; - maxElements = 30; + maxElements = 0; // disallow any postblit static j = 0; struct NoCopyRange @@ -1429,25 +2070,19 @@ if (isInputRange!R && !isInfinite!R) 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))); + assert(i == j && i == 101); // all 101 rvalue elements forwarded/moved } -version (unittest) +version (StdUnittest) { - private struct ForcedInputRange + private struct ForcedInputRange(T) { - int[]* array; + T[]* array; pure nothrow @safe @nogc: bool empty() { return !array || (*array).empty; } - ref int front() { return (*array)[0]; } + ref T front() { return (*array)[0]; } void popFront() { *array = (*array)[1 .. $]; } } } @@ -1460,7 +2095,7 @@ version (unittest) void test(A)(auto ref A alloc) { - ForcedInputRange r; + ForcedInputRange!int r; long[] a = alloc.makeArray!long(r); assert(a.length == 0 && a.ptr is null); auto arr2 = arr; @@ -1475,23 +2110,23 @@ version (unittest) } /** -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 +Grows `array` by appending `delta` more elements. The needed memory is +allocated using `alloc`. 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 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 +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: -$(D true) upon success, $(D false) if memory could not be allocated. In the -latter case $(D array) is left unaffected. +`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 `alloc`'s primitives do. The @@ -1581,13 +2216,13 @@ if (isInputRange!R) toFill.uninitializedFillDefault; } - for (; !range.empty; range.popFront, toFill.popFront) + for (; !range.empty; range.popFront, toFill = toFill[1 .. $]) { - assert(!toFill.empty); - import std.conv : emplace; - emplace!T(&toFill.front, range.front); + assert(toFill.length > 0); + import core.lifetime : emplace; + emplace!T(&toFill[0], range.front); } - assert(toFill.empty); + assert(toFill.length == 0); } else { @@ -1604,7 +2239,7 @@ if (isInputRange!R) array = cast(T[]) buf; return false; } - import std.conv : emplace; + import core.lifetime : emplace; emplace!T(buf[$ - T.sizeof .. $], range.front); } @@ -1627,7 +2262,7 @@ if (isInputRange!R) @system unittest { auto arr = theAllocator.makeArray!int([1, 2, 3]); - ForcedInputRange r; + ForcedInputRange!int r; int[] b = [ 1, 2, 3, 4 ]; auto temp = b; r.array = &temp; @@ -1635,8 +2270,38 @@ if (isInputRange!R) assert(arr == [1, 2, 3, 1, 2, 3, 4]); } +// Regression test for https://issues.dlang.org/show_bug.cgi?id=20929 +@system unittest +{ + static void test(Char, Allocator)(auto ref Allocator alloc) + { + auto arr = alloc.makeArray!Char(1, Char('f')); + + import std.utf : byUTF; + auto forwardRange = "oo".byUTF!Char(); + static assert(isForwardRange!(typeof(forwardRange))); + // Test the forward-range code-path. + assert(alloc.expandArray(arr, forwardRange)); + + assert(arr == "foo"); + + immutable(Char)[] temp = "bar"; + auto inputRange = ForcedInputRange!(immutable(Char))(&temp); + // Test the input-range code-path. + assert(alloc.expandArray(arr, inputRange)); + + assert(arr == "foobar"); + } + + import std.experimental.allocator.gc_allocator : GCAllocator; + test!char(GCAllocator.instance); + test!wchar(GCAllocator.instance); + test!char(theAllocator); + test!wchar(theAllocator); +} + /** -Shrinks an array by $(D delta) elements. +Shrinks an array by `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 @@ -1647,7 +2312,7 @@ 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)) +delta = number of elements to remove (upon success the new length of `array` is $(D array.length - delta)) Returns: `true` upon success, `false` if memory could not be reallocated. In the latter @@ -1725,23 +2390,25 @@ bool shrinkArray(T, Allocator)(auto ref Allocator alloc, /** -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) +Destroys and then deallocates (using `alloc`) 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(A, T)(auto ref A alloc, T* p) +void dispose(A, T)(auto ref A alloc, auto ref T* p) { static if (hasElaborateDestructor!T) { destroy(*p); } alloc.deallocate((cast(void*) p)[0 .. T.sizeof]); + static if (__traits(isRef, p)) + p = null; } /// Ditto -void dispose(A, T)(auto ref A alloc, T p) +void dispose(A, T)(auto ref A alloc, auto ref T p) if (is(T == class) || is(T == interface)) { if (!p) return; @@ -1760,10 +2427,12 @@ if (is(T == class) || is(T == interface)) auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length]; destroy(p); alloc.deallocate(support); + static if (__traits(isRef, p)) + p = null; } /// Ditto -void dispose(A, T)(auto ref A alloc, T[] array) +void dispose(A, T)(auto ref A alloc, auto ref T[] array) { static if (hasElaborateDestructor!(typeof(array[0]))) { @@ -1773,6 +2442,8 @@ void dispose(A, T)(auto ref A alloc, T[] array) } } alloc.deallocate(array); + static if (__traits(isRef, array)) + array = null; } @system unittest @@ -1813,7 +2484,30 @@ void dispose(A, T)(auto ref A alloc, T[] array) theAllocator.dispose(arr); } -@system unittest //bugzilla 15721 +// https://issues.dlang.org/show_bug.cgi?id=16512 +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + int* i = Mallocator.instance.make!int(0); + Mallocator.instance.dispose(i); + assert(i is null); + + Object o = Mallocator.instance.make!Object(); + Mallocator.instance.dispose(o); + assert(o is null); + + uint* u = Mallocator.instance.make!uint(0); + Mallocator.instance.dispose((){return u;}()); + assert(u !is null); + + uint[] ua = Mallocator.instance.makeArray!uint([0,1,2]); + Mallocator.instance.dispose(ua); + assert(ua is null); +} + +// https://issues.dlang.org/show_bug.cgi?id=15721 +@system unittest { import std.experimental.allocator.mallocator : Mallocator; @@ -1886,7 +2580,7 @@ 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) +void disposeMultidimensionalArray(T, Allocator)(auto ref Allocator alloc, auto ref T[] array) { static if (isArray!T) { @@ -1895,6 +2589,8 @@ void disposeMultidimensionalArray(T, Allocator)(auto ref Allocator alloc, T[] ar } dispose(alloc, array); + static if (__traits(isRef, array)) + array = null; } /// @@ -1953,84 +2649,74 @@ void disposeMultidimensionalArray(T, Allocator)(auto ref Allocator alloc, T[] ar /** -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 +Returns a dynamically-typed `CAllocator` built around a given statically- +typed allocator `a` of type `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 +$(LI If `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.) +$(LI If `A` has state, the result will $(REF move, std,algorithm,mutation) +the supplied allocator $(D A a) within. The result itself is allocated in its +own statically-typed allocator.) ) */ -CAllocatorImpl!A allocatorObject(A)(auto ref A a) +RCIAllocator allocatorObject(A)(auto ref A a) if (!isPointer!A) { - import std.conv : emplace; + import core.lifetime : 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) + __gshared ulong[s] state; + __gshared RCIAllocator result; + if (result.isNull) { // Don't care about a few races - result = emplace!(CAllocatorImpl!A)(state[]); + result = RCIAllocator(emplace!(CAllocatorImpl!A)(state[])); } - assert(result); + assert(!result.isNull); return result; } - else static if (is(typeof({ A b = a; A c = b; }))) // copyable + else { auto state = a.allocate(stateSize!(CAllocatorImpl!A)); + import std.algorithm.mutation : move; 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; + auto tmp = cast(CAllocatorImpl!A) emplace!(CAllocatorImpl!A)(state); + move(a, tmp.impl); + return RCIAllocator(tmp); } } /// Ditto -CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa) +RCIAllocator allocatorObject(A)(A* pa) { assert(pa); - import std.conv : emplace; + import core.lifetime : 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); + return RCIAllocator(emplace!(CAllocatorImpl!(A, Yes.indirect)) + (state, pa)); } /// @system unittest { import std.experimental.allocator.mallocator : Mallocator; - IAllocator a = allocatorObject(Mallocator.instance); + + RCIAllocator a = allocatorObject(Mallocator.instance); auto b = a.allocate(100); assert(b.length == 100); assert(a.deallocate(b)); @@ -2045,52 +2731,82 @@ CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa) assert(a.deallocate(b)); } +@system unittest +{ + import std.conv; + import std.experimental.allocator.mallocator; + import std.experimental.allocator.building_blocks.stats_collector; + + alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); + SCAlloc statsCollectorAlloc; + assert(statsCollectorAlloc.bytesUsed == 0); + + auto _allocator = allocatorObject(statsCollectorAlloc); + // Ensure that the allocator was passed through in CAllocatorImpl + // This allocator was used to allocate the chunk that holds the + // CAllocatorImpl object; which is it's own wrapper + assert((cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed + == stateSize!(CAllocatorImpl!(SCAlloc))); + _allocator.allocate(1); + assert((cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed + == stateSize!(CAllocatorImpl!(SCAlloc)) + 1); +} + /** -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 +Returns a dynamically-typed `CSharedAllocator` built around a given statically- +typed allocator `a` of type `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 +$(LI If `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 +$(LI If `A` has state and is copyable, the result will +$(REF move, std,algorithm,mutation) the supplied allocator $(D A a) within. +The result itself is allocated in its own statically-typed allocator.) +$(LI If `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) +//nothrow @safe +//nothrow @nogc @safe +nothrow +RCISharedAllocator sharedAllocatorObject(A)(auto ref A a) if (!isPointer!A) { - import std.conv : emplace; + import core.lifetime : 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) + static shared ulong[s] state; + static RCISharedAllocator result; + if (result.isNull) { // Don't care about a few races - result = cast(shared - CSharedAllocatorImpl!A)(emplace!(CSharedAllocatorImpl!A)(state[])); + result = RCISharedAllocator( + (cast(shared CSharedAllocatorImpl!A)( + emplace!(CSharedAllocatorImpl!A)( + (() @trusted => cast(ulong[]) state[])())))); } - assert(result); + assert(!result.isNull); 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.algorithm.mutation : move; import std.traits : hasMember; static if (hasMember!(A, "deallocate")) { scope(failure) a.deallocate(state); } - return emplace!(shared CSharedAllocatorImpl!A)(state); + auto tmp = emplace!(shared CSharedAllocatorImpl!A)(state); + move(a, tmp.impl); + return RCISharedAllocator(tmp); } else // the allocator object is not copyable { @@ -2099,17 +2815,17 @@ if (!isPointer!A) } /// Ditto -shared(CSharedAllocatorImpl!(A, Yes.indirect)) sharedAllocatorObject(A)(A* pa) +RCISharedAllocator sharedAllocatorObject(A)(A* pa) { assert(pa); - import std.conv : emplace; + import core.lifetime : 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); + return RCISharedAllocator(emplace!(shared CSharedAllocatorImpl!(A, Yes.indirect))(state, pa)); } @@ -2126,16 +2842,23 @@ class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) { import std.traits : hasMember; + static if (stateSize!Allocator) private size_t rc = 1; + /** The implementation is available as a public member. */ static if (indirect) { + nothrow: private Allocator* pimpl; + + @nogc pure @safe ref Allocator impl() { return *pimpl; } + + @nogc pure @safe this(Allocator* pa) { pimpl = pa; @@ -2147,6 +2870,7 @@ class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) else alias impl = Allocator.instance; } +nothrow: /// Returns `impl.alignment`. override @property uint alignment() { @@ -2293,6 +3017,44 @@ class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) return null; } } + + @nogc nothrow pure @safe + override void incRef() + { + static if (stateSize!Allocator) ++rc; + } + + @nogc nothrow pure @trusted + override bool decRef() + { + static if (stateSize!Allocator) + { + import core.stdc.string : memcpy; + + if (rc == 1) + { + static if (indirect) + { + Allocator* tmp = pimpl; + } + else + { + Allocator tmp; + memcpy(&tmp, &this.impl, Allocator.sizeof); + } + void[] support = (cast(void*) this)[0 .. stateSize!(typeof(this))]; + tmp.deallocate(support); + return false; + } + + --rc; + return true; + } + else + { + return true; + } + } } /** @@ -2308,17 +3070,25 @@ class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) : ISharedAllocator { import std.traits : hasMember; + import core.atomic : atomicOp, atomicLoad; + + static if (stateSize!Allocator) shared size_t rc = 1; /** The implementation is available as a public member. */ static if (indirect) { + nothrow: private shared Allocator* pimpl; + + @nogc pure @safe ref Allocator impl() shared { return *pimpl; } + + @nogc pure @safe this(Allocator* pa) shared { pimpl = pa; @@ -2330,6 +3100,7 @@ class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) else alias impl = Allocator.instance; } +nothrow: /// Returns `impl.alignment`. override @property uint alignment() shared { @@ -2476,6 +3247,45 @@ class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) return null; } } + + @nogc nothrow pure @safe + override void incRef() shared + { + static if (stateSize!Allocator) atomicOp!"+="(rc, 1); + } + + @nogc nothrow pure @trusted + override bool decRef() shared + { + static if (stateSize!Allocator) + { + import core.stdc.string : memcpy; + + // rc starts as 1 to avoid comparing with size_t(0) - 1 + if (atomicOp!"-="(rc, 1) == 0) + { + static if (indirect) + { + Allocator* tmp = pimpl; + } + else + { + Allocator tmp; + memcpy(cast(void*) &tmp, cast(void*) &this.impl, Allocator.sizeof); + Allocator empty; + memcpy(cast(void*) &this.impl, cast(void*) &empty, Allocator.sizeof); + } + void[] support = (cast(void*) this)[0 .. stateSize!(typeof(this))]; + (cast(bool delegate(void[]) @nogc nothrow pure)(&tmp.deallocate))(support); + return false; + } + return true; + } + else + { + return true; + } + } } @@ -2513,17 +3323,15 @@ class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) 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. +Stores an allocator object in thread-local storage (i.e. non-`shared` D +global). `ThreadLocal!A` is a subtype of `A` so it appears to implement +`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 +`A` must hold state, otherwise `ThreadLocal!A` refuses instantiation. This +means e.g. `ThreadLocal!Mallocator` does not work because `Mallocator`'s +state is not stored as members of `Mallocator`, but instead is hidden in the C library implementation. */ @@ -2554,23 +3362,28 @@ struct ThreadLocal(A) } /// +@system unittest { + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + static assert(!is(ThreadLocal!Mallocator)); static assert(!is(ThreadLocal!GCAllocator)); - alias ThreadLocal!(FreeList!(GCAllocator, 0, 8)) Allocator; + alias Allocator = ThreadLocal!(FreeList!(GCAllocator, 0, 8)); auto b = Allocator.instance.allocate(5); - static assert(hasMember!(Allocator, "allocate")); + static assert(__traits(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 +user code to allocate nodes externally. Then `EmbeddedTree`'s primitives wire the nodes appropriately. -Warning: currently $(D EmbeddedTree) is not using rebalancing, so it may +Warning: currently `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. */ @@ -2716,12 +3529,17 @@ private struct EmbeddedTree(T, alias less) void dump() { + import std.stdio : writeln; writeln(typeid(this), " @ ", cast(void*) &this); dump(root, 3); } void dump(Node* r, uint indent) { + import std.stdio : write, writeln; + import std.range : repeat; + import std.array : array; + write(repeat(' ', indent).array); if (!r) { @@ -2749,12 +3567,15 @@ private struct EmbeddedTree(T, alias less) } } +@system unittest { + import std.experimental.allocator.gc_allocator : GCAllocator; + alias a = GCAllocator.instance; alias Tree = EmbeddedTree!(int, (a, b) => a.payload < b.payload); Tree t; - assert(t.empty); + assert(t.empty == Ternary.yes); int[] vals = [ 6, 3, 9, 1, 0, 2, 8, 11 ]; foreach (v; vals) { @@ -2763,21 +3584,21 @@ unittest assert(n); t.assertSane; } - assert(!t.empty); + assert(t.empty != Ternary.yes); foreach (v; vals) { Tree.Node n = { v }; assert(t.remove(&n)); t.assertSane; } - assert(t.empty); + assert(t.empty == Ternary.yes); } /* -$(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 +`InternalPointersTree` adds a primitive on top of another allocator: calling +`resolveInternalPointer(p)` returns the block within which the internal +pointer `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 @@ -2786,6 +3607,8 @@ the block size and two for search management). */ private struct InternalPointersTree(Allocator) { + import std.experimental.allocator.building_blocks.affix_allocator : AffixAllocator; + alias Tree = EmbeddedTree!(size_t, (a, b) => cast(void*) a + a.payload < cast(void*) b); alias Parent = AffixAllocator!(Allocator, Tree.Node); @@ -2815,7 +3638,7 @@ private struct InternalPointersTree(Allocator) /// Ditto bool deallocate(void[] b) { - if (!b.ptr) return; + if (!b.ptr) return true; Tree.Node* n = &parent.prefix(b); blockMap.remove(n) || assert(false); parent.deallocate(b); @@ -2856,9 +3679,10 @@ private struct InternalPointersTree(Allocator) return Ternary(blockMap.empty); } - /** Returns the block inside which $(D p) resides, or $(D null) if the + /** Returns the block inside which `p` resides, or `null` if the pointer does not belong. */ + pure nothrow @safe @nogc Ternary resolveInternalPointer(const void* p, ref void[] result) { // Must define a custom find @@ -2870,7 +3694,7 @@ private struct InternalPointersTree(Allocator) { n = n.left; } - else if (p > (cast(void*) (n + 1)) + n.payload) + else if ((() @trusted => p > (cast(void*) (n + 1)) + n.payload)()) { n = n.right; } @@ -2884,13 +3708,17 @@ private struct InternalPointersTree(Allocator) auto n = find(); if (!n) return Ternary.no; - result = (cast(void*) (n + 1))[0 .. n.payload]; + result = (() @trusted => (cast(void*) (n + 1))[0 .. n.payload])(); return Ternary.yes; } } +@system unittest { + import std.experimental.allocator.mallocator : Mallocator; + import std.random : randomCover; + InternalPointersTree!(Mallocator) a; int[] vals = [ 6, 3, 9, 1, 2, 8, 11 ]; void[][] allox; @@ -2902,28 +3730,40 @@ unittest 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); + () pure nothrow @safe { + void[] p; + Ternary r = (() @nogc => a.resolveInternalPointer(&b[0], p))(); + assert(&p[0] == &b[0] && p.length >= b.length); + r = a.resolveInternalPointer((() @trusted => &b[0] + b.length)(), p); + assert(&p[0] == &b[0] && p.length >= b.length); + r = a.resolveInternalPointer((() @trusted => &b[0] + b.length / 2)(), p); + assert(&p[0] == &b[0] && p.length >= b.length); + auto bogus = new void[b.length]; + assert(a.resolveInternalPointer(&bogus[0], p) == Ternary.no); + }(); } foreach (b; allox.randomCover) { - a.deallocate(b); + () nothrow @nogc { a.deallocate(b); }(); } - assert(a.empty); + assert(a.empty == Ternary.yes); } //version (std_allocator_benchmark) +@system unittest { + import std.experimental.allocator.building_blocks.null_allocator : NullAllocator; + import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.experimental.allocator.building_blocks.segregator : Segregator; + import std.experimental.allocator.building_blocks.bucketizer : Bucketizer; + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + static void testSpeed(A)() { static if (stateSize!A) A a; @@ -2938,11 +3778,11 @@ unittest switch (uniform(0, 2)) { case 0: - a.deallocate(bufs[j]); + () nothrow @nogc { a.deallocate(bufs[j]); }(); bufs[j] = a.allocate(uniform(0, 4096)); break; case 1: - a.deallocate(bufs[j]); + () nothrow @nogc { a.deallocate(bufs[j]); }(); bufs[j] = null; break; default: @@ -2951,6 +3791,8 @@ unittest } } + import std.algorithm.comparison : max; + alias FList = FreeList!(GCAllocator, 0, unbounded); alias A = Segregator!( 8, FreeList!(GCAllocator, 0, 8), @@ -2961,23 +3803,34 @@ unittest 2048, Bucketizer!(FList, 1025, 2048, 256), 3584, Bucketizer!(FList, 2049, 3584, 512), 4072 * 1024, AllocatorList!( - (size_t n) => BitmappedBlock!(4096)(GCAllocator.instance.allocate( + (size_t n) => BitmappedBlock!(4096)(cast(ubyte[]) GCAllocator.instance.allocate( max(n, 4072 * 1024)))), GCAllocator ); - import std.datetime, std.experimental.allocator.null_allocator; + import std.stdio; + import std.conv : to; + import std.datetime.stopwatch; + import std.algorithm.iteration : map; + if (false) writeln(benchmark!( testSpeed!NullAllocator, testSpeed!Mallocator, testSpeed!GCAllocator, testSpeed!(ThreadLocal!A), testSpeed!(A), - )(20)[].map!(t => t.to!("seconds", double))); + )(20)[].map!(t => t.to!Duration)); } +@system unittest { + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.building_blocks.region : InSituRegion; + import std.experimental.allocator.building_blocks.fallback_allocator : FallbackAllocator; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + auto a = allocatorObject(Mallocator.instance); auto b = a.allocate(100); assert(b.length == 100); @@ -2995,16 +3848,24 @@ unittest } /// +@system unittest { + import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; + import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; + import std.experimental.allocator.building_blocks.segregator : Segregator; + import std.experimental.allocator.building_blocks.bucketizer : Bucketizer; + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + /// Define an allocator bound to the built-in GC. - IAllocator alloc = allocatorObject(GCAllocator.instance); + auto alloc = allocatorObject(GCAllocator.instance); auto b = alloc.allocate(42); assert(b.length == 42); - assert(alloc.deallocate(b) == Ternary.yes); + assert(alloc.deallocate(b)); + import std.algorithm.comparison : max; // 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!( @@ -3016,13 +3877,13 @@ unittest 2048, Bucketizer!(FList, 1025, 2048, 256), 3584, Bucketizer!(FList, 2049, 3584, 512), 4072 * 1024, AllocatorList!( - (n) => BitmappedBlock!(4096)(GCAllocator.instance.allocate( + (n) => BitmappedBlock!(4096)(cast(ubyte[]) GCAllocator.instance.allocate( max(n, 4072 * 1024)))), GCAllocator ) ); auto alloc2 = allocatorObject(A.instance); - b = alloc.allocate(101); - assert(alloc.deallocate(b) == Ternary.yes); + b = alloc2.allocate(101); + assert(alloc2.deallocate(b)); } diff --git a/libphobos/src/std/experimental/allocator/showcase.d b/libphobos/src/std/experimental/allocator/showcase.d index 6985e5d..3f08c56 100644 --- a/libphobos/src/std/experimental/allocator/showcase.d +++ b/libphobos/src/std/experimental/allocator/showcase.d @@ -1,9 +1,10 @@ +// Written in the D programming language. /** - 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. +Source: $(PHOBOSSRC std/experimental/allocator/_showcase.d) */ module std.experimental.allocator.showcase; @@ -14,8 +15,8 @@ import std.traits : hasMember; /** -Allocator that uses stack allocation for up to $(D stackSize) bytes and -then falls back to $(D Allocator). Defined as: +Allocator that uses stack allocation for up to `stackSize` bytes and +then falls back to `Allocator`. Defined as: ---- alias StackFront(size_t stackSize, Allocator) = diff --git a/libphobos/src/std/experimental/allocator/typed.d b/libphobos/src/std/experimental/allocator/typed.d index 92828f3..85cc649 100644 --- a/libphobos/src/std/experimental/allocator/typed.d +++ b/libphobos/src/std/experimental/allocator/typed.d @@ -1,3 +1,4 @@ +// Written in the D programming language. /** This module defines `TypedAllocator`, a statically-typed allocator that aggregates multiple untyped allocators and uses them depending on the static @@ -5,8 +6,10 @@ 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). +Source: $(PHOBOSSRC std/experimental/allocator/typed.d) + Macros: -T2=$(TR $(D $1) $(TD $(ARGS $+))) +T2=$(TR `$1` $(TD $(ARGS $+))) */ module std.experimental.allocator.typed; @@ -25,7 +28,7 @@ allocator accordingly. */ enum AllocFlag : uint { - init = 0, + _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 @@ -80,12 +83,12 @@ 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 +description are `int[]`, `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)[]).) +description are `int*[]`, `Object[]`, $(D Tuple!(int, string)[]).) $(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections |$(NBSP)AllocFlag.fixedSize, @@ -402,9 +405,10 @@ struct TypedAllocator(PrimaryAllocator, Policies...) | AllocFlag.hasNoIndirections, MmapAllocator, ); + MyAllocator a; auto b = &a.allocatorFor!0(); - static assert(is(typeof(*b) == shared GCAllocator)); + static assert(is(typeof(*b) == shared const(GCAllocator))); enum f1 = AllocFlag.fixedSize | AllocFlag.threadLocal; auto c = &a.allocatorFor!f1(); static assert(is(typeof(*c) == Mallocator)); diff --git a/libphobos/src/std/experimental/checkedint.d b/libphobos/src/std/experimental/checkedint.d index 48ed2f7..b33c2ed 100644 --- a/libphobos/src/std/experimental/checkedint.d +++ b/libphobos/src/std/experimental/checkedint.d @@ -1,3 +1,4 @@ +// Written in the D programming language. /** $(SCRIPT inhibitQuickIndex = 1;) @@ -185,14 +186,20 @@ $(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound)) binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=` is larger than the largest value representable by `T`.) ) +$(TR $(TD `hookToHash`) $(TD If defined, $(D hook.hookToHash(payload)) +(where `payload` is a reference to the value wrapped by Checked) is forwarded +to when `toHash` is called on a Checked type. Custom hashing can be implemented +in a `Hook`, otherwise the built-in hashing is used.) +) ) +Source: $(PHOBOSSRC std/experimental/checkedint.d) */ module std.experimental.checkedint; import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; /// -@system unittest +@safe unittest { int[] concatAndAdd(int[] a, int[] b, int offset) { @@ -208,6 +215,44 @@ import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]); } + +/// `Saturate` stops at an overflow +@safe unittest +{ + auto x = (cast(byte) 127).checked!Saturate; + assert(x == 127); + x++; + assert(x == 127); +} + +/// `WithNaN` has a special "Not a Number" (NaN) value akin to the homonym value reserved for floating-point values +@safe unittest +{ + auto x = 100.checked!WithNaN; + assert(x == 100); + x /= 0; + assert(x.isNaN); +} + +/// `ProperCompare` fixes the comparison operators ==, !=, <, <=, >, and >= to return correct results +@safe unittest +{ + uint x = 1; + auto y = x.checked!ProperCompare; + assert(x < -1); // built-in comparison + assert(y > -1); // ProperCompare +} + +/// `Throw` fails every incorrect operation by throwing an exception +@safe unittest +{ + import std.exception : assertThrown; + auto x = -1.checked!Throw; + assertThrown(x / 0); + assertThrown(x + int.min); + assertThrown(x == uint.max); +} + /** 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 @@ -218,7 +263,9 @@ 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; + import std.format.spec : FormatSpec; + import std.range.primitives : isInputRange, ElementType; + import std.traits : hasMember, isSomeChar; /** The type of the integral subject to checking. @@ -262,7 +309,7 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) { enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T); /// - @system unittest + @safe unittest { assert(Checked!short.min == -32768); assert(Checked!(short, WithNaN).min == -32767); @@ -295,7 +342,7 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) payload = rhs.payload; } /// - @system unittest + @safe unittest { auto a = checked(42L); assert(a == 42); @@ -306,15 +353,17 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) /** Assignment operator. Has the same constraints as the constructor. */ - void opAssign(U)(U rhs) if (is(typeof(Checked!(T, Hook)(rhs)))) + ref Checked opAssign(U)(U rhs) return + if (is(typeof(Checked!(T, Hook)(rhs)))) { static if (isIntegral!U) payload = rhs; else payload = rhs.payload; + return this; } /// - @system unittest + @safe unittest { Checked!long a; a = 42L; @@ -323,6 +372,42 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) assert(a == 4242); } + /// + @safe unittest + { + Checked!long a, b; + a = b = 3; + assert(a == 3 && b == 3); + } + + /** + Construct from a decimal string. The conversion follows the same rules as + $(REF to, std, conv) converting a string to the wrapped `T` type. + + Params: + str = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of characters + */ + this(Range)(Range str) + if (isInputRange!Range && isSomeChar!(ElementType!Range)) + { + import std.conv : to; + + this(to!T(str)); + } + + /** + $(REF to, std, conv) can convert a string to a `Checked!T`: + */ + @system unittest + { + import std.conv : to; + + const a = to!long("1234"); + const b = to!(Checked!long)("1234"); + assert(a == b); + } + // opCast /** Casting operator to integral, `bool`, or floating point type. If `Hook` @@ -379,7 +464,7 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) } } /// - @system unittest + @safe unittest { assert(cast(uint) checked(42) == 42); assert(cast(uint) checked!WithNaN(-42) == uint.max); @@ -435,6 +520,8 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) /// static if (is(T == int) && is(Hook == void)) @safe unittest { + import std.traits : isUnsigned; + static struct MyHook { static bool thereWereErrors; @@ -470,6 +557,110 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) assert(!MyHook.thereWereErrors); } + // toHash + /** + Generates a hash for `this`. If `Hook` defines `hookToHash`, the call + immediately returns `hook.hookToHash(payload)`. If `Hook` does not + implement `hookToHash`, but it has state, a hash will be generated for + the `Hook` using the built-in function and it will be xored with the + hash of the `payload`. + */ + size_t toHash() const nothrow @safe + { + static if (hasMember!(Hook, "hookToHash")) + { + return hook.hookToHash(payload); + } + else static if (stateSize!Hook > 0) + { + static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash() ^ hashOf(hook); + } + else + { + return hashOf(payload) ^ hashOf(hook); + } + } + else static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash(); + } + else + { + return .hashOf(payload); + } + } + + /// ditto + size_t toHash(this _)() shared const nothrow @safe + { + import core.atomic : atomicLoad, MemoryOrder; + static if (is(typeof(this.payload.atomicLoad!(MemoryOrder.acq)) P)) + { + auto payload = __ctfe ? cast(P) this.payload + : this.payload.atomicLoad!(MemoryOrder.acq); + } + else + { + alias payload = this.payload; + } + + static if (hasMember!(Hook, "hookToHash")) + { + return hook.hookToHash(payload); + } + else static if (stateSize!Hook > 0) + { + static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash() ^ hashOf(hook); + } + else + { + return hashOf(payload) ^ hashOf(hook); + } + } + else static if (hasMember!(typeof(payload), "toHash")) + { + return payload.toHash(); + } + else + { + return .hashOf(payload); + } + } + + /** + Writes a string representation of this to a `sink`. + + Params: + sink = A `Char` accepting + $(REF_ALTTEXT output range, isOutputRange, std,range,primitives). + fmt = A $(REF FormatSpec, std, format) which controls how this + is formatted. + */ + void toString(Writer, Char)(scope ref Writer sink, scope const ref FormatSpec!Char fmt) const + { + import std.format.write : formatValue; + if (fmt.spec == 's') + return formatValue(sink, this, fmt); + else + return formatValue(sink, payload, fmt); + } + + /** + `toString` is rarely directly invoked; the usual way of using it is via + $(REF format, std, format): + */ + @system unittest + { + import std.format; + + assert(format("%04d", checked(15)) == "0015"); + assert(format("0x%02x", checked(15)) == "0x0f"); + } + // opCmp /** @@ -536,6 +727,8 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) /// static if (is(T == int) && is(Hook == void)) @safe unittest { + import std.traits : isUnsigned; + static struct MyHook { static bool thereWereErrors; @@ -576,7 +769,7 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) } // For coverage - static if (is(T == int) && is(Hook == void)) @system unittest + static if (is(T == int) && is(Hook == void)) @safe unittest { assert(checked(42) <= checked!void(42)); assert(checked!void(42) <= checked(42u)); @@ -787,7 +980,7 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) } } - static if (is(T == int) && is(Hook == void)) @system unittest + static if (is(T == int) && is(Hook == void)) @safe unittest { const a = checked(42); assert(a + 1 == 43); @@ -854,7 +1047,7 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) { bool overflow; auto r = opChecked!op(lhs, T(payload), overflow); - if (overflow) r = hook.onOverflow!op(42); + if (overflow) r = hook.onOverflow!op(lhs, payload); return Checked!(typeof(r), Hook)(r); } else @@ -865,7 +1058,7 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) } } - static if (is(T == int) && is(Hook == void)) @system unittest + static if (is(T == int) && is(Hook == void)) @safe unittest { assert(1 + checked(1) == 2); static uint tally; @@ -905,6 +1098,10 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) Hook).max) and if `Hook` defines `onUpperBound`, the payload is assigned from $(D hook.onUpperBound(result, min)). + If the right-hand side is also a Checked but with a different hook or + underlying type, the hook and underlying type of this Checked takes + precedence. + In all other cases, the built-in behavior is carried out. Params: @@ -953,6 +1150,13 @@ if (isIntegral!T || is(T == Checked!(U, H), U, H)) return this; } + /// ditto + ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return + if (is(Rhs == Checked!(RhsT, RhsHook), RhsT, RhsHook)) + { + return opOpAssign!(op, typeof(rhs.payload))(rhs.payload); + } + /// static if (is(T == int) && is(Hook == void)) @safe unittest { @@ -994,7 +1198,7 @@ if (is(typeof(Checked!(T, Hook)(value)))) } /// -@system unittest +@safe unittest { static assert(is(typeof(checked(42)) == Checked!int)); assert(checked(42) == Checked!int(42)); @@ -1014,6 +1218,18 @@ if (is(typeof(Checked!(T, Hook)(value)))) test!(immutable ubyte); } +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=21758 + assert(4 * checked(5L) == 20); + assert(20 / checked(5L) == 4); + assert(2 ^^ checked(3L) == 8); + assert(12 % checked(5L) == 2); + assert((0xff & checked(3L)) == 3); + assert((0xf0 | checked(3L)) == 0xf3); + assert((0xff ^ checked(3L)) == 0xfc); +} + // Abort /** @@ -1157,7 +1373,7 @@ static: } } -@system unittest +@safe unittest { void test(T)() { @@ -1366,7 +1582,7 @@ default behavior. */ struct Warn { - import std.stdio : stderr; + import std.stdio : writefln; static: /** @@ -1383,7 +1599,7 @@ static: */ Dst onBadCast(Dst, Src)(Src src) { - stderr.writefln("Erroneous cast: cast(%s) %s(%s)", + trustedStderr.writefln("Erroneous cast: cast(%s) %s(%s)", Dst.stringof, Src.stringof, src); return cast(Dst) src; } @@ -1402,14 +1618,14 @@ static: */ Lhs onLowerBound(Rhs, T)(Rhs rhs, T bound) { - stderr.writefln("Lower bound error: %s(%s) < %s(%s)", + trustedStderr.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)", + trustedStderr.writefln("Upper bound error: %s(%s) > %s(%s)", Rhs.stringof, rhs, T.stringof, bound); return cast(T) rhs; } @@ -1436,7 +1652,7 @@ static: auto result = opChecked!"=="(lhs, rhs, error); if (error) { - stderr.writefln("Erroneous comparison: %s(%s) == %s(%s)", + trustedStderr.writefln("Erroneous comparison: %s(%s) == %s(%s)", Lhs.stringof, lhs, Rhs.stringof, rhs); return lhs == rhs; } @@ -1444,7 +1660,7 @@ static: } /// - @system unittest + @safe unittest { auto x = checked!Warn(-42); // Passes @@ -1475,7 +1691,7 @@ static: auto result = opChecked!"cmp"(lhs, rhs, error); if (error) { - stderr.writefln("Erroneous ordering comparison: %s(%s) and %s(%s)", + trustedStderr.writefln("Erroneous ordering comparison: %s(%s) and %s(%s)", Lhs.stringof, lhs, Rhs.stringof, rhs); return lhs < rhs ? -1 : lhs > rhs; } @@ -1483,7 +1699,7 @@ static: } /// - @system unittest + @safe unittest { auto x = checked!Warn(-42); // Passes @@ -1508,21 +1724,34 @@ static: */ typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs) { - stderr.writefln("Overflow on unary operator: %s%s(%s)", + trustedStderr.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)", + trustedStderr.writefln("Overflow on binary operator: %s(%s) %s %s(%s)", Lhs.stringof, lhs, x, Rhs.stringof, rhs); - return mixin("lhs" ~ x ~ "rhs"); + static if (x == "/") // Issue 20743: mixin below would cause SIGFPE on POSIX + return typeof(lhs / rhs).min; // or EXCEPTION_INT_OVERFLOW on Windows + else + return mixin("lhs" ~ x ~ "rhs"); + } + + // This is safe because we do not assign to the reference returned by + // `stderr`. The ability for the caller to do that is why `stderr` is not + // safe in the general case. + private @property auto ref trustedStderr() @trusted + { + import std.stdio : stderr; + + return stderr; } } /// -@system unittest +@safe unittest { auto x = checked!Warn(42); short x1 = cast(short) x; @@ -1531,6 +1760,39 @@ static: short y1 = cast(const byte) y; } +@system unittest +{ + auto a = checked!Warn(int.min); + auto b = checked!Warn(-1); + auto x = checked!Abort(int.min); + auto y = checked!Abort(-1); + + // Temporarily redirect output to stderr to make sure we get the right output. + import std.file : exists, remove; + import std.process : uniqueTempPath; + import std.stdio : stderr; + auto tmpname = uniqueTempPath; + scope(exit) if (exists(tmpname)) remove(tmpname); + auto t = stderr; + stderr.open(tmpname, "w"); + // Open a new scope to minimize code ran with stderr redirected. + { + scope(exit) stderr = t; + assert(a / b == a * b); + import std.exception : assertThrown; + import core.exception : AssertError; + assertThrown!AssertError(x / y); + } + import std.file : readText; + import std.ascii : newline; + auto witness = readText(tmpname); + auto expected = +"Overflow on binary operator: int(-2147483648) / const(int)(-1)" ~ newline ~ +"Overflow on binary operator: int(-2147483648) * const(int)(-1)" ~ newline ~ +"Overflow on binary operator: int(-2147483648) / const(int)(-1)" ~ newline; + assert(witness == expected, "'" ~ witness ~ "'"); +} + // ProperCompare /** @@ -1668,7 +1930,7 @@ struct ProperCompare assert(opCmpProper(42, 42.0) == 0); assert(opCmpProper(41, 42.0) < 0); assert(opCmpProper(42, 41.0) > 0); - import std.math : isNaN; + import std.math.traits : isNaN; assert(isNaN(opCmpProper(41, double.init))); assert(opCmpProper(42u, 42) == 0); assert(opCmpProper(42, 42u) == 0); @@ -1707,9 +1969,9 @@ static: */ enum T defaultValue(T) = T.min == 0 ? T.max : T.min; /** - The maximum value representable is $(D T.max) for signed integrals, $(D + The maximum value representable is `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. + T.min + 1) for signed integrals, `0` for unsigned integrals. */ enum T max(T) = cast(T) (T.min == 0 ? T.max - 1 : T.max); /// ditto @@ -1893,8 +2155,8 @@ static: 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))). + returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + + rhs))). Params: x = The operator symbol @@ -1932,8 +2194,8 @@ static: 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))). + returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + + rhs))). Params: x = The operator symbol @@ -2179,7 +2441,7 @@ static: Returns: The saturated result of the operator. */ - typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) + auto onOverflow(string x, Lhs)(Lhs lhs) { static assert(x == "-" || x == "++" || x == "--"); return x == "--" ? Lhs.min : Lhs.max; @@ -2618,7 +2880,7 @@ if (isIntegral!T && T.sizeof >= 4) testPow!ulong(3, 41); } -version (unittest) private struct CountOverflows +version (StdUnittest) private struct CountOverflows { uint calls; auto onOverflow(string op, Lhs)(Lhs lhs) @@ -2643,19 +2905,18 @@ version (unittest) private struct CountOverflows } } -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 { + static struct CountOpBinary + { + uint calls; + auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return mixin("lhs" ~ op ~ "rhs"); + } + } auto x = Checked!(const int, void)(42), y = Checked!(immutable int, void)(142); assert(x + y == 184); assert(x + 100 == 142); @@ -3061,3 +3322,140 @@ version (unittest) private struct CountOpBinary x = -1; assert(x > y); } + +@nogc nothrow pure @safe unittest +{ + alias cint = Checked!(int, void); + cint a = 1, b = 2; + a += b; + assert(a == cint(3)); + + alias ccint = Checked!(cint, Saturate); + ccint c = 14; + a += c; + assert(a == cint(17)); +} + +// toHash +@safe unittest +{ + assert(checked(42).toHash() == checked(42).toHash()); + assert(checked(12).toHash() != checked(19).toHash()); + + static struct Hook1 + { + static size_t hookToHash(T)(T payload) nothrow @trusted + { + static if (size_t.sizeof == 4) + { + return typeid(payload).getHash(&payload) ^ 0xFFFF_FFFF; + } + else + { + return typeid(payload).getHash(&payload) ^ 0xFFFF_FFFF_FFFF_FFFF; + } + + } + } + + auto a = checked!Hook1(78); + auto b = checked!Hook1(78); + assert(a.toHash() == b.toHash()); + + assert(checked!Hook1(12).toHash() != checked!Hook1(13).toHash()); + + static struct Hook2 + { + static if (size_t.sizeof == 4) + { + static size_t hashMask = 0xFFFF_0000; + } + else + { + static size_t hashMask = 0xFFFF_0000_FFFF_0000; + } + + static size_t hookToHash(T)(T payload) nothrow @trusted + { + return typeid(payload).getHash(&payload) ^ hashMask; + } + } + + auto x = checked!Hook2(1901); + auto y = checked!Hook2(1989); + + assert((() nothrow @safe => x.toHash() == x.toHash())()); + + assert(x.toHash() == x.toHash()); + assert(x.toHash() != y.toHash()); + assert(checked!Hook1(1901).toHash() != x.toHash()); + + immutable z = checked!Hook1(1901); + immutable t = checked!Hook1(1901); + immutable w = checked!Hook2(1901); + + assert(z.toHash() == t.toHash()); + assert(z.toHash() != x.toHash()); + assert(z.toHash() != w.toHash()); + + const long c = 0xF0F0F0F0; + const long d = 0xF0F0F0F0; + + assert(checked!Hook1(c).toHash() != checked!Hook2(c)); + assert(checked!Hook1(c).toHash() != checked!Hook1(d)); + + // Hook with state, does not implement hookToHash + static struct Hook3 + { + ulong var1 = ulong.max; + uint var2 = uint.max; + } + + assert(checked!Hook3(12).toHash() != checked!Hook3(13).toHash()); + assert(checked!Hook3(13).toHash() == checked!Hook3(13).toHash()); + + // Hook with no state and no hookToHash, payload has its own hashing function + auto x1 = Checked!(Checked!int, ProperCompare)(123); + auto x2 = Checked!(Checked!int, ProperCompare)(123); + auto x3 = Checked!(Checked!int, ProperCompare)(144); + + assert(x1.toHash() == x2.toHash()); + assert(x1.toHash() != x3.toHash()); + assert(x2.toHash() != x3.toHash()); + + // Check shared. + { + shared shared0 = checked(12345678); + shared shared1 = checked!Hook1(123456789); + shared shared2 = checked!Hook2(234567891); + shared shared3 = checked!Hook3(345678912); + assert(shared0.toHash() == hashOf(shared0)); + assert(shared1.toHash() == hashOf(shared1)); + assert(shared2.toHash() == hashOf(shared2)); + assert(shared3.toHash() == hashOf(shared3)); + } +} + +/// +@safe unittest +{ + struct MyHook + { + static size_t hookToHash(T)(const T payload) nothrow @trusted + { + return .hashOf(payload); + } + } + + int[Checked!(int, MyHook)] aa; + Checked!(int, MyHook) var = 42; + aa[var] = 100; + + assert(aa[var] == 100); + + int[Checked!(int, Abort)] bb; + Checked!(int, Abort) var2 = 42; + bb[var2] = 100; + + assert(bb[var2] == 100); +} diff --git a/libphobos/src/std/experimental/logger/core.d b/libphobos/src/std/experimental/logger/core.d index 2f857c6..08d6cbe 100644 --- a/libphobos/src/std/experimental/logger/core.d +++ b/libphobos/src/std/experimental/logger/core.d @@ -1,4 +1,7 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/logger/core.d) +*/ module std.experimental.logger.core; import core.sync.mutex : Mutex; @@ -9,9 +12,9 @@ import std.traits; import std.experimental.logger.filelogger; -/** This template evaluates if the passed $(D LogLevel) is active. +/** This template evaluates if the passed `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 +`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. */ @@ -56,10 +59,10 @@ template isLoggingActiveAt(LogLevel ll) } } -/// This compile-time flag is $(D true) if logging is not statically disabled. +/// This compile-time flag is `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 +/** This functions is used at runtime to determine if a `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. @@ -96,19 +99,19 @@ bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL, && condition; } -/** This template returns the $(D LogLevel) named "logLevel" of type $(D +/** This template returns the `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) +suffix "_loggerconfig.d". This `LogLevel` sets the minimal `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 +A minimal `LogLevel` can be defined on a per module basis. +In order to define a module `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 +If this module exists and it contains a `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). +parent module. Then the moduleLogLevel is `LogLevel.all`. */ template moduleLogLevel(string moduleName) if (!moduleName.length) @@ -156,16 +159,16 @@ private string parentOf(string mod) return null; } -/* This function formates a $(D SysTime) into an $(D OutputRange). +/* This function formates a `SysTime` into an `OutputRange`. -The $(D SysTime) is formatted similar to +The `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; + import std.format.write : formattedWrite; const auto dt = cast(DateTime) time; const auto fsec = time.fracSecs.total!"msecs"; @@ -177,13 +180,13 @@ if (isOutputRange!(OutputRange,string)) /** 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). +In order for the data to be processed, the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog` and the +`defaultLogLevel`; additionally the condition passed must be `true`. Params: - ll = The $(D LogLevel) used by this log call. - condition = The condition must be $(D true) for the data to be logged. + ll = The `LogLevel` used by this log call. + condition = The condition must be `true` for the data to be logged. args = The data that should be logged. Example: @@ -224,11 +227,11 @@ void log(T, string moduleName = __MODULE__)(const LogLevel ll, /** 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). +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog`. Params: - ll = The $(D LogLevel) used by this log call. + ll = The `LogLevel` used by this log call. args = The data that should be logged. Example: @@ -268,12 +271,12 @@ void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg, /** 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). +In order for the data to be processed the `LogLevel` of the +`sharedLog` must be greater or equal to the `defaultLogLevel` +add the condition passed must be `true`. Params: - condition = The condition must be $(D true) for the data to be logged. + condition = The condition must be `true` for the data to be logged. args = The data that should be logged. Example: @@ -307,8 +310,8 @@ void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg, /** 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). +In order for the data to be processed the `LogLevel` of the +`sharedLog` must be greater or equal to the `defaultLogLevel`. Params: args = The data that should be logged. @@ -343,16 +346,16 @@ void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, } } -/** This function logs data in a $(D printf)-style manner. +/** This function logs data in a `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). +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog` and the +`defaultLogLevel` additionally the condition passed must be `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. + ll = The `LogLevel` used by this log call. + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -376,15 +379,15 @@ void logf(int line = __LINE__, string file = __FILE__, } } -/** This function logs data in a $(D printf)-style manner. +/** This function logs data in a `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). +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog` and the +`defaultLogLevel`. Params: - ll = The $(D LogLevel) used by this log call. - msg = The $(D printf)-style string. + ll = The `LogLevel` used by this log call. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -407,15 +410,15 @@ void logf(int line = __LINE__, string file = __FILE__, } } -/** This function logs data in a $(D printf)-style manner. +/** This function logs data in a `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). +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `defaultLogLevel` additionally the condition +passed must be `true`. Params: - condition = The condition must be $(D true) for the data to be logged. - msg = The $(D printf)-style string. + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -435,13 +438,13 @@ void logf(int line = __LINE__, string file = __FILE__, } } -/** This function logs data in a $(D printf)-style manner. +/** This function logs data in a `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). +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `defaultLogLevel`. Params: - msg = The $(D printf)-style string. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -461,7 +464,7 @@ void logf(int line = __LINE__, string file = __FILE__, } } -/** This template provides the global log functions with the $(D LogLevel) +/** This template provides the global log functions with the `LogLevel` is encoded in the function name. The aliases following this template create the public names of these log @@ -495,18 +498,18 @@ template defaultLogFunction(LogLevel ll) } } -/** This function logs data to the $(D stdThreadLocalLog), optionally depending +/** This function logs data to the `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). +In order for the resulting log message to be logged the `LogLevel` must +be greater or equal than the `LogLevel` of the `stdThreadLocalLog` and +must be greater or equal than the global `LogLevel`. +Additionally the `LogLevel` must be greater or equal than the `LogLevel` +of the `stdSharedLogger`. +If a condition is given, it must evaluate to `true`. Params: - condition = The condition must be $(D true) for the data to be logged. + condition = The condition must be `true` for the data to be logged. args = The data that should be logged. Example: @@ -535,8 +538,8 @@ 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. +/** This template provides the global `printf`-style log functions with +the `LogLevel` is encoded in the function name. The aliases following this template create the public names of the log functions. @@ -569,17 +572,17 @@ template defaultLogFunctionf(LogLevel ll) } } -/** This function logs data to the $(D sharedLog) in a $(D printf)-style +/** This function logs data to the `sharedLog` in a `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). +In order for the resulting log message to be logged the `LogLevel` must +be greater or equal than the `LogLevel` of the `sharedLog` and +must be greater or equal than the global `LogLevel`. +Additionally the `LogLevel` must be greater or equal than the `LogLevel` +of the `stdSharedLogger`. Params: - msg = The $(D printf)-style string. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -591,18 +594,18 @@ 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 +The second version of the function logs data to the `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). +In order for the resulting log message to be logged the `LogLevel` must +be greater or equal than the `LogLevel` of the `sharedLog` and +must be greater or equal than the global `LogLevel`. +Additionally the `LogLevel` must be greater or equal than the `LogLevel` +of the `stdSharedLogger`. Params: - condition = The condition must be $(D true) for the data to be logged. - msg = The $(D printf)-style string. + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -654,7 +657,7 @@ private struct MsgRange private void formatString(A...)(MsgRange oRange, A args) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; foreach (arg; args) { @@ -677,13 +680,13 @@ private void formatString(A...)(MsgRange oRange, A args) /** 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 +If a log function with `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. */ + all = 1, /** Lowest possible assignable `LogLevel`. */ + trace = 32, /** `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 @@ -694,15 +697,15 @@ enum LogLevel : ubyte logged with this level. */ fatal = 192, /** Log messages that describe fatal errors should use this level. */ - off = ubyte.max /** Highest possible $(D LogLevel). */ + off = ubyte.max /** Highest possible `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 +logger a deriving class needs to implement the `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 +It is also possible to `override` the three methods `beginLogMsg`, +`logMsgPart` and `finishLogMsg` together, this option gives more flexibility. */ abstract class Logger @@ -727,7 +730,7 @@ abstract class Logger string prettyFuncName; /// the name of the module the log message is coming from string moduleName; - /// the $(D LogLevel) associated with the log message + /// the `LogLevel` associated with the log message LogLevel logLevel; /// thread id of the log message Tid threadId; @@ -735,7 +738,7 @@ abstract class Logger SysTime timestamp; /// the message of the log message string msg; - /// A refernce to the $(D Logger) used to create this $(D LogEntry) + /// A refernce to the `Logger` used to create this `LogEntry` Logger logger; } @@ -759,7 +762,7 @@ abstract class Logger } /** A custom logger must implement this method in order to work in a - $(D MultiLogger) and $(D ArrayLogger). + `MultiLogger` and `ArrayLogger`. Params: payload = All information associated with call to log function. @@ -768,14 +771,14 @@ abstract class Logger */ abstract protected void writeLogMsg(ref LogEntry payload) @safe; - /* The default implementation will use an $(D std.array.appender) + /* The default implementation will use an `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). + reimplementing `beginLogMsg`, `logMsgPart` and `finishLogMsg`. + `beginLogMsg` is always called first, followed by any number of calls + to `logMsgPart` and one call to `finishLogMsg`. - As an example for such a custom $(D Logger) compare this: + As an example for such a custom `Logger` compare this: ---------------- class CLogger : Logger { @@ -822,7 +825,7 @@ abstract class Logger } /** Logs a part of the log message. */ - protected void logMsgPart(const(char)[] msg) @safe + protected void logMsgPart(scope const(char)[] msg) @safe { static if (isLoggingActive) { @@ -831,7 +834,7 @@ abstract class Logger } /** Signals that the message has been written and no more calls to - $(D logMsgPart) follow. */ + `logMsgPart` follow. */ protected void finishLogMsg() @safe { static if (isLoggingActive) @@ -841,12 +844,12 @@ abstract class Logger } } - /** 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). + /** The `LogLevel` determines if the log call are processed or dropped + by the `Logger`. In order for the log call to be processed the + `LogLevel` of the log call must be greater or equal to the `LogLevel` + of the `logger`. - These two methods set and get the $(D LogLevel) of the used $(D Logger). + These two methods set and get the `LogLevel` of the used `Logger`. Example: ----------- @@ -866,10 +869,10 @@ abstract class Logger synchronized (mutex) this.logLevel_ = lv; } - /** This $(D delegate) is called in case a log message with - $(D LogLevel.fatal) gets logged. + /** This `delegate` is called in case a log message with + `LogLevel.fatal` gets logged. - By default an $(D Error) will be thrown. + By default an `Error` will be thrown. */ @property final void delegate() fatalHandler() @safe @nogc { @@ -884,10 +887,10 @@ abstract class Logger /** 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 + `forwardMsg` will ensure proper synchronization and then call + `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 + logging functions is that the `globalLogLevel` wont be evaluated again since it is assumed that the caller already checked that. */ void forwardMsg(ref LogEntry payload) @trusted @@ -905,8 +908,8 @@ abstract class Logger } } - /** This template provides the log functions for the $(D Logger) $(D class) - with the $(D LogLevel) encoded in the function name. + /** This template provides the log functions for the `Logger` `class` + with the `LogLevel` encoded in the function name. For further information see the the two functions defined inside of this template. @@ -916,11 +919,11 @@ abstract class Logger */ template memLogFunctions(LogLevel ll) { - /** This function logs data to the used $(D Logger). + /** This function logs data to the used `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). + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel`. Params: args = The data that should be logged. @@ -960,16 +963,16 @@ abstract class Logger } } - /** This function logs data to the used $(D Logger) depending on a + /** This function logs data to the used `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). + In order for the resulting log message to be logged the `LogLevel` must + be greater or equal than the `LogLevel` of the used `Logger` and + must be greater or equal than the global `LogLevel` additionally the + condition passed must be `true`. Params: - condition = The condition must be $(D true) for the data to be logged. + condition = The condition must be `true` for the data to be logged. args = The data that should be logged. Example: @@ -1008,17 +1011,17 @@ abstract class Logger } } - /** This function logs data to the used $(D Logger) in a - $(D printf)-style manner. + /** This function logs data to the used `Logger` in a + `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). + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel` additionally + the passed condition must be `true`. Params: - condition = The condition must be $(D true) for the data to be logged. - msg = The $(D printf)-style string. + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -1040,7 +1043,7 @@ abstract class Logger static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) synchronized (mutex) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) @@ -1059,15 +1062,15 @@ abstract class Logger } } - /** This function logs data to the used $(D Logger) in a - $(D printf)-style manner. + /** This function logs data to the used `Logger` in a + `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). + In order for the resulting log message to be logged the `LogLevel` must + be greater or equal than the `LogLevel` of the used `Logger` and + must be greater or equal than the global `LogLevel`. Params: - msg = The $(D printf)-style string. + msg = The `printf`-style string. args = The data that should be logged. Example: @@ -1088,7 +1091,7 @@ abstract class Logger static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) synchronized (mutex) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) { @@ -1132,15 +1135,15 @@ abstract class Logger /// Ditto alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf; - /** This method logs data with the $(D LogLevel) of the used $(D Logger). + /** This method logs data with the `LogLevel` of the used `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). + This method takes a `bool` as first argument. In order for the + data to be processed the `bool` must be `true` and the `LogLevel` + of the Logger must be greater or equal to the global `LogLevel`. Params: args = The data that should be logged. - condition = The condition must be $(D true) for the data to be logged. + condition = The condition must be `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. @@ -1200,15 +1203,15 @@ abstract class Logger } } - /** This function logs data to the used $(D Logger) with a specific - $(D LogLevel). + /** This function logs data to the used `Logger` with a specific + `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). + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel`. Params: - ll = The specific $(D LogLevel) used for logging the log message. + ll = The specific `LogLevel` used for logging the log message. args = The data that should be logged. Example: @@ -1268,16 +1271,16 @@ abstract class Logger } } - /** This function logs data to the used $(D Logger) depending on a - explicitly passed condition with the $(D LogLevel) of the used - $(D Logger). + /** This function logs data to the used `Logger` depending on a + explicitly passed condition with the `LogLevel` of the used + `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). + In order for the resulting log message to be logged the `LogLevel` + of the used `Logger` must be greater or equal than the global + `LogLevel` and the condition must be `true`. Params: - condition = The condition must be $(D true) for the data to be logged. + condition = The condition must be `true` for the data to be logged. args = The data that should be logged. Example: @@ -1339,12 +1342,12 @@ abstract class Logger } } - /** This function logs data to the used $(D Logger) with the $(D LogLevel) - of the used $(D Logger). + /** This function logs data to the used `Logger` with the `LogLevel` + of the used `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). + In order for the resulting log message to be logged the `LogLevel` + of the used `Logger` must be greater or equal than the global + `LogLevel`. Params: args = The data that should be logged. @@ -1365,7 +1368,7 @@ abstract class Logger string moduleName = __MODULE__, A...)(lazy A args) if ((args.length > 1 && !is(Unqual!(A[0]) : bool) - && !is(Unqual!(A[0]) == LogLevel)) + && !is(immutable A[0] == immutable LogLevel)) || args.length == 0) { static if (isLoggingActive) synchronized (mutex) @@ -1409,17 +1412,17 @@ abstract class Logger } } - /** 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. + /** This function logs data to the used `Logger` with a specific + `LogLevel` and depending on a condition in a `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). + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel` and the + condition must be `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. + ll = The specific `LogLevel` used for logging the log message. + condition = The condition must be `true` for the data to be logged. msg = The format string used for this log call. args = The data that should be logged. @@ -1441,7 +1444,7 @@ abstract class Logger { static if (isLoggingActive) synchronized (mutex) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) { @@ -1459,15 +1462,15 @@ abstract class Logger } } - /** This function logs data to the used $(D Logger) with a specific - $(D LogLevel) in a $(D printf)-style manner. + /** This function logs data to the used `Logger` with a specific + `LogLevel` in a `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). + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel`. Params: - ll = The specific $(D LogLevel) used for logging the log message. + ll = The specific `LogLevel` used for logging the log message. msg = The format string used for this log call. args = The data that should be logged. @@ -1489,7 +1492,7 @@ abstract class Logger { static if (isLoggingActive) synchronized (mutex) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) { @@ -1507,16 +1510,16 @@ abstract class Logger } } - /** 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. + /** This function logs data to the used `Logger` depending on a + condition with the `LogLevel` of the used `Logger` in a + `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). + In order for the resulting log message to be logged the `LogLevel` + of the used `Logger` must be greater or equal than the global + `LogLevel` and the condition must be `true`. Params: - condition = The condition must be $(D true) for the data to be logged. + condition = The condition must be `true` for the data to be logged. msg = The format string used for this log call. args = The data that should be logged. @@ -1538,7 +1541,7 @@ abstract class Logger { static if (isLoggingActive) synchronized (mutex) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, condition)) @@ -1557,11 +1560,11 @@ abstract class Logger } } - /** This method logs data to the used $(D Logger) with the $(D LogLevel) - of the this $(D Logger) in a $(D printf)-style manner. + /** This method logs data to the used `Logger` with the `LogLevel` + of the this `Logger` in a `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). + In order for the data to be processed the `LogLevel` of the `Logger` + must be greater or equal to the global `LogLevel`. Params: msg = The format string used for this log call. @@ -1584,7 +1587,7 @@ abstract class Logger { static if (isLoggingActive) synchronized (mutex) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel)) @@ -1622,10 +1625,10 @@ private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all; */ private @property Logger defaultSharedLoggerImpl() @trusted { - import std.conv : emplace; + import core.lifetime : emplace; import std.stdio : stderr; - static __gshared align(FileLogger.alignof) void[__traits(classInstanceSize, FileLogger)] _buffer; + __gshared align(FileLogger.alignof) void[__traits(classInstanceSize, FileLogger)] _buffer; import std.concurrency : initOnce; initOnce!stdSharedDefaultLogger({ @@ -1636,25 +1639,25 @@ private @property Logger defaultSharedLoggerImpl() @trusted return stdSharedDefaultLogger; } -/** This property sets and gets the default $(D Logger). +/** This property sets and gets the default `Logger`. Example: ------------- sharedLog = new FileLogger(yourFile); ------------- -The example sets a new $(D FileLogger) as new $(D sharedLog). +The example sets a new `FileLogger` as new `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 +While getting and setting `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). +writing `sharedLog`. -$(D sharedLog) is only thread-safe if the the used $(D Logger) is thread-safe. -The default $(D Logger) is thread-safe. +`sharedLog` is only thread-safe if the the used `Logger` is thread-safe. +The default `Logger` is thread-safe. ------------- if (sharedLog !is myLogger) sharedLog = new myLogger; @@ -1688,11 +1691,11 @@ if (sharedLog !is myLogger) atomicStore!(MemoryOrder.rel)(stdSharedLogger, cast(shared) logger); } -/** This methods get and set the global $(D LogLevel). +/** This methods get and set the global `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). +Every log message with a `LogLevel` lower as the global `LogLevel` +will be discarded before it reaches `writeLogMessage` method of any +`Logger`. */ /* Implementation note: For any public logging call, the global log level shall only be queried once on @@ -1712,18 +1715,18 @@ different levels at different spots in the code. // Thread Local -/** The $(D StdForwardLogger) will always forward anything to the sharedLog. +/** The `StdForwardLogger` will always forward anything to the sharedLog. -The $(D StdForwardLogger) will not throw if data is logged with $(D +The `StdForwardLogger` will not throw if data is logged with $(D LogLevel.fatal). */ class StdForwardLogger : Logger { - /** The default constructor for the $(D StdForwardLogger). + /** The default constructor for the `StdForwardLogger`. Params: - lv = The $(D LogLevel) for the $(D MultiLogger). By default the $(D - LogLevel) is $(D all). + lv = The `LogLevel` for the `MultiLogger`. By default the $(D + LogLevel) is `all`. */ this(const LogLevel lv = LogLevel.all) @safe { @@ -1743,9 +1746,9 @@ class StdForwardLogger : Logger auto nl1 = new StdForwardLogger(LogLevel.all); } -/** This $(D LogLevel) is unqiue to every thread. +/** This `LogLevel` is unqiue to every thread. -The thread local $(D Logger) will use this $(D LogLevel) to filter log calls +The thread local `Logger` will use this `LogLevel` to filter log calls every same way as presented earlier. */ //public LogLevel threadLogLevel = LogLevel.all; @@ -1756,7 +1759,7 @@ private Logger stdLoggerDefaultThreadLogger; */ private @property Logger stdThreadLocalLogImpl() @trusted { - import std.conv : emplace; + import core.lifetime : emplace; static void*[(__traits(classInstanceSize, StdForwardLogger) - 1) / (void*).sizeof + 1] _buffer; @@ -1769,14 +1772,14 @@ private @property Logger stdThreadLocalLogImpl() @trusted return stdLoggerDefaultThreadLogger; } -/** This function returns a thread unique $(D Logger), that by default -propergates all data logged to it to the $(D sharedLog). +/** This function returns a thread unique `Logger`, that by default +propergates all data logged to it to the `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 +These properties can be used to set and get this `Logger`. Every +modification to this `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 +This `Logger` is called by the free standing log functions. This allows to create thread local redirections and still use the free standing log functions. */ @@ -1843,7 +1846,7 @@ package class TestLogger : Logger } } -version (unittest) private void testFuncNames(Logger logger) @safe +version (StdUnittest) private void testFuncNames(Logger logger) @safe { string s = "I'm here"; logger.log(s); @@ -3068,7 +3071,7 @@ private void trustedStore(T)(ref shared T dst, ref T src) @trusted stdThreadLocalLog.logLevel = LogLevel.all; } -// Issue 14940 +// https://issues.dlang.org/show_bug.cgi?id=14940 @safe unittest { import std.typecons : Nullable; @@ -3098,7 +3101,7 @@ private void trustedStore(T)(ref shared T dst, ref T src) @trusted assert(tl.msg == SystemToStringMsg); } -// Issue 17328 +// https://issues.dlang.org/show_bug.cgi?id=17328 @safe unittest { import std.format : format; @@ -3120,7 +3123,7 @@ private void trustedStore(T)(ref shared T dst, ref T src) @trusted assert(fs.length == 2); } -// Issue 15954 +// https://issues.dlang.org/show_bug.cgi?id=15954 @safe unittest { import std.conv : to; @@ -3129,7 +3132,7 @@ private void trustedStore(T)(ref shared T dst, ref T src) @trusted assert(tl.msg == "123456789"); } -// Issue 16256 +// https://issues.dlang.org/show_bug.cgi?id=16256 @safe unittest { import std.conv : to; @@ -3138,14 +3141,15 @@ private void trustedStore(T)(ref shared T dst, ref T src) @trusted assert(tl.msg == "123456789"); } -// Issue 15517 +// https://issues.dlang.org/show_bug.cgi?id=15517 @system unittest { - import std.file : exists, remove; + import std.file : exists, remove, tempDir; + import std.path : buildPath; import std.stdio : File; import std.string : indexOf; - string fn = "logfile.log"; + string fn = tempDir.buildPath("logfile.log"); if (exists(fn)) { remove(fn); diff --git a/libphobos/src/std/experimental/logger/filelogger.d b/libphobos/src/std/experimental/logger/filelogger.d index 8f97b5b..6fd7e5f 100644 --- a/libphobos/src/std/experimental/logger/filelogger.d +++ b/libphobos/src/std/experimental/logger/filelogger.d @@ -1,4 +1,7 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/logger/filelogger.d) +*/ module std.experimental.logger.filelogger; import std.experimental.logger.core; @@ -10,7 +13,7 @@ import std.typecons : Flag; */ alias CreateFolder = Flag!"CreateFolder"; -/** This $(D Logger) implementation writes log messages to the associated +/** This `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. */ @@ -18,14 +21,14 @@ class FileLogger : Logger { import std.concurrency : Tid; import std.datetime.systime : SysTime; - import std.format : formattedWrite; + import std.format.write : formattedWrite; - /** A constructor for the $(D FileLogger) Logger. + /** A constructor for the `FileLogger` Logger. Params: - fn = The filename of the output file of the $(D FileLogger). If that + fn = The filename of the output file of the `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 + lv = The `LogLevel` for the `FileLogger`. By default the Example: ------------- @@ -34,22 +37,22 @@ class FileLogger : Logger auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes); ------------- */ - this(in string fn, const LogLevel lv = LogLevel.all) @safe + this(const 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). + /** A constructor for the `FileLogger` Logger that takes a reference to + a `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) + The `File` passed must be open for all the log call to the + `FileLogger`. If the `File` gets closed, using the `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). + lv = The `LogLevel` for the `FileLogger`. By default the + `LogLevel` for `FileLogger` is `LogLevel.all`. createFileNameFolder = if yes and fn contains a folder name, this folder will be created. @@ -60,7 +63,7 @@ class FileLogger : Logger auto l2 = new FileLogger(file, LogLevel.fatal); ------------- */ - this(in string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe + this(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe { import std.file : exists, mkdirRecurse; import std.path : dirName; @@ -80,17 +83,17 @@ class FileLogger : Logger this.file_.open(this.filename, "a"); } - /** A constructor for the $(D FileLogger) Logger that takes a reference to - a $(D File). + /** A constructor for the `FileLogger` Logger that takes a reference to + a `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) + The `File` passed must be open for all the log call to the + `FileLogger`. If the `File` gets closed, using the `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). + lv = The `LogLevel` for the `FileLogger`. By default the + `LogLevel` for `FileLogger` is `LogLevel.all`. Example: ------------- @@ -105,7 +108,7 @@ class FileLogger : Logger this.file_ = file; } - /** If the $(D FileLogger) is managing the $(D File) it logs to, this + /** If the `FileLogger` is managing the `File` it logs to, this method will return a reference to this File. */ @property File file() @safe @@ -114,7 +117,7 @@ class FileLogger : Logger } /* This method overrides the base class method in order to log to a file - without requiring heap allocated memory. Additionally, the $(D FileLogger) + without requiring heap allocated memory. Additionally, the `FileLogger` local mutex is logged to serialize the log calls. */ override protected void beginLogMsg(string file, int line, string funcName, @@ -128,21 +131,22 @@ class FileLogger : Logger auto lt = this.file_.lockingTextWriter(); systimeToISOString(lt, timestamp); - formattedWrite(lt, ":%s:%s:%u ", file[fnIdx .. $], - funcName[funIdx .. $], line); + import std.conv : to; + formattedWrite(lt, " [%s] %s:%u:%s ", logLevel.to!string, + file[fnIdx .. $], line, funcName[funIdx .. $]); } /* 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) + override protected void logMsgPart(scope 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. + log call. This requires flushing the `File` and releasing the + `FileLogger` local mutex. */ override protected void finishLogMsg() { @@ -151,7 +155,7 @@ class FileLogger : Logger } /* This methods overrides the base class method and delegates the - $(D LogEntry) data to the actual implementation. + `LogEntry` data to the actual implementation. */ override protected void writeLogMsg(ref LogEntry payload) { @@ -162,16 +166,19 @@ class FileLogger : Logger this.finishLogMsg(); } - /** If the $(D FileLogger) was constructed with a filename, this method - returns this filename. Otherwise an empty $(D string) is returned. + /** If the `FileLogger` was constructed with a filename, this method + returns this filename. Otherwise an empty `string` is returned. */ string getFilename() { return this.filename; } - private File file_; - private string filename; + /** The `File` log messages are written to. */ + protected File file_; + + /** The filename of the `File` log messages are written to. */ + protected string filename; } @system unittest diff --git a/libphobos/src/std/experimental/logger/multilogger.d b/libphobos/src/std/experimental/logger/multilogger.d index ed9cfd9..0751cb8 100644 --- a/libphobos/src/std/experimental/logger/multilogger.d +++ b/libphobos/src/std/experimental/logger/multilogger.d @@ -1,34 +1,37 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/logger/multilogger.d) +*/ 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). +/** This Element is stored inside the `MultiLogger` and associates a +`Logger` to a `string`. */ struct MultiLoggerEntry { - string name; /// The name if the $(D Logger) - Logger logger; /// The stored $(D Logger) + string name; /// The name if the `Logger` + Logger logger; /// The stored `Logger` } -/** MultiLogger logs to multiple $(D Logger). The $(D Logger)s are stored in an -$(D Logger[]) in their order of insertion. +/** MultiLogger logs to multiple `Logger`. The `Logger`s are stored in an +`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 +Every data logged to this `MultiLogger` will be distributed to all the $(D +Logger)s inserted into it. This `MultiLogger` implementation can +hold multiple `Logger`s with the same name. If the method `removeLogger` +is used to remove a `Logger` only the first occurrence with that name will be removed. */ class MultiLogger : Logger { - /** A constructor for the $(D MultiLogger) Logger. + /** A constructor for the `MultiLogger` Logger. Params: - lv = The $(D LogLevel) for the $(D MultiLogger). By default the - $(D LogLevel) for $(D MultiLogger) is $(D LogLevel.all). + lv = The `LogLevel` for the `MultiLogger`. By default the + `LogLevel` for `MultiLogger` is `LogLevel.all`. Example: ------------- @@ -40,32 +43,32 @@ class MultiLogger : Logger super(lv); } - /** This member holds all $(D Logger)s stored in the $(D MultiLogger). + /** This member holds all `Logger`s stored in the `MultiLogger`. - When inheriting from $(D MultiLogger) this member can be used to gain - access to the stored $(D Logger). + When inheriting from `MultiLogger` this member can be used to gain + access to the stored `Logger`. */ protected MultiLoggerEntry[] logger; - /** This method inserts a new Logger into the $(D MultiLogger). + /** This method inserts a new Logger into the `MultiLogger`. Params: - name = The name of the $(D Logger) to insert. - newLogger = The $(D Logger) to insert. + name = The name of the `Logger` to insert. + newLogger = The `Logger` to insert. */ void insertLogger(string name, Logger newLogger) @safe { this.logger ~= MultiLoggerEntry(name, newLogger); } - /** This method removes a Logger from the $(D MultiLogger). + /** This method removes a Logger from the `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. + toRemove = The name of the `Logger` to remove. If the `Logger` + is not found `null` will be returned. Only the first occurrence of + a `Logger` with the given name will be removed. - Returns: The removed $(D Logger). + Returns: The removed `Logger`. */ Logger removeLogger(in char[] toRemove) @safe { @@ -87,7 +90,7 @@ class MultiLogger : Logger } /* The override to pass the payload to all children of the - $(D MultiLoggerBase). + `MultiLoggerBase`. */ override protected void writeLogMsg(ref LogEntry payload) @safe { diff --git a/libphobos/src/std/experimental/logger/nulllogger.d b/libphobos/src/std/experimental/logger/nulllogger.d index 0c55377..0ff7663 100644 --- a/libphobos/src/std/experimental/logger/nulllogger.d +++ b/libphobos/src/std/experimental/logger/nulllogger.d @@ -1,21 +1,24 @@ -/// +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/experimental/logger/nulllogger.d) +*/ module std.experimental.logger.nulllogger; import std.experimental.logger.core; -/** The $(D NullLogger) will not process any log messages. +/** The `NullLogger` will not process any log messages. -In case of a log message with $(D LogLevel.fatal) nothing will happen. +In case of a log message with `LogLevel.fatal` nothing will happen. */ class NullLogger : Logger { - /** The default constructor for the $(D NullLogger). + /** The default constructor for the `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). + lv = The `LogLevel` for the `NullLogger`. By default the `LogLevel` + for `NullLogger` is `LogLevel.all`. */ this(const LogLevel lv = LogLevel.all) @safe { @@ -32,7 +35,6 @@ class NullLogger : Logger @safe unittest { import std.experimental.logger.core : 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 index b9a075c..79245ec 100644 --- a/libphobos/src/std/experimental/logger/package.d +++ b/libphobos/src/std/experimental/logger/package.d @@ -1,3 +1,4 @@ +// Written in the D programming language. /** Implements logging facilities. @@ -9,7 +10,7 @@ $(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. +`D` provides a standard interface for logging. The easiest way to create a log message is to write: ------------- @@ -19,7 +20,7 @@ void main() { log("Hello World"); } ------------- -This will print a message to the $(D stderr) device. The message will contain +This will print a message to the `stderr` device. The message will contain the filename, the line number, the name of the surrounding function, the time and the message. @@ -43,85 +44,85 @@ 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 +Additionally, this example shows how a new `FileLogger` is created. +Individual `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)) + $(LI `log`) + $(LI `trace`) + $(LI `info`) + $(LI `warning`) + $(LI `critical`) + $(LI `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). +The default `Logger` will by default log to `stderr` and has a default +`LogLevel` of `LogLevel.all`. The default Logger can be accessed by +using the property called `sharedLog`. This property is a reference to the +current default `Logger`. This reference can be used to assign a new +default `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). +Additional `Logger` can be created by creating a new instance of the +required `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). +The `LogLevel` of a log call can be defined in two ways. The first is by +calling `log` and passing the `LogLevel` explicitly as the first argument. +The second way of setting the `LogLevel` of a +log call, is by calling either `trace`, `info`, `warning`, +`critical`, or `fatal`. The log call will then have the respective +`LogLevel`. If no `LogLevel` is defined the log call will use the +current `LogLevel` of the used `Logger`. If data is logged with +`LogLevel` `fatal` by default an `Error` will be thrown. +This behaviour can be modified by using the member `fatalHandler` to +assign a custom delegate to handle log call with `LogLevel` `fatal`. $(H4 Conditional Logging) -Conditional logging can be achieved be passing a $(D bool) as first +Conditional logging can be achieved be passing a `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. +be `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). +In order to combine an explicit `LogLevel` passing with conditional +logging, the `LogLevel` has to be passed as first argument followed by the +`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). +Messages are logged if the `LogLevel` of the log message is greater than or +equal to the `LogLevel` of the used `Logger` and additionally if the +`LogLevel` of the log message is greater than or equal to the global `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 global `LogLevel` is accessible by using `globalLogLevel`. +To assign a `LogLevel` of a `Logger` use the `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 +If `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 +The additional $(B f) appended to the function name enables `printf`-style +logging for all combinations of explicit `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). +global `Logger` `sharedLog`. Actually, a thread local `Logger` of +type `StdForwardLogger` processes the log call and then, by default, forwards +the created `Logger.LogEntry` to the `sharedLog` `Logger`. +The thread local `Logger` is accessible by the `stdThreadLocalLog` +property. This property allows to assign user defined `Logger`. The default +`LogLevel` of the `stdThreadLocalLog` `Logger` is `LogLevel.all` +and it will therefore forward all messages to the `sharedLog` `Logger`. +The `LogLevel` of the `stdThreadLocalLog` can be used to filter log +calls before they reach the `sharedLog` `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) +To customize the `Logger` behavior, create a new `class` that inherits from +the abstract `Logger` `class`, and implements the `writeLogMsg` method. ------------- class MyCustomLogger : Logger @@ -142,40 +143,42 @@ 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. +overriding the `writeLogMsg` method the methods `beginLogMsg`, +`logMsgPart` and `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. +$(H3 Compile Time Disabling of `Logger`) +In order to disable logging at compile time, pass `StdLoggerDisableLogging` as a +version argument to the `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. +Specific `LogLevel` can be disabled at compile time as well. +In order to disable logging with the `trace` `LogLevel` pass +`StdLoggerDisableTrace` as a version. The following table shows which version statement disables which -$(D LogLevel). +`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)) + $(TR $(TD `LogLevel.trace` ) $(TD StdLoggerDisableTrace)) + $(TR $(TD `LogLevel.info` ) $(TD StdLoggerDisableInfo)) + $(TR $(TD `LogLevel.warning` ) $(TD StdLoggerDisableWarning)) + $(TR $(TD `LogLevel.error` ) $(TD StdLoggerDisableError)) + $(TR $(TD `LogLevel.critical` ) $(TD StdLoggerDisableCritical)) + $(TR $(TD `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 +By default four `Logger` implementations are given. The `FileLogger` +logs data to files. It can also be used to log to `stdout` and `stderr` +as these devices are files as well. A `Logger` that logs to `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). +The `MultiLogger` is basically an associative array of `string`s to +`Logger`. It propagates log calls to its stored `Logger`. The +`ArrayLogger` contains an array of `Logger` and also propagates log +calls to its stored `Logger`. The `NullLogger` does not do anything. It +will never log a message and will never throw on a log call with `LogLevel` +`error`. + +Source: $(PHOBOSSRC std/experimental/logger/package.d) */ module std.experimental.logger; diff --git a/libphobos/src/std/experimental/typecons.d b/libphobos/src/std/experimental/typecons.d index 07eed8f..46e21e7 100644 --- a/libphobos/src/std/experimental/typecons.d +++ b/libphobos/src/std/experimental/typecons.d @@ -1,14 +1,14 @@ // Written in the D programming language. /** -This module implements experimental additions/modifications to $(MREF std, _typecons). +This module implements experimental additions/modifications to $(MREF std, typecons). -Use this module to test out new functionality for $(REF wrap, 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 +implementation in $(MREF std, typecons) only allows for classes to use the wrap functionality. -Source: $(PHOBOSSRC std/experimental/_typecons.d) +Source: $(PHOBOSSRC std/experimental/typecons.d) Copyright: Copyright the respective authors, 2008- License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). @@ -23,8 +23,7 @@ module std.experimental.typecons; import std.meta; // : AliasSeq, allSatisfy; import std.traits; -import std.typecons : Tuple, tuple, Bind, DerivedFunctionType, mixinAll, staticIota, - GetOverloadedMethods; +import std.typecons : Tuple, tuple, Bind, DerivedFunctionType, GetOverloadedMethods; private { @@ -59,14 +58,14 @@ if (is(T == class) || is(T == interface)) @system unittest { - class C { @disable opCast(T)() {} } + class C { @disable void opCast(T)(); } auto c = new C; static assert(!__traits(compiles, cast(Object) c)); auto o = dynamicCast!Object(c); assert(c is o); - interface I { @disable opCast(T)() {} Object instance(); } - interface J { @disable opCast(T)() {} Object instance(); } + interface I { @disable void opCast(T)(); Object instance(); } + interface J { @disable void opCast(T)(); Object instance(); } class D : I, J { Object instance() { return this; } } I i = new D(); static assert(!__traits(compiles, cast(J) i)); @@ -112,13 +111,15 @@ if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) else { enum foundFunc = findCovariantFunction!(TargetMembers[i], Source, SourceMembers); - version (unittest) {} + + version (StdUnittest) {} else debug { static if (foundFunc == -1) pragma(msg, "Could not locate matching function for: ", TargetMembers[i].stringof); } + enum hasRequiredMethods = foundFunc != -1 && hasRequiredMethods!(i + 1); @@ -290,7 +291,7 @@ if (Targets.length >= 1 && allSatisfy!(isInterface, Targets)) } import std.conv : to; - import std.functional : forward; + import core.lifetime : forward; template generateFun(size_t i) { enum name = TargetMembers[i].name; @@ -299,7 +300,7 @@ if (Targets.length >= 1 && allSatisfy!(isInterface, Targets)) { string r; bool first = true; - foreach (i; staticIota!(0, num)) + foreach (i; 0 .. num) { import std.conv : to; r ~= (first ? "" : ", ") ~ " a" ~ (i+1).to!string; @@ -324,8 +325,8 @@ if (Targets.length >= 1 && allSatisfy!(isInterface, Targets)) } public: - mixin mixinAll!( - staticMap!(generateFun, staticIota!(0, TargetMembers.length))); + static foreach (i; 0 .. TargetMembers.length) + mixin(generateFun!i); } } } @@ -662,9 +663,10 @@ template unwrap(Target) assert(d.draw(10) == 10); } } + +// https://issues.dlang.org/show_bug.cgi?id=10377 @system unittest { - // Bugzilla 10377 import std.algorithm, std.range; interface MyInputRange(T) @@ -679,9 +681,10 @@ template unwrap(Target) auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)(); assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); } + +// https://issues.dlang.org/show_bug.cgi?id=10536 @system unittest { - // Bugzilla 10536 interface Interface { int foo(); @@ -695,9 +698,10 @@ template unwrap(Target) Interface i = new Pluggable().wrap!Interface; assert(i.foo() == 1); } + +// https://issues.dlang.org/show_bug.cgi?id=10538 @system unittest { - // Enhancement 10538 interface Interface { int foo(); @@ -980,9 +984,6 @@ pure nothrow @safe unittest 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); @@ -1051,7 +1052,7 @@ pure nothrow @safe unittest assert((arr ~ 4) == [1, 2, 3, 4]); } -// issue 17270 +// https://issues.dlang.org/show_bug.cgi?id=17270 pure nothrow @nogc @system unittest { int i = 1; diff --git a/libphobos/src/std/file.d b/libphobos/src/std/file.d index 99530cb..741656d 100644 --- a/libphobos/src/std/file.d +++ b/libphobos/src/std/file.d @@ -2,11 +2,12 @@ /** Utilities for manipulating files and scanning directories. Functions -in this module handle files as a unit, e.g., read or write one _file +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;) +$(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD General) $(TD @@ -54,17 +55,20 @@ $(TR $(TD Timestamp) $(TD $(LREF getTimesWin) $(LREF setTimes) $(LREF timeLastModified) + $(LREF timeLastAccessed) + $(LREF timeStatusChanged) )) $(TR $(TD Other) $(TD $(LREF DirEntry) $(LREF FileException) $(LREF PreserveAttributes) $(LREF SpanMode) + $(LREF getAvailableDiskSpace) +)) )) -) -Copyright: Copyright Digital Mars 2007 - 2011. +Copyright: Copyright The D Language Foundation 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, @@ -73,8 +77,8 @@ 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) + $(HTTP jmdavisprog.com, Jonathan M Davis) +Source: $(PHOBOSSRC std/file.d) */ module std.file; @@ -89,9 +93,18 @@ import std.range.primitives; import std.traits; import std.typecons; +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + version (Windows) { - import core.sys.windows.windows, std.windows.syserror; + import core.sys.windows.winbase, core.sys.windows.winnt, std.windows.syserror; } else version (Posix) { @@ -104,7 +117,7 @@ else // Character type used for operating system filesystem APIs version (Windows) { - private alias FSChar = wchar; + private alias FSChar = WCHAR; // WCHAR can be aliased to wchar or wchar_t } else version (Posix) { @@ -116,25 +129,21 @@ else // Purposefully not documented. Use at your own risk @property string deleteme() @safe { - import std.conv : to; + import std.conv : text; 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; - } + enum base = "deleteme.dmd.unittest.pid"; + static string fileName; - return _deleteme; + if (!fileName) + fileName = text(buildPath(tempDir(), base), thisProcessID); + return fileName; } -version (unittest) private struct TestAliasedString +version (StdUnittest) private struct TestAliasedString { - string get() @safe @nogc pure nothrow { return _s; } + string get() @safe @nogc pure nothrow return scope { return _s; } alias get this; @disable this(this); string _s; @@ -164,7 +173,7 @@ class FileException : Exception +/ immutable uint errno; - private this(in char[] name, in char[] msg, string file, size_t line, uint errno) @safe pure + private this(scope const(char)[] name, scope const(char)[] msg, string file, size_t line, uint errno) @safe pure { if (msg.empty) super(name.idup, file, line); @@ -180,34 +189,34 @@ class FileException : Exception Params: name = Name of file for which the error occurred. msg = Message describing the error. - file = The _file where the error occurred. + 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 + this(scope const(char)[] name, scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure { this(name, msg, file, line, 0); } /++ Constructor which takes the error number ($(LUCKY GetLastError) - in Windows, $(D_PARAM errno) in Posix). + 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__). + file = The file where the error occurred. + Defaults to `__FILE__`. line = The _line where the error occurred. - Defaults to $(D __LINE__). + Defaults to `__LINE__`. +/ - version (Windows) this(in char[] name, + version (Windows) this(scope const(char)[] name, uint errno = .GetLastError(), string file = __FILE__, size_t line = __LINE__) @safe { this(name, sysErrorString(errno), file, line, errno); } - else version (Posix) this(in char[] name, + else version (Posix) this(scope const(char)[] name, uint errno = .errno, string file = __FILE__, size_t line = __LINE__) @trusted @@ -217,7 +226,15 @@ class FileException : Exception } } -private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE__, size_t line = __LINE__) +/// +@safe unittest +{ + import std.exception : assertThrown; + + assertThrown!FileException("non.existing.file.".readText); +} + +private T cenforce(T)(T condition, lazy scope const(char)[] name, string file = __FILE__, size_t line = __LINE__) { if (condition) return condition; @@ -233,7 +250,7 @@ private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE version (Windows) @trusted -private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, +private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez, string file = __FILE__, size_t line = __LINE__) { if (condition) @@ -251,7 +268,7 @@ private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, version (Posix) @trusted -private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, +private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez, string file = __FILE__, size_t line = __LINE__) { if (condition) @@ -266,9 +283,9 @@ private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, throw new FileException(name, .errno, file, line); } +// https://issues.dlang.org/show_bug.cgi?id=17102 @safe unittest { - // issue 17102 try { cenforce(false, null, null, @@ -282,8 +299,8 @@ private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, */ /******************************************** -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) +Read entire contents of file `name` and returns it as an untyped +array. If the file size is larger than `upTo`, only `upTo` bytes are _read. Params: @@ -299,7 +316,7 @@ 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)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) return readImpl(name, name.tempCString!FSChar(), upTo); else return readImpl(null, name.tempCString!FSChar(), upTo); @@ -315,7 +332,7 @@ if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R && remove(deleteme); } - write(deleteme, "1234"); // deleteme is the name of a temporary file + std.file.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); @@ -333,12 +350,13 @@ if (isConvertibleToString!R) 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 +version (Posix) private void[] readImpl(scope const(char)[] name, scope 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; + import std.experimental.checkedint : checked; // A few internal configuration parameters { enum size_t @@ -359,48 +377,49 @@ version (Posix) private void[] readImpl(const(char)[] name, const(FSChar)* namez immutable initialAlloc = min(upTo, to!size_t(statbuf.st_size ? min(statbuf.st_size + 1, maxInitialAlloc) : minInitialAlloc)); - void[] result = uninitializedArray!(ubyte[])(initialAlloc); + void[] result = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)[0 .. initialAlloc]; scope(failure) GC.free(result.ptr); - size_t size = 0; + + auto size = checked(size_t(0)); for (;;) { - immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size, - min(result.length, upTo) - size); + immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size.get, + (min(result.length, upTo) - size).get); 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]; + result = GC.realloc(result.ptr, newAlloc.get, GC.BlkAttr.NO_SCAN)[0 .. newAlloc.get]; } return result.length - size >= maxSlackMemoryAllowed - ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size] - : result[0 .. size]; + ? GC.realloc(result.ptr, size.get, GC.BlkAttr.NO_SCAN)[0 .. size.get] + : result[0 .. size.get]; } -version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @safe +version (Windows) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez, + size_t upTo = size_t.max) @trusted { import core.memory : GC; import std.algorithm.comparison : min; - import std.array : uninitializedArray; - static trustedCreateFileW(const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode, + static trustedCreateFileW(scope const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode, SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { return CreateFileW(namez, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } - static trustedCloseHandle(HANDLE hObject) @trusted + static trustedCloseHandle(HANDLE hObject) { return CloseHandle(hObject); } - static trustedGetFileSize(HANDLE hFile, out ulong fileSize) @trusted + static trustedGetFileSize(HANDLE hFile, out ulong fileSize) { DWORD sizeHigh; DWORD sizeLow = GetFileSize(hFile, &sizeHigh); @@ -409,7 +428,7 @@ version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* nam fileSize = makeUlong(sizeLow, sizeHigh); return result; } - static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead) @trusted + static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead) { // Read by chunks of size < 4GB (Windows API limit) ulong totalNumRead = 0; @@ -437,11 +456,11 @@ version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* nam ulong fileSize = void; cenforce(trustedGetFileSize(h, fileSize), name, namez); size_t size = min(upTo, fileSize); - auto buf = uninitializedArray!(ubyte[])(size); + auto buf = () { return GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size]; } (); scope(failure) { - () @trusted { GC.free(buf.ptr); } (); + () { GC.free(buf.ptr); } (); } if (size) @@ -452,7 +471,7 @@ version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* nam 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"); + auto s = std.file.readText("/proc/cpuinfo"); assert(s.length > 0); //writefln("'%s'", s); } @@ -466,49 +485,143 @@ version (linux) @safe unittest 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 +/++ + Reads and validates (using $(REF validate, std, utf)) a text file. S can be + an array of any character type. However, no width or endian conversions are + performed. So, if the width or endianness of the characters in the given + file differ from the width or endianness of the element type of S, then + validation will fail. -Returns: Array of characters read. + Params: + S = the string type of the file + name = string or range of characters representing the file _name -Throws: $(D FileException) on file error, $(D UTFException) on UTF -decoding error. - */ + Returns: Array of characters read. -S readText(S = string, R)(R name) -if (isSomeString!S && - (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) && - !isConvertibleToString!R) + Throws: $(LREF FileException) if there is an error reading the file, + $(REF UTFException, std, utf) on UTF decoding error. ++/ +S readText(S = string, R)(auto ref R name) +if (isSomeString!S && (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) || is(StringTypeOf!R))) { - import std.utf : validate; - static auto trustedCast(void[] buf) @trusted { return cast(S) buf; } - auto result = trustedCast(read(name)); + import std.algorithm.searching : startsWith; + import std.encoding : getBOM, BOM; + import std.exception : enforce; + import std.format : format; + import std.utf : UTFException, validate; + + static if (is(StringTypeOf!R)) + StringTypeOf!R filename = name; + else + auto filename = name; + + static auto trustedCast(T)(void[] buf) @trusted { return cast(T) buf; } + auto data = trustedCast!(ubyte[])(read(filename)); + + immutable bomSeq = getBOM(data); + immutable bom = bomSeq.schema; + + static if (is(immutable ElementEncodingType!S == immutable char)) + { + with(BOM) switch (bom) + { + case utf16be: + case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16"); + case utf32be: + case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32"); + default: break; + } + } + else static if (is(immutable ElementEncodingType!S == immutable wchar)) + { + with(BOM) switch (bom) + { + case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8"); + case utf16be: + { + version (BigEndian) + break; + else + throw new UTFException("BOM is for UTF-16 LE on Big Endian machine"); + } + case utf16le: + { + version (BigEndian) + throw new UTFException("BOM is for UTF-16 BE on Little Endian machine"); + else + break; + } + case utf32be: + case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32"); + default: break; + } + } + else + { + with(BOM) switch (bom) + { + case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8"); + case utf16be: + case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16"); + case utf32be: + { + version (BigEndian) + break; + else + throw new UTFException("BOM is for UTF-32 LE on Big Endian machine"); + } + case utf32le: + { + version (BigEndian) + throw new UTFException("BOM is for UTF-32 BE on Little Endian machine"); + else + break; + } + default: break; + } + } + + if (data.length % ElementEncodingType!S.sizeof != 0) + throw new UTFException(format!"The content of %s is not UTF-%s"(filename, ElementEncodingType!S.sizeof * 8)); + + auto result = trustedCast!S(data); validate(result); return result; } -/// +/// Read file with UTF-8 text. @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"); + assert(content == "abc"); } -/// ditto -S readText(S = string, R)(auto ref R name) -if (isConvertibleToString!R) +// Read file with UTF-8 text but try to read it as UTF-16. +@safe unittest +{ + import std.exception : assertThrown; + import std.utf : UTFException; + + write(deleteme, "abc"); + scope(exit) remove(deleteme); + // Throws because the file is not valid UTF-16. + assertThrown!UTFException(readText!wstring(deleteme)); +} + +// Read file with UTF-16 text. +@safe unittest { - return readText!(S, StringTypeOf!R)(name); + import std.algorithm.searching : skipOver; + + write(deleteme, "\uFEFFabc"w); // With BOM + scope(exit) remove(deleteme); + auto content = readText!wstring(deleteme); + assert(content == "\uFEFFabc"w); + // Strips BOM if present. + content.skipOver('\uFEFF'); + assert(content == "abc"w); } @safe unittest @@ -516,8 +629,103 @@ if (isConvertibleToString!R) static assert(__traits(compiles, readText(TestAliasedString(null)))); } +@safe unittest +{ + import std.array : appender; + import std.bitmanip : append, Endian; + import std.exception : assertThrown; + import std.path : buildPath; + import std.string : representation; + import std.utf : UTFException; + + mkdir(deleteme); + scope(exit) rmdirRecurse(deleteme); + + immutable none8 = buildPath(deleteme, "none8"); + immutable none16 = buildPath(deleteme, "none16"); + immutable utf8 = buildPath(deleteme, "utf8"); + immutable utf16be = buildPath(deleteme, "utf16be"); + immutable utf16le = buildPath(deleteme, "utf16le"); + immutable utf32be = buildPath(deleteme, "utf32be"); + immutable utf32le = buildPath(deleteme, "utf32le"); + immutable utf7 = buildPath(deleteme, "utf7"); + + write(none8, "京都市"); + write(none16, "京都市"w); + write(utf8, (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市"); + { + auto str = "\uFEFF京都市"w; + auto arr = appender!(ubyte[])(); + foreach (c; str) + arr.append(c); + write(utf16be, arr.data); + } + { + auto str = "\uFEFF京都市"w; + auto arr = appender!(ubyte[])(); + foreach (c; str) + arr.append!(ushort, Endian.littleEndian)(c); + write(utf16le, arr.data); + } + { + auto str = "\U0000FEFF京都市"d; + auto arr = appender!(ubyte[])(); + foreach (c; str) + arr.append(c); + write(utf32be, arr.data); + } + { + auto str = "\U0000FEFF京都市"d; + auto arr = appender!(ubyte[])(); + foreach (c; str) + arr.append!(uint, Endian.littleEndian)(c); + write(utf32le, arr.data); + } + write(utf7, (cast(ubyte[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar".representation); + + assertThrown!UTFException(readText(none16)); + assert(readText(utf8) == (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市"); + assertThrown!UTFException(readText(utf16be)); + assertThrown!UTFException(readText(utf16le)); + assertThrown!UTFException(readText(utf32be)); + assertThrown!UTFException(readText(utf32le)); + assert(readText(utf7) == (cast(char[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar"); + + assertThrown!UTFException(readText!wstring(none8)); + assert(readText!wstring(none16) == "京都市"w); + assertThrown!UTFException(readText!wstring(utf8)); + version (BigEndian) + { + assert(readText!wstring(utf16be) == "\uFEFF京都市"w); + assertThrown!UTFException(readText!wstring(utf16le)); + } + else + { + assertThrown!UTFException(readText!wstring(utf16be)); + assert(readText!wstring(utf16le) == "\uFEFF京都市"w); + } + assertThrown!UTFException(readText!wstring(utf32be)); + assertThrown!UTFException(readText!wstring(utf32le)); + assertThrown!UTFException(readText!wstring(utf7)); + + assertThrown!UTFException(readText!dstring(utf8)); + assertThrown!UTFException(readText!dstring(utf16be)); + assertThrown!UTFException(readText!dstring(utf16le)); + version (BigEndian) + { + assert(readText!dstring(utf32be) == "\U0000FEFF京都市"d); + assertThrown!UTFException(readText!dstring(utf32le)); + } + else + { + assertThrown!UTFException(readText!dstring(utf32be)); + assert(readText!dstring(utf32le) == "\U0000FEFF京都市"d); + } + assertThrown!UTFException(readText!dstring(utf7)); +} + /********************************************* -Write $(D buffer) to file $(D name). +Write `buffer` to file `name`. Creates the file if it does not already exist. @@ -525,7 +733,7 @@ Params: name = string or range of characters representing the file _name buffer = data to be written to file -Throws: $(D FileException) on error. +Throws: $(LREF FileException) on error. See_also: $(REF toFile, std,stdio) */ @@ -533,14 +741,14 @@ 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)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) writeImpl(name, name.tempCString!FSChar(), buffer, false); else writeImpl(null, name.tempCString!FSChar(), buffer, false); } /// -@system unittest +@safe unittest { scope(exit) { @@ -550,7 +758,9 @@ if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || is 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); + const bytes = read(deleteme); + const fileInts = () @trusted { return cast(int[]) bytes; }(); + assert(fileInts == a); } /// ditto @@ -566,7 +776,7 @@ if (isConvertibleToString!R) } /********************************************* -Appends $(D buffer) to file $(D name). +Appends `buffer` to file `name`. Creates the file if it does not already exist. @@ -574,20 +784,20 @@ Params: name = string or range of characters representing the file _name buffer = data to be appended to file -Throws: $(D FileException) on error. +Throws: $(LREF 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)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) writeImpl(name, name.tempCString!FSChar(), buffer, true); else writeImpl(null, name.tempCString!FSChar(), buffer, true); } /// -@system unittest +@safe unittest { scope(exit) { @@ -599,7 +809,9 @@ if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || is 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); + const bytes = read(deleteme); + const fileInts = () @trusted { return cast(int[]) bytes; }(); + assert(fileInts == a ~ b); } /// ditto @@ -614,10 +826,10 @@ if (isConvertibleToString!R) static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3]))); } -// Posix implementation helper for write and append +// POSIX implementation helper for write and append -version (Posix) private void writeImpl(const(char)[] name, const(FSChar)* namez, - in void[] buffer, bool append) @trusted +version (Posix) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez, + scope const(void)[] buffer, bool append) @trusted { import std.conv : octal; @@ -647,8 +859,8 @@ version (Posix) private void writeImpl(const(char)[] name, const(FSChar)* 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 +version (Windows) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez, + scope const(void)[] buffer, bool append) @trusted { HANDLE h; if (append) @@ -688,12 +900,21 @@ version (Windows) private void writeImpl(const(char)[] name, const(FSChar)* name } /*************************************************** - * Rename file $(D from) _to $(D to). + * Rename file `from` _to `to`, moving it between directories if required. * If the target file exists, it is overwritten. + * + * It is not possible to rename a file across different mount points + * or drives. On POSIX, the operation is atomic. That means, if `to` + * already exists there will be no time period during the operation + * where `to` is missing. See + * $(HTTP man7.org/linux/man-pages/man2/rename.2.html, manpage for rename) + * for more details. + * * 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. + * + * Throws: $(LREF FileException) on error. */ void rename(RF, RT)(RF from, RT to) if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) || isSomeString!RF) @@ -705,12 +926,12 @@ if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) || auto fromz = from.tempCString!FSChar(); auto toz = to.tempCString!FSChar(); - static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char)) + static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char)) alias f = from; else enum string f = null; - static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char)) + static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char)) alias t = to; else enum string t = null; @@ -736,7 +957,23 @@ if (isConvertibleToString!RF || isConvertibleToString!RT) static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar))); } -private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz) @trusted +/// +@safe unittest +{ + auto t1 = deleteme, t2 = deleteme~"2"; + scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); + + t1.write("1"); + t1.rename(t2); + assert(t2.readText == "1"); + + t1.write("2"); + t1.rename(t2); + assert(t2.readText == "2"); +} + +private void renameImpl(scope const(char)[] f, scope const(char)[] t, + scope const(FSChar)* fromz, scope const(FSChar)* toz) @trusted { version (Windows) { @@ -773,28 +1010,29 @@ private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, 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). +Delete file `name`. Params: name = string or range of characters representing the file _name -Throws: $(D FileException) on error. +Throws: $(LREF 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)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) removeImpl(name, name.tempCString!FSChar()); else removeImpl(null, name.tempCString!FSChar()); @@ -807,12 +1045,24 @@ if (isConvertibleToString!R) remove!(StringTypeOf!R)(name); } +/// +@safe unittest +{ + import std.exception : assertThrown; + + deleteme.write("Hello"); + assert(deleteme.readText == "Hello"); + + deleteme.remove; + assertThrown!FileException(deleteme.readText); +} + @safe unittest { static assert(__traits(compiles, remove(TestAliasedString("foo")))); } -private void removeImpl(const(char)[] name, const(FSChar)* namez) @trusted +private void removeImpl(scope const(char)[] name, scope const(FSChar)* namez) @trusted { version (Windows) { @@ -840,9 +1090,10 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) WIN32_FILE_ATTRIBUTE_DATA fad = void; - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) { - static void getFA(const(char)[] name, const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted + static void getFA(scope const(char)[] name, scope const(FSChar)* namez, + out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted { import std.exception : enforce; enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad), @@ -852,7 +1103,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) } else { - static void getFA(const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted + static void getFA(scope const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted { import core.stdc.wchar_ : wcslen; import std.conv : to; @@ -874,13 +1125,15 @@ version (Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure return li.QuadPart; } -/*************************************************** -Get size of file $(D name) in bytes. +/** +Get size of file `name` in bytes. Params: name = string or range of characters representing the file _name - -Throws: $(D FileException) on error (e.g., file not found). +Returns: + The size of file in bytes. +Throws: + $(LREF FileException) on error (e.g., file not found). */ ulong getSize(R)(R name) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -899,7 +1152,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && { return stat(namez, &buf); } - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -921,11 +1174,25 @@ if (isConvertibleToString!R) static assert(__traits(compiles, getSize(TestAliasedString("foo")))); } +/// @safe unittest { + scope(exit) deleteme.remove; + // 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"); + assert(getSize(deleteme) == 3); +} + +@safe unittest +{ + // create a file of size 1 + write(deleteme, "a"); + scope(exit) deleteme.exists && deleteme.remove; assert(getSize(deleteme) == 1); // create a file of size 3 write(deleteme, "abc"); @@ -933,10 +1200,9 @@ if (isConvertibleToString!R) 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) +private SysTime statTimeToStdTime(char which)(ref const stat_t statbuf) { auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`); long stdTime = unixTimeToStdTime(unixTime); @@ -957,7 +1223,7 @@ private SysTime statTimeToStdTime(char which)(ref stat_t statbuf) } /++ - Get the access and modified times of file or folder $(D name). + Get the access and modified times of file or folder `name`. Params: name = File/Folder _name to get times for. @@ -965,7 +1231,7 @@ private SysTime statTimeToStdTime(char which)(ref stat_t statbuf) modificationTime = Time the file/folder was last modified. Throws: - $(D FileException) on error. + $(LREF FileException) on error. +/ void getTimes(R)(R name, out SysTime accessTime, @@ -993,7 +1259,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && } stat_t statbuf = void; - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -1013,27 +1279,49 @@ if (isConvertibleToString!R) return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime); } +/// +@safe unittest +{ + import std.datetime : abs, SysTime; + + scope(exit) deleteme.remove; + write(deleteme, "a"); + + SysTime accessTime, modificationTime; + + getTimes(deleteme, accessTime, modificationTime); + + import std.datetime : Clock, seconds; + auto currTime = Clock.currTime(); + enum leeway = 5.seconds; + + auto diffAccess = accessTime - currTime; + auto diffModification = modificationTime - currTime; + assert(abs(diffAccess) <= leeway); + assert(abs(diffModification) <= leeway); +} + @safe unittest { SysTime atime, mtime; static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime))); } -@system unittest +@safe unittest { import std.stdio : writefln; auto currTime = Clock.currTime(); write(deleteme, "a"); - scope(exit) { assert(exists(deleteme)); remove(deleteme); } + scope(exit) assert(deleteme.exists), deleteme.remove; - SysTime accessTime1 = void; - SysTime modificationTime1 = void; + SysTime accessTime1; + SysTime modificationTime1; getTimes(deleteme, accessTime1, modificationTime1); - enum leeway = dur!"seconds"(5); + enum leeway = 5.seconds; { auto diffa = accessTime1 - currTime; @@ -1079,10 +1367,10 @@ version (StdDdoc) /++ $(BLUE This function is Windows-Only.) - Get creation/access/modified times of file $(D name). + Get creation/access/modified times of file `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. + This is the same as `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. @@ -1091,7 +1379,7 @@ version (StdDdoc) fileModificationTime = Time the file was last modified. Throws: - $(D FileException) on error. + $(LREF FileException) on error. +/ void getTimesWin(R)(R name, out SysTime fileCreationTime, @@ -1199,9 +1487,23 @@ version (Windows) @system unittest } } +version (Darwin) +private +{ + import core.stdc.config : c_ulong; + enum ATTR_CMN_MODTIME = 0x00000400, ATTR_CMN_ACCTIME = 0x00001000; + alias attrgroup_t = uint; + static struct attrlist + { + ushort bitmapcount, reserved; + attrgroup_t commonattr, volattr, dirattr, fileattr, forkattr; + } + extern(C) int setattrlist(in char* path, scope ref attrlist attrs, + scope void* attrbuf, size_t attrBufSize, c_ulong options) nothrow @nogc @system; +} /++ - Set access/modified times of file or folder $(D name). + Set access/modified times of file or folder `name`. Params: name = File/Folder _name to get times for. @@ -1209,7 +1511,7 @@ version (Windows) @system unittest modificationTime = Time the file/folder was last modified. Throws: - $(D FileException) on error. + $(LREF FileException) on error. +/ void setTimes(R)(R name, SysTime accessTime, @@ -1217,30 +1519,48 @@ void setTimes(R)(R name, if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && !isConvertibleToString!R) { - version (Windows) - { - import std.datetime.systime : SysTimeToFILETIME; + auto namez = name.tempCString!FSChar(); + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) + alias names = name; + else + string names = null; + setTimesImpl(names, namez, accessTime, modificationTime); +} - 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); +/// +@safe unittest +{ + import std.datetime : DateTime, hnsecs, SysTime; - } - 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); - } + scope(exit) deleteme.remove; + write(deleteme, "a"); + + SysTime accessTime = SysTime(DateTime(2010, 10, 4, 0, 0, 30)); + SysTime modificationTime = SysTime(DateTime(2018, 10, 4, 0, 0, 30)); + setTimes(deleteme, accessTime, modificationTime); + + SysTime accessTimeResolved, modificationTimeResolved; + getTimes(deleteme, accessTimeResolved, modificationTimeResolved); + + assert(accessTime == accessTimeResolved); + assert(modificationTime == modificationTimeResolved); +} + +/// ditto +void setTimes(R)(auto ref R name, + SysTime accessTime, + SysTime modificationTime) +if (isConvertibleToString!R) +{ + setTimes!(StringTypeOf!R)(name, accessTime, modificationTime); +} +private void setTimesImpl(scope const(char)[] names, scope const(FSChar)* namez, + SysTime accessTime, SysTime modificationTime) @trusted +{ + version (Windows) + { + import std.datetime.systime : SysTimeToFILETIME; const ta = SysTimeToFILETIME(accessTime); const tm = SysTimeToFILETIME(modificationTime); alias defaults = @@ -1252,75 +1572,55 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS, HANDLE.init); - auto h = trustedCreateFileW(namez, defaults); + auto h = CreateFileW(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(CloseHandle(h), names, namez); - cenforce(trustedSetFileTime(h, null, ta, tm), names, namez); + cenforce(SetFileTime(h, null, &ta, &tm), names, namez); } - else version (Posix) + else { - 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); + cenforce(utimensat(AT_FDCWD, namez, t, 0) == 0, names, namez); } else { - static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted + version (Darwin) { - return utimes(namez, times); + // Set modification & access times with setattrlist to avoid precision loss. + attrlist attrs = { bitmapcount: 5, reserved: 0, + commonattr: ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME, + volattr: 0, dirattr: 0, fileattr: 0, forkattr: 0 }; + timespec[2] attrbuf = [modificationTime.toTimeSpec(), accessTime.toTimeSpec()]; + if (0 == setattrlist(namez, attrs, &attrbuf, attrbuf.sizeof, 0)) + return; + if (.errno != ENOTSUP) + cenforce(false, names, namez); + // Not all volumes support setattrlist. In such cases + // fall through to the utimes implementation. } 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); + cenforce(utimes(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 +@safe unittest { import std.stdio : File; string newdir = deleteme ~ r".dir"; @@ -1356,8 +1656,12 @@ if (isConvertibleToString!R) /++ Returns the time that the given file was last modified. + Params: + name = the name of the file to check + Returns: + A $(REF SysTime,std,datetime,systime). Throws: - $(D FileException) if the given file does not exist. + $(LREF FileException) if the given file does not exist. +/ SysTime timeLastModified(R)(R name) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -1381,7 +1685,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && } stat_t statbuf = void; - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -1398,6 +1702,19 @@ if (isConvertibleToString!R) return timeLastModified!(StringTypeOf!R)(name); } +/// +@safe unittest +{ + import std.datetime : abs, DateTime, hnsecs, SysTime; + scope(exit) deleteme.remove; + + import std.datetime : Clock, seconds; + auto currTime = Clock.currTime(); + enum leeway = 5.seconds; + deleteme.write("bb"); + assert(abs(deleteme.timeLastModified - currTime) <= leeway); +} + @safe unittest { static assert(__traits(compiles, timeLastModified(TestAliasedString("foo")))); @@ -1405,25 +1722,27 @@ if (isConvertibleToString!R) /++ Returns the time that the given file was last modified. If the - file does not exist, returns $(D returnIfMissing). + file does not exist, returns `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 + target) must be rebuilt from file `source` (i.e., `target` is + older than `source` or does not exist), use the comparison + below. The code throws a $(LREF FileException) if `source` does not + exist (as it should). On the other hand, the `SysTime.min` default + makes a non-existing `target` seem infinitely old so the test correctly prompts building it. Params: - name = The _name of the file to get the modification time for. + name = The name of the file to get the modification time for. returnIfMissing = The time to return if the given file does not exist. + Returns: + A $(REF SysTime,std,datetime,systime). Example: -------------------- -if (timeLastModified(source) >= timeLastModified(target, SysTime.min)) +if (source.timeLastModified >= target.timeLastModified(SysTime.min)) { // must (re)build } @@ -1463,9 +1782,77 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) } } +/// @safe unittest { - //std.process.system("echo a > deleteme") == 0 || assert(false); + import std.datetime : SysTime; + + assert("file.does.not.exist".timeLastModified(SysTime.min) == SysTime.min); + + auto source = deleteme ~ "source"; + auto target = deleteme ~ "target"; + scope(exit) source.remove, target.remove; + + source.write("."); + assert(target.timeLastModified(SysTime.min) < source.timeLastModified); + target.write("."); + assert(target.timeLastModified(SysTime.min) >= source.timeLastModified); +} + +version (StdDdoc) +{ + /++ + $(BLUE This function is POSIX-Only.) + + Returns the time that the given file was last modified. + Params: + statbuf = stat_t retrieved from file. + +/ + SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow {assert(false);} + /++ + $(BLUE This function is POSIX-Only.) + + Returns the time that the given file was last accessed. + Params: + statbuf = stat_t retrieved from file. + +/ + SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow {assert(false);} + /++ + $(BLUE This function is POSIX-Only.) + + Returns the time that the given file was last changed. + Params: + statbuf = stat_t retrieved from file. + +/ + SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow {assert(false);} +} +else version (Posix) +{ + SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow + { + return statTimeToStdTime!'m'(statbuf); + } + SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow + { + return statTimeToStdTime!'a'(statbuf); + } + SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow + { + return statTimeToStdTime!'c'(statbuf); + } + + @safe unittest + { + stat_t statbuf; + // check that both lvalues and rvalues work + timeLastAccessed(statbuf); + cast(void) timeLastAccessed(stat_t.init); + } +} + +@safe unittest +{ + //std.process.executeShell("echo a > deleteme"); if (exists(deleteme)) remove(deleteme); @@ -1502,7 +1889,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) version (FreeBSD) {} else version (DragonFlyBSD) {} else version (OSX) {} else -@system unittest +@safe unittest { import core.thread; @@ -1517,7 +1904,7 @@ version (OSX) {} else remove(deleteme); assert(time != lastTime); lastTime = time; - Thread.sleep(20.msecs); + () @trusted { Thread.sleep(20.msecs); }(); } } @@ -1543,6 +1930,19 @@ if (isConvertibleToString!R) return exists!(StringTypeOf!R)(name); } +/// +@safe unittest +{ + auto f = deleteme ~ "does.not.exist"; + assert(!f.exists); + + f.write("hello"); + assert(f.exists); + + f.remove; + assert(!f.exists); +} + private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc { version (Windows) @@ -1580,16 +1980,18 @@ private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc 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)); + assert(".".exists); + assert(!"this file does not exist".exists); + deleteme.write("a\n"); + scope(exit) deleteme.remove; + assert(deleteme.exists); } -@safe unittest // Bugzilla 16573 +// https://issues.dlang.org/show_bug.cgi?id=16573 +@safe unittest { enum S : string { foo = "foo" } assert(__traits(compiles, S.foo.exists)); @@ -1598,22 +2000,23 @@ private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc /++ Returns the attributes of the given file. - Note that the file attributes on Windows and Posix systems are + 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)) + GetFileAttributes), whereas on POSIX systems, they're the + `st_mode` value which is part of the $(D stat struct) gotten by + calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, `stat`) function. - On Posix systems, if the given file is a symbolic link, then + 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. + name = The file to get the attributes of. + Returns: + The attributes of the file as a `uint`. + Throws: $(LREF FileException) on error. +/ uint getAttributes(R)(R name) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -1628,7 +2031,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && } immutable result = trustedGetFileAttributesW(namez); - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -1645,7 +2048,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && } stat_t statbuf = void; - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -1662,6 +2065,40 @@ if (isConvertibleToString!R) return getAttributes!(StringTypeOf!R)(name); } +/// getAttributes with a file +@safe unittest +{ + import std.exception : assertThrown; + + auto f = deleteme ~ "file"; + scope(exit) f.remove; + + assert(!f.exists); + assertThrown!FileException(f.getAttributes); + + f.write("."); + auto attributes = f.getAttributes; + assert(!attributes.attrIsDir); + assert(attributes.attrIsFile); +} + +/// getAttributes with a directory +@safe unittest +{ + import std.exception : assertThrown; + + auto dir = deleteme ~ "dir"; + scope(exit) dir.rmdir; + + assert(!dir.exists); + assertThrown!FileException(dir.getAttributes); + + dir.mkdir; + auto attributes = dir.getAttributes; + assert(attributes.attrIsDir); + assert(!attributes.attrIsFile); +} + @safe unittest { static assert(__traits(compiles, getAttributes(TestAliasedString(null)))); @@ -1684,7 +2121,7 @@ if (isConvertibleToString!R) the attributes Throws: - $(D FileException) on error. + $(LREF FileException) on error. +/ uint getLinkAttributes(R)(R name) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -1702,7 +2139,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && return lstat(namez, &buf); } stat_t lstatbuf = void; - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -1718,6 +2155,64 @@ if (isConvertibleToString!R) return getLinkAttributes!(StringTypeOf!R)(name); } +/// +@safe unittest +{ + import std.exception : assertThrown; + + auto source = deleteme ~ "source"; + auto target = deleteme ~ "target"; + + assert(!source.exists); + assertThrown!FileException(source.getLinkAttributes); + + // symlinking isn't available on Windows + version (Posix) + { + scope(exit) source.remove, target.remove; + + target.write("target"); + target.symlink(source); + assert(source.readText == "target"); + assert(source.isSymlink); + assert(source.getLinkAttributes.attrIsSymlink); + } +} + +/// if the file is no symlink, getLinkAttributes behaves like getAttributes +@safe unittest +{ + import std.exception : assertThrown; + + auto f = deleteme ~ "file"; + scope(exit) f.remove; + + assert(!f.exists); + assertThrown!FileException(f.getLinkAttributes); + + f.write("."); + auto attributes = f.getLinkAttributes; + assert(!attributes.attrIsDir); + assert(attributes.attrIsFile); +} + +/// if the file is no symlink, getLinkAttributes behaves like getAttributes +@safe unittest +{ + import std.exception : assertThrown; + + auto dir = deleteme ~ "dir"; + scope(exit) dir.rmdir; + + assert(!dir.exists); + assertThrown!FileException(dir.getLinkAttributes); + + dir.mkdir; + auto attributes = dir.getLinkAttributes; + assert(attributes.attrIsDir); + assert(!attributes.attrIsFile); +} + @safe unittest { static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null)))); @@ -1726,12 +2221,16 @@ if (isConvertibleToString!R) /++ Set the _attributes of the given file. + For example, a programmatic equivalent of Unix's `chmod +x name` + to make a file executable is + `name.setAttributes(name.getAttributes | octal!700)`. + Params: name = the file _name attributes = the _attributes to set the file to Throws: - $(D FileException) if the given file does not exist. + $(LREF FileException) if the given file does not exist. +/ void setAttributes(R)(R name, uint attributes) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -1744,7 +2243,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && { return SetFileAttributesW(namez, dwFileAttributes); } - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -1758,7 +2257,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && return chmod(namez, mode); } assert(attributes <= mode_t.max); - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias names = name; else string names = null; @@ -1778,6 +2277,58 @@ if (isConvertibleToString!R) static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0))); } +/// setAttributes with a file +@safe unittest +{ + import std.exception : assertThrown; + import std.conv : octal; + + auto f = deleteme ~ "file"; + version (Posix) + { + scope(exit) f.remove; + + assert(!f.exists); + assertThrown!FileException(f.setAttributes(octal!777)); + + f.write("."); + auto attributes = f.getAttributes; + assert(!attributes.attrIsDir); + assert(attributes.attrIsFile); + + f.setAttributes(octal!777); + attributes = f.getAttributes; + + assert((attributes & 1023) == octal!777); + } +} + +/// setAttributes with a directory +@safe unittest +{ + import std.exception : assertThrown; + import std.conv : octal; + + auto dir = deleteme ~ "dir"; + version (Posix) + { + scope(exit) dir.rmdir; + + assert(!dir.exists); + assertThrown!FileException(dir.setAttributes(octal!777)); + + dir.mkdir; + auto attributes = dir.getAttributes; + assert(attributes.attrIsDir); + assert(!attributes.attrIsFile); + + dir.setAttributes(octal!777); + attributes = dir.getAttributes; + + assert((attributes & 1023) == octal!777); + } +} + /++ Returns whether the given file is a directory. @@ -1788,13 +2339,7 @@ if (isConvertibleToString!R) 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); --------------------- + $(LREF FileException) if the given file does not exist. +/ @property bool isDir(R)(R name) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -1817,6 +2362,26 @@ if (isConvertibleToString!R) return name.isDir!(StringTypeOf!R); } +/// + +@safe unittest +{ + import std.exception : assertThrown; + + auto dir = deleteme ~ "dir"; + auto f = deleteme ~ "f"; + scope(exit) dir.rmdir, f.remove; + + assert(!dir.exists); + assertThrown!FileException(dir.isDir); + + dir.mkdir; + assert(dir.isDir); + + f.write("."); + assert(!f.isDir); +} + @safe unittest { static assert(__traits(compiles, TestAliasedString(null).isDir)); @@ -1842,7 +2407,7 @@ if (isConvertibleToString!R) } } -@system unittest +@safe unittest { version (Windows) enum dir = "C:\\Program Files\\"; @@ -1865,13 +2430,7 @@ if (isConvertibleToString!R) 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) @@ -1884,6 +2443,27 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc } } +/// +@safe unittest +{ + import std.exception : assertThrown; + + auto dir = deleteme ~ "dir"; + auto f = deleteme ~ "f"; + scope(exit) dir.rmdir, f.remove; + + assert(!dir.exists); + assertThrown!FileException(dir.getAttributes.attrIsDir); + + dir.mkdir; + assert(dir.isDir); + assert(dir.getAttributes.attrIsDir); + + f.write("."); + assert(!f.isDir); + assert(!f.getAttributes.attrIsDir); +} + @safe unittest { version (Windows) @@ -1921,15 +2501,15 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc 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. + either `isFile` or `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 + On POSIX systems, if `isFile` is `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 `isFile` and `isDir` to be `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 + `getAttributes` to get the attributes to figure out what type of special + it is, or you can use `DirEntry` to get at its `statBuf`, which is the + result from `stat`. In either case, see the man page for `stat` for more information. Params: @@ -1939,14 +2519,8 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc 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); --------------------- - +/ + $(LREF FileException) if the given file does not exist. ++/ @property bool isFile(R)(R name) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && !isConvertibleToString!R) @@ -1964,7 +2538,27 @@ if (isConvertibleToString!R) return isFile!(StringTypeOf!R)(name); } -@system unittest // bugzilla 15658 +/// +@safe unittest +{ + import std.exception : assertThrown; + + auto dir = deleteme ~ "dir"; + auto f = deleteme ~ "f"; + scope(exit) dir.rmdir, f.remove; + + dir.mkdir; + assert(!dir.isFile); + + assert(!f.exists); + assertThrown!FileException(f.isFile); + + f.write("."); + assert(f.isFile); +} + +// https://issues.dlang.org/show_bug.cgi?id=15658 +@safe unittest { DirEntry e = DirEntry("."); static assert(is(typeof(isFile(e)))); @@ -2000,15 +2594,15 @@ if (isConvertibleToString!R) 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 + `attrIsFile` or `attrIsDir` will return `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) + On POSIX systems, if `attrIsFile` is `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 `attrIsFile` and `attrIsDir` to be `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). + it is (see the man page for `stat` for more information). Params: attributes = The file _attributes. @@ -2034,6 +2628,27 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc } } +/// +@safe unittest +{ + import std.exception : assertThrown; + + auto dir = deleteme ~ "dir"; + auto f = deleteme ~ "f"; + scope(exit) dir.rmdir, f.remove; + + dir.mkdir; + assert(!dir.isFile); + assert(!dir.getAttributes.attrIsFile); + + assert(!f.exists); + assertThrown!FileException(f.getAttributes.attrIsFile); + + f.write("."); + assert(f.isFile); + assert(f.getAttributes.attrIsFile); +} + @safe unittest { version (Windows) @@ -2070,7 +2685,7 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc /++ Returns whether the given file is a symbolic link. - On Windows, returns $(D true) when the file is either a symbolic link or a + On Windows, returns `true` when the file is either a symbolic link or a junction point. Params: @@ -2080,7 +2695,7 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc true if name is a symbolic link Throws: - $(D FileException) if the given file does not exist. + $(LREF FileException) if the given file does not exist. +/ @property bool isSymlink(R)(R name) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -2104,6 +2719,30 @@ if (isConvertibleToString!R) static assert(__traits(compiles, TestAliasedString(null).isSymlink)); } +/// +@safe unittest +{ + import std.exception : assertThrown; + + auto source = deleteme ~ "source"; + auto target = deleteme ~ "target"; + + assert(!source.exists); + assertThrown!FileException(source.isSymlink); + + // symlinking isn't available on Windows + version (Posix) + { + scope(exit) source.remove, target.remove; + + target.write("target"); + target.symlink(source); + assert(source.readText == "target"); + assert(source.isSymlink); + assert(source.getLinkAttributes.attrIsSymlink); + } +} + @system unittest { version (Windows) @@ -2181,7 +2820,7 @@ if (isConvertibleToString!R) /++ 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 + On Windows, return `true` when the file is either a symbolic link or a junction point. Params: @@ -2206,10 +2845,38 @@ bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc return (attributes & S_IFMT) == S_IFLNK; } +/// +@safe unittest +{ + import std.exception : assertThrown; + + auto source = deleteme ~ "source"; + auto target = deleteme ~ "target"; + + assert(!source.exists); + assertThrown!FileException(source.getLinkAttributes.attrIsSymlink); + + // symlinking isn't available on Windows + version (Posix) + { + scope(exit) source.remove, target.remove; + + target.write("target"); + target.symlink(source); + assert(source.readText == "target"); + assert(source.isSymlink); + assert(source.getLinkAttributes.attrIsSymlink); + } +} + +/** +Change directory to `pathname`. Equivalent to `cd` on +Windows and POSIX. -/**************************************************** - * Change directory to $(D pathname). - * Throws: $(D FileException) on error. +Params: + pathname = the directory to step into + +Throws: $(LREF FileException) on error. */ void chdir(R)(R pathname) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -2232,7 +2899,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && return core.sys.posix.unistd.chdir(pathz) == 0; } } - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias pathStr = pathname; else string pathStr = null; @@ -2246,16 +2913,41 @@ if (isConvertibleToString!R) return chdir!(StringTypeOf!R)(pathname); } +/// +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.sorting : sort; + import std.array : array; + import std.path : buildPath; + + auto cwd = getcwd; + auto dir = deleteme ~ "dir"; + dir.mkdir; + scope(exit) cwd.chdir, dir.rmdirRecurse; + + dir.buildPath("a").write("."); + dir.chdir; // step into dir + "b".write("."); + assert(dirEntries(".", SpanMode.shallow).array.sort.equal( + [".".buildPath("a"), ".".buildPath("b")] + )); +} + @safe unittest { static assert(__traits(compiles, chdir(TestAliasedString(null)))); } -/**************************************************** -Make directory $(D pathname). +/** +Make a new directory `pathname`. + +Params: + pathname = the path of the directory to make -Throws: $(D FileException) on Posix or $(D WindowsException) on Windows - if an error occured. +Throws: + $(LREF FileException) on POSIX or $(LREF WindowsException) on Windows + if an error occured. */ void mkdir(R)(R pathname) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -2270,7 +2962,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && { return CreateDirectoryW(pathz, null); } - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias pathStr = pathname; else string pathStr = null; @@ -2284,7 +2976,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && { return core.sys.posix.sys.stat.mkdir(pathz, mode); } - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias pathStr = pathname; else string pathStr = null; @@ -2305,10 +2997,29 @@ if (isConvertibleToString!R) static assert(__traits(compiles, mkdir(TestAliasedString(null)))); } +/// +@safe unittest +{ + import std.file : mkdir; + + auto dir = deleteme ~ "dir"; + scope(exit) dir.rmdir; + + dir.mkdir; + assert(dir.exists); +} + +/// +@safe unittest +{ + import std.exception : assertThrown; + assertThrown("a/b/c/d/e".mkdir); +} + // 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) +private bool ensureDirExists()(scope const(char)[] pathname) { import std.exception : enforce; const pathz = pathname.tempCString!FSChar(); @@ -2331,16 +3042,18 @@ private bool ensureDirExists()(in char[] pathname) 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. - */ +/** +Make directory and all parent directories as needed. + +Does nothing if the directory specified by +`pathname` already exists. + +Params: + pathname = the full path of the directory to create -void mkdirRecurse(in char[] pathname) @safe +Throws: $(LREF FileException) on error. + */ +void mkdirRecurse(scope const(char)[] pathname) @safe { import std.path : dirName, baseName; @@ -2355,6 +3068,36 @@ void mkdirRecurse(in char[] pathname) @safe } } +/// +@safe unittest +{ + import std.path : buildPath; + + auto dir = deleteme ~ "dir"; + scope(exit) dir.rmdirRecurse; + + dir.mkdir; + assert(dir.exists); + dir.mkdirRecurse; // does nothing + + // creates all parent directories as needed + auto nested = dir.buildPath("a", "b", "c"); + nested.mkdirRecurse; + assert(nested.exists); +} + +/// +@safe unittest +{ + import std.exception : assertThrown; + + scope(exit) deleteme.remove; + deleteme.write("a"); + + // cannot make directory as it's already a file + assertThrown!FileException(deleteme.mkdirRecurse); +} + @safe unittest { import std.exception : assertThrown; @@ -2383,7 +3126,7 @@ void mkdirRecurse(in char[] pathname) @safe assertThrown!FileException(mkdirRecurse(`1:\foobar`)); } - // bug3570 + // https://issues.dlang.org/show_bug.cgi?id=3570 { immutable basepath = deleteme ~ "_dir"; version (Windows) @@ -2403,12 +3146,12 @@ void mkdirRecurse(in char[] pathname) @safe } /**************************************************** -Remove directory $(D pathname). +Remove directory `pathname`. Params: pathname = Range or string specifying the directory name -Throws: $(D FileException) on error. +Throws: $(LREF FileException) on error. */ void rmdir(R)(R pathname) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && @@ -2431,7 +3174,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && return core.sys.posix.unistd.rmdir(pathz) == 0; } } - static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias pathStr = pathname; else string pathStr = null; @@ -2450,8 +3193,19 @@ if (isConvertibleToString!R) static assert(__traits(compiles, rmdir(TestAliasedString(null)))); } +/// +@safe unittest +{ + auto dir = deleteme ~ "dir"; + + dir.mkdir; + assert(dir.exists); + dir.rmdir; + assert(!dir.exists); +} + /++ - $(BLUE This function is Posix-Only.) + $(BLUE This function is POSIX-Only.) Creates a symbolic _link (_symlink). @@ -2463,7 +3217,7 @@ if (isConvertibleToString!R) current working directory. Throws: - $(D FileException) on error (which includes if the _symlink already + $(LREF FileException) on error (which includes if the _symlink already exists). +/ version (StdDdoc) void symlink(RO, RL)(RO original, RL link) @@ -2545,7 +3299,7 @@ version (Posix) @safe unittest /++ - $(BLUE This function is Posix-Only.) + $(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. @@ -2553,7 +3307,7 @@ version (Posix) @safe unittest working directory. Throws: - $(D FileException) on error. + $(LREF FileException) on error. +/ version (StdDdoc) string readLink(R)(R link) if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || @@ -2642,7 +3396,7 @@ version (Posix) @system unittest // input range of dchars import std.utf : byChar; immutable string link = deleteme ~ "/l"; symlink("f", link); - InputRange!dchar linkr = inputRangeObject(link); + InputRange!(ElementType!string) linkr = inputRangeObject(link); alias R = typeof(linkr); static assert(isInputRange!R); static assert(!isForwardRange!R); @@ -2652,11 +3406,12 @@ version (Posix) @system unittest // input range of dchars /**************************************************** * Get the current working directory. - * Throws: $(D FileException) on error. + * Throws: $(LREF FileException) on error. */ -version (Windows) string getcwd() +version (Windows) string getcwd() @trusted { import std.conv : to; + import std.experimental.checkedint : checked; /* GetCurrentDirectory's return value: 1. function succeeds: the number of characters that are written to the buffer, not including the terminating null character. @@ -2664,7 +3419,11 @@ version (Windows) string getcwd() 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 + version (StdUnittest) + enum BUF_SIZE = 10; // trigger reallocation code + else + enum BUF_SIZE = 4096; // enough for most common case + wchar[BUF_SIZE] buffW = void; immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr), "getcwd"); // we can do it because toUTFX always produces a fresh string @@ -2674,14 +3433,15 @@ version (Windows) string getcwd() } else //staticBuff isn't enough { - auto ptr = cast(wchar*) malloc(wchar.sizeof * n); + auto cn = checked(n); + auto ptr = cast(wchar*) malloc((cn * wchar.sizeof).get); scope(exit) free(ptr); - immutable n2 = GetCurrentDirectoryW(n, ptr); - cenforce(n2 && n2 < n, "getcwd"); + immutable n2 = GetCurrentDirectoryW(cn.get, ptr); + cenforce(n2 && n2 < cn, "getcwd"); return ptr[0 .. n2].to!string; } } -else version (Solaris) string getcwd() +else version (Solaris) string getcwd() @trusted { /* BUF_SIZE >= PATH_MAX */ enum BUF_SIZE = 4096; @@ -2691,7 +3451,7 @@ else version (Solaris) string getcwd() scope(exit) core.stdc.stdlib.free(p); return p[0 .. core.stdc.string.strlen(p)].idup; } -else version (Posix) string getcwd() +else version (Posix) string getcwd() @trusted { auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0), "cannot get cwd"); @@ -2699,31 +3459,27 @@ else version (Posix) string getcwd() return p[0 .. core.stdc.string.strlen(p)].idup; } -@system unittest +/// +@safe 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. * + * Returns: + * The path of the executable as a `string`. + * * Throws: * $(REF1 Exception, object) */ -@trusted string thisExePath () +@trusted string thisExePath() { - version (OSX) + version (Darwin) { + import core.sys.darwin.mach.dyld : _NSGetExecutablePath; import core.sys.posix.stdlib : realpath; import std.conv : to; import std.exception : errnoEnforce; @@ -2867,6 +3623,7 @@ else version (NetBSD) static assert(0, "thisExePath is not supported on this platform"); } +/// @safe unittest { import std.path : isAbsolute; @@ -2880,18 +3637,19 @@ else version (NetBSD) version (StdDdoc) { /++ - Info on a file, similar to what you'd get from stat on a Posix system. + Info on a file, similar to what you'd get from stat on a POSIX system. +/ struct DirEntry { + @safe: /++ - Constructs a $(D DirEntry) for the given file (or directory). + Constructs a `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. + $(LREF FileException) if the file does not exist. +/ this(string path); @@ -2905,7 +3663,7 @@ version (StdDdoc) } /++ - Returns the path to the file represented by this $(D DirEntry). + Returns the path to the file represented by this `DirEntry`. Example: -------------------- @@ -2916,11 +3674,11 @@ auto de2 = DirEntry("/usr/share/include"); assert(de2.name == "/usr/share/include"); -------------------- +/ - @property string name() const; + @property string name() const return scope; /++ - Returns whether the file represented by this $(D DirEntry) is a + Returns whether the file represented by this `DirEntry` is a directory. Example: @@ -2932,20 +3690,20 @@ auto de2 = DirEntry("/usr/share/include"); assert(de2.isDir); -------------------- +/ - @property bool isDir(); + @property bool isDir() scope; /++ - Returns whether the file represented by this $(D DirEntry) is a file. + Returns whether the file represented by this `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). + either `isFile` or `isDir` will return `true`. - On Posix systems, if $(D isFile) is $(D true), that indicates that + On POSIX systems, if `isFile` is `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 + POSIX systems, it's possible for both `isFile` and `isDir` to + be `false` for a particular file (in which case, it's a special + file). You can use `attributes` or `statBuf` to get more information about a special file (see the stat man page for more details). @@ -2958,91 +3716,100 @@ auto de2 = DirEntry("/usr/share/include"); assert(!de2.isFile); -------------------- +/ - @property bool isFile(); + @property bool isFile() scope; /++ - Returns whether the file represented by this $(D DirEntry) is a + Returns whether the file represented by this `DirEntry` is a symbolic link. - On Windows, return $(D true) when the file is either a symbolic + On Windows, return `true` when the file is either a symbolic link or a junction point. +/ - @property bool isSymlink(); + @property bool isSymlink() scope; /++ - Returns the size of the the file represented by this $(D DirEntry) + Returns the size of the the file represented by this `DirEntry` in bytes. +/ - @property ulong size(); + @property ulong size() scope; /++ $(BLUE This function is Windows-Only.) Returns the creation time of the file represented by this - $(D DirEntry). + `DirEntry`. +/ - @property SysTime timeCreated() const; + @property SysTime timeCreated() const scope; /++ - Returns the time that the file represented by this $(D DirEntry) was + Returns the time that the file represented by this `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). + `timeLastAccessed` will return the same value as + `timeLastModified`. +/ - @property SysTime timeLastAccessed(); + @property SysTime timeLastAccessed() scope; /++ - Returns the time that the file represented by this $(D DirEntry) was + Returns the time that the file represented by this `DirEntry` was last modified. +/ - @property SysTime timeLastModified(); + @property SysTime timeLastModified() scope; /++ - Returns the _attributes of the file represented by this $(D DirEntry). + $(BLUE This function is POSIX-Only.) - Note that the file _attributes on Windows and Posix systems are + Returns the time that the file represented by this `DirEntry` was + last changed (not only in contents, but also in permissions or ownership). + +/ + @property SysTime timeStatusChanged() const scope; + + /++ + Returns the _attributes of the file represented by this `DirEntry`. + + Note that the file _attributes on Windows and POSIX systems are completely different. On, Windows, they're what is returned by - $(D GetFileAttributes) + `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). + Whereas, an POSIX systems, they're the `st_mode` value which is + part of the `stat` struct gotten by calling `stat`. - On Posix systems, if the file represented by this $(D DirEntry) is a + On POSIX systems, if the file represented by this `DirEntry` is a symbolic link, then _attributes are the _attributes of the file pointed to by the symbolic link. +/ - @property uint attributes(); + @property uint attributes() scope; /++ - 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 POSIX systems, if the file represented by this `DirEntry` is a + symbolic link, then `linkAttributes` are the attributes of the + symbolic link itself. Otherwise, `linkAttributes` is identical to + `attributes`. - On Windows, $(D linkAttributes) is identical to $(D attributes). It + On Windows, `linkAttributes` is identical to `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(); + @property uint linkAttributes() scope; version (Windows) alias stat_t = void*; /++ - $(BLUE This function is Posix-Only.) + $(BLUE This function is POSIX-Only.) - The $(D stat) struct gotten from calling $(D stat). + The `stat` struct gotten from calling `stat`. +/ - @property stat_t statBuf(); + @property stat_t statBuf() scope; } } else version (Windows) { struct DirEntry { + @safe: public: alias name this; @@ -3065,14 +3832,16 @@ else version (Windows) } } - private this(string path, in WIN32_FIND_DATAW *fd) + private this(string path, WIN32_FIND_DATAW *fd) @trusted { 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); + fd.cFileName[$ - 1] = 0; + + size_t clength = wcslen(&fd.cFileName[0]); _name = buildPath(path, fd.cFileName[0 .. clength].to!string); _size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow; _timeCreated = FILETIMEToSysTime(&fd.ftCreationTime); @@ -3081,17 +3850,17 @@ else version (Windows) _attributes = fd.dwFileAttributes; } - @property string name() const pure nothrow + @property string name() const pure nothrow return scope { return _name; } - @property bool isDir() const pure nothrow + @property bool isDir() const pure nothrow scope { return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - @property bool isFile() const pure nothrow + @property bool isFile() const pure nothrow scope { //Are there no options in Windows other than directory and file? //If there are, then this probably isn't the best way to determine @@ -3099,37 +3868,37 @@ else version (Windows) return !isDir; } - @property bool isSymlink() const pure nothrow + @property bool isSymlink() const pure nothrow scope { return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } - @property ulong size() const pure nothrow + @property ulong size() const pure nothrow scope { return _size; } - @property SysTime timeCreated() const pure nothrow + @property SysTime timeCreated() const pure nothrow scope { return cast(SysTime)_timeCreated; } - @property SysTime timeLastAccessed() const pure nothrow + @property SysTime timeLastAccessed() const pure nothrow scope { return cast(SysTime)_timeLastAccessed; } - @property SysTime timeLastModified() const pure nothrow + @property SysTime timeLastModified() const pure nothrow scope { return cast(SysTime)_timeLastModified; } - @property uint attributes() const pure nothrow + @property uint attributes() const pure nothrow scope { return _attributes; } - @property uint linkAttributes() const pure nothrow + @property uint linkAttributes() const pure nothrow scope { return _attributes; } @@ -3149,6 +3918,7 @@ else version (Posix) { struct DirEntry { + @safe: public: alias name this; @@ -3164,7 +3934,7 @@ else version (Posix) _dTypeSet = false; } - private this(string path, core.sys.posix.dirent.dirent* fd) + private this(string path, core.sys.posix.dirent.dirent* fd) @safe { import std.path : buildPath; @@ -3203,74 +3973,74 @@ else version (Posix) } } - @property string name() const pure nothrow + @property string name() const pure nothrow return scope { return _name; } - @property bool isDir() + @property bool isDir() scope { _ensureStatOrLStatDone(); return (_statBuf.st_mode & S_IFMT) == S_IFDIR; } - @property bool isFile() + @property bool isFile() scope { _ensureStatOrLStatDone(); return (_statBuf.st_mode & S_IFMT) == S_IFREG; } - @property bool isSymlink() + @property bool isSymlink() scope { _ensureLStatDone(); return (_lstatMode & S_IFMT) == S_IFLNK; } - @property ulong size() + @property ulong size() scope { _ensureStatDone(); return _statBuf.st_size; } - @property SysTime timeStatusChanged() + @property SysTime timeStatusChanged() scope { _ensureStatDone(); return statTimeToStdTime!'c'(_statBuf); } - @property SysTime timeLastAccessed() + @property SysTime timeLastAccessed() scope { _ensureStatDone(); return statTimeToStdTime!'a'(_statBuf); } - @property SysTime timeLastModified() + @property SysTime timeLastModified() scope { _ensureStatDone(); return statTimeToStdTime!'m'(_statBuf); } - @property uint attributes() + @property uint attributes() scope { _ensureStatDone(); return _statBuf.st_mode; } - @property uint linkAttributes() + @property uint linkAttributes() scope { _ensureLStatDone(); return _lstatMode; } - @property stat_t statBuf() + @property stat_t statBuf() scope { _ensureStatDone(); @@ -3282,18 +4052,14 @@ else version (Posix) This is to support lazy evaluation, because doing stat's is expensive and not always needed. +/ - void _ensureStatDone() @safe + void _ensureStatDone() @trusted scope { 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, + enforce(stat(_name.tempCString(), &_statBuf) == 0, "Failed to stat file `" ~ _name ~ "'"); _didStat = true; @@ -3306,12 +4072,12 @@ else version (Posix) Try both stat and lstat for isFile and isDir to detect broken symlinks. +/ - void _ensureStatOrLStatDone() + void _ensureStatOrLStatDone() @trusted scope { if (_didStat) return; - if ( stat(_name.tempCString(), &_statBuf) != 0 ) + if (stat(_name.tempCString(), &_statBuf) != 0) { _ensureLStatDone(); @@ -3328,7 +4094,7 @@ else version (Posix) This is to support lazy evaluation, because doing stat's is expensive and not always needed. +/ - void _ensureLStatDone() + void _ensureLStatDone() @trusted scope { import std.exception : enforce; @@ -3336,7 +4102,6 @@ else version (Posix) return; stat_t statbuf = void; - enforce(lstat(_name.tempCString(), &statbuf) == 0, "Failed to stat file `" ~ _name ~ "'"); @@ -3348,9 +4113,9 @@ else version (Posix) 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. + 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. @@ -3413,7 +4178,7 @@ else version (Posix) core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr); { - //Issue 8298 + // https://issues.dlang.org/show_bug.cgi?id=8298 DirEntry de = DirEntry(symfile); assert(!de.isFile); @@ -3444,7 +4209,7 @@ alias PreserveAttributes = Flag!"preserveAttributes"; version (StdDdoc) { - /// Defaults to $(D Yes.preserveAttributes) on Windows, and the opposite on all other platforms. + /// Defaults to `Yes.preserveAttributes` on Windows, and the opposite on all other platforms. PreserveAttributes preserveAttributesDefault; } else version (Windows) @@ -3457,9 +4222,9 @@ else } /*************************************************** -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. +Copy file `from` _to file `to`. File timestamps are preserved. +File attributes are preserved, if `preserve` equals `Yes.preserveAttributes`. +On Windows only `Yes.preserveAttributes` (the default on Windows) is supported. If the target file exists, it is overwritten. Params: @@ -3467,7 +4232,7 @@ Params: to = string or range of characters representing the target file name preserve = whether to _preserve the file attributes -Throws: $(D FileException) on error. +Throws: $(LREF FileException) on error. */ void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault) if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF && @@ -3477,12 +4242,12 @@ if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && auto fromz = from.tempCString!FSChar(); auto toz = to.tempCString!FSChar(); - static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char)) + static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char)) alias f = from; else enum string f = null; - static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char)) + static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char)) alias t = to; else enum string t = null; @@ -3499,13 +4264,36 @@ if (isConvertibleToString!RF || isConvertibleToString!RT) copy!Types(from, to, preserve); } -@safe unittest // issue 15319 +/// +@safe unittest +{ + auto source = deleteme ~ "source"; + auto target = deleteme ~ "target"; + auto targetNonExistent = deleteme ~ "target2"; + + scope(exit) source.remove, target.remove, targetNonExistent.remove; + + source.write("source"); + target.write("target"); + + assert(target.readText == "target"); + + source.copy(target); + assert(target.readText == "source"); + + source.copy(targetNonExistent); + assert(targetNonExistent.readText == "source"); +} + +// https://issues.dlang.org/show_bug.cgi?id=15319 +@safe unittest { 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 +private void copyImpl(scope const(char)[] f, scope const(char)[] t, + scope const(FSChar)* fromz, scope const(FSChar)* toz, + PreserveAttributes preserve) @trusted { version (Windows) { @@ -3515,11 +4303,19 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co { import core.stdc.wchar_ : wcslen; import std.conv : to; + import std.format : format; + /++ + Reference resources: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew + Because OS copyfilew handles both source and destination paths, + the GetLastError does not accurately locate whether the error is for the source or destination. + +/ + if (!f) + f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]); if (!t) t = to!(typeof(t))(toz[0 .. wcslen(toz)]); - throw new FileException(t); + throw new FileException(format!"Copy from %s to %s"(f, t)); } } else version (Posix) @@ -3582,17 +4378,14 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co 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); + setTimesImpl(t, toz, statbufr.statTimeToStdTime!'a', statbufr.statTimeToStdTime!'m'); } } +// https://issues.dlang.org/show_bug.cgi?id=14817 @safe unittest { - import std.algorithm, std.file; // issue 14817 + import std.algorithm, std.file; auto t1 = deleteme, t2 = deleteme~"2"; scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); write(t1, "11"); @@ -3605,9 +4398,18 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co import std.utf : byChar; copy(t1.byChar, t2.byChar); assert(readText(t2.byChar) == "2"); + +// https://issues.dlang.org/show_bug.cgi?id=20370 + version (Windows) + assert(t1.timeLastModified == t2.timeLastModified); + else static if (is(typeof(&utimensat)) || is(typeof(&setattrlist))) + assert(t1.timeLastModified == t2.timeLastModified); + else + assert(abs(t1.timeLastModified - t2.timeLastModified) < dur!"usecs"(1)); } -@safe version (Posix) @safe unittest //issue 11434 +// https://issues.dlang.org/show_bug.cgi?id=11434 +@safe version (Posix) @safe unittest { import std.conv : octal; auto t1 = deleteme, t2 = deleteme~"2"; @@ -3619,7 +4421,8 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co assert(getAttributes(t2) == octal!100767); } -@safe unittest // issue 15865 +// https://issues.dlang.org/show_bug.cgi?id=15865 +@safe unittest { import std.exception : assertThrown; auto t = deleteme; @@ -3629,30 +4432,40 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co assert(readText(t) == "a"); } +// https://issues.dlang.org/show_bug.cgi?id=19834 +version (Windows) @safe unittest +{ + import std.exception : collectException; + import std.algorithm.searching : startsWith; + import std.format : format; + + auto f = deleteme; + auto t = f ~ "2"; + auto ex = collectException(copy(f, t)); + assert(ex.msg.startsWith(format!"Copy from %s to %s"(f, t))); +} + /++ Remove directory and all of its content and subdirectories, recursively. + Params: + pathname = the path of the directory to completely remove + de = The $(LREF DirEntry) to remove + Throws: - $(D FileException) if there is an error (including if the given + $(LREF FileException) if there is an error (including if the given file is not a directory). +/ -void rmdirRecurse(in char[] pathname) +void rmdirRecurse(scope const(char)[] pathname) @safe { //No references to pathname will be kept after rmdirRecurse, //so the cast is safe - rmdirRecurse(DirEntry(cast(string) pathname)); + rmdirRecurse(DirEntry((() @trusted => 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) +/// ditto +void rmdirRecurse(ref DirEntry de) @safe { if (!de.isDir) throw new FileException(de.name, "Not a directory"); @@ -3666,11 +4479,16 @@ void rmdirRecurse(ref DirEntry de) } else { - // all children, recursively depth-first - foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false)) - { - attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name); - } + // dirEntries is @system because it uses a DirIterator with a + // RefCounted variable, but here, no references to the payload is + // escaped to the outside, so this should be @trusted + () @trusted { + // 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); @@ -3682,11 +4500,26 @@ void rmdirRecurse(ref DirEntry de) //"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) +void rmdirRecurse(DirEntry de) @safe { rmdirRecurse(de); } +/// +@system unittest +{ + import std.path : buildPath; + + auto dir = deleteme.buildPath("a", "b", "c"); + + dir.mkdirRecurse; + assert(dir.exists); + + deleteme.rmdirRecurse; + assert(!dir.exists); + assert(!deleteme.exists); +} + version (Windows) @system unittest { import std.exception : enforce; @@ -3699,7 +4532,7 @@ version (Windows) @system unittest 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))); @@ -3713,9 +4546,8 @@ version (Posix) @system unittest 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"); + const linkTarget = deleteme ~ "/link"; + symlink(deleteme ~ "/a/b/c", linkTarget); rmdirRecurse(deleteme); enforce(!exists(deleteme)); } @@ -3761,7 +4593,7 @@ enum SpanMode $(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 + Note that `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, @@ -3770,9 +4602,39 @@ enum SpanMode breadth, } +/// +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.algorithm.sorting : sort; + import std.array : array; + import std.path : buildPath, relativePath; + + auto root = deleteme ~ "root"; + scope(exit) root.rmdirRecurse; + root.mkdir; + + root.buildPath("animals").mkdir; + root.buildPath("animals", "cat").mkdir; + + alias removeRoot = (return scope e) => e.relativePath(root); + + assert(root.dirEntries(SpanMode.depth).map!removeRoot.equal( + [buildPath("animals", "cat"), "animals"])); + + assert(root.dirEntries(SpanMode.breadth).map!removeRoot.equal( + ["animals", buildPath("animals", "cat")])); + + root.buildPath("plants").mkdir; + + assert(root.dirEntries(SpanMode.shallow).array.sort.map!removeRoot.equal( + ["animals", "plants"])); +} + private struct DirIteratorImpl { - import std.array : Appender, appender; + @safe: SpanMode _mode; // Whether we should follow symlinked directories while iterating. // It also indicates whether we should avoid functions which call @@ -3780,82 +4642,95 @@ private struct DirIteratorImpl // 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 + DirHandle[] _stack; + DirEntry[] _stashed; //used in depth first mode + //stack helpers - void pushExtra(DirEntry de){ _stashed.put(de); } + void pushExtra(DirEntry de) + { + _stashed ~= de; + } + //ditto - bool hasExtra(){ return !_stashed.data.empty; } + bool hasExtra() + { + return _stashed.length != 0; + } + //ditto DirEntry popExtra() { DirEntry de; - de = _stashed.data[$-1]; - _stashed.shrinkTo(_stashed.data.length - 1); + de = _stashed[$-1]; + _stashed.popBack(); return de; - } + version (Windows) { + WIN32_FIND_DATAW _findinfo; struct DirHandle { string dirpath; HANDLE h; } - bool stepIn(string directory) + bool stepIn(string directory) @safe { import std.path : chainPath; + auto searchPattern = chainPath(directory, "*.*"); + + static auto trustedFindFirstFileW(typeof(searchPattern) pattern, WIN32_FIND_DATAW* findinfo) @trusted + { + return FindFirstFileW(pattern.tempCString!FSChar(), findinfo); + } - auto search_pattern = chainPath(directory, "*.*"); - WIN32_FIND_DATAW findinfo; - HANDLE h = FindFirstFileW(search_pattern.tempCString!FSChar(), &findinfo); + HANDLE h = trustedFindFirstFileW(searchPattern, &_findinfo); cenforce(h != INVALID_HANDLE_VALUE, directory); - _stack.put(DirHandle(directory, h)); - return toNext(false, &findinfo); + _stack ~= DirHandle(directory, h); + return toNext(false, &_findinfo); } bool next() { - if (_stack.data.empty) + if (_stack.length == 0) return false; - WIN32_FIND_DATAW findinfo; - return toNext(true, &findinfo); + return toNext(true, &_findinfo); } - bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo) + bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo) @trusted { import core.stdc.wchar_ : wcscmp; if (fetch) { - if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE) + if (FindNextFileW(_stack[$-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) + while (wcscmp(&findinfo.cFileName[0], ".") == 0 || + wcscmp(&findinfo.cFileName[0], "..") == 0) + if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE) { popDirStack(); return false; } - _cur = DirEntry(_stack.data[$-1].dirpath, findinfo); + _cur = DirEntry(_stack[$-1].dirpath, findinfo); return true; } - void popDirStack() + void popDirStack() @trusted { - assert(!_stack.data.empty); - FindClose(_stack.data[$-1].h); - _stack.shrinkTo(_stack.data.length-1); + assert(_stack.length != 0); + FindClose(_stack[$-1].h); + _stack.popBack(); } - void releaseDirStack() + void releaseDirStack() @trusted { - foreach ( d; _stack.data) + foreach (d; _stack) FindClose(d.h); } @@ -3874,40 +4749,47 @@ private struct DirIteratorImpl bool stepIn(string directory) { - auto h = directory.length ? opendir(directory.tempCString()) : opendir("."); + static auto trustedOpendir(string dir) @trusted + { + return opendir(dir.tempCString()); + } + + auto h = directory.length ? trustedOpendir(directory) : trustedOpendir("."); cenforce(h, directory); - _stack.put(DirHandle(directory, h)); + _stack ~= (DirHandle(directory, h)); return next(); } - bool next() + bool next() @trusted { - if (_stack.data.empty) + if (_stack.length == 0) return false; - for (dirent* fdata; (fdata = readdir(_stack.data[$-1].h)) != null; ) + + for (dirent* fdata; (fdata = readdir(_stack[$-1].h)) != null; ) { // Skip "." and ".." - if (core.stdc.string.strcmp(fdata.d_name.ptr, ".") && - core.stdc.string.strcmp(fdata.d_name.ptr, "..") ) + if (core.stdc.string.strcmp(&fdata.d_name[0], ".") && + core.stdc.string.strcmp(&fdata.d_name[0], "..")) { - _cur = DirEntry(_stack.data[$-1].dirpath, fdata); + _cur = DirEntry(_stack[$-1].dirpath, fdata); return true; } } + popDirStack(); return false; } - void popDirStack() + void popDirStack() @trusted { - assert(!_stack.data.empty); - closedir(_stack.data[$-1].h); - _stack.shrinkTo(_stack.data.length-1); + assert(_stack.length != 0); + closedir(_stack[$-1].h); + _stack.popBack(); } - void releaseDirStack() + void releaseDirStack() @trusted { - foreach ( d; _stack.data) + foreach (d; _stack) closedir(d.h); } @@ -3922,11 +4804,8 @@ private struct DirIteratorImpl { _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)) + static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) alias pathnameStr = pathname; else { @@ -3948,8 +4827,17 @@ private struct DirIteratorImpl } } } - @property bool empty(){ return _stashed.data.empty && _stack.data.empty; } - @property DirEntry front(){ return _cur; } + + @property bool empty() + { + return _stashed.length == 0 && _stack.length == 0; + } + + @property DirEntry front() + { + return _cur; + } + void popFront() { switch (_mode) @@ -3993,25 +4881,29 @@ private struct DirIteratorImpl struct DirIterator { +@safe: private: RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl; - this(string pathname, SpanMode mode, bool followSymlink) + this(string pathname, SpanMode mode, bool followSymlink) @trusted { impl = typeof(impl)(pathname, mode, followSymlink); } public: - @property bool empty(){ return impl.empty; } - @property DirEntry front(){ return impl.front; } - void popFront(){ impl.popFront(); } - + @property bool empty() { 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, + Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of `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) + type `string` if only the name is needed, or `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. + contains the absolute or relative _path (depending on _pathname). + + Note: The order of returned directory entries is as it is provided by the + operating system / filesystem, and may not follow any particular sorting. Params: path = The directory to iterate over. @@ -4024,7 +4916,7 @@ public: std,_path). mode = Whether the directory's sub-directories should be - iterated in depth-first port-order ($(LREF depth)), + iterated in depth-first post-order ($(LREF depth)), depth-first pre-order ($(LREF breadth)), or not at all ($(LREF shallow)). @@ -4032,8 +4924,12 @@ public: should be treated as directories and their contents iterated over. + Returns: + An $(REF_ALTTEXT input range, isInputRange,std,range,primitives) of + $(LREF DirEntry). + Throws: - $(D FileException) if the directory does not exist. + $(LREF FileException) if the directory does not exist. Example: -------------------- @@ -4065,7 +4961,7 @@ foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread { string cmd = "dmd -c " ~ d.name; writeln(cmd); - std.process.system(cmd); + std.process.executeShell(cmd); } // Iterate over all D source files in current directory and all its @@ -4080,7 +4976,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true) return DirIterator(path, mode, followSymlink); } -/// Duplicate functionality of D1's $(D std.file.listdir()): +/// Duplicate functionality of D1's `std.file.listdir()`: @safe unittest { string[] listdir(string pathname) @@ -4092,7 +4988,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true) return std.file.dirEntries(pathname, SpanMode.shallow) .filter!(a => a.isFile) - .map!(a => std.path.baseName(a.name)) + .map!((return a) => std.path.baseName(a.name)) .array; } @@ -4122,7 +5018,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true) // 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 + string testdir = tempDir.buildPath("deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID)); mkdirRecurse(buildPath(testdir, "somedir")); scope(exit) rmdirRecurse(testdir); write(buildPath(testdir, "somefile"), null); @@ -4135,7 +5031,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true) 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!((return a) => absolutePath(a.name))(dirEntries(relpath, mode)), map!(a => a.name)(dirEntries(absolutePath(relpath), mode)))); return len; } @@ -4156,7 +5052,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true) assert(e.isFile || e.isDir, e.name); } - //issue 7264 + // https://issues.dlang.org/show_bug.cgi?id=7264 foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth)) { @@ -4165,14 +5061,14 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true) { static assert(is(typeof(entry) == DirEntry)); } - //issue 7138 + // https://issues.dlang.org/show_bug.cgi?id=7138 auto a = array(dirEntries(testdir, SpanMode.shallow)); - // issue 11392 + // https://issues.dlang.org/show_bug.cgi?id=11392 auto dFiles = dirEntries(testdir, SpanMode.shallow); foreach (d; dFiles){} - // issue 15146 + // https://issues.dlang.org/show_bug.cgi?id=15146 dirEntries("", SpanMode.shallow).walkLength(); } @@ -4263,6 +5159,40 @@ auto dirEntries(string path, string pattern, SpanMode mode, } } +// Make sure that dirEntries does not butcher Unicode file names +// https://issues.dlang.org/show_bug.cgi?id=17962 +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.algorithm.sorting : sort; + import std.array : array; + import std.path : buildPath; + import std.uni : normalize; + + // The Unicode normalization is required to make the tests pass on Mac OS X. + auto dir = deleteme ~ normalize("𐐷"); + scope(exit) if (dir.exists) rmdirRecurse(dir); + mkdir(dir); + auto files = ["Hello World", + "Ma Chérie.jpeg", + "さいごの果実.txt"].map!(a => buildPath(dir, normalize(a)))().array(); + sort(files); + foreach (file; files) + write(file, "nothing"); + + auto result = dirEntries(dir, SpanMode.shallow).map!((return a) => a.name.normalize()).array(); + sort(result); + + assert(equal(files, result)); +} + +// https://issues.dlang.org/show_bug.cgi?id=21250 +@system unittest +{ + import std.exception : assertThrown; + assertThrown!Exception(dirEntries("237f5babd6de21f40915826699582e36", "*.bin", SpanMode.depth)); +} /** * Reads a file line by line and parses the line into a single value or a @@ -4287,13 +5217,14 @@ auto dirEntries(string path, string pattern, SpanMode mode, * with extra characters are allowed. */ Select!(Types.length == 1, Types[0][], Tuple!(Types)[]) -slurp(Types...)(string filename, in char[] format) +slurp(Types...)(string filename, scope const(char)[] format) { import std.array : appender; import std.conv : text; import std.exception : enforce; - import std.format : formattedRead; + import std.format.read : formattedRead; import std.stdio : File; + import std.string : stripRight; auto app = appender!(typeof(return))(); ElementType!(typeof(return)) toAdd; @@ -4302,7 +5233,7 @@ slurp(Types...)(string filename, in char[] format) foreach (line; f.byLine()) { formattedRead(line, format, &toAdd); - enforce(line.empty, + enforce(line.stripRight("\r").empty, text("Trailing characters at the end of line: `", line, "'")); app.put(toAdd); @@ -4331,35 +5262,49 @@ slurp(Types...)(string filename, in char[] format) assert(a[1] == tuple(345, 1.125)); } +@system unittest +{ + import std.typecons : tuple; -/** -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)). + scope(exit) + { + assert(exists(deleteme)); + remove(deleteme); + } + write(deleteme, "10\r\n20"); + assert(slurp!(int)(deleteme, "%d") == [10, 20]); +} -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. +/** +Returns the path to a directory for temporary files. 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 +below 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)). +The POSIX `tempDir` algorithm is inspired by Python's +$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, `tempfile.tempdir`). + +Returns: + 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, `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 `TMPDIR` environment variable.) + $(LI The directory given by the `TEMP` environment variable.) + $(LI The directory given by the `TMP` environment variable.) + $(LI `/tmp`) + $(LI `/var/tmp`) + $(LI `/usr/tmp`) + ) + + On all platforms, `tempDir` returns `"."` on failure, representing + the current working directory. */ string tempDir() @trusted { @@ -4399,3 +5344,92 @@ string tempDir() @trusted } return cache; } + +/// +@safe unittest +{ + import std.ascii : letters; + import std.conv : to; + import std.path : buildPath; + import std.random : randomSample; + import std.utf : byCodeUnit; + + // random id with 20 letters + auto id = letters.byCodeUnit.randomSample(20).to!string; + auto myFile = tempDir.buildPath(id ~ "my_tmp_file"); + scope(exit) myFile.remove; + + myFile.write("hello"); + assert(myFile.readText == "hello"); +} + +/** +Returns the available disk space based on a given path. +On Windows, `path` must be a directory; on POSIX systems, it can be a file or directory. + +Params: + path = on Windows, it must be a directory; on POSIX it can be a file or directory +Returns: + Available space in bytes + +Throws: + $(LREF FileException) in case of failure +*/ +ulong getAvailableDiskSpace(scope const(char)[] path) @safe +{ + version (Windows) + { + import core.sys.windows.winbase : GetDiskFreeSpaceExW; + import core.sys.windows.winnt : ULARGE_INTEGER; + import std.internal.cstring : tempCStringW; + + ULARGE_INTEGER freeBytesAvailable; + auto err = () @trusted { + return GetDiskFreeSpaceExW(path.tempCStringW(), &freeBytesAvailable, null, null); + } (); + cenforce(err != 0, "Cannot get available disk space"); + + return freeBytesAvailable.QuadPart; + } + else version (Posix) + { + import std.internal.cstring : tempCString; + + version (FreeBSD) + { + import core.sys.freebsd.sys.mount : statfs, statfs_t; + + statfs_t stats; + auto err = () @trusted { + return statfs(path.tempCString(), &stats); + } (); + cenforce(err == 0, "Cannot get available disk space"); + + return stats.f_bavail * stats.f_bsize; + } + else + { + import core.sys.posix.sys.statvfs : statvfs, statvfs_t; + + statvfs_t stats; + auto err = () @trusted { + return statvfs(path.tempCString(), &stats); + } (); + cenforce(err == 0, "Cannot get available disk space"); + + return stats.f_bavail * stats.f_frsize; + } + } + else static assert(0, "Unsupported platform"); +} + +/// +@safe unittest +{ + import std.exception : assertThrown; + + auto space = getAvailableDiskSpace("."); + assert(space > 0); + + assertThrown!FileException(getAvailableDiskSpace("ThisFileDoesNotExist123123")); +} diff --git a/libphobos/src/std/format.d b/libphobos/src/std/format.d deleted file mode 100644 index 17e5906..0000000 --- a/libphobos/src/std/format.d +++ /dev/null @@ -1,6028 +0,0 @@ -// 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", cast(int) cont.length, cont.ptr); - - 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, cast(int) sep.length, sep.ptr); - 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/format/internal/floats.d b/libphobos/src/std/format/internal/floats.d new file mode 100644 index 0000000..81b21dc --- /dev/null +++ b/libphobos/src/std/format/internal/floats.d @@ -0,0 +1,2930 @@ +// Written in the D programming language. + +/* + Helper functions for formatting floating point numbers. + + Copyright: Copyright The D Language Foundation 2019 - + + License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + + Authors: Bernhard Seckinger + + Source: $(PHOBOSSRC std/format/internal/floats.d) + */ + +module std.format.internal.floats; + +import std.format.spec : FormatSpec; + +// wrapper for unittests +private auto printFloat(T, Char)(T val, FormatSpec!Char f) +if (is(T == float) || is(T == double) + || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) +{ + import std.array : appender; + auto w = appender!string(); + + printFloat(w, val, f); + return w.data; +} + +package(std.format) void printFloat(Writer, T, Char)(auto ref Writer w, T val, FormatSpec!Char f) +if (is(T == float) || is(T == double) + || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) +{ + import std.math.operations : extractBitpattern, FloatingPointBitpattern; + + auto bp = extractBitpattern(val); + + ulong mnt = bp.mantissa; + int exp = bp.exponent; + string sgn = bp.negative ? "-" : ""; + + if (sgn == "" && f.flPlus) sgn = "+"; + if (sgn == "" && f.flSpace) sgn = " "; + + assert(f.spec == 'a' || f.spec == 'A' + || f.spec == 'e' || f.spec == 'E' + || f.spec == 'f' || f.spec == 'F' + || f.spec == 'g' || f.spec == 'G', "unsupported format specifier"); + bool is_upper = f.spec == 'A' || f.spec == 'E' || f.spec=='F' || f.spec=='G'; + + // special treatment for nan and inf + if (exp == T.max_exp) + { + import std.format.internal.write : writeAligned; + + f.flZero = false; + writeAligned(w, sgn, "", (mnt == 0) ? ( is_upper ? "INF" : "inf" ) : ( is_upper ? "NAN" : "nan" ), f); + return; + } + + final switch (f.spec) + { + case 'a': case 'A': + printFloatA(w, val, f, sgn, exp, mnt, is_upper); + break; + case 'e': case 'E': + printFloatE!false(w, val, f, sgn, exp, mnt, is_upper); + break; + case 'f': case 'F': + printFloatF!false(w, val, f, sgn, exp, mnt, is_upper); + break; + case 'g': case 'G': + printFloatG(w, val, f, sgn, exp, mnt, is_upper); + break; + } +} + +private void printFloatA(Writer, T, Char)(auto ref Writer w, T val, + FormatSpec!Char f, string sgn, int exp, ulong mnt, bool is_upper) +if (is(T == float) || is(T == double) + || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) +{ + import std.algorithm.comparison : max; + import std.format.internal.write : writeAligned, PrecisionType; + + char[3] prefix; + if (sgn != "") prefix[0] = sgn[0]; + prefix[1] = '0'; + prefix[2] = is_upper ? 'X' : 'x'; + + // print exponent + if (mnt == 0) + { + if (f.precision == f.UNSPECIFIED) + f.precision = 0; + writeAligned(w, prefix[1 - sgn.length .. $], "0", ".", is_upper ? "P+0" : "p+0", + f, PrecisionType.fractionalDigits); + return; + } + + // save integer part + char first = '0' + ((mnt >> (T.mant_dig - 1)) & 1); + mnt &= (1L << (T.mant_dig - 1)) - 1; + + static if (is(T == float) || (is(T == real) && T.mant_dig == 64)) + { + mnt <<= 1; // make mnt dividable by 4 + enum mant_len = T.mant_dig; + } + else + enum mant_len = T.mant_dig - 1; + static assert(mant_len % 4 == 0, "mantissa with wrong length"); + + // print full mantissa + char[(mant_len - 1) / 4 + 3] hex_mant; + size_t hex_mant_pos = 2; + size_t pos = mant_len; + + auto gap = 39 - 32 * is_upper; + while (pos >= 4 && (mnt & (((1L << (pos - 1)) - 1) << 1) + 1) != 0) + { + pos -= 4; + size_t tmp = (mnt >> pos) & 15; + // For speed reasons the better readable + // ... = tmp < 10 ? ('0' + tmp) : ((is_upper ? 'A' : 'a') + tmp - 10)) + // has been replaced with an expression without branches, doing the same + hex_mant[hex_mant_pos++] = cast(char) (tmp + gap * ((tmp + 6) >> 4) + '0'); + } + hex_mant[0] = first; + hex_mant[1] = '.'; + + if (f.precision == f.UNSPECIFIED) + f.precision = cast(int) hex_mant_pos - 2; + + auto exp_sgn = exp >= 0 ? '+' : '-'; + if (exp < 0) exp = -exp; + + static if (is(T == real) && real.mant_dig == 64) + enum max_exp_digits = 8; + else static if (is(T == float)) + enum max_exp_digits = 5; + else + enum max_exp_digits = 6; + + char[max_exp_digits] exp_str; + size_t exp_pos = max_exp_digits; + + do + { + exp_str[--exp_pos] = '0' + exp % 10; + exp /= 10; + } while (exp > 0); + + exp_str[--exp_pos] = exp_sgn; + exp_str[--exp_pos] = is_upper ? 'P' : 'p'; + + if (f.precision < hex_mant_pos - 2) + { + import std.format.internal.write : RoundingClass, round; + + RoundingClass rc; + + if (hex_mant[f.precision + 2] == '0') + rc = RoundingClass.ZERO; + else if (hex_mant[f.precision + 2] < '8') + rc = RoundingClass.LOWER; + else if (hex_mant[f.precision + 2] > '8') + rc = RoundingClass.UPPER; + else + rc = RoundingClass.FIVE; + + if (rc == RoundingClass.ZERO || rc == RoundingClass.FIVE) + { + foreach (i;f.precision + 3 .. hex_mant_pos) + { + if (hex_mant[i] > '0') + { + rc = rc == RoundingClass.ZERO ? RoundingClass.LOWER : RoundingClass.UPPER; + break; + } + } + } + + hex_mant_pos = f.precision + 2; + + round(hex_mant, 0, hex_mant_pos, rc, sgn == "-", is_upper ? 'F' : 'f'); + } + + writeAligned(w, prefix[1 - sgn.length .. $], hex_mant[0 .. 1], hex_mant[1 .. hex_mant_pos], + exp_str[exp_pos .. $], f, PrecisionType.fractionalDigits); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + assert(printFloat(float.nan, f) == "nan"); + assert(printFloat(-float.nan, f) == "-nan"); + assert(printFloat(float.infinity, f) == "inf"); + assert(printFloat(-float.infinity, f) == "-inf"); + assert(printFloat(0.0f, f) == "0x0p+0"); + assert(printFloat(-0.0f, f) == "-0x0p+0"); + + assert(printFloat(double.nan, f) == "nan"); + assert(printFloat(-double.nan, f) == "-nan"); + assert(printFloat(double.infinity, f) == "inf"); + assert(printFloat(-double.infinity, f) == "-inf"); + assert(printFloat(0.0, f) == "0x0p+0"); + assert(printFloat(-0.0, f) == "-0x0p+0"); + + static if (real.mant_dig > 64) + { + pragma(msg, "printFloat tests disabled because of unsupported `real` format"); + } + else + { + assert(printFloat(real.nan, f) == "nan"); + assert(printFloat(-real.nan, f) == "-nan"); + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(-real.infinity, f) == "-inf"); + assert(printFloat(0.0L, f) == "0x0p+0"); + assert(printFloat(-0.0L, f) == "-0x0p+0"); + } + + import std.math.operations : nextUp; + + assert(printFloat(nextUp(0.0f), f) == "0x0.000002p-126"); + assert(printFloat(float.epsilon, f) == "0x1p-23"); + assert(printFloat(float.min_normal, f) == "0x1p-126"); + assert(printFloat(float.max, f) == "0x1.fffffep+127"); + + assert(printFloat(nextUp(0.0), f) == "0x0.0000000000001p-1022"); + assert(printFloat(double.epsilon, f) == "0x1p-52"); + assert(printFloat(double.min_normal, f) == "0x1p-1022"); + assert(printFloat(double.max, f) == "0x1.fffffffffffffp+1023"); + + static if (real.mant_dig == 64) + { + assert(printFloat(nextUp(0.0L), f) == "0x0.0000000000000002p-16382"); + assert(printFloat(real.epsilon, f) == "0x1p-63"); + assert(printFloat(real.min_normal, f) == "0x1p-16382"); + assert(printFloat(real.max, f) == "0x1.fffffffffffffffep+16383"); + } + + import std.math.constants : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, + LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; + + assert(printFloat(cast(float) E, f) == "0x1.5bf0a8p+1"); + assert(printFloat(cast(float) PI, f) == "0x1.921fb6p+1"); + assert(printFloat(cast(float) PI_2, f) == "0x1.921fb6p+0"); + assert(printFloat(cast(float) PI_4, f) == "0x1.921fb6p-1"); + assert(printFloat(cast(float) M_1_PI, f) == "0x1.45f306p-2"); + assert(printFloat(cast(float) M_2_PI, f) == "0x1.45f306p-1"); + assert(printFloat(cast(float) M_2_SQRTPI, f) == "0x1.20dd76p+0"); + assert(printFloat(cast(float) LN10, f) == "0x1.26bb1cp+1"); + assert(printFloat(cast(float) LN2, f) == "0x1.62e43p-1"); + assert(printFloat(cast(float) LOG2, f) == "0x1.344136p-2"); + assert(printFloat(cast(float) LOG2E, f) == "0x1.715476p+0"); + assert(printFloat(cast(float) LOG2T, f) == "0x1.a934fp+1"); + assert(printFloat(cast(float) LOG10E, f) == "0x1.bcb7b2p-2"); + assert(printFloat(cast(float) SQRT2, f) == "0x1.6a09e6p+0"); + assert(printFloat(cast(float) SQRT1_2, f) == "0x1.6a09e6p-1"); + + assert(printFloat(cast(double) E, f) == "0x1.5bf0a8b145769p+1"); + assert(printFloat(cast(double) PI, f) == "0x1.921fb54442d18p+1"); + assert(printFloat(cast(double) PI_2, f) == "0x1.921fb54442d18p+0"); + assert(printFloat(cast(double) PI_4, f) == "0x1.921fb54442d18p-1"); + assert(printFloat(cast(double) M_1_PI, f) == "0x1.45f306dc9c883p-2"); + assert(printFloat(cast(double) M_2_PI, f) == "0x1.45f306dc9c883p-1"); + assert(printFloat(cast(double) M_2_SQRTPI, f) == "0x1.20dd750429b6dp+0"); + assert(printFloat(cast(double) LN10, f) == "0x1.26bb1bbb55516p+1"); + assert(printFloat(cast(double) LN2, f) == "0x1.62e42fefa39efp-1"); + assert(printFloat(cast(double) LOG2, f) == "0x1.34413509f79ffp-2"); + assert(printFloat(cast(double) LOG2E, f) == "0x1.71547652b82fep+0"); + assert(printFloat(cast(double) LOG2T, f) == "0x1.a934f0979a371p+1"); + assert(printFloat(cast(double) LOG10E, f) == "0x1.bcb7b1526e50ep-2"); + assert(printFloat(cast(double) SQRT2, f) == "0x1.6a09e667f3bcdp+0"); + assert(printFloat(cast(double) SQRT1_2, f) == "0x1.6a09e667f3bcdp-1"); + + static if (real.mant_dig == 64) + { + assert(printFloat(E, f) == "0x1.5bf0a8b145769536p+1"); + assert(printFloat(PI, f) == "0x1.921fb54442d1846ap+1"); + assert(printFloat(PI_2, f) == "0x1.921fb54442d1846ap+0"); + assert(printFloat(PI_4, f) == "0x1.921fb54442d1846ap-1"); + assert(printFloat(M_1_PI, f) == "0x1.45f306dc9c882a54p-2"); + assert(printFloat(M_2_PI, f) == "0x1.45f306dc9c882a54p-1"); + assert(printFloat(M_2_SQRTPI, f) == "0x1.20dd750429b6d11ap+0"); + assert(printFloat(LN10, f) == "0x1.26bb1bbb5551582ep+1"); + assert(printFloat(LN2, f) == "0x1.62e42fefa39ef358p-1"); + assert(printFloat(LOG2, f) == "0x1.34413509f79fef32p-2"); + assert(printFloat(LOG2E, f) == "0x1.71547652b82fe178p+0"); + assert(printFloat(LOG2T, f) == "0x1.a934f0979a3715fcp+1"); + assert(printFloat(LOG10E, f) == "0x1.bcb7b1526e50e32ap-2"); + assert(printFloat(SQRT2, f) == "0x1.6a09e667f3bcc908p+0"); + assert(printFloat(SQRT1_2, f) == "0x1.6a09e667f3bcc908p-1"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.precision = 3; + + assert(printFloat(1.0f, f) == "0x1.000p+0"); + assert(printFloat(3.3f, f) == "0x1.a66p+1"); + assert(printFloat(2.9f, f) == "0x1.733p+1"); + + assert(printFloat(1.0, f) == "0x1.000p+0"); + assert(printFloat(3.3, f) == "0x1.a66p+1"); + assert(printFloat(2.9, f) == "0x1.733p+1"); + + static if (real.mant_dig == 64) + { + assert(printFloat(1.0L, f) == "0x1.000p+0"); + assert(printFloat(3.3L, f) == "0x1.a66p+1"); + assert(printFloat(2.9L, f) == "0x1.733p+1"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.precision = 0; + + assert(printFloat(1.0f, f) == "0x1p+0"); + assert(printFloat(3.3f, f) == "0x2p+1"); + assert(printFloat(2.9f, f) == "0x1p+1"); + + assert(printFloat(1.0, f) == "0x1p+0"); + assert(printFloat(3.3, f) == "0x2p+1"); + assert(printFloat(2.9, f) == "0x1p+1"); + + static if (real.mant_dig == 64) + { + assert(printFloat(1.0L, f) == "0x1p+0"); + assert(printFloat(3.3L, f) == "0x2p+1"); + assert(printFloat(2.9L, f) == "0x1p+1"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.precision = 0; + f.flHash = true; + + assert(printFloat(1.0f, f) == "0x1.p+0"); + assert(printFloat(3.3f, f) == "0x2.p+1"); + assert(printFloat(2.9f, f) == "0x1.p+1"); + + assert(printFloat(1.0, f) == "0x1.p+0"); + assert(printFloat(3.3, f) == "0x2.p+1"); + assert(printFloat(2.9, f) == "0x1.p+1"); + + static if (real.mant_dig == 64) + { + assert(printFloat(1.0L, f) == "0x1.p+0"); + assert(printFloat(3.3L, f) == "0x2.p+1"); + assert(printFloat(2.9L, f) == "0x1.p+1"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.width = 22; + + assert(printFloat(1.0f, f) == " 0x1p+0"); + assert(printFloat(3.3f, f) == " 0x1.a66666p+1"); + assert(printFloat(2.9f, f) == " 0x1.733334p+1"); + + assert(printFloat(1.0, f) == " 0x1p+0"); + assert(printFloat(3.3, f) == " 0x1.a666666666666p+1"); + assert(printFloat(2.9, f) == " 0x1.7333333333333p+1"); + + static if (real.mant_dig == 64) + { + f.width = 25; + assert(printFloat(1.0L, f) == " 0x1p+0"); + assert(printFloat(3.3L, f) == " 0x1.a666666666666666p+1"); + assert(printFloat(2.9L, f) == " 0x1.7333333333333334p+1"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.width = 22; + f.flDash = true; + + assert(printFloat(1.0f, f) == "0x1p+0 "); + assert(printFloat(3.3f, f) == "0x1.a66666p+1 "); + assert(printFloat(2.9f, f) == "0x1.733334p+1 "); + + assert(printFloat(1.0, f) == "0x1p+0 "); + assert(printFloat(3.3, f) == "0x1.a666666666666p+1 "); + assert(printFloat(2.9, f) == "0x1.7333333333333p+1 "); + + static if (real.mant_dig == 64) + { + f.width = 25; + assert(printFloat(1.0L, f) == "0x1p+0 "); + assert(printFloat(3.3L, f) == "0x1.a666666666666666p+1 "); + assert(printFloat(2.9L, f) == "0x1.7333333333333334p+1 "); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.width = 22; + f.flZero = true; + + assert(printFloat(1.0f, f) == "0x00000000000000001p+0"); + assert(printFloat(3.3f, f) == "0x0000000001.a66666p+1"); + assert(printFloat(2.9f, f) == "0x0000000001.733334p+1"); + + assert(printFloat(1.0, f) == "0x00000000000000001p+0"); + assert(printFloat(3.3, f) == "0x001.a666666666666p+1"); + assert(printFloat(2.9, f) == "0x001.7333333333333p+1"); + + static if (real.mant_dig == 64) + { + f.width = 25; + assert(printFloat(1.0L, f) == "0x00000000000000000001p+0"); + assert(printFloat(3.3L, f) == "0x001.a666666666666666p+1"); + assert(printFloat(2.9L, f) == "0x001.7333333333333334p+1"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.width = 22; + f.flPlus = true; + + assert(printFloat(1.0f, f) == " +0x1p+0"); + assert(printFloat(3.3f, f) == " +0x1.a66666p+1"); + assert(printFloat(2.9f, f) == " +0x1.733334p+1"); + + assert(printFloat(1.0, f) == " +0x1p+0"); + assert(printFloat(3.3, f) == " +0x1.a666666666666p+1"); + assert(printFloat(2.9, f) == " +0x1.7333333333333p+1"); + + static if (real.mant_dig == 64) + { + f.width = 25; + assert(printFloat(1.0L, f) == " +0x1p+0"); + assert(printFloat(3.3L, f) == " +0x1.a666666666666666p+1"); + assert(printFloat(2.9L, f) == " +0x1.7333333333333334p+1"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.width = 22; + f.flDash = true; + f.flSpace = true; + + assert(printFloat(1.0f, f) == " 0x1p+0 "); + assert(printFloat(3.3f, f) == " 0x1.a66666p+1 "); + assert(printFloat(2.9f, f) == " 0x1.733334p+1 "); + + assert(printFloat(1.0, f) == " 0x1p+0 "); + assert(printFloat(3.3, f) == " 0x1.a666666666666p+1 "); + assert(printFloat(2.9, f) == " 0x1.7333333333333p+1 "); + + static if (real.mant_dig == 64) + { + f.width = 25; + assert(printFloat(1.0L, f) == " 0x1p+0 "); + assert(printFloat(3.3L, f) == " 0x1.a666666666666666p+1 "); + assert(printFloat(2.9L, f) == " 0x1.7333333333333334p+1 "); + } +} + +@safe unittest +{ + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.precision = 1; + + fpctrl.rounding = FloatingPointControl.roundToNearest; + + /* tiesAwayFromZero currently not supported + assert(printFloat(0x1.18p0, f) == "0x1.2p+0"); + assert(printFloat(0x1.28p0, f) == "0x1.3p+0"); + assert(printFloat(0x1.1ap0, f) == "0x1.2p+0"); + assert(printFloat(0x1.16p0, f) == "0x1.1p+0"); + assert(printFloat(0x1.10p0, f) == "0x1.1p+0"); + assert(printFloat(-0x1.18p0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.28p0, f) == "-0x1.3p+0"); + assert(printFloat(-0x1.1ap0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.16p0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.10p0, f) == "-0x1.1p+0"); + */ + + assert(printFloat(0x1.18p0, f) == "0x1.2p+0"); + assert(printFloat(0x1.28p0, f) == "0x1.2p+0"); + assert(printFloat(0x1.1ap0, f) == "0x1.2p+0"); + assert(printFloat(0x1.16p0, f) == "0x1.1p+0"); + assert(printFloat(0x1.10p0, f) == "0x1.1p+0"); + assert(printFloat(-0x1.18p0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.28p0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.1ap0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.16p0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.10p0, f) == "-0x1.1p+0"); + + fpctrl.rounding = FloatingPointControl.roundToZero; + + assert(printFloat(0x1.18p0, f) == "0x1.1p+0"); + assert(printFloat(0x1.28p0, f) == "0x1.2p+0"); + assert(printFloat(0x1.1ap0, f) == "0x1.1p+0"); + assert(printFloat(0x1.16p0, f) == "0x1.1p+0"); + assert(printFloat(0x1.10p0, f) == "0x1.1p+0"); + assert(printFloat(-0x1.18p0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.28p0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.1ap0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.16p0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.10p0, f) == "-0x1.1p+0"); + + fpctrl.rounding = FloatingPointControl.roundUp; + + assert(printFloat(0x1.18p0, f) == "0x1.2p+0"); + assert(printFloat(0x1.28p0, f) == "0x1.3p+0"); + assert(printFloat(0x1.1ap0, f) == "0x1.2p+0"); + assert(printFloat(0x1.16p0, f) == "0x1.2p+0"); + assert(printFloat(0x1.10p0, f) == "0x1.1p+0"); + assert(printFloat(-0x1.18p0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.28p0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.1ap0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.16p0, f) == "-0x1.1p+0"); + assert(printFloat(-0x1.10p0, f) == "-0x1.1p+0"); + + fpctrl.rounding = FloatingPointControl.roundDown; + + assert(printFloat(0x1.18p0, f) == "0x1.1p+0"); + assert(printFloat(0x1.28p0, f) == "0x1.2p+0"); + assert(printFloat(0x1.1ap0, f) == "0x1.1p+0"); + assert(printFloat(0x1.16p0, f) == "0x1.1p+0"); + assert(printFloat(0x1.10p0, f) == "0x1.1p+0"); + assert(printFloat(-0x1.18p0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.28p0, f) == "-0x1.3p+0"); + assert(printFloat(-0x1.1ap0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.16p0, f) == "-0x1.2p+0"); + assert(printFloat(-0x1.10p0, f) == "-0x1.1p+0"); + } +} + +// for 100% coverage +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + f.precision = 3; + + assert(printFloat(0x1.19f81p0, f) == "0x1.1a0p+0"); + assert(printFloat(0x1.19f01p0, f) == "0x1.19fp+0"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'A'; + f.precision = 3; + + assert(printFloat(0x1.19f81p0, f) == "0X1.1A0P+0"); + assert(printFloat(0x1.19f01p0, f) == "0X1.19FP+0"); +} + +private void printFloatE(bool g, Writer, T, Char)(auto ref Writer w, T val, + FormatSpec!Char f, string sgn, int exp, ulong mnt, bool is_upper) +if (is(T == float) || is(T == double) + || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) +{ + import std.format.internal.write : writeAligned, PrecisionType, RoundingClass, round; + + static if (!g) + { + if (f.precision == f.UNSPECIFIED) + f.precision = 6; + } + + // special treatment for 0.0 + if (mnt == 0) + { + static if (g) + writeAligned(w, sgn, "0", ".", "", f, PrecisionType.allDigits); + else + writeAligned(w, sgn, "0", ".", is_upper ? "E+00" : "e+00", f, PrecisionType.fractionalDigits); + return; + } + + char[T.mant_dig + T.max_exp] dec_buf; + char[T.max_10_exp.stringof.length + 2] exp_buf; + + int final_exp = 0; + + RoundingClass rc; + + // Depending on exp, we will use one of three algorithms: + // + // Algorithm A: For large exponents (exp >= T.mant_dig) + // Algorithm B: For small exponents (exp < T.mant_dig - 61) + // Algorithm C: For exponents close to 0. + // + // Algorithm A: + // The number to print looks like this: mantissa followed by several zeros. + // + // We know, that there is no fractional part, so we can just use integer division, + // consecutivly dividing by 10 and writing down the remainder from right to left. + // Unfortunately the integer is too large to fit in an ulong, so we use something + // like BigInt: An array of ulongs. We only use 60 bits of that ulongs, because + // this simplifies (and speeds up) the division to come. + // + // For the division we use integer division with reminder for each ulong and put + // the reminder of each step in the first 4 bits of ulong of the next step (think of + // long division for the rationale behind this). The final reminder is the next + // digit (from right to left). + // + // This results in the output we would have for the %f specifier. We now adjust this + // for %e: First we calculate the place, where the exponent should be printed, filling + // up with zeros if needed and second we move the leftmost digit one to the left + // and inserting a dot. + // + // After that we decide on the rounding type, using the digits right of the position, + // where the exponent will be printed (currently they are still there, but will be + // overwritten later). + // + // Algorithm B: + // The number to print looks like this: zero dot several zeros followed by the mantissa + // + // We know, that the number has no integer part. The algorithm consecutivly multiplies + // by 10. The integer part (rounded down) after the multiplication is the next digit + // (from left to right). This integer part is removed after each step. + // Again, the number is represented as an array of ulongs, with only 60 bits used of + // every ulong. + // + // For the multiplication we use normal integer multiplication, which can result in digits + // in the uppermost 4 bits. These 4 digits are the carry which is added to the result + // of the next multiplication and finally the last carry is the next digit. + // + // Other than for the %f specifier, this multiplication is splitted into two almost + // identical parts. The first part lasts as long as we find zeros. We need to do this + // to calculate the correct exponent. + // + // The second part will stop, when only zeros remain or when we've got enough digits + // for the requested precision. In the second case, we have to find out, which rounding + // we have. Aside from special cases we do this by calculating one more digit. + // + // Algorithm C: + // This time, we know, that the integral part and the fractional part each fit into a + // ulong. The mantissa might be partially in both parts or completely in the fractional + // part. + // + // We first calculate the integral part by consecutive division by 10. Depending on the + // precision this might result in more digits, than we need. In that case we calculate + // the position of the exponent and the rounding type. + // + // If there is no integral part, we need to find the first non zero digit. We do this by + // consecutive multiplication by 10, saving the first non zero digit followed by a dot. + // + // In either case, we continue filling up with the fractional part until we have enough + // digits. If still necessary, we decide the rounding type, mainly by looking at the + // next digit. + + size_t right = 1; + size_t start = 1; + size_t left = 1; + + static if (is(T == real) && real.mant_dig == 64) + { + enum small_bound = 0; + enum max_buf = 275; + } + else + { + enum small_bound = T.mant_dig - 61; + static if (is(T == float)) + enum max_buf = 4; + else + enum max_buf = 18; + } + + ulong[max_buf] bigbuf; + if (exp >= T.mant_dig) + { + start = left = right = dec_buf.length; + + // large number without fractional digits + // + // As this number does not fit in a ulong, we use an array of ulongs. We only use 60 of the 64 bits, + // because this makes it much more easy to implement the division by 10. + int count = exp / 60 + 1; + + // only the first few ulongs contain the mantiassa. The rest are zeros. + int lower = 60 - (exp - T.mant_dig + 1) % 60; + + static if (is(T == real) && real.mant_dig == 64) + { + // for x87 reals, the lowest ulong may contain more than 60 bits, + // because the mantissa is 63 (>60) bits long + // therefore we need one ulong less + if (lower <= 3) count--; + } + + // saved in big endian format + ulong[] mybig = bigbuf[0 .. count]; + + if (lower < T.mant_dig) + { + mybig[0] = mnt >> lower; + mybig[1] = (mnt & ((1L << lower) - 1)) << 60 - lower; + } + else + mybig[0] = (mnt & ((1L << lower) - 1)) << 60 - lower; + + // Generation of digits by consecutive division with reminder by 10. + int msu = 0; // Most significant ulong; when it get's zero, we can ignore it further on + while (msu < count - 1 || mybig[$ - 1] != 0) + { + ulong mod = 0; + foreach (i;msu .. count) + { + mybig[i] |= mod << 60; + mod = mybig[i] % 10; + mybig[i] /= 10; + } + if (mybig[msu] == 0) + ++msu; + + dec_buf[--left] = cast(byte) ('0' + mod); + ++final_exp; + } + --final_exp; + + static if (g) + start = left + f.precision; + else + start = left + f.precision + 1; + + // move leftmost digit one more left and add dot between + dec_buf[left - 1] = dec_buf[left]; + dec_buf[left] = '.'; + --left; + + // rounding type + if (start >= right) + rc = RoundingClass.ZERO; + else if (dec_buf[start] != '0' && dec_buf[start] != '5') + rc = dec_buf[start] > '5' ? RoundingClass.UPPER : RoundingClass.LOWER; + else + { + rc = dec_buf[start] == '5' ? RoundingClass.FIVE : RoundingClass.ZERO; + foreach (i; start + 1 .. right) + if (dec_buf[i] > '0') + { + rc = rc == RoundingClass.FIVE ? RoundingClass.UPPER : RoundingClass.LOWER; + break; + } + } + + if (start < right) right = start; + } + else if (exp < small_bound) + { + // small number without integer digits + // + // Again this number does not fit in a ulong and we use an array of ulongs. And again we + // only use 60 bits, because this simplifies the multiplication by 10. + int count = (T.mant_dig - exp - 2) / 60 + 1; + + // saved in little endian format + ulong[] mybig = bigbuf[0 .. count]; + + // only the last few ulongs contain the mantiassa. Because of little endian + // format these are the ulongs at index 0 and 1 (and 2 in case of x87 reals). + // The rest are zeros. + int upper = 60 - (-exp - 1) % 60; + + static if (is(T == real) && real.mant_dig == 64) + { + if (upper < 4) + { + mybig[0] = (mnt & ((1L << (4 - upper)) - 1)) << 56 + upper; + mybig[1] = (mnt >> (4 - upper)) & ((1L << 60) - 1); + mybig[2] = mnt >> 64 - upper; + } + else + { + mybig[0] = (mnt & ((1L << (T.mant_dig - upper)) - 1)) << 60 - (T.mant_dig - upper); + mybig[1] = mnt >> (T.mant_dig - upper); + } + } + else + { + if (upper < T.mant_dig) + { + mybig[0] = (mnt & ((1L << (T.mant_dig - upper)) - 1)) << 60 - (T.mant_dig - upper); + mybig[1] = mnt >> (T.mant_dig - upper); + } + else + mybig[0] = mnt << (upper - T.mant_dig); + } + + int lsu = 0; // Least significant ulong; when it get's zero, we can ignore it further on + + // adding zeros, until we reach first nonzero + while (lsu < count - 1 || mybig[$ - 1]!=0) + { + ulong over = 0; + foreach (i; lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + if (mybig[lsu] == 0) + ++lsu; + --final_exp; + + if (over != 0) + { + dec_buf[right++] = cast(byte) ('0' + over); + dec_buf[right++] = '.'; + break; + } + } + + // adding more digits + static if (g) + start = right - 1; + else + start = right; + while ((lsu < count - 1 || mybig[$ - 1] != 0) && right - start < f.precision) + { + ulong over = 0; + foreach (i;lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + if (mybig[lsu] == 0) + ++lsu; + + dec_buf[right++] = cast(byte) ('0' + over); + } + + // rounding type + if (lsu >= count - 1 && mybig[count - 1] == 0) + rc = RoundingClass.ZERO; + else if (lsu == count - 1 && mybig[lsu] == 1L << 59) + rc = RoundingClass.FIVE; + else + { + ulong over = 0; + foreach (i;lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + rc = over >= 5 ? RoundingClass.UPPER : RoundingClass.LOWER; + } + } + else + { + // medium sized number, probably with integer and fractional digits + // this is fastest, because both parts fit into a ulong each + ulong int_part = mnt >> (T.mant_dig - 1 - exp); + ulong frac_part = mnt & ((1L << (T.mant_dig - 1 - exp)) - 1); + + // for x87 reals the mantiassa might be up to 3 bits too long + // we need to save these bits as a tail and handle this separately + static if (is(T == real) && real.mant_dig == 64) + { + ulong tail = 0; + ulong tail_length = 0; + if (exp < 3) + { + tail = frac_part & ((1L << (3 - exp)) - 1); + tail_length = 3 - exp; + frac_part >>= 3 - exp; + exp = 3; + } + } + + start = 0; + + // could we already decide on the rounding mode in the integer part? + bool found = false; + + if (int_part > 0) + { + import core.bitop : bsr; + left = right = int_part.bsr * 100 / 332 + 4; + + // integer part, if there is something to print + while (int_part >= 10) + { + dec_buf[--left] = '0' + (int_part % 10); + int_part /= 10; + ++final_exp; + ++start; + } + + dec_buf[--left] = '.'; + dec_buf[--left] = cast(byte) ('0' + int_part); + + static if (g) + auto limit = f.precision + 1; + else + auto limit = f.precision + 2; + + if (right - left > limit) + { + auto old_right = right; + right = left + limit; + + if (dec_buf[right] == '5' || dec_buf[right] == '0') + { + rc = dec_buf[right] == '5' ? RoundingClass.FIVE : RoundingClass.ZERO; + if (frac_part != 0) + rc = rc == RoundingClass.FIVE ? RoundingClass.UPPER : RoundingClass.LOWER; + else + foreach (i;right + 1 .. old_right) + if (dec_buf[i] > '0') + { + rc = rc == RoundingClass.FIVE ? RoundingClass.UPPER : RoundingClass.LOWER; + break; + } + } + else + rc = dec_buf[right] > '5' ? RoundingClass.UPPER : RoundingClass.LOWER; + found = true; + } + } + else + { + // fractional part, skipping leading zeros + while (frac_part != 0) + { + --final_exp; + frac_part *= 10; + static if (is(T == real) && real.mant_dig == 64) + { + if (tail_length > 0) + { + // together this is *= 10; + tail *= 5; + tail_length--; + + frac_part += tail >> tail_length; + if (tail_length > 0) + tail &= (1L << tail_length) - 1; + } + } + auto tmp = frac_part >> (T.mant_dig - 1 - exp); + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + if (tmp > 0) + { + dec_buf[right++] = cast(byte) ('0' + tmp); + dec_buf[right++] = '.'; + break; + } + } + + rc = RoundingClass.ZERO; + } + + static if (g) + size_t limit = f.precision - 1; + else + size_t limit = f.precision; + + // the fractional part after the zeros + while (frac_part != 0 && start < limit) + { + frac_part *= 10; + static if (is(T == real) && real.mant_dig == 64) + { + if (tail_length > 0) + { + // together this is *= 10; + tail *= 5; + tail_length--; + + frac_part += tail >> tail_length; + if (tail_length > 0) + tail &= (1L << tail_length) - 1; + } + } + dec_buf[right++] = cast(byte) ('0' + (frac_part >> (T.mant_dig - 1 - exp))); + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + ++start; + } + + static if (g) + limit = right - left - 1; + else + limit = start; + + // rounding mode, if not allready known + if (frac_part != 0 && !found) + { + frac_part *= 10; + auto nextDigit = frac_part >> (T.mant_dig - 1 - exp); + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + + if (nextDigit == 5 && frac_part == 0) + rc = RoundingClass.FIVE; + else if (nextDigit >= 5) + rc = RoundingClass.UPPER; + else + rc = RoundingClass.LOWER; + } + } + + if (round(dec_buf, left, right, rc, sgn == "-")) + { + left--; + right--; + dec_buf[left + 2] = dec_buf[left + 1]; + dec_buf[left + 1] = '.'; + final_exp++; + } + + // printing exponent + auto neg = final_exp < 0; + if (neg) final_exp = -final_exp; + + size_t exp_pos = exp_buf.length; + + do + { + exp_buf[--exp_pos] = '0' + final_exp%10; + final_exp /= 10; + } while (final_exp > 0); + if (exp_buf.length - exp_pos == 1) + exp_buf[--exp_pos] = '0'; + exp_buf[--exp_pos] = neg ? '-' : '+'; + exp_buf[--exp_pos] = is_upper ? 'E' : 'e'; + + while (right > left + 1 && dec_buf[right - 1] == '0') right--; + + if (right == left + 1) + dec_buf[right++] = '.'; + + static if (g) + writeAligned(w, sgn, dec_buf[left .. left + 1], dec_buf[left + 1 .. right], + exp_buf[exp_pos .. $], f, PrecisionType.allDigits); + else + writeAligned(w, sgn, dec_buf[left .. left + 1], dec_buf[left + 1 .. right], + exp_buf[exp_pos .. $], f, PrecisionType.fractionalDigits); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(float.nan, f) == "nan"); + assert(printFloat(-float.nan, f) == "-nan"); + assert(printFloat(float.infinity, f) == "inf"); + assert(printFloat(-float.infinity, f) == "-inf"); + assert(printFloat(0.0f, f) == "0.000000e+00"); + assert(printFloat(-0.0f, f) == "-0.000000e+00"); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "9.999946e-41"); + assert(printFloat(cast(float) -1e-40, f) == "-9.999946e-41"); + assert(printFloat(1e-30f, f) == "1.000000e-30"); + assert(printFloat(-1e-30f, f) == "-1.000000e-30"); + assert(printFloat(1e-10f, f) == "1.000000e-10"); + assert(printFloat(-1e-10f, f) == "-1.000000e-10"); + assert(printFloat(0.1f, f) == "1.000000e-01"); + assert(printFloat(-0.1f, f) == "-1.000000e-01"); + assert(printFloat(10.0f, f) == "1.000000e+01"); + assert(printFloat(-10.0f, f) == "-1.000000e+01"); + assert(printFloat(1e30f, f) == "1.000000e+30"); + assert(printFloat(-1e30f, f) == "-1.000000e+30"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "1.401298e-45"); + assert(printFloat(nextDown(-0.0f), f) == "-1.401298e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.width = 20; + f.precision = 10; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == " 0.0000000000e+00"); + assert(printFloat(-0.0f, f) == " -0.0000000000e+00"); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == " 9.9999461011e-41"); + assert(printFloat(cast(float) -1e-40, f) == " -9.9999461011e-41"); + assert(printFloat(1e-30f, f) == " 1.0000000032e-30"); + assert(printFloat(-1e-30f, f) == " -1.0000000032e-30"); + assert(printFloat(1e-10f, f) == " 1.0000000134e-10"); + assert(printFloat(-1e-10f, f) == " -1.0000000134e-10"); + assert(printFloat(0.1f, f) == " 1.0000000149e-01"); + assert(printFloat(-0.1f, f) == " -1.0000000149e-01"); + assert(printFloat(10.0f, f) == " 1.0000000000e+01"); + assert(printFloat(-10.0f, f) == " -1.0000000000e+01"); + assert(printFloat(1e30f, f) == " 1.0000000150e+30"); + assert(printFloat(-1e30f, f) == " -1.0000000150e+30"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == " 1.4012984643e-45"); + assert(printFloat(nextDown(-0.0f), f) == " -1.4012984643e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.width = 20; + f.precision = 10; + f.flDash = true; + + assert(printFloat(float.nan, f) == "nan "); + assert(printFloat(-float.nan, f) == "-nan "); + assert(printFloat(float.infinity, f) == "inf "); + assert(printFloat(-float.infinity, f) == "-inf "); + assert(printFloat(0.0f, f) == "0.0000000000e+00 "); + assert(printFloat(-0.0f, f) == "-0.0000000000e+00 "); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "9.9999461011e-41 "); + assert(printFloat(cast(float) -1e-40, f) == "-9.9999461011e-41 "); + assert(printFloat(1e-30f, f) == "1.0000000032e-30 "); + assert(printFloat(-1e-30f, f) == "-1.0000000032e-30 "); + assert(printFloat(1e-10f, f) == "1.0000000134e-10 "); + assert(printFloat(-1e-10f, f) == "-1.0000000134e-10 "); + assert(printFloat(0.1f, f) == "1.0000000149e-01 "); + assert(printFloat(-0.1f, f) == "-1.0000000149e-01 "); + assert(printFloat(10.0f, f) == "1.0000000000e+01 "); + assert(printFloat(-10.0f, f) == "-1.0000000000e+01 "); + assert(printFloat(1e30f, f) == "1.0000000150e+30 "); + assert(printFloat(-1e30f, f) == "-1.0000000150e+30 "); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "1.4012984643e-45 "); + assert(printFloat(nextDown(-0.0f), f) == "-1.4012984643e-45 "); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.width = 20; + f.precision = 10; + f.flZero = true; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == "00000.0000000000e+00"); + assert(printFloat(-0.0f, f) == "-0000.0000000000e+00"); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "00009.9999461011e-41"); + assert(printFloat(cast(float) -1e-40, f) == "-0009.9999461011e-41"); + assert(printFloat(1e-30f, f) == "00001.0000000032e-30"); + assert(printFloat(-1e-30f, f) == "-0001.0000000032e-30"); + assert(printFloat(1e-10f, f) == "00001.0000000134e-10"); + assert(printFloat(-1e-10f, f) == "-0001.0000000134e-10"); + assert(printFloat(0.1f, f) == "00001.0000000149e-01"); + assert(printFloat(-0.1f, f) == "-0001.0000000149e-01"); + assert(printFloat(10.0f, f) == "00001.0000000000e+01"); + assert(printFloat(-10.0f, f) == "-0001.0000000000e+01"); + assert(printFloat(1e30f, f) == "00001.0000000150e+30"); + assert(printFloat(-1e30f, f) == "-0001.0000000150e+30"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "00001.4012984643e-45"); + assert(printFloat(nextDown(-0.0f), f) == "-0001.4012984643e-45"); +} + +@safe unittest +{ + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.precision = 1; + + fpctrl.rounding = FloatingPointControl.roundToNearest; + + /* + assert(printFloat(11.5f, f) == "1.2e+01"); + assert(printFloat(12.5f, f) == "1.3e+01"); + assert(printFloat(11.7f, f) == "1.2e+01"); + assert(printFloat(11.3f, f) == "1.1e+01"); + assert(printFloat(11.0f, f) == "1.1e+01"); + assert(printFloat(-11.5f, f) == "-1.2e+01"); + assert(printFloat(-12.5f, f) == "-1.3e+01"); + assert(printFloat(-11.7f, f) == "-1.2e+01"); + assert(printFloat(-11.3f, f) == "-1.1e+01"); + assert(printFloat(-11.0f, f) == "-1.1e+01"); + */ + + assert(printFloat(11.5f, f) == "1.2e+01"); + assert(printFloat(12.5f, f) == "1.2e+01"); + assert(printFloat(11.7f, f) == "1.2e+01"); + assert(printFloat(11.3f, f) == "1.1e+01"); + assert(printFloat(11.0f, f) == "1.1e+01"); + assert(printFloat(-11.5f, f) == "-1.2e+01"); + assert(printFloat(-12.5f, f) == "-1.2e+01"); + assert(printFloat(-11.7f, f) == "-1.2e+01"); + assert(printFloat(-11.3f, f) == "-1.1e+01"); + assert(printFloat(-11.0f, f) == "-1.1e+01"); + + fpctrl.rounding = FloatingPointControl.roundToZero; + + assert(printFloat(11.5f, f) == "1.1e+01"); + assert(printFloat(12.5f, f) == "1.2e+01"); + assert(printFloat(11.7f, f) == "1.1e+01"); + assert(printFloat(11.3f, f) == "1.1e+01"); + assert(printFloat(11.0f, f) == "1.1e+01"); + assert(printFloat(-11.5f, f) == "-1.1e+01"); + assert(printFloat(-12.5f, f) == "-1.2e+01"); + assert(printFloat(-11.7f, f) == "-1.1e+01"); + assert(printFloat(-11.3f, f) == "-1.1e+01"); + assert(printFloat(-11.0f, f) == "-1.1e+01"); + + fpctrl.rounding = FloatingPointControl.roundUp; + + assert(printFloat(11.5f, f) == "1.2e+01"); + assert(printFloat(12.5f, f) == "1.3e+01"); + assert(printFloat(11.7f, f) == "1.2e+01"); + assert(printFloat(11.3f, f) == "1.2e+01"); + assert(printFloat(11.0f, f) == "1.1e+01"); + assert(printFloat(-11.5f, f) == "-1.1e+01"); + assert(printFloat(-12.5f, f) == "-1.2e+01"); + assert(printFloat(-11.7f, f) == "-1.1e+01"); + assert(printFloat(-11.3f, f) == "-1.1e+01"); + assert(printFloat(-11.0f, f) == "-1.1e+01"); + + fpctrl.rounding = FloatingPointControl.roundDown; + + assert(printFloat(11.5f, f) == "1.1e+01"); + assert(printFloat(12.5f, f) == "1.2e+01"); + assert(printFloat(11.7f, f) == "1.1e+01"); + assert(printFloat(11.3f, f) == "1.1e+01"); + assert(printFloat(11.0f, f) == "1.1e+01"); + assert(printFloat(-11.5f, f) == "-1.2e+01"); + assert(printFloat(-12.5f, f) == "-1.3e+01"); + assert(printFloat(-11.7f, f) == "-1.2e+01"); + assert(printFloat(-11.3f, f) == "-1.2e+01"); + assert(printFloat(-11.0f, f) == "-1.1e+01"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(double.nan, f) == "nan"); + assert(printFloat(-double.nan, f) == "-nan"); + assert(printFloat(double.infinity, f) == "inf"); + assert(printFloat(-double.infinity, f) == "-inf"); + assert(printFloat(0.0, f) == "0.000000e+00"); + assert(printFloat(-0.0, f) == "-0.000000e+00"); + // / 1000 needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(1e-307 / 1000, f) == "1.000000e-310"); + assert(printFloat(-1e-307 / 1000, f) == "-1.000000e-310"); + assert(printFloat(1e-30, f) == "1.000000e-30"); + assert(printFloat(-1e-30, f) == "-1.000000e-30"); + assert(printFloat(1e-10, f) == "1.000000e-10"); + assert(printFloat(-1e-10, f) == "-1.000000e-10"); + assert(printFloat(0.1, f) == "1.000000e-01"); + assert(printFloat(-0.1, f) == "-1.000000e-01"); + assert(printFloat(10.0, f) == "1.000000e+01"); + assert(printFloat(-10.0, f) == "-1.000000e+01"); + assert(printFloat(1e300, f) == "1.000000e+300"); + assert(printFloat(-1e300, f) == "-1.000000e+300"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0), f) == "4.940656e-324"); + assert(printFloat(nextDown(-0.0), f) == "-4.940656e-324"); +} + +@safe unittest +{ + static if (real.mant_dig > 64) + { + pragma(msg, "printFloat tests disabled because of unsupported `real` format"); + } + else + { + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(real.nan, f) == "nan"); + assert(printFloat(-real.nan, f) == "-nan"); + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(-real.infinity, f) == "-inf"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + + import std.math.operations : nextUp; + + double eps = nextUp(0.0); + f.precision = 1000; + assert(printFloat(eps, f) == + "4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599" + ~"23797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036" + ~"88718636056998730723050006387409153564984387312473397273169615140031715385398074126238565591171026" + ~"65855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332" + ~"45247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431" + ~"93609238289345836806010601150616980975307834227731832924790498252473077637592724787465608477820373" + ~"44696995336470179726777175851256605511991315048911014510378627381672509558373897335989936648099411" + ~"64205702637090279242767544565229087538682506419718265533447265625000000000000000000000000000000000" + ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ~"000000000000000000000e-324"); + + f.precision = 50; + assert(printFloat(double.max, f) == + "1.79769313486231570814527423731704356798070567525845e+308"); + assert(printFloat(double.epsilon, f) == + "2.22044604925031308084726333618164062500000000000000e-16"); + + f.precision = 10; + assert(printFloat(1.0/3.0, f) == "3.3333333333e-01"); + assert(printFloat(1.0/7.0, f) == "1.4285714286e-01"); + assert(printFloat(1.0/9.0, f) == "1.1111111111e-01"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + f.precision = 15; + + import std.math.constants : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, + LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; + + assert(printFloat(cast(double) E, f) == "2.718281828459045e+00"); + assert(printFloat(cast(double) PI, f) == "3.141592653589793e+00"); + assert(printFloat(cast(double) PI_2, f) == "1.570796326794897e+00"); + assert(printFloat(cast(double) PI_4, f) == "7.853981633974483e-01"); + assert(printFloat(cast(double) M_1_PI, f) == "3.183098861837907e-01"); + assert(printFloat(cast(double) M_2_PI, f) == "6.366197723675814e-01"); + assert(printFloat(cast(double) M_2_SQRTPI, f) == "1.128379167095513e+00"); + assert(printFloat(cast(double) LN10, f) == "2.302585092994046e+00"); + assert(printFloat(cast(double) LN2, f) == "6.931471805599453e-01"); + assert(printFloat(cast(double) LOG2, f) == "3.010299956639812e-01"); + assert(printFloat(cast(double) LOG2E, f) == "1.442695040888963e+00"); + assert(printFloat(cast(double) LOG2T, f) == "3.321928094887362e+00"); + assert(printFloat(cast(double) LOG10E, f) == "4.342944819032518e-01"); + assert(printFloat(cast(double) SQRT2, f) == "1.414213562373095e+00"); + assert(printFloat(cast(double) SQRT1_2, f) == "7.071067811865476e-01"); +} + +// for 100% coverage +@safe unittest +{ + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + auto f = FormatSpec!dchar(""); + f.spec = 'E'; + f.precision = 80; + assert(printFloat(5.62776e+12f, f) == + "5.62775982080000000000000000000000000000000000000000000000000000000000000000000000E+12"); + + f.precision = 49; + assert(printFloat(2.5997869e-12f, f) == + "2.5997869221999758693186777236405760049819946289062E-12"); + + f.precision = 6; + assert(printFloat(-1.1418613e+07f, f) == "-1.141861E+07"); + assert(printFloat(-1.368281e+07f, f) == "-1.368281E+07"); + + f.precision = 1; + assert(printFloat(-245.666f, f) == "-2.5E+02"); + + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundUp; + + f.precision = 0; + assert(printFloat(709422.0f, f) == "8E+05"); + } +} + +@safe unittest +{ + static if (real.mant_dig > 64) + { + pragma(msg, "printFloat tests disabled because of unsupported `real` format"); + } + else + { + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(real.nan, f) == "nan"); + assert(printFloat(-real.nan, f) == "-nan"); + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(-real.infinity, f) == "-inf"); + assert(printFloat(0.0L, f) == "0.000000e+00"); + assert(printFloat(-0.0L, f) == "-0.000000e+00"); + } + + static if (real.mant_dig == 64) + { + assert(printFloat(1e-4940L, f) == "1.000000e-4940"); + assert(printFloat(-1e-4940L, f) == "-1.000000e-4940"); + assert(printFloat(1e-30L, f) == "1.000000e-30"); + assert(printFloat(-1e-30L, f) == "-1.000000e-30"); + assert(printFloat(1e-10L, f) == "1.000000e-10"); + assert(printFloat(-1e-10L, f) == "-1.000000e-10"); + assert(printFloat(0.1L, f) == "1.000000e-01"); + assert(printFloat(-0.1L, f) == "-1.000000e-01"); + assert(printFloat(10.0L, f) == "1.000000e+01"); + assert(printFloat(-10.0L, f) == "-1.000000e+01"); + version (Windows) {} // issue 20972 + else + { + assert(printFloat(1e4000L, f) == "1.000000e+4000"); + assert(printFloat(-1e4000L, f) == "-1.000000e+4000"); + } + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0L), f) == "3.645200e-4951"); + assert(printFloat(nextDown(-0.0L), f) == "-3.645200e-4951"); + } +} + +@safe unittest +{ + import std.exception : assertCTFEable; + import std.math.exponential : log2; + import std.math.operations : nextDown; + + assertCTFEable!( + { + // log2 is broken for x87-reals on some computers in CTFE + // the following tests excludes these computers from the tests + // (issue 21757) + enum test = cast(int) log2(3.05e2312L); + static if (real.mant_dig == 64 && test == 7681) + { + auto f = FormatSpec!dchar(""); + f.spec = 'e'; + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(10.0L, f) == "1.000000e+01"); + assert(printFloat(2.6080L, f) == "2.608000e+00"); + assert(printFloat(3.05e2312L, f) == "3.050000e+2312"); + + f.precision = 60; + assert(printFloat(2.65e-54L, f) == + "2.650000000000000000059009987400547013941028940935296547599415e-54"); + + /* + commented out, because CTFE is currently too slow for 5000 digits with extreme values + + f.precision = 5000; + auto result2 = printFloat(1.2119e-4822L, f); + assert(result2.length == 5008); + assert(result2[$ - 20 .. $] == "60729486595339e-4822"); + auto result3 = printFloat(real.min_normal, f); + assert(result3.length == 5008); + assert(result3[$ - 20 .. $] == "20781410082267e-4932"); + auto result4 = printFloat(real.min_normal.nextDown, f); + assert(result4.length == 5008); + assert(result4[$ - 20 .. $] == "81413263331006e-4932"); + */ + } + }); +} + +private void printFloatF(bool g, Writer, T, Char)(auto ref Writer w, T val, + FormatSpec!Char f, string sgn, int exp, ulong mnt, bool is_upper) +if (is(T == float) || is(T == double) + || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) +{ + import std.format.internal.write : writeAligned, PrecisionType, RoundingClass, round; + + static if (!g) + { + if (f.precision == f.UNSPECIFIED) + f.precision = 6; + } + + // special treatment for 0.0 + if (exp == 0 && mnt == 0) + { + writeAligned(w, sgn, "0", ".", "", f, PrecisionType.fractionalDigits); + return; + } + + char[T.max_exp + T.mant_dig + 1] dec_buf; + + RoundingClass rc; + + // Depending on exp, we will use one of three algorithms: + // + // Algorithm A: For large exponents (exp >= T.mant_dig) + // Algorithm B: For small exponents (exp < T.mant_dig - 61) + // Algorithm C: For exponents close to 0. + // + // Algorithm A: + // The number to print looks like this: mantissa followed by several zeros. + // + // We know, that there is no fractional part, so we can just use integer division, + // consecutivly dividing by 10 and writing down the remainder from right to left. + // Unfortunately the integer is too large to fit in an ulong, so we use something + // like BigInt: An array of ulongs. We only use 60 bits of that ulongs, because + // this simplifies (and speeds up) the division to come. + // + // For the division we use integer division with reminder for each ulong and put + // the reminder of each step in the first 4 bits of ulong of the next step (think of + // long division for the rationale behind this). The final reminder is the next + // digit (from right to left). + // + // Algorithm B: + // The number to print looks like this: zero dot several zeros followed by the mantissa + // + // We know, that the number has no integer part. The algorithm consecutivly multiplies + // by 10. The integer part (rounded down) after the multiplication is the next digit + // (from left to right). This integer part is removed after each step. + // Again, the number is represented as an array of ulongs, with only 60 bits used of + // every ulong. + // + // For the multiplication we use normal integer multiplication, which can result in digits + // in the uppermost 4 bits. These 4 digits are the carry which is added to the result + // of the next multiplication and finally the last carry is the next digit. + // + // The calculation will stop, when only zeros remain or when we've got enough digits + // for the requested precision. In the second case, we have to find out, which rounding + // we have. Aside from special cases we do this by calculating one more digit. + // + // Algorithm C: + // This time, we know, that the integral part and the fractional part each fit into a + // ulong. The mantissa might be partially in both parts or completely in the fractional + // part. + // + // We first calculate the integral part by consecutive division by 10. Then we calculate + // the fractional part by consecutive multiplication by 10. Again only until we have enough + // digits. Finally, we decide the rounding type, mainly by looking at the next digit. + + static if (is(T == real) && real.mant_dig == 64) + { + enum small_bound = 0; + enum max_buf = 275; + } + else + { + enum small_bound = T.mant_dig - 61; + static if (is(T == float)) + enum max_buf = 4; + else + enum max_buf = 18; + } + + size_t start = 2; + size_t left = 2; + size_t right = 2; + + ulong[max_buf] bigbuf; + if (exp >= T.mant_dig) + { + left = start = dec_buf.length - 1; + right = dec_buf.length; + dec_buf[start] = '.'; + + // large number without fractional digits + // + // As this number does not fit in a ulong, we use an array of ulongs. We only use 60 of the 64 bits, + // because this makes it much more easy to implement the division by 10. + int count = exp / 60 + 1; + + // only the first few ulongs contain the mantiassa. The rest are zeros. + int lower = 60 - (exp - T.mant_dig + 1) % 60; + + static if (is(T == real) && real.mant_dig == 64) + { + // for x87 reals, the lowest ulong may contain more than 60 bits, + // because the mantissa is 63 (>60) bits long + // therefore we need one ulong less + if (lower <= 3) count--; + } + + // saved in big endian format + ulong[] mybig = bigbuf[0 .. count]; + + if (lower < T.mant_dig) + { + mybig[0] = mnt >> lower; + mybig[1] = (mnt & ((1L << lower) - 1)) << 60 - lower; + } + else + mybig[0] = (mnt & ((1L << lower) - 1)) << 60 - lower; + + // Generation of digits by consecutive division with reminder by 10. + int msu = 0; // Most significant ulong; when it get's zero, we can ignore it furtheron + while (msu < count - 1 || mybig[$ - 1] != 0) + { + ulong mod = 0; + foreach (i;msu .. count) + { + mybig[i] |= mod << 60; + mod = mybig[i] % 10; + mybig[i] /= 10; + } + if (mybig[msu] == 0) + ++msu; + + dec_buf[--left] = cast(byte) ('0' + mod); + } + + rc = RoundingClass.ZERO; + } + else if (exp < small_bound) + { + // small number without integer digits + // + // Again this number does not fit in a ulong and we use an array of ulongs. And again we + // only use 60 bits, because this simplifies the multiplication by 10. + int count = (T.mant_dig - exp - 2) / 60 + 1; + + // saved in little endian format + ulong[] mybig = bigbuf[0 .. count]; + + // only the last few ulongs contain the mantiassa. Because of little endian + // format these are the ulongs at index 0 and 1 (and 2 in case of x87 reals). + // The rest are zeros. + int upper = 60 - (-exp - 1) % 60; + + static if (is(T == real) && real.mant_dig == 64) + { + if (upper < 4) + { + mybig[0] = (mnt & ((1L << (4 - upper)) - 1)) << 56 + upper; + mybig[1] = (mnt >> (4 - upper)) & ((1L << 60) - 1); + mybig[2] = mnt >> 64 - upper; + } + else + { + mybig[0] = (mnt & ((1L << (T.mant_dig - upper)) - 1)) << 60 - (T.mant_dig - upper); + mybig[1] = mnt >> (T.mant_dig - upper); + } + } + else + { + if (upper < T.mant_dig) + { + mybig[0] = (mnt & ((1L << (T.mant_dig - upper)) - 1)) << 60 - (T.mant_dig - upper); + mybig[1] = mnt >> (T.mant_dig - upper); + } + else + mybig[0] = mnt << (upper - T.mant_dig); + } + + dec_buf[--left] = '0'; // 0 left of the dot + dec_buf[right++] = '.'; + + static if (g) + { + // precision starts at first non zero, so we move start + // to the right, until we found first non zero, thus avoiding + // a premature break of the loop + bool found = false; + start = left + 1; + } + + // Generation of digits by consecutive multiplication by 10. + int lsu = 0; // Least significant ulong; when it get's zero, we can ignore it furtheron + while ((lsu < count - 1 || mybig[$ - 1] != 0) && right - start - 1 < f.precision) + { + ulong over = 0; + foreach (i;lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + if (mybig[lsu] == 0) + ++lsu; + + dec_buf[right++] = cast(byte) ('0' + over); + + static if (g) + { + if (dec_buf[right - 1] != '0') + found = true; + else if (!found) + start++; + } + } + + static if (g) start = 2; + + if (lsu >= count - 1 && mybig[count - 1] == 0) + rc = RoundingClass.ZERO; + else if (lsu == count - 1 && mybig[lsu] == 1L << 59) + rc = RoundingClass.FIVE; + else + { + ulong over = 0; + foreach (i;lsu .. count) + { + mybig[i] = mybig[i] * 10 + over; + over = mybig[i] >> 60; + mybig[i] &= (1L << 60) - 1; + } + rc = over >= 5 ? RoundingClass.UPPER : RoundingClass.LOWER; + } + } + else + { + // medium sized number, probably with integer and fractional digits + // this is fastest, because both parts fit into a ulong each + ulong int_part = mnt >> (T.mant_dig - 1 - exp); + ulong frac_part = mnt & ((1L << (T.mant_dig - 1 - exp)) - 1); + + // for x87 reals the mantiassa might be up to 3 bits too long + // we need to save these bits as a tail and handle this separately + static if (is(T == real) && real.mant_dig == 64) + { + ulong tail = 0; + ulong tail_length = 0; + if (exp < 3) + { + tail = frac_part & ((1L << (3 - exp)) - 1); + tail_length = 3 - exp; + frac_part >>= 3 - exp; + exp = 3; + } + } + + static if (g) auto found = int_part > 0; // searching first non zero + + // creating int part + if (int_part == 0) + dec_buf[--left] = '0'; + else + { + import core.bitop : bsr; + left = right = start = int_part.bsr * 100 / 332 + 4; + + while (int_part > 0) + { + dec_buf[--left] = '0' + (int_part % 10); + int_part /= 10; + } + } + + static if (g) size_t save_start = right; + + dec_buf[right++] = '.'; + + // creating frac part + static if (g) start = left + (found ? 0 : 1); + while (frac_part != 0 && right - start - 1 < f.precision) + { + frac_part *= 10; + static if (is(T == real) && real.mant_dig == 64) + { + if (tail_length > 0) + { + // together this is *= 10; + tail *= 5; + tail_length--; + + frac_part += tail >> tail_length; + if (tail_length > 0) + tail &= (1L << tail_length) - 1; + } + } + dec_buf[right++] = cast(byte)('0' + (frac_part >> (T.mant_dig - 1 - exp))); + + static if (g) + { + if (dec_buf[right - 1] != '0') + found = true; + else if (!found) + start++; + } + + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + } + + static if (g) start = save_start; + + if (frac_part == 0) + rc = RoundingClass.ZERO; + else + { + frac_part *= 10; + auto nextDigit = frac_part >> (T.mant_dig - 1 - exp); + frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); + + if (nextDigit == 5 && frac_part == 0) + rc = RoundingClass.FIVE; + else if (nextDigit >= 5) + rc = RoundingClass.UPPER; + else + rc = RoundingClass.LOWER; + } + } + + if (round(dec_buf, left, right, rc, sgn == "-")) left--; + + while (right > start + 1 && dec_buf[right - 1] == '0') right--; + + static if (g) + writeAligned(w, sgn, dec_buf[left .. start], dec_buf[start .. right], "", f, PrecisionType.allDigits); + else + writeAligned(w, sgn, dec_buf[left .. start], dec_buf[start .. right], "", f, PrecisionType.fractionalDigits); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + assert(printFloat(float.nan, f) == "nan"); + assert(printFloat(-float.nan, f) == "-nan"); + assert(printFloat(float.infinity, f) == "inf"); + assert(printFloat(-float.infinity, f) == "-inf"); + assert(printFloat(0.0f, f) == "0.000000"); + assert(printFloat(-0.0f, f) == "-0.000000"); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "0.000000"); + assert(printFloat(cast(float) -1e-40, f) == "-0.000000"); + assert(printFloat(1e-30f, f) == "0.000000"); + assert(printFloat(-1e-30f, f) == "-0.000000"); + assert(printFloat(1e-10f, f) == "0.000000"); + assert(printFloat(-1e-10f, f) == "-0.000000"); + assert(printFloat(0.1f, f) == "0.100000"); + assert(printFloat(-0.1f, f) == "-0.100000"); + assert(printFloat(10.0f, f) == "10.000000"); + assert(printFloat(-10.0f, f) == "-10.000000"); + assert(printFloat(1e30f, f) == "1000000015047466219876688855040.000000"); + assert(printFloat(-1e30f, f) == "-1000000015047466219876688855040.000000"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "0.000000"); + assert(printFloat(nextDown(-0.0f), f) == "-0.000000"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + f.width = 20; + f.precision = 10; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == " 0.0000000000"); + assert(printFloat(-0.0f, f) == " -0.0000000000"); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == " 0.0000000000"); + assert(printFloat(cast(float) -1e-40, f) == " -0.0000000000"); + assert(printFloat(1e-30f, f) == " 0.0000000000"); + assert(printFloat(-1e-30f, f) == " -0.0000000000"); + assert(printFloat(1e-10f, f) == " 0.0000000001"); + assert(printFloat(-1e-10f, f) == " -0.0000000001"); + assert(printFloat(0.1f, f) == " 0.1000000015"); + assert(printFloat(-0.1f, f) == " -0.1000000015"); + assert(printFloat(10.0f, f) == " 10.0000000000"); + assert(printFloat(-10.0f, f) == " -10.0000000000"); + assert(printFloat(1e30f, f) == "1000000015047466219876688855040.0000000000"); + assert(printFloat(-1e30f, f) == "-1000000015047466219876688855040.0000000000"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == " 0.0000000000"); + assert(printFloat(nextDown(-0.0f), f) == " -0.0000000000"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + f.width = 20; + f.precision = 10; + f.flDash = true; + + assert(printFloat(float.nan, f) == "nan "); + assert(printFloat(-float.nan, f) == "-nan "); + assert(printFloat(float.infinity, f) == "inf "); + assert(printFloat(-float.infinity, f) == "-inf "); + assert(printFloat(0.0f, f) == "0.0000000000 "); + assert(printFloat(-0.0f, f) == "-0.0000000000 "); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "0.0000000000 "); + assert(printFloat(cast(float) -1e-40, f) == "-0.0000000000 "); + assert(printFloat(1e-30f, f) == "0.0000000000 "); + assert(printFloat(-1e-30f, f) == "-0.0000000000 "); + assert(printFloat(1e-10f, f) == "0.0000000001 "); + assert(printFloat(-1e-10f, f) == "-0.0000000001 "); + assert(printFloat(0.1f, f) == "0.1000000015 "); + assert(printFloat(-0.1f, f) == "-0.1000000015 "); + assert(printFloat(10.0f, f) == "10.0000000000 "); + assert(printFloat(-10.0f, f) == "-10.0000000000 "); + assert(printFloat(1e30f, f) == "1000000015047466219876688855040.0000000000"); + assert(printFloat(-1e30f, f) == "-1000000015047466219876688855040.0000000000"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "0.0000000000 "); + assert(printFloat(nextDown(-0.0f), f) == "-0.0000000000 "); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + f.width = 20; + f.precision = 10; + f.flZero = true; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == "000000000.0000000000"); + assert(printFloat(-0.0f, f) == "-00000000.0000000000"); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "000000000.0000000000"); + assert(printFloat(cast(float) -1e-40, f) == "-00000000.0000000000"); + assert(printFloat(1e-30f, f) == "000000000.0000000000"); + assert(printFloat(-1e-30f, f) == "-00000000.0000000000"); + assert(printFloat(1e-10f, f) == "000000000.0000000001"); + assert(printFloat(-1e-10f, f) == "-00000000.0000000001"); + assert(printFloat(0.1f, f) == "000000000.1000000015"); + assert(printFloat(-0.1f, f) == "-00000000.1000000015"); + assert(printFloat(10.0f, f) == "000000010.0000000000"); + assert(printFloat(-10.0f, f) == "-00000010.0000000000"); + assert(printFloat(1e30f, f) == "1000000015047466219876688855040.0000000000"); + assert(printFloat(-1e30f, f) == "-1000000015047466219876688855040.0000000000"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "000000000.0000000000"); + assert(printFloat(nextDown(-0.0f), f) == "-00000000.0000000000"); +} + +@safe unittest +{ + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + f.precision = 0; + + fpctrl.rounding = FloatingPointControl.roundToNearest; + + /* + assert(printFloat(11.5f, f) == "12"); + assert(printFloat(12.5f, f) == "13"); + assert(printFloat(11.7f, f) == "12"); + assert(printFloat(11.3f, f) == "11"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-12"); + assert(printFloat(-12.5f, f) == "-13"); + assert(printFloat(-11.7f, f) == "-12"); + assert(printFloat(-11.3f, f) == "-11"); + assert(printFloat(-11.0f, f) == "-11"); + */ + + assert(printFloat(11.5f, f) == "12"); + assert(printFloat(12.5f, f) == "12"); + assert(printFloat(11.7f, f) == "12"); + assert(printFloat(11.3f, f) == "11"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-12"); + assert(printFloat(-12.5f, f) == "-12"); + assert(printFloat(-11.7f, f) == "-12"); + assert(printFloat(-11.3f, f) == "-11"); + assert(printFloat(-11.0f, f) == "-11"); + + fpctrl.rounding = FloatingPointControl.roundToZero; + + assert(printFloat(11.5f, f) == "11"); + assert(printFloat(12.5f, f) == "12"); + assert(printFloat(11.7f, f) == "11"); + assert(printFloat(11.3f, f) == "11"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-11"); + assert(printFloat(-12.5f, f) == "-12"); + assert(printFloat(-11.7f, f) == "-11"); + assert(printFloat(-11.3f, f) == "-11"); + assert(printFloat(-11.0f, f) == "-11"); + + fpctrl.rounding = FloatingPointControl.roundUp; + + assert(printFloat(11.5f, f) == "12"); + assert(printFloat(12.5f, f) == "13"); + assert(printFloat(11.7f, f) == "12"); + assert(printFloat(11.3f, f) == "12"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-11"); + assert(printFloat(-12.5f, f) == "-12"); + assert(printFloat(-11.7f, f) == "-11"); + assert(printFloat(-11.3f, f) == "-11"); + assert(printFloat(-11.0f, f) == "-11"); + + fpctrl.rounding = FloatingPointControl.roundDown; + + assert(printFloat(11.5f, f) == "11"); + assert(printFloat(12.5f, f) == "12"); + assert(printFloat(11.7f, f) == "11"); + assert(printFloat(11.3f, f) == "11"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-12"); + assert(printFloat(-12.5f, f) == "-13"); + assert(printFloat(-11.7f, f) == "-12"); + assert(printFloat(-11.3f, f) == "-12"); + assert(printFloat(-11.0f, f) == "-11"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + assert(printFloat(double.nan, f) == "nan"); + assert(printFloat(-double.nan, f) == "-nan"); + assert(printFloat(double.infinity, f) == "inf"); + assert(printFloat(-double.infinity, f) == "-inf"); + assert(printFloat(0.0, f) == "0.000000"); + assert(printFloat(-0.0, f) == "-0.000000"); + // / 1000 needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(1e-307 / 1000, f) == "0.000000"); + assert(printFloat(-1e-307 / 1000, f) == "-0.000000"); + assert(printFloat(1e-30, f) == "0.000000"); + assert(printFloat(-1e-30, f) == "-0.000000"); + assert(printFloat(1e-10, f) == "0.000000"); + assert(printFloat(-1e-10, f) == "-0.000000"); + assert(printFloat(0.1, f) == "0.100000"); + assert(printFloat(-0.1, f) == "-0.100000"); + assert(printFloat(10.0, f) == "10.000000"); + assert(printFloat(-10.0, f) == "-10.000000"); + assert(printFloat(1e300, f) == + "100000000000000005250476025520442024870446858110815915491585411551180245798890819578637137508044786" + ~"404370444383288387817694252323536043057564479218478670698284838720092657580373783023379478809005936" + ~"895323497079994508111903896764088007465274278014249457925878882005684283811566947219638686545940054" + ~"0160.000000"); + assert(printFloat(-1e300, f) == + "-100000000000000005250476025520442024870446858110815915491585411551180245798890819578637137508044786" + ~"404370444383288387817694252323536043057564479218478670698284838720092657580373783023379478809005936" + ~"895323497079994508111903896764088007465274278014249457925878882005684283811566947219638686545940054" + ~"0160.000000"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0), f) == "0.000000"); + assert(printFloat(nextDown(-0.0), f) == "-0.000000"); +} + +@safe unittest +{ + static if (real.mant_dig > 64) + { + pragma(msg, "printFloat tests disabled because of unsupported `real` format"); + } + else + { + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + assert(printFloat(real.nan, f) == "nan"); + assert(printFloat(-real.nan, f) == "-nan"); + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(-real.infinity, f) == "-inf"); + assert(printFloat(0.0L, f) == "0.000000"); + assert(printFloat(-0.0L, f) == "-0.000000"); + } + + static if (real.mant_dig == 64) + { + assert(printFloat(1e-4940L, f) == "0.000000"); + assert(printFloat(-1e-4940L, f) == "-0.000000"); + assert(printFloat(1e-30L, f) == "0.000000"); + assert(printFloat(-1e-30L, f) == "-0.000000"); + assert(printFloat(1e-10L, f) == "0.000000"); + assert(printFloat(-1e-10L, f) == "-0.000000"); + assert(printFloat(0.1L, f) == "0.100000"); + assert(printFloat(-0.1L, f) == "-0.100000"); + assert(printFloat(10.0L, f) == "10.000000"); + assert(printFloat(-10.0L, f) == "-10.000000"); + version (Windows) {} // issue 20972 + else + { + auto result1 = printFloat(1e4000L, f); + assert(result1.length == 4007 && result1[0 .. 40] == "9999999999999999999965463873099623784932"); + auto result2 = printFloat(-1e4000L, f); + assert(result2.length == 4008 && result2[0 .. 40] == "-999999999999999999996546387309962378493"); + } + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0L), f) == "0.000000"); + assert(printFloat(nextDown(-0.0L), f) == "-0.000000"); + } +} + +@safe unittest +{ + import std.exception : assertCTFEable; + import std.math.exponential : log2; + import std.math.operations : nextDown; + + assertCTFEable!( + { + // log2 is broken for x87-reals on some computers in CTFE + // the following tests excludes these computers from the tests + // (issue 21757) + enum test = cast(int) log2(3.05e2312L); + static if (real.mant_dig == 64 && test == 7681) + { + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(10.0L, f) == "10.000000"); + assert(printFloat(2.6080L, f) == "2.608000"); + auto result1 = printFloat(3.05e2312L, f); + assert(result1.length == 2320); + assert(result1[0 .. 20] == "30499999999999999999"); + + f.precision = 60; + assert(printFloat(2.65e-54L, f) == + "0.000000000000000000000000000000000000000000000000000002650000"); + + /* + commented out, because CTFE is currently too slow for 5000 digits with extreme values + + f.precision = 5000; + auto result2 = printFloat(1.2119e-4822L, f); + assert(result2.length == 5002); + assert(result2[$ - 20 .. $] == "60076763752233836613"); + auto result3 = printFloat(real.min_normal, f); + assert(result3.length == 5002); + assert(result3[$ - 20 .. $] == "47124010882722980874"); + auto result4 = printFloat(real.min_normal.nextDown, f); + assert(result4.length == 5002); + assert(result4[$ - 20 .. $] == "52925846892214823939"); + */ + } + }); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + + import std.math.operations : nextUp; + + double eps = nextUp(0.0); + f.precision = 1000; + assert(printFloat(eps, f) == + "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ~"00000000000000000000000000000049406564584124654417656879286822137236505980261432476442558568250067" + ~"55072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131" + ~"90311404527845817167848982103688718636056998730723050006387409153564984387312473397273169615140031" + ~"71538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012" + ~"97099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107" + ~"49170333222684475333572083243193609238289345836806010601150616980975307834227731832924790498252473" + ~"07763759272478746560847782037344696995336470179726777175851256605511991315048911014510378627381672" + ~"509558373897335989937"); + + f.precision = 0; + assert(printFloat(double.max, f) == + "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878" + ~"17154045895351438246423432132688946418276846754670353751698604991057655128207624549009038932894407" + ~"58685084551339423045832369032229481658085593321233482747978262041447231687381771809192998812504040" + ~"26184124858368"); + + f.precision = 50; + assert(printFloat(double.epsilon, f) == + "0.00000000000000022204460492503130808472633361816406"); + + f.precision = 10; + assert(printFloat(1.0/3.0, f) == "0.3333333333"); + assert(printFloat(1.0/7.0, f) == "0.1428571429"); + assert(printFloat(1.0/9.0, f) == "0.1111111111"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + f.precision = 15; + + import std.math.constants : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, + LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; + + assert(printFloat(cast(double) E, f) == "2.718281828459045"); + assert(printFloat(cast(double) PI, f) == "3.141592653589793"); + assert(printFloat(cast(double) PI_2, f) == "1.570796326794897"); + assert(printFloat(cast(double) PI_4, f) == "0.785398163397448"); + assert(printFloat(cast(double) M_1_PI, f) == "0.318309886183791"); + assert(printFloat(cast(double) M_2_PI, f) == "0.636619772367581"); + assert(printFloat(cast(double) M_2_SQRTPI, f) == "1.128379167095513"); + assert(printFloat(cast(double) LN10, f) == "2.302585092994046"); + assert(printFloat(cast(double) LN2, f) == "0.693147180559945"); + assert(printFloat(cast(double) LOG2, f) == "0.301029995663981"); + assert(printFloat(cast(double) LOG2E, f) == "1.442695040888963"); + assert(printFloat(cast(double) LOG2T, f) == "3.321928094887362"); + assert(printFloat(cast(double) LOG10E, f) == "0.434294481903252"); + assert(printFloat(cast(double) SQRT2, f) == "1.414213562373095"); + assert(printFloat(cast(double) SQRT1_2, f) == "0.707106781186548"); +} + +// for 100% coverage +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'f'; + f.precision = 1; + assert(printFloat(9.99, f) == "10.0"); + + import std.math.operations : nextUp; + + float eps = nextUp(0.0f); + + f.precision = 148; + assert(printFloat(eps, f) == + "0.0000000000000000000000000000000000000000000014012984643248170709237295832899161312802619418765157" + ~"717570682838897910826858606014866381883621215820312"); + + f.precision = 149; + assert(printFloat(eps, f) == + "0.0000000000000000000000000000000000000000000014012984643248170709237295832899161312802619418765157" + ~"7175706828388979108268586060148663818836212158203125"); +} + +private void printFloatG(Writer, T, Char)(auto ref Writer w, T val, + FormatSpec!Char f, string sgn, int exp, ulong mnt, bool is_upper) +if (is(T == float) || is(T == double) + || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) +{ + import core.math : abs = fabs; + + if (f.precision == f.UNSPECIFIED) + f.precision = 6; + + if (f.precision == 0) + f.precision = 1; + + import std.math.hardware; + import std.format.internal.write : RoundingMode; + + auto rm = RoundingMode.toNearestTiesToEven; + + if (!__ctfe) + { + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + switch (FloatingPointControl.rounding) + { + case FloatingPointControl.roundUp: + rm = RoundingMode.up; + break; + case FloatingPointControl.roundDown: + rm = RoundingMode.down; + break; + case FloatingPointControl.roundToZero: + rm = RoundingMode.toZero; + break; + case FloatingPointControl.roundToNearest: + rm = RoundingMode.toNearestTiesToEven; + break; + default: assert(false, "Unknown floating point rounding mode"); + } + } + } + + bool useE = false; + + final switch (rm) + { + case RoundingMode.up: + useE = abs(val) >= 10.0 ^^ f.precision - (val > 0 ? 1 : 0) + || abs(val) < 0.0001 - (val > 0 ? (10.0 ^^ (-4 - f.precision)) : 0); + break; + case RoundingMode.down: + useE = abs(val) >= 10.0 ^^ f.precision - (val < 0 ? 1 : 0) + || abs(val) < 0.0001 - (val < 0 ? (10.0 ^^ (-4 - f.precision)) : 0); + break; + case RoundingMode.toZero: + useE = abs(val) >= 10.0 ^^ f.precision + || abs(val) < 0.0001; + break; + case RoundingMode.toNearestTiesToEven: + case RoundingMode.toNearestTiesAwayFromZero: + useE = abs(val) >= 10.0 ^^ f.precision - 0.5 + || abs(val) < 0.0001 - 0.5 * (10.0 ^^ (-4 - f.precision)); + break; + } + + if (useE) + return printFloatE!true(w, val, f, sgn, exp, mnt, is_upper); + else + return printFloatF!true(w, val, f, sgn, exp, mnt, is_upper); +} + +@safe unittest +{ + // This one tests the switch between e-like and f-like output. + // There is a small gap left between the two, where the used + // variation is not clearly defined. This is intentional and due + // to the way, D handles floating point numbers. On different + // computers with different reals the results may vary in this gap. + + import std.math.operations : nextDown, nextUp; + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + + double val = 999999.5; + assert(printFloat(val.nextUp, f) == "1e+06"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "999999"); + + val = 0.00009999995; + assert(printFloat(val.nextUp, f) == "0.0001"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "9.99999e-05"); + + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundToZero; + + val = 1000000; + assert(printFloat(val.nextUp, f) == "1e+06"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "999999"); + + val = 0.0001; + assert(printFloat(val.nextUp, f) == "0.0001"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "9.99999e-05"); + + fpctrl.rounding = FloatingPointControl.roundUp; + + val = 999999; + assert(printFloat(val.nextUp, f) == "1e+06"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "999999"); + + // 0.0000999999 is actually represented as 0.0000999998999..., which is + // less than 0.0000999999, so we need to use nextUp to get the corner case here + val = nextUp(0.0000999999); + assert(printFloat(val.nextUp, f) == "0.0001"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "9.99999e-05"); + + fpctrl.rounding = FloatingPointControl.roundDown; + + val = 1000000; + assert(printFloat(val.nextUp, f) == "1e+06"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "999999"); + + val = 0.0001; + assert(printFloat(val.nextUp, f) == "0.0001"); + val = nextDown(val); + assert(printFloat(val.nextDown, f) == "9.99999e-05"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + assert(printFloat(float.nan, f) == "nan"); + assert(printFloat(-float.nan, f) == "-nan"); + assert(printFloat(float.infinity, f) == "inf"); + assert(printFloat(-float.infinity, f) == "-inf"); + assert(printFloat(0.0f, f) == "0"); + assert(printFloat(-0.0f, f) == "-0"); + + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "9.99995e-41"); + assert(printFloat(cast(float) -1e-40, f) == "-9.99995e-41"); + assert(printFloat(1e-30f, f) == "1e-30"); + assert(printFloat(-1e-30f, f) == "-1e-30"); + assert(printFloat(1e-10f, f) == "1e-10"); + assert(printFloat(-1e-10f, f) == "-1e-10"); + assert(printFloat(0.1f, f) == "0.1"); + assert(printFloat(-0.1f, f) == "-0.1"); + assert(printFloat(10.0f, f) == "10"); + assert(printFloat(-10.0f, f) == "-10"); + assert(printFloat(1e30f, f) == "1e+30"); + assert(printFloat(-1e30f, f) == "-1e+30"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "1.4013e-45"); + assert(printFloat(nextDown(-0.0f), f) == "-1.4013e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + f.width = 20; + f.precision = 10; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == " 0"); + assert(printFloat(-0.0f, f) == " -0"); + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == " 9.999946101e-41"); + assert(printFloat(cast(float) -1e-40, f) == " -9.999946101e-41"); + assert(printFloat(1e-30f, f) == " 1.000000003e-30"); + assert(printFloat(-1e-30f, f) == " -1.000000003e-30"); + assert(printFloat(1e-10f, f) == " 1.000000013e-10"); + assert(printFloat(-1e-10f, f) == " -1.000000013e-10"); + assert(printFloat(0.1f, f) == " 0.1000000015"); + assert(printFloat(-0.1f, f) == " -0.1000000015"); + assert(printFloat(10.0f, f) == " 10"); + assert(printFloat(-10.0f, f) == " -10"); + assert(printFloat(1e30f, f) == " 1.000000015e+30"); + assert(printFloat(-1e30f, f) == " -1.000000015e+30"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == " 1.401298464e-45"); + assert(printFloat(nextDown(-0.0f), f) == " -1.401298464e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + f.width = 20; + f.precision = 10; + f.flDash = true; + + assert(printFloat(float.nan, f) == "nan "); + assert(printFloat(-float.nan, f) == "-nan "); + assert(printFloat(float.infinity, f) == "inf "); + assert(printFloat(-float.infinity, f) == "-inf "); + assert(printFloat(0.0f, f) == "0 "); + assert(printFloat(-0.0f, f) == "-0 "); + + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "9.999946101e-41 "); + assert(printFloat(cast(float) -1e-40, f) == "-9.999946101e-41 "); + assert(printFloat(1e-30f, f) == "1.000000003e-30 "); + assert(printFloat(-1e-30f, f) == "-1.000000003e-30 "); + assert(printFloat(1e-10f, f) == "1.000000013e-10 "); + assert(printFloat(-1e-10f, f) == "-1.000000013e-10 "); + assert(printFloat(0.1f, f) == "0.1000000015 "); + assert(printFloat(-0.1f, f) == "-0.1000000015 "); + assert(printFloat(10.0f, f) == "10 "); + assert(printFloat(-10.0f, f) == "-10 "); + assert(printFloat(1e30f, f) == "1.000000015e+30 "); + assert(printFloat(-1e30f, f) == "-1.000000015e+30 "); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "1.401298464e-45 "); + assert(printFloat(nextDown(-0.0f), f) == "-1.401298464e-45 "); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + f.width = 20; + f.precision = 10; + f.flZero = true; + + assert(printFloat(float.nan, f) == " nan"); + assert(printFloat(-float.nan, f) == " -nan"); + assert(printFloat(float.infinity, f) == " inf"); + assert(printFloat(-float.infinity, f) == " -inf"); + assert(printFloat(0.0f, f) == "00000000000000000000"); + assert(printFloat(-0.0f, f) == "-0000000000000000000"); + + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "000009.999946101e-41"); + assert(printFloat(cast(float) -1e-40, f) == "-00009.999946101e-41"); + assert(printFloat(1e-30f, f) == "000001.000000003e-30"); + assert(printFloat(-1e-30f, f) == "-00001.000000003e-30"); + assert(printFloat(1e-10f, f) == "000001.000000013e-10"); + assert(printFloat(-1e-10f, f) == "-00001.000000013e-10"); + assert(printFloat(0.1f, f) == "000000000.1000000015"); + assert(printFloat(-0.1f, f) == "-00000000.1000000015"); + assert(printFloat(10.0f, f) == "00000000000000000010"); + assert(printFloat(-10.0f, f) == "-0000000000000000010"); + assert(printFloat(1e30f, f) == "000001.000000015e+30"); + assert(printFloat(-1e30f, f) == "-00001.000000015e+30"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "000001.401298464e-45"); + assert(printFloat(nextDown(-0.0f), f) == "-00001.401298464e-45"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + f.precision = 10; + f.flHash = true; + + assert(printFloat(float.nan, f) == "nan"); + assert(printFloat(-float.nan, f) == "-nan"); + assert(printFloat(float.infinity, f) == "inf"); + assert(printFloat(-float.infinity, f) == "-inf"); + assert(printFloat(0.0f, f) == "0.000000000"); + assert(printFloat(-0.0f, f) == "-0.000000000"); + + // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(cast(float) 1e-40, f) == "9.999946101e-41"); + assert(printFloat(cast(float) -1e-40, f) == "-9.999946101e-41"); + assert(printFloat(1e-30f, f) == "1.000000003e-30"); + assert(printFloat(-1e-30f, f) == "-1.000000003e-30"); + assert(printFloat(1e-10f, f) == "1.000000013e-10"); + assert(printFloat(-1e-10f, f) == "-1.000000013e-10"); + assert(printFloat(0.1f, f) == "0.1000000015"); + assert(printFloat(-0.1f, f) == "-0.1000000015"); + assert(printFloat(10.0f, f) == "10.00000000"); + assert(printFloat(-10.0f, f) == "-10.00000000"); + assert(printFloat(1e30f, f) == "1.000000015e+30"); + assert(printFloat(-1e30f, f) == "-1.000000015e+30"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0f), f) == "1.401298464e-45"); + assert(printFloat(nextDown(-0.0f), f) == "-1.401298464e-45"); +} + +@safe unittest +{ + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + char[256] buf; + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + f.precision = 2; + + fpctrl.rounding = FloatingPointControl.roundToNearest; + + /* + assert(printFloat(11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "12"); + assert(printFloat(12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "13"); + assert(printFloat(11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "12"); + assert(printFloat(11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "11"); + assert(printFloat(11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "11"); + assert(printFloat(-11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-12"); + assert(printFloat(-12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-13"); + assert(printFloat(-11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "-12"); + assert(printFloat(-11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "-11"); + assert(printFloat(-11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "-11"); + */ + + // ties to even + assert(printFloat(11.5f, f) == "12"); + assert(printFloat(12.5f, f) == "12"); + assert(printFloat(11.7f, f) == "12"); + assert(printFloat(11.3f, f) == "11"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-12"); + assert(printFloat(-12.5f, f) == "-12"); + assert(printFloat(-11.7f, f) == "-12"); + assert(printFloat(-11.3f, f) == "-11"); + assert(printFloat(-11.0f, f) == "-11"); + + fpctrl.rounding = FloatingPointControl.roundToZero; + + assert(printFloat(11.5f, f) == "11"); + assert(printFloat(12.5f, f) == "12"); + assert(printFloat(11.7f, f) == "11"); + assert(printFloat(11.3f, f) == "11"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-11"); + assert(printFloat(-12.5f, f) == "-12"); + assert(printFloat(-11.7f, f) == "-11"); + assert(printFloat(-11.3f, f) == "-11"); + assert(printFloat(-11.0f, f) == "-11"); + + fpctrl.rounding = FloatingPointControl.roundUp; + + assert(printFloat(11.5f, f) == "12"); + assert(printFloat(12.5f, f) == "13"); + assert(printFloat(11.7f, f) == "12"); + assert(printFloat(11.3f, f) == "12"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-11"); + assert(printFloat(-12.5f, f) == "-12"); + assert(printFloat(-11.7f, f) == "-11"); + assert(printFloat(-11.3f, f) == "-11"); + assert(printFloat(-11.0f, f) == "-11"); + + fpctrl.rounding = FloatingPointControl.roundDown; + + assert(printFloat(11.5f, f) == "11"); + assert(printFloat(12.5f, f) == "12"); + assert(printFloat(11.7f, f) == "11"); + assert(printFloat(11.3f, f) == "11"); + assert(printFloat(11.0f, f) == "11"); + assert(printFloat(-11.5f, f) == "-12"); + assert(printFloat(-12.5f, f) == "-13"); + assert(printFloat(-11.7f, f) == "-12"); + assert(printFloat(-11.3f, f) == "-12"); + assert(printFloat(-11.0f, f) == "-11"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + + assert(printFloat(double.nan, f) == "nan"); + assert(printFloat(-double.nan, f) == "-nan"); + assert(printFloat(double.infinity, f) == "inf"); + assert(printFloat(-double.infinity, f) == "-inf"); + assert(printFloat(0.0, f) == "0"); + assert(printFloat(-0.0, f) == "-0"); + + // / 1000 needed due to https://issues.dlang.org/show_bug.cgi?id=20361 + assert(printFloat(1e-307 / 1000, f) == "1e-310"); + assert(printFloat(-1e-307 / 1000, f) == "-1e-310"); + assert(printFloat(1e-30, f) == "1e-30"); + assert(printFloat(-1e-30, f) == "-1e-30"); + assert(printFloat(1e-10, f) == "1e-10"); + assert(printFloat(-1e-10, f) == "-1e-10"); + assert(printFloat(0.1, f) == "0.1"); + assert(printFloat(-0.1, f) == "-0.1"); + assert(printFloat(10.0, f) == "10"); + assert(printFloat(-10.0, f) == "-10"); + assert(printFloat(1e300, f) == "1e+300"); + assert(printFloat(-1e300, f) == "-1e+300"); + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0), f) == "4.94066e-324"); + assert(printFloat(nextDown(-0.0), f) == "-4.94066e-324"); +} + +@safe unittest +{ + static if (real.mant_dig > 64) + { + pragma(msg, "printFloat tests disabled because of unsupported `real` format"); + } + else + { + char[256] buf; + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + + assert(printFloat(real.nan, f) == "nan"); + assert(printFloat(-real.nan, f) == "-nan"); + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(-real.infinity, f) == "-inf"); + } +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + + import std.math.operations : nextUp; + + double eps = nextUp(0.0); + f.precision = 1000; + assert(printFloat(eps, f) == + "4.940656458412465441765687928682213723650598026143247644255856825006" + ~ "755072702087518652998363616359923797965646954457177309266567103559" + ~ "397963987747960107818781263007131903114045278458171678489821036887" + ~ "186360569987307230500063874091535649843873124733972731696151400317" + ~ "153853980741262385655911710266585566867681870395603106249319452715" + ~ "914924553293054565444011274801297099995419319894090804165633245247" + ~ "571478690147267801593552386115501348035264934720193790268107107491" + ~ "703332226844753335720832431936092382893458368060106011506169809753" + ~ "078342277318329247904982524730776375927247874656084778203734469699" + ~ "533647017972677717585125660551199131504891101451037862738167250955" + ~ "837389733598993664809941164205702637090279242767544565229087538682" + ~ "506419718265533447265625e-324"); + + f.precision = 50; + assert(printFloat(double.max, f) == + "1.7976931348623157081452742373170435679807056752584e+308"); + assert(printFloat(double.epsilon, f) == + "2.220446049250313080847263336181640625e-16"); + + f.precision = 10; + assert(printFloat(1.0/3.0, f) == "0.3333333333"); + assert(printFloat(1.0/7.0, f) == "0.1428571429"); + assert(printFloat(1.0/9.0, f) == "0.1111111111"); +} + +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + f.precision = 15; + + import std.math.constants : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, + LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; + + assert(printFloat(cast(double) E, f) == "2.71828182845905"); + assert(printFloat(cast(double) PI, f) == "3.14159265358979"); + assert(printFloat(cast(double) PI_2, f) == "1.5707963267949"); + assert(printFloat(cast(double) PI_4, f) == "0.785398163397448"); + assert(printFloat(cast(double) M_1_PI, f) == "0.318309886183791"); + assert(printFloat(cast(double) M_2_PI, f) == "0.636619772367581"); + assert(printFloat(cast(double) M_2_SQRTPI, f) == "1.12837916709551"); + assert(printFloat(cast(double) LN10, f) == "2.30258509299405"); + assert(printFloat(cast(double) LN2, f) == "0.693147180559945"); + assert(printFloat(cast(double) LOG2, f) == "0.301029995663981"); + assert(printFloat(cast(double) LOG2E, f) == "1.44269504088896"); + assert(printFloat(cast(double) LOG2T, f) == "3.32192809488736"); + assert(printFloat(cast(double) LOG10E, f) == "0.434294481903252"); + assert(printFloat(cast(double) SQRT2, f) == "1.4142135623731"); + assert(printFloat(cast(double) SQRT1_2, f) == "0.707106781186548"); +} + +// for 100% coverage +@safe unittest +{ + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + f.precision = 0; + + assert(printFloat(0.009999, f) == "0.01"); +} + +@safe unittest +{ + static if (real.mant_dig > 64) + { + pragma(msg, "printFloat tests disabled because of unsupported `real` format"); + } + else + { + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + assert(printFloat(real.nan, f) == "nan"); + assert(printFloat(-real.nan, f) == "-nan"); + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(-real.infinity, f) == "-inf"); + assert(printFloat(0.0L, f) == "0"); + assert(printFloat(-0.0L, f) == "-0"); + } + + static if (real.mant_dig == 64) + { + assert(printFloat(1e-4940L, f) == "1e-4940"); + assert(printFloat(-1e-4940L, f) == "-1e-4940"); + assert(printFloat(1e-30L, f) == "1e-30"); + assert(printFloat(-1e-30L, f) == "-1e-30"); + assert(printFloat(1e-10L, f) == "1e-10"); + assert(printFloat(-1e-10L, f) == "-1e-10"); + assert(printFloat(0.1L, f) == "0.1"); + assert(printFloat(-0.1L, f) == "-0.1"); + assert(printFloat(10.0L, f) == "10"); + assert(printFloat(-10.0L, f) == "-10"); + version (Windows) {} // issue 20972 + else + { + assert(printFloat(1e4000L, f) == "1e+4000"); + assert(printFloat(-1e4000L, f) == "-1e+4000"); + } + + import std.math.operations : nextUp, nextDown; + assert(printFloat(nextUp(0.0L), f) == "3.6452e-4951"); + assert(printFloat(nextDown(-0.0L), f) == "-3.6452e-4951"); + } +} + +@safe unittest +{ + import std.exception : assertCTFEable; + import std.math.exponential : log2; + import std.math.operations : nextDown; + + assertCTFEable!( + { + // log2 is broken for x87-reals on some computers in CTFE + // the following tests excludes these computers from the tests + // (issue 21757) + enum test = cast(int) log2(3.05e2312L); + static if (real.mant_dig == 64 && test == 7681) + { + auto f = FormatSpec!dchar(""); + f.spec = 'g'; + assert(printFloat(real.infinity, f) == "inf"); + assert(printFloat(10.0L, f) == "10"); + assert(printFloat(2.6080L, f) == "2.608"); + assert(printFloat(3.05e2312L, f) == "3.05e+2312"); + + f.precision = 60; + assert(printFloat(2.65e-54L, f) == + "2.65000000000000000005900998740054701394102894093529654759941e-54"); + + /* + commented out, because CTFE is currently too slow for 5000 digits with extreme values + + f.precision = 5000; + auto result2 = printFloat(1.2119e-4822L, f); + assert(result2.length == 5007); + assert(result2[$ - 20 .. $] == "26072948659534e-4822"); + auto result3 = printFloat(real.min_normal, f); + assert(result3.length == 5007); + assert(result3[$ - 20 .. $] == "72078141008227e-4932"); + auto result4 = printFloat(real.min_normal.nextDown, f); + assert(result4.length == 5007); + assert(result4[$ - 20 .. $] == "48141326333101e-4932"); + */ + } + }); +} + +// check no allocations +@safe unittest +{ + import std.format : NoOpSink; + auto w = NoOpSink(); + + import core.memory; + auto stats = () @trusted { return GC.stats; } (); + + auto f = FormatSpec!dchar(""); + f.spec = 'a'; + printFloat(w, float.nan, f); + printFloat(w, -float.infinity, f); + printFloat(w, 0.0f, f); + + printFloat(w, -double.nan, f); + printFloat(w, double.infinity, f); + printFloat(w, -0.0, f); + + import std.math.operations : nextUp; + import std.math.constants : E; + + printFloat(w, nextUp(0.0f), f); + printFloat(w, cast(float) E, f); + + f.precision = 1000; + printFloat(w, float.nan, f); + printFloat(w, 0.0, f); + printFloat(w, 1.23456789e+100, f); + + f.spec = 'E'; + f.precision = 80; + printFloat(w, 5.62776e+12f, f); + + f.precision = 6; + printFloat(w, -1.1418613e+07f, f); + + f.precision = 20; + printFloat(w, double.max, f); + printFloat(w, nextUp(0.0), f); + + f.precision = 1000; + printFloat(w, 1.0, f); + + f.spec = 'f'; + f.precision = 15; + printFloat(w, cast(double) E, f); + + f.precision = 20; + printFloat(w, double.max, f); + printFloat(w, nextUp(0.0), f); + + f.precision = 1000; + printFloat(w, 1.0, f); + + f.spec = 'g'; + f.precision = 15; + printFloat(w, cast(double) E, f); + + f.precision = 20; + printFloat(w, double.max, f); + printFloat(w, nextUp(0.0), f); + + f.flHash = true; + f.precision = 1000; + printFloat(w, 1.0, f); + + assert(() @trusted { return GC.stats.usedSize; } () == stats.usedSize); +} diff --git a/libphobos/src/std/format/internal/read.d b/libphobos/src/std/format/internal/read.d new file mode 100644 index 0000000..102e59f --- /dev/null +++ b/libphobos/src/std/format/internal/read.d @@ -0,0 +1,410 @@ +// Written in the D programming language. + +/* + Copyright: Copyright The D Language Foundation 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/internal/read.d) + */ +module std.format.internal.read; + +import std.range.primitives : ElementEncodingType, ElementType, isInputRange; + +import std.traits : isAggregateType, isArray, isAssociativeArray, + isDynamicArray, isFloatingPoint, isIntegral, isSomeChar, isSomeString, + isStaticArray, StringTypeOf; + +import std.format.spec : FormatSpec; + +package(std.format): + +void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) +{ + import std.ascii : isDigit; + import std.conv : text; + import std.range.primitives : empty, front, popFront; + + 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 = ""; +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) +if (isInputRange!Range && is(immutable T == immutable bool)) +{ + import std.algorithm.searching : find; + import std.conv : parse, text; + import std.format : enforceFmt, unformatValue; + + if (spec.spec == 's') return parse!T(input); + + enforceFmt(find(acceptedSpecs!long, spec.spec).length, + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return unformatValue!long(input, spec) != 0; +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) +if (isInputRange!Range && is(T == typeof(null))) +{ + import std.conv : parse, text; + import std.format : enforceFmt; + + enforceFmt(spec.spec == 's', + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return parse!T(input); +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope 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; + import std.format : enforceFmt, FormatException; + + if (spec.spec == 'r') + { + static if (is(immutable ElementEncodingType!Range == immutable char) + || is(immutable ElementEncodingType!Range == immutable byte) + || is(immutable ElementEncodingType!Range == immutable ubyte)) + return rawRead!T(input); + else + throw new FormatException( + "The raw read specifier %r may only be used with narrow strings and ranges of bytes." + ); + } + + enforceFmt(find(acceptedSpecs!T, spec.spec).length, + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + enforceFmt(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, "base must be not equal to zero"); + + return parse!T(input, base); + +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope 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; + import std.format : enforceFmt, FormatException; + + if (spec.spec == 'r') + { + static if (is(immutable ElementEncodingType!Range == immutable char) + || is(immutable ElementEncodingType!Range == immutable byte) + || is(immutable ElementEncodingType!Range == immutable ubyte)) + return rawRead!T(input); + else + throw new FormatException( + "The raw read specifier %r may only be used with narrow strings and ranges of bytes." + ); + } + + enforceFmt(find(acceptedSpecs!T, spec.spec).length, + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return parse!T(input); +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope 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; + import std.range.primitives : empty, front, popFront; + import std.format : enforceFmt, unformatValue; + + if (spec.spec == 's' || spec.spec == 'c') + { + auto result = to!T(input.front); + input.popFront(); + return result; + } + + enforceFmt(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(false, T.stringof ~ ".sizeof must be 1, 2, or 4 not " ~ + to!string(T.sizeof)); +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) +if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) +{ + import std.conv : text; + import std.range.primitives : empty, front, popFront, put; + import std.format : enforceFmt; + + const spec = fmt.spec; + if (spec == '(') + { + return unformatRange!T(input, fmt); + } + enforceFmt(spec == 's', + text("Wrong unformat specifier '%", spec , "' for ", T.stringof)); + + static if (isStaticArray!T) + { + T result; + auto app = result[]; + } + else + { + import std.array : appender; + auto app = appender!T(); + } + if (fmt.trailing.empty) + { + for (; !input.empty; input.popFront()) + { + static if (isStaticArray!T) + if (app.empty) + break; + app.put(input.front); + } + } + else + { + immutable end = fmt.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) + { + enforceFmt(app.empty, "need more input"); + return result; + } + else + return app.data; +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) +if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T + && !is(T == enum)) +{ + import std.conv : parse, text; + import std.format : enforceFmt; + + const spec = fmt.spec; + if (spec == '(') + { + return unformatRange!T(input, fmt); + } + + enforceFmt(spec == 's', + text("Wrong unformat specifier '%", spec , "' for ", T.stringof)); + + return parse!T(input); +} + +T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) +if (isInputRange!Range && isAssociativeArray!T && !is(T == enum)) +{ + import std.conv : parse, text; + import std.format : enforceFmt; + + const spec = fmt.spec; + if (spec == '(') + { + return unformatRange!T(input, fmt); + } + + enforceFmt(spec == 's', + text("Wrong unformat specifier '%", 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(immutable ElementEncodingType!Range == immutable char) + || is(immutable ElementEncodingType!Range == immutable byte) + || is(immutable ElementEncodingType!Range == immutable ubyte)) +{ + import std.range.primitives : popFront; + + 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; +} + +private T unformatRange(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) +in (spec.spec == '(', "spec.spec must be '(' not " ~ spec.spec) +{ + import std.range.primitives : empty, front, popFront; + import std.format : enforceFmt, format; + + 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; + } + } + + bool checkEnd() + { + return input.empty || !cont.empty && input.front == cont.front; + } + + if (!checkEnd()) + { + for (;;) + { + auto fmt = FormatSpec!Char(spec.nested); + fmt.readUpToNextSpec(input); + enforceFmt(!input.empty, "Unexpected end of input when parsing range"); + + static if (isStaticArray!T) + { + result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt); + } + else static if (isDynamicArray!T) + { + import std.conv : WideElementType; + result ~= unformatElement!(WideElementType!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); + } + + static if (isStaticArray!T) + { + enforceFmt(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; + + if (checkEnd()) + break; + + if (!sep.empty && input.front == sep.front) + { + while (!sep.empty) + { + enforceFmt(!input.empty, + "Unexpected end of input when parsing range separator"); + enforceFmt(input.front == sep.front, + "Unexpected character when parsing range separator"); + input.popFront(); + sep.popFront(); + } + } + } + } + static if (isStaticArray!T) + { + enforceFmt(i == T.length, + "Too few (%d) format specifiers for static array of length %d".format(i, T.length)); + } + return result; +} + +T unformatElement(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) +if (isInputRange!Range) +{ + import std.conv : parseElement; + import std.format.read : unformatValue; + + 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); +} diff --git a/libphobos/src/std/format/internal/write.d b/libphobos/src/std/format/internal/write.d new file mode 100644 index 0000000..5883d8d --- /dev/null +++ b/libphobos/src/std/format/internal/write.d @@ -0,0 +1,3980 @@ +// Written in the D programming language. + +/* + Copyright: Copyright The D Language Foundation 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/internal/write.d) + */ +module std.format.internal.write; + +import std.format.spec : FormatSpec; +import std.range.primitives : isInputRange; +import std.traits; + +version (StdUnittest) +{ + import std.exception : assertCTFEable; + import std.format : format; +} + +package(std.format): + +/* + `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or + `0` with integral-specific format specs. + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + BooleanTypeOf!T val = obj; + + if (f.spec == 's') + writeAligned(w, val ? "true" : "false", f); + else + formatValueImpl(w, cast(byte) val, f); +} + +@safe pure unittest +{ + assertCTFEable!( + { + formatTest(false, "false"); + formatTest(true, "true"); + }); +} + +@safe 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"; } + } + + () @trusted { + 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]"); +} + +// https://issues.dlang.org/show_bug.cgi?id=20534 +@safe pure unittest +{ + assert(format("%r",false) == "\0"); +} + +@safe pure unittest +{ + assert(format("%07s",true) == " true"); +} + +@safe pure unittest +{ + assert(format("%=8s",true) == " true "); + assert(format("%=9s",false) == " false "); + assert(format("%=9s",true) == " true "); + assert(format("%-=9s",true) == " true "); + assert(format("%=10s",false) == " false "); + assert(format("%-=10s",false) == " false "); +} + +/* + `null` literal is formatted as `"null"` + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(immutable T == immutable typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.format : enforceFmt; + + const spec = f.spec; + enforceFmt(spec == 's', "null literal cannot match %" ~ spec); + + writeAligned(w, "null", f); +} + +@safe pure unittest +{ + import std.exception : collectExceptionMsg; + import std.format : FormatException; + import std.range.primitives : back; + + assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p'); + + assertCTFEable!( + { + formatTest(null, "null"); + }); +} + +@safe pure unittest +{ + string t = format("[%6s] [%-6s]", null, null); + assert(t == "[ null] [null ]"); +} + +/* + Integrals are formatted like $(REF printf, core, stdc, stdio). + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.range.primitives : put; + + 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' || f.spec == 'a' || f.spec == 'A' ? 16 : + f.spec == 'o' ? 8 : + f.spec == 'b' ? 2 : + f.spec == 's' || f.spec == 'd' || f.spec == 'u' + || f.spec == 'e' || f.spec == 'E' || f.spec == 'f' || f.spec == 'F' + || f.spec == 'g' || f.spec == 'G' ? 10 : + 0; + + import std.format : enforceFmt; + enforceFmt(base > 0, + "incompatible format character for integral argument: %" ~ f.spec); + + import std.math.algebraic : abs; + + bool negative = false; + ulong arg = val; + static if (isSigned!U) + { + if (f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u') + { + if (val < 0) + { + negative = true; + arg = cast(ulong) abs(val); + } + } + } + + arg &= Unsigned!U.max; + + char[64] digits = void; + size_t pos = digits.length - 1; + do + { + digits[pos--] = '0' + arg % base; + if (base > 10 && digits[pos + 1] > '9') + digits[pos + 1] += ((f.spec == 'x' || f.spec == 'a') ? 'a' : 'A') - '0' - 10; + arg /= base; + } while (arg > 0); + + char[3] prefix = void; + size_t left = 2; + size_t right = 2; + + // add sign + if (f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u') + { + if (negative) + prefix[right++] = '-'; + else if (f.flPlus) + prefix[right++] = '+'; + else if (f.flSpace) + prefix[right++] = ' '; + } + + // not a floating point like spec + if (f.spec == 'x' || f.spec == 'X' || f.spec == 'b' || f.spec == 'o' || f.spec == 'u' + || f.spec == 'd' || f.spec == 's') + { + if (f.flHash && (base == 16) && obj != 0) + { + prefix[--left] = f.spec; + prefix[--left] = '0'; + } + if (f.flHash && (base == 8) && obj != 0 + && (digits.length - (pos + 1) >= f.precision || f.precision == f.UNSPECIFIED)) + prefix[--left] = '0'; + + writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], "", f, true); + return; + } + + FormatSpec!Char fs = f; + if (f.precision == f.UNSPECIFIED) + fs.precision = cast(typeof(fs.precision)) (digits.length - pos - 2); + + // %f like output + if (f.spec == 'f' || f.spec == 'F' + || ((f.spec == 'g' || f.spec == 'G') && (fs.precision >= digits.length - pos - 2))) + { + if (f.precision == f.UNSPECIFIED) + fs.precision = 0; + + writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], ".", "", fs, + (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits); + + return; + } + + import std.algorithm.searching : all; + + // at least one digit for %g + if ((f.spec == 'g' || f.spec == 'G') && fs.precision == 0) + fs.precision = 1; + + // rounding + size_t digit_end = pos + fs.precision + ((f.spec == 'g' || f.spec == 'G') ? 1 : 2); + if (digit_end <= digits.length) + { + RoundingClass rt = RoundingClass.ZERO; + if (digit_end < digits.length) + { + auto tie = (f.spec == 'a' || f.spec == 'A') ? '8' : '5'; + if (digits[digit_end] >= tie) + { + rt = RoundingClass.UPPER; + if (digits[digit_end] == tie && digits[digit_end + 1 .. $].all!(a => a == '0')) + rt = RoundingClass.FIVE; + } + else + { + rt = RoundingClass.LOWER; + if (digits[digit_end .. $].all!(a => a == '0')) + rt = RoundingClass.ZERO; + } + } + + if (round(digits, pos + 1, digit_end, rt, negative, + f.spec == 'a' ? 'f' : (f.spec == 'A' ? 'F' : '9'))) + { + pos--; + digit_end--; + } + } + + // convert to scientific notation + char[1] int_digit = void; + int_digit[0] = digits[pos + 1]; + digits[pos + 1] = '.'; + + char[4] suffix = void; + + if (f.spec == 'e' || f.spec == 'E' || f.spec == 'g' || f.spec == 'G') + { + suffix[0] = (f.spec == 'e' || f.spec == 'g') ? 'e' : 'E'; + suffix[1] = '+'; + suffix[2] = cast(char) ('0' + (digits.length - pos - 2) / 10); + suffix[3] = cast(char) ('0' + (digits.length - pos - 2) % 10); + } + else + { + if (right == 3) + prefix[0] = prefix[2]; + prefix[1] = '0'; + prefix[2] = f.spec == 'a' ? 'x' : 'X'; + + left = right == 3 ? 0 : 1; + right = 3; + + suffix[0] = f.spec == 'a' ? 'p' : 'P'; + suffix[1] = '+'; + suffix[2] = cast(char) ('0' + ((digits.length - pos - 2) * 4) / 10); + suffix[3] = cast(char) ('0' + ((digits.length - pos - 2) * 4) % 10); + } + + import std.algorithm.comparison : min; + + // remove trailing zeros + if ((f.spec == 'g' || f.spec == 'G') && !f.flHash) + { + digit_end = min(digit_end, digits.length); + while (digit_end > pos + 1 && + (digits[digit_end - 1] == '0' || digits[digit_end - 1] == '.')) + digit_end--; + } + + writeAligned(w, prefix[left .. right], int_digit[0 .. $], + digits[pos + 1 .. min(digit_end, $)], + suffix[0 .. $], fs, + (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits); +} + +// https://issues.dlang.org/show_bug.cgi?id=18838 +@safe pure unittest +{ + assert("%12,d".format(0) == " 0"); +} + +@safe pure unittest +{ + import std.exception : collectExceptionMsg; + import std.format : FormatException; + import std.range.primitives : back; + + assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c'); + + assertCTFEable!( + { + formatTest(9, "9"); + formatTest(10, "10"); + }); +} + +@safe 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"; } + } + + () @trusted { + 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"); +} + +// https://issues.dlang.org/show_bug.cgi?id=20064 +@safe unittest +{ + assert(format( "%03,d", 1234) == "1,234"); + assert(format( "%04,d", 1234) == "1,234"); + assert(format( "%05,d", 1234) == "1,234"); + assert(format( "%06,d", 1234) == "01,234"); + assert(format( "%07,d", 1234) == "001,234"); + assert(format( "%08,d", 1234) == "0,001,234"); + assert(format( "%09,d", 1234) == "0,001,234"); + assert(format("%010,d", 1234) == "00,001,234"); + assert(format("%011,d", 1234) == "000,001,234"); + assert(format("%012,d", 1234) == "0,000,001,234"); + assert(format("%013,d", 1234) == "0,000,001,234"); + assert(format("%014,d", 1234) == "00,000,001,234"); + assert(format("%015,d", 1234) == "000,000,001,234"); + assert(format("%016,d", 1234) == "0,000,000,001,234"); + assert(format("%017,d", 1234) == "0,000,000,001,234"); + + assert(format( "%03,d", -1234) == "-1,234"); + assert(format( "%04,d", -1234) == "-1,234"); + assert(format( "%05,d", -1234) == "-1,234"); + assert(format( "%06,d", -1234) == "-1,234"); + assert(format( "%07,d", -1234) == "-01,234"); + assert(format( "%08,d", -1234) == "-001,234"); + assert(format( "%09,d", -1234) == "-0,001,234"); + assert(format("%010,d", -1234) == "-0,001,234"); + assert(format("%011,d", -1234) == "-00,001,234"); + assert(format("%012,d", -1234) == "-000,001,234"); + assert(format("%013,d", -1234) == "-0,000,001,234"); + assert(format("%014,d", -1234) == "-0,000,001,234"); + assert(format("%015,d", -1234) == "-00,000,001,234"); + assert(format("%016,d", -1234) == "-000,000,001,234"); + assert(format("%017,d", -1234) == "-0,000,000,001,234"); +} + +@safe pure unittest +{ + string t1 = format("[%6s] [%-6s]", 123, 123); + assert(t1 == "[ 123] [123 ]"); + + string t2 = format("[%6s] [%-6s]", -123, -123); + assert(t2 == "[ -123] [-123 ]"); +} + +@safe pure unittest +{ + formatTest(byte.min, "-128"); + formatTest(short.min, "-32768"); + formatTest(int.min, "-2147483648"); + formatTest(long.min, "-9223372036854775808"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21777 +@safe pure unittest +{ + assert(format!"%20.5,d"(cast(short) 120) == " 00,120"); + assert(format!"%20.5,o"(cast(short) 120) == " 00,170"); + assert(format!"%20.5,x"(cast(short) 120) == " 00,078"); + assert(format!"%20.5,2d"(cast(short) 120) == " 0,01,20"); + assert(format!"%20.5,2o"(cast(short) 120) == " 0,01,70"); + assert(format!"%20.5,4d"(cast(short) 120) == " 0,0120"); + assert(format!"%20.5,4o"(cast(short) 120) == " 0,0170"); + assert(format!"%20.5,4x"(cast(short) 120) == " 0,0078"); + assert(format!"%20.5,2x"(3000) == " 0,0b,b8"); + assert(format!"%20.5,4d"(3000) == " 0,3000"); + assert(format!"%20.5,4o"(3000) == " 0,5670"); + assert(format!"%20.5,4x"(3000) == " 0,0bb8"); + assert(format!"%20.5,d"(-400) == " -00,400"); + assert(format!"%20.30d"(-400) == "-000000000000000000000000000400"); + assert(format!"%20.5,4d"(0) == " 0,0000"); + assert(format!"%0#.8,2s"(12345) == "00,01,23,45"); + assert(format!"%0#.9,3x"(55) == "0x000,000,037"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21814 +@safe pure unittest +{ + assert(format("%,0d",1000) == "1000"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21817 +@safe pure unittest +{ + assert(format!"%u"(-5) == "4294967291"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21820 +@safe pure unittest +{ + assert(format!"%#.0o"(0) == "0"); +} + +@safe pure unittest +{ + assert(format!"%e"(10000) == "1.0000e+04"); + assert(format!"%.2e"(10000) == "1.00e+04"); + assert(format!"%.10e"(10000) == "1.0000000000e+04"); + + assert(format!"%e"(9999) == "9.999e+03"); + assert(format!"%.2e"(9999) == "1.00e+04"); + assert(format!"%.10e"(9999) == "9.9990000000e+03"); + + assert(format!"%f"(10000) == "10000"); + assert(format!"%.2f"(10000) == "10000.00"); + + assert(format!"%g"(10000) == "10000"); + assert(format!"%.2g"(10000) == "1e+04"); + assert(format!"%.10g"(10000) == "10000"); + + assert(format!"%#g"(10000) == "10000."); + assert(format!"%#.2g"(10000) == "1.0e+04"); + assert(format!"%#.10g"(10000) == "10000.00000"); + + assert(format!"%g"(9999) == "9999"); + assert(format!"%.2g"(9999) == "1e+04"); + assert(format!"%.10g"(9999) == "9999"); + + assert(format!"%a"(0x10000) == "0x1.0000p+16"); + assert(format!"%.2a"(0x10000) == "0x1.00p+16"); + assert(format!"%.10a"(0x10000) == "0x1.0000000000p+16"); + + assert(format!"%a"(0xffff) == "0xf.fffp+12"); + assert(format!"%.2a"(0xffff) == "0x1.00p+16"); + assert(format!"%.10a"(0xffff) == "0xf.fff0000000p+12"); +} + +@safe pure unittest +{ + assert(format!"%.3e"(ulong.max) == "1.845e+19"); + assert(format!"%.3f"(ulong.max) == "18446744073709551615.000"); + assert(format!"%.3g"(ulong.max) == "1.84e+19"); + assert(format!"%.3a"(ulong.max) == "0x1.000p+64"); + + assert(format!"%.3e"(long.min) == "-9.223e+18"); + assert(format!"%.3f"(long.min) == "-9223372036854775808.000"); + assert(format!"%.3g"(long.min) == "-9.22e+18"); + assert(format!"%.3a"(long.min) == "-0x8.000p+60"); + + assert(format!"%e"(0) == "0e+00"); + assert(format!"%f"(0) == "0"); + assert(format!"%g"(0) == "0"); + assert(format!"%a"(0) == "0x0p+00"); +} + +@safe pure unittest +{ + assert(format!"%.0g"(1500) == "2e+03"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21900# +@safe pure unittest +{ + assert(format!"%.1a"(472) == "0x1.ep+08"); +} + +/* + Floating-point values are formatted like $(REF printf, core, stdc, stdio) + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.algorithm.searching : find; + import std.format : enforceFmt; + import std.range.primitives : put; + + FloatingPointTypeOf!T val = obj; + const char spec = f.spec; + + if (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", spec).length, + "incompatible format character for floating point argument: %" ~ spec); + + FormatSpec!Char fs = f; // fs is copy for change its values. + fs.spec = spec == 's' ? 'g' : spec; + + static if (is(T == float) || is(T == double) + || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) + { + alias tval = val; + } + else + { + import std.math.traits : isInfinity; + import std.math.operations : nextUp; + + // reals that are not supported by printFloat are cast to double. + double tval = val; + + // Numbers greater than double.max are converted to double.max: + if (val > double.max && !isInfinity(val)) + tval = double.max; + if (val < -double.max && !isInfinity(val)) + tval = -double.max; + + // Numbers between the smallest representable double subnormal and 0.0 + // are converted to the smallest representable double subnormal: + enum doubleLowest = nextUp(0.0); + if (val > 0 && val < doubleLowest) + tval = doubleLowest; + if (val < 0 && val > -doubleLowest) + tval = -doubleLowest; + } + + import std.format.internal.floats : printFloat; + printFloat(w, tval, fs); +} + +@safe unittest +{ + assert(format("%.1f", 1337.7) == "1337.7"); + assert(format("%,3.2f", 1331.982) == "1,331.98"); + assert(format("%,3.0f", 1303.1982) == "1,303"); + assert(format("%#,3.4f", 1303.1982) == "1,303.1982"); + assert(format("%#,3.0f", 1303.1982) == "1,303."); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : collectExceptionMsg; + import std.format : FormatException; + import std.meta : AliasSeq; + import std.range.primitives : back; + + assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd'); + + static 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"); + } +} + +@safe 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"; } + } + + () @trusted { + 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"); +} + +// https://issues.dlang.org/show_bug.cgi?id=19939 +@safe unittest +{ + assert(format("^%13,3.2f$", 1.00) == "^ 1.00$"); + assert(format("^%13,3.2f$", 10.00) == "^ 10.00$"); + assert(format("^%13,3.2f$", 100.00) == "^ 100.00$"); + assert(format("^%13,3.2f$", 1_000.00) == "^ 1,000.00$"); + assert(format("^%13,3.2f$", 10_000.00) == "^ 10,000.00$"); + assert(format("^%13,3.2f$", 100_000.00) == "^ 100,000.00$"); + assert(format("^%13,3.2f$", 1_000_000.00) == "^ 1,000,000.00$"); + assert(format("^%13,3.2f$", 10_000_000.00) == "^10,000,000.00$"); +} + +// https://issues.dlang.org/show_bug.cgi?id=20069 +@safe unittest +{ + assert(format("%012,f", -1234.0) == "-1,234.000000"); + assert(format("%013,f", -1234.0) == "-1,234.000000"); + assert(format("%014,f", -1234.0) == "-01,234.000000"); + assert(format("%011,f", 1234.0) == "1,234.000000"); + assert(format("%012,f", 1234.0) == "1,234.000000"); + assert(format("%013,f", 1234.0) == "01,234.000000"); + assert(format("%014,f", 1234.0) == "001,234.000000"); + assert(format("%015,f", 1234.0) == "0,001,234.000000"); + assert(format("%016,f", 1234.0) == "0,001,234.000000"); + + assert(format( "%08,.2f", -1234.0) == "-1,234.00"); + assert(format( "%09,.2f", -1234.0) == "-1,234.00"); + assert(format("%010,.2f", -1234.0) == "-01,234.00"); + assert(format("%011,.2f", -1234.0) == "-001,234.00"); + assert(format("%012,.2f", -1234.0) == "-0,001,234.00"); + assert(format("%013,.2f", -1234.0) == "-0,001,234.00"); + assert(format("%014,.2f", -1234.0) == "-00,001,234.00"); + assert(format( "%08,.2f", 1234.0) == "1,234.00"); + assert(format( "%09,.2f", 1234.0) == "01,234.00"); + assert(format("%010,.2f", 1234.0) == "001,234.00"); + assert(format("%011,.2f", 1234.0) == "0,001,234.00"); + assert(format("%012,.2f", 1234.0) == "0,001,234.00"); + assert(format("%013,.2f", 1234.0) == "00,001,234.00"); + assert(format("%014,.2f", 1234.0) == "000,001,234.00"); + assert(format("%015,.2f", 1234.0) == "0,000,001,234.00"); + assert(format("%016,.2f", 1234.0) == "0,000,001,234.00"); +} + +@safe unittest +{ + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest); + } + + // issue 20320 + real a = 0.16; + real b = 0.016; + assert(format("%.1f", a) == "0.2"); + assert(format("%.2f", b) == "0.02"); + + double a1 = 0.16; + double b1 = 0.016; + assert(format("%.1f", a1) == "0.2"); + assert(format("%.2f", b1) == "0.02"); + + // issue 9889 + assert(format("%.1f", 0.09) == "0.1"); + assert(format("%.1f", -0.09) == "-0.1"); + assert(format("%.1f", 0.095) == "0.1"); + assert(format("%.1f", -0.095) == "-0.1"); + assert(format("%.1f", 0.094) == "0.1"); + assert(format("%.1f", -0.094) == "-0.1"); +} + +@safe unittest +{ + double a = 123.456; + double b = -123.456; + double c = 123.0; + + assert(format("%10.4f",a) == " 123.4560"); + assert(format("%-10.4f",a) == "123.4560 "); + assert(format("%+10.4f",a) == " +123.4560"); + assert(format("% 10.4f",a) == " 123.4560"); + assert(format("%010.4f",a) == "00123.4560"); + assert(format("%#10.4f",a) == " 123.4560"); + + assert(format("%10.4f",b) == " -123.4560"); + assert(format("%-10.4f",b) == "-123.4560 "); + assert(format("%+10.4f",b) == " -123.4560"); + assert(format("% 10.4f",b) == " -123.4560"); + assert(format("%010.4f",b) == "-0123.4560"); + assert(format("%#10.4f",b) == " -123.4560"); + + assert(format("%10.0f",c) == " 123"); + assert(format("%-10.0f",c) == "123 "); + assert(format("%+10.0f",c) == " +123"); + assert(format("% 10.0f",c) == " 123"); + assert(format("%010.0f",c) == "0000000123"); + assert(format("%#10.0f",c) == " 123."); + + assert(format("%+010.4f",a) == "+0123.4560"); + assert(format("% 010.4f",a) == " 0123.4560"); + assert(format("% +010.4f",a) == "+0123.4560"); +} + +@safe unittest +{ + string t1 = format("[%6s] [%-6s]", 12.3, 12.3); + assert(t1 == "[ 12.3] [12.3 ]"); + + string t2 = format("[%6s] [%-6s]", -12.3, -12.3); + assert(t2 == "[ -12.3] [-12.3 ]"); +} + +// https://issues.dlang.org/show_bug.cgi?id=20396 +@safe unittest +{ + import std.math.operations : nextUp; + + assert(format!"%a"(nextUp(0.0f)) == "0x0.000002p-126"); + assert(format!"%a"(nextUp(0.0)) == "0x0.0000000000001p-1022"); +} + +// https://issues.dlang.org/show_bug.cgi?id=20371 +@safe unittest +{ + assert(format!"%.1000a"(1.0).length == 1007); + assert(format!"%.600f"(0.1).length == 602); + assert(format!"%.600e"(0.1L).length == 606); +} + +@safe unittest +{ + import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined + + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundUp; + assert(format!"%.0e"(3.5) == "4e+00"); + assert(format!"%.0e"(4.5) == "5e+00"); + assert(format!"%.0e"(-3.5) == "-3e+00"); + assert(format!"%.0e"(-4.5) == "-4e+00"); + + fpctrl.rounding = FloatingPointControl.roundDown; + assert(format!"%.0e"(3.5) == "3e+00"); + assert(format!"%.0e"(4.5) == "4e+00"); + assert(format!"%.0e"(-3.5) == "-4e+00"); + assert(format!"%.0e"(-4.5) == "-5e+00"); + + fpctrl.rounding = FloatingPointControl.roundToZero; + assert(format!"%.0e"(3.5) == "3e+00"); + assert(format!"%.0e"(4.5) == "4e+00"); + assert(format!"%.0e"(-3.5) == "-3e+00"); + assert(format!"%.0e"(-4.5) == "-4e+00"); + + fpctrl.rounding = FloatingPointControl.roundToNearest; + assert(format!"%.0e"(3.5) == "4e+00"); + assert(format!"%.0e"(4.5) == "4e+00"); + assert(format!"%.0e"(-3.5) == "-4e+00"); + assert(format!"%.0e"(-4.5) == "-4e+00"); + } +} + +@safe pure unittest +{ + static assert(format("%e",1.0) == "1.000000e+00"); + static assert(format("%e",-1.234e156) == "-1.234000e+156"); + static assert(format("%a",1.0) == "0x1p+0"); + static assert(format("%a",-1.234e156) == "-0x1.7024c96ca3ce4p+518"); + static assert(format("%f",1.0) == "1.000000"); + static assert(format("%f",-1.234e156) == + "-123399999999999990477495546305353609103201879173427886566531" ~ + "0740685826234179310516880117527217443004051984432279880308552" ~ + "009640198043032289366552939010719744.000000"); + static assert(format("%g",1.0) == "1"); + static assert(format("%g",-1.234e156) == "-1.234e+156"); + + static assert(format("%e",1.0f) == "1.000000e+00"); + static assert(format("%e",-1.234e23f) == "-1.234000e+23"); + static assert(format("%a",1.0f) == "0x1p+0"); + static assert(format("%a",-1.234e23f) == "-0x1.a2187p+76"); + static assert(format("%f",1.0f) == "1.000000"); + static assert(format("%f",-1.234e23f) == "-123399998884238311030784.000000"); + static assert(format("%g",1.0f) == "1"); + static assert(format("%g",-1.234e23f) == "-1.234e+23"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21641 +@safe unittest +{ + float a = -999999.8125; + assert(format("%#.5g",a) == "-1.0000e+06"); + assert(format("%#.6g",a) == "-1.00000e+06"); +} + +// https://issues.dlang.org/show_bug.cgi?id=8424 +@safe pure unittest +{ + static assert(format("%s", 0.6f) == "0.6"); + static assert(format("%s", 0.6) == "0.6"); + static assert(format("%s", 0.6L) == "0.6"); +} + +// https://issues.dlang.org/show_bug.cgi?id=9297 +@safe pure unittest +{ + static if (real.mant_dig == 64) // 80 bit reals + { + assert(format("%.25f", 1.6180339887_4989484820_4586834365L) == "1.6180339887498948482072100"); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=21853 +@safe pure unittest +{ + import std.math.exponential : log2; + + // log2 is broken for x87-reals on some computers in CTFE + // the following test excludes these computers from the test + // (issue 21757) + enum test = cast(int) log2(3.05e2312L); + static if (real.mant_dig == 64 && test == 7681) // 80 bit reals + { + static assert(format!"%e"(real.max) == "1.189731e+4932"); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=21842 +@safe pure unittest +{ + assert(format!"%-+05,g"(1.0) == "+1 "); +} + +// https://issues.dlang.org/show_bug.cgi?id=20536 +@safe pure unittest +{ + real r = .00000095367431640625L; + assert(format("%a", r) == "0x1p-20"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21840 +@safe pure unittest +{ + assert(format!"% 0,e"(0.0) == " 0.000000e+00"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21841 +@safe pure unittest +{ + assert(format!"%0.0,e"(0.0) == "0e+00"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21836 +@safe pure unittest +{ + assert(format!"%-5,1g"(0.0) == "0 "); +} + +// https://issues.dlang.org/show_bug.cgi?id=21838 +@safe pure unittest +{ + assert(format!"%#,a"(0.0) == "0x0.p+0"); +} + +/* + Formatting a `creal` is deprecated but still kept around for a while. + */ +deprecated("Use of complex types is deprecated. Use std.complex") +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(immutable T : immutable creal) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.range.primitives : put; + + immutable creal val = obj; + + formatValueImpl(w, val.re, f); + if (val.im >= 0) + { + put(w, '+'); + } + formatValueImpl(w, val.im, f); + put(w, 'i'); +} + +/* + Formatting an `ireal` is deprecated but still kept around for a while. + */ +deprecated("Use of imaginary types is deprecated. Use std.complex") +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(immutable T : immutable ireal) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.range.primitives : put; + + immutable ireal val = obj; + + formatValueImpl(w, val.im, f); + put(w, 'i'); +} + +/* + Individual characters are formatted as Unicode characters with `%s` + and as integers with integral-specific format specs + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.meta : AliasSeq; + + CharTypeOf!T[1] val = obj; + + if (f.spec == 's' || f.spec == 'c') + writeAligned(w, val[], f); + else + { + alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2]; + formatValueImpl(w, cast(U) val[0], f); + } +} + +@safe pure unittest +{ + assertCTFEable!( + { + formatTest('c', "c"); + }); +} + +@safe 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"; } + } + + () @trusted { + 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']); +} + + +@safe pure unittest +{ + string t1 = format("[%6s] [%-6s]", 'A', 'A'); + assert(t1 == "[ A] [A ]"); + string t2 = format("[%6s] [%-6s]", '本', '本'); + assert(t2 == "[ 本] [本 ]"); +} + +/* + Strings are formatted like $(REF printf, core, stdc, stdio) + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T obj, + scope 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 unittest +{ + formatTest("abc", "abc"); +} + +@safe pure unittest +{ + import std.exception : collectExceptionMsg; + import std.range.primitives : back; + + assert(collectExceptionMsg(format("%d", "hi")).back == 'd'); +} + +@safe 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; } + } + + () @trusted { + 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"); +} + +@safe unittest +{ + class C3 + { + string val; + alias val this; + this(string s){ val = s; } + override string toString() const { return "C"; } + } + + () @trusted { 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']); +} + +@safe pure unittest +{ + string t1 = format("[%6s] [%-6s]", "AB", "AB"); + assert(t1 == "[ AB] [AB ]"); + string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä"); + assert(t2 == "[ 本Ä] [本Ä ]"); +} + +// https://issues.dlang.org/show_bug.cgi?id=6640 +@safe unittest +{ + import std.range.primitives : front, popFront; + + 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]); + } +} + +@safe unittest +{ + import std.meta : AliasSeq; + + // string literal from valid UTF sequence is encoding free. + static 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) + () @trusted + { + // U+FFFF with UTF-8 (Invalid code point for interchange) + formatTest([cast(string)[0xEF, 0xBF, 0xBF]], + `[[cast(char) 0xEF, cast(char) 0xBF, cast(char) 0xBF]]`); + + // U+FFFF with UTF-16 (Invalid code point for interchange) + formatTest([cast(wstring)[0xFFFF]], + `[[cast(wchar) 0xFFFF]]`); + + // U+FFFF with UTF-32 (Invalid code point for interchange) + formatTest([cast(dstring)[0xFFFF]], + `[[cast(dchar) 0xFFFF]]`); + } (); +} + +/* + Static-size arrays are formatted as dynamic arrays. + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T obj, + scope const ref FormatSpec!Char f) +if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + formatValueImpl(w, obj[], f); +} + +// Test for https://issues.dlang.org/show_bug.cgi?id=8310 +@safe unittest +{ + import std.array : appender; + import std.format : formatValue; + + 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); +} + +// https://issues.dlang.org/show_bug.cgi?id=18205 +@safe pure unittest +{ + assert("|%8s|".format("abc") == "| abc|"); + assert("|%8s|".format("αβγ") == "| αβγ|"); + assert("|%8s|".format(" ") == "| |"); + assert("|%8s|".format("été"d) == "| été|"); + assert("|%8s|".format("été 2018"w) == "|été 2018|"); + + assert("%2s".format("e\u0301"w) == " e\u0301"); + assert("%2s".format("a\u0310\u0337"d) == " a\u0310\u0337"); +} + +/* + Dynamic arrays are formatted as input ranges. + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + static if (is(immutable(ArrayTypeOf!T) == immutable(void[]))) + { + formatValueImpl(w, cast(const ubyte[]) obj, f); + } + else static if (!isInputRange!T) + { + alias U = Unqual!(ArrayTypeOf!T); + static assert(isInputRange!U, U.stringof ~ " must be an InputRange"); + U val = obj; + formatValueImpl(w, val, f); + } + else + { + formatRange(w, obj, f); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=20848 +@safe unittest +{ + class C + { + immutable(void)[] data; + } + + import std.typecons : Nullable; + Nullable!C c; +} + +// alias this, input range I/F, and toString() +@safe 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"; } + } + + () @trusted { + 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"); + } (); +} + +@safe 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 = () @trusted { return 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 +{ + // 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`]); +} + +// https://issues.dlang.org/show_bug.cgi?id=18778 +@safe pure unittest +{ + assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C"); +} + +@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, "([])"); +} + +// input range formatting +private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f) +if (isInputRange!T) +{ + import std.conv : text; + import std.format : FormatException, formatValue, NoOpSink; + import std.range.primitives : ElementType, empty, front, hasLength, + walkLength, isForwardRange, isInfinite, popFront, put; + + // in this mode, we just want to do a representative print to discover + // if the format spec is valid + enum formatTestMode = is(Writer == NoOpSink); + + // 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)) + writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f); + 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 + { + import std.format : enforceFmt; + enforceFmt(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); + static if (formatTestMode) break; // one is enough to test + } + } + } + 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 (formatTestMode) break; // one is enough to test + } + } + static if (!isInfinite!T) put(w, f.seqAfter); + } + } + else if (f.spec == 'r') + { + static if (is(DynamicArrayTypeOf!T)) + { + alias ARR = DynamicArrayTypeOf!T; + scope a = cast(ARR) val; + foreach (e ; a) + { + formatValue(w, e, f); + static if (formatTestMode) break; // one is enough to test + } + } + else + { + for (size_t i; !val.empty; val.popFront(), ++i) + { + formatValue(w, val.front, f); + static if (formatTestMode) break; // one is enough to test + } + } + } + else if (f.spec == '(') + { + if (val.empty) + return; + // Nested specifier is to be used + for (;;) + { + auto fmt = FormatSpec!Char(f.nested); + w: while (true) + { + immutable r = fmt.writeUpToNextSpec(w); + // There was no format specifier, so break + if (!r) + break; + if (f.flDash) + formatValue(w, val.front, fmt); + else + formatElement(w, val.front, fmt); + // Check if there will be a format specifier farther on in the + // string. If so, continue the loop, otherwise break. This + // prevents extra copies of the `sep` from showing up. + foreach (size_t i; 0 .. fmt.trailing.length) + if (fmt.trailing[i] == '%') + continue w; + break w; + } + static if (formatTestMode) + { + break; // one is enough to test + } + else + { + 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 FormatException(text("Incorrect format specifier for range: %", f.spec)); +} + +// https://issues.dlang.org/show_bug.cgi?id=20218 +@safe pure unittest +{ + void notCalled() + { + import std.range : repeat; + + auto value = 1.repeat; + + // test that range is not evaluated to completion at compiletime + format!"%s"(value); + } +} + +// character formatting with ecaping +void formatChar(Writer)(ref Writer w, in dchar c, in char quote) +{ + import std.format : formattedWrite; + import std.range.primitives : put; + 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); +} + +/* + Associative arrays are formatted by using `':'` and $(D ", ") as + separators, and enclosed by `'['` and `']'`. + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) +if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.format : enforceFmt, formatValue; + import std.range.primitives : put; + + AssocArrayTypeOf!T val = obj; + const spec = f.spec; + + enforceFmt(spec == 's' || spec == '(', + "incompatible format character for associative array argument: %" ~ spec); + + enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; + auto fmtSpec = spec == '(' ? f.nested : defSpec; + + auto key_first = true; + + // testing correct nested format spec + import std.format : NoOpSink; + auto noop = NoOpSink(); + auto test = FormatSpec!Char(fmtSpec); + enforceFmt(test.writeUpToNextSpec(noop), + "nested format string for associative array contains no format specifier"); + enforceFmt(test.indexStart <= 2, + "positional parameter in nested format string for associative array may only be 1 or 2"); + if (test.indexStart == 2) + key_first = false; + + enforceFmt(test.writeUpToNextSpec(noop), + "nested format string for associative array contains only one format specifier"); + enforceFmt(test.indexStart <= 2, + "positional parameter in nested format string for associative array may only be 1 or 2"); + enforceFmt(test.indexStart == 0 || ((test.indexStart == 2) == key_first), + "wrong combination of positional parameters in nested format string"); + + enforceFmt(!test.writeUpToNextSpec(noop), + "nested format string for associative array contains more than two format specifiers"); + + size_t i = 0; + immutable end = val.length; + + if (spec == 's') + put(w, f.seqBefore); + foreach (k, ref v; val) + { + auto fmt = FormatSpec!Char(fmtSpec); + + foreach (pos; 1 .. 3) + { + fmt.writeUpToNextSpec(w); + + if (key_first == (pos == 1)) + { + if (f.flDash) + formatValue(w, k, fmt); + else + formatElement(w, k, fmt); + } + else + { + if (f.flDash) + formatValue(w, v, fmt); + else + 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 (spec == 's') + put(w, f.seqAfter); +} + +@safe unittest +{ + import std.exception : collectExceptionMsg; + import std.format : FormatException; + import std.range.primitives : back; + + 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]}`]); + + // https://issues.dlang.org/show_bug.cgi?id=12135 + formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>"); + formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>"); +} + +@safe 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"; } + } + + () @trusted { + 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"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21875 +@safe unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + + auto aa = [ 1 : "x", 2 : "y", 3 : "z" ]; + + assertThrown!FormatException(format("%(%)", aa)); + assertThrown!FormatException(format("%(%s%)", aa)); + assertThrown!FormatException(format("%(%s%s%s%)", aa)); +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + + auto aa = [ 1 : "x", 2 : "y", 3 : "z" ]; + + assertThrown!FormatException(format("%(%3$s%s%)", aa)); + assertThrown!FormatException(format("%(%s%3$s%)", aa)); + assertThrown!FormatException(format("%(%1$s%1$s%)", aa)); + assertThrown!FormatException(format("%(%2$s%2$s%)", aa)); + assertThrown!FormatException(format("%(%s%1$s%)", aa)); +} + +// https://issues.dlang.org/show_bug.cgi?id=21808 +@safe unittest +{ + auto spelled = [ 1 : "one" ]; + assert(format("%-(%2$s (%1$s)%|, %)", spelled) == "one (1)"); + + spelled[2] = "two"; + auto result = format("%-(%2$s (%1$s)%|, %)", spelled); + assert(result == "one (1), two (2)" || result == "two (2), one (1)"); +} + +enum HasToStringResult +{ + none, + hasSomeToString, + inCharSink, + inCharSinkFormatString, + inCharSinkFormatSpec, + constCharSink, + constCharSinkFormatString, + constCharSinkFormatSpec, + customPutWriter, + customPutWriterFormatSpec, +} + +private enum hasPreviewIn = !is(typeof(mixin(q{(in ref int a) => a}))); + +template hasToString(T, Char) +{ + static if (isPointer!T) + { + // X* does not have toString, even if X is aggregate type has toString. + enum hasToString = HasToStringResult.none; + } + else static if (is(typeof( + { + T val = void; + const FormatSpec!Char f; + static struct S {void put(scope Char s){}} + S s; + val.toString(s, f); + static assert(!__traits(compiles, val.toString(s, FormatSpec!Char())), + "force toString to take parameters by ref"); + static assert(!__traits(compiles, val.toString(S(), f)), + "force toString to take parameters by ref"); + }))) + { + enum hasToString = HasToStringResult.customPutWriterFormatSpec; + } + else static if (is(typeof( + { + T val = void; + static struct S {void put(scope Char s){}} + S s; + val.toString(s); + static assert(!__traits(compiles, val.toString(S())), + "force toString to take parameters by ref"); + }))) + { + enum hasToString = HasToStringResult.customPutWriter; + } + else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); }))) + { + enum hasToString = HasToStringResult.constCharSinkFormatSpec; + } + else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}, "%s"); }))) + { + enum hasToString = HasToStringResult.constCharSinkFormatString; + } + else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}); }))) + { + enum hasToString = HasToStringResult.constCharSink; + } + + else static if (hasPreviewIn && + is(typeof({ T val = void; FormatSpec!Char f; val.toString((in char[] s){}, f); }))) + { + enum hasToString = HasToStringResult.inCharSinkFormatSpec; + } + else static if (hasPreviewIn && + is(typeof({ T val = void; val.toString((in char[] s){}, "%s"); }))) + { + enum hasToString = HasToStringResult.inCharSinkFormatString; + } + else static if (hasPreviewIn && + is(typeof({ T val = void; val.toString((in char[] s){}); }))) + { + enum hasToString = HasToStringResult.inCharSink; + } + + else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S) + { + enum hasToString = HasToStringResult.hasSomeToString; + } + else + { + enum hasToString = HasToStringResult.none; + } +} + +@safe unittest +{ + import std.range.primitives : isOutputRange; + + static struct A + { + void toString(Writer)(ref Writer w) + if (isOutputRange!(Writer, string)) + {} + } + static struct B + { + void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {} + } + static struct C + { + void toString(scope void delegate(scope const(char)[]) sink, string fmt) {} + } + static struct D + { + void toString(scope void delegate(scope const(char)[]) sink) {} + } + static struct E + { + string toString() {return "";} + } + static struct F + { + void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) + if (isOutputRange!(Writer, string)) + {} + } + static struct G + { + string toString() {return "";} + void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {} + } + static struct H + { + string toString() {return "";} + void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) + if (isOutputRange!(Writer, string)) + {} + } + static struct I + { + void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {} + void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) + if (isOutputRange!(Writer, string)) + {} + } + static struct J + { + string toString() {return "";} + void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt) + if (isOutputRange!(Writer, string)) + {} + } + static struct K + { + void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt) + if (isOutputRange!(Writer, string)) + {} + } + static struct L + { + void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt) + if (isOutputRange!(Writer, string)) + {} + } + static struct M + { + void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) {} + } + static struct N + { + void toString(scope void delegate(in char[]) sink, string fmt) {} + } + static struct O + { + void toString(scope void delegate(in char[]) sink) {} + } + + with(HasToStringResult) + { + static assert(hasToString!(A, char) == customPutWriter); + static assert(hasToString!(B, char) == constCharSinkFormatSpec); + static assert(hasToString!(C, char) == constCharSinkFormatString); + static assert(hasToString!(D, char) == constCharSink); + static assert(hasToString!(E, char) == hasSomeToString); + static assert(hasToString!(F, char) == customPutWriterFormatSpec); + static assert(hasToString!(G, char) == customPutWriter); + static assert(hasToString!(H, char) == customPutWriterFormatSpec); + static assert(hasToString!(I, char) == customPutWriterFormatSpec); + static assert(hasToString!(J, char) == hasSomeToString); + static assert(hasToString!(K, char) == constCharSinkFormatSpec); + static assert(hasToString!(L, char) == none); + static if (hasPreviewIn) + { + static assert(hasToString!(M, char) == inCharSinkFormatSpec); + static assert(hasToString!(N, char) == inCharSinkFormatString); + static assert(hasToString!(O, char) == inCharSink); + } + } +} + +// object formatting with toString +private void formatObject(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f) +if (hasToString!(T, Char)) +{ + import std.format : NoOpSink; + import std.range.primitives : put; + + enum overload = hasToString!(T, Char); + + enum noop = is(Writer == NoOpSink); + + static if (overload == HasToStringResult.customPutWriterFormatSpec) + { + static if (!noop) val.toString(w, f); + } + else static if (overload == HasToStringResult.customPutWriter) + { + static if (!noop) val.toString(w); + } + else static if (overload == HasToStringResult.constCharSinkFormatSpec) + { + static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f); + } + else static if (overload == HasToStringResult.constCharSinkFormatString) + { + static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr()); + } + else static if (overload == HasToStringResult.constCharSink) + { + static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }); + } + else static if (overload == HasToStringResult.inCharSinkFormatSpec) + { + static if (!noop) val.toString((in char[] s) { put(w, s); }, f); + } + else static if (overload == HasToStringResult.inCharSinkFormatString) + { + static if (!noop) val.toString((in char[] s) { put(w, s); }, f.getCurFmtStr()); + } + else static if (overload == HasToStringResult.inCharSink) + { + static if (!noop) val.toString((in char[] s) { put(w, s); }); + } + else static if (overload == HasToStringResult.hasSomeToString) + { + static if (!noop) put(w, val.toString()); + } + else + { + static assert(0, "No way found to format " ~ T.stringof ~ " as string"); + } +} + +@system unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + + 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(scope const(char)[]) sink, + FormatSpec!char) const; } + static class CIK1 : IK1 { override void toString(scope void delegate(scope const(char)[]) sink, + FormatSpec!char) const { sink("CIK1"); } } + static struct KS1 { void toString(scope void delegate(scope const(char)[]) sink, + FormatSpec!char) const { sink("KS1"); } } + + static union KU1 { void toString(scope void delegate(scope const(char)[]) sink, + FormatSpec!char) const { sink("KU1"); } } + + static class KC1 { void toString(scope void delegate(scope 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 + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) +if (is(T == class) && !is(T == enum)) +{ + import std.range.primitives : put; + + enforceValidFormatSpec!(T, Char)(f); + + // TODO: remove this check once `@disable override` deprecation cycle is finished + static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) + static assert(!__traits(isDisabled, T.toString), T.stringof ~ + " cannot be formatted because its `toString` is marked with `@disable`"); + + if (val is null) + put(w, "null"); + else + { + import std.algorithm.comparison : among; + enum overload = hasToString!(T, Char); + with(HasToStringResult) + static if ((is(T == immutable) || is(T == const) || is(T == shared)) && overload == none) + { + // Remove this when Object gets const toString + // https://issues.dlang.org/show_bug.cgi?id=7879 + static if (is(T == immutable)) + put(w, "immutable("); + else static if (is(T == const)) + put(w, "const("); + else static if (is(T == shared)) + put(w, "shared("); + + put(w, typeid(Unqual!T).name); + put(w, ')'); + } + else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) || + (!isInputRange!T && !is(BuiltinTypeOf!T))) + { + formatObject!(Writer, T, Char)(w, val, f); + } + else + { + static if (!is(__traits(parent, T.toString) == Object)) // not inherited Object.toString + { + formatObject(w, val, f); + } + else static if (isInputRange!T) + { + formatRange(w, val, f); + } + else static if (is(BuiltinTypeOf!T X)) + { + X x = val; + formatValueImpl(w, x, f); + } + else + { + formatObject(w, val, f); + } + } + } +} + +@system unittest +{ + import std.array : appender; + import std.range.interfaces : inputRangeObject; + + // class range (https://issues.dlang.org/show_bug.cgi?id=5154) + auto c = inputRangeObject([1,2,3,4]); + formatTest(c, "[1, 2, 3, 4]"); + assert(c.empty); + c = null; + formatTest(c, "null"); +} + +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=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(scope const(char)[]) dg, + scope 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]"); +} + +// outside the unittest block, otherwise the FQN of the +// class contains the line number of the unittest +version (StdUnittest) +{ + private class C {} +} + +// https://issues.dlang.org/show_bug.cgi?id=7879 +@safe unittest +{ + const(C) c; + auto s = format("%s", c); + assert(s == "null"); + + immutable(C) c2 = new C(); + s = format("%s", c2); + assert(s == "immutable(std.format.internal.write.C)"); + + const(C) c3 = new C(); + s = format("%s", c3); + assert(s == "const(std.format.internal.write.C)"); + + shared(C) c4 = new C(); + s = format("%s", c4); + assert(s == "shared(std.format.internal.write.C)"); +} + +// https://issues.dlang.org/show_bug.cgi?id=7879 +@safe unittest +{ + class F + { + override string toString() const @safe + { + return "Foo"; + } + } + + const(F) c; + auto s = format("%s", c); + assert(s == "null"); + + const(F) c2 = new F(); + s = format("%s", c2); + assert(s == "Foo", s); +} + +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) +if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) +{ + import std.range.primitives : put; + + enforceValidFormatSpec!(T, Char)(f); + if (val is null) + put(w, "null"); + else + { + static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) + static assert(!__traits(isDisabled, T.toString), T.stringof ~ + " cannot be formatted because its `toString` is marked with `@disable`"); + + static if (hasToString!(T, Char) != HasToStringResult.none) + { + 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)) + { + formatValueImpl(w, *cast(void**)&val, f); + } + else + { + formatValueImpl(w, cast(Object) val, f); + } + } + else + { + formatValueImpl(w, cast(Object) val, f); + } + } + } +} + +@system unittest +{ + import std.range.interfaces : InputRange, inputRangeObject; + + // interface + 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"); + + // https://issues.dlang.org/show_bug.cgi?id=11175 + version (Windows) + { + import core.sys.windows.com : IID, IUnknown; + import core.sys.windows.windef : 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); + } +} + +// Maybe T is noncopyable struct, so receive it by 'auto ref'. +void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T val, + scope const ref FormatSpec!Char f) +if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) + && !is(T == enum)) +{ + import std.range.primitives : put; + + static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) + static assert(!__traits(isDisabled, T.toString), T.stringof ~ + " cannot be formatted because its `toString` is marked with `@disable`"); + + 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 (__traits(identifier, val.tupleof[i]) == "this") + continue; + else 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); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=9588 +@safe pure unittest +{ + struct S { int x; bool empty() { return false; } } + formatTest(S(), "S(0)"); +} + +// https://issues.dlang.org/show_bug.cgi?id=4638 +@safe unittest +{ + 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"); +} + +// https://issues.dlang.org/show_bug.cgi?id=3890 +@safe unittest +{ + struct Int{ int n; } + struct Pair{ string s; Int i; } + formatTest(Pair("hello", Int(5)), + `Pair("hello", Int(5))`); +} + +// https://issues.dlang.org/show_bug.cgi?id=9117 +@safe unittest +{ + import std.format : formattedWrite; + + 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(scope 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"); +} + +@safe 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; + () @trusted { u2.s = "hello"; } (); + formatTest(u2, "hello"); +} + +@safe unittest +{ + import std.array : appender; + import std.format : formatValue; + + // https://issues.dlang.org/show_bug.cgi?id=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 : appender; + import std.format : formatValue; + + static struct S{ @disable this(this); } + S s; + + FormatSpec!char f; + auto w = appender!string(); + formatValue(w, s, f); + assert(w.data == "S()"); +} + +@safe unittest +{ + import std.array : appender; + import std.format : formatValue; + + //struct Foo { @disable string toString(); } + //Foo foo; + + interface Bar { @disable string toString(); } + Bar bar; + + auto w = appender!(char[])(); + FormatSpec!char f; + + // NOTE: structs cant be tested : the assertion is correct so compilation + // continues and fails when trying to link the unimplemented toString. + //static assert(!__traits(compiles, formatValue(w, foo, f))); + static assert(!__traits(compiles, formatValue(w, bar, f))); +} + +// https://issues.dlang.org/show_bug.cgi?id=21722 +@safe unittest +{ + struct Bar + { + void toString (scope void delegate (scope const(char)[]) sink, string fmt) + { + sink("Hello"); + } + } + + Bar b; + auto result = () @trusted { return format("%b", b); } (); + assert(result == "Hello"); + + static if (hasPreviewIn) + { + struct Foo + { + void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) + { + sink("Hello"); + } + } + + Foo f; + assert(format("%b", f) == "Hello"); + + struct Foo2 + { + void toString(scope void delegate(in char[]) sink, string fmt) + { + sink("Hello"); + } + } + + Foo2 f2; + assert(format("%b", f2) == "Hello"); + } +} + +@safe unittest +{ + import std.array : appender; + import std.format : singleSpec; + + // Bug #17269. Behavior similar to `struct A { Nullable!string B; }` + struct StringAliasThis + { + @property string value() const { assert(0); } + alias value this; + string toString() { return "helloworld"; } + private string _value; + } + struct TestContainer + { + StringAliasThis testVar; + } + + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, TestContainer(), spec); + + assert(w.data == "TestContainer(helloworld)", w.data); +} + +// https://issues.dlang.org/show_bug.cgi?id=17269 +@safe unittest +{ + import std.typecons : Nullable; + + struct Foo + { + Nullable!string bar; + } + + Foo f; + formatTest(f, "Foo(Nullable.null)"); +} + +// https://issues.dlang.org/show_bug.cgi?id=19003 +@safe unittest +{ + struct S + { + int i; + + @disable this(); + + invariant { assert(this.i); } + + this(int i) @safe in { assert(i); } do { this.i = i; } + + string toString() { return "S"; } + } + + S s = S(1); + + format!"%s"(s); +} + +void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f) +{ + import std.format : enforceFmt; + import std.range : isInputRange; + import std.format.internal.write : hasToString, HasToStringResult; + + enum overload = hasToString!(T, Char); + static if ( + overload != HasToStringResult.constCharSinkFormatSpec && + overload != HasToStringResult.constCharSinkFormatString && + overload != HasToStringResult.inCharSinkFormatSpec && + overload != HasToStringResult.inCharSinkFormatString && + overload != HasToStringResult.customPutWriterFormatSpec && + !isInputRange!T) + { + enforceFmt(f.spec == 's', + "Expected '%s' format specifier for type '" ~ T.stringof ~ "'"); + } +} + +/* + `enum`s are formatted like their base value + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) +if (is(T == enum)) +{ + import std.array : appender; + import std.range.primitives : put; + + if (f.spec == 's') + { + foreach (i, e; EnumMembers!T) + { + if (val == e) + { + formatValueImpl(w, __traits(allMembers, T)[i], f); + return; + } + } + + auto w2 = appender!string(); + + // val is not a member of T, output cast(T) rawValue instead. + put(w2, "cast(" ~ T.stringof ~ ")"); + static assert(!is(OriginalType!T == T), "OriginalType!" ~ T.stringof ~ + "must not be equal to " ~ T.stringof); + + FormatSpec!Char f2 = f; + f2.width = 0; + formatValueImpl(w2, cast(OriginalType!T) val, f2); + writeAligned(w, w2.data, f); + return; + } + formatValueImpl(w, cast(OriginalType!T) val, f); +} + +@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"); +} + +@safe pure unittest +{ + enum A { one, two, three } + + string t1 = format("[%6s] [%-6s]", A.one, A.one); + assert(t1 == "[ one] [one ]"); + string t2 = format("[%10s] [%-10s]", cast(A) 10, cast(A) 10); + assert(t2 == "[ cast(A)" ~ "10] [cast(A)" ~ "10 ]"); // due to bug in style checker +} + +// https://issues.dlang.org/show_bug.cgi?id=8921 +@safe unittest +{ + 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]"); +} + +/* + Pointers are formatted as hex integers. + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T val, scope const ref FormatSpec!Char f) +if (isPointer!T && !is(T == enum) && !hasToString!(T, Char)) +{ + 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) + { + writeAligned(w, "null", f); + return; + } + FormatSpec!Char fs = f; // fs is copy for change its values. + fs.spec = 'X'; + formatValueImpl(w, pnum, fs); + } + else + { + import std.format : enforceFmt; + enforceFmt(f.spec == 'X' || f.spec == 'x', + "Expected one of %s, %x or %X for pointer type."); + formatValueImpl(w, pnum, f); + } +} + +@safe pure unittest +{ + int* p; + + string t1 = format("[%6s] [%-6s]", p, p); + assert(t1 == "[ null] [null ]"); +} + +@safe pure unittest +{ + int* p = null; + formatTest(p, "null"); + + auto q = () @trusted { return cast(void*) 0xFFEECCAA; }(); + formatTest(q, "FFEECCAA"); +} + +// https://issues.dlang.org/show_bug.cgi?id=11782 +@safe pure unittest +{ + import std.range : iota; + + auto a = iota(0, 10); + auto b = iota(0, 10); + auto p = () @trusted { auto p = &a; return p; }(); + + assert(format("%s",p) != format("%s",b)); +} + +@safe pure unittest +{ + // Test for https://issues.dlang.org/show_bug.cgi?id=7869 + struct S + { + string toString() const { return ""; } + } + S* p = null; + formatTest(p, "null"); + + S* q = () @trusted { return cast(S*) 0xFFEECCAA; } (); + formatTest(q, "FFEECCAA"); +} + +// https://issues.dlang.org/show_bug.cgi?id=8186 +@system unittest +{ + class B + { + int* a; + this() { a = new int; } + alias a this; + } + formatTest(B.init, "null"); +} + +// https://issues.dlang.org/show_bug.cgi?id=9336 +@system pure unittest +{ + shared int i; + format("%s", &i); +} + +// https://issues.dlang.org/show_bug.cgi?id=11778 +@safe pure unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + + int* p = null; + assertThrown!FormatException(format("%d", p)); + assertThrown!FormatException(format("%04d", () @trusted { return p + 2; } ())); +} + +// https://issues.dlang.org/show_bug.cgi?id=12505 +@safe pure unittest +{ + void* p = null; + formatTest("%08X", p, "00000000"); +} + +/* + SIMD vectors are formatted as arrays. + */ +void formatValueImpl(Writer, V, Char)(auto ref Writer w, V val, scope const ref FormatSpec!Char f) +if (isSIMDVector!V) +{ + formatValueImpl(w, val.array, f); +} + +@safe unittest +{ + import core.simd; // cannot be selective, because float4 might not be defined + + static if (is(float4)) + { + version (X86) + { + version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */} + } + else + { + float4 f; + f.array[0] = 1; + f.array[1] = 2; + f.array[2] = 3; + f.array[3] = 4; + formatTest(f, "[1, 2, 3, 4]"); + } + } +} + +/* + Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes` + + Known bug: Because of issue https://issues.dlang.org/show_bug.cgi?id=18269 + the FunctionAttributes might be wrong. + */ +void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T, scope const ref FormatSpec!Char f) +if (isDelegate!T) +{ + formatValueImpl(w, T.stringof, f); +} + +@safe unittest +{ + import std.array : appender; + import std.format : formatValue; + + void func() @system { __gshared int x; ++x; throw new Exception("msg"); } + version (linux) + { + FormatSpec!char f; + auto w = appender!string(); + formatValue(w, &func, f); + assert(w.data.length >= 15 && w.data[0 .. 15] == "void delegate()"); + } +} + +// string elements are formatted like UTF-8 string literals. +void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) +if (is(StringTypeOf!T) && !hasToString!(T, Char) && !is(T == enum)) +{ + import std.array : appender; + import std.format.write : formattedWrite, formatValue; + import std.range.primitives : put; + import std.utf : decode, UTFException; + + StringTypeOf!T str = val; // https://issues.dlang.org/show_bug.cgi?id=8015 + + if (f.spec == 's') + { + try + { + // ignore other specifications and quote + for (size_t i = 0; i < str.length; ) + { + 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; + } + put(w, '\"'); + for (size_t i = 0; i < str.length; ) + { + auto c = decode(str, i); + formatChar(w, c, '"'); + } + put(w, '\"'); + return; + } + catch (UTFException) + { + } + + // If val contains invalid UTF sequence, formatted like HexString literal + LinvalidSeq: + static if (is(typeof(str[0]) : const(char))) + { + enum type = ""; + alias IntArr = const(ubyte)[]; + } + else static if (is(typeof(str[0]) : const(wchar))) + { + enum type = "w"; + alias IntArr = const(ushort)[]; + } + else static if (is(typeof(str[0]) : const(dchar))) + { + enum type = "d"; + alias IntArr = const(uint)[]; + } + formattedWrite(w, "[%(cast(" ~ type ~ "char) 0x%X%|, %)]", cast(IntArr) str); + } + else + formatValue(w, str, f); +} + +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, "Hello World", spec); + + assert(w.data == "\"Hello World\""); +} + +@safe unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, "H", spec); + + assert(w.data == "\"H\"", w.data); +} + +// https://issues.dlang.org/show_bug.cgi?id=15888 +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + ushort[] a = [0xFF_FE, 0x42]; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, cast(wchar[]) a, spec); + assert(w.data == `[cast(wchar) 0xFFFE, cast(wchar) 0x42]`); + + uint[] b = [0x0F_FF_FF_FF, 0x42]; + w = appender!string(); + spec = singleSpec("%s"); + formatElement(w, cast(dchar[]) b, spec); + assert(w.data == `[cast(dchar) 0xFFFFFFF, cast(dchar) 0x42]`); +} + +// Character elements are formatted like UTF-8 character literals. +void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) +if (is(CharTypeOf!T) && !is(T == enum)) +{ + import std.range.primitives : put; + import std.format.write : formatValue; + + if (f.spec == 's') + { + put(w, '\''); + formatChar(w, val, '\''); + put(w, '\''); + } + else + formatValue(w, val, f); +} + +// Maybe T is noncopyable struct, so receive it by 'auto ref'. +void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) +if ((!is(StringTypeOf!T) || hasToString!(T, Char)) && !is(CharTypeOf!T) || is(T == enum)) +{ + import std.format.write : formatValue; + + formatValue(w, val, f); +} + +// Fix for https://issues.dlang.org/show_bug.cgi?id=1591 +int getNthInt(string kind, A...)(uint index, A args) +{ + return getNth!(kind, isIntegral, int)(index, args); +} + +T getNth(string kind, alias Condition, T, A...)(uint index, A args) +{ + import std.conv : text, to; + import std.format : FormatException; + + 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")); + } +} + +private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f) +{ + import std.system : endian, Endian; + + return endian == Endian.littleEndian && f.flPlus + || endian == Endian.bigEndian && f.flDash; +} + +void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f) +if (isSomeString!T) +{ + FormatSpec!Char fs = f; + fs.flZero = false; + writeAligned(w, "", "", s, fs); +} + +@safe pure unittest +{ + import std.array : appender; + import std.format : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%s"); + writeAligned(w, "a本Ä", spec); + assert(w.data == "a本Ä", w.data); +} + +@safe pure unittest +{ + import std.array : appender; + import std.format : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%10s"); + writeAligned(w, "a本Ä", spec); + assert(w.data == " a本Ä", "|" ~ w.data ~ "|"); +} + +@safe pure unittest +{ + import std.array : appender; + import std.format : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%-10s"); + writeAligned(w, "a本Ä", spec); + assert(w.data == "a本Ä ", w.data); +} + +enum PrecisionType +{ + none, + integer, + fractionalDigits, + allDigits, +} + +void writeAligned(Writer, T1, T2, T3, Char)(auto ref Writer w, + T1 prefix, T2 grouped, T3 suffix, scope const ref FormatSpec!Char f, + bool integer_precision = false) +if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3) +{ + writeAligned(w, prefix, grouped, "", suffix, f, + integer_precision ? PrecisionType.integer : PrecisionType.none); +} + +void writeAligned(Writer, T1, T2, T3, T4, Char)(auto ref Writer w, + T1 prefix, T2 grouped, T3 fracts, T4 suffix, scope const ref FormatSpec!Char f, + PrecisionType p = PrecisionType.none) +if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3 && isSomeString!T4) +{ + // writes: left padding, prefix, leading zeros, grouped, fracts, suffix, right padding + + if (p == PrecisionType.integer && f.precision == f.UNSPECIFIED) + p = PrecisionType.none; + + import std.range.primitives : put; + + long prefixWidth; + long groupedWidth = grouped.length; // TODO: does not take graphemes into account + long fractsWidth = fracts.length; // TODO: does not take graphemes into account + long suffixWidth; + + // TODO: remove this workaround which hides issue 21815 + if (f.width > 0) + { + prefixWidth = getWidth(prefix); + suffixWidth = getWidth(suffix); + } + + auto doGrouping = f.flSeparator && groupedWidth > 0 + && f.separators > 0 && f.separators != f.UNSPECIFIED; + // front = number of symbols left of the leftmost separator + long front = doGrouping ? (groupedWidth - 1) % f.separators + 1 : 0; + // sepCount = number of separators to be inserted + long sepCount = doGrouping ? (groupedWidth - 1) / f.separators : 0; + + long trailingZeros = 0; + if (p == PrecisionType.fractionalDigits) + trailingZeros = f.precision - (fractsWidth - 1); + if (p == PrecisionType.allDigits && f.flHash) + { + if (grouped != "0") + trailingZeros = f.precision - (fractsWidth - 1) - groupedWidth; + else + { + trailingZeros = f.precision - fractsWidth; + foreach (i;0 .. fracts.length) + if (fracts[i] != '0' && fracts[i] != '.') + { + trailingZeros = f.precision - (fracts.length - i); + break; + } + } + } + + auto nodot = fracts == "." && trailingZeros == 0 && !f.flHash; + + if (nodot) fractsWidth = 0; + + long width = prefixWidth + sepCount + groupedWidth + fractsWidth + trailingZeros + suffixWidth; + long delta = f.width - width; + + // with integers, precision is considered the minimum number of digits; + // if digits are missing, we have to recalculate everything + long pregrouped = 0; + if (p == PrecisionType.integer && groupedWidth < f.precision) + { + pregrouped = f.precision - groupedWidth; + delta -= pregrouped; + if (doGrouping) + { + front = ((front - 1) + pregrouped) % f.separators + 1; + delta -= (f.precision - 1) / f.separators - sepCount; + } + } + + // left padding + if ((!f.flZero || p == PrecisionType.integer) && delta > 0) + { + if (f.flEqual) + { + foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && !f.flDash) ? 1 : 0)) + put(w, ' '); + } + else if (!f.flDash) + { + foreach (i ; 0 .. delta) + put(w, ' '); + } + } + + // prefix + put(w, prefix); + + // leading grouped zeros + if (f.flZero && p != PrecisionType.integer && !f.flDash && delta > 0) + { + if (doGrouping) + { + // front2 and sepCount2 are the same as above for the leading zeros + long front2 = (delta + front - 1) % (f.separators + 1) + 1; + long sepCount2 = (delta + front - 1) / (f.separators + 1); + delta -= sepCount2; + + // according to POSIX: if the first symbol is a separator, + // an additional zero is put left of it, even if that means, that + // the total width is one more then specified + if (front2 > f.separators) { front2 = 1; } + + foreach (i ; 0 .. delta) + { + if (front2 == 0) + { + put(w, f.separatorChar); + front2 = f.separators; + } + front2--; + + put(w, '0'); + } + + // separator between zeros and grouped + if (front == f.separators) + put(w, f.separatorChar); + } + else + foreach (i ; 0 .. delta) + put(w, '0'); + } + + // grouped content + if (doGrouping) + { + // TODO: this does not take graphemes into account + foreach (i;0 .. pregrouped + grouped.length) + { + if (front == 0) + { + put(w, f.separatorChar); + front = f.separators; + } + front--; + + put(w, i < pregrouped ? '0' : grouped[cast(size_t) (i - pregrouped)]); + } + } + else + { + foreach (i;0 .. pregrouped) + put(w, '0'); + put(w, grouped); + } + + // fracts + if (!nodot) + put(w, fracts); + + // trailing zeros + foreach (i ; 0 .. trailingZeros) + put(w, '0'); + + // suffix + put(w, suffix); + + // right padding + if (delta > 0) + { + if (f.flEqual) + { + foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && f.flDash) ? 1 : 0)) + put(w, ' '); + } + else if (f.flDash) + { + foreach (i ; 0 .. delta) + put(w, ' '); + } + } +} + +@safe pure unittest +{ + import std.array : appender; + import std.format : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pregroupingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%20s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == " pregroupingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%-20s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pregroupingsuf ", w.data); + + w = appender!string(); + spec = singleSpec("%020s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pre000000groupingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%-020s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pregroupingsuf ", w.data); + + w = appender!string(); + spec = singleSpec("%20,1s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data); + + w = appender!string(); + spec = singleSpec("%20,2s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == " pregr,ou,pi,ngsuf", w.data); + + w = appender!string(); + spec = singleSpec("%20,3s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == " pregr,oup,ingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%20,10s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == " pregroupingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%020,1s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data); + + w = appender!string(); + spec = singleSpec("%020,2s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pre00,gr,ou,pi,ngsuf", w.data); + + w = appender!string(); + spec = singleSpec("%020,3s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pre00,0gr,oup,ingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%020,10s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pre000,00groupingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%021,3s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pre000,0gr,oup,ingsuf", w.data); + + // According to https://github.com/dlang/phobos/pull/7112 this + // is defined by POSIX standard: + w = appender!string(); + spec = singleSpec("%022,3s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%023,3s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%,3s"); + writeAligned(w, "pre", "grouping", "suf", spec); + assert(w.data == "pregr,oup,ingsuf", w.data); +} + +@safe pure unittest +{ + import std.array : appender; + import std.format : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%.10s"); + writeAligned(w, "pre", "grouping", "suf", spec, true); + assert(w.data == "pre00groupingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%.10,3s"); + writeAligned(w, "pre", "grouping", "suf", spec, true); + assert(w.data == "pre0,0gr,oup,ingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%25.10,3s"); + writeAligned(w, "pre", "grouping", "suf", spec, true); + assert(w.data == " pre0,0gr,oup,ingsuf", w.data); + + // precision has precedence over zero flag + w = appender!string(); + spec = singleSpec("%025.12,3s"); + writeAligned(w, "pre", "grouping", "suf", spec, true); + assert(w.data == " pre000,0gr,oup,ingsuf", w.data); + + w = appender!string(); + spec = singleSpec("%025.13,3s"); + writeAligned(w, "pre", "grouping", "suf", spec, true); + assert(w.data == " pre0,000,0gr,oup,ingsuf", w.data); +} + +@safe unittest +{ + assert(format("%,d", 1000) == "1,000"); + assert(format("%,f", 1234567.891011) == "1,234,567.891011"); + 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) ~ "'"); +} + +private long getWidth(T)(T s) +{ + import std.algorithm.searching : all; + import std.uni : graphemeStride; + + // check for non-ascii character + if (s.all!(a => a <= 0x7F)) return s.length; + + //TODO: optimize this + long width = 0; + for (size_t i; i < s.length; i += graphemeStride(s, i)) + ++width; + return width; +} + +enum RoundingClass { ZERO, LOWER, FIVE, UPPER } +enum RoundingMode { up, down, toZero, toNearestTiesToEven, toNearestTiesAwayFromZero } + +bool round(T)(ref T sequence, size_t left, size_t right, RoundingClass type, bool negative, char max = '9') +in (left >= 0) // should be left > 0, but if you know ahead, that there's no carry, left == 0 is fine +in (left < sequence.length) +in (right >= 0) +in (right <= sequence.length) +in (right >= left) +in (max == '9' || max == 'f' || max == 'F') +{ + import std.math.hardware; + + auto mode = RoundingMode.toNearestTiesToEven; + + if (!__ctfe) + { + // std.math's FloatingPointControl isn't available on all target platforms + static if (is(FloatingPointControl)) + { + switch (FloatingPointControl.rounding) + { + case FloatingPointControl.roundUp: + mode = RoundingMode.up; + break; + case FloatingPointControl.roundDown: + mode = RoundingMode.down; + break; + case FloatingPointControl.roundToZero: + mode = RoundingMode.toZero; + break; + case FloatingPointControl.roundToNearest: + mode = RoundingMode.toNearestTiesToEven; + break; + default: assert(false, "Unknown floating point rounding mode"); + } + } + } + + bool roundUp = false; + if (mode == RoundingMode.up) + roundUp = type != RoundingClass.ZERO && !negative; + else if (mode == RoundingMode.down) + roundUp = type != RoundingClass.ZERO && negative; + else if (mode == RoundingMode.toZero) + roundUp = false; + else + { + roundUp = type == RoundingClass.UPPER; + + if (type == RoundingClass.FIVE) + { + // IEEE754 allows for two different ways of implementing roundToNearest: + + if (mode == RoundingMode.toNearestTiesAwayFromZero) + roundUp = true; + else + { + // Round to nearest, ties to even + auto last = sequence[right - 1]; + if (last == '.') last = sequence[right - 2]; + roundUp = (last <= '9' && last % 2 != 0) || (last > '9' && last % 2 == 0); + } + } + } + + if (!roundUp) return false; + + foreach_reverse (i;left .. right) + { + if (sequence[i] == '.') continue; + if (sequence[i] == max) + sequence[i] = '0'; + else + { + if (max != '9' && sequence[i] == '9') + sequence[i] = max == 'f' ? 'a' : 'A'; + else + sequence[i]++; + return false; + } + } + + sequence[left - 1] = '1'; + return true; +} + +@safe unittest +{ + char[10] c; + size_t left = 5; + size_t right = 8; + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.UPPER, false) == true); + assert(c[4 .. 8] == "1.00"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.FIVE, false) == true); + assert(c[4 .. 8] == "1.00"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.LOWER, false) == false); + assert(c[4 .. 8] == "x.99"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.ZERO, false) == false); + assert(c[4 .. 8] == "x.99"); + + import std.math.hardware; + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundUp; + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.UPPER, false) == true); + assert(c[4 .. 8] == "1.00"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.FIVE, false) == true); + assert(c[4 .. 8] == "1.00"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.LOWER, false) == true); + assert(c[4 .. 8] == "1.00"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.ZERO, false) == false); + assert(c[4 .. 8] == "x.99"); + + fpctrl.rounding = FloatingPointControl.roundDown; + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.UPPER, false) == false); + assert(c[4 .. 8] == "x.99"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.FIVE, false) == false); + assert(c[4 .. 8] == "x.99"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.LOWER, false) == false); + assert(c[4 .. 8] == "x.99"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.ZERO, false) == false); + assert(c[4 .. 8] == "x.99"); + + fpctrl.rounding = FloatingPointControl.roundToZero; + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.UPPER, false) == false); + assert(c[4 .. 8] == "x.99"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.FIVE, false) == false); + assert(c[4 .. 8] == "x.99"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.LOWER, false) == false); + assert(c[4 .. 8] == "x.99"); + + c[4 .. 8] = "x.99"; + assert(round(c, left, right, RoundingClass.ZERO, false) == false); + assert(c[4 .. 8] == "x.99"); + } +} + +@safe unittest +{ + char[10] c; + size_t left = 5; + size_t right = 8; + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.UPPER, true) == false); + assert(c[4 .. 8] == "x8.6"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.FIVE, true) == false); + assert(c[4 .. 8] == "x8.6"); + + c[4 .. 8] = "x8.4"; + assert(round(c, left, right, RoundingClass.FIVE, true) == false); + assert(c[4 .. 8] == "x8.4"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.LOWER, true) == false); + assert(c[4 .. 8] == "x8.5"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.ZERO, true) == false); + assert(c[4 .. 8] == "x8.5"); + + import std.math.hardware; + static if (is(FloatingPointControl)) + { + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundUp; + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.UPPER, true) == false); + assert(c[4 .. 8] == "x8.5"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.FIVE, true) == false); + assert(c[4 .. 8] == "x8.5"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.LOWER, true) == false); + assert(c[4 .. 8] == "x8.5"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.ZERO, true) == false); + assert(c[4 .. 8] == "x8.5"); + + fpctrl.rounding = FloatingPointControl.roundDown; + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.UPPER, true) == false); + assert(c[4 .. 8] == "x8.6"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.FIVE, true) == false); + assert(c[4 .. 8] == "x8.6"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.LOWER, true) == false); + assert(c[4 .. 8] == "x8.6"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.ZERO, true) == false); + assert(c[4 .. 8] == "x8.5"); + + fpctrl.rounding = FloatingPointControl.roundToZero; + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.UPPER, true) == false); + assert(c[4 .. 8] == "x8.5"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.FIVE, true) == false); + assert(c[4 .. 8] == "x8.5"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.LOWER, true) == false); + assert(c[4 .. 8] == "x8.5"); + + c[4 .. 8] = "x8.5"; + assert(round(c, left, right, RoundingClass.ZERO, true) == false); + assert(c[4 .. 8] == "x8.5"); + } +} + +@safe unittest +{ + char[10] c; + size_t left = 5; + size_t right = 8; + + c[4 .. 8] = "x8.9"; + assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false); + assert(c[4 .. 8] == "x8.a"); + + c[4 .. 8] = "x8.9"; + assert(round(c, left, right, RoundingClass.UPPER, true, 'F') == false); + assert(c[4 .. 8] == "x8.A"); + + c[4 .. 8] = "x8.f"; + assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false); + assert(c[4 .. 8] == "x9.0"); +} + +version (StdUnittest) +private void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__) +{ + formatTest(val, [expected], ln, fn); +} + +version (StdUnittest) +private void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe +{ + formatTest(fmt, val, [expected], ln, fn); +} + +version (StdUnittest) +private void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) +{ + import core.exception : AssertError; + import std.algorithm.searching : canFind; + import std.array : appender; + import std.conv : text; + import std.exception : enforce; + import std.format.write : formatValue; + + FormatSpec!char f; + auto w = appender!string(); + formatValue(w, val, f); + enforce!AssertError(expected.canFind(w.data), + text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); +} + +version (StdUnittest) +private void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe +{ + import core.exception : AssertError; + import std.algorithm.searching : canFind; + import std.array : appender; + import std.conv : text; + import std.exception : enforce; + import std.format.write : formattedWrite; + + auto w = appender!string(); + formattedWrite(w, fmt, val); + enforce!AssertError(expected.canFind(w.data), + text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); +} diff --git a/libphobos/src/std/format/package.d b/libphobos/src/std/format/package.d new file mode 100644 index 0000000..2d57e48 --- /dev/null +++ b/libphobos/src/std/format/package.d @@ -0,0 +1,1787 @@ +// Written in the D programming language. + +/** +This package provides string formatting functionality using +`printf` style format strings. + +$(BOOKTABLE , +$(TR $(TH Submodule) $(TH Function Name) $(TH Description)) +$(TR + $(TD $(I package)) + $(TD $(LREF format)) + $(TD Converts its arguments according to a format string into a string.) +) +$(TR + $(TD $(I package)) + $(TD $(LREF sformat)) + $(TD Converts its arguments according to a format string into a buffer.) +) +$(TR + $(TD $(I package)) + $(TD $(LREF FormatException)) + $(TD Signals a problem while formatting.) +) +$(TR + $(TD $(MREF_ALTTEXT $(D write), std, format, write)) + $(TD $(REF_ALTTEXT $(D formattedWrite), formattedWrite, std, format, write)) + $(TD Converts its arguments according to a format string and writes + the result to an output range.) +) +$(TR + $(TD $(MREF_ALTTEXT $(D write), std, format, write)) + $(TD $(REF_ALTTEXT $(D formatValue), formatValue, std, format, write)) + $(TD Formats a value of any type according to a format specifier and + writes the result to an output range.) +) +$(TR + $(TD $(MREF_ALTTEXT $(D read), std, format, read)) + $(TD $(REF_ALTTEXT $(D formattedRead), formattedRead, std, format, read)) + $(TD Reads an input range according to a format string and stores the read + values into its arguments.) +) +$(TR + $(TD $(MREF_ALTTEXT $(D read), std, format, read)) + $(TD $(REF_ALTTEXT $(D unformatValue), unformatValue, std, format, read)) + $(TD Reads a value from the given input range and converts it according to + a format specifier.) +) +$(TR + $(TD $(MREF_ALTTEXT $(D spec), std, format, spec)) + $(TD $(REF_ALTTEXT $(D FormatSpec), FormatSpec, std, format, spec)) + $(TD A general handler for format strings.) +) +$(TR + $(TD $(MREF_ALTTEXT $(D spec), std, format, spec)) + $(TD $(REF_ALTTEXT $(D singleSpec), singleSpec, std, format, spec)) + $(TD Helper function that returns a `FormatSpec` for a single format specifier.) +)) + +Limitation: This package does not support localization, but + adheres to the rounding mode of the floating point unit, if + available. + +$(SECTION3 Format Strings) + +The functions contained in this package use $(I format strings). A +format string describes the layout of another string for reading or +writing purposes. A format string is composed of normal text +interspersed with $(I format specifiers). A format specifier starts +with a percentage sign $(B '%'), optionally followed by one or more +$(I parameters) and ends with a $(I format indicator). A format +indicator may be a simple $(I format character) or a $(I compound +indicator). + +$(I Format strings) are composed according to the following grammar: + +$(PRE +$(I FormatString): + $(I FormatStringItem) $(I FormatString) +$(I FormatStringItem): + $(I Character) + $(I FormatSpecifier) +$(I FormatSpecifier): + $(B '%') $(I Parameters) $(I FormatIndicator) + +$(I FormatIndicator): + $(I FormatCharacter) + $(I CompoundIndicator) +$(I FormatCharacter): + $(I see remark below) +$(I CompoundIndicator): + $(B '$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') + $(B '$(LPAREN)') $(I FormatString) $(B '%|') $(I Delimiter) $(B '%$(RPAREN)') +$(I Delimiter) + $(I empty) + $(I Character) $(I Delimiter) + +$(I Parameters): + $(I Position) $(I Flags) $(I Width) $(I Precision) $(I Separator) +$(I Position): + $(I empty) + $(I Integer) $(B '$') + $(I Integer) $(B ':') $(I Integer) $(B '$') + $(I Integer) $(B ':') $(B '$') +$(I Flags): + $(I empty) + $(I Flag) $(I Flags) +$(I Flag): + $(B '-')|$(B '+')|$(B ' ')|$(B '0')|$(B '#')|$(B '=') +$(I Width): + $(I OptionalPositionalInteger) +$(I Precision): + $(I empty) + $(B '.') $(I OptionalPositionalInteger) +$(I Separator): + $(I empty) + $(B ',') $(I OptionalInteger) + $(B ',') $(I OptionalInteger) $(B '?') +$(I OptionalInteger): + $(I empty) + $(I Integer) + $(B '*') +$(I OptionalPositionalInteger): + $(I OptionalInteger) + $(B '*') $(I Integer) $(B '$') + +$(I Character) + $(B '%%') + $(I AnyCharacterExceptPercent) +$(I Integer): + $(I NonZeroDigit) $(I Digits) +$(I Digits): + $(I empty) + $(I Digit) $(I Digits) +$(I NonZeroDigit): + $(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') +$(I Digit): + $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') +) + +Note: $(I FormatCharacter) is unspecified. It can be any character +that has no other purpose in this grammar, but it is +recommended to assign (lower- and uppercase) letters. + +Note: The $(I Parameters) of a $(I CompoundIndicator) are currently +limited to a $(B '-') flag. + +$(SECTION4 Format Indicator) + +The $(I format indicator) can either be a single character or an +expression surrounded by $(B %\() and $(B %\)). It specifies the +basic manner in which a value will be formatted and is the minimum +requirement to format a value. + +The following characters can be used as $(I format characters): + +$(BOOKTABLE , + $(TR $(TH FormatCharacter) $(TH Semantics)) + $(TR $(TD $(B 's')) + $(TD To be formatted in a human readable format. + Can be used with all types.)) + $(TR $(TD $(B 'c')) + $(TD To be formatted as a character.)) + $(TR $(TD $(B 'd')) + $(TD To be formatted as a signed decimal integer.)) + $(TR $(TD $(B 'u')) + $(TD To be formatted as a decimal image of the underlying bit representation.)) + $(TR $(TD $(B 'b')) + $(TD To be formatted as a binary image of the underlying bit representation.)) + $(TR $(TD $(B 'o')) + $(TD To be formatted as an octal image of the underlying bit representation.)) + $(TR $(TD $(B 'x') / $(B 'X')) + $(TD To be formatted as a hexadecimal image of the underlying bit representation.)) + $(TR $(TD $(B 'e') / $(B 'E')) + $(TD To be formatted as a real number in decimal scientific notation.)) + $(TR $(TD $(B 'f') / $(B 'F')) + $(TD To be formatted as a real number in decimal natural notation.)) + $(TR $(TD $(B 'g') / $(B 'G')) + $(TD To be formatted as a real number in decimal short notation. + Depending on the number, a scientific notation or + a natural notation is used.)) + $(TR $(TD $(B 'a') / $(B 'A')) + $(TD To be formatted as a real number in hexadezimal scientific notation.)) + $(TR $(TD $(B 'r')) + $(TD To be formatted as raw bytes. + The output may not be printable and depends on endianess.)) +) + +The $(I compound indicator) can be used to describe compound types +like arrays or structs in more detail. A compound type is enclosed +within $(B '%\(') and $(B '%\)'). The enclosed sub-format string is +applied to individual elements. The trailing portion of the +sub-format string following the specifier for the element is +interpreted as the delimiter, and is therefore omitted following the +last 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 element. + +The $(I format string) inside of the $(I compound indicator) should +contain exactly one $(I format specifier) (two in case of associative +arrays), which specifies the formatting mode of the elements of the +compound type. This $(I format specifier) can be a $(I compound +indicator) itself. + +Note: Inside a $(I compound indicator), strings and characters are +escaped automatically. To avoid this behavior, use `"%-$(LPAREN)"` +instead of `"%$(LPAREN)"`. + +$(SECTION4 Flags) + +There are several flags that affect the outcome of the formatting. + +$(BOOKTABLE , + $(TR $(TH Flag) $(TH Semantics)) + $(TR $(TD $(B '-')) + $(TD When the formatted result is shorter then the value + given by the width parameter, the output is right + justified. With the $(B '-') flag this is changed + to left justification. + + There are two exceptions where the $(B '-') flag has a + different meaning: (1) with $(B 'r') it denotes to use little + endian and (2) in case of a compound indicator it means that + no special handling of the members is applied.)) + $(TR $(TD $(B '=')) + $(TD When the formatted result is shorter then the value + given by the width parameter, the output is centered. + If the central position is not possible it is moved slightly + to the right. In this case, if $(B '-') flag is present in + addition to the $(B '=') flag, it is moved slightly to the left.)) + $(TR $(TD $(B '+') / $(B ' ')) + $(TD Applies to numerical values. By default, positive numbers are not + formatted to include the `+` sign. With one of these two flags present, + positive numbers are preceded by a plus sign or a space. + When both flags are present, a plus sign is used. + + In case of $(B 'r'), a big endian format is used.)) + $(TR $(TD $(B '0')) + $(TD Is applied to numerical values that are printed right justified. + If the zero flag is present, the space left to the number is + filled with zeros instead of spaces.)) + $(TR $(TD $(B '#')) + $(TD Denotes that an alternative output must be used. This depends on the type + to be formatted and the $(I format character) used. See the + sections below for more information.)) +) + +$(SECTION4 Width$(COMMA) Precision and Separator) + +The $(I width) parameter specifies the minimum width of the result. + +The meaning of $(I precision) depends on the format indicator. For +integers it denotes the minimum number of digits printed, for +real numbers it denotes the number of fractional digits and for +strings and compound types it denotes the maximum number of elements +that are included in the output. + +A $(I separator) is used for formatting numbers. If it is specified, +the output is divided into chunks of three digits, separated by a $(B +','). The number of digits in a chunk can be given explicitly by +providing a number or a $(B '*') after the $(B ','). + +In all three cases the number of digits can be replaced by a $(B +'*'). In this scenario, the next argument is used as the number of +digits. If the argument is a negative number, the $(I precision) and +$(I separator) parameters are considered unspecified. For $(I width), +the absolute value is used and the $(B '-') flag is set. + +The $(I separator) can also be followed by a $(B '?'). In that case, +an additional argument is used to specify the symbol that should be +used to separate the chunks. + +$(SECTION4 Position) + +By default, the arguments are processed in the provided order. With +the $(I position) parameter it is possible to address arguments +directly. It is also possible to denote a series of arguments with +two numbers separated by $(B ':'), that are all processed in the same +way. The second number can be omitted. In that case the series ends +with the last argument. + +It's also possible to use positional arguments for $(I width), $(I +precision) and $(I separator) by adding a number and a $(B +'$(DOLLAR)') after the $(B '*'). + +$(SECTION4 Types) + +This section describes the result of combining types with format +characters. It is organized in 2 subsections: a list of general +information regarding the formatting of types in the presence of +format characters and a table that contains details for every +available combination of type and format character. + +When formatting types, the following rules apply: + +$(UL + $(LI If the format character is upper case, the resulting string will + be formatted using upper case letters.) + $(LI The default precision for floating point numbers is 6 digits.) + $(LI Rounding of floating point numbers adheres to the rounding mode + of the floating point unit, if available.) + $(LI The floating point values `NaN` and `Infinity` are formatted as + `nan` and `inf`, possibly preceded by $(B '+') or $(B '-') sign.) + $(LI Formatting reals is only supported for 64 bit reals and 80 bit reals. + All other reals are cast to double before they are formatted. This will + cause the result to be `inf` for very large numbers.) + $(LI Characters and strings formatted with the $(B 's') format character + inside of compound types are surrounded by single and double quotes + and unprintable characters are escaped. To avoid this, a $(B '-') + flag can be specified for the compound specifier + $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"` instead of `"%$(LPAREN)%s%$(RPAREN)"` $(RPAREN).) + $(LI Structs, unions, classes and interfaces are formatted by calling a + `toString` method if available. + See $(MREF_ALTTEXT $(D module std.format.write), std, format, write) for more + details.) + $(LI Only part of these combinations can be used for reading. See + $(MREF_ALTTEXT $(D module std.format.read), std, format, read) for more + detailed information.) +) + +This table contains descriptions for every possible combination of +type and format character: + +$(BOOKTABLE , + $(TR $(THMINWIDTH Type) $(THMINWIDTH Format Character) $(TH Formatted as...)) + $(TR $(MULTIROW_CELL 1, `null`) + $(TD $(B 's')) + $(TD `null`) + ) + $(TR $(MULTIROW_CELL 3, `bool`) + $(TD $(B 's')) + $(TD `false` or `true`) + ) + $(TR $(TD $(B 'b'), $(B 'd'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) + $(TD As the integrals 0 or 1 with the same format character. + + $(I Please note, that $(B 'o') and $(B 'x') with $(B '#') flag + might produce unexpected results due to special handling of + the value 0.)) + ) + $(TR $(TD $(B 'r')) + $(TD `\0` or `\1`) + ) + $(TR $(MULTIROW_CELL 4, $(I Integral)) + $(TD $(B 's'), $(B 'd')) + $(TD A signed decimal number. The $(B '#') flag is ignored.) + ) + $(TR $(TD $(B 'b'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) + $(TD An unsigned binary, decimal, octal or hexadecimal number. + + In case of $(B 'o') and $(B 'x'), the $(B '#') flag + denotes that the number must be preceded by `0` and `0x`, with + the exception of the value 0, where this does not apply. For + $(B 'b') and $(B 'u') the $(B '#') flag has no effect.) + ) + $(TR $(TD $(B 'e'), $(B 'E'), $(B 'f'), $(B 'F'), $(B 'g'), $(B 'G'), $(B 'a'), $(B 'A')) + $(TD As a floating point value with the same specifier. + + Default precision is large enough to add all digits + of the integral value. + + In case of ($B 'a') and $(B 'A'), the integral digit can be + any hexadecimal digit. + ) + ) + $(TR $(TD $(B 'r')) + $(TD Characters taken directly from the binary representation.) + ) + $(TR $(MULTIROW_CELL 5, $(I Floating Point)) + $(TD $(B 'e'), $(B 'E')) + $(TD Scientific notation: Exactly one integral digit followed by a dot + and fractional digits, followed by the exponent. + The exponent is formatted as $(B 'e') followed by + a $(B '+') or $(B '-') sign, followed by at least + two digits. + + When there are no fractional digits and the $(B '#') flag + is $(I not) present, the dot is omitted.) + ) + $(TR $(TD $(B 'f'), $(B 'F')) + $(TD Natural notation: Integral digits followed by a dot and + fractional digits. + + When there are no fractional digits and the $(B '#') flag + is $(I not) present, the dot is omitted. + + $(I Please note: the difference between $(B 'f') and $(B 'F') + is only visible for `NaN` and `Infinity`.)) + ) + $(TR $(TD $(B 's'), $(B 'g'), $(B 'G')) + $(TD Short notation: If the absolute value is larger than `10 ^^ precision` + or smaller than `0.0001`, the scientific notation is used. + If not, the natural notation is applied. + + In both cases $(I precision) denotes the count of all digits, including + the integral digits. Trailing zeros (including a trailing dot) are removed. + + If $(B '#') flag is present, trailing zeros are not removed.) + ) + $(TR $(TD $(B 'a'), $(B 'A')) + $(TD Hexadecimal scientific notation: `0x` followed by `1` + (or `0` in case of value zero or denormalized number) + followed by a dot, fractional digits in hexadecimal + notation and an exponent. The exponent is build by `p`, + followed by a sign and the exponent in $(I decimal) notation. + + When there are no fractional digits and the $(B '#') flag + is $(I not) present, the dot is omitted.) + ) + $(TR $(TD $(B 'r')) + $(TD Characters taken directly from the binary representation.) + ) + $(TR $(MULTIROW_CELL 3, $(I Character)) + $(TD $(B 's'), $(B 'c')) + $(TD As the character. + + Inside of a compound indicator $(B 's') is treated differently: The + character is surrounded by single quotes and non printable + characters are escaped. This can be avoided by preceding + the compound indicator with a $(B '-') flag + $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"`$(RPAREN).) + ) + $(TR $(TD $(B 'b'), $(B 'd'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) + $(TD As the integral that represents the character.) + ) + $(TR $(TD $(B 'r')) + $(TD Characters taken directly from the binary representation.) + ) + $(TR $(MULTIROW_CELL 3, $(I String)) + $(TD $(B 's')) + $(TD The sequence of characters that form the string. + + Inside of a compound indicator the string is surrounded by double quotes + and non printable characters are escaped. This can be avoided + by preceding the compound indicator with a $(B '-') flag + $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"`$(RPAREN).) + ) + $(TR $(TD $(B 'r')) + $(TD The sequence of characters, each formatted with $(B 'r').) + ) + $(TR $(TD compound) + $(TD As an array of characters.) + ) + $(TR $(MULTIROW_CELL 3, $(I Array)) + $(TD $(B 's')) + $(TD When the elements are characters, the array is formatted as + a string. In all other cases the array is surrounded by square brackets + and the elements are separated by a comma and a space. If the elements + are strings, they are surrounded by double quotes and non + printable characters are escaped.) + ) + $(TR $(TD $(B 'r')) + $(TD The sequence of the elements, each formatted with $(B 'r').) + ) + $(TR $(TD compound) + $(TD The sequence of the elements, each formatted according to the specifications + given inside of the compound specifier.) + ) + $(TR $(MULTIROW_CELL 2, $(I Associative Array)) + $(TD $(B 's')) + $(TD As a sequence of the elements in unpredictable order. The output is + surrounded by square brackets. The elements are separated by a + comma and a space. The elements are formatted as `key:value`.) + ) + $(TR $(TD compound) + $(TD As a sequence of the elements in unpredictable order. Each element + is formatted according to the specifications given inside of the + compound specifier. The first specifier is used for formatting + the key and the second specifier is used for formatting the value. + The order can be changed with positional arguments. For example + `"%(%2$s (%1$s), %)"` will write the value, followed by the key in + parenthesis.) + ) + $(TR $(MULTIROW_CELL 2, $(I Enum)) + $(TD $(B 's')) + $(TD The name of the value. If the name is not available, the base value + is used, preceeded by a cast.) + ) + $(TR $(TD All, but $(B 's')) + $(TD Enums can be formatted with all format characters that can be used + with the base value. In that case they are formatted like the base value.) + ) + $(TR $(MULTIROW_CELL 3, $(I Input Range)) + $(TD $(B 's')) + $(TD When the elements of the range are characters, they are written like a string. + In all other cases, the elements are enclosed by square brackets and separated + by a comma and a space.) + ) + $(TR $(TD $(B 'r')) + $(TD The sequence of the elements, each formatted with $(B 'r').) + ) + $(TR $(TD compound) + $(TD The sequence of the elements, each formatted according to the specifications + given inside of the compound specifier.) + ) + $(TR $(MULTIROW_CELL 1, $(I Struct)) + $(TD $(B 's')) + $(TD When the struct has neither an applicable `toString` + nor is an input range, it is formatted as follows: + `StructType(field1, field2, ...)`.) + ) + $(TR $(MULTIROW_CELL 1, $(I Class)) + $(TD $(B 's')) + $(TD When the class has neither an applicable `toString` + nor is an input range, it is formatted as the + fully qualified name of the class.) + ) + $(TR $(MULTIROW_CELL 1, $(I Union)) + $(TD $(B 's')) + $(TD When the union has neither an applicable `toString` + nor is an input range, it is formatted as its base name.) + ) + $(TR $(MULTIROW_CELL 2, $(I Pointer)) + $(TD $(B 's')) + $(TD A null pointer is formatted as 'null'. All other pointers are + formatted as hexadecimal numbers with the format character $(B 'X').) + ) + $(TR $(TD $(B 'x'), $(B 'X')) + $(TD Formatted as a hexadecimal number.) + ) + $(TR $(MULTIROW_CELL 3, $(I SIMD vector)) + $(TD $(B 's')) + $(TD The array is surrounded by square brackets + and the elements are separated by a comma and a space.) + ) + $(TR $(TD $(B 'r')) + $(TD The sequence of the elements, each formatted with $(B 'r').) + ) + $(TR $(TD compound) + $(TD The sequence of the elements, each formatted according to the specifications + given inside of the compound specifier.) + ) + $(TR $(MULTIROW_CELL 1, $(I Delegate)) + $(TD $(B 's'), $(B 'r'), compound) + $(TD As the `.stringof` of this delegate treated as a string. + + $(I Please note: The implementation is currently buggy + and its use is discouraged.)) + ) +) + +Copyright: Copyright The D Language Foundation 2000-2021. + +Macros: +SUBREF = $(REF_ALTTEXT $2, $2, std, format, $1)$(NBSP) +MULTIROW_CELL = $+ +THMINWIDTH = $0 + +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; + +/// Simple use: +@safe unittest +{ + // Easiest way is to use `%s` everywhere: + assert(format("I got %s %s for %s euros.", 30, "eggs", 5.27) == "I got 30 eggs for 5.27 euros."); + + // Other format characters provide more control: + assert(format("I got %b %(%X%) for %f euros.", 30, "eggs", 5.27) == "I got 11110 65676773 for 5.270000 euros."); +} + +/// Compound specifiers allow formatting arrays and other compound types: +@safe unittest +{ +/* +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: + */ + assert(format("My items are %(%s %).", [1,2,3]) == "My items are 1 2 3."); + assert(format("My items are %(%s, %).", [1,2,3]) == "My items are 1, 2, 3."); + +/* +The "%|" 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: + */ + assert(format("My items are %(-%s-%|, %).", [1,2,3]) == "My items are -1-, -2-, -3-."); + +/* +These compound format specifiers may be nested in the case of a +nested array argument: + */ + auto mat = [[1, 2, 3], + [4, 5, 6], + [7, 8, 9]]; + + assert(format("%(%(%d %) - %)", mat), "1 2 3 - 4 5 6 - 7 8 9"); + assert(format("[%(%(%d %) - %)]", mat), "[1 2 3 - 4 5 6 - 7 8 9]"); + assert(format("[%([%(%d %)]%| - %)]", mat), "[1 2 3] - [4 5 6] - [7 8 9]"); + +/* +Strings and characters are escaped automatically inside compound +format specifiers. To avoid this behavior, use "%-(" instead of "%(": + */ + assert(format("My friends are %s.", ["John", "Nancy"]) == `My friends are ["John", "Nancy"].`); + assert(format("My friends are %(%s, %).", ["John", "Nancy"]) == `My friends are "John", "Nancy".`); + assert(format("My friends are %-(%s, %).", ["John", "Nancy"]) == `My friends are John, Nancy.`); +} + +/// Using parameters: +@safe unittest +{ + // Flags can be used to influence to outcome: + assert(format("%g != %+#g", 3.14, 3.14) == "3.14 != +3.14000"); + + // Width and precision help to arrange the formatted result: + assert(format(">%10.2f<", 1234.56789) == "> 1234.57<"); + + // Numbers can be grouped: + assert(format("%,4d", int.max) == "21,4748,3647"); + + // It's possible to specify the position of an argument: + assert(format("%3$s %1$s", 3, 17, 5) == "5 3"); +} + +/// Providing parameters as arguments: +@safe unittest +{ + // Width as argument + assert(format(">%*s<", 10, "abc") == "> abc<"); + + // Precision as argument + assert(format(">%.*f<", 5, 123.2) == ">123.20000<"); + + // Grouping as argument + assert(format("%,*d", 1, int.max) == "2,1,4,7,4,8,3,6,4,7"); + + // Grouping separator as argument + assert(format("%,3?d", '_', int.max) == "2_147_483_647"); + + // All at once + assert(format("%*.*,*?d", 20, 15, 6, '/', int.max) == " 000/002147/483647"); +} + +public import std.format.read; +public import std.format.spec; +public import std.format.write; + +import std.exception : enforce; +import std.range.primitives : isInputRange; +import std.traits : CharTypeOf, isSomeChar, isSomeString, StringTypeOf; +import std.format.internal.write : hasToString; + +/** +Signals an issue encountered while formatting. + */ +class FormatException : Exception +{ + /// Generic constructor. + @safe @nogc pure nothrow + this() + { + super("format error"); + } + + /** + Creates a new instance of `FormatException`. + + Params: + msg = message of the exception + fn = file name of the file where the exception was created (optional) + ln = line number of the file where the exception was created (optional) + next = for internal use, should always be null (optional) + */ + @safe @nogc pure nothrow + this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) + { + super(msg, fn, ln, next); + } +} + +/// +@safe unittest +{ + import std.exception : assertThrown; + + assertThrown!FormatException(format("%d", "foo")); +} + +package alias enforceFmt = enforce!FormatException; + +// @@@DEPRECATED_[2.107.0]@@@ +deprecated("formatElement was accidentally made public and will be removed in 2.107.0") +void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) +if (is(StringTypeOf!T) && !hasToString!(T, Char) && !is(T == enum)) +{ + import std.format.internal.write : fe = formatElement; + + fe(w, val, f); +} + +// @@@DEPRECATED_[2.107.0]@@@ +deprecated("formatElement was accidentally made public and will be removed in 2.107.0") +void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) +if (is(CharTypeOf!T) && !is(T == enum)) +{ + import std.format.internal.write : fe = formatElement; + + fe(w, val, f); +} + +// @@@DEPRECATED_[2.107.0]@@@ +deprecated("formatElement was accidentally made public and will be removed in 2.107.0") +void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) +if ((!is(StringTypeOf!T) || hasToString!(T, Char)) && !is(CharTypeOf!T) || is(T == enum)) +{ + import std.format.internal.write : fe = formatElement; + + fe(w, val, f); +} + +// Like NullSink, but toString() isn't even called at all. Used to test the format string. +package struct NoOpSink +{ + void put(E)(scope const E) pure @safe @nogc nothrow {} +} + +// @@@DEPRECATED_[2.107.0]@@@ +deprecated("unformatElement was accidentally made public and will be removed in 2.107.0") +T unformatElement(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) +if (isInputRange!Range) +{ + import std.format.internal.read : ue = unformatElement; + + return ue(input, spec); +} + +// Used to check format strings are compatible with argument types +package(std) enum checkFormatException(alias fmt, Args...) = +{ + import std.conv : text; + + try + { + auto n = .formattedWrite(NoOpSink(), fmt, Args.init); + + enforceFmt(n == Args.length, text("Orphan format arguments: args[", n, "..", Args.length, "]")); + } + catch (Exception e) + return e.msg; + return null; +}(); + +/** +Converts its arguments according to a format string into a string. + +The second version of `format` takes the format string as template +argument. In this case, it is checked for consistency at +compile-time and produces slightly faster code, because the length of +the output buffer can be estimated in advance. + +Params: + fmt = a $(MREF_ALTTEXT format string, std,format) + args = a variadic list of arguments to be formatted + Char = character type of `fmt` + Args = a variadic list of types of the arguments + +Returns: + The formatted string. + +Throws: + A $(LREF FormatException) if formatting did not succeed. + +See_Also: + $(LREF sformat) for a variant, that tries to avoid garbage collection. + */ +immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) +if (isSomeChar!Char) +{ + import std.array : appender; + + 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; + enforceFmt(n == args.length, text("Orphan format arguments: args[", n, "..", args.length, "]")); + } + return w.data; +} + +/// +@safe pure unittest +{ + assert(format("Here are %d %s.", 3, "apples") == "Here are 3 apples."); + + assert("Increase: %7.2f %%".format(17.4285) == "Increase: 17.43 %"); +} + +@safe pure unittest +{ + import std.exception : assertCTFEable, assertThrown; + + assertCTFEable!( + { + 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 pure unittest +{ + assert(format("%.2f"d, 0.4) == "0.40"); + assert("%02d"d.format(1) == "01"d); +} + +@safe unittest +{ + int i; + string s; + + 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); + 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"); +} + +@safe unittest +{ + import std.conv : octal; + + string s; + int i; + + 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 == "0"); + + 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); + + // Empty static character arrays work as well + const char[0] cempty; + assert(format("test%spath", cempty) == "testpath"); + const wchar[0] wempty; + assert(format("test%spath", wempty) == "testpath"); + const dchar[0] dempty; + assert(format("test%spath", dempty) == "testpath"); + + void* p = () @trusted { return 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); // https://issues.dlang.org/show_bug.cgi?id=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 = () @trusted { return 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 = () @trusted { return 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 +{ + import std.exception : assertCTFEable; + + 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.000000", "'" ~ tmp ~ "'"); + + tmp = format("%,f", 1234567.891011); + assert(tmp == "1,234,567.891011", "'" ~ tmp ~ "'"); + + tmp = format("%,f", -1234567.891011); + assert(tmp == "-1,234,567.891011", "'" ~ tmp ~ "'"); + + tmp = format("%,2f", 1234567.891011); + assert(tmp == "1,23,45,67.891011", "'" ~ tmp ~ "'"); + + tmp = format("%18,f", 1234567.891011); + assert(tmp == " 1,234,567.891011", "'" ~ tmp ~ "'"); + + tmp = format("%18,?f", '.', 1234567.891011); + assert(tmp == " 1.234.567.891011", "'" ~ 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.891", "'" ~ 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.141593e+00", "'" ~ tmp ~ "'"); + + tmp = format("%15,e", 3.141592653589793238462); + assert(tmp == " 3.141593e+00", "'" ~ tmp ~ "'"); + + tmp = format("%15,e", -3.141592653589793238462); + assert(tmp == " -3.141593e+00", "'" ~ tmp ~ "'"); + + tmp = format("%.4,*e", 2, 3.141592653589793238462); + assert(tmp == "3.1416e+00", "'" ~ tmp ~ "'"); + + tmp = format("%13.4,*e", 2, 3.141592653589793238462); + assert(tmp == " 3.1416e+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.891011", "'" ~ tmp ~ "'"); +} + +// Test for multiple indexes +@safe unittest +{ + auto tmp = format("%2:5$s", 1, 2, 3, 4, 5); + assert(tmp == "2345", tmp); +} + +// https://issues.dlang.org/show_bug.cgi?id=18047 +@safe unittest +{ + auto cmp = " 123,456"; + assert(cmp.length == 12, format("%d", cmp.length)); + auto tmp = format("%12,d", 123456); + assert(tmp.length == 12, format("%d", tmp.length)); + + assert(tmp == cmp, "'" ~ tmp ~ "'"); +} + +// https://issues.dlang.org/show_bug.cgi?id=17459 +@safe unittest +{ + auto cmp = "100"; + auto tmp = format("%0d", 100); + assert(tmp == cmp, tmp); + + cmp = "0100"; + tmp = format("%04d", 100); + assert(tmp == cmp, tmp); + + cmp = "0,000,000,100"; + tmp = format("%012,3d", 100); + assert(tmp == cmp, tmp); + + cmp = "0,000,001,000"; + tmp = format("%012,3d", 1_000); + assert(tmp == cmp, tmp); + + cmp = "0,000,100,000"; + tmp = format("%012,3d", 100_000); + assert(tmp == cmp, tmp); + + cmp = "0,001,000,000"; + tmp = format("%012,3d", 1_000_000); + assert(tmp == cmp, tmp); + + cmp = "0,100,000,000"; + tmp = format("%012,3d", 100_000_000); + assert(tmp == cmp, tmp); +} + +// https://issues.dlang.org/show_bug.cgi?id=17459 +@safe unittest +{ + auto cmp = "100,000"; + auto tmp = format("%06,d", 100_000); + assert(tmp == cmp, tmp); + + cmp = "100,000"; + tmp = format("%07,d", 100_000); + assert(tmp == cmp, tmp); + + cmp = "0,100,000"; + tmp = format("%08,d", 100_000); + assert(tmp == cmp, tmp); +} + +// https://issues.dlang.org/show_bug.cgi?id=20288 +@safe unittest +{ + string s = format("%,.2f", double.nan); + assert(s == "nan", s); + + s = format("%,.2F", double.nan); + assert(s == "NAN", s); + + s = format("%,.2f", -double.nan); + assert(s == "-nan", s); + + s = format("%,.2F", -double.nan); + assert(s == "-NAN", s); + + string g = format("^%13s$", "nan"); + string h = "^ nan$"; + assert(g == h, "\ngot:" ~ g ~ "\nexp:" ~ h); + string a = format("^%13,3.2f$", double.nan); + string b = format("^%13,3.2F$", double.nan); + string c = format("^%13,3.2f$", -double.nan); + string d = format("^%13,3.2F$", -double.nan); + assert(a == "^ nan$", "\ngot:'"~ a ~ "'\nexp:'^ nan$'"); + assert(b == "^ NAN$", "\ngot:'"~ b ~ "'\nexp:'^ NAN$'"); + assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); + assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); + + a = format("^%-13,3.2f$", double.nan); + b = format("^%-13,3.2F$", double.nan); + c = format("^%-13,3.2f$", -double.nan); + d = format("^%-13,3.2F$", -double.nan); + assert(a == "^nan $", "\ngot:'"~ a ~ "'\nexp:'^nan $'"); + assert(b == "^NAN $", "\ngot:'"~ b ~ "'\nexp:'^NAN $'"); + assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); + assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); + + a = format("^%+13,3.2f$", double.nan); + b = format("^%+13,3.2F$", double.nan); + c = format("^%+13,3.2f$", -double.nan); + d = format("^%+13,3.2F$", -double.nan); + assert(a == "^ +nan$", "\ngot:'"~ a ~ "'\nexp:'^ +nan$'"); + assert(b == "^ +NAN$", "\ngot:'"~ b ~ "'\nexp:'^ +NAN$'"); + assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); + assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); + + a = format("^%-+13,3.2f$", double.nan); + b = format("^%-+13,3.2F$", double.nan); + c = format("^%-+13,3.2f$", -double.nan); + d = format("^%-+13,3.2F$", -double.nan); + assert(a == "^+nan $", "\ngot:'"~ a ~ "'\nexp:'^+nan $'"); + assert(b == "^+NAN $", "\ngot:'"~ b ~ "'\nexp:'^+NAN $'"); + assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); + assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); + + a = format("^%- 13,3.2f$", double.nan); + b = format("^%- 13,3.2F$", double.nan); + c = format("^%- 13,3.2f$", -double.nan); + d = format("^%- 13,3.2F$", -double.nan); + assert(a == "^ nan $", "\ngot:'"~ a ~ "'\nexp:'^ nan $'"); + assert(b == "^ NAN $", "\ngot:'"~ b ~ "'\nexp:'^ NAN $'"); + assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); + assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); +} + +@safe unittest +{ + struct S + { + int a; + + void toString(void delegate(const(char)[]) sink, string fmt) + { + auto spec = singleSpec(fmt); + sink.formatValue(a, spec); + } + } + + S s = S(1); + auto result = () @trusted { return format!"%5,3d"(s); } (); + assert(result == " 1"); +} + +/// ditto +typeof(fmt) format(alias fmt, Args...)(Args args) +if (isSomeString!(typeof(fmt))) +{ + import std.array : appender; + import std.range.primitives : ElementEncodingType; + import std.traits : Unqual; + + alias e = checkFormatException!(fmt, Args); + alias Char = Unqual!(ElementEncodingType!(typeof(fmt))); + + static assert(!e, e); + auto w = appender!(immutable(Char)[]); + + // no need to traverse the string twice during compile time + if (!__ctfe) + { + enum len = guessLength!Char(fmt); + w.reserve(len); + } + else + { + w.reserve(fmt.length); + } + + formattedWrite(w, fmt, args); + return w.data; +} + +/// The format string can be checked at compile-time: +@safe pure unittest +{ + auto s = format!"%s is %s"("Pi", 3.14); + assert(s == "Pi is 3.14"); + + // This line doesn't compile, because 3.14 cannot be formatted with %d: + // s = format!"%s is %d"("Pi", 3.14); +} + +@safe pure unittest +{ + string s; + 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 +} + +// https://issues.dlang.org/show_bug.cgi?id=17381 +@safe pure unittest +{ + static assert(!__traits(compiles, format!"%s"(1.5, 2))); + static assert(!__traits(compiles, format!"%f"(1.5, 2))); + static assert(!__traits(compiles, format!"%s"(1.5L, 2))); + static assert(!__traits(compiles, format!"%f"(1.5L, 2))); +} + +// called during compilation to guess the length of the +// result of format +private size_t guessLength(Char, S)(S fmtString) +{ + import std.array : appender; + + size_t len; + auto output = appender!(immutable(Char)[])(); + auto spec = FormatSpec!Char(fmtString); + while (spec.writeUpToNextSpec(output)) + { + // take a guess + if (spec.width == 0 && (spec.precision == spec.UNSPECIFIED || spec.precision == spec.DYNAMIC)) + { + switch (spec.spec) + { + case 'c': + ++len; + break; + case 'd': + case 'x': + case 'X': + len += 3; + break; + case 'b': + len += 8; + break; + case 'f': + case 'F': + len += 10; + break; + case 's': + case 'e': + case 'E': + case 'g': + case 'G': + len += 12; + break; + default: break; + } + + continue; + } + + if ((spec.spec == 'e' || spec.spec == 'E' || spec.spec == 'g' || + spec.spec == 'G' || spec.spec == 'f' || spec.spec == 'F') && + spec.precision != spec.UNSPECIFIED && spec.precision != spec.DYNAMIC && + spec.width == 0 + ) + { + len += spec.precision + 5; + continue; + } + + if (spec.width == spec.precision) + len += spec.width; + else if (spec.width > 0 && spec.width != spec.DYNAMIC && + (spec.precision == spec.UNSPECIFIED || spec.width > spec.precision)) + { + len += spec.width; + } + else if (spec.precision != spec.UNSPECIFIED && spec.precision > spec.width) + len += spec.precision; + } + len += output.data.length; + return len; +} + +@safe pure +unittest +{ + assert(guessLength!char("%c") == 1); + assert(guessLength!char("%d") == 3); + assert(guessLength!char("%x") == 3); + assert(guessLength!char("%b") == 8); + assert(guessLength!char("%f") == 10); + assert(guessLength!char("%s") == 12); + assert(guessLength!char("%02d") == 2); + assert(guessLength!char("%02d") == 2); + assert(guessLength!char("%4.4d") == 4); + assert(guessLength!char("%2.4f") == 4); + assert(guessLength!char("%02d:%02d:%02d") == 8); + assert(guessLength!char("%0.2f") == 7); + assert(guessLength!char("%0*d") == 0); +} + +/** +Converts its arguments according to a format string into a buffer. +The buffer has to be large enough to hold the formatted string. + +The second version of `sformat` takes the format string as a template +argument. In this case, it is checked for consistency at +compile-time. + +Params: + buf = the buffer where the formatted string should go + fmt = a $(MREF_ALTTEXT format string, std,format) + args = a variadic list of arguments to be formatted + Char = character type of `fmt` + Args = a variadic list of types of the arguments + +Returns: + A slice of `buf` containing the formatted string. + +Throws: + A $(REF_ALTTEXT RangeError, RangeError, core, exception) if `buf` + isn't large enough to hold the formatted string + and a $(LREF FormatException) if formatting did not succeed. + +Note: + In theory this function should be `@nogc`. But with the current + implementation there are some cases where allocations occur: + + $(UL + $(LI An exception is thrown.) + $(LI A custom `toString` function of a compound type allocates.)) + */ +char[] sformat(Char, Args...)(return scope char[] buf, scope const(Char)[] fmt, Args args) +{ + import core.exception : RangeError; + import std.range.primitives; + import std.utf : encode; + + static struct Sink + { + char[] buf; + size_t i; + 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(scope 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(scope const(wchar)[] s) + { + for (; !s.empty; s.popFront()) + put(s.front); + } + void put(scope const(dchar)[] s) + { + for (; !s.empty; s.popFront()) + put(s.front); + } + } + auto sink = Sink(buf); + 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; + enforceFmt( + n == args.length, + text("Orphan format arguments: args[", n, " .. ", args.length, "]") + ); + } + return buf[0 .. sink.i]; +} + +/// ditto +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); +} + +/// +@safe pure unittest +{ + char[20] buf; + assert(sformat(buf[], "Here are %d %s.", 3, "apples") == "Here are 3 apples."); + + assert(buf[].sformat("Increase: %7.2f %%", 17.4285) == "Increase: 17.43 %"); +} + +/// The format string can be checked at compile-time: +@safe pure unittest +{ + char[20] buf; + + assert(sformat!"Here are %d %s."(buf[], 3, "apples") == "Here are 3 apples."); + + // This line doesn't compile, because 3.14 cannot be formatted with %d: + // writeln(sformat!"Here are %d %s."(buf[], 3.14, "apples")); +} + +// checking, what is implicitly and explicitly stated in the public unittest +@safe unittest +{ + import std.exception : assertThrown; + + char[20] buf; + assertThrown!FormatException(sformat(buf[], "Here are %d %s.", 3.14, "apples")); + assert(!__traits(compiles, sformat!"Here are %d %s."(buf[], 3.14, "apples"))); +} + +@safe unittest +{ + import core.exception : RangeError; + import std.exception : assertCTFEable, assertThrown; + + 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"); + () @trusted { + 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"); + }); +} + +@safe unittest // ensure that sformat avoids the GC +{ + import core.memory : GC; + + const a = ["foo", "bar"]; + const u = () @trusted { return GC.stats().usedSize; } (); + char[20] buf; + sformat(buf, "%d", 123); + sformat(buf, "%s", a); + sformat(buf, "%s", 'c'); + const v = () @trusted { return GC.stats().usedSize; } (); + assert(u == v); +} + +version (StdUnittest) +private void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__) +{ + formatReflectTest(val, fmt, [formatted], fn, ln); +} + +version (StdUnittest) +private void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__) +{ + import core.exception : AssertError; + import std.algorithm.searching : canFind; + import std.array : appender; + import std.math.operations : isClose; + import std.traits : FloatingPointTypeOf; + + auto w = appender!string(); + formattedWrite(w, fmt, val); + + auto input = w.data; + enforce!AssertError(formatted.canFind(input), input, fn, ln); + + T val2; + formattedRead(input, fmt, val2); + + static if (is(FloatingPointTypeOf!T)) + enforce!AssertError(isClose(val, val2), input, fn, ln); + else + enforce!AssertError(val == val2, input, fn, ln); +} + +@safe 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`); + 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; + + assertCTFEable!( + { + booleanTest(); + integerTest(); + floatingTest(); + charTest(); + strTest(); + daTest(); + saTest(); + aaTest(); + }); +} diff --git a/libphobos/src/std/format/read.d b/libphobos/src/std/format/read.d new file mode 100644 index 0000000..0de8818 --- /dev/null +++ b/libphobos/src/std/format/read.d @@ -0,0 +1,721 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, format). + +It provides two functions for reading formatted input: $(LREF +unformatValue) and $(LREF formattedRead). The former reads a single +value. The latter reads several values at once and matches the +characters found between format specifiers. + +Parameters are ignored, except for the ones consisting of a single +$(B '*'). See $(LREF formattedRead) for more information. + +A space outside of a format specifier has a special meaning: it +matches any sequence of whitespace characters, not just a single +space. + +The following combinations of format characters and types are +available: + +$(BOOKTABLE , +$(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o, x, X) $(TH e, E, f, g, G) $(TH r) $(TH compound)) +$(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH))) +$(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH))) +$(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) +$(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) +$(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) +) + +Below are highlighted examples on how these combinations are used +with $(LREF unformatValue), however, they apply for $(LREF +formattedRead) also + +Copyright: Copyright The D Language Foundation 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/read.d) + */ +module std.format.read; + +/// Booleans +@safe pure unittest +{ + import std.format.spec : singleSpec; + + auto str = "false"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!bool(spec) == false); + + str = "1"; + spec = singleSpec("%d"); + assert(str.unformatValue!bool(spec) == true); +} + +/// Null values +@safe pure unittest +{ + import std.format.spec : singleSpec; + + auto str = "null"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(typeof(null))(spec) == null); +} + +/// Integrals +@safe pure unittest +{ + import std.format.spec : singleSpec; + + // signed decimal values + auto str = "123"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!int(spec) == 123); + + // hexadecimal values + str = "ABC"; + spec = singleSpec("%X"); + assert(str.unformatValue!int(spec) == 2748); + + // octal values + str = "11610"; + spec = singleSpec("%o"); + assert(str.unformatValue!int(spec) == 5000); + + // raw read, depends on endianess + str = "\x75\x01"; + spec = singleSpec("%r"); + auto result = str.unformatValue!short(spec); + assert(result == 373 /* little endian */ || result == 29953 /* big endian */ ); +} + +/// Floating point numbers +@safe pure unittest +{ + import std.format.spec : singleSpec; + import std.math.operations : isClose; + + // natural notation + auto str = "123.456"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!double(spec).isClose(123.456)); + + // scientific notation + str = "1e17"; + spec = singleSpec("%e"); + assert(str.unformatValue!double(spec).isClose(1e17)); + + // raw read, depends on endianess + str = "\x40\x00\x00\xBF"; + spec = singleSpec("%r"); + auto result = str.unformatValue!float(spec); + assert(isClose(result, -0.5) /* little endian */ || isClose(result, 2.0) /* big endian */ ); +} + +/// Characters +@safe pure unittest +{ + import std.format.spec : singleSpec; + + // only the first character is read + auto str = "abc"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!char(spec) == 'a'); + + // using a numerical format character treats the read number as unicode code point + 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 +@safe pure unittest +{ + import std.format.spec : singleSpec; + + // string value + string str = "aaa"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(dchar[])(spec) == "aaa"d); + + // fixed size array with characters + str = "aaa"; + spec = singleSpec("%s"); + dchar[3] ret = ['a', 'a', 'a']; + assert(str.unformatValue!(dchar[3])(spec) == ret); + + // dynamic array + str = "[1, 2, 3, 4]"; + spec = singleSpec("%s"); + assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]); + + // fixed size array with integers + str = "[1, 2, 3, 4]"; + spec = singleSpec("%s"); + int[4] ret2 = [1, 2, 3, 4]; + assert(str.unformatValue!(int[4])(spec) == ret2); + + // compound specifiers can be used for more control + str = "1,2,3"; + spec = singleSpec("%(%s,%)"); + assert(str.unformatValue!(int[])(spec) == [1, 2, 3]); + + str = "cool"; + spec = singleSpec("%(%c%)"); + assert(str.unformatValue!(char[])(spec) == ['c', 'o', 'o', 'l']); +} + +/// Associative arrays +@safe pure unittest +{ + import std.format.spec : singleSpec; + + // as single value + auto str = `["one": 1, "two": 2]`; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]); + + // with compound specifier for more control + str = "1/1, 2/4, 3/9"; + spec = singleSpec("%(%d/%d%|, %)"); + assert(str.unformatValue!(int[int])(spec) == [1: 1, 2: 4, 3: 9]); +} + +import std.format.spec : FormatSpec; +import std.format.internal.read; +import std.traits : isSomeString; + +/** +Reads an input range according to a format string and stores the read +values into its arguments. + +Format specifiers with format character $(B 'd'), $(B 'u') and $(B +'c') can take a $(B '*') parameter for skipping values. + +The second version of `formattedRead` takes the format string as +template argument. In this case, it is checked for consistency at +compile-time. + +Params: + r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives), + where the formatted input is read from + fmt = a $(MREF_ALTTEXT format string, std,format) + args = a variadic list of arguments where the read values are stored + Range = the type of the input range `r` + Char = the character type used for `fmt` + Args = a variadic list of types of the arguments + +Returns: + The number of variables filled. If the input range `r` ends early, + this number will be less than the number of variables provided. + +Throws: + A $(REF_ALTTEXT FormatException, FormatException, std, format) + if reading did not succeed. + +Note: + For backward compatibility the arguments `args` can be given as pointers + to that variable, but it is not recommended to do so, because this + option might be removed in the future. + */ +uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, auto ref Args args) +{ + import std.format : enforceFmt; + import std.range.primitives : empty; + import std.traits : isPointer; + import std.typecons : isTuple; + + auto spec = FormatSpec!Char(fmt); + static if (!Args.length) + { + spec.readUpToNextSpec(r); + enforceFmt(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 .. $]); + } +} + +/// ditto +uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args) +if (isSomeString!(typeof(fmt))) +{ + import std.format : checkFormatException; + + alias e = checkFormatException!(fmt, Args); + static assert(!e, e.msg); + return .formattedRead(r, fmt, args); +} + +/// +@safe pure unittest +{ + string object; + char cmp; + int value; + + assert(formattedRead("angle < 36", "%s %c %d", object, cmp, value) == 3); + assert(object == "angle"); + assert(cmp == '<'); + assert(value == 36); + + // reading may end early: + assert(formattedRead("length >", "%s %c %d", object, cmp, value) == 2); + assert(object == "length"); + assert(cmp == '>'); + // value is not changed: + assert(value == 36); +} + +/// The format string can be checked at compile-time: +@safe pure unittest +{ + string a; + int b; + double c; + + assert("hello!124:34.5".formattedRead!"%s!%s:%s"(a, b, c) == 3); + assert(a == "hello"); + assert(b == 124); + assert(c == 34.5); +} + +/// Skipping values +@safe pure unittest +{ + string item; + double amount; + + assert("orange: (12%) 15.25".formattedRead("%s: (%*d%%) %f", item, amount) == 2); + assert(item == "orange"); + assert(amount == 15.25); + + // can also be used with tuples + import std.typecons : Tuple; + + Tuple!(int, float) t; + char[] line = "1 7643 2.125".dup; + formattedRead(line, "%s %*u %s", t); + assert(t[0] == 1 && t[1] == 2.125); +} + +@safe unittest +{ + import std.math.operations : isClose; + import std.math.traits : isNaN; + import std.range.primitives : empty; + + string s = " 1.2 3.4 "; + double x, y, z; + assert(formattedRead(s, " %s %s %s ", x, y, z) == 2); + assert(s.empty); + assert(isClose(x, 1.2)); + assert(isClose(y, 3.4)); + assert(isNaN(z)); +} + +// for backwards compatibility +@safe 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 +@safe pure unittest +{ + import std.math.operations : isClose; + import std.math.traits : isNaN; + import std.range.primitives : empty; + + string s = " 1.2 3.4 "; + double x, y, z; + assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2); + assert(s.empty); + assert(isClose(x, 1.2)); + assert(isClose(y, 3.4)); + assert(isNaN(z)); +} + +@safe 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); +} + +@safe 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); +} + +@safe 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); +} + +@safe 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); +} + +@safe pure unittest +{ + import std.typecons : Tuple; + + 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); +} + +@safe pure unittest +{ + string line; + + char c1, c2; + + line = "abc"; + formattedRead(line, "%s%c", &c1, &c2); + assert(c1 == 'a' && c2 == 'b'); + assert(line == "c"); +} + +@safe pure unittest +{ + string line; + + line = "[1,2,3]"; + int[] s1; + formattedRead(line, "%s", &s1); + assert(s1 == [1,2,3]); +} + +@safe 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"]); +} + +@safe pure unittest +{ + import std.exception : assertThrown; + + 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)); +} + +@safe pure unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + + 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!FormatException(formattedRead(input, "[%(%s,%)]", &sa2)); +} + +@safe 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"); +} + +@safe 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]); +} + +// test rvalue using +@safe pure unittest +{ + string[int] aa1; + formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1); + assert(aa1 == [1:"hello", 2:"world"]); + + int[string] aa2; + formattedRead(`{"hello"=1; "world"=2}`, "{%(%s=%s; %)}", aa2); + assert(aa2 == ["hello":1, "world":2]); +} + +/** +Reads a value from the given _input range and converts it according to a +format specifier. + +Params: + input = the $(REF_ALTTEXT input range, isInputRange, std, range, primitives), + to read from + spec = a $(MREF_ALTTEXT format string, std,format) + T = type to return + Range = the type of the input range `input` + Char = the character type used for `spec` + +Returns: + A value from `input` of type `T`. + +Throws: + A $(REF_ALTTEXT FormatException, FormatException, std, format) + if reading did not succeed. + +See_Also: + $(REF parse, std, conv) and $(REF to, std, conv) + */ +T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) +{ + return unformatValueImpl!T(input, spec); +} + +/// +@safe pure unittest +{ + import std.format.spec : singleSpec; + + string s = "42"; + auto spec = singleSpec("%s"); + assert(unformatValue!int(s, spec) == 42); +} + +// https://issues.dlang.org/show_bug.cgi?id=7241 +@safe pure unittest +{ + string input = "a"; + auto spec = FormatSpec!char("%s"); + spec.readUpToNextSpec(input); + auto result = unformatValue!(dchar[1])(input, spec); + assert(result[0] == 'a'); +} + +// https://issues.dlang.org/show_bug.cgi?id=20393 +@safe pure unittest +{ + import std.exception : assertThrown; + string str = "foo 12a-buzz"; + string a, c; + int b; + assertThrown(formattedRead(str, "%s %d-%s", &a, &b, &c)); +} diff --git a/libphobos/src/std/format/spec.d b/libphobos/src/std/format/spec.d new file mode 100644 index 0000000..b129686 --- /dev/null +++ b/libphobos/src/std/format/spec.d @@ -0,0 +1,949 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, format). + +It centers around a struct called $(LREF FormatSpec), which takes a +$(MREF_ALTTEXT format string, std,format) and provides tools for +parsing this string. Additionally this module contains a function +$(LREF singleSpec) which helps treating a single format specifier. + +Copyright: Copyright The D Language Foundation 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/spec.d) + */ +module std.format.spec; + +import std.traits : Unqual; + +template FormatSpec(Char) +if (!is(Unqual!Char == Char)) +{ + alias FormatSpec = FormatSpec!(Unqual!Char); +} + +/** +A general handler for format strings. + +This handler centers around the function $(LREF writeUpToNextSpec), +which parses the $(MREF_ALTTEXT format string, std,format) until the +next format specifier is found. After the call, it provides +information about this format specifier in its numerous variables. + +Params: + Char = the character type of the format string + */ +struct FormatSpec(Char) +if (is(Unqual!Char == Char)) +{ + import std.algorithm.searching : startsWith; + import std.ascii : isDigit; + import std.conv : parse, text, to; + import std.range.primitives; + + /** + Minimum width. + + _Default: `0`. + */ + int width = 0; + + /** + Precision. Its semantic depends on the format character. + + See $(MREF_ALTTEXT format string, std,format) for more details. + _Default: `UNSPECIFIED`. + */ + int precision = UNSPECIFIED; + + /** + Number of elements between separators. + + _Default: `UNSPECIFIED`. + */ + int separators = UNSPECIFIED; + + /** + The separator charactar is supplied at runtime. + + _Default: false. + */ + bool dynamicSeparatorChar = false; + + /** + Set to `DYNAMIC` when the separator character is supplied at runtime. + + _Default: `UNSPECIFIED`. + + $(RED Warning: + `separatorCharPos` is deprecated. It will be removed in 2.107.0. + Please use `dynamicSeparatorChar` instead.) + */ + // @@@DEPRECATED_[2.107.0]@@@ + deprecated("separatorCharPos will be removed in 2.107.0. Please use dynamicSeparatorChar instead.") + int separatorCharPos() { return dynamicSeparatorChar ? DYNAMIC : UNSPECIFIED; } + + /// ditto + // @@@DEPRECATED_[2.107.0]@@@ + deprecated("separatorCharPos will be removed in 2.107.0. Please use dynamicSeparatorChar instead.") + void separatorCharPos(int value) { dynamicSeparatorChar = value == DYNAMIC; } + + /** + Character to use as separator. + + _Default: `','`. + */ + dchar separatorChar = ','; + + /** + Special value for `width`, `precision` and `separators`. + + It flags that these values will be passed at runtime through + variadic arguments. + */ + enum int DYNAMIC = int.max; + + /** + Special value for `precision` and `separators`. + + It flags that these values have not been specified. + */ + enum int UNSPECIFIED = DYNAMIC - 1; + + /** + The format character. + + _Default: `'s'`. + */ + char spec = 's'; + + /** + Index of the argument for positional parameters. + + Counting starts with `1`. Set to `0` if not used. Default: `0`. + */ + ubyte indexStart; + + /** + Index of the last argument for positional parameter ranges. + + Counting starts with `1`. Set to `0` if not used. Default: `0`. + */ + ubyte indexEnd; + + version (StdDdoc) + { + /// The format specifier contained a `'-'`. + bool flDash; + + /// The format specifier contained a `'0'`. + bool flZero; + + /// The format specifier contained a space. + bool flSpace; + + /// The format specifier contained a `'+'`. + bool flPlus; + + /// The format specifier contained a `'#'`. + bool flHash; + + /// The format specifier contained a `'='`. + bool flEqual; + + /// The format specifier contained a `','`. + 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, "flEqual", 1, + bool, "flSeparator", 1, + ubyte, "", 1)); + ubyte allFlags; + } + } + + /// The inner format string of a nested format specifier. + const(Char)[] nested; + + /** + The separator of a nested format specifier. + + `null` means, there is no separator. `empty`, but not `null`, + means zero length separator. + */ + const(Char)[] sep; + + /// Contains the part of the format string, that has not yet been parsed. + const(Char)[] trailing; + + /// Sequence `"["` inserted before each range or range like structure. + enum immutable(Char)[] seqBefore = "["; + + /// Sequence `"]"` inserted after each range or range like structure. + enum immutable(Char)[] seqAfter = "]"; + + /** + Sequence `":"` inserted between element key and element value of + an associative array. + */ + enum immutable(Char)[] keySeparator = ":"; + + /** + Sequence `", "` inserted between elements of a range, a range like + structure or the elements of an associative array. + */ + enum immutable(Char)[] seqSeparator = ", "; + + /** + Creates a new `FormatSpec`. + + The string is lazily evaluated. That means, nothing is done, + until $(LREF writeUpToNextSpec) is called. + + Params: + fmt = a $(MREF_ALTTEXT format string, std,format) + */ + this(in Char[] fmt) @safe pure + { + trailing = fmt; + } + + /** + Writes the format string to an output range until the next format + specifier is found and parse that format specifier. + + See the $(MREF_ALTTEXT description of format strings, std,format) for more + details about the format specifier. + + Params: + writer = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives), + where the format string is written to + OutputRange = type of the output range + + Returns: + True, if a format specifier is found and false, if the end of the + format string has been reached. + + Throws: + A $(REF_ALTTEXT FormatException, FormatException, std,format) + when parsing the format specifier did not succeed. + */ + bool writeUpToNextSpec(OutputRange)(ref OutputRange writer) scope + { + import std.format : enforceFmt; + + 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; + } + + private void fillUp() scope + { + import std.format : enforceFmt, FormatException; + + // Reset content + if (__ctfe) + { + flDash = false; + flZero = false; + flSpace = false; + flPlus = false; + flEqual = 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 FormatException( + 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 '=': flEqual = 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++] == '$', + text("$ expected after '*", -width, "' in format string")); + } + 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 = trailing.length - tmp.length; + 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 = trailing.length - tmp.length; + 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 = trailing.length - tmp.length; + } + else + { + // "," was specified, but nothing after it + separators = 3; + } + + if (trailing[i] == '?') + { + dynamicSeparatorChar = true; + ++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 = trailing.length - tmp.length; + } + else if (isDigit(trailing[i])) + { + auto tmp = trailing[i .. $]; + precision = parse!int(tmp); + i = trailing.length - tmp.length; + } + 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 FormatException(text("Incorrect format specifier: ", trailing)); + } + + //-------------------------------------------------------------------------- + package bool readUpToNextSpec(R)(ref R r) scope + { + import std.ascii : isLower, isWhite; + import std.format : enforceFmt; + import std.utf : stride; + + // Reset content + if (__ctfe) + { + flDash = false; + flZero = false; + flSpace = false; + flPlus = false; + flHash = false; + flEqual = 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, "Required at least one more input"); + // Require a '%' + enforceFmt (r.front == '%', + text("parseToFormatSpec: Cannot find character '", + c2, "' in the input string.")); + trailing = trailing[2 .. $]; + r.popFront(); + } + else + { + enforceFmt(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 + { + enforceFmt(!r.empty && r.front == trailing.front, + text("parseToFormatSpec: Cannot find character '", + c, "' in the input string.")); + r.popFront(); + } + trailing = trailing[stride(trailing, 0) .. $]; + } + } + return false; + } + + package string getCurFmtStr() const + { + import std.array : appender; + import std.format.write : formatValue; + + 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 (flEqual) put(w, '='); + if (flHash) put(w, '#'); + if (width != 0) + formatValue(w, width, f); + if (precision != FormatSpec!Char.UNSPECIFIED) + { + put(w, '.'); + formatValue(w, precision, f); + } + if (flSeparator) put(w, ','); + if (separators != FormatSpec!Char.UNSPECIFIED) + formatValue(w, separators, f); + put(w, spec); + return w.data; + } + + /** + Provides a string representation. + + Returns: + The string representation. + */ + string toString() const @safe pure + { + import std.array : appender; + + auto app = appender!string(); + app.reserve(200 + trailing.length); + toString(app); + return app.data; + } + + /** + Writes a string representation to an output range. + + Params: + writer = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives), + where the representation is written to + OutputRange = type of the output range + */ + void toString(OutputRange)(ref OutputRange writer) const + if (isOutputRange!(OutputRange, char)) + { + import std.format.write : formatValue; + + auto s = singleSpec("%s"); + + put(writer, "address = "); + formatValue(writer, &this, s); + put(writer, "\nwidth = "); + formatValue(writer, width, s); + put(writer, "\nprecision = "); + formatValue(writer, precision, s); + put(writer, "\nspec = "); + formatValue(writer, spec, s); + put(writer, "\nindexStart = "); + formatValue(writer, indexStart, s); + put(writer, "\nindexEnd = "); + formatValue(writer, indexEnd, s); + put(writer, "\nflDash = "); + formatValue(writer, flDash, s); + put(writer, "\nflZero = "); + formatValue(writer, flZero, s); + put(writer, "\nflSpace = "); + formatValue(writer, flSpace, s); + put(writer, "\nflPlus = "); + formatValue(writer, flPlus, s); + put(writer, "\nflEqual = "); + formatValue(writer, flEqual, s); + put(writer, "\nflHash = "); + formatValue(writer, flHash, s); + put(writer, "\nflSeparator = "); + formatValue(writer, flSeparator, s); + put(writer, "\nnested = "); + formatValue(writer, nested, s); + put(writer, "\ntrailing = "); + formatValue(writer, trailing, s); + put(writer, '\n'); + } +} + +/// +@safe pure unittest +{ + import std.array : appender; + + auto a = appender!(string)(); + auto fmt = "Number: %6.4e\nString: %s"; + auto f = FormatSpec!char(fmt); + + assert(f.writeUpToNextSpec(a) == true); + + assert(a.data == "Number: "); + assert(f.trailing == "\nString: %s"); + assert(f.spec == 'e'); + assert(f.width == 6); + assert(f.precision == 4); + + assert(f.writeUpToNextSpec(a) == true); + + assert(a.data == "Number: \nString: "); + assert(f.trailing == ""); + assert(f.spec == 's'); + + assert(f.writeUpToNextSpec(a) == false); + + assert(a.data == "Number: \nString: "); +} + +@safe unittest +{ + import std.array : appender; + import std.conv : text; + import std.exception : assertThrown; + import std.format : FormatException; + + auto w = appender!(char[])(); + auto f = FormatSpec!char("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!char("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"); + // https://issues.dlang.org/show_bug.cgi?id=4775 + f = FormatSpec!char("%%%s"); + w.clear(); + f.writeUpToNextSpec(w); + assert(w.data == "%" && f.trailing == ""); + f = FormatSpec!char("%%%%%s%%"); + w.clear(); + while (f.writeUpToNextSpec(w)) continue; + assert(w.data == "%%%"); + + f = FormatSpec!char("a%%b%%c%"); + w.clear(); + assertThrown!FormatException(f.writeUpToNextSpec(w)); + assert(w.data == "a%b%c" && f.trailing == "%"); +} + +// https://issues.dlang.org/show_bug.cgi?id=5237 +@safe unittest +{ + import std.array : appender; + + 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"); +} + +// https://issues.dlang.org/show_bug.cgi?id=14059 +@safe unittest +{ + import std.array : appender; + import std.exception : assertThrown; + import std.format : FormatException; + + auto a = appender!(string)(); + + auto f = FormatSpec!char("%-(%s%"); // %)") + assertThrown!FormatException(f.writeUpToNextSpec(a)); + + f = FormatSpec!char("%(%-"); // %)") + assertThrown!FormatException(f.writeUpToNextSpec(a)); +} + +@safe unittest +{ + import std.array : appender; + import std.format : format; + + 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); +} + +@safe pure unittest +{ + import std.algorithm.searching : canFind, findSplitBefore; + + auto expected = "width = 2" ~ + "\nprecision = 5" ~ + "\nspec = f" ~ + "\nindexStart = 0" ~ + "\nindexEnd = 0" ~ + "\nflDash = false" ~ + "\nflZero = false" ~ + "\nflSpace = false" ~ + "\nflPlus = false" ~ + "\nflEqual = false" ~ + "\nflHash = false" ~ + "\nflSeparator = false" ~ + "\nnested = " ~ + "\ntrailing = \n"; + auto spec = singleSpec("%2.5f"); + auto res = spec.toString(); + // make sure the address exists, then skip it + assert(res.canFind("address")); + assert(res.findSplitBefore("width")[1] == expected); +} + +// https://issues.dlang.org/show_bug.cgi?id=15348 +@safe pure unittest +{ + import std.array : appender; + import std.exception : collectExceptionMsg; + import std.format : FormatException; + + auto w = appender!(char[])(); + auto f = FormatSpec!char("%*10d"); + + assert(collectExceptionMsg!FormatException(f.writeUpToNextSpec(w)) + == "$ expected after '*10' in format string"); +} + +/** +Helper function that returns a `FormatSpec` for a single format specifier. + +Params: + fmt = a $(MREF_ALTTEXT format string, std,format) + containing a single format specifier + Char = character type of `fmt` + +Returns: + A $(LREF FormatSpec) with the format specifier parsed. + +Throws: + A $(REF_ALTTEXT FormatException, FormatException, std,format) when the + format string contains no format specifier or more than a single format + specifier or when the format specifier is malformed. + */ +FormatSpec!Char singleSpec(Char)(Char[] fmt) +{ + import std.conv : text; + import std.format : enforceFmt; + import std.range.primitives : empty, front; + + enforceFmt(fmt.length >= 2, "fmt must be at least 2 characters long"); + enforceFmt(fmt.front == '%', "fmt must start with a '%' character"); + enforceFmt(fmt[1] != '%', "'%%' is not a permissible format specifier"); + + static struct DummyOutputRange + { + void put(C)(scope const C[] buf) {} // eat elements + } + auto a = DummyOutputRange(); + auto spec = FormatSpec!Char(fmt); + //dummy write + spec.writeUpToNextSpec(a); + + enforceFmt(spec.trailing.empty, + text("Trailing characters in fmt string: '", spec.trailing)); + + return spec; +} + +/// +@safe pure unittest +{ + import std.array : appender; + import std.format.write : formatValue; + + auto spec = singleSpec("%10.3e"); + auto writer = appender!string(); + writer.formatValue(42.0, spec); + + assert(writer.data == " 4.200e+01"); +} + +@safe pure unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + + auto spec = singleSpec("%2.3e"); + + assert(spec.trailing == ""); + assert(spec.spec == 'e'); + assert(spec.width == 2); + assert(spec.precision == 3); + + assertThrown!FormatException(singleSpec("")); + assertThrown!FormatException(singleSpec("%")); + assertThrown!FormatException(singleSpec("%2.3")); + assertThrown!FormatException(singleSpec("2.3e")); + assertThrown!FormatException(singleSpec("Test%2.3e")); + assertThrown!FormatException(singleSpec("%2.3eTest")); + assertThrown!FormatException(singleSpec("%%")); +} + +// @@@DEPRECATED_[2.107.0]@@@ +deprecated("enforceValidFormatSpec was accidentally made public and will be removed in 2.107.0") +void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f) +{ + import std.format.internal.write : evfs = enforceValidFormatSpec; + + evfs!T(f); +} + +@safe unittest +{ + import std.exception : collectExceptionMsg; + import std.format : format, FormatException; + + // 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"); + + // dynamicSeparatorChar + 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"); +} + diff --git a/libphobos/src/std/format/write.d b/libphobos/src/std/format/write.d new file mode 100644 index 0000000..c758768 --- /dev/null +++ b/libphobos/src/std/format/write.d @@ -0,0 +1,1289 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, format). + +It provides two functions for writing formatted output: $(LREF +formatValue) and $(LREF formattedWrite). The former writes a single +value. The latter writes several values at once, interspersed with +unformatted text. + +The following combinations of format characters and types are +available: + +$(BOOKTABLE , +$(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o) $(TH x, X) $(TH e, E, f, F, g, G, a, A) $(TH r) $(TH compound)) +$(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH))) +$(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH))) +$(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH))) +$(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH))) +$(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) +$(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) +$(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) +$(TR $(TD $(I pointer)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD $(I SIMD vectors)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) +$(TR $(TD $(I delegates)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) +) + +Enums can be used with all format characters of the base type. + +$(SECTION3 Structs$(COMMA) Unions$(COMMA) Classes$(COMMA) and Interfaces) + +Aggregate types can define various `toString` functions. If this +function takes a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, +spec) or a $(I format string) as argument, the function decides +which format characters are accepted. If no `toString` is defined and +the aggregate is an $(REF_ALTTEXT input range, isInputRange, std, +range, primitives), it is treated like a range, that is $(B 's'), $(B +'r') and a compound specifier are accepted. In all other cases +aggregate types only accept $(B 's'). + +`toString` should have one of the following signatures: + +--- +void toString(Writer, Char)(ref Writer w, const ref FormatSpec!Char fmt) +void toString(Writer)(ref Writer w) +string toString(); +--- + +Where `Writer` is an $(REF_ALTTEXT output range, isOutputRange, +std,range,primitives) which accepts characters $(LPAREN)of type +`Char` in the first version$(RPAREN). The template type does not have +to be called `Writer`. + +Sometimes it's not possible to use a template, for example when +`toString` overrides `Object.toString`. In this case, the following +$(LPAREN)slower and less flexible$(RPAREN) functions can be used: + +--- +void toString(void delegate(const(char)[]) sink, const ref FormatSpec!char fmt); +void toString(void delegate(const(char)[]) sink, string fmt); +void toString(void delegate(const(char)[]) sink); +--- + +When several of the above `toString` versions are available, the +versions with `Writer` take precedence over the versions with a +`sink`. `string toString()` has the lowest priority. + +If none of the above mentioned `toString` versions are available, the +aggregates will be formatted by other means, in the following +order: + +If an aggregate is an $(REF_ALTTEXT input range, isInputRange, std, +range, primitives), it is formatted like an input range. + +If an aggregate is a builtin type (using `alias this`), it is formatted +like the builtin type. + +If all else fails, structs are formatted like `Type(field1, field2, ...)`, +classes and interfaces are formatted with their fully qualified name +and unions with their base name. + +Copyright: Copyright The D Language Foundation 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/write.d) + */ +module std.format.write; + +/** +`bool`s are formatted as `"true"` or `"false"` with `%s` and like the +`byte`s 1 and 0 with all other format characters. + */ +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%s"); + formatValue(w1, true, spec1); + + assert(w1.data == "true"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%#x"); + formatValue(w2, true, spec2); + + assert(w2.data == "0x1"); +} + +/// The `null` literal is formatted as `"null"`. +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatValue(w, null, spec); + + assert(w.data == "null"); +} + +/** +Integrals are formatted in (signed) every day notation with `%s` and +`%d` and as an (unsigned) image of the underlying bit representation +with `%b` (binary), `%u` (decimal), `%o` (octal), and `%x` (hexadecimal). + */ +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%d"); + formatValue(w1, -1337, spec1); + + assert(w1.data == "-1337"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%x"); + formatValue(w2, -1337, spec2); + + assert(w2.data == "fffffac7"); +} + +/** +Floating-point values are formatted in natural notation with `%f`, in +scientific notation with `%e`, in short notation with `%g`, and in +hexadecimal scientific notation with `%a`. If a rounding mode is +available, they are rounded according to this rounding mode, otherwise +they are rounded to the nearest value, ties to even. + */ +@safe unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%.3f"); + formatValue(w1, 1337.7779, spec1); + + assert(w1.data == "1337.778"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%.3e"); + formatValue(w2, 1337.7779, spec2); + + assert(w2.data == "1.338e+03"); + + auto w3 = appender!string(); + auto spec3 = singleSpec("%.3g"); + formatValue(w3, 1337.7779, spec3); + + assert(w3.data == "1.34e+03"); + + auto w4 = appender!string(); + auto spec4 = singleSpec("%.3a"); + formatValue(w4, 1337.7779, spec4); + + assert(w4.data == "0x1.4e7p+10"); +} + +/** +Individual characters (`char`, `wchar`, or `dchar`) are formatted as +Unicode characters with `%s` and `%c` and as integers (`ubyte`, +`ushort`, `uint`) with all other format characters. With +$(MREF_ALTTEXT compound specifiers, std,format) characters are +treated differently. + */ +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%c"); + formatValue(w1, 'ì', spec1); + + assert(w1.data == "ì"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%#x"); + formatValue(w2, 'ì', spec2); + + assert(w2.data == "0xec"); +} + +/** +Strings are formatted as a sequence of characters with `%s`. +Non-printable characters are not escaped. With a compound specifier +the string is treated like a range of characters. With $(MREF_ALTTEXT +compound specifiers, std,format) strings are treated differently. + */ +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%s"); + formatValue(w1, "hello", spec1); + + assert(w1.data == "hello"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%(%#x%|/%)"); + formatValue(w2, "hello", spec2); + + assert(w2.data == "0x68/0x65/0x6c/0x6c/0x6f"); +} + +/// Static arrays are formatted as dynamic arrays. +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%s"); + int[2] two = [1, 2]; + formatValue(w, two, spec); + + assert(w.data == "[1, 2]"); +} + +/** +Dynamic arrays are formatted as input ranges. + */ +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%s"); + auto two = [1, 2]; + formatValue(w1, two, spec1); + + assert(w1.data == "[1, 2]"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%(%g%|, %)"); + auto consts = [3.1415926, 299792458, 6.67430e-11]; + formatValue(w2, consts, spec2); + + assert(w2.data == "3.14159, 2.99792e+08, 6.6743e-11"); + + // void[] is treated like ubyte[] + auto w3 = appender!string(); + auto spec3 = singleSpec("%s"); + void[] val = cast(void[]) cast(ubyte[])[1, 2, 3]; + formatValue(w3, val, spec3); + + assert(w3.data == "[1, 2, 3]"); +} + +/** +Associative arrays are formatted by using `':'` and `", "` as +separators, enclosed by `'['` and `']'` when used with `%s`. It's +also possible to use a compound specifier for better control. + +Please note, that the order of the elements is not defined, therefore +the result of this function might differ. + */ +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto aa = [10:17.5, 20:9.99]; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%s"); + formatValue(w1, aa, spec1); + + assert(w1.data == "[10:17.5, 20:9.99]" || w1.data == "[20:9.99, 10:17.5]"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%(%x = %.0e%| # %)"); + formatValue(w2, aa, spec2); + + assert(w2.data == "a = 2e+01 # 14 = 1e+01" || w2.data == "14 = 1e+01 # a = 2e+01"); +} + +/** +`enum`s are formatted as their name when used with `%s` and like +their base value else. + */ +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + enum A { first, second, third } + + auto w1 = appender!string(); + auto spec1 = singleSpec("%s"); + formatValue(w1, A.second, spec1); + + assert(w1.data == "second"); + + auto w2 = appender!string(); + auto spec2 = singleSpec("%d"); + formatValue(w2, A.second, spec2); + + assert(w2.data == "1"); + + // values of an enum that have no name are formatted with %s using a cast + A a = A.third; + a++; + + auto w3 = appender!string(); + auto spec3 = singleSpec("%s"); + formatValue(w3, a, spec3); + + assert(w3.data == "cast(A)3"); +} + +/** +`structs`, `unions`, `classes` and `interfaces` can be formatted in +several different ways. The following example highlights `struct` +formatting, however, it applies to other aggregates as well. + */ +@safe unittest +{ + import std.array : appender; + import std.format.spec : FormatSpec, singleSpec; + + // Using a `toString` with a writer + static struct Point1 + { + import std.range.primitives : isOutputRange, put; + + int x, y; + + void toString(W)(ref W writer, scope const ref FormatSpec!char f) + if (isOutputRange!(W, char)) + { + put(writer, "("); + formatValue(writer, x, f); + put(writer, ","); + formatValue(writer, y, f); + put(writer, ")"); + } + } + + auto w1 = appender!string(); + auto spec1 = singleSpec("%s"); + auto p1 = Point1(16, 11); + + formatValue(w1, p1, spec1); + assert(w1.data == "(16,11)"); + + // Using a `toString` with a sink + static struct Point2 + { + int x, y; + + void toString(scope void delegate(scope const(char)[]) @safe sink, + scope const FormatSpec!char fmt) const + { + sink("("); + sink.formatValue(x, fmt); + sink(","); + sink.formatValue(y, fmt); + sink(")"); + } + } + + auto w2 = appender!string(); + auto spec2 = singleSpec("%03d"); + auto p2 = Point2(16,11); + + formatValue(w2, p2, spec2); + assert(w2.data == "(016,011)"); + + // Using `string toString()` + static struct Point3 + { + int x, y; + + string toString() + { + import std.conv : to; + + return "(" ~ to!string(x) ~ "," ~ to!string(y) ~ ")"; + } + } + + auto w3 = appender!string(); + auto spec3 = singleSpec("%s"); // has to be %s + auto p3 = Point3(16,11); + + formatValue(w3, p3, spec3); + assert(w3.data == "(16,11)"); + + // without `toString` + static struct Point4 + { + int x, y; + } + + auto w4 = appender!string(); + auto spec4 = singleSpec("%s"); // has to be %s + auto p4 = Point4(16,11); + + formatValue(w4, p4, spec3); + assert(w4.data == "Point4(16, 11)"); +} + +/// Pointers are formatted as hexadecimal integers. +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto w1 = appender!string(); + auto spec1 = singleSpec("%s"); + auto p1 = () @trusted { return cast(void*) 0xFFEECCAA; } (); + formatValue(w1, p1, spec1); + + assert(w1.data == "FFEECCAA"); + + // null pointers are printed as `"null"` when used with `%s` and as hexadecimal integer else + auto w2 = appender!string(); + auto spec2 = singleSpec("%s"); + auto p2 = () @trusted { return cast(void*) 0x00000000; } (); + formatValue(w2, p2, spec2); + + assert(w2.data == "null"); + + auto w3 = appender!string(); + auto spec3 = singleSpec("%x"); + formatValue(w3, p2, spec3); + + assert(w3.data == "0"); +} + +/// SIMD vectors are formatted as arrays. +@safe unittest +{ + import core.simd; // cannot be selective, because float4 might not be defined + import std.array : appender; + import std.format.spec : singleSpec; + + auto w = appender!string(); + auto spec = singleSpec("%s"); + + static if (is(float4)) + { + version (X86) {} + else + { + float4 f4; + f4.array[0] = 1; + f4.array[1] = 2; + f4.array[2] = 3; + f4.array[3] = 4; + + formatValue(w, f4, spec); + assert(w.data == "[1, 2, 3, 4]"); + } + } +} + +import std.format.internal.write; + +import std.format.spec : FormatSpec; +import std.traits : isSomeString; + +/** +Converts its arguments according to a format string and writes +the result to an output range. + +The second version of `formattedWrite` takes the format string as a +template argument. In this case, it is checked for consistency at +compile-time. + +Params: + w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives), + where the formatted result is written to + fmt = a $(MREF_ALTTEXT format string, std,format) + args = a variadic list of arguments to be formatted + Writer = the type of the writer `w` + Char = character type of `fmt` + Args = a variadic list of types of the arguments + +Returns: + The index of the last argument that was formatted. If no positional + arguments are used, this is the number of arguments that where formatted. + +Throws: + A $(REF_ALTTEXT FormatException, FormatException, std, format) + if formatting did not succeed. + +Note: + In theory this function should be `@nogc`. But with the current + implementation there are some cases where allocations occur. + See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details. + */ +uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] fmt, Args args) +{ + import std.conv : text; + import std.format : enforceFmt, FormatException; + import std.traits : isSomeChar; + + 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 == Args.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, "The index must be greater than zero"); + 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, "The precision must be greater than zero"); + 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.dynamicSeparatorChar) + { + auto separatorChar = + getNth!("separator character", isSomeChar, dchar)(currentArg, args); + spec.separatorChar = separatorChar; + spec.dynamicSeparatorChar = false; + ++currentArg; + } + + if (currentArg == Args.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; Args) + { + 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 < Args.length) + goto case; + else + goto default; + } + else + break SWITCH; + } + default: + throw new FormatException( + text("Positional specifier %", spec.indexStart, '$', spec.spec, + " index exceeds ", Args.length)); + } + } + return currentArg; +} + +/// +@safe pure unittest +{ + import std.array : appender; + + auto writer1 = appender!string(); + formattedWrite(writer1, "%s is the ultimate %s.", 42, "answer"); + assert(writer1[] == "42 is the ultimate answer."); + + auto writer2 = appender!string(); + formattedWrite(writer2, "Increase: %7.2f %%", 17.4285); + assert(writer2[] == "Increase: 17.43 %"); +} + +/// ditto +uint formattedWrite(alias fmt, Writer, Args...)(auto ref Writer w, Args args) +if (isSomeString!(typeof(fmt))) +{ + import std.format : checkFormatException; + + alias e = checkFormatException!(fmt, Args); + static assert(!e, e.msg); + return .formattedWrite(w, fmt, args); +} + +/// The format string can be checked at compile-time: +@safe pure unittest +{ + import std.array : appender; + + auto writer = appender!string(); + writer.formattedWrite!"%d is the ultimate %s."(42, "answer"); + assert(writer[] == "42 is the ultimate answer."); + + // This line doesn't compile, because 3.14 cannot be formatted with %d: + // writer.formattedWrite!"%d is the ultimate %s."(3.14, "answer"); +} + +@safe pure unittest +{ + import std.array : appender; + + auto stream = appender!string(); + formattedWrite(stream, "%s", 1.1); + assert(stream.data == "1.1", stream.data); +} + +@safe pure unittest +{ + import std.array; + + auto w = appender!string(); + formattedWrite(w, "%s %d", "@safe/pure", 42); + assert(w.data == "@safe/pure 42"); +} + +@safe pure unittest +{ + char[20] buf; + auto w = buf[]; + formattedWrite(w, "%s %d", "@safe/pure", 42); + assert(buf[0 .. $ - w.length] == "@safe/pure 42"); +} + +@safe pure unittest +{ + import std.algorithm.iteration : map; + import std.array : appender; + + 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 +{ + // testing positional parameters + import std.array : appender; + import std.exception : collectExceptionMsg; + import std.format : FormatException; + + 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); +} + +// https://issues.dlang.org/show_bug.cgi?id=3479 +@safe unittest +{ + import std.array : appender; + + auto stream = appender!(char[])(); + formattedWrite(stream, "%2$.*1$d", 12, 10); + assert(stream.data == "000000000010", stream.data); +} + +// https://issues.dlang.org/show_bug.cgi?id=6893 +@safe unittest +{ + import std.array : appender; + + enum E : ulong { A, B, C } + auto stream = appender!(char[])(); + formattedWrite(stream, "%s", E.C); + assert(stream.data == "C"); +} + +@safe pure unittest +{ + import std.array : appender; + + auto stream = appender!string(); + formattedWrite(stream, "%u", 42); + assert(stream.data == "42", stream.data); +} + +@safe pure unittest +{ + // testing raw writes + import std.array : appender; + + 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 unittest +{ + import std.array : appender; + import std.conv : text, octal; + + auto stream = appender!(char[])(); + + 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); + 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, "%a %A", 1.32, 6.78f); + 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 == "0"); + 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"); + + 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); + 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"); + + @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); + //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 : appender; + import std.meta : AliasSeq; + + 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); +} + +/** +Formats a value of any type according to a format specifier and +writes the result to an output range. + +More details about how types are formatted, and how the format +specifier influences the outcome, can be found in the definition of a +$(MREF_ALTTEXT format string, std,format). + +Params: + w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) where + the formatted value is written to + val = the value to write + f = a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, spec) defining the + format specifier + Writer = the type of the output range `w` + T = the type of value `val` + Char = the character type used for `f` + +Throws: + A $(LREF FormatException) if formatting did not succeed. + +Note: + In theory this function should be `@nogc`. But with the current + implementation there are some cases where allocations occur. + See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details. + +See_Also: + $(LREF formattedWrite) which formats several values at once. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) +{ + import std.format : enforceFmt; + + enforceFmt(f.width != f.DYNAMIC && f.precision != f.DYNAMIC + && f.separators != f.DYNAMIC && !f.dynamicSeparatorChar, + "Dynamic argument not allowed for `formatValue`"); + + formatValueImpl(w, val, f); +} + +/// +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : singleSpec; + + auto writer = appender!string(); + auto spec = singleSpec("%08b"); + writer.formatValue(42, spec); + assert(writer.data == "00101010"); + + spec = singleSpec("%2s"); + writer.formatValue('=', spec); + assert(writer.data == "00101010 ="); + + spec = singleSpec("%+14.6e"); + writer.formatValue(42.0, spec); + assert(writer.data == "00101010 = +4.200000e+01"); +} + +// https://issues.dlang.org/show_bug.cgi?id=15386 +@safe pure unittest +{ + import std.array : appender; + import std.format.spec : FormatSpec; + import std.format : FormatException; + import std.exception : assertThrown; + + auto w = appender!(char[])(); + auto dor = appender!(char[])(); + auto fs = FormatSpec!char("%.*s"); + fs.writeUpToNextSpec(dor); + assertThrown!FormatException(formatValue(w, 0, fs)); + + fs = FormatSpec!char("%*s"); + fs.writeUpToNextSpec(dor); + assertThrown!FormatException(formatValue(w, 0, fs)); + + fs = FormatSpec!char("%,*s"); + fs.writeUpToNextSpec(dor); + assertThrown!FormatException(formatValue(w, 0, fs)); + + fs = FormatSpec!char("%,?s"); + fs.writeUpToNextSpec(dor); + assertThrown!FormatException(formatValue(w, 0, fs)); + + assertThrown!FormatException(formattedWrite(w, "%(%0*d%)", new int[1])); +} diff --git a/libphobos/src/std/functional.d b/libphobos/src/std/functional.d index f35d6ff..cec61fe 100644 --- a/libphobos/src/std/functional.d +++ b/libphobos/src/std/functional.d @@ -8,6 +8,7 @@ functions are helpful when constructing predicates for the algorithms in $(MREF std, algorithm) or $(MREF std, range). $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TH Function Name) $(TH Description) ) @@ -20,9 +21,6 @@ $(TR $(TH Function Name) $(TH Description) 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. )) @@ -36,7 +34,11 @@ $(TR $(TH Function Name) $(TH Description) $(TD Creates a function that binds the first argument of a given function to a given value. )) - $(TR $(TD $(LREF reverseArgs), $(LREF binaryReverseArgs)) + $(TR $(TD $(LREF curry)) + $(TD Converts a multi-argument function into a series of single-argument + functions. f(x, y) == curry(f)(x)(y) + )) + $(TR $(TD $(LREF reverseArgs)) $(TD Predicate that reverses the order of its arguments. )) $(TR $(TD $(LREF toDelegate)) @@ -46,12 +48,12 @@ $(TR $(TH Function Name) $(TH Description) $(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) +Source: $(PHOBOSSRC std/functional.d) */ /* Copyright Andrei Alexandrescu 2008 - 2009. @@ -61,9 +63,10 @@ Distributed under the Boost Software License, Version 1.0. */ module std.functional; -import std.meta; // AliasSeq, Reverse -import std.traits; // isCallable, Parameters +import std.meta : AliasSeq, Reverse; +import std.traits : isCallable, Parameters; +import std.internal.attributes : betterC; private template needOpCallAlias(alias fun) { @@ -93,12 +96,19 @@ private template needOpCallAlias(alias fun) } /** -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). +Transforms a `string` representing an expression into a unary +function. The `string` must either use symbol name `a` as +the parameter or provide the symbol via the `parmName` argument. + +Params: + fun = a `string` or a callable + parmName = the name of the parameter if `fun` is a string. Defaults + to `"a"`. +Returns: + If `fun` is a `string`, a new single parameter function + + If `fun` is not a `string`, an alias to `fun`. */ - template unaryFun(alias fun, string parmName = "a") { static if (is(typeof(fun) : string)) @@ -116,7 +126,7 @@ template unaryFun(alias fun, string parmName = "a") } else static if (needOpCallAlias!fun) { - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 alias unaryFun = fun.opCall; } else @@ -147,7 +157,7 @@ template unaryFun(alias fun, string parmName = "a") int num = 41; assert(unaryFun!"a + 1"(num) == 42); - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 struct Seen { static bool opCall(int n) { return true; } @@ -176,13 +186,20 @@ template unaryFun(alias fun, string parmName = "a") } /** -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). +Transforms a `string` representing an expression into a binary function. The +`string` must either use symbol names `a` and `b` as the parameters or +provide the symbols via the `parm1Name` and `parm2Name` arguments. + +Params: + fun = a `string` or a callable + parm1Name = the name of the first parameter if `fun` is a string. + Defaults to `"a"`. + parm2Name = the name of the second parameter if `fun` is a string. + Defaults to `"b"`. +Returns: + If `fun` is not a string, `binaryFun` aliases itself away to + `fun`. */ - template binaryFun(alias fun, string parm1Name = "a", string parm2Name = "b") { @@ -203,7 +220,7 @@ template binaryFun(alias fun, string parm1Name = "a", } else static if (needOpCallAlias!fun) { - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 alias binaryFun = fun.opCall; } else @@ -233,7 +250,7 @@ template binaryFun(alias fun, string parm1Name = "a", //@@BUG //assert(binaryFun!("return a + b;")(41, 1) == 42); - // Issue 9906 + // https://issues.dlang.org/show_bug.cgi?id=9906 struct Seen { static bool opCall(int x, int y) { return true; } @@ -301,7 +318,7 @@ private uint _ctfeSkipName(ref string op, string name) return 0; } -// returns 1 if $(D fun) is trivial unary function +// returns 1 if `fun` is trivial unary function private uint _ctfeMatchUnary(string fun, string name) { if (!__ctfe) assert(false); @@ -348,7 +365,7 @@ private uint _ctfeMatchUnary(string fun, string name) static assert(_ctfeMatchUnary("ё[21]", "ё")); } -// returns 1 if $(D fun) is trivial binary function +// returns 1 if `fun` is trivial binary function private uint _ctfeMatchBinary(string fun, string name1, string name2) { if (!__ctfe) assert(false); @@ -531,13 +548,18 @@ alias equalTo = safeOp!"=="; 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)). +N-ary predicate that reverses the order of arguments, e.g., given +$(D pred(a, b, c)), returns $(D pred(c, b, a)). + +Params: + pred = A callable +Returns: + A function which calls `pred` after reversing the given parameters */ template reverseArgs(alias pred) { auto reverseArgs(Args...)(auto ref Args args) - if (is(typeof(pred(Reverse!args)))) + if (is(typeof(pred(Reverse!args)))) { return pred(Reverse!args); } @@ -548,6 +570,24 @@ template reverseArgs(alias pred) { alias gt = reverseArgs!(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 = reverseArgs!(foo); + assert(zyx(5, 4) == foo(4, 5)); +} + +/// +@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; @@ -581,38 +621,13 @@ template reverseArgs(alias pred) } /** - 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 `pred`. -/** -Negates predicate $(D pred). +Params: + pred = A string or a callable +Returns: + A function which calls `pred` and returns the logical negation of its + return value. */ template not(alias pred) { @@ -633,7 +648,6 @@ template not(alias pred) @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!"); @@ -653,18 +667,20 @@ template not(alias pred) /** $(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially applies) $(D_PARAM fun) by tying its first argument to $(D_PARAM arg). + +Params: + fun = A callable + arg = The first argument to apply to `fun` +Returns: + A new function which calls `fun` with `arg` plus the passed parameters. */ 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 + import std.traits : isCallable; + // Check whether fun is a user defined type which implements opCall or a template. + // As opCall itself can be templated, std.traits.isCallable does not work here. + enum isSomeFunctor = (is(typeof(fun) == struct) || is(typeof(fun) == class)) && __traits(hasMember, fun, "opCall"); + static if (isSomeFunctor || __traits(isTemplate, fun)) { auto partial(Ts...)(Ts args2) { @@ -687,6 +703,36 @@ template partial(alias fun, alias arg) } } } + else static if (!isCallable!fun) + { + static assert(false, "Cannot apply partial to a non-callable '" ~ fun.stringof ~ "'."); + } + else // Assume fun is callable and uniquely defined. + { + static if (Parameters!fun.length == 0) + { + static assert(0, "Cannot partially apply '" ~ fun.stringof ~ "'." ~ + "'" ~ fun.stringof ~ "' has 0 arguments."); + } + else static if (!is(typeof(arg) : Parameters!fun[0])) + { + string errorMsg() + { + string msg = "Argument mismatch for '" ~ fun.stringof ~ "': expected " ~ + Parameters!fun[0].stringof ~ ", but got " ~ typeof(arg).stringof ~ "."; + return msg; + } + static assert(0, errorMsg()); + } + else + { + import std.traits : ReturnType; + ReturnType!fun partial(Parameters!fun[1..$] args2) + { + return fun(arg, args2); + } + } + } } /// @@ -773,6 +819,11 @@ template partial(alias fun, alias arg) assert(partial!(tcallable, 5)(6) == 11); static assert(!is(typeof(partial!(tcallable, "5")(6)))); + static struct NonCallable{} + static assert(!__traits(compiles, partial!(NonCallable, 5)), "Partial should not work on non-callable structs."); + static assert(!__traits(compiles, partial!(NonCallable.init, 5)), + "Partial should not work on instances of non-callable structs."); + static A funOneArg(A)(A a) { return a; } alias funOneArg1 = partial!(funOneArg, 1); assert(funOneArg1() == 1); @@ -786,15 +837,223 @@ template partial(alias fun, alias arg) assert(dg2() == 1); } +// Fix https://issues.dlang.org/show_bug.cgi?id=15732 +@safe unittest +{ + // Test whether it works with functions. + auto partialFunction(){ + auto fullFunction = (float a, float b, float c) => a + b / c; + alias apply1 = partial!(fullFunction, 1); + return &apply1; + } + auto result = partialFunction()(2, 4); + assert(result == 1.5f); + + // And with delegates. + auto partialDelegate(float c){ + auto fullDelegate = (float a, float b) => a + b / c; + alias apply1 = partial!(fullDelegate, 1); + return &apply1; + } + auto result2 = partialDelegate(4)(2); + assert(result2 == 1.5f); +} + /** -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. +Takes a function of (potentially) many arguments, and returns a function taking +one argument and returns a callable taking the rest. f(x, y) == curry(f)(x)(y) + +Params: + F = a function taking at least one argument + t = a callable object whose opCall takes at least 1 object +Returns: + A single parameter callable object +*/ +template curry(alias F) +if (isCallable!F && Parameters!F.length) +{ + //inspired from the implementation from Artur Skawina here: + //https://forum.dlang.org/post/mailman.1626.1340110492.24740.digitalmars-d@puremagic.com + //This implementation stores a copy of all filled in arguments with each curried result + //this way, the curried functions are independent and don't share any references + //eg: auto fc = curry!f; auto fc1 = fc(1); auto fc2 = fc(2); fc1(3) != fc2(3) + struct CurryImpl(size_t N) + { + alias FParams = Parameters!F; + FParams[0 .. N] storedArguments; + static if (N > 0) + { + this(U : FParams[N - 1])(ref CurryImpl!(N - 1) prev, ref U arg) + { + storedArguments[0 .. N - 1] = prev.storedArguments[]; + storedArguments[N-1] = arg; + } + } + + auto opCall(U : FParams[N])(auto ref U arg) return scope + { + static if (N == FParams.length - 1) + { + return F(storedArguments, arg); + } + else + { + return CurryImpl!(N + 1)(this, arg); + } + } + } + + auto curry() + { + CurryImpl!0 res; + return res; // return CurryImpl!0.init segfaults for delegates on Windows + } +} + +/// +pure @safe @nogc nothrow unittest +{ + int f(int x, int y, int z) + { + return x + y + z; + } + auto cf = curry!f; + auto cf1 = cf(1); + auto cf2 = cf(2); + + assert(cf1(2)(3) == f(1, 2, 3)); + assert(cf2(2)(3) == f(2, 2, 3)); +} + +///ditto +auto curry(T)(T t) +if (isCallable!T && Parameters!T.length) +{ + static auto fun(ref T inst, ref Parameters!T args) + { + return inst(args); + } + + return curry!fun()(t); +} + +/// +pure @safe @nogc nothrow unittest +{ + //works with callable structs too + struct S + { + int w; + int opCall(int x, int y, int z) + { + return w + x + y + z; + } + } + + S s; + s.w = 5; + + auto cs = curry(s); + auto cs1 = cs(1); + auto cs2 = cs(2); + + assert(cs1(2)(3) == s(1, 2, 3)); + assert(cs1(2)(3) == (1 + 2 + 3 + 5)); + assert(cs2(2)(3) ==s(2, 2, 3)); +} + + +@safe pure @nogc nothrow unittest +{ + //currying a single argument function does nothing + int pork(int a){ return a*2;} + auto curryPork = curry!pork; + assert(curryPork(0) == pork(0)); + assert(curryPork(1) == pork(1)); + assert(curryPork(-1) == pork(-1)); + assert(curryPork(1000) == pork(1000)); + + //test 2 argument function + double mixedVeggies(double a, int b, bool) + { + return a + b; + } + + auto mixedCurry = curry!mixedVeggies; + assert(mixedCurry(10)(20)(false) == mixedVeggies(10, 20, false)); + assert(mixedCurry(100)(200)(true) == mixedVeggies(100, 200, true)); + + // struct with opCall + struct S + { + double opCall(int x, double y, short z) const pure nothrow @nogc + { + return x*y*z; + } + } + + S s; + auto curriedStruct = curry(s); + assert(curriedStruct(1)(2)(short(3)) == s(1, 2, short(3))); + assert(curriedStruct(300)(20)(short(10)) == s(300, 20, short(10))); +} + +pure @safe nothrow unittest +{ + auto cfl = curry!((double a, int b) => a + b); + assert(cfl(13)(2) == 15); + + int c = 42; + auto cdg = curry!((double a, int b) => a + b + c); + assert(cdg(13)(2) == 57); + + static class C + { + int opCall(int mult, int add) pure @safe nothrow @nogc scope + { + return mult * 42 + add; + } + } + + scope C ci = new C(); + scope cc = curry(ci); + assert(cc(2)(4) == ci(2, 4)); +} + +// Disallows callables without parameters +pure @safe @nogc nothrow unittest +{ + static void noargs() {} + static assert(!__traits(compiles, curry!noargs())); + + static struct NoArgs + { + void opCall() {} + } + + static assert(!__traits(compiles, curry(NoArgs.init))); +} + +private template Iota(size_t n) +{ + static if (n == 0) + alias Iota = AliasSeq!(); + else + alias Iota = AliasSeq!(Iota!(n - 1), n - 1); +} + +/** +Takes multiple functions and adjoins them together. + +Params: + F = the call-able(s) to adjoin +Returns: + A new function which returns a $(REF Tuple, std,typecons). Each of the + elements of the tuple will be the return values of `F`. 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])). +(`F[0]`). */ template adjoin(F...) if (F.length == 1) @@ -808,27 +1067,21 @@ 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.meta : staticMap; + + auto resultElement(size_t i)() { - import std.format : format; - import std.range : iota; - return mixin (q{tuple(%(F[%s](a)%|, %))}.format(iota(0, F.length))); + return F[i](a); } + + return tuple(staticMap!(resultElement, Iota!(F.length))); } } /// @safe unittest { - import std.functional, std.typecons : Tuple; + import 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); @@ -881,37 +1134,49 @@ if (F.length > 1) enum Tuple!(IS, IS, IS, IS) ret2 = adjoin!(bar, bar, bar, bar)(); } +// https://issues.dlang.org/show_bug.cgi?id=21347 +@safe @betterC unittest +{ + alias f = (int n) => n + 1; + alias g = (int n) => n + 2; + alias h = (int n) => n + 3; + alias i = (int n) => n + 4; + + auto result = adjoin!(f, g, h, i)(0); + + assert(result[0] == 1); + assert(result[1] == 2); + assert(result[2] == 3); + assert(result[3] == 4); +} + /** - 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. + Composes passed-in functions $(D fun[0], fun[1], ...). + + Params: + fun = the call-able(s) or `string`(s) to compose into one function + Returns: + A new function `f(x)` that in turn returns `fun[0](fun[1](...(x)))...`. See_Also: $(LREF pipe) */ template compose(fun...) +if (fun.length > 0) { static if (fun.length == 1) { alias compose = unaryFun!(fun[0]); } - else static if (fun.length == 2) + else { - // starch alias fun0 = unaryFun!(fun[0]); - alias fun1 = unaryFun!(fun[1]); + alias rest = compose!(fun[1 .. $]); - // protein: the core composition operation - typeof({ E a; return fun0(fun1(a)); }()) compose(E)(E a) + auto compose(Args...)(Args args) { - return fun0(fun1(a)); + return fun0(rest(args)); } } - else - { - // protein: assembling operations - alias compose = compose!(fun[0], compose!(fun[1 .. $])); - } } /// @@ -927,12 +1192,28 @@ template compose(fun...) assert(compose!(map!(to!(int)), split)("1 2 3").equal([1, 2, 3])); } +// https://issues.dlang.org/show_bug.cgi?id=6484 +@safe unittest +{ + int f(int a) { return a; } + int g(int a) { return a; } + int h(int a,int b,int c) { return a * b * c; } + + alias F = compose!(f,g,h); + assert(F(1,2,3) == f(g(h(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. + Params: + fun = the call-able(s) or `string`(s) to compose into one function + Returns: + A new function `f(x)` that in turn returns `fun[$-1](...fun[1](fun[0](x)))...`. + Example: ---- @@ -946,6 +1227,7 @@ int[] a = pipe!(readText, split, map!(to!(int)))("file.txt"); */ alias pipe(fun...) = compose!(Reverse!(fun)); +/// @safe unittest { import std.conv : to; @@ -985,26 +1267,36 @@ unittest } ---- -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. +Params: + fun = the call-able to memozie + maxSize = The maximum size of the GC buffer to hold the return values +Returns: + A new function which calls `fun` and caches its return values. + +Note: + Technically the memoized function should be pure because `memoize` assumes it will + always return the same result for a given tuple of arguments. However, `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 + // https://issues.dlang.org/show_bug.cgi?id=13580 + // alias Args = Parameters!fun; ReturnType!fun memoize(Parameters!fun args) { alias Args = Parameters!fun; import std.typecons : Tuple; + import std.traits : Unqual; - static ReturnType!fun[Tuple!Args] memo; + static Unqual!(ReturnType!fun)[Tuple!Args] memo; auto t = Tuple!Args(args); if (auto p = t in memo) return *p; - return memo[t] = fun(args); + auto r = fun(args); + memo[t] = r; + return r; } } @@ -1012,12 +1304,14 @@ template memoize(alias fun) template memoize(alias fun, uint maxSize) { import std.traits : ReturnType; - // alias Args = Parameters!fun; // Bugzilla 13580 + // https://issues.dlang.org/show_bug.cgi?id=13580 + // alias Args = Parameters!fun; ReturnType!fun memoize(Parameters!fun args) { - import std.traits : hasIndirections; + import std.meta : staticMap; + import std.traits : hasIndirections, Unqual; import std.typecons : tuple; - static struct Value { Parameters!fun args; ReturnType!fun res; } + static struct Value { staticMap!(Unqual, Parameters!fun) args; Unqual!(ReturnType!fun) res; } static Value[] memo; static size_t[] initialized; @@ -1036,7 +1330,7 @@ template memoize(alias fun, uint maxSize) } import core.bitop : bt, bts; - import std.conv : emplace; + import core.lifetime : emplace; size_t hash; foreach (ref arg; args) @@ -1046,7 +1340,9 @@ template memoize(alias fun, uint 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) + // only set to initialized after setting args and value + // https://issues.dlang.org/show_bug.cgi?id=14025 + bts(initialized.ptr, idx1); return memo[idx1].res; } else if (memo[idx1].args == args) @@ -1056,7 +1352,7 @@ template memoize(alias fun, uint 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) + bts(initialized.ptr, idx2); } else if (memo[idx2].args == args) return memo[idx2].res; @@ -1072,9 +1368,10 @@ template memoize(alias fun, uint maxSize) * 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 +@safe nothrow +unittest { - ulong fib(ulong n) @safe + ulong fib(ulong n) @safe nothrow { return n < 2 ? n : memoize!fib(n - 2) + memoize!fib(n - 1); } @@ -1094,8 +1391,8 @@ template memoize(alias fun, uint maxSize) } /** - * 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. + * This memoizes all values of `fact` up to the largest argument. To only cache the final + * result, move `memoize` outside the function as shown below. */ @safe unittest { @@ -1108,7 +1405,7 @@ template memoize(alias fun, uint maxSize) } /** - * When the $(D maxSize) parameter is specified, memoize will used + * When the `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 @@ -1154,7 +1451,7 @@ template memoize(alias fun, uint maxSize) } assert(fact(10) == 3628800); - // Issue 12568 + // https://issues.dlang.org/show_bug.cgi?id=12568 static uint len2(const string s) { // Error alias mLen2 = memoize!len2; if (s.length == 0) @@ -1169,8 +1466,9 @@ template memoize(alias fun, uint maxSize) assert(func(int.init) == 1); } -// 16079: memoize should work with arrays -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=16079 +// memoize should work with arrays +@system unittest // not @safe with -dip1000 due to memoize { int executed = 0; T median(T)(const T[] nums) { @@ -1193,7 +1491,7 @@ template memoize(alias fun, uint maxSize) assert(executed == 1); } -// 16079: memoize should work with structs +// https://issues.dlang.org/show_bug.cgi?id=16079: memoize should work with structs @safe unittest { int executed = 0; @@ -1212,9 +1510,20 @@ template memoize(alias fun, uint maxSize) assert(executed == 1); } -// 16079: memoize should work with classes +// https://issues.dlang.org/show_bug.cgi?id=20439 memoize should work with void opAssign @safe unittest { + static struct S + { + void opAssign(S) {} + } + + assert(memoize!(() => S()) == S()); +} + +// https://issues.dlang.org/show_bug.cgi?id=16079: memoize should work with classes +@system unittest // not @safe with -dip1000 due to memoize +{ int executed = 0; T pickFirst(T)(T first) { @@ -1246,6 +1555,33 @@ template memoize(alias fun, uint maxSize) assert(executed == 1); } +// https://issues.dlang.org/show_bug.cgi?id=20302 +@system unittest +{ + version (none) // TODO change `none` to `all` and fix remaining limitations + struct S { const int len; } + else + struct S { int len; } + + static string fun000( string str, S s) { return str[0 .. s.len] ~ "123"; } + static string fun001( string str, const S s) { return str[0 .. s.len] ~ "123"; } + static string fun010(const string str, S s) { return str[0 .. s.len] ~ "123"; } + static string fun011(const string str, const S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun100( string str, S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun101( string str, const S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun110(const string str, S s) { return str[0 .. s.len] ~ "123"; } + static const(string) fun111(const string str, const S s) { return str[0 .. s.len] ~ "123"; } + + static foreach (fun; AliasSeq!(fun000, fun001, fun010, fun011, fun100, fun101, fun110, fun111)) + {{ + alias mfun = memoize!fun; + assert(mfun("abcdefgh", S(3)) == "abc123"); + + alias mfun2 = memoize!(fun, 42); + assert(mfun2("asd", S(3)) == "asd123"); + }} +} + private struct DelegateFaker(F) { import std.typecons : FuncInfo, MemberFunctionGenerator; @@ -1305,6 +1641,11 @@ private struct DelegateFaker(F) * Convert a callable to a delegate with the same parameter list and * return type, avoiding heap allocations and use of auxiliary storage. * + * Params: + * fp = a function pointer or an aggregate type with `opCall` defined. + * Returns: + * A delegate with the context pointer pointing to nothing. + * * Example: * ---- * void doStuff() { @@ -1321,7 +1662,7 @@ private struct DelegateFaker(F) * * BUGS: * $(UL - * $(LI Does not work with $(D @safe) functions.) + * $(LI Does not work with `@safe` functions.) * $(LI Ignores C-style / D-style variadic arguments.) * ) */ @@ -1467,98 +1808,9 @@ if (isCallable!(F)) } } -/** -Forwards function arguments with saving ref-ness. -*/ +// forward used to be here but was moved to druntime 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 + import core.lifetime : fun = forward; + alias forward = fun!args; } diff --git a/libphobos/src/std/getopt.d b/libphobos/src/std/getopt.d index 5beddcc..d516012 100644 --- a/libphobos/src/std/getopt.d +++ b/libphobos/src/std/getopt.d @@ -3,7 +3,7 @@ /** Processing of command line options. -The getopt module implements a $(D getopt) function, which adheres to +The getopt module implements a `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 @@ -13,12 +13,12 @@ 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 +Credits: This module and its documentation are inspired by Perl's + $(HTTPS perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of + D's `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) +Source: $(PHOBOSSRC std/getopt.d) */ /* Copyright Andrei Alexandrescu 2008 - 2015. @@ -28,16 +28,17 @@ Distributed under the Boost Software License, Version 1.0. */ module std.getopt; -import std.exception; // basicExceptionCtors +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.) + `std.getopt.config.passThrough` was not present.) $(LI A command-line option was not found, and - $(D std.getopt.config.required) was present.) + `std.getopt.config.required` was present.) + $(LI A callback option is missing a value.) ) */ class GetOptException : Exception @@ -80,31 +81,31 @@ void main(string[] args) } --------- - The $(D getopt) function takes a reference to the command line - (as received by $(D main)) as its first argument, and an + The `getopt` function takes a reference to the command line + (as received by `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. + `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 + `getopt` are removed from `args`. Whatever in the + arguments did not look like an option is left in `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 + to their defaults and then invoke `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. + but not present), a `ConvException` exception is thrown. + If `std.getopt.config.passThrough` was not passed to `getopt` + and an unrecognized command-line argument is found, or if a required + argument is missing a `GetOptException` is thrown. - Depending on the type of the pointer being bound, $(D getopt) + Depending on the type of the pointer being bound, `getopt` recognizes the following kinds of options: $(OL - $(LI $(I Boolean options). A lone argument sets the option to $(D true). + $(LI $(I Boolean options). A lone argument sets the option to `true`. Additionally $(B true) or $(B false) can be set within the option separated with an "=" sign: @@ -113,11 +114,11 @@ void main(string[] args) 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 `verbose` to `true`, invoke the program with either + `--verbose` or `--verbose=true`. - To set $(D debugging) to $(D false), invoke the program with - $(D --debugging=false). + To set `debugging` to `false`, invoke the program with + `--debugging=false`. ) $(LI $(I Numeric options.) If an option is bound to a numeric type, a @@ -129,8 +130,8 @@ void main(string[] args) getopt(args, "timeout", &timeout); --------- - To set $(D timeout) to $(D 5), invoke the program with either - $(D --timeout=5) or $(D --timeout 5). + To set `timeout` to `5`, invoke the program with either + `--timeout=5` or $(D --timeout 5). ) $(LI $(I Incremental options.) If an option name has a "+" suffix and is @@ -145,7 +146,7 @@ void main(string[] args) 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 + `paranoid` to 42; instead, `paranoid` is set to 2 and "42" is not considered as part of the normal program arguments. ) @@ -159,8 +160,8 @@ void main(string[] args) getopt(args, "color", &color); --------- - To set $(D color) to $(D Color.yes), invoke the program with either - $(D --color=yes) or $(D --color yes). + To set `color` to `Color.yes`, invoke the program with either + `--color=yes` or $(D --color yes). ) $(LI $(I String options.) If an option is bound to a string, a string is @@ -173,7 +174,7 @@ 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 + will set `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'. ) @@ -187,14 +188,15 @@ 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 + "--output myfile.txt --output yourfile.txt" will set `outputFiles` to $(D [ "myfile.txt", "yourfile.txt" ]). - Alternatively you can set $(LREF arraySep) as the element separator: + Alternatively you can set $(LREF arraySep) to allow multiple elements in + one parameter. --------- string[] outputFiles; -arraySep = ","; // defaults to "", separation by whitespace +arraySep = ","; // defaults to "", meaning one element per parameter getopt(args, "output", &outputFiles); --------- @@ -211,13 +213,13 @@ 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 ]. + `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 +arraySep = ","; // defaults to "", meaning one element per parameter getopt(args, "tune", &tuningParms); --------- @@ -309,7 +311,7 @@ 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: +by passing `getopt` the `caseSensitive` directive like this: --------- bool foo, bar; @@ -321,8 +323,8 @@ getopt(args, 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: +The directive is active until the end of `getopt`, or until the +converse directive `caseInsensitive` is encountered: --------- bool foo, bar; @@ -341,28 +343,21 @@ 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 +only one dash (e.g. `-t`). `getopt` accepts such parameters +seamlessly. When used with a double-dash (e.g. `--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); ---------- +used with a single dash, a single-letter option is accepted. -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. +To set `timeout` to `5`, use either of the following: `--timeout=5`, +`--timeout 5`, `--t=5`, `--t 5`, `-t5`, or `-t 5`. Forms such as +`-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: +with the `std.getopt.config.bundling` directive: --------- bool foo, bar; @@ -373,7 +368,7 @@ getopt(args, --------- In case you want to only enable bundling for some of the parameters, -bundling can be turned off with $(D std.getopt.config.noBundling). +bundling can be turned off with `std.getopt.config.noBundling`. Required: An option can be marked as required. If that option is not present in the @@ -387,13 +382,13 @@ getopt(args, "bar|b", &bar); --------- -Only the option directly following $(D std.getopt.config.required) is +Only the option directly following `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): +`getopt` did not understand, it can pass the +`std.getopt.config.passThrough` directive to `getopt`: --------- bool foo, bar; @@ -404,22 +399,22 @@ getopt(args, --------- An unrecognized option such as "--baz" will be found untouched in -$(D args) after $(D getopt) returns. +`args` after `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 +description for this option. The `getopt` function returns a struct of type +`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 +about these options was requested. The `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 +A lone double-dash terminates `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) +parses foo but leaves "--bar" in `args`. The double-dash itself is +removed from the argument array unless the `std.getopt.config.keepEndOfOptions` directive is given. */ GetoptResult getopt(T...)(ref string[] args, T opts) @@ -460,9 +455,9 @@ GetoptResult getopt(T...)(ref string[] args, T opts) } /** - Configuration options for $(D getopt). + Configuration options for `getopt`. - You can pass them to $(D getopt) in any position, except in between an option + You can pass them to `getopt` in any position, except in between an option string and its bound pointer. */ enum config { @@ -486,9 +481,9 @@ enum config { required } -/** The result of the $(D getopt) function. +/** The result of the `getopt` function. -$(D helpWanted) is set if the option `--help` or `-h` was passed to the option parser. +`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 @@ -561,7 +556,6 @@ follow this pattern: 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)); @@ -580,24 +574,27 @@ private template optionValidator(A...) { msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0); } - else foreach (i; staticIota!(1, A.length)) + else { - 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])) + static foreach (i; 1 .. A.length) { - msg = format(fmt, "a string can not be preceeded by two strings", i); - break; + static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) && + !(is(A[i] == config))) + { + msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i); + goto end; + } + else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1])) + { + msg = format(fmt, "a receiver can not be preceeded by a receiver", i); + goto end; + } + 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); + goto end; + } } } static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config)) @@ -606,6 +603,7 @@ private template optionValidator(A...) A.length -1); } } + end: return msg; } enum message = validator; @@ -654,8 +652,10 @@ private template optionValidator(A...) static assert(optionValidator!(C,A,P,C,A,S,F) == ""); } -@system unittest // bugzilla 15914 +// https://issues.dlang.org/show_bug.cgi?id=15914 +@safe unittest { + import std.exception : assertThrown; bool opt; string[] args = ["program", "-a"]; getopt(args, config.passThrough, 'a', &opt); @@ -725,13 +725,13 @@ private void getoptImpl(T...)(ref string[] args, ref configuration cfg, static if (is(typeof(opts[1]) : string)) { - auto receiver = opts[2]; + alias receiver = opts[2]; optionHelp.help = opts[1]; immutable lowSliceIdx = 3; } else { - auto receiver = opts[1]; + alias receiver = opts[1]; immutable lowSliceIdx = 2; } @@ -869,7 +869,6 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, { 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("", ""))); @@ -877,7 +876,7 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, { // Eat the next argument too. Check to make sure there's one // to be eaten first, though. - enforce(i < args.length, + enforce!GetOptException(i < args.length, "Missing value for argument " ~ a ~ "."); val = args[i]; args = args[0 .. i] ~ args[i + 1 .. $]; @@ -907,13 +906,17 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, } else static if (is(typeof(receiver("")) : void)) { - static assert(is(typeof(receiver("")) : void)); + alias RType = typeof(receiver("")); + static assert(is(RType : void), + "Invalid receiver return type " ~ RType.stringof); // boolean-style receiver receiver(option); } else { - static assert(is(typeof(receiver()) : void)); + alias RType = typeof(receiver()); + static assert(is(RType : void), + "Invalid receiver return type " ~ RType.stringof); // boolean-style receiver without argument receiver(); } @@ -973,8 +976,8 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, return ret; } -// 17574 -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=17574 +@safe unittest { import std.algorithm.searching : startsWith; @@ -993,8 +996,8 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, assert(goe.msg.startsWith("Could not find")); } -// 5316 - arrays with arraySep -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=5316 - arrays with arraySep +@safe unittest { import std.conv; @@ -1022,8 +1025,8 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, assert(names == ["foo", "bar", "baz"], to!string(names)); } -// 5316 - associative arrays with arraySep -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=5316 - associative arrays with arraySep +@safe unittest { import std.conv; @@ -1055,30 +1058,33 @@ private bool handleOption(R)(string option, R receiver, ref string[] args, /** The option character (default '-'). - Defaults to '-' but it can be assigned to prior to calling $(D getopt). + Defaults to '-' but it can be assigned to prior to calling `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. + Defaults to "--" but can be assigned to prior to calling `getopt`. Assigning an + empty string to `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). + Defaults to '=' but can be assigned to prior to calling `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). + When set to "", parameters to array and associative array receivers are + treated as an individual argument. That is, only one argument is appended or + inserted per appearance of the option switch. If `arraySep` is set to + something else, then each parameter is first split by the separator, and the + individual pieces are treated as arguments to the same option. - Defaults to "" but can be assigned to prior to calling $(D getopt). + Defaults to "" but can be assigned to prior to calling `getopt`. */ string arraySep = ""; @@ -1097,12 +1103,12 @@ private struct configuration ubyte, "", 2)); } -private bool optMatch(string arg, string optPattern, ref string value, +private bool optMatch(string arg, scope string optPattern, ref string value, configuration cfg) @safe { - import std.array : split; + import std.algorithm.iteration : splitter; import std.string : indexOf; - import std.uni : toUpper; + import std.uni : icmp; //writeln("optMatch:\n ", arg, "\n ", optPattern, "\n ", value); //scope(success) writeln("optMatch result: ", value); if (arg.length < 2 || arg[0] != optionChar) return false; @@ -1142,11 +1148,10 @@ private bool optMatch(string arg, string optPattern, ref string value, } //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value); // Split the option - const variants = split(optPattern, "|"); - foreach (v ; variants) + foreach (v; splitter(optPattern, "|")) { //writeln("Trying variant: ", v, " against ", arg); - if (arg == v || !cfg.caseSensitive && toUpper(arg) == toUpper(v)) + if (arg == v || (!cfg.caseSensitive && icmp(arg, v) == 0)) return true; if (cfg.bundling && !isLong && v.length == 1 && indexOf(arg, v) >= 0) @@ -1176,10 +1181,10 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow } } -@system unittest +@safe unittest { import std.conv; - import std.math; + import std.math.operations : isClose; uint paranoid = 2; string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"]; @@ -1236,8 +1241,8 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow getopt(testArgs, "tune", &tuningParms); assert(testArgs.length == 1); assert(tuningParms.length == 2); - assert(approxEqual(tuningParms["alpha"], 0.5)); - assert(approxEqual(tuningParms["beta"], 0.6)); + assert(isClose(tuningParms["alpha"], 0.5)); + assert(isClose(tuningParms["beta"], 0.6)); arraySep = ""; } @@ -1356,6 +1361,12 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow args = ["program.name", "--verbose", "2"]; try { getopt(args, "verbose", &myStaticHandler3); assert(0); } catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); } + + // check that GetOptException is thrown if the value is missing + args = ["program.name", "--verbose"]; + try { getopt(args, "verbose", &myStaticHandler3); assert(0); } + catch (GetOptException e) {} + catch (Exception e) { assert(0); } } @safe unittest // @safe std.getopt.config option use @@ -1368,9 +1379,9 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(x == 2); } -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=2142 +@safe unittest { - // From bugzilla 2142 bool f_linenum, f_filename; string[] args = [ "", "-nl" ]; getopt @@ -1385,9 +1396,9 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(f_filename); } -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=6887 +@safe unittest { - // From bugzilla 6887 string[] p; string[] args = ["", "-pa"]; getopt(args, "p", &p); @@ -1395,25 +1406,25 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(p[0] == "a"); } -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=6888 +@safe unittest { - // From bugzilla 6888 int[string] foo; auto args = ["", "-t", "a=1"]; getopt(args, "t", &foo); assert(foo == ["a":1]); } -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=9583 +@safe 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 +@safe unittest { string foo, bar; auto args = ["prog", "-thello", "-dbar=baz"]; @@ -1421,7 +1432,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(foo == "hello"); assert(bar == "bar=baz"); - // From bugzilla 5762 + // From https://issues.dlang.org/show_bug.cgi?id=5762 string a; args = ["prog", "-a-0x12"]; getopt(args, config.bundling, "a|addr", &a); @@ -1430,7 +1441,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow getopt(args, config.bundling, "a|addr", &a); assert(a == "-0x12"); - // From https://d.puremagic.com/issues/show_bug.cgi?id=11764 + // From https://issues.dlang.org/show_bug.cgi?id=11764 args = ["main", "-test"]; bool opt; args.getopt(config.passThrough, "opt", &opt); @@ -1448,7 +1459,8 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(o == "str"); } -@system unittest // 5228 +// https://issues.dlang.org/show_bug.cgi?id=5228 +@safe unittest { import std.conv; import std.exception; @@ -1461,7 +1473,8 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assertThrown!ConvException(getopt(args, "abc", &abc)); } -@system unittest // From bugzilla 7693 +// https://issues.dlang.org/show_bug.cgi?id=7693 +@safe unittest { import std.exception; @@ -1481,7 +1494,8 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assertNotThrown(getopt(args, "foo", &foo)); } -@system unittest // same bug as 7693 only for bool +// Same as https://issues.dlang.org/show_bug.cgi?id=7693 only for `bool` +@safe unittest { import std.exception; @@ -1493,7 +1507,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(foo); } -@system unittest +@safe unittest { bool foo; auto args = ["prog", "--foo"]; @@ -1501,7 +1515,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(foo); } -@system unittest +@safe unittest { bool foo; bool bar; @@ -1512,7 +1526,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(bar); } -@system unittest +@safe unittest { bool foo; bool bar; @@ -1524,7 +1538,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(bar); } -@system unittest +@safe unittest { import std.exception; @@ -1536,7 +1550,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow config.passThrough)); } -@system unittest +@safe unittest { import std.exception; @@ -1550,7 +1564,7 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(!bar); } -@system unittest +@safe unittest { bool foo; auto args = ["prog", "-f"]; @@ -1566,8 +1580,9 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(r.helpWanted); } -// Issue 13316 - std.getopt: implicit help option breaks the next argument -@system unittest +// std.getopt: implicit help option breaks the next argument +// https://issues.dlang.org/show_bug.cgi?id=13316 +@safe unittest { string[] args = ["program", "--help", "--", "something"]; getopt(args); @@ -1583,8 +1598,9 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(args == ["program", "nonoption", "--option"]); } -// Issue 13317 - std.getopt: endOfOptions broken when it doesn't look like an option -@system unittest +// std.getopt: endOfOptions broken when it doesn't look like an option +// https://issues.dlang.org/show_bug.cgi?id=13317 +@safe unittest { auto endOfOptionsBackup = endOfOptions; scope(exit) endOfOptions = endOfOptionsBackup; @@ -1596,13 +1612,25 @@ private void setConfig(ref configuration cfg, config option) @safe pure nothrow assert(args == ["program", "--option"]); } -/** This function prints the passed $(D Option)s and text in an aligned manner on $(D stdout). +// make std.getopt ready for DIP 1000 +// https://issues.dlang.org/show_bug.cgi?id=20480 +@safe unittest +{ + string[] args = ["test", "--foo", "42", "--bar", "BAR"]; + int foo; + string bar; + getopt(args, "foo", &foo, "bar", "bar help", &bar); + assert(foo == 42); + assert(bar == "BAR"); +} + +/** This function prints the passed `Option`s and text in an aligned manner on `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 +will be aligned to the longest option of every `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 +`Option`. If a help message is present it will be printed next. The format is illustrated by this code: ------------ @@ -1616,7 +1644,7 @@ foreach (it; opt) Params: text = The text to printed at the beginning of the help output. - opt = The $(D Option) extracted from the $(D getopt) parameter. + opt = The `Option` extracted from the `getopt` parameter. */ void defaultGetoptPrinter(string text, Option[] opt) { @@ -1625,19 +1653,20 @@ void defaultGetoptPrinter(string text, Option[] opt) defaultGetoptFormatter(stdout.lockingTextWriter(), text, opt); } -/** This function writes the passed text and $(D Option) into an output range +/** This function writes the passed text and `Option` into an output range in the manner described in the documentation of function -$(D defaultGetoptPrinter). +`defaultGetoptPrinter`, unless the style option is used. 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. + opt = The `Option` extracted from the `getopt` parameter. + style = The manner in which to display the output of each `Option.` */ -void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) +void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, string style = "%*s %*s%*s%s\n") { import std.algorithm.comparison : min, max; - import std.format : formattedWrite; + import std.format.write : formattedWrite; output.formattedWrite("%s\n", text); @@ -1655,12 +1684,12 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) foreach (it; opt) { - output.formattedWrite("%*s %*s%*s%s\n", ls, it.optShort, ll, it.optLong, + output.formattedWrite(style, ls, it.optShort, ll, it.optLong, hasRequired ? re.length : 1, it.required ? re : " ", it.help); } } -@system unittest +@safe unittest { import std.conv; @@ -1689,7 +1718,7 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) assert(wanted == helpMsg); } -@system unittest +@safe unittest { import std.array ; import std.conv; @@ -1718,7 +1747,8 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) assert(wanted == helpMsg, helpMsg ~ wanted); } -@system unittest // Issue 14724 +// https://issues.dlang.org/show_bug.cgi?id=14724 +@safe unittest { bool a; auto args = ["prog", "--help"]; @@ -1740,7 +1770,8 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) // throw on duplicate options @system unittest { - import core.exception; + import core.exception : AssertError; + import std.exception : assertNotThrown, assertThrown; auto args = ["prog", "--abc", "1"]; int abc, def; assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc)); @@ -1748,7 +1779,8 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def)); } -@system unittest // Issue 17327 repeated option use +// https://issues.dlang.org/show_bug.cgi?id=17327 repeated option use +@safe unittest { long num = 0; @@ -1835,7 +1867,9 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) assert(y == 50); } -@system unittest // Hyphens at the start of option values; Issue 17650 +// Hyphens at the start of option values; +// https://issues.dlang.org/show_bug.cgi?id=17650 +@safe unittest { auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"]; @@ -1855,3 +1889,32 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) assert(c == '-'); assert(f == "-"); } + +@safe 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, "\t\t%*s %*s%*s\n%s\n"); + + string helpMsg = app.data; + //writeln(helpMsg); + assert(helpMsg.length); + assert(helpMsg.count("\n") == 5, 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\t\t-f --foo \nHelp\n\t\t-h --help \nThis help " + ~ "information.\n"; + assert(wanted == helpMsg); +} diff --git a/libphobos/src/std/internal/attributes.d b/libphobos/src/std/internal/attributes.d new file mode 100644 index 0000000..2405326 --- /dev/null +++ b/libphobos/src/std/internal/attributes.d @@ -0,0 +1,11 @@ +module std.internal.attributes; + +/** +Used to annotate `unittest`s which need to be tested in a `-betterC` environment. + +Such `unittest`s will be compiled and executed without linking druntime in, with +a `__traits(getUnitTests, mixin(__MODULE__))` style test runner. +Note that just like any other `unittest` in phobos, they will also be compiled +and executed without `-betterC`. +*/ +package(std) enum betterC = 1; diff --git a/libphobos/src/std/internal/cstring.d b/libphobos/src/std/internal/cstring.d index e5bc7f7..a61ee81 100644 --- a/libphobos/src/std/internal/cstring.d +++ b/libphobos/src/std/internal/cstring.d @@ -11,7 +11,7 @@ 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)) +COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, `core.$1.$2`) */ module std.internal.cstring; @@ -24,16 +24,16 @@ module std.internal.cstring; import core.sys.posix.stdlib : setenv; import std.exception : enforce; - void setEnvironment(in char[] name, in char[] value) + void setEnvironment(scope const(char)[] name, scope const(char)[] value) { enforce(setenv(name.tempCString(), value.tempCString(), 1) != -1); } } version (Windows) { - import core.sys.windows.windows : SetEnvironmentVariableW; + import core.sys.windows.winbase : SetEnvironmentVariableW; import std.exception : enforce; - void setEnvironment(in char[] name, in char[] value) + void setEnvironment(scope const(char)[] name, scope const(char)[] value) { enforce(SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW())); } } } @@ -41,18 +41,6 @@ module std.internal.cstring; 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. @@ -63,8 +51,8 @@ Params: 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*). +has two properties: `ptr` to access $(I C string) as $(D const To*) +and `buffPtr` to access it as `To*`. The value returned can be indexed by [] to access it as an array. @@ -77,149 +65,99 @@ 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). +allocate temporary one using C's `malloc`. Note: This function is intended to be used in function call expression (like -$(D strlen(str.tempCString()))). Incorrect usage of this function may +`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) +auto tempCString(To = char, From)(scope 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: + auto res = TempCStringBuffer!To.trustedVoidInit(); // expensive to fill _buff[] - @disable this(); - @disable this(this); - alias ptr this; - - @property inout(To)* buffPtr() inout pure - { - return _ptr == useStack ? _buff.ptr : _ptr; - } + // Note: res._ptr can't point to res._buff as structs are movable. - @property const(To)* ptr() const pure + // https://issues.dlang.org/show_bug.cgi?id=14980 + static if (isSomeString!From) + { + if (str is null) { - return buffPtr; + res._length = 0; + res._ptr = null; + return res; } + } - const(To)[] opIndex() const pure + // Use slice assignment if available. + static if (To.sizeof == CF.sizeof && is(typeof(res._buff[0 .. str.length] = str[]))) + { + if (str.length < res._buff.length) { - return buffPtr[0 .. _length]; + res._buff[0 .. str.length] = str[]; + res._buff[str.length] = 0; + res._ptr = res.useStack; } - - ~this() + else { - if (_ptr != useStack) + import std.internal.memory : enforceMalloc; + if (false) { - import core.stdc.stdlib : free; - free(_ptr); + // This code is removed by the compiler but causes `@safe`ty + // to be inferred correctly. + CF[0] x; + x[] = str[0 .. 0]; } + res._ptr = () @trusted { + auto p = cast(CF*) enforceMalloc((str.length + 1) * CF.sizeof); + p[0 .. str.length] = str[]; + p[str.length] = 0; + return cast(To*) p; + }(); } - - private: - To* _ptr; - size_t _length; // length of the string - - // the 'small string optimization' - version (unittest) - { - // smaller size to trigger reallocations. Padding is to account for - // unittest/non-unittest cross-compilation (to avoid corruption) - To[16 / To.sizeof] _buff; - To[(256 - 16) / To.sizeof] _unittest_pad; - } - else - { - To[256 / To.sizeof] _buff; // production size - } - - static Res trustedVoidInit() { Res res = void; return res; } + res._length = str.length; + 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 + else { - pragma(inline, false); // because it's rarely called - - import core.exception : onOutOfMemoryError; - import core.stdc.stdlib : malloc, realloc; - import core.stdc.string : memcpy; + static assert(!(isSomeString!From && CF.sizeof == To.sizeof), "Should be using slice assignment."); + To[] p = res._buff; + size_t i; - if (res_is_onstack) + size_t strLength; + static if (hasLength!From) { - 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]; + strLength = str.length; } + import std.utf : byUTF; + static if (isSomeString!From) + auto r = cast(const(CF)[])str; // because inout(CF) causes problems with byUTF else + alias r = str; + To[] heapBuffer; + foreach (const c; byUTF!(Unqual!To)(r)) { - 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; + if (i + 1 == p.length) + { + if (heapBuffer is null) + heapBuffer = trustedReallocStack(p, strLength); + else + heapBuffer = trustedRealloc(heapBuffer); + p = heapBuffer; + } + p[i++] = c; } - q[i++] = c; + p[i] = 0; + res._length = i; + res._ptr = (heapBuffer is null ? res.useStack : &heapBuffer[0]); + return res; } - q[i] = 0; - res._length = i; - res._ptr = p_is_onstack ? useStack : &p[0]; - return res; } /// @@ -244,21 +182,30 @@ nothrow @nogc @system unittest // both primary expressions are ended. } -@safe nothrow @nogc unittest +@safe pure nothrow @nogc unittest { - assert("abc".tempCString().asArray == "abc"); - assert("abc"d.tempCString().ptr.asArray == "abc"); - assert("abc".tempCString!wchar().buffPtr.asArray == "abc"w); + static inout(C)[] arrayFor(C)(inout(C)* cstr) pure nothrow @nogc @trusted + { + assert(cstr); + size_t length = 0; + while (cstr[length]) + ++length; + return cstr[0 .. length]; + } + + assert(arrayFor("abc".tempCString()) == "abc"); + assert(arrayFor("abc"d.tempCString().ptr) == "abc"); + assert(arrayFor("abc".tempCString!wchar().buffPtr) == "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(arrayFor(tempCString(abc[].byChar).buffPtr) == abc); + assert(arrayFor(tempCString(abc[].byWchar).buffPtr) == abc); assert(tempCString(abc[].byChar)[] == abc); } -// Bugzilla 14980 -nothrow @nogc @safe unittest +// https://issues.dlang.org/show_bug.cgi?id=14980 +pure nothrow @nogc @safe unittest { const(char[]) str = null; auto res = tempCString(str); @@ -267,4 +214,99 @@ nothrow @nogc @safe unittest } version (Windows) - alias tempCStringW = tempCString!(wchar, const(char)[]); +{ + import core.sys.windows.winnt : WCHAR; + alias tempCStringW = tempCString!(WCHAR, const(char)[]); +} + +private struct TempCStringBuffer(To = char) +{ +@trusted pure nothrow @nogc: + + @disable this(); + @disable this(this); + alias ptr this; /// implicitly covert to raw pointer + + @property inout(To)* buffPtr() inout + { + return _ptr == useStack ? _buff.ptr : _ptr; + } + + @property const(To)* ptr() const + { + return buffPtr; + } + + const(To)[] opIndex() const pure + { + return buffPtr[0 .. _length]; + } + + ~this() + { + if (_ptr != useStack) + { + import core.memory : pureFree; + pureFree(_ptr); + } + } + +private: + enum To* useStack = () @trusted { return cast(To*) size_t.max; }(); + + To* _ptr; + size_t _length; // length of the string + version (StdUnittest) + // the 'small string optimization' + { + // smaller size to trigger reallocations. Padding is to account for + // unittest/non-unittest cross-compilation (to avoid corruption) + To[16 / To.sizeof] _buff; + To[(256 - 16) / To.sizeof] _unittest_pad; + } + else + { + To[256 / To.sizeof] _buff; // production size + } + + static TempCStringBuffer trustedVoidInit() { TempCStringBuffer res = void; return res; } +} + +private To[] trustedRealloc(To)(return scope To[] buf) + @trusted @nogc pure nothrow +{ + pragma(inline, false); // because it's rarely called + import std.internal.memory : enforceRealloc; + + const size_t newlen = buf.length * 3 / 2; + if (buf.length >= size_t.max / (2 * To.sizeof)) + { + version (D_Exceptions) + { + import core.exception : onOutOfMemoryError; + onOutOfMemoryError(); + } + else + { + assert(0, "Memory allocation failed"); + } + } + auto ptr = cast(To*) enforceRealloc(buf.ptr, newlen * To.sizeof); + return ptr[0 .. newlen]; + +} + +private To[] trustedReallocStack(To)(scope To[] buf, size_t strLength) + @trusted @nogc pure nothrow +{ + pragma(inline, false); // because it's rarely called + + import std.internal.memory : enforceMalloc; + + size_t newlen = buf.length * 3 / 2; + if (newlen <= strLength) + newlen = strLength + 1; // +1 for terminating 0 + auto ptr = cast(To*) enforceMalloc(newlen * To.sizeof); + ptr[0 .. buf.length] = buf[]; + return ptr[0 .. newlen]; +} diff --git a/libphobos/src/std/internal/math/biguintcore.d b/libphobos/src/std/internal/math/biguintcore.d index 6fc2d16..59d7842 100644 --- a/libphobos/src/std/internal/math/biguintcore.d +++ b/libphobos/src/std/internal/math/biguintcore.d @@ -35,43 +35,184 @@ module std.internal.math.biguintcore; version (D_InlineAsm_X86) { - import std.internal.math.biguintx86; -} -else -{ - import std.internal.math.biguintnoasm; + static import std.internal.math.biguintx86; } +static import std.internal.math.biguintnoasm; + +import std.internal.math.biguintnoasm : BigDigit, KARATSUBALIMIT, + KARATSUBASQUARELIMIT; alias multibyteAdd = multibyteAddSub!('+'); alias multibyteSub = multibyteAddSub!('-'); - -import core.cpuid; +private import std.traits; +private import std.range.primitives; public import std.ascii : LetterCase; import std.range.primitives; import std.traits; -shared static this() +private: + +// dipatchers to the right low-level primitives. Added to allow BigInt CTFE for +// 32 bit systems (https://issues.dlang.org/show_bug.cgi?id=14767) although it's +// used by the other architectures too. +// See comments below in case it has to be refactored. +version (X86) +uint multibyteAddSub(char op)(uint[] dest, const(uint)[] src1, const (uint)[] src2, uint carry) { - CACHELIMIT = core.cpuid.datacache[0].size*1024/2; + // must be checked before, otherwise D_InlineAsm_X86 is true. + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteAddSub!op(dest, src1, src2, carry); + // Runtime. + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteAddSub!op(dest, src1, src2, carry); + // Runtime if no asm available. + else + return std.internal.math.biguintnoasm.multibyteAddSub!op(dest, src1, src2, carry); } +// Any other architecture +else alias multibyteAddSub = std.internal.math.biguintnoasm.multibyteAddSub; + +version (X86) +uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteIncrementAssign!op(dest, carry); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteIncrementAssign!op(dest, carry); + else + return std.internal.math.biguintnoasm.multibyteIncrementAssign!op(dest, carry); +} +else alias multibyteIncrementAssign = std.internal.math.biguintnoasm.multibyteIncrementAssign; + +version (X86) +uint multibyteShl()(uint[] dest, const(uint)[] src, uint numbits) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteShl(dest, src, numbits); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteShl(dest, src, numbits); + else + return std.internal.math.biguintnoasm.multibyteShl(dest, src, numbits); +} +else alias multibyteShl = std.internal.math.biguintnoasm.multibyteShl; + +version (X86) +void multibyteShr()(uint[] dest, const(uint)[] src, uint numbits) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteShr(dest, src, numbits); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteShr(dest, src, numbits); + else + std.internal.math.biguintnoasm.multibyteShr(dest, src, numbits); +} +else alias multibyteShr = std.internal.math.biguintnoasm.multibyteShr; + +version (X86) +uint multibyteMul()(uint[] dest, const(uint)[] src, uint multiplier, uint carry) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteMul(dest, src, multiplier, carry); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteMul(dest, src, multiplier, carry); + else + return std.internal.math.biguintnoasm.multibyteMul(dest, src, multiplier, carry); +} +else alias multibyteMul = std.internal.math.biguintnoasm.multibyteMul; + +version (X86) +uint multibyteMulAdd(char op)(uint[] dest, const(uint)[] src, uint multiplier, uint carry) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteMulAdd!op(dest, src, multiplier, carry); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteMulAdd!op(dest, src, multiplier, carry); + else + return std.internal.math.biguintnoasm.multibyteMulAdd!op(dest, src, multiplier, carry); +} +else alias multibyteMulAdd = std.internal.math.biguintnoasm.multibyteMulAdd; + +version (X86) +void multibyteMultiplyAccumulate()(uint[] dest, const(uint)[] left, const(uint)[] right) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteMultiplyAccumulate(dest, left, right); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteMultiplyAccumulate(dest, left, right); + else + std.internal.math.biguintnoasm.multibyteMultiplyAccumulate(dest, left, right); +} +else alias multibyteMultiplyAccumulate = std.internal.math.biguintnoasm.multibyteMultiplyAccumulate; + +version (X86) +uint multibyteDivAssign()(uint[] dest, uint divisor, uint overflow) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteDivAssign(dest, divisor, overflow); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteDivAssign(dest, divisor, overflow); + else + return std.internal.math.biguintnoasm.multibyteDivAssign(dest, divisor, overflow); +} +else alias multibyteDivAssign = std.internal.math.biguintnoasm.multibyteDivAssign; + +version (X86) +void multibyteAddDiagonalSquares()(uint[] dest, const(uint)[] src) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteAddDiagonalSquares(dest, src); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteAddDiagonalSquares(dest, src); + else + std.internal.math.biguintnoasm.multibyteAddDiagonalSquares(dest, src); +} +else alias multibyteAddDiagonalSquares = std.internal.math.biguintnoasm.multibyteAddDiagonalSquares; + +version (X86) +void multibyteTriangleAccumulate()(uint[] dest, const(uint)[] x) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteTriangleAccumulate(dest, x); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteTriangleAccumulate(dest, x); + else + std.internal.math.biguintnoasm.multibyteTriangleAccumulate(dest, x); +} +else alias multibyteTriangleAccumulate = std.internal.math.biguintnoasm.multibyteTriangleAccumulate; + +version (X86) +void multibyteSquare()(BigDigit[] result, const(BigDigit)[] x) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteSquare(result, x); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteSquare(result, x); + else + std.internal.math.biguintnoasm.multibyteSquare(result, x); +} +else alias multibyteSquare = std.internal.math.biguintnoasm.multibyteSquare; -private: // Limits for when to switch between algorithms. -immutable size_t CACHELIMIT; // Half the size of the data cache. +// Half the size of the data cache. +@nogc nothrow pure @safe size_t getCacheLimit() +{ + import core.cpuid : dataCaches; + return dataCaches[0].size * 1024 / 2; +} 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 }; + enum { LG2BIGDIGITBITS = 5, BIGDIGITSHIFTMASK = 31 } alias BIGHALFDIGIT = ushort; } else static if (BigDigit.sizeof == long.sizeof) { alias BIGHALFDIGIT = uint; - enum { LG2BIGDIGITBITS = 6, BIGDIGITSHIFTMASK = 63 }; + enum { LG2BIGDIGITBITS = 6, BIGDIGITSHIFTMASK = 63 } } else static assert(0, "Unsupported BigDigit size"); @@ -98,17 +239,18 @@ struct BigUint private: pure invariant() { - assert( data.length >= 1 && (data.length == 1 || data[$-1] != 0 )); + assert( data.length >= 1 && (data.length == 1 || data[$-1] != 0 ), + "Invariant requires data to not empty or zero"); } immutable(BigDigit) [] data = ZERO; - this(immutable(BigDigit) [] x) pure nothrow @nogc @safe + this(return scope 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) + this(T)(T x) pure nothrow @safe scope if (isIntegral!T) { opAssign(x); } @@ -118,7 +260,7 @@ private: }; public: // Length in uints - @property size_t uintLength() pure nothrow const @safe @nogc + @property size_t uintLength() pure nothrow const @safe @nogc scope { static if (BigDigit.sizeof == uint.sizeof) { @@ -130,7 +272,7 @@ public: ((data[$-1] & 0xFFFF_FFFF_0000_0000L) ? 1 : 0); } } - @property size_t ulongLength() pure nothrow const @safe @nogc + @property size_t ulongLength() pure nothrow const @safe @nogc scope { static if (BigDigit.sizeof == uint.sizeof) { @@ -143,7 +285,7 @@ public: } // The value at (cast(ulong[]) data)[n] - ulong peekUlong(int n) pure nothrow const @safe @nogc + ulong peekUlong(size_t n) pure nothrow const @safe @nogc scope { static if (BigDigit.sizeof == int.sizeof) { @@ -155,7 +297,8 @@ public: return data[n]; } } - uint peekUint(int n) pure nothrow const @safe @nogc + + uint peekUint(size_t n) pure nothrow const @safe @nogc scope { static if (BigDigit.sizeof == int.sizeof) { @@ -167,9 +310,9 @@ public: return (n & 1) ? cast(uint)(x >> 32) : cast(uint) x; } } -public: + /// - void opAssign(Tulong)(Tulong u) pure nothrow @safe if (is (Tulong == ulong)) + void opAssign(Tulong)(Tulong u) pure nothrow @safe scope if (is (Tulong == ulong)) { if (u == 0) data = ZERO; else if (u == 1) data = ONE; @@ -196,13 +339,13 @@ public: } } } - void opAssign(Tdummy = void)(BigUint y) pure nothrow @nogc @safe + void opAssign(Tdummy = void)(BigUint y) pure nothrow @nogc @safe scope { this.data = y.data; } /// - int opCmp(Tdummy = void)(const BigUint y) pure nothrow @nogc const @safe + int opCmp(Tdummy = void)(const BigUint y) pure nothrow @nogc const @safe scope { if (data.length != y.data.length) return (data.length > y.data.length) ? 1 : -1; @@ -213,7 +356,7 @@ public: } /// - int opCmp(Tulong)(Tulong y) pure nothrow @nogc const @safe if (is (Tulong == ulong)) + int opCmp(Tulong)(Tulong y) pure nothrow @nogc const @safe scope if (is (Tulong == ulong)) { if (data.length > maxBigDigits!Tulong) return 1; @@ -239,12 +382,12 @@ public: return 0; } - bool opEquals(Tdummy = void)(ref const BigUint y) pure nothrow @nogc const @safe + bool opEquals(Tdummy = void)(ref const BigUint y) pure nothrow @nogc const @safe scope { return y.data[] == data[]; } - bool opEquals(Tdummy = void)(ulong y) pure nothrow @nogc const @safe + bool opEquals(Tdummy = void)(ulong y) pure nothrow @nogc const @safe scope { if (data.length > 2) return false; @@ -257,18 +400,18 @@ public: return (data[0] == ylo); } - bool isZero() pure const nothrow @safe @nogc + bool isZero() pure const nothrow @safe @nogc scope { return data.length == 1 && data[0] == 0; } - size_t numBytes() pure nothrow const @safe @nogc + size_t numBytes() pure nothrow const @safe @nogc scope { return data.length * BigDigit.sizeof; } // the extra bytes are added to the start of the string - char [] toDecimalString(int frontExtraBytes) const pure nothrow + char [] toDecimalString(int frontExtraBytes) const pure nothrow @safe scope { immutable predictlength = 20+20*(data.length/2); // just over 19 char [] buff = new char[frontExtraBytes + predictlength]; @@ -285,7 +428,7 @@ public: */ char [] toHexString(int frontExtraBytes, char separator = 0, int minPadding=0, char padChar = '0', - LetterCase letterCase = LetterCase.upper) const pure nothrow @safe + LetterCase letterCase = LetterCase.upper) const pure nothrow @safe scope { // Calculate number of extra padding bytes size_t extraPad = (minPadding > data.length * 2 * BigDigit.sizeof) @@ -349,7 +492,7 @@ public: /** * Convert to an octal string. */ - char[] toOctalString() const + char[] toOctalString() pure nothrow @safe const scope { auto predictLength = 1 + data.length*BigDigitBits / 3; char[] buff = new char[predictLength]; @@ -358,7 +501,7 @@ public: } // return false if invalid character found - bool fromHexString(Range)(Range s) if ( + bool fromHexString(Range)(Range s) scope if ( isBidirectionalRange!Range && isSomeChar!(ElementType!Range)) { import std.range : walkLength; @@ -427,7 +570,7 @@ public: } // return true if OK; false if erroneous characters found - bool fromDecimalString(Range)(Range s) if ( + bool fromDecimalString(Range)(Range s) scope if ( isForwardRange!Range && isSomeChar!(ElementType!Range)) { import std.range : walkLength; @@ -452,14 +595,125 @@ public: return true; } + void fromMagnitude(Range)(Range magnitude) scope + if (isInputRange!Range + && (isForwardRange!Range || hasLength!Range) + && isUnsigned!(ElementType!Range)) + { + while (!magnitude.empty && magnitude.front == 0) + magnitude.popFront; + static if (hasLength!Range) + immutable inputLen = magnitude.length; + else + immutable inputLen = magnitude.save.walkLength; + if (!inputLen) + { + this.data = ZERO; + return; + } + // `magnitude` has its most significant element first but BigUint.data + // stores the most significant last. + BigDigit[] newDigits; + alias E = ElementType!Range; + static if (E.sizeof == BigDigit.sizeof) + { + newDigits = new BigDigit[inputLen]; + foreach_reverse (ref digit; newDigits) + { + digit = magnitude.front; + magnitude.popFront(); + } + } + else static if (E.sizeof < BigDigit.sizeof) + { + enum elementsPerDigit = BigDigit.sizeof / E.sizeof; + newDigits = new BigDigit[(inputLen + elementsPerDigit - 1) / elementsPerDigit]; + immutable remainder = inputLen % elementsPerDigit; + // If there is a remainder specially assemble the most significant digit. + if (remainder) + { + BigDigit tmp = magnitude.front; + magnitude.popFront(); + foreach (_; 1 .. remainder) + { + tmp = (tmp << (E.sizeof * 8)) | magnitude.front; + magnitude.popFront(); + } + newDigits[$-1] = tmp; + } + // Assemble full digits from most to least significant. + foreach_reverse (ref digit; newDigits[0 .. $ - int(remainder != 0)]) + { + BigDigit tmp; + static foreach (n; 0 .. elementsPerDigit) + { + tmp |= cast(BigDigit) magnitude.front << + ((BigDigit.sizeof - (E.sizeof * (n + 1))) * 8); + magnitude.popFront(); + } + digit = tmp; + } + } + else static if (E.sizeof > BigDigit.sizeof) + { + enum digitsPerElement = E.sizeof / BigDigit.sizeof; + newDigits = new BigDigit[inputLen * digitsPerElement]; + size_t i = newDigits.length - 1; + foreach (element; magnitude) + { + static foreach (n; 0 .. digitsPerElement) + newDigits[i - n] = + cast(BigDigit) (element >> ((E.sizeof - (BigDigit.sizeof * (n + 1))) * 8)); + i -= digitsPerElement; + } + while (newDigits[$-1] == 0) + newDigits = newDigits[0 .. $-1]; + } + else + static assert(0); + this.data = trustedAssumeUnique(newDigits); + return; + } + + nothrow pure @safe unittest + { + immutable BigDigit[] referenceData = [BigDigit(0x2003_4005), 0x6007_8009, 0xABCD]; + // Internal representation is most-significant-last but `fromMagnitude` + // argument is most-significant-first. + immutable BigDigit[] referenceMagnitude = [BigDigit(0xABCD), 0x6007_8009, 0x2003_4005]; + BigUint b; + // Test with reference magnitude. + b.fromMagnitude(referenceMagnitude); + assert(b.data == referenceData); + // Test ubyte array. + import std.bitmanip : nativeToBigEndian; + ubyte[] ubyteMagnitude = nativeToBigEndian(referenceMagnitude[0]) ~ + nativeToBigEndian(referenceMagnitude[1]) ~ + nativeToBigEndian(referenceMagnitude[2]); + b.data = ZERO; + b.fromMagnitude(ubyteMagnitude); + assert(b.data == referenceData); + // Test ulong array. + static if (BigDigit.sizeof == uint.sizeof) + immutable(ulong)[] ulongMagnitude = [ulong(referenceMagnitude[0]), + ((cast(ulong) referenceMagnitude[1]) << 32) | referenceMagnitude[2], + ]; + else static if (BigDigit.sizeof == ulong.sizeof) + alias ulongMagnitude = referenceMagnitude; + b.data = ZERO; + b.fromMagnitude(ulongMagnitude); + assert(b.data == referenceData); + } + //////////////////////// // // All of these member functions create a new BigUint. // return x >> y - BigUint opShr(Tulong)(Tulong y) pure nothrow const if (is (Tulong == ulong)) + BigUint opBinary(string op, Tulong)(Tulong y) pure nothrow @safe const return scope + if (op == ">>" && is (Tulong == ulong)) { - assert(y>0); + assert(y > 0, "Can not right shift BigUint by 0"); uint bits = cast(uint) y & BIGDIGITSHIFTMASK; if ((y >> LG2BIGDIGITBITS) >= data.length) return BigUint(ZERO); uint words = cast(uint)(y >> LG2BIGDIGITBITS); @@ -480,12 +734,14 @@ public: } // return x << y - BigUint opShl(Tulong)(Tulong y) pure nothrow const if (is (Tulong == ulong)) + BigUint opBinary(string op, Tulong)(Tulong y) pure nothrow @safe const scope + if (op == "<<" && is (Tulong == ulong)) { - assert(y>0); + assert(y > 0, "Can not left shift BigUint by 0"); if (isZero()) return this; uint bits = cast(uint) y & BIGDIGITSHIFTMASK; - assert((y >> LG2BIGDIGITBITS) < cast(ulong)(uint.max)); + assert((y >> LG2BIGDIGITBITS) < cast(ulong)(uint.max), + "Shift result exceeds temporary store"); uint words = cast(uint)(y >> LG2BIGDIGITBITS); BigDigit [] result = new BigDigit[data.length + words+1]; result[0 .. words] = 0; @@ -505,15 +761,16 @@ public: // 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)) + static BigUint addOrSubInt(Tulong)(const scope BigUint x, Tulong y, + bool wantSub, ref bool sign) pure nothrow @safe if (is(Tulong == ulong)) { BigUint r; if (wantSub) { // perform a subtraction if (x.data.length > 2) { - r.data = subInt(x.data, y); + // subInt returns GC allocated array, can be safely cast to immutable + r.data = (() @trusted => cast(immutable) subInt(x.data, y))(); } else { // could change sign! @@ -548,21 +805,23 @@ public: } else { - r.data = addInt(x.data, y); + // addInt returns GC allocated array, can be safely cast to immutable + r.data = (() @trusted => cast(immutable) 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 cast(immutable) sub(x.data, y.data, &negative))(); *sign ^= negative; if (r.isZero()) { @@ -571,19 +830,22 @@ public: } else { - r.data = add(x.data, y.data); + // add returns GC allocated array, can be safely cast to immutable + r.data = (() @trusted => cast(immutable) add(x.data, y.data))(); } return r; } // return x*y. - // y must not be zero. - static BigUint mulInt(T = ulong)(BigUint x, T y) pure nothrow + static BigUint mulInt(T = ulong)(BigUint x, T y) pure nothrow @safe { if (y == 0 || x == 0) return BigUint(ZERO); - uint hi = cast(uint)(y >>> 32); - uint lo = cast(uint)(y & 0xFFFF_FFFF); + static if (T.sizeof * 8 <= 32) + uint hi = 0; + else + uint hi = cast(uint) (y >>> 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) @@ -596,7 +858,7 @@ public: /* return x * y. */ - static BigUint mul(BigUint x, BigUint y) pure nothrow + static BigUint mul(scope BigUint x, scope BigUint y) pure nothrow @safe { if (y == 0 || x == 0) return BigUint(ZERO); @@ -617,8 +879,8 @@ public: } // return x / y - static BigUint divInt(T)(BigUint x, T y_) pure nothrow - if ( is(Unqual!T == uint) ) + static BigUint divInt(T)(scope return BigUint x, T y_) pure nothrow @safe + if ( is(immutable T == immutable uint) ) { uint y = y_; if (y == 1) @@ -643,8 +905,8 @@ public: return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); } - static BigUint divInt(T)(BigUint x, T y) pure nothrow - if ( is(Unqual!T == ulong) ) + static BigUint divInt(T)(scope BigUint x, T y) pure nothrow @safe + if ( is(immutable T == immutable ulong) ) { if (y <= uint.max) return divInt!uint(x, cast(uint) y); @@ -659,11 +921,11 @@ public: } // return x % y - static uint modInt(T)(BigUint x, T y_) pure if ( is(Unqual!T == uint) ) + static uint modInt(T)(scope BigUint x, T y_) pure if ( is(immutable T == immutable uint) ) { import core.memory : GC; uint y = y_; - assert(y != 0); + assert(y != 0, "% 0 not allowed"); if ((y&(-y)) == y) { // perfect power of 2 return x.data[0] & (y-1); @@ -680,7 +942,7 @@ public: } // return x / y - static BigUint div(BigUint x, BigUint y) pure nothrow + static BigUint div(scope return BigUint x, scope BigUint y) pure nothrow @safe { if (y.data.length > x.data.length) return BigUint(ZERO); @@ -692,7 +954,7 @@ public: } // return x % y - static BigUint mod(BigUint x, BigUint y) pure nothrow + static BigUint mod(scope return BigUint x, scope BigUint y) pure nothrow @safe { if (y.data.length > x.data.length) return x; if (y.data.length == 1) @@ -705,8 +967,33 @@ public: return BigUint(removeLeadingZeros(trustedAssumeUnique(rem))); } + // Return x / y in quotient, x % y in remainder + static void divMod(BigUint x, scope BigUint y, + out BigUint quotient, out BigUint remainder) pure nothrow @safe + { + /* TODO Qualify parameter `x` as `return` when it applies to `out` parameters */ + if (y.data.length > x.data.length) + { + quotient = 0uL; + remainder = x; + } + else if (y.data.length == 1) + { + quotient = divInt(x, y.data[0]); + remainder = BigUint([modInt(x, y.data[0])]); + } + else + { + BigDigit[] quot = new BigDigit[x.data.length - y.data.length + 1]; + BigDigit[] rem = new BigDigit[y.data.length]; + divModInternal(quot, rem, x.data, y.data); + quotient = BigUint(removeLeadingZeros(trustedAssumeUnique(quot))); + remainder = BigUint(removeLeadingZeros(trustedAssumeUnique(rem))); + } + } + // return x op y - static BigUint bitwiseOp(string op)(BigUint x, BigUint y, bool xSign, bool ySign, ref bool resultSign) + static BigUint bitwiseOp(string op)(scope BigUint x, scope BigUint y, bool xSign, bool ySign, ref bool resultSign) pure nothrow @safe if (op == "|" || op == "^" || op == "&") { auto d1 = includeSign(x.data, y.uintLength, xSign); @@ -733,7 +1020,7 @@ public: * exponentiation is used. * Memory allocation is minimized: at most one temporary BigUint is used. */ - static BigUint pow(BigUint x, ulong y) pure nothrow + static BigUint pow(scope return BigUint x, ulong y) pure nothrow @safe { // Deal with the degenerate cases first. if (y == 0) return BigUint(ONE); @@ -752,7 +1039,7 @@ public: // 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); + assert(x0 != 0, "pow(0, y) not allowed"); // Length of the non-zero portion size_t nonzerolength = x.data.length - firstnonzero; ulong y0; @@ -941,9 +1228,9 @@ public: } // Implement toHash so that BigUint works properly as an AA key. - size_t toHash() const @trusted nothrow + size_t toHash() const @nogc nothrow pure @safe scope { - return typeid(data).getHash(&data); + return .hashOf(data); } } // end BigUint @@ -953,24 +1240,33 @@ public: // ulong comparison test BigUint a = [1]; assert(a == 1); - assert(a < 0x8000_0000_0000_0000UL); // bug 9548 + // https://issues.dlang.org/show_bug.cgi?id=9548 + assert(a < 0x8000_0000_0000_0000UL); - // bug 12234 + // https://issues.dlang.org/show_bug.cgi?id=12234 BigUint z = [0]; assert(z == 0UL); assert(!(z > 0UL)); assert(!(z < 0UL)); } +// https://issues.dlang.org/show_bug.cgi?id=16223 +@system pure nothrow unittest +{ + BigUint a = [3]; + int b = 5; + assert(BigUint.mulInt(a,b) == 15); +} + // Remove leading zeros from x, to restore the BigUint invariant -inout(BigDigit) [] removeLeadingZeros(inout(BigDigit) [] x) pure nothrow @safe +inout(BigDigit) [] removeLeadingZeros(scope return 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 +pure @safe unittest { BigUint r = BigUint([5]); BigUint t = BigUint([7]); @@ -992,7 +1288,7 @@ pure @system unittest // Pow tests -pure @system unittest +pure @safe unittest { BigUint r, s; r.fromHexString("80000000_00000001"); @@ -1084,7 +1380,7 @@ pure nothrow @safe } // Encode BigInt as BigDigit array (sign and 2's complement) -BigDigit[] includeSign(const(BigDigit) [] x, size_t minSize, bool sign) +BigDigit[] includeSign(scope const(BigDigit) [] x, size_t minSize, bool sign) pure nothrow @safe { size_t length = (x.length > minSize) ? x.length : minSize; @@ -1139,7 +1435,7 @@ T intpow(T)(T x, ulong n) pure nothrow @safe // returns the maximum power of x that will fit in a uint. int highestPowerBelowUintMax(uint x) pure nothrow @safe { - assert(x>1); + assert(x > 1, "x must be greater than 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]; @@ -1154,7 +1450,7 @@ int highestPowerBelowUintMax(uint x) pure nothrow @safe // returns the maximum power of x that will fit in a ulong. int highestPowerBelowUlongMax(uint x) pure nothrow @safe { - assert(x>1); + assert(x > 1, "x must be greater than 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, @@ -1172,10 +1468,10 @@ int highestPowerBelowUlongMax(uint x) pure nothrow @safe return 2; } -version (unittest) +version (StdUnittest) { -int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe +private int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe { int pwr = 1; for (ulong q = x;x*q < cast(ulong) uint.max; ) @@ -1199,9 +1495,11 @@ int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe /* General unsigned subtraction routine for bigints. * Sets result = x - y. If the result is negative, negative will be true. + * Returns: + * unique memory */ -BigDigit [] sub(const BigDigit [] x, const BigDigit [] y, bool *negative) -pure nothrow +BigDigit [] sub(const scope BigDigit [] x, const scope BigDigit [] y, bool *negative) +pure nothrow @safe { if (x.length == y.length) { @@ -1255,8 +1553,12 @@ pure nothrow } -// return a + b -BigDigit [] add(const BigDigit [] a, const BigDigit [] b) pure nothrow +/* + * return a + b + * Returns: + * unique memory + */ +BigDigit [] add(const scope BigDigit [] a, const scope BigDigit [] b) pure nothrow @safe { const(BigDigit) [] x, y; if (a.length < b.length) @@ -1288,7 +1590,7 @@ BigDigit [] add(const BigDigit [] a, const BigDigit [] b) pure nothrow /** return x + y */ -BigDigit [] addInt(const BigDigit[] x, ulong y) pure nothrow +BigDigit [] addInt(const BigDigit[] x, ulong y) @safe pure nothrow { uint hi = cast(uint)(y >>> 32); uint lo = cast(uint)(y& 0xFFFF_FFFF); @@ -1315,7 +1617,7 @@ BigDigit [] addInt(const BigDigit[] x, ulong y) pure nothrow /** Return x - y. * x must be greater than y. */ -BigDigit [] subInt(const BigDigit[] x, ulong y) pure nothrow +BigDigit [] subInt(const BigDigit[] x, ulong y) pure nothrow @safe { uint hi = cast(uint)(y >>> 32); uint lo = cast(uint)(y & 0xFFFF_FFFF); @@ -1340,12 +1642,13 @@ BigDigit [] subInt(const BigDigit[] x, ulong y) pure nothrow * */ void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y) - pure nothrow + pure nothrow @safe { import core.memory : GC; - assert( result.length == x.length + y.length ); - assert( y.length > 0 ); - assert( x.length >= y.length); + assert( result.length == x.length + y.length, + "result array must have enough space to store computed result"); + assert( y.length > 0, "y must not be empty"); + assert( x.length >= y.length, "x must be greater or equal than y"); if (y.length <= KARATSUBALIMIT) { // Small multiplier, we'll just use the asm classic multiply. @@ -1355,6 +1658,7 @@ void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y) return; } + immutable CACHELIMIT = getCacheLimit; if (x.length + y.length < CACHELIMIT) return mulSimple(result, x, y); @@ -1402,59 +1706,81 @@ void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y) 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 ); + bool isExtraSmall = extra * extra * 2 < y.length * y.length; + if (numchunks == 1 && isExtraSmall) + { + // We divide (x_first_half * y) and (x_last_half * y) + // between 1.414:1 and 1.707:1 (1.707 = 1+1/sqrt(2)). + // (1.414 ~ 1.707)/2:1 is balanced. + BigDigit [] scratchbuff = new BigDigit[karatsubaRequiredBuffSize(y.length) + y.length]; + BigDigit [] partial = scratchbuff[$ - y.length .. $]; + scratchbuff = scratchbuff[0 .. $ - y.length]; + mulKaratsuba(result[0 .. half + y.length], y, x[0 .. half], scratchbuff); + partial[] = result[half .. half + y.length]; + mulKaratsuba(result[half .. $], y, x[half .. $], scratchbuff); + BigDigit c = addAssignSimple(result[half .. half + y.length], partial); + if (c) multibyteIncrementAssign!('+')(result[half + y.length..$], c); + () @trusted { GC.free(scratchbuff.ptr); } (); } 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; + if (isExtraSmall) + { + // 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, + "Unexpected size"); + } + 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, + "Unexpected size"); + } + // 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 .. $]; + scratchbuff = scratchbuff[0 .. $ - 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); } (); } - () @trusted { GC.free(scratchbuff.ptr); } (); } else { @@ -1465,17 +1791,43 @@ void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y) } } +// https://issues.dlang.org/show_bug.cgi?id=20493 +@safe unittest +{ + // the bug report has a testcase with very large numbers (~10^3800 and ~10^2300) + // the number itself isn't important, only the amount of digits, so we do a simpler + // multiplication of the same size, analogous to: + // 11111111 * 11111111 = 0123456787654321 + // but instead of base 10, it's in base `BigDigit` + + BigDigit[398] x = 1; + BigDigit[236] y = 1; + BigDigit[x.length + y.length] result; + mulInternal(result[], x[], y[]); + + // create an array of the form [1, 2, ..., y.length, ..., y.length, y.length-1, ..., 1, 0] + BigDigit[x.length + y.length] expected = y.length; + foreach (BigDigit i; 0 .. y.length) + { + expected[i] = i+1; + expected[$-1-i] = i; + } + + assert(result == expected); +} + /** 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 +void squareInternal(BigDigit[] result, const BigDigit[] x) pure nothrow @safe { import core.memory : GC; // Squaring is potentially half a multiply, plus add the squares of // the diagonal elements. - assert(result.length == 2*x.length); + assert(result.length == 2*x.length, + "result needs to have twice the capacity of x"); if (x.length <= KARATSUBASQUARELIMIT) { if (x.length == 1) @@ -1496,13 +1848,15 @@ 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 + const BigDigit [] v) pure nothrow @safe { 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); + assert(quotient.length == u.length - v.length + 1, + "Invalid quotient length"); + assert(remainder == null || remainder.length == v.length, + "Invalid remainder"); + assert(v.length > 1, "v must have more than 1 element"); + assert(u.length >= v.length, "u must be as longer or longer than v"); // Normalize by shifting v left just enough so that // its high-order bit is on, and shift u left the @@ -1541,7 +1895,7 @@ void divModInternal(BigDigit [] quotient, BigDigit[] remainder, const BigDigit [ () @trusted { GC.free(un.ptr); GC.free(vn.ptr); } (); } -pure @system unittest +pure @safe unittest { immutable(uint) [] u = [0, 0xFFFF_FFFE, 0x8000_0000]; immutable(uint) [] v = [0xFFFF_FFFF, 0x8000_0000]; @@ -1563,7 +1917,7 @@ private: // every 8 digits. // buff.length must be data.length*8 if separator is zero, // or data.length*9 if separator is non-zero. It will be completely filled. -char [] biguintToHex(char [] buff, const BigDigit [] data, char separator=0, +char [] biguintToHex(scope return char [] buff, const scope BigDigit [] data, char separator=0, LetterCase letterCase = LetterCase.upper) pure nothrow @safe { int x=0; @@ -1613,10 +1967,10 @@ size_t biguintToOctal(char[] buff, const(BigDigit)[] data) if (shift < 0) { // Some bits were carried over from previous word. - assert(shift > -3); + assert(shift > -3, "shift must be greater than -3"); output(((bigdigit << -shift) | carry) & 0b111); shift += 3; - assert(shift > 0); + assert(shift > 0, "shift must be 1 or greater"); } while (shift <= BigDigitBits - 3) @@ -1631,13 +1985,13 @@ size_t biguintToOctal(char[] buff, const(BigDigit)[] data) carry = (bigdigit >>> shift) & 0b11; } shift -= BigDigitBits; - assert(shift >= -2 && shift <= 0); + assert(shift >= -2 && shift <= 0, "shift must in [-2,0]"); } if (shift < 0) { // Last word had bits that haven't been output yet. - assert(shift > -3); + assert(shift > -3, "Shift must be greater than -3"); output(carry); } @@ -1656,7 +2010,7 @@ size_t biguintToOctal(char[] buff, const(BigDigit)[] data) * Returns: * the lowest index of buff which was used. */ -size_t biguintToDecimal(char [] buff, BigDigit [] data) pure nothrow +size_t biguintToDecimal(char [] buff, BigDigit [] data) pure nothrow @safe { ptrdiff_t sofar = buff.length; // Might be better to divide by (10^38/2^32) since that gives 38 digits for @@ -1701,9 +2055,10 @@ if ( in { static if (hasLength!Range) - assert((data.length >= 2) || (data.length == 1 && s.length == 1)); + assert((data.length >= 2) || (data.length == 1 && s.length == 1), + "data has a invalid length"); } -body +do { import std.conv : ConvException; @@ -1837,26 +2192,27 @@ private: // Classic 'schoolbook' multiplication. void mulSimple(BigDigit[] result, const(BigDigit) [] left, - const(BigDigit)[] right) pure nothrow + const(BigDigit)[] right) pure nothrow @safe in { - assert(result.length == left.length + right.length); - assert(right.length>1); + assert(result.length == left.length + right.length, + "Result must be able to store left + right"); + assert(right.length>1, "right must not be empty"); } -body +do { 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 +void squareSimple(BigDigit[] result, const(BigDigit) [] x) pure nothrow @safe in { - assert(result.length == 2*x.length); - assert(x.length>1); + assert(result.length == 2*x.length, "result must be twice as long as x"); + assert(x.length>1, "x must not be empty"); } -body +do { multibyteSquare(result, x); } @@ -1866,14 +2222,16 @@ body // as the larger length. // Returns carry (0 or 1). uint addSimple(BigDigit[] result, const BigDigit [] left, const BigDigit [] right) -pure nothrow +pure nothrow @safe in { - assert(result.length == left.length); - assert(left.length >= right.length); - assert(right.length>0); + assert(result.length == left.length, + "result and left must be of the same length"); + assert(left.length >= right.length, + "left must be longer or of equal length to right"); + assert(right.length > 0, "right must not be empty"); } -body +do { uint carry = multibyteAdd(result[0 .. right.length], left[0 .. right.length], right, 0); @@ -1891,11 +2249,13 @@ 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); + assert(result.length == left.length, + "result and left must be of the same length"); + assert(left.length >= right.length, + "left must be longer or of equal length to right"); + assert(right.length > 0, "right must not be empty"); } -body +do { BigDigit carry = multibyteSub(result[0 .. right.length], left[0 .. right.length], right, 0); @@ -1912,9 +2272,10 @@ body * Returns carry = 1 if result was less than right. */ BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right) -pure nothrow +pure nothrow @safe { - assert(result.length >= right.length); + assert(result.length >= right.length, + "result must be longer or of equal length to right"); 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); @@ -1924,9 +2285,10 @@ pure nothrow /* result = result + right */ BigDigit addAssignSimple(BigDigit [] result, const(BigDigit) [] right) -pure nothrow +pure nothrow @safe { - assert(result.length >= right.length); + assert(result.length >= right.length, + "result must be longer or of equal length to right"); 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); @@ -1936,7 +2298,7 @@ pure nothrow /* performs result += wantSub? - right : right; */ BigDigit addOrSubAssignSimple(BigDigit [] result, const(BigDigit) [] right, - bool wantSub) pure nothrow + bool wantSub) pure nothrow @safe { if (wantSub) return subAssignSimple(result, right); @@ -1946,9 +2308,10 @@ BigDigit addOrSubAssignSimple(BigDigit [] result, const(BigDigit) [] right, // return true if x= y.length); + assert(x.length >= y.length, + "x must be longer or of equal length to y"); auto k = x.length-1; while (x[k]==0 && k >= y.length) --k; @@ -1961,9 +2324,10 @@ bool less(const(BigDigit)[] x, const(BigDigit)[] y) pure nothrow // Set result = abs(x-y), return true if result is negative(x= y.length) ? x.length : y.length); + assert(result.length == ((x.length >= y.length) ? x.length : y.length), + "result must capable to store the maximum of x and y"); size_t minlen; bool negative; @@ -2000,10 +2364,12 @@ bool inplaceSub(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y) /* Determine how much space is required for the temporaries * when performing a Karatsuba multiplication. + * TODO: determining a tight bound is non-trivial and depends on KARATSUBALIMIT, see: + * https://issues.dlang.org/show_bug.cgi?id=20493 */ size_t karatsubaRequiredBuffSize(size_t xlen) pure nothrow @safe { - return xlen <= KARATSUBALIMIT ? 0 : 2*xlen; // - KARATSUBALIMIT+2; + return xlen <= KARATSUBALIMIT ? 0 : (xlen * 9) / 4; } /* Sets result = x*y, using Karatsuba multiplication. @@ -2018,11 +2384,12 @@ size_t karatsubaRequiredBuffSize(size_t xlen) pure nothrow @safe * 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 + const(BigDigit)[] y, BigDigit [] scratchbuff) pure nothrow @safe { - assert(x.length >= y.length); - assert(result.length < uint.max, "Operands too large"); - assert(result.length == x.length + y.length); + assert(x.length >= y.length, "x must be greater or equal to y"); + assert(result.length < uint.max, "Operands too large"); + assert(result.length == x.length + y.length, + "result must be as large as x + y"); if (x.length <= KARATSUBALIMIT) { return mulSimple(result, x, y); @@ -2123,12 +2490,13 @@ void mulKaratsuba(BigDigit [] result, const(BigDigit) [] x, } void squareKaratsuba(BigDigit [] result, const BigDigit [] x, - BigDigit [] scratchbuff) pure nothrow + BigDigit [] scratchbuff) pure nothrow @safe { // 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); + assert(result.length == 2*x.length, + "result must be twice the length of x"); if (x.length <= KARATSUBASQUARELIMIT) { return squareSimple(result, x); @@ -2180,13 +2548,14 @@ void squareKaratsuba(BigDigit [] result, const BigDigit [] x, * u[0 .. v.length] holds the remainder. */ void schoolbookDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) - pure nothrow + pure nothrow @safe { - 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]); + assert(quotient.length == u.length - v.length, + "quotient has wrong length"); + assert(v.length > 1, "v must not be empty"); + assert(u.length >= v.length, "u must be larger or equal to v"); + assert((v[$ - 1] & 0x8000_0000) != 0, "Invalid value at v[$ - 1]"); + assert(u[$ - 1] < v[$ - 1], "u[$ - 1] must be less than v[$ - 1]"); // BUG: This code only works if BigDigit is uint. uint vhi = v[$-1]; uint vlo = v[$-2]; @@ -2208,7 +2577,7 @@ void schoolbookDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) { // Note: On DMD, this is only ~10% faster than the non-asm code. uint *p = &u[j + v.length - 1]; - asm pure nothrow + asm pure nothrow @trusted { mov EAX, p; mov EDX, [EAX+4]; @@ -2307,7 +2676,8 @@ private: size_t highestDifferentDigit(const BigDigit [] left, const BigDigit [] right) pure nothrow @nogc @safe { - assert(left.length == right.length); + assert(left.length == right.length, + "left have a length equal to that of right"); for (ptrdiff_t i = left.length - 1; i>0; --i) { if (left[i] != right[i]) @@ -2323,7 +2693,7 @@ int firstNonZeroDigit(const BigDigit [] x) pure nothrow @nogc @safe while (x[k]==0) { ++k; - assert(k 1); - assert((v[$ - 1] & 0x8000_0000) != 0); - assert(!(u[$ - 1] & 0x8000_0000)); - assert(quotient.length == u.length - v.length); + assert(v.length > 1, "v must not be empty"); + assert((v[$ - 1] & 0x8000_0000) != 0, "Invalid value at v[$ - 1]"); + assert(!(u[$ - 1] & 0x8000_0000), "Invalid value at u[$ - 1]"); + assert(quotient.length == u.length - v.length, + "quotient must be of equal length of u - v"); if (mayOverflow) { - assert(u[$-1] == 0); - assert(u[$-2] & 0x8000_0000); + assert(u[$-1] == 0, "Invalid value at u[$ - 1]"); + assert(u[$-2] & 0x8000_0000, "Invalid value at u[$ - 2]"); } // 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)); + assert((mayOverflow ? u.length-1 : u.length) <= 2 * v.length, + "Invalid length of u"); + assert((mayOverflow ? u.length-1 : u.length) >= v.length, + "Invalid length of u"); + assert(scratch.length >= quotient.length + (mayOverflow ? 0 : 1), + "Invalid quotient length"); } -body +do { if (quotient.length < FASTDIVLIMIT) { @@ -2444,9 +2818,9 @@ body // 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 + BigDigit[] scratch, bool mayOverflow = false) pure nothrow @safe { - assert(rem.length == v.length); + assert(rem.length == v.length, "rem must be as long as v"); mulInternal(scratch, quot, v[0 .. k]); uint carry = 0; if (mayOverflow) @@ -2462,14 +2836,15 @@ void adjustRemainder(BigDigit[] quot, BigDigit[] rem, const(BigDigit)[] v, // Cope with unbalanced division by performing block schoolbook division. void blockDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) -pure nothrow +pure nothrow @safe { 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); + assert(quotient.length == u.length - v.length, + "quotient must be of equal length of u - v"); + assert(v.length > 1, "v must not be empty"); + assert(u.length >= v.length, "u must be longer or of equal length as v"); + assert((v[$-1] & 0x8000_0000)!=0, "Invalid value at v[$ - 1]"); + assert((u[$-1] & 0x8000_0000)==0, "Invalid value at u[$ - 1]"); BigDigit [] scratch = new BigDigit[v.length + 1]; // Perform block schoolbook division, with 'v.length' blocks. @@ -2487,7 +2862,7 @@ pure nothrow u[m - v.length .. m + v.length + (mayOverflow? 1: 0)], v, scratch, mayOverflow); if (mayOverflow) { - assert(quotient[m] == 0); + assert(quotient[m] == 0, "quotient must not be 0"); quotient[m] = saveq; } m -= v.length; @@ -2498,18 +2873,21 @@ pure nothrow @system unittest { - import core.stdc.stdio; - - void printBiguint(const uint [] data) + version (none) { - char [] buff = biguintToHex(new char[data.length*9], data, '_'); - printf("%.*s\n", cast(int) buff.length, buff.ptr); - } + import core.stdc.stdio; - void printDecimalBigUint(BigUint data) - { - auto str = data.toDecimalString(0); - printf("%.*s\n", cast(int) str.length, str.ptr); + void printBiguint(const uint [] data) + { + char [] buff = biguintToHex(new char[data.length*9], data, '_'); + printf("%.*s\n", cast(int) buff.length, buff.ptr); + } + + void printDecimalBigUint(BigUint data) + { + auto str = data.toDecimalString(0); + printf("%.*s\n", cast(int) str.length, str.ptr); + } } uint [] a, b; diff --git a/libphobos/src/std/internal/math/biguintnoasm.d b/libphobos/src/std/internal/math/biguintnoasm.d index aea1d50..2b2b5f1 100644 --- a/libphobos/src/std/internal/math/biguintnoasm.d +++ b/libphobos/src/std/internal/math/biguintnoasm.d @@ -61,10 +61,10 @@ uint multibyteAddSub(char op)(uint[] dest, const(uint) [] src1, } 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); + assert(c[0]==0x8000_0003, "c[0] has invalid value"); + assert(c[1]==4, "c[1] must be for"); + assert(c[19]==0x3333_3333, "c[19] has invalid value"); // check for overrun + assert(carry == 1, "carry must be 1"); for (size_t i = 0; i < a.length; ++i) { a[i] = b[i] = c[i] = 0; @@ -75,10 +75,10 @@ uint multibyteAddSub(char op)(uint[] dest, const(uint) [] src1, b[10]=0x1D950C84; a[5] =0x44444444; carry = multibyteAddSub!('-')(a[0 .. 12], a[0 .. 12], b[0 .. 12], 0); - assert(a[11] == 0); + assert(a[11] == 0, "a[11] must be 0"); for (size_t i = 0; i < 10; ++i) if (i != 5) - assert(a[i] == 0); + assert(a[i] == 0, "a[1] must be 0"); for (size_t q = 3; q < 36; ++q) { @@ -89,7 +89,7 @@ uint multibyteAddSub(char op)(uint[] dest, const(uint) [] src1, 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); + assert(a[q-2]==0, "a[q-2] must be 0"); } } @@ -193,7 +193,7 @@ void multibyteShr(uint [] dest, const(uint) [] src, uint numbits) uint multibyteMul(uint[] dest, const(uint)[] src, uint multiplier, uint carry) pure @nogc @safe { - assert(dest.length == src.length); + assert(dest.length == src.length, "dest and src must have the same length"); ulong c = carry; for (size_t i = 0; i < src.length; ++i) { @@ -220,7 +220,7 @@ uint multibyteMul(uint[] dest, const(uint)[] src, uint multiplier, uint carry) uint multibyteMulAdd(char op)(uint [] dest, const(uint)[] src, uint multiplier, uint carry) pure @nogc @safe { - assert(dest.length == src.length); + assert(dest.length == src.length, "dest and src must have the same length"); ulong c = carry; for (size_t i = 0; i < src.length; ++i) { diff --git a/libphobos/src/std/internal/math/errorfunction.d b/libphobos/src/std/internal/math/errorfunction.d index 4012e64..8894ffb 100644 --- a/libphobos/src/std/internal/math/errorfunction.d +++ b/libphobos/src/std/internal/math/errorfunction.d @@ -24,6 +24,7 @@ */ module std.internal.math.errorfunction; import std.math; +import core.math : fabs, sqrt; pure: nothrow: @@ -50,16 +51,16 @@ 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[10] P = [ -0x1.30dfa809b3cc6676p-17L, 0x1.38637cd0913c0288p+18L, + 0x1.2f015e047b4476bp+22L, 0x1.24726f46aa9ab08p+25L, 0x1.64b13c6395dc9c26p+27L, + 0x1.294c93046ad55b5p+29L, 0x1.5962a82f92576dap+30L, 0x1.11a709299faba04ap+31L, + 0x1.11028065b087be46p+31L, 0x1.0d8ef40735b097ep+30L ]; -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 +immutable real[11] Q = [ 0x1.14d8e2a72dec49f4p+19L, 0x1.0c880ff467626e1p+23L, + 0x1.04417ef060b58996p+26L, 0x1.404e61ba86df4ebap+28L, 0x1.0f81887bc82b873ap+30L, + 0x1.4552a5e39fb49322p+31L, 0x1.11779a0ceb2a01cep+32L, 0x1.3544dd691b5b1d5cp+32L, + 0x1.a91781f12251f02ep+31L, 0x1.0d8ef3da605a1c86p+30L, 1.0L ]; // For 128 bit quadruple-precision floats, we use a higher-precision implementation @@ -120,7 +121,7 @@ static if (isIEEEQuadruple) 1.367697521219069280358984081407807931847E1L, 2.276988395995528495055594829206582732682E1L, 7.647745753648996559837591812375456641163E-1L, - 1.0 + 1.0L ]; // erfc(0.375) = C14a + C14b to extra precision. @@ -150,7 +151,7 @@ static if (isIEEEQuadruple) 2.628752920321455606558942309396855629459E1L, 2.455649035885114308978333741080991380610E1L, 1.378826653595128464383127836412100939126E0L, - 1.0 + 1.0L ]; // erfc(0.5) = C15a + C15b to extra precision. immutable real C15a = 0.4794921875L; @@ -179,7 +180,7 @@ static if (isIEEEQuadruple) 4.465334221323222943418085830026979293091E1L, 2.612723259683205928103787842214809134746E1L, 2.341526751185244109722204018543276124997E0L, - 1.0 + 1.0L ]; // erfc(0.625) = C16a + C16b to extra precision. immutable real C16a = 0.3767547607421875L; @@ -208,7 +209,7 @@ static if (isIEEEQuadruple) 5.555800830216764702779238020065345401144E1L, 2.646215470959050279430447295801291168941E1L, 2.984905282103517497081766758550112011265E0L, - 1.0 + 1.0L ]; // erfc(0.75) = C17a + C17b to extra precision. immutable real C17a = 0.2888336181640625L; @@ -238,7 +239,7 @@ static if (isIEEEQuadruple) 5.998153487868943708236273854747564557632E1L, 2.657695108438628847733050476209037025318E1L, 3.252140524394421868923289114410336976512E0L, - 1.0 + 1.0L ]; // erfc(0.875) = C18a + C18b to extra precision. @@ -268,7 +269,7 @@ static if (isIEEEQuadruple) 6.868976819510254139741559102693828237440E1L, 2.801505816247677193480190483913753613630E1L, 3.604439909194350263552750347742663954481E0L, - 1.0 + 1.0L ]; // erfc(1.0) = C19a + C19b to extra precision. @@ -298,7 +299,7 @@ static if (isIEEEQuadruple) 8.848641738570783406484348434387611713070E1L, 3.132269062552392974833215844236160958502E1L, 4.430131663290563523933419966185230513168E0L, - 1.0 + 1.0L ]; // erfc(1.125) = C20a + C20b to extra precision. @@ -604,26 +605,26 @@ 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[5] R = [ 0x1.b9f6d8b78e22459ep-6L, 0x1.1b84686b0a4ea43ap-1L, + 0x1.b8f6aebe96000c2ap+1L, 0x1.cb1dbedac27c8ec2p+2L, 0x1.cf885f8f572a4c14p+1L ]; immutable real[6] S = [ - 0x1.87ae3cae5f65eb5ep-5, 0x1.01616f266f306d08p+0, 0x1.a4abe0411eed6c22p+2, - 0x1.eac9ce3da600abaap+3, 0x1.5752a9ac2faebbccp+3, 1.0 + 0x1.87ae3cae5f65eb5ep-5L, 0x1.01616f266f306d08p+0L, 0x1.a4abe0411eed6c22p+2L, + 0x1.eac9ce3da600abaap+3L, 0x1.5752a9ac2faebbccp+3L, 1.0L ]; /* 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] T = [ 0x1.0da01654d757888cp+20L, 0x1.2eb7497bc8b4f4acp+17L, + 0x1.79078c19530f72a8p+15L, 0x1.4eaf2126c0b2c23p+11L, 0x1.1f2ea81c9d272a2ep+8L, + 0x1.59ca6e2d866e625p+2L, 0x1.c188e0b67435faf4p-4L ]; - immutable real[7] U = [ 0x1.dde6025c395ae34ep+19, 0x1.c4bc8b6235df35aap+18, - 0x1.8465900e88b6903ap+16, 0x1.855877093959ffdp+13, 0x1.e5c44395625ee358p+9, - 0x1.6a0fed103f1c68a6p+5, 1.0 + immutable real[7] U = [ 0x1.dde6025c395ae34ep+19L, 0x1.c4bc8b6235df35aap+18L, + 0x1.8465900e88b6903ap+16L, 0x1.855877093959ffdp+13L, 0x1.e5c44395625ee358p+9L, + 0x1.6a0fed103f1c68a6p+5L, 1.0L ]; } } @@ -835,7 +836,7 @@ real erf(real x) return -1.0; if (x == real.infinity) return 1.0; - immutable ax = abs(x); + immutable ax = fabs(x); if (ax > 1.0L) return 1.0L - erfc(x); @@ -867,16 +868,16 @@ real erf(real x) @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 erfc0_250 = 0.723663330078125L + 1.0279753638067014931732235184287934646022E-5L; + enum real erfc0_375 = 0.5958709716796875L + 1.2118885490201676174914080878232469565953E-5L; + enum real erfc0_500 = 0.4794921875L + 7.9346869534623172533461080354712635484242E-6L; + enum real erfc0_625 = 0.3767547607421875L + 4.3570693945275513594941232097252997287766E-6L; + enum real erfc0_750 = 0.2888336181640625L + 1.0748182422368401062165408589222625794046E-5L; + enum real erfc0_875 = 0.215911865234375L + 1.3073705765341685464282101150637224028267E-5L; + enum real erfc1_000 = 0.15728759765625L + 1.1609394035130658779364917390740703933002E-5L; + enum real erfc1_125 = 0.111602783203125L + 8.9850951672359304215530728365232161564636E-6L; - enum real erf0_875 = (1-0.215911865234375) - 1.3073705765341685464282101150637224028267E-5; + enum real erf0_875 = (1-0.215911865234375L) - 1.3073705765341685464282101150637224028267E-5L; static bool isNaNWithPayload(real x, ulong payload) @safe pure nothrow @nogc { @@ -931,7 +932,7 @@ real expx2(real x, int sign) const real M = 32_768.0; const real MINV = 3.0517578125e-5L; - x = abs(x); + x = fabs(x); if (sign < 0) x = -x; @@ -984,7 +985,7 @@ Journal of Statistical Software 11, (July 2004). real normalDistributionImpl(real a) { real x = a * SQRT1_2; - real z = abs(x); + real z = fabs(x); if ( z < 1.0 ) return 0.5L + 0.5L * erf(x); @@ -1002,7 +1003,7 @@ real normalDistributionImpl(real a) @safe unittest { -assert(fabs(normalDistributionImpl(1L) - (0.841344746068543))< 0.0000000000000005); +assert(fabs(normalDistributionImpl(1L) - (0.841344746068543L)) < 0.0000000000000005L); assert(isIdentical(normalDistributionImpl(NaN(0x325)), NaN(0x325))); } @@ -1024,56 +1025,56 @@ real normalDistributionInvImpl(real p) in { assert(p >= 0.0L && p <= 1.0L, "Domain error"); } -body +do { 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 +[ -0x1.758f4d969484bfdcp-7L, 0x1.53cee17a59259dd2p-3L, + -0x1.ea01e4400a9427a2p-1L, 0x1.61f7504a0105341ap+1L, -0x1.09475a594d0399f6p+2L, + 0x1.7c59e7a0df99e3e2p+1L, -0x1.87a81da52edcdf14p-1L, 0x1.1fb149fd3f83600cp-7L ]; 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 +[ -0x1.64b92ae791e64bb2p-7L, 0x1.7585c7d597298286p-3L, + -0x1.40011be4f7591ce6p+0L, 0x1.1fc067d8430a425ep+2L, -0x1.21008ffb1e7ccdf2p+3L, + 0x1.3d1581cf9bc12fccp+3L, -0x1.53723a89fd8f083cp+2L, 1.0L ]; 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 +[ 0x1.20ceea49ea142f12p-13L, 0x1.cbe8a7267aea80bp-7L, + 0x1.79fea765aa787c48p-2L, 0x1.d1f59faa1f4c4864p+1L, 0x1.1c22e426a013bb96p+4L, + 0x1.a8675a0c51ef3202p+5L, 0x1.75782c4f83614164p+6L, 0x1.7a2f3d90948f1666p+6L, + 0x1.5cd116ee4c088c3ap+5L, 0x1.1361e3eb6e3cc20ap+2L ]; 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 +[ 0x1.3a4ce1406cea98fap-13L, 0x1.f45332623335cda2p-7L, + 0x1.98f28bbd4b98db1p-2L, 0x1.ec3b24f9c698091cp+1L, 0x1.1cc56ecda7cf58e4p+4L, + 0x1.92c6f7376bf8c058p+5L, 0x1.4154c25aa47519b4p+6L, 0x1.1b321d3b927849eap+6L, + 0x1.403a5f5a4ce7b202p+4L, 1.0L ]; 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 +[ 0x1.8c124a850116a6d8p-21L, 0x1.534abda3c2fb90bap-13L, + 0x1.29a055ec93a4718cp-7L, 0x1.6468e98aad6dd474p-3L, 0x1.3dab2ef4c67a601cp+0L, + 0x1.e1fb3a1e70c67464p+1L, 0x1.b6cce8035ff57b02p+2L, 0x1.9f4c9e749ff35f62p+1L ]; 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 +[ 0x1.af03f4fc0655e006p-21L, 0x1.713192048d11fb2p-13L, + 0x1.4357e5bbf5fef536p-7L, 0x1.7fdac8749985d43cp-3L, 0x1.4a080c813a2d8e84p+0L, + 0x1.c3a4b423cdb41bdap+1L, 0x1.8160694e24b5557ap+2L, 1.0L ]; 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 +[ -0x1.55da447ae3806168p-34L, -0x1.145635641f8778a6p-24L, + -0x1.abf46d6b48040128p-17L, -0x1.7da550945da790fcp-11L, -0x1.aa0b2a31157775fap-8L, + 0x1.b11d97522eed26bcp-3L, 0x1.1106d22f9ae89238p+1L, 0x1.029a358e1e630f64p+1L ]; 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 +[ -0x1.74022dd5523e6f84p-34L, -0x1.2cb60d61e29ee836p-24L, + -0x1.d19e6ec03a85e556p-17L, -0x1.9ea2a7b4422f6502p-11L, -0x1.c54b1e852f107162p-8L, + 0x1.e05268dd3c07989ep-3L, 0x1.239c6aff14afbf82p+1L, 1.0L ]; if (p <= 0.0L || p >= 1.0L) @@ -1130,9 +1131,9 @@ static immutable real[8] Q3 = { // 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); + assert(fabs(normalDistributionInvImpl(0.001) - (-3.09023230616779L)) < 0.00000000000005L); + assert(fabs(normalDistributionInvImpl(1e-50) - (-14.9333375347885L)) < 0.00000000000005L); + assert(feqrel(normalDistributionInvImpl(0.999L), -normalDistributionInvImpl(0.001L)) > real.mant_dig-6); // Excel 2003 gets all the following values wrong! assert(normalDistributionInvImpl(0.0) == -real.infinity); @@ -1141,5 +1142,5 @@ static immutable real[8] Q3 = // (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); + assert( fabs(unknown1 -(-33.79958617269L) ) < 0.00000005L); } diff --git a/libphobos/src/std/internal/math/gammafunction.d b/libphobos/src/std/internal/math/gammafunction.d index c9677c7..7f72234 100644 --- a/libphobos/src/std/internal/math/gammafunction.d +++ b/libphobos/src/std/internal/math/gammafunction.d @@ -21,6 +21,7 @@ Macros: module std.internal.math.gammafunction; import std.internal.math.errorfunction; import std.math; +import core.math : fabs, sin, sqrt; pure: nothrow: @@ -34,46 +35,46 @@ immutable real EULERGAMMA = 0.57721_56649_01532_86060_65120_90082_40243_10421_59 // 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[8] GammaNumeratorCoeffs = [ 1.0L, + 0x1.acf42d903366539ep-1L, 0x1.73a991c8475f1aeap-2L, 0x1.c7e918751d6b2a92p-4L, + 0x1.86d162cca32cfe86p-6L, 0x1.0c378e2e6eaf7cd8p-8L, 0x1.dc5c66b7d05feb54p-12L, + 0x1.616457b47e448694p-15L ]; -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] GammaDenominatorCoeffs = [ 1.0L, + 0x1.a8f9faae5d8fc8bp-2L, -0x1.cb7895a6756eebdep-3L, -0x1.7b9bab006d30652ap-5L, + 0x1.c671af78f312082ep-6L, -0x1.a11ebbfaf96252dcp-11L, -0x1.447b4d2230a77ddap-10L, + 0x1.ec1d45bb85e06696p-13L,-0x1.d4ce24d05bd0a8e6p-17L ]; -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] GammaSmallCoeffs = [ 1.0L, + 0x1.2788cfc6fb618f52p-1L, -0x1.4fcf4026afa2f7ecp-1L, -0x1.5815e8fa24d7e306p-5L, + 0x1.5512320aea2ad71ap-3L, -0x1.59af0fb9d82e216p-5L, -0x1.3b4b61d3bfdf244ap-7L, + 0x1.d9358e9d9d69fd34p-8L, -0x1.38fc4bcbada775d6p-10L ]; -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[9] GammaSmallNegCoeffs = [ -1.0L, + 0x1.2788cfc6fb618f54p-1L, 0x1.4fcf4026afa2bc4cp-1L, -0x1.5815e8fa2468fec8p-5L, + -0x1.5512320baedaf4b6p-3L, -0x1.59af0fa283baf07ep-5L, 0x1.3b4a70de31e05942p-7L, + 0x1.d9398be3bad13136p-8L, 0x1.291b73ee05bcbba2p-10L ]; 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 + 0x1.5555555555553f98p-4L, -0x1.6c16c16c07509b1p-9L, 0x1.a01a012461cbf1e4p-11L, + -0x1.3813089d3f9d164p-11L, 0x1.b911a92555a277b8p-11L, -0x1.ed0a7b4206087b22p-10L, + 0x1.402523859811b308p-8L ]; 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 + -0x1.0edd25913aaa40a2p+23L, -0x1.31c6ce2e58842d1ep+24L, -0x1.f015814039477c3p+23L, + -0x1.74ffe40c4b184b34p+22L, -0x1.0d9c6d08f9eab55p+20L, -0x1.54c6b71935f1fc88p+16L, + -0x1.0e761b42932b2aaep+11L ]; 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 + -0x1.4055572d75d08c56p+24L, -0x1.deeb6013998e4d76p+24L, -0x1.106f7cded5dcc79ep+24L, + -0x1.25e17184848c66d2p+22L, -0x1.301303b99a614a0ap+19L, -0x1.09e76ab41ae965p+15L, + -0x1.00f95ced9e5f54eep+9L, 1.0L ]; /* @@ -89,9 +90,9 @@ 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 + 0x1.55555555555543aap-4L, 0x1.c71c71c720dd8792p-9L, -0x1.5f7268f0b5907438p-9L, + -0x1.e13cd410e0477de6p-13L, 0x1.9b0f31643442616ep-11L, 0x1.2527623a3472ae08p-14L, + -0x1.37f6bc8ef8b374dep-11L,-0x1.8c968886052b872ap-16L, 0x1.76baa9c6d3eeddbcp-11L ]; static immutable real[7] LargeStirlingCoeffs = [ 1.0L, @@ -144,76 +145,76 @@ real gammaStirling(real x) 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 ] + [ -0.333333333333333333333L, 0.0833333333333333333333L, + -0.0148148148148148148148L, 0.00115740740740740740741L, + 0.000352733686067019400353L, -0.0001787551440329218107L, + 0.39192631785224377817e-4L, -0.218544851067999216147e-5L, + -0.18540622107151599607e-5L, 0.829671134095308600502e-6L, + -0.176659527368260793044e-6L, 0.670785354340149858037e-8L, + 0.102618097842403080426e-7L, -0.438203601845335318655e-8L, + 0.914769958223679023418e-9L, -0.255141939949462497669e-10L, + -0.583077213255042506746e-10L, 0.243619480206674162437e-10L, + -0.502766928011417558909e-11L ], + [ -0.00185185185185185185185L, -0.00347222222222222222222L, + 0.00264550264550264550265L, -0.000990226337448559670782L, + 0.000205761316872427983539L, -0.40187757201646090535e-6L, + -0.18098550334489977837e-4L, 0.764916091608111008464e-5L, + -0.161209008945634460038e-5L, 0.464712780280743434226e-8L, + 0.137863344691572095931e-6L, -0.575254560351770496402e-7L, + 0.119516285997781473243e-7L, -0.175432417197476476238e-10L, + -0.100915437106004126275e-8L, 0.416279299184258263623e-9L, + -0.856390702649298063807e-10L ], + [ 0.00413359788359788359788L, -0.00268132716049382716049L, + 0.000771604938271604938272L, 0.200938786008230452675e-5L, + -0.000107366532263651605215L, 0.529234488291201254164e-4L, + -0.127606351886187277134e-4L, 0.342357873409613807419e-7L, + 0.137219573090629332056e-5L, -0.629899213838005502291e-6L, + 0.142806142060642417916e-6L, -0.204770984219908660149e-9L, + -0.140925299108675210533e-7L, 0.622897408492202203356e-8L, + -0.136704883966171134993e-8L ], + [ 0.000649434156378600823045L, 0.000229472093621399176955L, + -0.000469189494395255712128L, 0.000267720632062838852962L, + -0.756180167188397641073e-4L, -0.239650511386729665193e-6L, + 0.110826541153473023615e-4L, -0.56749528269915965675e-5L, + 0.142309007324358839146e-5L, -0.278610802915281422406e-10L, + -0.169584040919302772899e-6L, 0.809946490538808236335e-7L, + -0.191111684859736540607e-7L ], + [ -0.000861888290916711698605L, 0.000784039221720066627474L, + -0.000299072480303190179733L, -0.146384525788434181781e-5L, + 0.664149821546512218666e-4L, -0.396836504717943466443e-4L, + 0.113757269706784190981e-4L, 0.250749722623753280165e-9L, + -0.169541495365583060147e-5L, 0.890750753220530968883e-6L, + -0.229293483400080487057e-6L ], + [ -0.000336798553366358150309L, -0.697281375836585777429e-4L, + 0.000277275324495939207873L, -0.000199325705161888477003L, + 0.679778047793720783882e-4L, 0.141906292064396701483e-6L, + -0.135940481897686932785e-4L, 0.801847025633420153972e-5L, + -0.229148117650809517038e-5L ], + [ 0.000531307936463992223166L, -0.000592166437353693882865L, + 0.000270878209671804482771L, 0.790235323266032787212e-6L, + -0.815396936756196875093e-4L, 0.561168275310624965004e-4L, + -0.183291165828433755673e-4L, -0.307961345060330478256e-8L, + 0.346515536880360908674e-5L, -0.20291327396058603727e-5L, + 0.57887928631490037089e-6L ], + [ 0.000344367606892377671254L, 0.517179090826059219337e-4L, + -0.000334931610811422363117L, 0.000281269515476323702274L, + -0.000109765822446847310235L, -0.127410090954844853795e-6L, + 0.277444515115636441571e-4L, -0.182634888057113326614e-4L, + 0.578769494973505239894e-5L ], + [ -0.000652623918595309418922L, 0.000839498720672087279993L, + -0.000438297098541721005061L, -0.696909145842055197137e-6L, + 0.000166448466420675478374L, -0.000127835176797692185853L, + 0.462995326369130429061e-4L ], + [ -0.000596761290192746250124L, -0.720489541602001055909e-4L, + 0.000678230883766732836162L, -0.0006401475260262758451L, + 0.000277501076343287044992L ], + [ 0.00133244544948006563713L, -0.0019144384985654775265L, + 0.00110893691345966373396L ], + [ 0.00157972766073083495909L, 0.000162516262783915816899L, + -0.00206334210355432762645L, 0.00213896861856890981541L, + -0.00101085593912630031708L ], + [ -0.00407251211951401664727L, 0.00640336283380806979482L, + -0.00404101610816766177474L ] ]; // avoid nans when one of the arguments is inf: @@ -492,7 +493,7 @@ real logGamma(real x) } while ( x < 2.0L ) { - if ( fabs(x) <= 0.03125 ) + if ( fabs(x) <= 0.03125L ) { if ( x == 0.0L ) return real.infinity; @@ -568,9 +569,9 @@ real logGamma(real x) assert( feqrel(log(fabs(gamma(testpoints[i]))), testpoints[i+1]) > 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); + assert(feqrel(logGamma(-50.2L),log(fabs(gamma(-50.2L)))) > real.mant_dig-2); + assert(feqrel(logGamma(-0.008L),log(fabs(gamma(-0.008L)))) > real.mant_dig-2); + assert(feqrel(logGamma(-38.8L),log(fabs(gamma(-38.8L)))) > 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 @@ -597,8 +598,8 @@ private { */ 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) + enum real MAXLOG = 0x1.62e42fefa39ef35793c7673007e6p+13L; // log(real.max) + enum real MINLOG = -0x1.6546282207802c89d24d65e96274p+13L; // log(real.min_normal*real.epsilon) = log(smallest denormal) } else static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) { @@ -1039,17 +1040,17 @@ done: // 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); + assert(feqrel(betaIncomplete(8, 10, 0.2L), 0.010_934_315_234_099_2L) >= real.mant_dig - 5); + assert(feqrel(betaIncomplete(2, 2.5L, 0.9L), 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); + assert(feqrel(betaIncomplete(1000, 800, 0.5L), 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(betaIncomplete(1000, 800, 0.5L), 1.179140859734704555102808541457164E-06L) >= real.mant_dig - 14); + assert(feqrel(betaIncomplete(0.0001, 10000, 0.0001L), 0.999978059362107134278786L) >= real.mant_dig - 18); + assert(betaIncomplete(0.01L, 327726.7L, 0.545113L) == 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(betaIncomplete(0.01L, 498.437L, 0.0121433L), 0.99999664562033077636065L) >= real.mant_dig - 1); + assert(feqrel(betaIncompleteInv(5, 10, 0.2000002972865658842L), 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 @@ -1060,32 +1061,32 @@ done: // 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); + assert(betaIncompleteInv(0.01L, 8e-48L, 5.45464e-20L) == 1-real.epsilon); + assert(betaIncompleteInv(0.01L, 8e-48L, 9e-26L) == 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), + betaIncompleteInv(0x1.b3d151fbba0eb18p+1L, 1.2265e-19L, 2.44859e-18L), 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), + assert(feqrel(betaIncompleteInv(0x1.ff1275ae5b939bcap-41L, 4.6713e18L, 0.0813601L), 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); + real a1 = 3.40483L; + assert(betaIncompleteInv(a1, 4.0640301659679627772e19L, 0.545113L) == 0x1.ba8c08108aaf5d14p-109L); + real b1 = 2.82847e-25L; + assert(feqrel(betaIncompleteInv(0.01L, b1, 9e-26L), 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))); + assert( isNaN(betaIncompleteInv(0.12167L, 4.0640301659679627772e19L, 0.0813601L))); // 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); + assert(betaIncomplete(1.16251e20L, 2.18e39L, 5.45e-20L) == -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); + assert(1 - betaIncomplete(0.01L, 328222, 4.0375e-5L) == 0x1.5f62926b4p-30L); } } @@ -1326,11 +1327,13 @@ real betaDistPowerSeries(real a, real b, real x ) * values of a and x. */ real gammaIncomplete(real a, real x ) -in { +in +{ assert(x >= 0); assert(a > 0); } -body { +do +{ /* left tail of incomplete gamma function: * * inf. k @@ -1370,11 +1373,13 @@ body { /** ditto */ real gammaIncompleteCompl(real a, real x ) -in { +in +{ assert(x >= 0); assert(a > 0); } -body { +do +{ if (x == 0) return 1.0L; if ( (x < 1.0L) || (x < a) ) @@ -1456,11 +1461,13 @@ body { * root of incompleteGammaCompl(a,x) - p = 0. */ real gammaIncompleteComplInv(real a, real p) -in { +in +{ assert(p >= 0 && p <= 1); assert(a>0); } -body { +do +{ if (p == 0) return real.infinity; real y0 = p; @@ -1585,17 +1592,17 @@ ihalve: @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(fabs(gammaIncompleteComplInv(1, 0.5L) - 0.693147188044814L) < 0.00000005L); +assert(fabs(gammaIncompleteComplInv(12, 0.99L) - 5.42818075054289L) < 0.00000005L); +assert(fabs(gammaIncompleteComplInv(100, 0.8L) - 91.5013985848288L) < 0.000005L); 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); +assert(fabs(1.0L-gammaIncompleteCompl(0.5L, 2) - 0.954499729507309L) < 0.00000005L); +assert(fabs(gammaIncomplete(0.5L, 2) - 0.954499729507309L) < 0.00000005L); // Fixed Cephes bug: assert(gammaIncompleteCompl(384, real.infinity)==0); assert(gammaIncompleteComplInv(3, 0)==real.infinity); @@ -1603,9 +1610,9 @@ assert(gammaIncompleteComplInv(3, 0)==real.infinity); // 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); + assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109L) < 0.000000000005L); else - assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109) < 0.00000005); + assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109L) < 0.00000005L); } @@ -1688,7 +1695,7 @@ real digamma(real x) s += 1.0; } - if ( s < 1.0e17 ) + if ( s < 1.0e17L ) { z = 1.0/(s * s); y = z * poly(z, Bn_n); @@ -1755,7 +1762,7 @@ real logmdigamma(real x) } real y; - if ( s < 1.0e17 ) + if ( s < 1.0e17L ) { immutable real z = 1.0/(s * s); y = z * poly(z, Bn_n); @@ -1771,9 +1778,9 @@ real logmdigamma(real x) 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))); + assert(isClose(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))); + assert(isClose(digamma(x), log(x) - logmdigamma(x))); } /** Inverse of the Log Minus Digamma function @@ -1830,12 +1837,12 @@ real logmdigammaInverse(real y) 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)); + assert(isClose(logmdigammaInverse(test[0]), test[1], 2e-15L)); + + assert(isClose(logmdigamma(logmdigammaInverse(1)), 1, 1e-15L)); + assert(isClose(logmdigamma(logmdigammaInverse(real.min_normal)), real.min_normal, 1e-15L)); + assert(isClose(logmdigamma(logmdigammaInverse(real.max/2)), real.max/2, 1e-15L)); + assert(isClose(logmdigammaInverse(logmdigamma(1)), 1, 1e-15L)); + assert(isClose(logmdigammaInverse(logmdigamma(real.min_normal)), real.min_normal, 1e-15L)); + assert(isClose(logmdigammaInverse(logmdigamma(real.max/2)), real.max/2, 1e-15L)); } diff --git a/libphobos/src/std/internal/memory.d b/libphobos/src/std/internal/memory.d new file mode 100644 index 0000000..991cd68 --- /dev/null +++ b/libphobos/src/std/internal/memory.d @@ -0,0 +1,58 @@ +module std.internal.memory; + +package(std): + +version (D_Exceptions) +{ + import core.exception : onOutOfMemoryError; + private enum allocationFailed = `onOutOfMemoryError();`; +} +else +{ + private enum allocationFailed = `assert(0, "Memory allocation failed");`; +} + +// (below comments are non-DDOC, but are written in similar style) + +/+ +Mnemonic for `enforce!OutOfMemoryError(malloc(size))` that (unlike malloc) +can be considered pure because it causes the program to abort if the result +of the allocation is null, with the consequence that errno will not be +visibly changed by calling this function. Note that `malloc` can also +return `null` in non-failure situations if given an argument of 0. Hence, +it is a programmer error to use this function if the requested allocation +size is logically permitted to be zero. `enforceCalloc` and `enforceRealloc` +work analogously. + +All these functions are usable in `betterC`. ++/ +void* enforceMalloc()(size_t size) @nogc nothrow pure @safe +{ + auto result = fakePureMalloc(size); + if (!result) mixin(allocationFailed); + return result; +} + +// ditto +void* enforceCalloc()(size_t nmemb, size_t size) @nogc nothrow pure @safe +{ + auto result = fakePureCalloc(nmemb, size); + if (!result) mixin(allocationFailed); + return result; +} + +// ditto +void* enforceRealloc()(return scope void* ptr, size_t size) @nogc nothrow pure @system +{ + auto result = fakePureRealloc(ptr, size); + if (!result) mixin(allocationFailed); + return result; +} + +// Purified for local use only. +extern (C) @nogc nothrow pure private +{ + pragma(mangle, "malloc") void* fakePureMalloc(size_t) @safe; + pragma(mangle, "calloc") void* fakePureCalloc(size_t nmemb, size_t size) @safe; + pragma(mangle, "realloc") void* fakePureRealloc(return scope void* ptr, size_t size) @system; +} diff --git a/libphobos/src/std/internal/scopebuffer.d b/libphobos/src/std/internal/scopebuffer.d index 70a7c8d..7c1f8c0 100644 --- a/libphobos/src/std/internal/scopebuffer.d +++ b/libphobos/src/std/internal/scopebuffer.d @@ -2,7 +2,7 @@ * 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) + * Source: $(PHOBOSSRC std/internal/scopebuffer.d) */ module std.internal.scopebuffer; @@ -12,6 +12,7 @@ module std.internal.scopebuffer; import core.stdc.stdlib : realloc; import std.traits; +import std.internal.attributes : betterC; /************************************** * ScopeBuffer encapsulates using a local array as a temporary buffer. @@ -74,11 +75,11 @@ string cat(string s1, string s2) return textbuf[].idup; } --- - * ScopeBuffer is intended for high performance usages in $(D @system) and $(D @trusted) code. + * ScopeBuffer is intended for high performance usages in `@system` and `@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. + * instance's contents after `ScopeBuffer.free()` has been called. * * The `realloc` parameter defaults to C's `realloc()`. Another can be supplied to override it. * @@ -122,13 +123,13 @@ if (isAssignable!T && assert(!(buf.length & wasResized)); // assure even length of scratch buffer space assert(buf.length <= uint.max); // because we cast to uint later } - body + do { this.buf = buf.ptr; this.bufLen = cast(uint) buf.length; } - @system unittest + @system @betterC unittest { ubyte[10] tmpbuf = void; auto sbuf = ScopeBuffer!ubyte(tmpbuf); @@ -171,8 +172,8 @@ if (isAssignable!T && /************************ * 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. + * If `const(T)` can be converted to `T`, then put will accept + * `const(T)[]` as input. It will accept a `T[]` otherwise. */ package alias CT = Select!(is(const(T) : T), const(T), T); /// ditto @@ -203,7 +204,7 @@ if (isAssignable!T && assert(upper <= bufLen); assert(lower <= upper); } - body + do { return buf[lower .. upper]; } @@ -244,7 +245,7 @@ if (isAssignable!T && { assert(i <= this.used); } - body + do { this.used = cast(uint) i; } @@ -263,7 +264,7 @@ if (isAssignable!T && { assert(newsize <= uint.max); } - body + do { //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize); newsize |= wasResized; @@ -289,7 +290,7 @@ if (isAssignable!T && } } -@system unittest +@system @betterC unittest { import core.stdc.stdio; import std.range; @@ -350,7 +351,7 @@ if (isAssignable!T && } // const -@system unittest +@system @betterC unittest { char[10] tmpbuf = void; auto textbuf = ScopeBuffer!char(tmpbuf); @@ -377,14 +378,14 @@ auto scopeBuffer(T)(T[] tmpbuf) } /// -@system unittest +@system @betterC unittest { ubyte[10] tmpbuf = void; auto sb = scopeBuffer(tmpbuf); scope(exit) sb.free(); } -@system unittest +@system @betterC unittest { ScopeBuffer!(int*) b; int*[] s; diff --git a/libphobos/src/std/internal/test/dummyrange.d b/libphobos/src/std/internal/test/dummyrange.d index a6bce0a..e07e275 100644 --- a/libphobos/src/std/internal/test/dummyrange.d +++ b/libphobos/src/std/internal/test/dummyrange.d @@ -340,7 +340,7 @@ if (is(T == uint)) pure struct Cmp(T) if (is(T == double)) { - import std.math : approxEqual; + import std.math.operations : isClose; static auto iota(size_t low = 1, size_t high = 11) { @@ -354,7 +354,7 @@ if (is(T == double)) arr = iota().array; } - alias cmp = approxEqual!(double,double); + alias cmp = isClose!(double,double,double); enum dummyValue = 1337.0; enum dummyValueRslt = 1337.0 * 2.0; @@ -386,8 +386,6 @@ struct TestFoo 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; @@ -421,7 +419,6 @@ if (is(T == TestFoo)) { import std.algorithm.comparison : equal; import std.range : iota, retro, repeat; - import std.traits : Unqual; static void testInputRange(T,Cmp)() { @@ -431,7 +428,7 @@ if (is(T == TestFoo)) { if (numRuns == 1) { - static if (is(Unqual!(ElementType!(T)) == uint)) + static if (is(immutable ElementType!(T) == immutable uint)) { it.reinit(); } @@ -540,7 +537,7 @@ if (is(T == TestFoo)) import std.meta : AliasSeq; - foreach (S; AliasSeq!(uint, double, TestFoo)) + static foreach (S; AliasSeq!(uint, double, TestFoo)) { foreach (T; AllDummyRangesType!(S[])) { diff --git a/libphobos/src/std/internal/windows/advapi32.d b/libphobos/src/std/internal/windows/advapi32.d index 9ed6762..2220eec 100644 --- a/libphobos/src/std/internal/windows/advapi32.d +++ b/libphobos/src/std/internal/windows/advapi32.d @@ -6,13 +6,13 @@ * * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Kenji Hara - * Source: $(PHOBOSSRC std/internal/windows/_advapi32.d) + * Source: $(PHOBOSSRC std/internal/windows/advapi32.d) */ module std.internal.windows.advapi32; version (Windows): -import core.sys.windows.windows; +import core.sys.windows.winbase, core.sys.windows.winnt, core.sys.windows.winreg; pragma(lib, "advapi32.lib"); diff --git a/libphobos/src/std/json.d b/libphobos/src/std/json.d index 8ba0f05..39f89a6 100644 --- a/libphobos/src/std/json.d +++ b/libphobos/src/std/json.d @@ -6,8 +6,8 @@ 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) +References: $(LINK http://json.org/), $(LINK http://seriot.ch/parsing_json.html) +Source: $(PHOBOSSRC std/json.d) */ /* Copyright Jeremie Pelletier 2008 - 2009. @@ -39,7 +39,7 @@ import std.traits; long x; if (const(JSONValue)* code = "code" in j) { - if (code.type() == JSON_TYPE.INTEGER) + if (code.type() == JSONType.integer) x = code.integer; else x = to!int(code.str); @@ -77,31 +77,47 @@ enum JSONOptions 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 ('/') + strictParsing = 0x8, /// Strictly follow RFC-8259 grammar when parsing } /** JSON type enumeration */ -enum JSON_TYPE : byte +enum JSONType : 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 + /// Indicates the type of a `JSONValue`. + null_, + string, /// ditto + integer, /// ditto + uinteger, /// ditto + float_, /// ditto + array, /// ditto + object, /// ditto + true_, /// ditto + false_, /// ditto + // FIXME: Find some way to deprecate the enum members below, which does NOT + // create lots of spam-like deprecation warnings, which can't be fixed + // by the user. See discussion on this issue at + // https://forum.dlang.org/post/feudrhtxkaxxscwhhhff@forum.dlang.org + /* deprecated("Use .null_") */ NULL = null_, + /* deprecated("Use .string") */ STRING = string, + /* deprecated("Use .integer") */ INTEGER = integer, + /* deprecated("Use .uinteger") */ UINTEGER = uinteger, + /* deprecated("Use .float_") */ FLOAT = float_, + /* deprecated("Use .array") */ ARRAY = array, + /* deprecated("Use .object") */ OBJECT = object, + /* deprecated("Use .true_") */ TRUE = true_, + /* deprecated("Use .false_") */ FALSE = false_, } +deprecated("Use JSONType and the new enum member names") alias JSON_TYPE = JSONType; + /** JSON value node */ struct JSONValue { - import std.exception : enforceEx, enforce; + import std.exception : enforce; union Store { @@ -113,12 +129,12 @@ struct JSONValue JSONValue[] array; } private Store store; - private JSON_TYPE type_tag; + private JSONType type_tag; /** - Returns the JSON_TYPE of the value stored in this structure. + Returns the JSONType of the value stored in this structure. */ - @property JSON_TYPE type() const pure nothrow @safe @nogc + @property JSONType type() const pure nothrow @safe @nogc { return type_tag; } @@ -127,23 +143,23 @@ struct JSONValue { string s = "{ \"language\": \"D\" }"; JSONValue j = parseJSON(s); - assert(j.type == JSON_TYPE.OBJECT); - assert(j["language"].type == JSON_TYPE.STRING); + assert(j.type == JSONType.object); + assert(j["language"].type == JSONType.string); } /*** - * Value getter/setter for $(D JSON_TYPE.STRING). - * Throws: $(D JSONException) for read access if $(D type) is not - * $(D JSON_TYPE.STRING). + * Value getter/setter for `JSONType.string`. + * Throws: `JSONException` for read access if `type` is not + * `JSONType.string`. */ - @property string str() const pure @trusted + @property string str() const pure @trusted return scope { - enforce!JSONException(type == JSON_TYPE.STRING, + enforce!JSONException(type == JSONType.string, "JSONValue is not a string"); return store.str; } /// ditto - @property string str(string v) pure nothrow @nogc @safe + @property string str(return string v) pure nothrow @nogc @trusted return // TODO make @safe { assign(v); return v; @@ -162,13 +178,13 @@ struct JSONValue } /*** - * Value getter/setter for $(D JSON_TYPE.INTEGER). - * Throws: $(D JSONException) for read access if $(D type) is not - * $(D JSON_TYPE.INTEGER). + * Value getter/setter for `JSONType.integer`. + * Throws: `JSONException` for read access if `type` is not + * `JSONType.integer`. */ - @property inout(long) integer() inout pure @safe + @property long integer() const pure @safe { - enforce!JSONException(type == JSON_TYPE.INTEGER, + enforce!JSONException(type == JSONType.integer, "JSONValue is not an integer"); return store.integer; } @@ -180,13 +196,13 @@ struct JSONValue } /*** - * Value getter/setter for $(D JSON_TYPE.UINTEGER). - * Throws: $(D JSONException) for read access if $(D type) is not - * $(D JSON_TYPE.UINTEGER). + * Value getter/setter for `JSONType.uinteger`. + * Throws: `JSONException` for read access if `type` is not + * `JSONType.uinteger`. */ - @property inout(ulong) uinteger() inout pure @safe + @property ulong uinteger() const pure @safe { - enforce!JSONException(type == JSON_TYPE.UINTEGER, + enforce!JSONException(type == JSONType.uinteger, "JSONValue is not an unsigned integer"); return store.uinteger; } @@ -198,14 +214,14 @@ struct JSONValue } /*** - * Value getter/setter for $(D JSON_TYPE.FLOAT). Note that despite + * Value getter/setter for `JSONType.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). + * Throws: `JSONException` for read access if `type` is not + * `JSONType.float_`. */ - @property inout(double) floating() inout pure @safe + @property double floating() const pure @safe { - enforce!JSONException(type == JSON_TYPE.FLOAT, + enforce!JSONException(type == JSONType.float_, "JSONValue is not a floating type"); return store.floating; } @@ -217,9 +233,41 @@ struct JSONValue } /*** - * Value getter/setter for $(D JSON_TYPE.OBJECT). - * Throws: $(D JSONException) for read access if $(D type) is not - * $(D JSON_TYPE.OBJECT). + * Value getter/setter for boolean stored in JSON. + * Throws: `JSONException` for read access if `this.type` is not + * `JSONType.true_` or `JSONType.false_`. + */ + @property bool boolean() const pure @safe + { + if (type == JSONType.true_) return true; + if (type == JSONType.false_) return false; + + throw new JSONException("JSONValue is not a boolean type"); + } + /// ditto + @property bool boolean(bool v) pure nothrow @safe @nogc + { + assign(v); + return v; + } + /// + @safe unittest + { + JSONValue j = true; + assert(j.boolean == true); + + j.boolean = false; + assert(j.boolean == false); + + j.integer = 12; + import std.exception : assertThrown; + assertThrown!JSONException(j.boolean); + } + + /*** + * Value getter/setter for `JSONType.object`. + * Throws: `JSONException` for read access if `type` is not + * `JSONType.object`. * Note: this is @system because of the following pattern: --- auto a = &(json.object()); @@ -227,22 +275,22 @@ struct JSONValue (*a)["hello"] = "world"; // segmentation fault --- */ - @property ref inout(JSONValue[string]) object() inout pure @system + @property ref inout(JSONValue[string]) object() inout pure @system return { - enforce!JSONException(type == JSON_TYPE.OBJECT, + enforce!JSONException(type == JSONType.object, "JSONValue is not an object"); return store.object; } /// ditto - @property JSONValue[string] object(JSONValue[string] v) pure nothrow @nogc @safe + @property JSONValue[string] object(return JSONValue[string] v) pure nothrow @nogc @trusted // TODO make @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. + * Value getter for `JSONType.object`. + * Unlike `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: * --- @@ -252,20 +300,20 @@ struct JSONValue * assert("hello" !in json.object); * --- * - * Throws: $(D JSONException) for read access if $(D type) is not - * $(D JSON_TYPE.OBJECT). + * Throws: `JSONException` for read access if `type` is not + * `JSONType.object`. */ @property inout(JSONValue[string]) objectNoRef() inout pure @trusted { - enforce!JSONException(type == JSON_TYPE.OBJECT, + enforce!JSONException(type == JSONType.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). + * Value getter/setter for `JSONType.array`. + * Throws: `JSONException` for read access if `type` is not + * `JSONType.array`. * Note: this is @system because of the following pattern: --- auto a = &(json.array()); @@ -275,20 +323,20 @@ struct JSONValue */ @property ref inout(JSONValue[]) array() inout pure @system { - enforce!JSONException(type == JSON_TYPE.ARRAY, + enforce!JSONException(type == JSONType.array, "JSONValue is not an array"); return store.array; } /// ditto - @property JSONValue[] array(JSONValue[] v) pure nothrow @nogc @safe + @property JSONValue[] array(return JSONValue[] v) pure nothrow @nogc @trusted scope // TODO make @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. + * Value getter for `JSONType.array`. + * Unlike `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: @@ -299,38 +347,137 @@ struct JSONValue * assert(json.array.length == 1); * --- * - * Throws: $(D JSONException) for read access if $(D type) is not - * $(D JSON_TYPE.ARRAY). + * Throws: `JSONException` for read access if `type` is not + * `JSONType.array`. */ @property inout(JSONValue[]) arrayNoRef() inout pure @trusted { - enforce!JSONException(type == JSON_TYPE.ARRAY, + enforce!JSONException(type == JSONType.array, "JSONValue is not an array"); return store.array; } - /// Test whether the type is $(D JSON_TYPE.NULL) + /// Test whether the type is `JSONType.null_` @property bool isNull() const pure nothrow @safe @nogc { - return type == JSON_TYPE.NULL; + return type == JSONType.null_; } - private void assign(T)(T arg) @safe + /*** + * Generic type value getter + * A convenience getter that returns this `JSONValue` as the specified D type. + * Note: only numeric, `bool`, `string`, `JSONValue[string]` and `JSONValue[]` types are accepted + * Throws: `JSONException` if `T` cannot hold the contents of this `JSONValue` + * `ConvException` in case of integer overflow when converting to `T` + */ + @property inout(T) get(T)() inout const pure @safe + { + static if (is(immutable T == immutable string)) + { + return str; + } + else static if (is(immutable T == immutable bool)) + { + return boolean; + } + else static if (isFloatingPoint!T) + { + switch (type) + { + case JSONType.float_: + return cast(T) floating; + case JSONType.uinteger: + return cast(T) uinteger; + case JSONType.integer: + return cast(T) integer; + default: + throw new JSONException("JSONValue is not a number type"); + } + } + else static if (isIntegral!T) + { + switch (type) + { + case JSONType.uinteger: + return uinteger.to!T; + case JSONType.integer: + return integer.to!T; + default: + throw new JSONException("JSONValue is not a an integral type"); + } + } + else + { + static assert(false, "Unsupported type"); + } + } + // This specialization is needed because arrayNoRef requires inout + @property inout(T) get(T : JSONValue[])() inout pure @trusted /// ditto + { + return arrayNoRef; + } + /// ditto + @property inout(T) get(T : JSONValue[string])() inout pure @trusted + { + return object; + } + /// + @safe unittest + { + import std.exception; + import std.conv; + string s = + `{ + "a": 123, + "b": 3.1415, + "c": "text", + "d": true, + "e": [1, 2, 3], + "f": { "a": 1 }, + "g": -45, + "h": ` ~ ulong.max.to!string ~ `, + }`; + + struct a { } + + immutable json = parseJSON(s); + assert(json["a"].get!double == 123.0); + assert(json["a"].get!int == 123); + assert(json["a"].get!uint == 123); + assert(json["b"].get!double == 3.1415); + assertThrown!JSONException(json["b"].get!int); + assert(json["c"].get!string == "text"); + assert(json["d"].get!bool == true); + assertNotThrown(json["e"].get!(JSONValue[])); + assertNotThrown(json["f"].get!(JSONValue[string])); + static assert(!__traits(compiles, json["a"].get!a)); + assertThrown!JSONException(json["e"].get!float); + assertThrown!JSONException(json["d"].get!(JSONValue[string])); + assertThrown!JSONException(json["f"].get!(JSONValue[])); + assert(json["g"].get!int == -45); + assertThrown!ConvException(json["g"].get!uint); + assert(json["h"].get!ulong == ulong.max); + assertThrown!ConvException(json["h"].get!uint); + assertNotThrown(json["h"].get!float); + } + + private void assign(T)(T arg) { static if (is(T : typeof(null))) { - type_tag = JSON_TYPE.NULL; + type_tag = JSONType.null_; } else static if (is(T : string)) { - type_tag = JSON_TYPE.STRING; + type_tag = JSONType.string; string t = arg; () @trusted { store.str = t; }(); } - else static if (isSomeString!T) // issue 15884 + // https://issues.dlang.org/show_bug.cgi?id=15884 + else static if (isSomeString!T) { - type_tag = JSON_TYPE.STRING; - // FIXME: std.array.array(Range) is not deduced as 'pure' + type_tag = JSONType.string; + // FIXME: std.Array.Array(Range) is not deduced as 'pure' () @trusted { import std.utf : byUTF; store.str = cast(immutable)(arg.byUTF!char.array); @@ -338,27 +485,27 @@ struct JSONValue } else static if (is(T : bool)) { - type_tag = arg ? JSON_TYPE.TRUE : JSON_TYPE.FALSE; + type_tag = arg ? JSONType.true_ : JSONType.false_; } else static if (is(T : ulong) && isUnsigned!T) { - type_tag = JSON_TYPE.UINTEGER; + type_tag = JSONType.uinteger; store.uinteger = arg; } else static if (is(T : long)) { - type_tag = JSON_TYPE.INTEGER; + type_tag = JSONType.integer; store.integer = arg; } else static if (isFloatingPoint!T) { - type_tag = JSON_TYPE.FLOAT; + type_tag = JSONType.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; + type_tag = JSONType.object; static if (is(Value : JSONValue)) { JSONValue[string] t = arg; @@ -374,7 +521,7 @@ struct JSONValue } else static if (isArray!T) { - type_tag = JSON_TYPE.ARRAY; + type_tag = JSONType.array; static if (is(ElementEncodingType!T : JSONValue)) { JSONValue[] t = arg; @@ -401,7 +548,7 @@ struct JSONValue private void assignRef(T)(ref T arg) if (isStaticArray!T) { - type_tag = JSON_TYPE.ARRAY; + type_tag = JSONType.array; static if (is(ElementEncodingType!T : JSONValue)) { store.array = arg; @@ -416,15 +563,15 @@ struct JSONValue } /** - * 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 + * Constructor for `JSONValue`. If `arg` is a `JSONValue` + * its value and type will be copied to the new `JSONValue`. + * Note that this is a shallow copy: if type is `JSONType.object` + * or `JSONType.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 + * Otherwise, `arg` must be implicitly convertible to one of the + * following types: `typeof(null)`, `string`, `ulong`, + * `long`, `double`, an associative array `V[K]` for any `V` + * and `K` i.e. a JSON object, any array or `bool`. The type will * be set accordingly. */ this(T)(T arg) if (!isStaticArray!T) @@ -449,10 +596,10 @@ struct JSONValue j = JSONValue(42); j = JSONValue( [1, 2, 3] ); - assert(j.type == JSON_TYPE.ARRAY); + assert(j.type == JSONType.array); j = JSONValue( ["language": "D"] ); - assert(j.type == JSON_TYPE.OBJECT); + assert(j.type == JSONType.object); } void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue)) @@ -467,12 +614,12 @@ struct JSONValue /*** * Array syntax for json arrays. - * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.ARRAY). + * Throws: `JSONException` if `type` is not `JSONType.array`. */ ref inout(JSONValue) opIndex(size_t i) inout pure @safe { auto a = this.arrayNoRef; - enforceEx!JSONException(i < a.length, + enforce!JSONException(i < a.length, "JSONValue array index is out of range"); return a[i]; } @@ -486,9 +633,9 @@ struct JSONValue /*** * Hash syntax for json objects. - * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT). + * Throws: `JSONException` if `type` is not `JSONType.object`. */ - ref inout(JSONValue) opIndex(string k) inout pure @safe + ref inout(JSONValue) opIndex(return string k) inout pure @safe { auto o = this.objectNoRef; return *enforce!JSONException(k in o, @@ -502,20 +649,20 @@ struct JSONValue } /*** - * Operator sets $(D value) for element of JSON object by $(D key). + * Operator sets `value` for element of JSON object by `key`. * * If JSON value is null, then operator initializes it with object and then - * sets $(D value) for it. + * sets `value` for it. * - * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT) - * or $(D JSON_TYPE.NULL). + * Throws: `JSONException` if `type` is not `JSONType.object` + * or `JSONType.null_`. */ - void opIndexAssign(T)(auto ref T value, string key) pure + void opIndexAssign(T)(auto ref T value, string key) { - enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL, + enforce!JSONException(type == JSONType.object || type == JSONType.null_, "JSONValue must be object or null"); JSONValue[string] aa = null; - if (type == JSON_TYPE.OBJECT) + if (type == JSONType.object) { aa = this.objectNoRef; } @@ -531,10 +678,10 @@ struct JSONValue assert( j["language"].str == "Perl" ); } - void opIndexAssign(T)(T arg, size_t i) pure + void opIndexAssign(T)(T arg, size_t i) { auto a = this.arrayNoRef; - enforceEx!JSONException(i < a.length, + enforce!JSONException(i < a.length, "JSONValue array index is out of range"); a[i] = arg; this.array = a; @@ -547,7 +694,7 @@ struct JSONValue assert( j[1].str == "D" ); } - JSONValue opBinary(string op : "~", T)(T arg) @safe + JSONValue opBinary(string op : "~", T)(T arg) { auto a = this.arrayNoRef; static if (isArray!T) @@ -564,7 +711,7 @@ struct JSONValue } } - void opOpAssign(string op : "~", T)(T arg) @safe + void opOpAssign(string op : "~", T)(T arg) { auto a = this.arrayNoRef; static if (isArray!T) @@ -583,16 +730,16 @@ struct JSONValue } /** - * Support for the $(D in) operator. + * Support for the `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). + * when found, the `const(JSONValue)*` that matches to the key, + * otherwise `null`. * - * Throws: $(D JSONException) if the right hand side argument $(D JSON_TYPE) - * is not $(D OBJECT). + * Throws: `JSONException` if the right hand side argument `JSONType` + * is not `object`. */ auto opBinaryRight(string op : "in")(string k) const @safe { @@ -615,30 +762,67 @@ struct JSONValue // 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; + case JSONType.integer: + switch (rhs.type_tag) + { + case JSONType.integer: + return store.integer == rhs.store.integer; + case JSONType.uinteger: + return store.integer == rhs.store.uinteger; + case JSONType.float_: + return store.integer == rhs.store.floating; + default: + return false; + } + case JSONType.uinteger: + switch (rhs.type_tag) + { + case JSONType.integer: + return store.uinteger == rhs.store.integer; + case JSONType.uinteger: + return store.uinteger == rhs.store.uinteger; + case JSONType.float_: + return store.uinteger == rhs.store.floating; + default: + return false; + } + case JSONType.float_: + switch (rhs.type_tag) + { + case JSONType.integer: + return store.floating == rhs.store.integer; + case JSONType.uinteger: + return store.floating == rhs.store.uinteger; + case JSONType.float_: + return store.floating == rhs.store.floating; + default: + return false; + } + case JSONType.string: + return type_tag == rhs.type_tag && store.str == rhs.store.str; + case JSONType.object: + return type_tag == rhs.type_tag && store.object == rhs.store.object; + case JSONType.array: + return type_tag == rhs.type_tag && store.array == rhs.store.array; + case JSONType.true_: + case JSONType.false_: + case JSONType.null_: + return type_tag == rhs.type_tag; } } - /// Implements the foreach $(D opApply) interface for json arrays. + /// + @safe unittest + { + assert(JSONValue(0u) == JSONValue(0)); + assert(JSONValue(0u) == JSONValue(0.0)); + assert(JSONValue(0) == JSONValue(0.0)); + } + + /// Implements the foreach `opApply` interface for json arrays. int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system { int result; @@ -653,10 +837,10 @@ struct JSONValue return result; } - /// Implements the foreach $(D opApply) interface for json objects. + /// Implements the foreach `opApply` interface for json objects. int opApply(scope int delegate(string key, ref JSONValue) dg) @system { - enforce!JSONException(type == JSON_TYPE.OBJECT, + enforce!JSONException(type == JSONType.object, "JSONValue is not an object"); int result; @@ -671,7 +855,7 @@ struct JSONValue } /*** - * Implicitly calls $(D toJSON) on this JSONValue. + * Implicitly calls `toJSON` on this JSONValue. * * $(I options) can be used to tweak the conversion behavior. */ @@ -680,8 +864,14 @@ struct JSONValue return toJSON(this, false, options); } + /// + void toString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const + { + toJSON(sink, this, false, options); + } + /*** - * Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but + * Implicitly calls `toJSON` on this JSONValue, like `toString`, but * also passes $(I true) as $(I pretty) argument. * * $(I options) can be used to tweak the conversion behavior @@ -690,11 +880,47 @@ struct JSONValue { return toJSON(this, true, options); } + + /// + void toPrettyString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const + { + toJSON(sink, this, true, options); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=20874 +@system unittest +{ + static struct MyCustomType + { + public string toString () const @system { return null; } + alias toString this; + } + + static struct B + { + public JSONValue asJSON() const @system { return JSONValue.init; } + alias asJSON this; + } + + if (false) // Just checking attributes + { + JSONValue json; + MyCustomType ilovedlang; + json = ilovedlang; + json["foo"] = ilovedlang; + auto s = ilovedlang in json; + + B b; + json ~= b; + json ~ b; + } } /** Parses a serialized string and returns a tree of JSON values. -Throws: $(LREF JSONException) if the depth exceeds the max depth. +Throws: $(LREF JSONException) if string does not follow the JSON grammar or the depth exceeds the max depth, + $(LREF ConvException) if a number in the input cannot be represented by a native D type. Params: json = json-formatted string to parse maxDepth = maximum depth of nesting allowed, -1 disables depth checking @@ -703,10 +929,10 @@ Params: 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; + import std.ascii : isDigit, isHexDigit, toUpper, toLower; + import std.typecons : Nullable, Yes; JSONValue root; - root.type_tag = JSON_TYPE.NULL; + root.type_tag = JSONType.null_; // Avoid UTF decoding when possible, as it is unnecessary when // processing JSON. @@ -715,17 +941,37 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) else alias Char = Unqual!(ElementType!T); - if (json.empty) return root; - int depth = -1; - Char next = 0; + Nullable!Char next; int line = 1, pos = 0; + immutable bool strict = (options & JSONOptions.strictParsing) != 0; void error(string msg) { throw new JSONException(msg, line, pos); } + if (json.empty) + { + if (strict) + { + error("Empty JSON body"); + } + return root; + } + + bool isWhite(dchar c) + { + if (strict) + { + // RFC 7159 has a stricter definition of whitespace than general ASCII. + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + import std.ascii : isWhite; + // Accept ASCII NUL as whitespace in non-strict mode. + return c == 0 || isWhite(c); + } + Char popChar() { if (json.empty) error("Unexpected end of data."); @@ -755,17 +1001,35 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) Char peekChar() { - if (!next) + if (next.isNull) { if (json.empty) return '\0'; next = popChar(); } + return next.get; + } + + Nullable!Char peekCharNullable() + { + if (next.isNull && !json.empty) + { + next = popChar(); + } return next; } void skipWhitespace() { - while (isWhite(peekChar())) next = 0; + while (true) + { + auto c = peekCharNullable(); + if (c.isNull || + !isWhite(c.get)) + { + return; + } + next.nullify(); + } } Char getChar(bool SkipWhitespace = false)() @@ -773,10 +1037,10 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) static if (SkipWhitespace) skipWhitespace(); Char c; - if (next) + if (!next.isNull) { - c = next; - next = 0; + c = next.get; + next.nullify(); } else c = popChar(); @@ -784,11 +1048,11 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) return c; } - void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) + void checkChar(bool SkipWhitespace = true)(char c, bool caseSensitive = true) { static if (SkipWhitespace) skipWhitespace(); auto c2 = getChar(); - static if (!CaseSensitive) c2 = toLower(c2); + if (!caseSensitive) c2 = toLower(c2); if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'.")); } @@ -819,7 +1083,6 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) string parseString() { - import std.ascii : isControl; import std.uni : isSurrogateHi, isSurrogateLo; import std.utf : encode, decode; @@ -880,8 +1143,12 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) default: // RFC 7159 states that control characters U+0000 through // U+001F must not appear unescaped in a JSON string. + // Note: std.ascii.isControl can't be used for this test + // because it considers ASCII DEL (0x7f) to be a control + // character but RFC 7159 does not. + // Accept unescaped ASCII NULs in non-strict mode. auto c = getChar(); - if (isControl(c)) + if (c < 0x20 && (strict || c != 0)) error("Illegal control character."); str.put(c); goto Next; @@ -927,6 +1194,11 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) JSONValue[string] obj; do { + skipWhitespace(); + if (!strict && peekChar() == '}') + { + break; + } checkChar('"'); string name = parseString(); checkChar(':'); @@ -943,13 +1215,18 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) case '[': if (testChar(']')) { - value.type_tag = JSON_TYPE.ARRAY; + value.type_tag = JSONType.array; break; } JSONValue[] arr; do { + skipWhitespace(); + if (!strict && peekChar() == ']') + { + break; + } JSONValue element; parseValue(element); arr ~= element; @@ -968,12 +1245,11 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) tryGetSpecialFloat(str, value.store.floating)) { // found a special float, its value was placed in value.store.floating - value.type_tag = JSON_TYPE.FLOAT; + value.type_tag = JSONType.float_; break; } - value.type_tag = JSON_TYPE.STRING; - value.store.str = str; + value.assign(str); break; case '0': .. case '9': @@ -1001,7 +1277,18 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) isNegative = true; } - readInteger(); + if (strict && c == '0') + { + number.put('0'); + if (isDigit(peekChar())) + { + error("Additional digits not allowed after initial zero digit"); + } + } + else + { + readInteger(); + } if (testChar('.')) { @@ -1023,44 +1310,63 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) string data = number.data; if (isFloat) { - value.type_tag = JSON_TYPE.FLOAT; + value.type_tag = JSONType.float_; value.store.floating = parse!double(data); } else { if (isNegative) + { value.store.integer = parse!long(data); + value.type_tag = JSONType.integer; + } else - value.store.uinteger = parse!ulong(data); - - value.type_tag = !isNegative && value.store.uinteger & (1UL << 63) ? - JSON_TYPE.UINTEGER : JSON_TYPE.INTEGER; + { + // only set the correct union member to not confuse CTFE + ulong u = parse!ulong(data); + if (u & (1UL << 63)) + { + value.store.uinteger = u; + value.type_tag = JSONType.uinteger; + } + else + { + value.store.integer = u; + value.type_tag = JSONType.integer; + } + } } break; - case 't': case 'T': - value.type_tag = JSON_TYPE.TRUE; - checkChar!(false, false)('r'); - checkChar!(false, false)('u'); - checkChar!(false, false)('e'); + if (strict) goto default; + goto case; + case 't': + value.type_tag = JSONType.true_; + checkChar!false('r', strict); + checkChar!false('u', strict); + checkChar!false('e', strict); 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'); + if (strict) goto default; + goto case; + case 'f': + value.type_tag = JSONType.false_; + checkChar!false('a', strict); + checkChar!false('l', strict); + checkChar!false('s', strict); + checkChar!false('e', strict); break; - case 'n': case 'N': - value.type_tag = JSON_TYPE.NULL; - checkChar!(false, false)('u'); - checkChar!(false, false)('l'); - checkChar!(false, false)('l'); + if (strict) goto default; + goto case; + case 'n': + value.type_tag = JSONType.null_; + checkChar!false('u', strict); + checkChar!false('l', strict); + checkChar!false('l', strict); break; default: @@ -1071,16 +1377,21 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) } parseValue(root); + if (strict) + { + skipWhitespace(); + if (!peekCharNullable().isNull) error("Trailing non-whitespace characters"); + } return root; } @safe unittest { enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`; - static assert(parseJSON(issue15742objectOfObject).type == JSON_TYPE.OBJECT); + static assert(parseJSON(issue15742objectOfObject).type == JSONType.object); enum issue15742arrayOfArray = `[[1]]`; - static assert(parseJSON(issue15742arrayOfArray).type == JSON_TYPE.ARRAY); + static assert(parseJSON(issue15742arrayOfArray).type == JSONType.array); } @safe unittest @@ -1110,9 +1421,15 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) assert(json["key1"]["key2"].integer == 1); } +// https://issues.dlang.org/show_bug.cgi?id=20527 +@safe unittest +{ + static assert(parseJSON(`{"a" : 2}`)["a"].integer == 2); +} + /** Parses a serialized string and returns a tree of JSON values. -Throws: $(REF JSONException, std,json) if the depth exceeds the max depth. +Throws: $(LREF JSONException) 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 @@ -1128,15 +1445,26 @@ 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. +If `pretty` is false no whitespaces are generated. +If `pretty` is true serialized string is formatted to be human-readable. +Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in `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(); + toJSON(json, root, pretty, options); + return json.data; +} - void toStringImpl(Char)(string str) @safe +/// +void toJSON(Out)( + auto ref Out json, + const ref JSONValue root, + in bool pretty = false, + in JSONOptions options = JSONOptions.none) +if (isOutputRange!(Out,char)) +{ + void toStringImpl(Char)(string str) { json.put('"'); @@ -1166,7 +1494,7 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o // Make sure we do UTF decoding iff we want to // escape Unicode characters. assert(((options & JSONOptions.escapeNonAsciiChars) != 0) - == is(Char == dchar)); + == is(Char == dchar), "JSONOptions.escapeNonAsciiChars needs dchar strings"); with (JSONOptions) if (isControl(c) || ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80)) @@ -1197,7 +1525,7 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o json.put('"'); } - void toString(string str) @safe + void toString(string str) { // Avoid UTF decoding when possible, as it is unnecessary when // processing JSON. @@ -1207,7 +1535,19 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o toStringImpl!char(str); } - void toValue(ref in JSONValue value, ulong indentLevel) @safe + // recursive @safe inference is broken here + // workaround: if json.put is @safe, we should be too, + // so annotate the recursion as @safe manually + static if (isSafe!({ json.put(""); })) + { + void delegate(ref const JSONValue, ulong) @safe toValue; + } + else + { + void delegate(ref const JSONValue, ulong) @system toValue; + } + + void toValueImpl(ref const JSONValue value, ulong indentLevel) { void putTabs(ulong additionalIndent = 0) { @@ -1228,7 +1568,7 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o final switch (value.type) { - case JSON_TYPE.OBJECT: + case JSONType.object: auto obj = value.objectNoRef; if (!obj.length) { @@ -1257,7 +1597,7 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o } import std.algorithm.sorting : sort; - // @@@BUG@@@ 14439 + // https://issues.dlang.org/show_bug.cgi?id=14439 // auto names = obj.keys; // aa.keys can't be called in @safe code auto names = new string[obj.length]; size_t i = 0; @@ -1275,7 +1615,7 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o } break; - case JSON_TYPE.ARRAY: + case JSONType.array: auto arr = value.arrayNoRef; if (arr.empty) { @@ -1297,20 +1637,20 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o } break; - case JSON_TYPE.STRING: + case JSONType.string: toString(value.str); break; - case JSON_TYPE.INTEGER: + case JSONType.integer: json.put(to!string(value.store.integer)); break; - case JSON_TYPE.UINTEGER: + case JSONType.uinteger: json.put(to!string(value.store.uinteger)); break; - case JSON_TYPE.FLOAT: - import std.math : isNaN, isInfinity; + case JSONType.float_: + import std.math.traits : isNaN, isInfinity; auto val = value.store.floating; @@ -1340,34 +1680,41 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o } else { - import std.format : format; + import std.algorithm.searching : canFind; + import std.format : sformat; // 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)); + char[25] buf; + auto result = buf[].sformat!"%.18g"(val); + json.put(result); + if (!result.canFind('e') && !result.canFind('.')) + json.put(".0"); } break; - case JSON_TYPE.TRUE: + case JSONType.true_: json.put("true"); break; - case JSON_TYPE.FALSE: + case JSONType.false_: json.put("false"); break; - case JSON_TYPE.NULL: + case JSONType.null_: json.put("null"); break; } } + toValue = &toValueImpl; + toValue(root, 0); - return json.data; } -@safe unittest // bugzilla 12897 + // https://issues.dlang.org/show_bug.cgi?id=12897 +@safe unittest { JSONValue jv0 = JSONValue("test测试"); assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`); @@ -1381,6 +1728,71 @@ string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions o assert(toJSON(jv1, false, JSONOptions.none) == `"été"`); } +// https://issues.dlang.org/show_bug.cgi?id=20511 +@system unittest +{ + import std.format.write : formattedWrite; + import std.range : nullSink, outputRangeObject; + + outputRangeObject!(const(char)[])(nullSink) + .formattedWrite!"%s"(JSONValue.init); +} + +// Issue 16432 - JSON incorrectly parses to string +@safe unittest +{ + // Floating points numbers are rounded to the nearest integer and thus get + // incorrectly parsed + + import std.math.operations : isClose; + + string s = "{\"rating\": 3.0 }"; + JSONValue j = parseJSON(s); + assert(j["rating"].type == JSONType.float_); + j = j.toString.parseJSON; + assert(j["rating"].type == JSONType.float_); + assert(isClose(j["rating"].floating, 3.0)); + + s = "{\"rating\": -3.0 }"; + j = parseJSON(s); + assert(j["rating"].type == JSONType.float_); + j = j.toString.parseJSON; + assert(j["rating"].type == JSONType.float_); + assert(isClose(j["rating"].floating, -3.0)); + + // https://issues.dlang.org/show_bug.cgi?id=13660 + auto jv1 = JSONValue(4.0); + auto textual = jv1.toString(); + auto jv2 = parseJSON(textual); + assert(jv1.type == JSONType.float_); + assert(textual == "4.0"); + assert(jv2.type == JSONType.float_); +} + +@safe unittest +{ + // Adapted from https://github.com/dlang/phobos/pull/5005 + // Result from toString is not checked here, because this + // might differ (%e-like or %f-like output) depending + // on OS and compiler optimization. + import std.math.operations : isClose; + + // test positive extreme values + JSONValue j; + j["rating"] = 1e18 - 65; + assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 65)); + + j["rating"] = 1e18 - 64; + assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 64)); + + // negative extreme values + j["rating"] = -1e18 + 65; + assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 65)); + + j["rating"] = -1e18 + 64; + assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 64)); +} + /** Exception thrown on JSON errors */ @@ -1405,7 +1817,7 @@ class JSONException : Exception { import std.exception; JSONValue jv = "123"; - assert(jv.type == JSON_TYPE.STRING); + assert(jv.type == JSONType.string); assertNotThrown(jv.str); assertThrown!JSONException(jv.integer); assertThrown!JSONException(jv.uinteger); @@ -1416,19 +1828,19 @@ class JSONException : Exception assertThrown!JSONException(jv[2]); jv = -3; - assert(jv.type == JSON_TYPE.INTEGER); + assert(jv.type == JSONType.integer); assertNotThrown(jv.integer); jv = cast(uint) 3; - assert(jv.type == JSON_TYPE.UINTEGER); + assert(jv.type == JSONType.uinteger); assertNotThrown(jv.uinteger); jv = 3.0; - assert(jv.type == JSON_TYPE.FLOAT); + assert(jv.type == JSONType.float_); assertNotThrown(jv.floating); jv = ["key" : "value"]; - assert(jv.type == JSON_TYPE.OBJECT); + assert(jv.type == JSONType.object); assertNotThrown(jv.object); assertNotThrown(jv["key"]); assert("key" in jv); @@ -1442,82 +1854,81 @@ class JSONException : Exception { static assert(is(typeof(value) == JSONValue)); assert(key == "key"); - assert(value.type == JSON_TYPE.STRING); + assert(value.type == JSONType.string); assertNotThrown(value.str); assert(value.str == "value"); } jv = [3, 4, 5]; - assert(jv.type == JSON_TYPE.ARRAY); + assert(jv.type == JSONType.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); + assert(value.type == JSONType.integer); assertNotThrown(value.integer); assert(index == (value.integer-3)); } jv = null; - assert(jv.type == JSON_TYPE.NULL); + assert(jv.type == JSONType.null_); assert(jv.isNull); jv = "foo"; assert(!jv.isNull); jv = JSONValue("value"); - assert(jv.type == JSON_TYPE.STRING); + assert(jv.type == JSONType.string); assert(jv.str == "value"); JSONValue jv2 = JSONValue("value"); - assert(jv2.type == JSON_TYPE.STRING); + assert(jv2.type == JSONType.string); assert(jv2.str == "value"); JSONValue jv3 = JSONValue("\u001c"); - assert(jv3.type == JSON_TYPE.STRING); + assert(jv3.type == JSONType.string); assert(jv3.str == "\u001C"); } +// https://issues.dlang.org/show_bug.cgi?id=11504 @system unittest { - // Bugzilla 11504 - JSONValue jv = 1; - assert(jv.type == JSON_TYPE.INTEGER); + assert(jv.type == JSONType.integer); jv.str = "123"; - assert(jv.type == JSON_TYPE.STRING); + assert(jv.type == JSONType.string); assert(jv.str == "123"); jv.integer = 1; - assert(jv.type == JSON_TYPE.INTEGER); + assert(jv.type == JSONType.integer); assert(jv.integer == 1); jv.uinteger = 2u; - assert(jv.type == JSON_TYPE.UINTEGER); + assert(jv.type == JSONType.uinteger); assert(jv.uinteger == 2u); jv.floating = 1.5; - assert(jv.type == JSON_TYPE.FLOAT); + assert(jv.type == JSONType.float_); assert(jv.floating == 1.5); jv.object = ["key" : JSONValue("value")]; - assert(jv.type == JSON_TYPE.OBJECT); + assert(jv.type == JSONType.object); assert(jv.object == ["key" : JSONValue("value")]); jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)]; - assert(jv.type == JSON_TYPE.ARRAY); + assert(jv.type == JSONType.array); assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]); jv = true; - assert(jv.type == JSON_TYPE.TRUE); + assert(jv.type == JSONType.true_); jv = false; - assert(jv.type == JSON_TYPE.FALSE); + assert(jv.type == JSONType.false_); enum E{True = true} jv = E.True; - assert(jv.type == JSON_TYPE.TRUE); + assert(jv.type == JSONType.true_); } @system pure unittest @@ -1653,32 +2064,32 @@ class JSONException : Exception @system pure unittest { - // Bugzilla 12969 + // https://issues.dlang.org/show_bug.cgi?id=12969 JSONValue jv; jv["int"] = 123; - assert(jv.type == JSON_TYPE.OBJECT); + assert(jv.type == JSONType.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"].type == JSONType.array); assert(jv["array"][2].integer == 3); jv["str"] = "D language"; - assert(jv["str"].type == JSON_TYPE.STRING); + assert(jv["str"].type == JSONType.string); assert(jv["str"].str == "D language"); jv["bool"] = false; - assert(jv["bool"].type == JSON_TYPE.FALSE); + assert(jv["bool"].type == JSONType.false_); assert(jv.object.length == 4); jv = [5, 4, 3, 2, 1]; - assert( jv.type == JSON_TYPE.ARRAY ); - assert( jv[3].integer == 2 ); + assert(jv.type == JSONType.array); + assert(jv[3].integer == 2); } @safe unittest @@ -1702,7 +2113,7 @@ EOF"; @safe unittest { import std.exception : assertThrown; - import std.math : isNaN, isInfinity; + import std.math.traits : isNaN, isInfinity; // expected representations of NaN and Inf enum { @@ -1753,28 +2164,30 @@ pure nothrow @safe @nogc unittest assert(testVal.isNull); } -pure nothrow @safe unittest // issue 15884 +// https://issues.dlang.org/show_bug.cgi?id=15884 +pure nothrow @safe unittest { import std.typecons; void Test(C)() { C[] a = ['x']; JSONValue testVal = a; - assert(testVal.type == JSON_TYPE.STRING); + assert(testVal.type == JSONType.string); testVal = a.idup; - assert(testVal.type == JSON_TYPE.STRING); + assert(testVal.type == JSONType.string); } Test!char(); Test!wchar(); Test!dchar(); } -@safe unittest // issue 15885 +// https://issues.dlang.org/show_bug.cgi?id=15885 +@safe unittest { enum bool realInDoublePrecision = real.mant_dig == double.mant_dig; static bool test(const double num0) { - import std.math : feqrel; + import std.math.operations : feqrel; const json0 = JSONValue(num0); const num1 = to!double(toJSON(json0)); static if (realInDoublePrecision) @@ -1802,34 +2215,39 @@ pure nothrow @safe unittest // issue 15884 assert(test(3*minSub)); } -@safe unittest // issue 17555 +// https://issues.dlang.org/show_bug.cgi?id=17555 +@safe unittest { import std.exception : assertThrown; assertThrown!JSONException(parseJSON("\"a\nb\"")); } -@safe unittest // issue 17556 +// https://issues.dlang.org/show_bug.cgi?id=17556 +@safe unittest { auto v = JSONValue("\U0001D11E"); auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars); assert(j == `"\uD834\uDD1E"`); } -@safe unittest // issue 5904 +// https://issues.dlang.org/show_bug.cgi?id=5904 +@safe unittest { string s = `"\uD834\uDD1E"`; auto j = parseJSON(s); assert(j.str == "\U0001D11E"); } -@safe unittest // issue 17557 +// https://issues.dlang.org/show_bug.cgi?id=17557 +@safe unittest { assert(parseJSON("\"\xFF\"").str == "\xFF"); assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E"); } -@safe unittest // issue 17553 +// https://issues.dlang.org/show_bug.cgi?id=17553 +@safe unittest { auto v = JSONValue("\xFF"); assert(toJSON(v) == "\"\xFF\""); @@ -1842,10 +2260,145 @@ pure nothrow @safe unittest // issue 15884 assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E"); } -@safe unittest // JSONOptions.doNotEscapeSlashes (issue 17587) +// JSONOptions.doNotEscapeSlashes (https://issues.dlang.org/show_bug.cgi?id=17587) +@safe unittest { assert(parseJSON(`"/"`).toString == `"\/"`); assert(parseJSON(`"\/"`).toString == `"\/"`); assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); } + +// JSONOptions.strictParsing (https://issues.dlang.org/show_bug.cgi?id=16639) +@safe unittest +{ + import std.exception : assertThrown; + + // Unescaped ASCII NULs + assert(parseJSON("[\0]").type == JSONType.array); + assertThrown!JSONException(parseJSON("[\0]", JSONOptions.strictParsing)); + assert(parseJSON("\"\0\"").str == "\0"); + assertThrown!JSONException(parseJSON("\"\0\"", JSONOptions.strictParsing)); + + // Unescaped ASCII DEL (0x7f) in strings + assert(parseJSON("\"\x7f\"").str == "\x7f"); + assert(parseJSON("\"\x7f\"", JSONOptions.strictParsing).str == "\x7f"); + + // "true", "false", "null" case sensitivity + assert(parseJSON("true").type == JSONType.true_); + assert(parseJSON("true", JSONOptions.strictParsing).type == JSONType.true_); + assert(parseJSON("True").type == JSONType.true_); + assertThrown!JSONException(parseJSON("True", JSONOptions.strictParsing)); + assert(parseJSON("tRUE").type == JSONType.true_); + assertThrown!JSONException(parseJSON("tRUE", JSONOptions.strictParsing)); + + assert(parseJSON("false").type == JSONType.false_); + assert(parseJSON("false", JSONOptions.strictParsing).type == JSONType.false_); + assert(parseJSON("False").type == JSONType.false_); + assertThrown!JSONException(parseJSON("False", JSONOptions.strictParsing)); + assert(parseJSON("fALSE").type == JSONType.false_); + assertThrown!JSONException(parseJSON("fALSE", JSONOptions.strictParsing)); + + assert(parseJSON("null").type == JSONType.null_); + assert(parseJSON("null", JSONOptions.strictParsing).type == JSONType.null_); + assert(parseJSON("Null").type == JSONType.null_); + assertThrown!JSONException(parseJSON("Null", JSONOptions.strictParsing)); + assert(parseJSON("nULL").type == JSONType.null_); + assertThrown!JSONException(parseJSON("nULL", JSONOptions.strictParsing)); + + // Whitespace characters + assert(parseJSON("[\f\v]").type == JSONType.array); + assertThrown!JSONException(parseJSON("[\f\v]", JSONOptions.strictParsing)); + assert(parseJSON("[ \t\r\n]").type == JSONType.array); + assert(parseJSON("[ \t\r\n]", JSONOptions.strictParsing).type == JSONType.array); + + // Empty input + assert(parseJSON("").type == JSONType.null_); + assertThrown!JSONException(parseJSON("", JSONOptions.strictParsing)); + + // Numbers with leading '0's + assert(parseJSON("01").integer == 1); + assertThrown!JSONException(parseJSON("01", JSONOptions.strictParsing)); + assert(parseJSON("-01").integer == -1); + assertThrown!JSONException(parseJSON("-01", JSONOptions.strictParsing)); + assert(parseJSON("0.01").floating == 0.01); + assert(parseJSON("0.01", JSONOptions.strictParsing).floating == 0.01); + assert(parseJSON("0e1").floating == 0); + assert(parseJSON("0e1", JSONOptions.strictParsing).floating == 0); + + // Trailing characters after JSON value + assert(parseJSON(`""asdf`).str == ""); + assertThrown!JSONException(parseJSON(`""asdf`, JSONOptions.strictParsing)); + assert(parseJSON("987\0").integer == 987); + assertThrown!JSONException(parseJSON("987\0", JSONOptions.strictParsing)); + assert(parseJSON("987\0\0").integer == 987); + assertThrown!JSONException(parseJSON("987\0\0", JSONOptions.strictParsing)); + assert(parseJSON("[]]").type == JSONType.array); + assertThrown!JSONException(parseJSON("[]]", JSONOptions.strictParsing)); + assert(parseJSON("123 \t\r\n").integer == 123); // Trailing whitespace is OK + assert(parseJSON("123 \t\r\n", JSONOptions.strictParsing).integer == 123); +} + +@system unittest +{ + import std.algorithm.iteration : map; + import std.array : array; + import std.exception : assertThrown; + + string s = `{ "a" : [1,2,3,], }`; + JSONValue j = parseJSON(s); + assert(j["a"].array().map!(i => i.integer()).array == [1,2,3]); + + assertThrown(parseJSON(s, -1, JSONOptions.strictParsing)); +} + +@system unittest +{ + import std.algorithm.iteration : map; + import std.array : array; + import std.exception : assertThrown; + + string s = `{ "a" : { } , }`; + JSONValue j = parseJSON(s); + assert("a" in j); + auto t = j["a"].object(); + assert(t.empty); + + assertThrown(parseJSON(s, -1, JSONOptions.strictParsing)); +} + +// https://issues.dlang.org/show_bug.cgi?id=20330 +@safe unittest +{ + import std.array : appender; + + string s = `{"a":[1,2,3]}`; + JSONValue j = parseJSON(s); + + auto app = appender!string(); + j.toString(app); + + assert(app.data == s, app.data); +} + +// https://issues.dlang.org/show_bug.cgi?id=20330 +@safe unittest +{ + import std.array : appender; + import std.format.write : formattedWrite; + + string s = +`{ + "a": [ + 1, + 2, + 3 + ] +}`; + JSONValue j = parseJSON(s); + + auto app = appender!string(); + j.toPrettyString(app); + + assert(app.data == s, app.data); +} diff --git a/libphobos/src/std/math.d b/libphobos/src/std/math.d deleted file mode 100644 index 336c11a..0000000 --- a/libphobos/src/std/math.d +++ /dev/null @@ -1,8586 +0,0 @@ -// 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 (S390) version = IBMZ_Any; -version (SPARC) version = SPARC_Any; -version (SPARC64) version = SPARC_Any; -version (SystemZ) version = IBMZ_Any; -version (RISCV32) version = RISCV_Any; -version (RISCV64) version = RISCV_Any; - -version (D_InlineAsm_X86) version = InlineAsm_X86_Any; -version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; - -version (InlineAsm_X86_Any) version = InlineAsm_X87; -version (InlineAsm_X87) -{ - static assert(real.mant_dig == 64); - version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC; -} - -version (X86_64) version = StaticallyHaveSSE; -version (X86) version (OSX) version = StaticallyHaveSSE; - -version (StaticallyHaveSSE) -{ - private enum bool haveSSE = true; -} -else version (X86) -{ - static import core.cpuid; - private alias haveSSE = core.cpuid.sse; -} - -version (D_SoftFloat) -{ - // Some soft float implementations may support IEEE floating flags. - // The implementation here supports hardware flags only and is so currently - // only available for supported targets. -} -else version (X86_Any) version = IeeeFlagsSupport; -else version (PPC_Any) version = IeeeFlagsSupport; -else version (RISCV_Any) version = IeeeFlagsSupport; -else version (MIPS_Any) version = IeeeFlagsSupport; -else version (ARM_Any) version = IeeeFlagsSupport; - -// Struct FloatingPointControl is only available if hardware FP units are available. -version (D_HardFloat) -{ - // FloatingPointControl.clearExceptions() depends on version IeeeFlagsSupport - version (IeeeFlagsSupport) version = FloatingPointControlSupport; -} - -version (GNU) -{ - // The compiler can unexpectedly rearrange floating point operations and - // access to the floating point status flags when optimizing. This means - // ieeeFlags tests cannot be reliably checked in optimized code. - // See https://github.com/ldc-developers/ldc/issues/888 -} -else -{ - version = IeeeFlagsUnittest; - version = FloatingPointControlUnittest; -} - -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, is(real_t == real) ? "%.*Lg" : "%.*g", ndigits, cast(real_t) x); - iy = sprintf(bufy.ptr, is(real_t == real) ? "%.*Lg" : "%.*g", 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; - - // For IBM doubledouble the larger magnitude double comes first. - // It's really a double[2] and arrays don't index differently - // between little and big-endian targets. - enum DOUBLEPAIR_MSB = 0; - enum DOUBLEPAIR_LSB = 1; - - // The exponent/sign byte is for most significant part. - version (LittleEndian) - { - enum EXPPOS_SHORT = 3; - enum SIGNPOS_BYTE = 7; - } - else - { - enum EXPPOS_SHORT = 0; - 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; - - // Other kinds of extractors for real formats. - static if (F.realFormat == RealFormat.ieeeSingle) - int vi; - } - floatBits y = void; - y.rv = x; - - // Find the exponent (power of 2) - // Do this by shifting the raw value so that the exponent lies in the low bits, - // then mask out the sign bit, and subtract the bias. - static if (F.realFormat == RealFormat.ieeeSingle) - { - int exp = ((y.vi >> (T.mant_dig - 1)) & 0xff) - 0x7f; - } - 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - 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; - } - - static if (F.realFormat == RealFormat.ieeeSingle) - { - if (exp < (T.mant_dig - 1)) - { - // Clear all bits representing the fraction part. - const uint fraction_mask = F.MANTISSAMASK_INT >> exp; - - if ((y.vi & fraction_mask) != 0) - { - // If 'x' is negative, then first substract 1.0 from the value. - if (y.vi < 0) - y.vi += 0x00800000 >> exp; - y.vi &= ~fraction_mask; - } - } - } - else - { - static if (F.realFormat == RealFormat.ieeeExtended53) - exp = (T.mant_dig + 11 - 1) - exp; // mant_dig is really 64 - else - 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); - assert(approxEqual(r, t)); - - x = -x; - r = -r; - t = tan(x); - //printf("tan(%Lg) = %Lg, should be %Lg\n", x, t, r); - assert(approxEqual(r, t)); - } - // 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(asin(0.5).approxEqual(PI / 6)); -} - -/*************** - * 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(atan2(1.0, sqrt(3.0)).approxEqual(PI / 6)); -} - -/*********************************** - * 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(sinh(1.0).approxEqual((E - 1.0 / E) / 2)); -} - -/*********************************** - * 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - // 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) - return real.infinity; - if (x < UF) - return 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(exp(3.0).feqrel(E * E * E) > 16); -} - -/** - * 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) - return 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) - return real.infinity; - if (x < UF) - return 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 -{ - version (FloatingPointControlSupport) - { - FloatingPointControl ctrl; - if (FloatingPointControl.hasExceptionTraps) - ctrl.disableExceptions(FloatingPointControl.allExceptions); - ctrl.rounding = FloatingPointControl.roundToNearest; - } - - enum realFormat = floatTraits!real.realFormat; - static if (realFormat == RealFormat.ieeeQuadruple) - { - 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 (realFormat == RealFormat.ieeeExtended || - realFormat == RealFormat.ieeeExtended53) - { - 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 (realFormat == RealFormat.ieeeDouble) - { - 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 minEqualMantissaBits = real.mant_dig - 13; - real x; - version (IeeeFlagsSupport) IeeeFlags f; - foreach (ref pair; exptestpoints) - { - version (IeeeFlagsSupport) resetIeeeFlags(); - x = exp(pair[0]); - assert(feqrel(x, pair[1]) >= minEqualMantissaBits); - } - - // 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. - version (IeeeFlagsSupport) - { - 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); - } - else - { - x = exp(real.nan); - assert(isIdentical(abs(x), real.nan)); - - x = exp(-real.nan); - assert(isIdentical(abs(x), real.nan)); - } - - 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - 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 || - floatTraits!(real).realFormat == RealFormat.ieeeExtended53 || - floatTraits!(real).realFormat == RealFormat.ieeeQuadruple) - { - 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.0L); - 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 (MSVC_InlineAsm) - { - 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 (MSVC_InlineAsm) - { - 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 (MSVC_InlineAsm) - { - 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - long result; - - // Rounding limit when casting from real(80-bit) to ulong. - static if (F.realFormat == RealFormat.ieeeExtended) - enum real OF = 9.22337203685477580800E18L; - else - enum real OF = 4.50359962737049600000E15L; - - 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 exp = (vu[F.EXPPOS_SHORT] & F.EXPMASK) - (F.EXPBIAS + 1); - const implicitOne = 1UL << 48; - auto vl = cast(ulong*)(&x); - vl[MANTISSA_MSB] &= implicitOne - 1; - vl[MANTISSA_MSB] |= implicitOne; - - long result; - - 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 not implemented for Digital Mars C runtime.) - */ -long lround(real x) @trusted nothrow @nogc -{ - version (CRuntime_DigitalMars) - assert(0, "lround not implemented"); - else - return core.stdc.math.llroundl(x); -} - -/// -@safe nothrow @nogc unittest -{ - version (CRuntime_DigitalMars) {} - else - { - 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 (MSVC_InlineAsm) - { - 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"); -} - - -version (IeeeFlagsSupport) -{ - -/** 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). - // The RISC-V (32 & 64 bit) fcsr is 32-bit register. - 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 version (RISCV_Any) - { - version (D_SoftFloat) - return 0; - else - { - uint result = void; - asm pure nothrow @nogc - { - "frflags %0" : "=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 nothrow @nogc - { - "fnclex"; - } - - // Also clear exception flags in MXCSR, SSE's control register. - if (haveSSE) - { - uint mxcsr; - asm nothrow @nogc - { - "stmxcsr %0" : "=m" (mxcsr); - } - mxcsr &= ~EXCEPTIONS_MASK; - asm 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 nothrow @nogc - { - "vmsr FPSCR, %0" : : "r" (old); - } - } - } - else version (RISCV_Any) - { - version (D_SoftFloat) - return; - else - { - uint newValues = 0x0; - asm nothrow @nogc - { - "fsflags %0" : : "r" (newValues); - } - } - } - else - assert(0, "Not yet supported"); - } - else - version (InlineAsm_X86_Any) - { - asm 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 (IeeeFlagsUnittest) -@system unittest -{ - static void func() { - int a = 10 * 10; - } - pragma(inline, false) static void blockopt(ref real x) {} - real a = 3.5; - // Set all the flags to zero - resetIeeeFlags(); - assert(!ieeeFlags.divByZero); - blockopt(a); // avoid constant propagation by the optimizer - // Perform a division by zero. - a /= 0.0L; - assert(a == real.infinity); - assert(ieeeFlags.divByZero); - blockopt(a); // avoid constant propagation by the optimizer - // 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 (IeeeFlagsUnittest) -@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()); - } - } -} - -/// 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()); -} - -} // IeeeFlagsSupport - - -version (FloatingPointControlSupport) -{ - -/** 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 (PPC_Any) - { - enum : ExceptionMask - { - inexactException = 0x0008, - divByZeroException = 0x0010, - underflowException = 0x0020, - overflowException = 0x0040, - invalidException = 0x0080, - severeExceptions = overflowException | divByZeroException - | invalidException, - allExceptions = severeExceptions | underflowException - | inexactException, - } - } - else version (HPPA) - { - enum : ExceptionMask - { - inexactException = 0x01, - underflowException = 0x02, - overflowException = 0x04, - divByZeroException = 0x08, - invalidException = 0x10, - severeExceptions = overflowException | divByZeroException - | invalidException, - allExceptions = severeExceptions | underflowException - | inexactException, - } - } - else version (MIPS_Any) - { - enum : ExceptionMask - { - inexactException = 0x0080, - divByZeroException = 0x0400, - overflowException = 0x0200, - underflowException = 0x0100, - invalidException = 0x0800, - severeExceptions = overflowException | divByZeroException - | invalidException, - allExceptions = severeExceptions | underflowException - | inexactException, - } - } - else version (SPARC_Any) - { - enum : ExceptionMask - { - inexactException = 0x0800000, - divByZeroException = 0x1000000, - overflowException = 0x4000000, - underflowException = 0x2000000, - invalidException = 0x8000000, - severeExceptions = overflowException | divByZeroException - | invalidException, - allExceptions = severeExceptions | underflowException - | inexactException, - } - } - else version (IBMZ_Any) - { - enum : ExceptionMask - { - inexactException = 0x08000000, - divByZeroException = 0x40000000, - overflowException = 0x20000000, - underflowException = 0x10000000, - invalidException = 0x80000000, - severeExceptions = overflowException | divByZeroException - | invalidException, - allExceptions = severeExceptions | underflowException - | inexactException, - } - } - else version (RISCV_Any) - { - enum : ExceptionMask - { - inexactException = 0x01, - divByZeroException = 0x02, - underflowException = 0x04, - overflowException = 0x08, - invalidException = 0x10, - 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 (HPPA) - { - alias ControlState = uint; - } - else version (PPC_Any) - { - alias ControlState = uint; - } - else version (MIPS_Any) - { - alias ControlState = uint; - } - else version (SPARC_Any) - { - alias ControlState = ulong; - } - else version (IBMZ_Any) - { - alias ControlState = uint; - } - else version (RISCV_Any) - { - 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 - { - version (IeeeFlagsSupport) - resetIeeeFlags(); - else - static assert(false, "Not implemented for this architecture"); - } - - // 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) - { - ControlState cont; - 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 version (RISCV_Any) - { - version (D_SoftFloat) - return 0; - else - { - ControlState cont; - asm pure nothrow @nogc - { - "frcsr %0" : "=r" (cont); - } - return cont; - } - } - else - assert(0, "Not yet supported"); - } - else - version (D_InlineAsm_X86) - { - short cont; - asm pure nothrow @nogc - { - xor EAX, EAX; - fstcw cont; - } - return cont; - } - else - version (D_InlineAsm_X86_64) - { - short cont; - asm pure 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 nothrow @nogc - { - "fclex; fldcw %0" : : "m" (newState); - } - - // Also update MXCSR, SSE's control register. - if (haveSSE) - { - uint mxcsr; - asm 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 nothrow @nogc - { - "ldmxcsr %0" : : "m" (mxcsr); - } - } - } - else version (AArch64) - { - asm nothrow @nogc - { - "msr FPCR, %0;" : : "r" (newState); - } - } - else version (ARM) - { - version (ARM_SoftFloat) - return; - else - { - asm nothrow @nogc - { - "vmsr FPSCR, %0" : : "r" (newState); - } - } - } - else version (RISCV_Any) - { - version (D_SoftFloat) - return; - else - { - asm nothrow @nogc - { - "fscsr %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 -{ - void ensureDefaults() - { - assert(FloatingPointControl.rounding - == FloatingPointControl.roundToNearest); - if (FloatingPointControl.hasExceptionTraps) - assert(FloatingPointControl.enabledExceptions == 0); - } - - { - FloatingPointControl ctrl; - } - ensureDefaults(); - - { - 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(); -} - -version (FloatingPointControlUnittest) -@system unittest // rounding -{ - import std.meta : AliasSeq; - - foreach (T; AliasSeq!(float, double, real)) - { - /* Be careful with changing the rounding mode, it interferes - * with common subexpressions. Changing rounding modes should - * be done with separate functions that are not inlined. - */ - - { - static T addRound(T)(uint rm) - { - pragma(inline, false) static void blockopt(ref T x) {} - pragma(inline, false); - FloatingPointControl fpctrl; - fpctrl.rounding = rm; - T x = 1; - blockopt(x); // avoid constant propagation by the optimizer - x += 0.1; - return x; - } - - T u = addRound!(T)(FloatingPointControl.roundUp); - T d = addRound!(T)(FloatingPointControl.roundDown); - T z = addRound!(T)(FloatingPointControl.roundToZero); - - assert(u > d); - assert(z == d); - } - - { - static T subRound(T)(uint rm) - { - pragma(inline, false) static void blockopt(ref T x) {} - pragma(inline, false); - FloatingPointControl fpctrl; - fpctrl.rounding = rm; - T x = -1; - blockopt(x); // avoid constant propagation by the optimizer - x -= 0.1; - return x; - } - - T u = subRound!(T)(FloatingPointControl.roundUp); - T d = subRound!(T)(FloatingPointControl.roundDown); - T z = subRound!(T)(FloatingPointControl.roundToZero); - - assert(u > d); - assert(z == u); - } - } -} - -} // FloatingPointControlSupport - - -/********************************* - * 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - // 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - // For 80-bit reals, the "implied bit" is a nuisance... - ushort *pe = cast(ushort *)&x; - ulong *ps = cast(ulong *)&x; - // EPSILON is 1 for 64-bit, and 2048 for 53-bit precision reals. - enum ulong EPSILON = 2UL ^^ (64 - real.mant_dig); - - 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 -= EPSILON; - // 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 += EPSILON; - 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); - - // Test disabled on most targets. - // See https://issues.dlang.org/show_bug.cgi?id=5628 - version (X86_64) enum BUG5628 = false; - else version (ARM) enum BUG5628 = false; - else version (GNU) enum BUG5628 = false; - else enum BUG5628 = true; - - static if (BUG5628) - { - 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.ieeeExtended53 - || 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 || - F.realFormat == RealFormat.ieeeExtended53) - { - // 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 version (DragonFlyBSD) - { - 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/math/algebraic.d b/libphobos/src/std/math/algebraic.d new file mode 100644 index 0000000..ae7cbf9 --- /dev/null +++ b/libphobos/src/std/math/algebraic.d @@ -0,0 +1,1072 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains classical algebraic functions like `abs`, `sqrt`, and `poly`. + +Copyright: Copyright The D Language Foundation 2000 - 2011. +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/algebraic.d) + +Macros: + TABLE_SV = + + $0
Special Values
+ NAN = $(RED NAN) + POWER = $1$2 + SUB = $1$2 + PLUSMN = ± + INFIN = ∞ + PLUSMNINF = ±∞ + LT = < + + */ + +module std.math.algebraic; + +static import core.math; +static import core.stdc.math; +import std.traits : CommonType, isFloatingPoint, isIntegral, isSigned, Unqual; + +/*********************************** + * Calculates the absolute value of a number. + * + * Params: + * Num = (template parameter) type of number + * x = real number value + * + * Returns: + * The absolute value of the number. If floating-point or integral, + * the return type will be the same as the input. + * + * Limitations: + * Does not work correctly for signed intergal types and value `Num`.min. + */ +auto abs(Num)(Num x) @nogc pure nothrow +if ((is(immutable Num == immutable short) || is(immutable Num == immutable byte)) || + (is(typeof(Num.init >= 0)) && is(typeof(-Num.init)))) +{ + static if (isFloatingPoint!(Num)) + return fabs(x); + else + { + static if (is(immutable Num == immutable short) || is(immutable Num == immutable byte)) + return x >= 0 ? x : cast(Num) -int(x); + else + return x >= 0 ? x : -x; + } +} + +/// ditto +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isIdentical, isNaN; + + assert(isIdentical(abs(-0.0L), 0.0L)); + assert(isNaN(abs(real.nan))); + assert(abs(-real.infinity) == real.infinity); + assert(abs(-56) == 56); + assert(abs(2321312L) == 2321312L); +} + +@safe pure nothrow @nogc unittest +{ + short s = -8; + byte b = -8; + assert(abs(s) == 8); + assert(abs(b) == 8); + immutable(byte) c = -8; + assert(abs(c) == 8); +} + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + static foreach (T; AliasSeq!(float, double, real)) + {{ + T f = 3; + assert(abs(f) == f); + assert(abs(-f) == f); + }} +} + +// see https://issues.dlang.org/show_bug.cgi?id=20205 +// to avoid falling into the trap again +@safe pure nothrow @nogc unittest +{ + assert(50 - abs(-100) == -50); +} + +// https://issues.dlang.org/show_bug.cgi?id=19162 +@safe unittest +{ + struct Vector(T, int size) + { + T x, y, z; + } + + static auto abs(T, int size)(auto ref const Vector!(T, size) v) + { + return v; + } + Vector!(int, 3) v; + assert(abs(v) == v); +} + +/******************************* + * Returns |x| + * + * $(TABLE_SV + * $(TR $(TH x) $(TH fabs(x))) + * $(TR $(TD $(PLUSMN)0.0) $(TD +0.0) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) ) + * ) + */ +pragma(inline, true) +real fabs(real x) @safe pure nothrow @nogc { return core.math.fabs(x); } + +///ditto +pragma(inline, true) +double fabs(double x) @safe pure nothrow @nogc { return core.math.fabs(x); } + +///ditto +pragma(inline, true) +float fabs(float x) @safe pure nothrow @nogc { return core.math.fabs(x); } + +/// +@safe unittest +{ + import std.math.traits : isIdentical; + + assert(isIdentical(fabs(0.0f), 0.0f)); + assert(isIdentical(fabs(-0.0f), 0.0f)); + assert(fabs(-10.0f) == 10.0f); + + assert(isIdentical(fabs(0.0), 0.0)); + assert(isIdentical(fabs(-0.0), 0.0)); + assert(fabs(-10.0) == 10.0); + + assert(isIdentical(fabs(0.0L), 0.0L)); + assert(isIdentical(fabs(-0.0L), 0.0L)); + assert(fabs(-10.0L) == 10.0L); +} + +@safe unittest +{ + real function(real) pfabs = &fabs; + assert(pfabs != null); +} + +@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); +} + +/*************************************** + * 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)) + * ) + */ +pragma(inline, true) +float sqrt(float x) @nogc @safe pure nothrow { return core.math.sqrt(x); } + +/// ditto +pragma(inline, true) +double sqrt(double x) @nogc @safe pure nothrow { return core.math.sqrt(x); } + +/// ditto +pragma(inline, true) +real sqrt(real x) @nogc @safe pure nothrow { return core.math.sqrt(x); } + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : feqrel; + import std.math.traits : isNaN; + + assert(sqrt(2.0).feqrel(1.4142) > 16); + assert(sqrt(9.0).feqrel(3.0) > 16); + + assert(isNaN(sqrt(-1.0f))); + assert(isNaN(sqrt(-1.0))); + assert(isNaN(sqrt(-1.0L))); +} + +@safe unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=5305 + float function(float) psqrtf = &sqrt; + assert(psqrtf != null); + double function(double) psqrtd = &sqrt; + assert(psqrtd != null); + real function(real) psqrtr = &sqrt; + assert(psqrtr != null); + + //ctfe + enum ZX80 = sqrt(7.0f); + enum ZX81 = sqrt(7.0); + enum ZX82 = sqrt(7.0L); +} + +@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); +} + +/*************** + * 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) + { + import std.math.traits : copysign; + import std.math.exponential : exp2; + + 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); +} + +/// +@safe unittest +{ + import std.math.operations : feqrel; + + assert(cbrt(1.0).feqrel(1.0) > 16); + assert(cbrt(27.0).feqrel(3.0) > 16); + assert(cbrt(15.625).feqrel(2.5) > 16); +} + +/*********************************************************************** + * 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)) + * ) + */ +T hypot(T)(const T x, const T y) @safe pure nothrow @nogc +if (isFloatingPoint!T) +{ + // 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 2^^-N. + // If both are tiny, avoid underflow by scaling by 2^^N. + import core.math : fabs, sqrt; + import std.math : floatTraits, RealFormat; + + alias F = floatTraits!T; + + T u = fabs(x); + T v = fabs(y); + if (!(u >= v)) // check for NaN as well. + { + v = u; + u = fabs(y); + if (u == T.infinity) return u; // hypot(inf, nan) == inf + if (v == T.infinity) return v; // hypot(nan, inf) == inf + } + + static if (F.realFormat == RealFormat.ieeeSingle) + { + enum SQRTMIN = 0x1p-60f; + enum SQRTMAX = 0x1p+60f; + enum SCALE_UNDERFLOW = 0x1p+90f; + enum SCALE_OVERFLOW = 0x1p-90f; + } + else static if (F.realFormat == RealFormat.ieeeDouble || + F.realFormat == RealFormat.ieeeExtended53 || + F.realFormat == RealFormat.ibmExtended) + { + enum SQRTMIN = 0x1p-450L; + enum SQRTMAX = 0x1p+500L; + enum SCALE_UNDERFLOW = 0x1p+600L; + enum SCALE_OVERFLOW = 0x1p-600L; + } + else static if (F.realFormat == RealFormat.ieeeExtended || + F.realFormat == RealFormat.ieeeQuadruple) + { + enum SQRTMIN = 0x1p-8000L; + enum SQRTMAX = 0x1p+8000L; + enum SCALE_UNDERFLOW = 0x1p+10000L; + enum SCALE_OVERFLOW = 0x1p-10000L; + } + else + assert(0, "hypot not implemented"); + + // Now u >= v, or else one is NaN. + T ratio = 1.0; + if (v >= SQRTMAX) + { + // hypot(huge, huge) -- avoid overflow + ratio = SCALE_UNDERFLOW; + u *= SCALE_OVERFLOW; + v *= SCALE_OVERFLOW; + } + else if (u <= SQRTMIN) + { + // hypot (tiny, tiny) -- avoid underflow + // This is only necessary to avoid setting the underflow + // flag. + ratio = SCALE_OVERFLOW; + u *= SCALE_UNDERFLOW; + v *= SCALE_UNDERFLOW; + } + + if (u * T.epsilon > v) + { + // hypot (huge, tiny) = huge + return u; + } + + // both are in the normal range + return ratio * sqrt(u*u + v*v); +} + +/// +@safe unittest +{ + import std.math.operations : feqrel; + + assert(hypot(1.0, 1.0).feqrel(1.4142) > 16); + assert(hypot(3.0, 4.0).feqrel(5.0) > 16); + assert(hypot(real.infinity, 1.0L) == real.infinity); + assert(hypot(real.infinity, real.nan) == real.infinity); +} + +@safe unittest +{ + import std.math.operations : feqrel; + + assert(hypot(1.0f, 1.0f).feqrel(1.4142f) > 16); + assert(hypot(3.0f, 4.0f).feqrel(5.0f) > 16); + assert(hypot(float.infinity, 1.0f) == float.infinity); + assert(hypot(float.infinity, float.nan) == float.infinity); + + assert(hypot(1.0L, 1.0L).feqrel(1.4142L) > 16); + assert(hypot(3.0L, 4.0L).feqrel(5.0L) > 16); + assert(hypot(double.infinity, 1.0) == double.infinity); + assert(hypot(double.infinity, double.nan) == double.infinity); +} + +@safe unittest +{ + import std.math.operations : feqrel; + import std.math.traits : isIdentical; + import std.meta : AliasSeq; + + static foreach (T; AliasSeq!(float, double, real)) + {{ + static T[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*T.epsilon, 9.0], + [88/(64*sqrt(T.min_normal)), 105/(64*sqrt(T.min_normal)), 137/(64*sqrt(T.min_normal))], + [88/(128*sqrt(T.min_normal)), 105/(128*sqrt(T.min_normal)), 137/(128*sqrt(T.min_normal))], + [3*T.min_normal*T.epsilon, 4*T.min_normal*T.epsilon, 5*T.min_normal*T.epsilon], + [ T.min_normal, T.min_normal, sqrt(2.0L)*T.min_normal], + [ T.max/sqrt(2.0L), T.max/sqrt(2.0L), T.max], + [ T.infinity, T.nan, T.infinity], + [ T.nan, T.infinity, T.infinity], + [ T.nan, T.nan, T.nan], + [ T.nan, T.max, T.nan], + [ T.max, T.nan, T.nan], + ]; + for (int i = 0; i < vals.length; i++) + { + T x = vals[i][0]; + T y = vals[i][1]; + T z = vals[i][2]; + T h = hypot(x, y); + assert(isIdentical(z,h) || feqrel(z, h) >= T.mant_dig - 1); + } + }} +} + +/*********************************************************************** + * Calculates the distance of the point (x, y, z) from the origin (0, 0, 0) + * in three-dimensional space. + * The distance is the value of the square root of the sums of the squares + * of x, y, and z: + * + * sqrt($(POWER x, 2) + $(POWER y, 2) + $(POWER z, 2)) + * + * Note that the distance between two points (x1, y1, z1) and (x2, y2, z2) + * in three-dimensional space can be calculated as hypot(x2-x1, y2-y1, z2-z1). + * + * Params: + * x = floating point value + * y = floating point value + * z = floating point value + * + * Returns: + * The square root of the sum of the squares of the given arguments. + */ +T hypot(T)(const T x, const T y, const T z) @safe pure nothrow @nogc +if (isFloatingPoint!T) +{ + import core.math : fabs, sqrt; + import std.math.operations : fmax; + const absx = fabs(x); + const absy = fabs(y); + const absz = fabs(z); + + // Scale all parameters to avoid overflow. + const ratio = fmax(absx, fmax(absy, absz)); + if (ratio == 0.0) + return ratio; + + return ratio * sqrt((absx / ratio) * (absx / ratio) + + (absy / ratio) * (absy / ratio) + + (absz / ratio) * (absz / ratio)); +} + +/// +@safe unittest +{ + import std.math.operations : isClose; + + assert(isClose(hypot(1.0, 2.0, 2.0), 3.0)); + assert(isClose(hypot(2.0, 3.0, 6.0), 7.0)); + assert(isClose(hypot(1.0, 4.0, 8.0), 9.0)); +} + +@safe unittest +{ + import std.meta : AliasSeq; + import std.math.traits : isIdentical; + import std.math.operations : isClose; + static foreach (T; AliasSeq!(float, double, real)) + {{ + static T[4][] vals = [ + [ 0.0L, 0.0L, 0.0L, 0.0L ], + [ 0.0L, 1.0L, 1.0L, sqrt(2.0L) ], + [ 1.0L, 1.0L, 1.0L, sqrt(3.0L) ], + [ 1.0L, 2.0L, 2.0L, 3.0L ], + [ 2.0L, 3.0L, 6.0L, 7.0L ], + [ 1.0L, 4.0L, 8.0L, 9.0L ], + [ 4.0L, 4.0L, 7.0L, 9.0L ], + [ 12.0L, 16.0L, 21.0L, 29.0L ], + [ 1e+8L, 1.0L, 1e-8L, 1e+8L ], + [ 1.0L, 1e+8L, 1e-8L, 1e+8L ], + [ 1e-8L, 1.0L, 1e+8L, 1e+8L ], + [ 1e-2L, 1e-4L, 1e-4L, 0.010000999950004999375L ], + [ 2147483647.0L, 2147483647.0L, 2147483647.0L, 3719550785.027307813987L ] + ]; + for (int i = 0; i < vals.length; i++) + { + T x = vals[i][0]; + T y = vals[i][1]; + T z = vals[i][2]; + T r = vals[i][3]; + T a = hypot(x, y, z); + assert(isIdentical(r, a) || isClose(r, a)); + } + }} +} + +/*********************************** + * 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); +} +do +{ + static if (is(immutable T2 == immutable real)) + { + return polyImpl(x, A); + } + else + { + return polyImplBase(x, A); + } +} + +/// ditto +Unqual!(CommonType!(T1, T2)) poly(T1, T2, int N)(T1 x, ref const T2[N] A) @safe pure nothrow @nogc +if (isFloatingPoint!T1 && isFloatingPoint!T2 && N > 0 && N <= 10) +{ + // statically unrolled version for up to 10 coefficients + typeof(return) r = A[N - 1]; + static foreach (i; 1 .. N) + { + r *= x; + r += A[N - 1 - i]; + } + return r; +} + +/// +@safe nothrow @nogc unittest +{ + real x = 3.1L; + static real[] pp = [56.1L, 32.7L, 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 version (DragonFlyBSD) + { + 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); + } +} + +/** + * Gives the next power of two after `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 `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.math.traits : isNaN; + import std.meta : AliasSeq; + + static 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); + }} +} + +// https://issues.dlang.org/show_bug.cgi?id=15973 +@safe @nogc pure nothrow unittest +{ + 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 `val`. $(T) can be any built-in + * numerical type. + * + * Params: + * val = any number + * + * Returns: + * the last power of two before `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.math.traits : isNaN; + import std.meta : AliasSeq; + + static 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); + } +} + +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) +{ + import std.math.traits : copysign, isFinite; + import std.math.exponential : frexp; + + if (!x.isFinite) + return x; + + if (!x) + return x; + + int exp; + auto y = frexp(x, exp); + + static if (type == PowType.ceil) + y = core.math.ldexp(cast(T) 0.5, exp + 1); + else + y = core.math.ldexp(cast(T) 0.5, exp); + + if (!y.isFinite) + return cast(T) 0.0; + + y = copysign(y, x); + + return y; +} diff --git a/libphobos/src/std/math/constants.d b/libphobos/src/std/math/constants.d new file mode 100644 index 0000000..0c0da0d --- /dev/null +++ b/libphobos/src/std/math/constants.d @@ -0,0 +1,38 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains several useful mathematical constants. + +Copyright: Copyright The D Language Foundation 2000 - 2011. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP digitalmars.com, Walter Bright), Don Clugston +Source: $(PHOBOSSRC std/math/constants.d) + +Macros: + SUB = $1$2 + PI = π + SQRT = √ + HALF = ½ + */ +module std.math.constants; + +// 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; /** π = 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. diff --git a/libphobos/src/std/math/exponential.d b/libphobos/src/std/math/exponential.d new file mode 100644 index 0000000..a9ec930 --- /dev/null +++ b/libphobos/src/std/math/exponential.d @@ -0,0 +1,3439 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains several exponential and logarithm functions. + +Copyright: Copyright The D Language Foundation 2000 - 2011. + D implementations of exp, expm1, exp2, log, log10, log1p, and log2 + 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/exponential.d) + +Macros: + TABLE_SV = + + $0
Special Values
+ NAN = $(RED NAN) + PLUSMN = ± + INFIN = ∞ + PLUSMNINF = ±∞ + LT = < + GT = > + */ + +module std.math.exponential; + +import std.traits : isFloatingPoint, isIntegral, isSigned, isUnsigned, Largest, Unqual; + +static import core.math; +static import core.stdc.math; + +version (DigitalMars) +{ + version = INLINE_YL2X; // x87 has opcodes for these +} + +version (D_InlineAsm_X86) version = InlineAsm_X86_Any; +version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; + +version (InlineAsm_X86_Any) version = InlineAsm_X87; +version (InlineAsm_X87) +{ + static assert(real.mant_dig == 64); + version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC; +} + +version (D_HardFloat) +{ + // FloatingPointControl.clearExceptions() depends on version IeeeFlagsSupport + version (IeeeFlagsSupport) version = FloatingPointControlSupport; +} + +/** + * 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) + { + if (n == -1) return 1 / x; + + 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 +{ + import std.math.operations : feqrel; + + assert(pow(2.0, 5) == 32.0); + assert(pow(1.5, 9).feqrel(38.4433) > 16); + assert(pow(real.nan, 2) is real.nan); + assert(pow(real.infinity, 2) == real.infinity); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose, feqrel; + + // 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); + + assert(isClose(pow(xd, neg2), cast(double) (1 / (x * x)), 1e-15)); + assert(isClose(pow(xf, neg8), cast(float) (1 / ((x * x) * (x * x) * (x * x) * (x * x))), 1e-15)); + + assert(feqrel(pow(x, neg3), 1 / (x * x * x)) >= real.mant_dig - 1); +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + + assert(isClose(pow(2.0L, 10L), 1024, 1e-18)); +} + +// https://issues.dlang.org/show_bug.cgi?id=21601 +@safe @nogc nothrow pure unittest +{ + // When reals are large enough the results of pow(b, e) can be + // calculated correctly, if b is of type float or double and e is + // not too large. + static if (real.mant_dig >= 64) + { + // expected result: 3.790e-42 + assert(pow(-513645318757045764096.0f, -2) > 0.0); + + // expected result: 3.763915357831797e-309 + assert(pow(-1.6299717435255677e+154, -2) > 0.0); + } +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + import std.math.traits : isInfinity; + + static float f1 = 19100.0f; + static float f2 = 0.000012f; + + assert(isClose(pow(f1,9), 3.3829868e+38f)); + assert(isInfinity(pow(f1,10))); + assert(pow(f2,9) > 0.0f); + assert(isClose(pow(f2,10), 0.0f, 0.0, float.min_normal)); + + static double d1 = 21800.0; + static double d2 = 0.000012; + + assert(isClose(pow(d1,71), 1.0725339442974e+308)); + assert(isInfinity(pow(d1,72))); + assert(pow(d2,65) > 0.0f); + assert(isClose(pow(d2,66), 0.0, 0.0, double.min_normal)); + + static if (real.mant_dig == 64) // x87 + { + static real r1 = 21950.0L; + static real r2 = 0.000011L; + + assert(isClose(pow(r1,1136), 7.4066175654969242752260330529e+4931L)); + assert(isInfinity(pow(r1,1137))); + assert(pow(r2,998) > 0.0L); + assert(isClose(pow(r2,999), 0.0L, 0.0, real.min_normal)); + } +} + +@safe @nogc nothrow pure unittest +{ + import std.math.operations : isClose; + + enum f1 = 19100.0f; + enum f2 = 0.000012f; + + static assert(isClose(pow(f1,9), 3.3829868e+38f)); + static assert(pow(f1,10) > float.max); + static assert(pow(f2,9) > 0.0f); + static assert(isClose(pow(f2,10), 0.0f, 0.0, float.min_normal)); + + enum d1 = 21800.0; + enum d2 = 0.000012; + + static assert(isClose(pow(d1,71), 1.0725339442974e+308)); + static assert(pow(d1,72) > double.max); + static assert(pow(d2,65) > 0.0f); + static assert(isClose(pow(d2,66), 0.0, 0.0, double.min_normal)); + + static if (real.mant_dig == 64) // x87 + { + enum r1 = 21950.0L; + enum r2 = 0.000011L; + + static assert(isClose(pow(r1,1136), 7.4066175654969242752260330529e+4931L)); + static assert(pow(r1,1137) > real.max); + static assert(pow(r2,998) > 0.0L); + static assert(isClose(pow(r2,999), 0.0L, 0.0, real.min_normal)); + } +} + +/** + * Compute the power of two integral numbers. + * + * Params: + * x = base + * n = exponent + * + * Returns: + * x raised to the power of n. If n is negative the result is 1 / pow(x, -n), + * which is calculated as integer division with remainder. This may result in + * a division by zero error. + * + * If both x and n are 0, the result is 1. + * + * Throws: + * If x is 0 and n is negative, the result is the same as the result of a + * division by zero. + */ +typeof(Unqual!(F).init * Unqual!(G).init) pow(F, G)(F x, G n) @nogc @trusted pure nothrow +if (isIntegral!(F) && isIntegral!(G)) +{ + import std.traits : isSigned; + + typeof(return) p, v = void; + Unqual!G m = n; + + static if (isSigned!(F)) + { + if (x == -1) return cast(typeof(return)) (m & 1 ? -1 : 1); + } + static if (isSigned!(G)) + { + if (x == 0 && m <= -1) return x / 0; + } + if (x == 1) return 1; + static if (isSigned!(G)) + { + if (m < 0) return 0; + } + + 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 +{ + assert(pow(2, 3) == 8); + assert(pow(3, 2) == 9); + + assert(pow(2, 10) == 1_024); + assert(pow(2, 20) == 1_048_576); + assert(pow(2, 30) == 1_073_741_824); + + assert(pow(0, 0) == 1); + + assert(pow(1, -5) == 1); + assert(pow(1, -6) == 1); + assert(pow(-1, -5) == -1); + assert(pow(-1, -6) == 1); + + assert(pow(-2, 5) == -32); + assert(pow(-2, -5) == 0); + assert(pow(cast(double) -2, -5) == -0.03125); +} + +@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); +} + +// https://issues.dlang.org/show_bug.cgi?id=7006 +@safe pure nothrow @nogc unittest +{ + assert(pow(5, -1) == 0); + assert(pow(-5, -1) == 0); + assert(pow(5, -2) == 0); + assert(pow(-5, -2) == 0); + assert(pow(-1, int.min) == 1); + assert(pow(-2, int.min) == 0); + + assert(pow(4294967290UL,2) == 18446744022169944100UL); + assert(pow(0,uint.max) == 0); +} + +/**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); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(pow(2, 5.0) == 32.0); + assert(pow(7, 3.0) == 343.0); + assert(pow(2, real.nan) is real.nan); + assert(pow(2, real.infinity) == real.infinity); +} + +/** + * 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)) +{ + import core.math : fabs, sqrt; + import std.math.traits : isInfinity, isNaN, signbit; + + 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 (isInfinity(x)) + { + if (!signbit(y) && !signbit(x)) + return F.infinity; + else + return F.nan; + } + else if (fabs(x) > 1) + { + if (signbit(y)) + return +0.0; + else + return F.infinity; + } + else if (fabs(x) == 1) + { + return F.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 if (i == y) + return F.infinity; + else + return -F.nan; + } + else if (y < 0.0) + { + if (i == y && i & 1) + return -0.0; + else if (i == y) + return +0.0; + else + return F.nan; + } + } + 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 + import std.math.rounding : floor; + if (floor(y) != y) + return sqrt(x); // Complex result -- create a NaN + + const hy = 0.5 * y; + 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 +{ + import std.math.operations : isClose; + + assert(isClose(pow(2.0, 3.0), 8.0)); + assert(isClose(pow(1.5, 10.0), 57.6650390625)); + + // square root of 9 + assert(isClose(pow(9.0, 0.5), 3.0)); + // 10th root of 1024 + assert(isClose(pow(1024.0, 0.1), 2.0)); + + assert(isClose(pow(-4.0, 3.0), -64.0)); + + // reciprocal of 4 ^^ 2 + assert(isClose(pow(4.0, -2.0), 0.0625)); + // reciprocal of (-2) ^^ 3 + assert(isClose(pow(-2.0, -3.0), -0.125)); + + assert(isClose(pow(-2.5, 3.0), -15.625)); + // reciprocal of 2.5 ^^ 3 + assert(isClose(pow(2.5, -3.0), 0.064)); + // reciprocal of (-2.5) ^^ 3 + assert(isClose(pow(-2.5, -3.0), -0.064)); + + // reciprocal of square root of 4 + assert(isClose(pow(4.0, -0.5), 0.5)); + + // per definition + assert(isClose(pow(0.0, 0.0), 1.0)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + + // the result is a complex number + // which cannot be represented as floating point number + import std.math.traits : isNaN; + assert(isNaN(pow(-2.5, -1.5))); + + // use the ^^-operator of std.complex instead + import std.complex : complex; + auto c1 = complex(-2.5, 0.0); + auto c2 = complex(-1.5, 0.0); + auto result = c1 ^^ c2; + // exact result apparently depends on `real` precision => increased tolerance + assert(isClose(result.re, -4.64705438e-17, 2e-4)); + assert(isClose(result.im, 2.52982e-1, 2e-4)); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN; + + assert(pow(1.5, real.infinity) == real.infinity); + assert(pow(0.5, real.infinity) == 0.0); + assert(pow(1.5, -real.infinity) == 0.0); + assert(pow(0.5, -real.infinity) == real.infinity); + assert(pow(real.infinity, 1.0) == real.infinity); + assert(pow(real.infinity, -1.0) == 0.0); + assert(pow(real.infinity, real.infinity) == real.infinity); + assert(pow(-real.infinity, 1.0) == -real.infinity); + assert(pow(-real.infinity, 2.0) == real.infinity); + assert(pow(-real.infinity, -1.0) == -0.0); + assert(pow(-real.infinity, -2.0) == 0.0); + assert(isNaN(pow(1.0, real.infinity))); + assert(pow(0.0, -1.0) == real.infinity); + assert(pow(real.nan, 0.0) == 1.0); + assert(isNaN(pow(real.nan, 3.0))); + assert(isNaN(pow(3.0, real.nan))); +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + + assert(isClose(pow(2.0L, 10.0L), 1024, 1e-18)); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.traits : isIdentical, isNaN; + import std.math.constants : PI; + + // 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)); + + // https://issues.dlang.org/show_bug.cgi?id=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(isClose(pow(two, three), 8.0)); + assert(isClose(pow(two, -2.5), 0.1767766953)); + + // Test integer to float power. + immutable uint twoI = 2; + assert(isClose(pow(twoI, three), 8.0)); +} + +// https://issues.dlang.org/show_bug.cgi?id=20508 +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN; + + assert(isNaN(pow(-double.infinity, 0.5))); + + assert(isNaN(pow(-real.infinity, real.infinity))); + assert(isNaN(pow(-real.infinity, -real.infinity))); + assert(isNaN(pow(-real.infinity, 1.234))); + assert(isNaN(pow(-real.infinity, -0.751))); + assert(pow(-real.infinity, 0.0) == 1.0); +} + +/** Computes the value of a positive integer `x`, raised to the power `n`, modulo `m`. + * + * Params: + * x = base + * n = exponent + * m = modulus + * + * Returns: + * `x` to the power `n`, modulo `m`. + * The return type is the largest of `x`'s and `m`'s type. + * + * The function requires that all values have unsigned types. + */ +Unqual!(Largest!(F, H)) powmod(F, G, H)(F x, G n, H m) +if (isUnsigned!F && isUnsigned!G && isUnsigned!H) +{ + import std.meta : AliasSeq; + + alias T = Unqual!(Largest!(F, H)); + static if (T.sizeof <= 4) + { + alias DoubleT = AliasSeq!(void, ushort, uint, void, ulong)[T.sizeof]; + } + + static T mulmod(T a, T b, T c) + { + static if (T.sizeof == 8) + { + static T addmod(T a, T b, T c) + { + b = c - b; + if (a >= b) + return a - b; + else + return c - b + a; + } + + T result = 0, tmp; + + b %= c; + while (a > 0) + { + if (a & 1) + result = addmod(result, b, c); + + a >>= 1; + b = addmod(b, b, c); + } + + return result; + } + else + { + DoubleT result = cast(DoubleT) (cast(DoubleT) a * cast(DoubleT) b); + return result % c; + } + } + + T base = x, result = 1, modulus = m; + Unqual!G exponent = n; + + while (exponent > 0) + { + if (exponent & 1) + result = mulmod(result, base, modulus); + + base = mulmod(base, base, modulus); + exponent >>= 1; + } + + return result; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(powmod(1U, 10U, 3U) == 1); + assert(powmod(3U, 2U, 6U) == 3); + assert(powmod(5U, 5U, 15U) == 5); + assert(powmod(2U, 3U, 5U) == 3); + assert(powmod(2U, 4U, 5U) == 1); + assert(powmod(2U, 5U, 5U) == 2); +} + +@safe pure nothrow @nogc unittest +{ + ulong a = 18446744073709551615u, b = 20u, c = 18446744073709551610u; + assert(powmod(a, b, c) == 95367431640625u); + a = 100; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 18223853583554725198u); + a = 117; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 11493139548346411394u); + a = 134; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 10979163786734356774u); + a = 151; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 7023018419737782840u); + a = 168; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 58082701842386811u); + a = 185; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 17423478386299876798u); + a = 202; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 5522733478579799075u); + a = 219; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 15230218982491623487u); + a = 236; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 5198328724976436000u); + + a = 0; b = 7919; c = 18446744073709551557u; + assert(powmod(a, b, c) == 0); + a = 123; b = 0; c = 18446744073709551557u; + assert(powmod(a, b, c) == 1); + + immutable ulong a1 = 253, b1 = 7919, c1 = 18446744073709551557u; + assert(powmod(a1, b1, c1) == 3883707345459248860u); + + uint x = 100 ,y = 7919, z = 1844674407u; + assert(powmod(x, y, z) == 1613100340u); + x = 134; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 734956622u); + x = 151; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 1738696945u); + x = 168; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 1247580927u); + x = 185; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 1293855176u); + x = 202; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 1566963682u); + x = 219; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 181227807u); + x = 236; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 217988321u); + x = 253; y = 7919; z = 1844674407u; + assert(powmod(x, y, z) == 1588843243u); + + x = 0; y = 7919; z = 184467u; + assert(powmod(x, y, z) == 0); + x = 123; y = 0; z = 1844674u; + assert(powmod(x, y, z) == 1); + + immutable ubyte x1 = 117; + immutable uint y1 = 7919; + immutable uint z1 = 1844674407u; + auto res = powmod(x1, y1, z1); + assert(is(typeof(res) == uint)); + assert(res == 9479781u); + + immutable ushort x2 = 123; + immutable uint y2 = 203; + immutable ubyte z2 = 113; + auto res2 = powmod(x2, y2, z2); + assert(is(typeof(res2) == ushort)); + assert(res2 == 42u); +} + +/** + * 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)) ) + * ) + */ +pragma(inline, true) +real exp(real x) @trusted pure nothrow @nogc // TODO: @safe +{ + import std.math.constants : LOG2E; + + version (InlineAsm_X87) + { + // e^^x = 2^^(LOG2E*x) + // (This is valid because the overflow & underflow limits for exp + // and exp2 are so similar). + if (!__ctfe) + return exp2Asm(LOG2E*x); + } + return expImpl(x); +} + +/// ditto +pragma(inline, true) +double exp(double x) @safe pure nothrow @nogc { return __ctfe ? cast(double) exp(cast(real) x) : expImpl(x); } + +/// ditto +pragma(inline, true) +float exp(float x) @safe pure nothrow @nogc { return __ctfe ? cast(float) exp(cast(real) x) : expImpl(x); } + +/// +@safe unittest +{ + import std.math.operations : feqrel; + import std.math.constants : E; + + assert(exp(0.0) == 1.0); + assert(exp(3.0).feqrel(E * E * E) > 16); +} + +private T expImpl(T)(T x) @safe pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat; + import std.math.traits : isNaN; + import std.math.rounding : floor; + import std.math.algebraic : poly; + import std.math.constants : LOG2E; + + alias F = floatTraits!T; + static if (F.realFormat == RealFormat.ieeeSingle) + { + static immutable T[6] P = [ + 5.0000001201E-1, + 1.6666665459E-1, + 4.1665795894E-2, + 8.3334519073E-3, + 1.3981999507E-3, + 1.9875691500E-4, + ]; + + enum T C1 = 0.693359375; + enum T C2 = -2.12194440e-4; + + // Overflow and Underflow limits. + enum T OF = 88.72283905206835; + enum T UF = -103.278929903431851103; // ln(2^-149) + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + // Coefficients for exp(x) + static immutable T[3] P = [ + 9.99999999999999999910E-1L, + 3.02994407707441961300E-2L, + 1.26177193074810590878E-4L, + ]; + static immutable T[4] Q = [ + 2.00000000000000000009E0L, + 2.27265548208155028766E-1L, + 2.52448340349684104192E-3L, + 3.00198505138664455042E-6L, + ]; + + // C1 + C2 = LN2. + enum T C1 = 6.93145751953125E-1; + enum T C2 = 1.42860682030941723212E-6; + + // Overflow and Underflow limits. + enum T OF = 7.09782712893383996732E2; // ln((1-2^-53) * 2^1024) + enum T UF = -7.451332191019412076235E2; // ln(2^-1075) + } + else static if (F.realFormat == RealFormat.ieeeExtended || + F.realFormat == RealFormat.ieeeExtended53) + { + // Coefficients for exp(x) + static immutable T[3] P = [ + 9.9999999999999999991025E-1L, + 3.0299440770744196129956E-2L, + 1.2617719307481059087798E-4L, + ]; + static immutable T[4] Q = [ + 2.0000000000000000000897E0L, + 2.2726554820815502876593E-1L, + 2.5244834034968410419224E-3L, + 3.0019850513866445504159E-6L, + ]; + + // C1 + C2 = LN2. + enum T C1 = 6.9314575195312500000000E-1L; + enum T C2 = 1.4286068203094172321215E-6L; + + // Overflow and Underflow limits. + enum T OF = 1.1356523406294143949492E4L; // ln((1-2^-64) * 2^16384) + enum T UF = -1.13994985314888605586758E4L; // ln(2^-16446) + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + // Coefficients for exp(x) - 1 + static immutable T[5] P = [ + 9.999999999999999999999999999999999998502E-1L, + 3.508710990737834361215404761139478627390E-2L, + 2.708775201978218837374512615596512792224E-4L, + 6.141506007208645008909088812338454698548E-7L, + 3.279723985560247033712687707263393506266E-10L + ]; + static immutable T[6] Q = [ + 2.000000000000000000000000000000000000150E0, + 2.368408864814233538909747618894558968880E-1L, + 3.611828913847589925056132680618007270344E-3L, + 1.504792651814944826817779302637284053660E-5L, + 1.771372078166251484503904874657985291164E-8L, + 2.980756652081995192255342779918052538681E-12L + ]; + + // C1 + C2 = LN2. + enum T C1 = 6.93145751953125E-1L; + enum T C2 = 1.428606820309417232121458176568075500134E-6L; + + // Overflow and Underflow limits. + enum T OF = 1.135583025911358400418251384584930671458833e4L; + enum T UF = -1.143276959615573793352782661133116431383730e4L; + } + else + static assert(0, "Not implemented for this architecture"); + + // Special cases. + if (isNaN(x)) + return x; + if (x > OF) + return real.infinity; + if (x < UF) + return 0.0; + + // Express: e^^x = e^^g * 2^^n + // = e^^g * e^^(n * LOG2E) + // = e^^(g + n * LOG2E) + T xx = floor((cast(T) LOG2E) * x + cast(T) 0.5); + const int n = cast(int) xx; + x -= xx * C1; + x -= xx * C2; + + static if (F.realFormat == RealFormat.ieeeSingle) + { + xx = x * x; + x = poly(x, P) * xx + x + 1.0f; + } + else + { + // Rational approximation for exponential of the fractional part: + // e^^x = 1 + 2x P(x^^2) / (Q(x^^2) - P(x^^2)) + xx = x * x; + const T px = x * poly(xx, P); + x = px / (poly(xx, Q) - px); + x = (cast(T) 1.0) + (cast(T) 2.0) * x; + } + + // Scale by power of 2. + x = core.math.ldexp(x, n); + + return x; +} + +@safe @nogc nothrow unittest +{ + import std.math : floatTraits, RealFormat; + import std.math.operations : NaN, feqrel, isClose; + import std.math.constants : E; + import std.math.traits : isIdentical; + import std.math.algebraic : abs; + + version (IeeeFlagsSupport) import std.math.hardware : IeeeFlags, resetIeeeFlags, ieeeFlags; + version (FloatingPointControlSupport) + { + import std.math.hardware : FloatingPointControl; + + FloatingPointControl ctrl; + if (FloatingPointControl.hasExceptionTraps) + ctrl.disableExceptions(FloatingPointControl.allExceptions); + ctrl.rounding = FloatingPointControl.roundToNearest; + } + + static void testExp(T)() + { + enum realFormat = floatTraits!T.realFormat; + static if (realFormat == RealFormat.ieeeQuadruple) + { + static immutable T[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, T.infinity ], // close overflow + [ 0x1p+80L, T.infinity ], // far overflow + [ T.infinity, T.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 (realFormat == RealFormat.ieeeExtended || + realFormat == RealFormat.ieeeExtended53) + { + static immutable T[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, T.infinity ], // close overflow + [ 0x1p+80L, T.infinity ], // far overflow + [ T.infinity, T.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 (realFormat == RealFormat.ieeeDouble) + { + static immutable T[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, T.infinity ], // close overflow + [ 0x1p+80L, T.infinity ], // far overflow + [ T.infinity, T.infinity ], + [-0x1.6p+9L, 0x1.44a3824e5285fp-1016L ], // near underflow + [-0x1.64p+9L, 0x0.06f84920bb2d4p-1022L ], // near underflow - subnormal + [-0x1.743p+9L, 0x0.0000000000001p-1022L ], // ditto + [-0x1.8p+9L, 0 ], // close underflow + [-0x1p+30L, 0 ], // far underflow + ]; + } + else static if (realFormat == RealFormat.ieeeSingle) + { + static immutable T[2][] exptestpoints = + [ // x, exp(x) + [ 1.0L, E ], + [ 0.5L, 0x1.a61299p+0L ], + [ 3.0L, E*E*E ], + [ 0x1.62p+6L, 0x1.99b988p+127L ], // near overflow + [ 0x1.7p+6L, T.infinity ], // close overflow + [ 0x1p+80L, T.infinity ], // far overflow + [ T.infinity, T.infinity ], + [-0x1.5cp+6L, 0x1.666d0ep-126L ], // near underflow + [-0x1.7p+6L, 0x0.026a42p-126L ], // near underflow - subnormal + [-0x1.9cp+6L, 0x0.000002p-126L ], // ditto + [-0x1.ap+6L, 0 ], // close underflow + [-0x1p+30L, 0 ], // far underflow + ]; + } + else + static assert(0, "No exp() tests for real type!"); + + const minEqualMantissaBits = T.mant_dig - 13; + T x; + version (IeeeFlagsSupport) IeeeFlags f; + foreach (ref pair; exptestpoints) + { + version (IeeeFlagsSupport) resetIeeeFlags(); + x = exp(pair[0]); + //printf("exp(%La) = %La, should be %La\n", cast(real) pair[0], cast(real) x, cast(real) pair[1]); + assert(feqrel(x, pair[1]) >= minEqualMantissaBits); + } + + // Ideally, exp(0) would not set the inexact flag. + // Unfortunately, fldl2e sets it! + // So it's not realistic to avoid setting it. + assert(exp(cast(T) 0.0) == 1.0); + + // NaN propagation. Doesn't set flags, bcos was already NaN. + version (IeeeFlagsSupport) + { + resetIeeeFlags(); + x = exp(T.nan); + f = ieeeFlags; + assert(isIdentical(abs(x), T.nan)); + assert(f.flags == 0); + + resetIeeeFlags(); + x = exp(-T.nan); + f = ieeeFlags; + assert(isIdentical(abs(x), T.nan)); + assert(f.flags == 0); + } + else + { + x = exp(T.nan); + assert(isIdentical(abs(x), T.nan)); + + x = exp(-T.nan); + assert(isIdentical(abs(x), T.nan)); + } + + x = exp(NaN(0x123)); + assert(isIdentical(x, NaN(0x123))); + } + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(real, double, float)) + testExp!T(); + + // High resolution test (verified against GNU MPFR/Mathematica). + assert(exp(0.5L) == 0x1.A612_98E1_E069_BC97_2DFE_FAB6_DF34p+0L); + + assert(isClose(exp(3.0L), E * E * E, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/** + * 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)) ) + * ) + */ +pragma(inline, true) +real expm1(real x) @trusted pure nothrow @nogc // TODO: @safe +{ + version (InlineAsm_X87) + { + if (!__ctfe) + return expm1Asm(x); + } + return expm1Impl(x); +} + +/// ditto +pragma(inline, true) +double expm1(double x) @safe pure nothrow @nogc +{ + return __ctfe ? cast(double) expm1(cast(real) x) : expm1Impl(x); +} + +/// ditto +pragma(inline, true) +float expm1(float x) @safe pure nothrow @nogc +{ + // no single-precision version in Cephes => use double precision + return __ctfe ? cast(float) expm1(cast(real) x) : cast(float) expm1Impl(cast(double) x); +} + +/// +@safe unittest +{ + import std.math.traits : isIdentical; + import std.math.operations : feqrel; + + assert(isIdentical(expm1(0.0), 0.0)); + assert(expm1(1.0).feqrel(1.71828) > 16); + assert(expm1(2.0).feqrel(6.3890) > 16); +} + +version (InlineAsm_X87) +private real expm1Asm(real x) @trusted pure nothrow @nogc +{ + version (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 (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 + static assert(0); +} + +private T expm1Impl(T)(T x) @safe pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat; + import std.math.rounding : floor; + import std.math.algebraic : poly; + import std.math.constants : LN2; + + // Coefficients for exp(x) - 1 and overflow/underflow limits. + enum realFormat = floatTraits!T.realFormat; + static if (realFormat == RealFormat.ieeeQuadruple) + { + static immutable T[8] P = [ + 2.943520915569954073888921213330863757240E8L, + -5.722847283900608941516165725053359168840E7L, + 8.944630806357575461578107295909719817253E6L, + -7.212432713558031519943281748462837065308E5L, + 4.578962475841642634225390068461943438441E4L, + -1.716772506388927649032068540558788106762E3L, + 4.401308817383362136048032038528753151144E1L, + -4.888737542888633647784737721812546636240E-1L + ]; + + static immutable T[9] Q = [ + 1.766112549341972444333352727998584753865E9L, + -7.848989743695296475743081255027098295771E8L, + 1.615869009634292424463780387327037251069E8L, + -2.019684072836541751428967854947019415698E7L, + 1.682912729190313538934190635536631941751E6L, + -9.615511549171441430850103489315371768998E4L, + 3.697714952261803935521187272204485251835E3L, + -8.802340681794263968892934703309274564037E1L, + 1.0 + ]; + + enum T OF = 1.1356523406294143949491931077970764891253E4L; + enum T UF = -1.143276959615573793352782661133116431383730e4L; + } + else static if (realFormat == RealFormat.ieeeExtended) + { + static immutable T[5] P = [ + -1.586135578666346600772998894928250240826E4L, + 2.642771505685952966904660652518429479531E3L, + -3.423199068835684263987132888286791620673E2L, + 1.800826371455042224581246202420972737840E1L, + -5.238523121205561042771939008061958820811E-1L, + ]; + static immutable T[6] Q = [ + -9.516813471998079611319047060563358064497E4L, + 3.964866271411091674556850458227710004570E4L, + -7.207678383830091850230366618190187434796E3L, + 7.206038318724600171970199625081491823079E2L, + -4.002027679107076077238836622982900945173E1L, + 1.0 + ]; + + enum T OF = 1.1356523406294143949492E4L; + enum T UF = -4.5054566736396445112120088E1L; + } + else static if (realFormat == RealFormat.ieeeDouble) + { + static immutable T[3] P = [ + 9.9999999999999999991025E-1, + 3.0299440770744196129956E-2, + 1.2617719307481059087798E-4, + ]; + static immutable T[4] Q = [ + 2.0000000000000000000897E0, + 2.2726554820815502876593E-1, + 2.5244834034968410419224E-3, + 3.0019850513866445504159E-6, + ]; + } + else + static assert(0, "no coefficients for expm1()"); + + static if (realFormat == RealFormat.ieeeDouble) // special case for double precision + { + if (x < -0.5 || x > 0.5) + return exp(x) - 1.0; + if (x == 0.0) + return x; + + const T xx = x * x; + x = x * poly(xx, P); + x = x / (poly(xx, Q) - x); + return x + x; + } + else + { + // C1 + C2 = LN2. + enum T C1 = 6.9314575195312500000000E-1L; + enum T C2 = 1.428606820309417232121458176568075500134E-6L; + + // Special cases. + if (x > OF) + return real.infinity; + if (x == cast(T) 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((cast(T) 0.5) + x / cast(T) LN2); + x -= n * C1; + x -= n * C2; + + // Rational approximation: + // exp(x) - 1 = x + 0.5 x^^2 + x^^3 P(x) / Q(x) + T px = x * poly(x, P); + T qx = poly(x, Q); + const T xx = x * x; + qx = x + ((cast(T) 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 = core.math.ldexp(cast(T) 1.0, n); + x = px * qx + (px - cast(T) 1.0); + + return x; + } +} + +@safe @nogc nothrow unittest +{ + import std.math.traits : isNaN; + import std.math.operations : isClose, CommonDefaultFor; + + static void testExpm1(T)() + { + // NaN + assert(isNaN(expm1(cast(T) T.nan))); + + static immutable T[] xs = [ -2, -0.75, -0.3, 0.0, 0.1, 0.2, 0.5, 1.0 ]; + foreach (x; xs) + { + const T e = expm1(x); + const T r = exp(x) - 1; + + //printf("expm1(%Lg) = %Lg, should approximately be %Lg\n", cast(real) x, cast(real) e, cast(real) r); + assert(isClose(r, e, CommonDefaultFor!(T,T), CommonDefaultFor!(T,T))); + } + } + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(real, double)) + testExpm1!T(); +} + +/** + * 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 // TODO: @safe +{ + version (InlineAsm_X87) + { + if (!__ctfe) + return exp2Asm(x); + } + return exp2Impl(x); +} + +/// ditto +pragma(inline, true) +double exp2(double x) @nogc @safe pure nothrow { return __ctfe ? cast(double) exp2(cast(real) x) : exp2Impl(x); } + +/// ditto +pragma(inline, true) +float exp2(float x) @nogc @safe pure nothrow { return __ctfe ? cast(float) exp2(cast(real) x) : exp2Impl(x); } + +/// +@safe unittest +{ + import std.math.traits : isIdentical; + import std.math.operations : feqrel; + + assert(isIdentical(exp2(0.0), 1.0)); + assert(exp2(2.0).feqrel(4.0) > 16); + assert(exp2(8.0).feqrel(256.0) > 16); +} + +@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 ); + } +} + +version (InlineAsm_X87) +private real exp2Asm(real x) @nogc @trusted pure nothrow +{ + version (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 (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 T exp2Impl(T)(T x) @nogc @safe pure nothrow +{ + import std.math : floatTraits, RealFormat; + import std.math.traits : isNaN; + import std.math.rounding : floor; + import std.math.algebraic : poly; + + // Coefficients for exp2(x) + enum realFormat = floatTraits!T.realFormat; + static if (realFormat == RealFormat.ieeeQuadruple) + { + static immutable T[5] P = [ + 9.079594442980146270952372234833529694788E12L, + 1.530625323728429161131811299626419117557E11L, + 5.677513871931844661829755443994214173883E8L, + 6.185032670011643762127954396427045467506E5L, + 1.587171580015525194694938306936721666031E2L + ]; + + static immutable T[6] Q = [ + 2.619817175234089411411070339065679229869E13L, + 1.490560994263653042761789432690793026977E12L, + 1.092141473886177435056423606755843616331E10L, + 2.186249607051644894762167991800811827835E7L, + 1.236602014442099053716561665053645270207E4L, + 1.0 + ]; + } + else static if (realFormat == RealFormat.ieeeExtended) + { + static immutable T[3] P = [ + 2.0803843631901852422887E6L, + 3.0286971917562792508623E4L, + 6.0614853552242266094567E1L, + ]; + static immutable T[4] Q = [ + 6.0027204078348487957118E6L, + 3.2772515434906797273099E5L, + 1.7492876999891839021063E3L, + 1.0000000000000000000000E0L, + ]; + } + else static if (realFormat == RealFormat.ieeeDouble) + { + static immutable T[3] P = [ + 1.51390680115615096133E3L, + 2.02020656693165307700E1L, + 2.30933477057345225087E-2L, + ]; + static immutable T[3] Q = [ + 4.36821166879210612817E3L, + 2.33184211722314911771E2L, + 1.00000000000000000000E0L, + ]; + } + else static if (realFormat == RealFormat.ieeeSingle) + { + static immutable T[6] P = [ + 6.931472028550421E-001L, + 2.402264791363012E-001L, + 5.550332471162809E-002L, + 9.618437357674640E-003L, + 1.339887440266574E-003L, + 1.535336188319500E-004L, + ]; + } + else + static assert(0, "no coefficients for exp2()"); + + // Overflow and Underflow limits. + enum T OF = T.max_exp; + enum T UF = T.min_exp - 1; + + // Special cases. + if (isNaN(x)) + return x; + if (x > OF) + return real.infinity; + if (x < UF) + return 0.0; + + static if (realFormat == RealFormat.ieeeSingle) // special case for single precision + { + // The following is necessary because range reduction blows up. + if (x == 0.0f) + return 1.0f; + + // Separate into integer and fractional parts. + const T i = floor(x); + int n = cast(int) i; + x -= i; + if (x > 0.5f) + { + n += 1; + x -= 1.0f; + } + + // Rational approximation: + // exp2(x) = 1.0 + x P(x) + x = 1.0f + x * poly(x, P); + } + else + { + // Separate into integer and fractional parts. + const T i = floor(x + cast(T) 0.5); + int n = cast(int) i; + x -= i; + + // Rational approximation: + // exp2(x) = 1.0 + 2x P(x^^2) / (Q(x^^2) - P(x^^2)) + const T xx = x * x; + const T px = x * poly(xx, P); + x = px / (poly(xx, Q) - px); + x = (cast(T) 1.0) + (cast(T) 2.0) * x; + } + + // Scale by power of 2. + x = core.math.ldexp(x, n); + + return x; +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : feqrel, NaN, isClose; + import std.math.traits : isIdentical; + import std.math.constants : SQRT2; + + assert(feqrel(exp2(0.5L), SQRT2) >= real.mant_dig -1); + assert(exp2(8.0L) == 256.0); + assert(exp2(-9.0L)== 1.0L/512.0); + + static void testExp2(T)() + { + // NaN + const T specialNaN = NaN(0x0123L); + assert(isIdentical(exp2(specialNaN), specialNaN)); + + // over-/underflow + enum T OF = T.max_exp; + enum T UF = T.min_exp - T.mant_dig; + assert(isIdentical(exp2(OF + 1), cast(T) T.infinity)); + assert(isIdentical(exp2(UF - 1), cast(T) 0.0)); + + static immutable T[2][] vals = + [ + // x, exp2(x) + [ 0.0, 1.0 ], + [ -0.0, 1.0 ], + [ 0.5, SQRT2 ], + [ 8.0, 256.0 ], + [ -9.0, 1.0 / 512 ], + ]; + + foreach (ref val; vals) + { + const T x = val[0]; + const T r = val[1]; + const T e = exp2(x); + + //printf("exp2(%Lg) = %Lg, should be %Lg\n", cast(real) x, cast(real) e, cast(real) r); + assert(isClose(r, e)); + } + } + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(real, double, float)) + testExp2!T(); +} + +/********************************************************************* + * 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) +{ + import std.math : floatTraits, RealFormat, MANTISSA_MSB, MANTISSA_LSB; + import std.math.traits : isSubnormal; + + if (__ctfe) + { + // Handle special cases. + if (value == 0) { exp = 0; return value; } + if (value == T.infinity) { exp = int.max; return value; } + if (value == -T.infinity || value != value) { exp = int.min; return value; } + // Handle ordinary cases. + // In CTFE there is no performance advantage for having separate + // paths for different floating point types. + T absValue = value < 0 ? -value : value; + int expCount; + static if (T.mant_dig > double.mant_dig) + { + for (; absValue >= 0x1.0p+1024L; absValue *= 0x1.0p-1024L) + expCount += 1024; + for (; absValue < 0x1.0p-1021L; absValue *= 0x1.0p+1021L) + expCount -= 1021; + } + const double dval = cast(double) absValue; + int dexp = cast(int) (((*cast(const long*) &dval) >>> 52) & 0x7FF) + double.min_exp - 2; + dexp++; + expCount += dexp; + absValue *= 2.0 ^^ -dexp; + // If the original value was subnormal or if it was a real + // then absValue can still be outside the [0.5, 1.0) range. + if (absValue < 0.5) + { + assert(T.mant_dig > double.mant_dig || isSubnormal(value)); + do + { + absValue += absValue; + expCount--; + } while (absValue < 0.5); + } + else + { + assert(absValue < 1 || T.mant_dig > double.mant_dig); + for (; absValue >= 1; absValue *= T(0.5)) + expCount++; + } + exp = expCount; + return value < 0 ? -absValue : absValue; + } + + Unqual!T vf = value; + ushort* vu = cast(ushort*)&vf; + static if (is(immutable T == immutable 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + 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"); + } +} + +/// +@safe unittest +{ + import std.math.operations : isClose; + + int exp; + real mantissa = frexp(123.456L, exp); + + assert(isClose(mantissa * pow(2.0L, cast(real) exp), 123.456L)); + + 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 @nogc nothrow unittest +{ + import std.math.operations : isClose; + + int exp; + real mantissa = frexp(123.456L, exp); + + // check if values are equal to 19 decimal digits of precision + assert(isClose(mantissa * pow(2.0L, cast(real) exp), 123.456L, 1e-18)); +} + +@safe unittest +{ + import std.math : floatTraits, RealFormat; + import std.math.traits : isIdentical; + import std.meta : AliasSeq; + import std.typecons : tuple, Tuple; + + static 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), + + // https://issues.dlang.org/show_bug.cgi?id=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); + } + } + }} + + // CTFE + alias CtfeFrexpResult= Tuple!(real, int); + static CtfeFrexpResult ctfeFrexp(T)(const T value) + { + int exp; + auto significand = frexp(value, exp); + return CtfeFrexpResult(significand, exp); + } + static foreach (T; AliasSeq!(real, double, float)) + {{ + enum 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), + + // https://issues.dlang.org/show_bug.cgi?id=16026: + tuple(3 * (T.min_normal * T.epsilon), T( .75), (T.min_exp - T.mant_dig) + 2) + ]; + + static foreach (elem; vals) + { + static assert(ctfeFrexp(elem[0]) is CtfeFrexpResult(elem[1], elem[2])); + } + + static if (floatTraits!(T).realFormat == RealFormat.ieeeExtended) + { + enum 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 + ]; + static foreach (elem; extendedvals) + { + static assert(ctfeFrexp(elem[0]) is CtfeFrexpResult(elem[1], cast(int) elem[2])); + } + } + }} +} + +@safe unittest +{ + import std.meta : AliasSeq; + void foo() { + static 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 std.math : floatTraits, RealFormat, MANTISSA_MSB, MANTISSA_LSB; + + 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + 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) + { + assert(0, "ilogb not implemented"); + } +} +/// 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); +} + +/// +@safe pure unittest +{ + assert(ilogb(1) == 0); + assert(ilogb(3) == 1); + assert(ilogb(3.0) == 1); + assert(ilogb(100_000_000) == 26); + + assert(ilogb(0) == FP_ILOGB0); + assert(ilogb(0.0) == FP_ILOGB0); + assert(ilogb(double.nan) == FP_ILOGBNAN); + assert(ilogb(double.infinity) == int.max); +} + +/** +Special return values of $(LREF ilogb). + */ +alias FP_ILOGB0 = core.stdc.math.FP_ILOGB0; +/// ditto +alias FP_ILOGBNAN = core.stdc.math.FP_ILOGBNAN; + +/// +@safe pure unittest +{ + assert(ilogb(0) == FP_ILOGB0); + assert(ilogb(0.0) == FP_ILOGB0); + assert(ilogb(double.nan) == FP_ILOGBNAN); +} + +@safe nothrow @nogc unittest +{ + import std.math : floatTraits, RealFormat; + import std.math.operations : nextUp; + import std.meta : AliasSeq; + import std.typecons : Tuple; + static 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 + */ + +pragma(inline, true) +real ldexp(real n, int exp) @safe pure nothrow @nogc { return core.math.ldexp(n, exp); } +///ditto +pragma(inline, true) +double ldexp(double n, int exp) @safe pure nothrow @nogc { return core.math.ldexp(n, exp); } +///ditto +pragma(inline, true) +float ldexp(float n, int exp) @safe pure nothrow @nogc { return core.math.ldexp(n, exp); } + +/// +@nogc @safe pure nothrow unittest +{ + import std.meta : AliasSeq; + static 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 +{ + import std.math : floatTraits, RealFormat; + + static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended || + floatTraits!(real).realFormat == RealFormat.ieeeExtended53 || + floatTraits!(real).realFormat == RealFormat.ieeeQuadruple) + { + 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 https://issues.dlang.org/show_bug.cgi?id=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); +} +*/ + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + + 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(isClose(z, l, 1e-6)); + } + + real function(real, int) pldexp = &ldexp; + assert(pldexp != null); +} + +private +{ + import std.math : floatTraits, RealFormat; + + 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 = [ + 1.418134209872192732479751274970992665513E5L, + -8.977257995689735303686582344659576526998E4L, + 2.048819892795278657810231591630928516206E4L, + -2.024301798136027039250415126250455056397E3L, + 8.057002716646055371965756206836056074715E1L, + -8.828896441624934385266096344596648080902E-1L + ]; + static immutable real[7] 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 +{ + import std.math.constants : LN2, LOG2, SQRT1_2; + import std.math.traits : isInfinity, isNaN, signbit; + import std.math.algebraic : poly; + + 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) + { + exp -= 1; + x = 2.0 * x - 1.0; + } + else + { + x = x - 1.0; + } + z = x * x; + y = x * (z * poly(x, logCoeffsP) / poly(x, logCoeffsQ)); + y += exp * C2; + z = y - 0.5 * z; + + // 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 +{ + import std.math.operations : feqrel; + import std.math.constants : E; + + assert(feqrel(log(E), 1) >= real.mant_dig - 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 +{ + import std.math.constants : LOG2, LN2, SQRT1_2; + import std.math.algebraic : poly; + import std.math.traits : isNaN, isInfinity, signbit; + + 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) + { + exp -= 1; + x = 2.0 * x - 1.0; + } + else + x = x - 1.0; + + z = x * x; + y = x * (z * poly(x, logCoeffsP) / poly(x, logCoeffsQ)); + y = y - 0.5 * z; + + // 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 +{ + import std.math.algebraic : fabs; + + 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 +{ + import std.math.traits : isNaN, isInfinity, signbit; + import std.math.constants : LN2; + + 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 (core.math.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); + } +} + +/// +@safe pure unittest +{ + import std.math.traits : isIdentical, isNaN; + import std.math.operations : feqrel; + + assert(isIdentical(log1p(0.0), 0.0)); + assert(log1p(1.0).feqrel(0.69314) > 16); + + assert(log1p(-1.0) == -real.infinity); + assert(isNaN(log1p(-2.0))); + assert(log1p(real.nan) is real.nan); + assert(log1p(-real.nan) is -real.nan); + assert(log1p(real.infinity) == real.infinity); +} + +/*************************************** + * 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 +{ + import std.math.traits : isNaN, isInfinity, signbit; + import std.math.constants : SQRT1_2, LOG2E; + import std.math.algebraic : poly; + + version (INLINE_YL2X) + return core.math.yl2x(x, 1.0L); + 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) + { + exp -= 1; + x = 2.0 * x - 1.0; + } + else + x = x - 1.0; + + z = x * x; + y = x * (z * poly(x, logCoeffsP) / poly(x, logCoeffsQ)); + y = y - 0.5 * z; + + // 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; + } +} + +/// +@safe unittest +{ + import std.math.operations : isClose; + + assert(isClose(log2(1024.0L), 10)); +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + + // check if values are equal to 19 decimal digits of precision + assert(isClose(log2(1024.0L), 10, 1e-18)); +} + +/***************************************** + * 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 (InlineAsm_X87_MSVC) + { + version (X86_64) + { + asm pure nothrow @nogc + { + naked ; + fld real ptr [RCX] ; + fxtract ; + fstp ST(0) ; + ret ; + } + } + else + { + asm pure nothrow @nogc + { + fld x ; + fxtract ; + fstp ST(0) ; + } + } + } + else + return core.stdc.math.logbl(x); +} + +/// +@safe @nogc nothrow unittest +{ + assert(logb(1.0) == 0); + assert(logb(100.0) == 6); + + assert(logb(0.0) == -real.infinity); + assert(logb(real.infinity) == real.infinity); + assert(logb(-real.infinity) == real.infinity); +} + +/************************************* + * 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) ) + * ) + */ +pragma(inline, true) +real scalbn(real x, int n) @safe pure nothrow @nogc { return _scalbn(x,n); } + +/// ditto +pragma(inline, true) +double scalbn(double x, int n) @safe pure nothrow @nogc { return _scalbn(x,n); } + +/// ditto +pragma(inline, true) +float scalbn(float x, int n) @safe pure nothrow @nogc { return _scalbn(x,n); } + +/// +@safe pure nothrow @nogc unittest +{ + assert(scalbn(0x1.2345678abcdefp0L, 999) == 0x1.2345678abcdefp999L); + assert(scalbn(-real.infinity, 5) == -real.infinity); + assert(scalbn(2.0,10) == 2048.0); + assert(scalbn(2048.0f,-10) == 2.0f); +} + +pragma(inline, true) +private F _scalbn(F)(F x, int n) +{ + import std.math.traits : isInfinity; + + if (__ctfe) + { + // Handle special cases. + if (x == F(0.0) || isInfinity(x)) + return x; + } + return core.math.ldexp(x, n); +} + +@safe pure nothrow @nogc unittest +{ + // CTFE-able test + static assert(scalbn(0x1.2345678abcdefp0L, 999) == 0x1.2345678abcdefp999L); + static assert(scalbn(-real.infinity, 5) == -real.infinity); + // Test with large exponent delta n where the result is in bounds but 2.0L ^^ n is not. + enum initialExponent = real.min_exp + 2, resultExponent = real.max_exp - 2; + enum int n = resultExponent - initialExponent; + enum real x = 0x1.2345678abcdefp0L * (2.0L ^^ initialExponent); + enum staticResult = scalbn(x, n); + static assert(staticResult == 0x1.2345678abcdefp0L * (2.0L ^^ resultExponent)); + assert(scalbn(x, n) == staticResult); +} + diff --git a/libphobos/src/std/math/hardware.d b/libphobos/src/std/math/hardware.d new file mode 100644 index 0000000..90bc96d --- /dev/null +++ b/libphobos/src/std/math/hardware.d @@ -0,0 +1,1212 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains hardware support for floating point numbers. + +Copyright: Copyright The D Language Foundation 2000 - 2011. +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/hardware.d) + */ + +/* NOTE: This file has been patched from the original DMD distribution to + * work with the GDC compiler. + */ +module std.math.hardware; + +static import core.stdc.fenv; + +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 (S390) version = IBMZ_Any; +version (SPARC) version = SPARC_Any; +version (SPARC64) version = SPARC_Any; +version (SystemZ) version = IBMZ_Any; +version (RISCV32) version = RISCV_Any; +version (RISCV64) version = RISCV_Any; + +version (D_InlineAsm_X86) version = InlineAsm_X86_Any; +version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; + +version (InlineAsm_X86_Any) version = InlineAsm_X87; +version (InlineAsm_X87) +{ + static assert(real.mant_dig == 64); + version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC; +} + +version (X86_64) version = StaticallyHaveSSE; +version (X86) version (OSX) version = StaticallyHaveSSE; + +version (StaticallyHaveSSE) +{ + private enum bool haveSSE = true; +} +else version (X86) +{ + static import core.cpuid; + private alias haveSSE = core.cpuid.sse; +} + +version (D_SoftFloat) +{ + // Some soft float implementations may support IEEE floating flags. + // The implementation here supports hardware flags only and is so currently + // only available for supported targets. +} +else version (X86_Any) version = IeeeFlagsSupport; +else version (PPC_Any) version = IeeeFlagsSupport; +else version (RISCV_Any) version = IeeeFlagsSupport; +else version (MIPS_Any) version = IeeeFlagsSupport; +else version (ARM_Any) version = IeeeFlagsSupport; + +// Struct FloatingPointControl is only available if hardware FP units are available. +version (D_HardFloat) +{ + // FloatingPointControl.clearExceptions() depends on version IeeeFlagsSupport + version (IeeeFlagsSupport) version = FloatingPointControlSupport; +} + +version (GNU) +{ + // The compiler can unexpectedly rearrange floating point operations and + // access to the floating point status flags when optimizing. This means + // ieeeFlags tests cannot be reliably checked in optimized code. + // See https://github.com/ldc-developers/ldc/issues/888 +} +else +{ + version = IeeeFlagsUnittest; + version = FloatingPointControlUnittest; +} + +version (IeeeFlagsSupport) +{ + +/** 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 +{ +nothrow @nogc: + +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). + // The RISC-V (32 & 64 bit) fcsr is 32-bit register. + 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, + } + } + + static uint getIeeeFlags() @trusted pure + { + 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 version (RISCV_Any) + { + version (D_SoftFloat) + return 0; + else + { + uint result = void; + asm pure nothrow @nogc + { + "frflags %0" : "=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 version (RISCV_Any) + { + mixin(` + uint result = void; + asm pure nothrow @nogc + { + "frflags %0" : "=r" (result); + } + return result; + `); + } + else + assert(0, "Not yet supported"); + } + + static void resetIeeeFlags() @trusted + { + version (GNU) + { + version (X86_Any) + { + asm nothrow @nogc + { + "fnclex"; + } + + // Also clear exception flags in MXCSR, SSE's control register. + if (haveSSE) + { + uint mxcsr; + asm nothrow @nogc + { + "stmxcsr %0" : "=m" (mxcsr); + } + mxcsr &= ~EXCEPTIONS_MASK; + asm 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 nothrow @nogc + { + "vmsr FPSCR, %0" : : "r" (old); + } + } + } + else version (RISCV_Any) + { + version (D_SoftFloat) + return; + else + { + uint newValues = 0x0; + asm nothrow @nogc + { + "fsflags %0" : : "r" (newValues); + } + } + } + else + assert(0, "Not yet supported"); + } + else + version (InlineAsm_X86_Any) + { + asm 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 version (RISCV_Any) + { + mixin(` + uint newValues = 0x0; + asm pure nothrow @nogc + { + "fsflags %0" : : "r" (newValues); + } + `); + } + 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: + /** + * The result cannot be represented exactly, so rounding occurred. + * Example: `x = sin(0.1);` + */ + @property bool inexact() @safe const { return (flags & INEXACT_MASK) != 0; } + + /** + * A zero was generated by underflow + * Example: `x = real.min*real.epsilon/2;` + */ + @property bool underflow() @safe const { return (flags & UNDERFLOW_MASK) != 0; } + + /** + * An infinity was generated by overflow + * Example: `x = real.max*2;` + */ + @property bool overflow() @safe const { return (flags & OVERFLOW_MASK) != 0; } + + /** + * An infinity was generated by division by zero + * Example: `x = 3/0.0;` + */ + @property bool divByZero() @safe const { return (flags & DIVBYZERO_MASK) != 0; } + + /** + * A machine NaN was generated. + * Example: `x = real.infinity * 0.0;` + */ + @property bool invalid() @safe const { return (flags & INVALID_MASK) != 0; } +} + +/// +version (IeeeFlagsUnittest) +@safe unittest +{ + import std.math.traits : isNaN; + + static void func() { + int a = 10 * 10; + } + pragma(inline, false) static void blockopt(ref real x) {} + real a = 3.5; + // Set all the flags to zero + resetIeeeFlags(); + assert(!ieeeFlags.divByZero); + blockopt(a); // avoid constant propagation by the optimizer + // Perform a division by zero. + a /= 0.0L; + assert(a == real.infinity); + assert(ieeeFlags.divByZero); + blockopt(a); // avoid constant propagation by the optimizer + // 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 (IeeeFlagsUnittest) +@safe unittest +{ + import std.meta : AliasSeq; + + static struct Test + { + void delegate() @trusted action; + bool function() @trusted ieeeCheck; + } + + static 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.1L; }, + () => 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()); + } + }} +} + +/// Set all of the floating-point status flags to false. +void resetIeeeFlags() @trusted nothrow @nogc +{ + IeeeFlags.resetIeeeFlags(); +} + +/// +@safe unittest +{ + pragma(inline, false) static void blockopt(ref real x) {} + resetIeeeFlags(); + real a = 3.5; + blockopt(a); // avoid constant propagation by the optimizer + a /= 0.0L; + blockopt(a); // avoid constant propagation by the optimizer + assert(a == real.infinity); + assert(ieeeFlags.divByZero); + + resetIeeeFlags(); + assert(!ieeeFlags.divByZero); +} + +/// Returns: snapshot of the current state of the floating-point status flags +@property IeeeFlags ieeeFlags() @trusted pure nothrow @nogc +{ + return IeeeFlags(IeeeFlags.getIeeeFlags()); +} + +/// +@safe nothrow unittest +{ + import std.math.traits : isNaN; + + pragma(inline, false) static void blockopt(ref real x) {} + resetIeeeFlags(); + real a = 3.5; + blockopt(a); // avoid constant propagation by the optimizer + + a /= 0.0L; + assert(a == real.infinity); + assert(ieeeFlags.divByZero); + blockopt(a); // avoid constant propagation by the optimizer + + a *= 0.0L; + assert(isNaN(a)); + assert(ieeeFlags.invalid); +} + +} // IeeeFlagsSupport + + +version (FloatingPointControlSupport) +{ + +/** 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 + + // The set hardware exceptions and rounding modes will be disabled when + // leaving this scope. +} +---- + + */ +struct FloatingPointControl +{ +nothrow @nogc: + + 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 + * + * Changing the rounding mode in the middle of a function can interfere + * with optimizations of floating point expressions, as the optimizer assumes + * that the rounding mode does not change. + * It is best to change the rounding mode only at the + * beginning of the function, and keep it until the function returns. + * It is also best to add the line: + * --- + * pragma(inline, false); + * --- + * as the first line of the function so it will not get inlined. + * Params: + * newMode = the new rounding mode + */ + @property void rounding(RoundingMode newMode) @trusted + { + initialize(); + setControlState((getControlState() & (-1 - roundingMask)) | (newMode & roundingMask)); + } + + /// Returns: the currently active rounding mode + @property static RoundingMode rounding() @trusted pure + { + 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 (PPC_Any) + { + enum : ExceptionMask + { + inexactException = 0x0008, + divByZeroException = 0x0010, + underflowException = 0x0020, + overflowException = 0x0040, + invalidException = 0x0080, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (RISCV_Any) + { + enum : ExceptionMask + { + inexactException = 0x01, + divByZeroException = 0x02, + underflowException = 0x04, + overflowException = 0x08, + invalidException = 0x10, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (HPPA) + { + enum : ExceptionMask + { + inexactException = 0x01, + underflowException = 0x02, + overflowException = 0x04, + divByZeroException = 0x08, + invalidException = 0x10, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (MIPS_Any) + { + enum : ExceptionMask + { + inexactException = 0x0080, + divByZeroException = 0x0400, + overflowException = 0x0200, + underflowException = 0x0100, + invalidException = 0x0800, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (SPARC_Any) + { + enum : ExceptionMask + { + inexactException = 0x0800000, + divByZeroException = 0x1000000, + overflowException = 0x4000000, + underflowException = 0x2000000, + invalidException = 0x8000000, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (IBMZ_Any) + { + 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"); + + version (ARM_Any) + { + static bool hasExceptionTraps_impl() @safe + { + 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; + } + } + + /// Returns: true if the current FPU supports exception trapping + @property static bool hasExceptionTraps() @safe pure + { + version (X86_Any) + return true; + else version (PPC_Any) + return true; + else version (MIPS_Any) + return true; + else version (ARM_Any) + { + // The hasExceptionTraps_impl function is basically pure, + // as it restores all global state + auto fptr = ( () @trusted => cast(bool function() @safe + pure nothrow @nogc)&hasExceptionTraps_impl)(); + return fptr(); + } + else + assert(0, "Not yet supported"); + } + + /// Enable (unmask) specific hardware exceptions. Multiple exceptions may be ORed together. + void enableExceptions(ExceptionMask exceptions) @trusted + { + 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) @trusted + { + 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() @trusted pure + { + 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() @trusted + { + clearExceptions(); + if (initialized) + setControlState(savedState); + } + +private: + ControlState savedState; + + bool initialized = false; + + version (ARM_Any) + { + alias ControlState = uint; + } + else version (HPPA) + { + alias ControlState = uint; + } + else version (PPC_Any) + { + alias ControlState = uint; + } + else version (RISCV_Any) + { + alias ControlState = uint; + } + else version (MIPS_Any) + { + alias ControlState = uint; + } + else version (SPARC_Any) + { + alias ControlState = ulong; + } + else version (IBMZ_Any) + { + alias ControlState = uint; + } + else version (X86_Any) + { + alias ControlState = ushort; + } + else + static assert(false, "Not implemented for this architecture"); + + void initialize() @safe + { + // BUG: This works around the absence of this() constructors. + if (initialized) return; + clearExceptions(); + savedState = getControlState(); + initialized = true; + } + + // Clear all pending exceptions + static void clearExceptions() @safe + { + version (IeeeFlagsSupport) + resetIeeeFlags(); + else + static assert(false, "Not implemented for this architecture"); + } + + // Read from the control register + package(std.math) static ControlState getControlState() @trusted pure + { + 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 version (RISCV_Any) + { + version (D_SoftFloat) + return 0; + else + { + ControlState cont; + asm pure nothrow @nogc + { + "frcsr %0" : "=r" (cont); + } + return cont; + } + } + else + assert(0, "Not yet supported"); + } + else + version (D_InlineAsm_X86) + { + short cont; + asm pure nothrow @nogc + { + xor EAX, EAX; + fstcw cont; + } + return cont; + } + else version (D_InlineAsm_X86_64) + { + short cont; + asm pure nothrow @nogc + { + xor RAX, RAX; + fstcw cont; + } + return cont; + } + else version (RISCV_Any) + { + mixin(` + ControlState cont; + asm pure nothrow @nogc + { + "frcsr %0" : "=r" (cont); + } + return cont; + `); + } + else + assert(0, "Not yet supported"); + } + + // Set the control register + package(std.math) static void setControlState(ControlState newState) @trusted + { + version (GNU) + { + version (X86_Any) + { + asm nothrow @nogc + { + "fclex; fldcw %0" : : "m" (newState); + } + + // Also update MXCSR, SSE's control register. + if (haveSSE) + { + uint mxcsr; + asm 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 nothrow @nogc + { + "ldmxcsr %0" : : "m" (mxcsr); + } + } + } + else version (AArch64) + { + asm nothrow @nogc + { + "msr FPCR, %0;" : : "r" (newState); + } + } + else version (ARM) + { + version (ARM_SoftFloat) + return; + else + { + asm nothrow @nogc + { + "vmsr FPSCR, %0" : : "r" (newState); + } + } + } + else version (RISCV_Any) + { + version (D_SoftFloat) + return; + else + { + asm nothrow @nogc + { + "fscsr %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 version (RISCV_Any) + { + mixin(` + asm pure nothrow @nogc + { + "fscsr %0" : : "r" (newState); + } + `); + } + else + assert(0, "Not yet supported"); + } +} + +/// +version (FloatingPointControlUnittest) +@safe unittest +{ + import std.math.rounding : lrint; + + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundDown; + assert(lrint(1.5) == 1.0); + + fpctrl.rounding = FloatingPointControl.roundUp; + assert(lrint(1.4) == 2.0); + + fpctrl.rounding = FloatingPointControl.roundToNearest; + assert(lrint(1.5) == 2.0); +} + +@safe unittest +{ + void ensureDefaults() + { + assert(FloatingPointControl.rounding + == FloatingPointControl.roundToNearest); + if (FloatingPointControl.hasExceptionTraps) + assert(FloatingPointControl.enabledExceptions == 0); + } + + { + FloatingPointControl ctrl; + } + ensureDefaults(); + + { + 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(); +} + +version (FloatingPointControlUnittest) +@safe unittest // rounding +{ + import std.meta : AliasSeq; + + static T addRound(T)(uint rm) + { + pragma(inline, false) static void blockopt(ref T x) {} + pragma(inline, false); + FloatingPointControl fpctrl; + fpctrl.rounding = rm; + T x = 1; + blockopt(x); // avoid constant propagation by the optimizer + x += 0.1L; + return x; + } + + static T subRound(T)(uint rm) + { + pragma(inline, false) static void blockopt(ref T x) {} + pragma(inline, false); + FloatingPointControl fpctrl; + fpctrl.rounding = rm; + T x = -1; + blockopt(x); // avoid constant propagation by the optimizer + x -= 0.1L; + return x; + } + + static foreach (T; AliasSeq!(float, double, real)) + {{ + /* Be careful with changing the rounding mode, it interferes + * with common subexpressions. Changing rounding modes should + * be done with separate functions that are not inlined. + */ + + { + T u = addRound!(T)(FloatingPointControl.roundUp); + T d = addRound!(T)(FloatingPointControl.roundDown); + T z = addRound!(T)(FloatingPointControl.roundToZero); + + assert(u > d); + assert(z == d); + } + + { + T u = subRound!(T)(FloatingPointControl.roundUp); + T d = subRound!(T)(FloatingPointControl.roundDown); + T z = subRound!(T)(FloatingPointControl.roundToZero); + + assert(u > d); + assert(z == u); + } + }} +} + +} diff --git a/libphobos/src/std/math/operations.d b/libphobos/src/std/math/operations.d new file mode 100644 index 0000000..dfb1aee --- /dev/null +++ b/libphobos/src/std/math/operations.d @@ -0,0 +1,1998 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains several functions for work with floating point numbers. + +Copyright: Copyright The D Language Foundation 2000 - 2011. +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/operations.d) + +Macros: + TABLE_SV = + + $0
Special Values
+ SVH = $(TR $(TH $1) $(TH $2)) + SV = $(TR $(TD $1) $(TD $2)) + NAN = $(RED NAN) + PLUSMN = ± + INFIN = ∞ + LT = < + GT = > + */ + +module std.math.operations; + +import std.traits : CommonType, isFloatingPoint, isIntegral, Unqual; + +// 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 +{ + import std.math : floatTraits, RealFormat; + + alias F = floatTraits!(real); + static if (F.realFormat == RealFormat.ieeeExtended || + F.realFormat == RealFormat.ieeeExtended53) + { + // 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 + } + if (__ctfe) + { + v = 1; // We use a double in CTFE. + assert(payload >>> 51 == 0, + "Cannot set more than 51 bits of NaN payload in CTFE."); + } + + + 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; + + if (__ctfe) + { + v |= 0x7FF0_0000_0000_0000; + return *cast(double*) &v; + } + else 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; + } +} + +/// +@safe @nogc pure nothrow unittest +{ + import std.math.traits : isNaN; + + real a = NaN(1_000_000); + assert(isNaN(a)); + assert(getNaNPayload(a) == 1_000_000); +} + +@system pure nothrow @nogc unittest // not @safe because taking address of local. +{ + import std.math : floatTraits, RealFormat; + + 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 +{ + import std.math : floatTraits, RealFormat; + + // assert(isNaN(x)); + alias F = floatTraits!(real); + ulong m = void; + if (__ctfe) + { + double y = x; + m = *cast(ulong*) &y; + // 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.ieeeDouble) + { + 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) + { + m = *cast(ulong*)(6+cast(ubyte*)(&x)); + } + else + { + m = *cast(ulong*)(2+cast(ubyte*)(&x)); + } + + m >>= 1; // there's no implicit bit + } + else + { + 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; +} + +/// +@safe @nogc pure nothrow unittest +{ + import std.math.traits : isNaN; + + real a = NaN(1_000_000); + assert(isNaN(a)); + assert(getNaNPayload(a) == 1_000_000); +} + +@safe @nogc pure nothrow unittest +{ + import std.math.traits : isIdentical, isNaN; + + enum real a = NaN(1_000_000); + static assert(isNaN(a)); + static assert(getNaNPayload(a) == 1_000_000); + real b = NaN(1_000_000); + assert(isIdentical(b, a)); + // The CTFE version of getNaNPayload relies on it being impossible + // for a CTFE-constructed NaN to have more than 51 bits of payload. + enum nanNaN = NaN(getNaNPayload(real.nan)); + assert(isIdentical(real.nan, nanNaN)); + static if (real.init != real.init) + { + enum initNaN = NaN(getNaNPayload(real.init)); + assert(isIdentical(real.init, initNaN)); + } +} + +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 +{ + import std.math : floatTraits, RealFormat, MANTISSA_MSB, MANTISSA_LSB; + + alias F = floatTraits!(real); + static if (F.realFormat != RealFormat.ieeeDouble) + { + if (__ctfe) + { + if (x == -real.infinity) + return -real.max; + if (!(x < real.infinity)) // Infinity or NaN. + return x; + real delta; + // Start with a decent estimate of delta. + if (x <= 0x1.ffffffffffffep+1023 && x >= -double.max) + { + const double d = cast(double) x; + delta = (cast(real) nextUp(d) - cast(real) d) * 0x1p-11L; + while (x + (delta * 0x1p-100L) > x) + delta *= 0x1p-100L; + } + else + { + delta = 0x1p960L; + while (!(x + delta > x) && delta < real.max * 0x1p-100L) + delta *= 0x1p100L; + } + if (x + delta > x) + { + while (x + (delta / 2) > x) + delta /= 2; + } + else + { + do { delta += delta; } while (!(x + delta > x)); + } + if (x < 0 && x + delta == 0) + return -0.0L; + return x + delta; + } + } + 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + // For 80-bit reals, the "implied bit" is a nuisance... + ushort *pe = cast(ushort *)&x; + ulong *ps = cast(ulong *)&x; + // EPSILON is 1 for 64-bit, and 2048 for 53-bit precision reals. + enum ulong EPSILON = 2UL ^^ (64 - real.mant_dig); + + 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 -= EPSILON; + // 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 += EPSILON; + 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 s = *cast(ulong *)&x; + + if ((s & 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 (s & 0x8000_0000_0000_0000) // Negative number + { + if (s == 0x8000_0000_0000_0000) // it was negative zero + { + s = 0x0000_0000_0000_0001; // change to smallest subnormal + return *cast(double*) &s; + } + --s; + } + else + { // Positive number + ++s; + } + return *cast(double*) &s; +} + +/** ditto */ +float nextUp(float x) @trusted pure nothrow @nogc +{ + uint s = *cast(uint *)&x; + + if ((s & 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 (s & 0x8000_0000) // Negative number + { + if (s == 0x8000_0000) // it was negative zero + { + s = 0x0000_0001; // change to smallest subnormal + return *cast(float*) &s; + } + + --s; + } + else + { + // Positive number + ++s; + } + return *cast(float*) &s; +} + +/// +@safe @nogc pure nothrow unittest +{ + assert(nextUp(1.0 - 1.0e-6).feqrel(0.999999) > 16); + assert(nextUp(1.0 - real.epsilon).feqrel(1.0) > 16); +} + +/** + * 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 +{ + import std.math : floatTraits, RealFormat; + import std.math.traits : isIdentical; + + static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended || + floatTraits!(real).realFormat == RealFormat.ieeeDouble || + floatTraits!(real).realFormat == RealFormat.ieeeExtended53 || + floatTraits!(real).realFormat == RealFormat.ieeeQuadruple) + { + // Tests for reals + assert(isIdentical(nextUp(NaN(0xABC)), NaN(0xABC))); + //static 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); + static assert( nextUp(-real.infinity) == -real.max ); + static assert( nextUp(-1.0L-real.epsilon) == -1.0 ); + static 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) ); + static assert( nextUp(-real.min_normal) == -real.min_normal*(1-real.epsilon) ); + static assert( nextUp(-real.min_normal*(1-real.epsilon)) == -real.min_normal*(1-2*real.epsilon) ); + static assert( -0.0L is nextUp(-real.min_normal*real.epsilon) ); + static assert( nextUp(-0.0L) == real.min_normal*real.epsilon ); + static assert( nextUp(0.0L) == real.min_normal*real.epsilon ); + static assert( nextUp(real.min_normal*(1-real.epsilon)) == real.min_normal ); + static 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 ); + static assert( nextUp(1.0L) == 1.0 + real.epsilon ); + static assert( nextUp(2.0L-real.epsilon) == 2.0 ); + static assert( nextUp(real.max) == real.infinity ); + static assert( nextUp(real.infinity)==real.infinity ); + // ctfe near double.max boundary + static assert(nextUp(nextDown(cast(real) double.max)) == cast(real) double.max); + } + + 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); + + // CTFE + + enum double ctfe_n = NaN(0xABC); + //static assert(isIdentical(nextUp(ctfe_n), ctfe_n)); // FIXME: https://issues.dlang.org/show_bug.cgi?id=20197 + static assert(nextUp(double.nan) is double.nan); + // negative numbers + static assert( nextUp(-double.infinity) == -double.max ); + static assert( nextUp(-1-double.epsilon) == -1.0 ); + static assert( nextUp(-2.0) == -2.0 + double.epsilon); + // subnormals and zero + + static assert( nextUp(-double.min_normal) == -double.min_normal*(1-double.epsilon) ); + static assert( nextUp(-double.min_normal*(1-double.epsilon)) == -double.min_normal*(1-2*double.epsilon) ); + static assert( -0.0 is nextUp(-double.min_normal*double.epsilon) ); + static assert( nextUp(0.0) == double.min_normal*double.epsilon ); + static assert( nextUp(-0.0) == double.min_normal*double.epsilon ); + static assert( nextUp(double.min_normal*(1-double.epsilon)) == double.min_normal ); + static assert( nextUp(double.min_normal) == double.min_normal*(1+double.epsilon) ); + // positive numbers + static assert( nextUp(1.0) == 1.0 + double.epsilon ); + static assert( nextUp(2.0-double.epsilon) == 2.0 ); + static assert( nextUp(double.max) == double.infinity ); + + enum float ctfe_fn = NaN(0xABC); + //static assert(isIdentical(nextUp(ctfe_fn), ctfe_fn)); // FIXME: https://issues.dlang.org/show_bug.cgi?id=20197 + static assert(nextUp(float.nan) is float.nan); + static assert(nextUp(-float.min_normal) == -float.min_normal*(1-float.epsilon)); + static assert(nextUp(1.0f) == 1.0f+float.epsilon); + static assert(nextUp(-0.0f) == float.min_normal*float.epsilon); + static assert(nextUp(float.infinity)==float.infinity); + static assert(nextDown(1.0L+real.epsilon)==1.0); + static assert(nextDown(1.0+double.epsilon)==1.0); + static assert(nextDown(1.0f+float.epsilon)==1.0); + static 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. + * If x or y is a NaN, the result is a NaN. + * + * 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 +{ + import std.math.traits : isNaN; + + if (x == y || isNaN(y)) + { + return y; + } + + if (isNaN(x)) + { + return x; + } + + return ((y>x) ? nextUp(x) : nextDown(x)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN; + + float a = 1; + assert(is(typeof(nextafter(a, a)) == float)); + assert(nextafter(a, a.infinity) > a); + assert(isNaN(nextafter(a, a.nan))); + assert(isNaN(nextafter(a.nan, a))); + + double b = 2; + assert(is(typeof(nextafter(b, b)) == double)); + assert(nextafter(b, b.infinity) > b); + assert(isNaN(nextafter(b, b.nan))); + assert(isNaN(nextafter(b.nan, b))); + + real c = 3; + assert(is(typeof(nextafter(c, c)) == real)); + assert(nextafter(c, c.infinity) > c); + assert(isNaN(nextafter(c, c.nan))); + assert(isNaN(nextafter(c.nan, c))); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN, signbit; + + // CTFE + enum float a = 1; + static assert(is(typeof(nextafter(a, a)) == float)); + static assert(nextafter(a, a.infinity) > a); + static assert(isNaN(nextafter(a, a.nan))); + static assert(isNaN(nextafter(a.nan, a))); + + enum double b = 2; + static assert(is(typeof(nextafter(b, b)) == double)); + static assert(nextafter(b, b.infinity) > b); + static assert(isNaN(nextafter(b, b.nan))); + static assert(isNaN(nextafter(b.nan, b))); + + enum real c = 3; + static assert(is(typeof(nextafter(c, c)) == real)); + static assert(nextafter(c, c.infinity) > c); + static assert(isNaN(nextafter(c, c.nan))); + static assert(isNaN(nextafter(c.nan, c))); + + enum real negZero = nextafter(+0.0L, -0.0L); + static assert(negZero == -0.0L); + static assert(signbit(negZero)); + + static assert(nextafter(c, c) == c); +} + +//real nexttoward(real x, real y) { return core.stdc.math.nexttowardl(x, y); } + +/** + * Returns the positive difference between x and y. + * + * Equivalent to `fmax(x-y, 0)`. + * + * 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) ? +0.0 : x - y; +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN; + + assert(fdim(2.0, 0.0) == 2.0); + assert(fdim(-2.0, 0.0) == 0.0); + assert(fdim(real.infinity, 2.0) == real.infinity); + assert(isNaN(fdim(real.nan, 2.0))); + assert(isNaN(fdim(2.0, real.nan))); + assert(isNaN(fdim(real.nan, real.nan))); +} + +/** + * Returns the larger of `x` and `y`. + * + * If one of the arguments is a `NaN`, the other is returned. + * + * See_Also: $(REF max, std,algorithm,comparison) is faster because it does not perform the `isNaN` test. + */ +F fmax(F)(const F x, const F y) @safe pure nothrow @nogc +if (__traits(isFloating, F)) +{ + import std.math.traits : isNaN; + + // Do the more predictable test first. Generates 0 branches with ldc and 1 branch with gdc. + // See https://godbolt.org/z/erxrW9 + if (isNaN(x)) return y; + return y > x ? y : x; +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + static foreach (F; AliasSeq!(float, double, real)) + { + assert(fmax(F(0.0), F(2.0)) == 2.0); + assert(fmax(F(-2.0), 0.0) == F(0.0)); + assert(fmax(F.infinity, F(2.0)) == F.infinity); + assert(fmax(F.nan, F(2.0)) == F(2.0)); + assert(fmax(F(2.0), F.nan) == F(2.0)); + } +} + +/** + * Returns the smaller of `x` and `y`. + * + * If one of the arguments is a `NaN`, the other is returned. + * + * See_Also: $(REF min, std,algorithm,comparison) is faster because it does not perform the `isNaN` test. + */ +F fmin(F)(const F x, const F y) @safe pure nothrow @nogc +if (__traits(isFloating, F)) +{ + import std.math.traits : isNaN; + + // Do the more predictable test first. Generates 0 branches with ldc and 1 branch with gdc. + // See https://godbolt.org/z/erxrW9 + if (isNaN(x)) return y; + return y < x ? y : x; +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + static foreach (F; AliasSeq!(float, double, real)) + { + assert(fmin(F(0.0), F(2.0)) == 0.0); + assert(fmin(F(-2.0), F(0.0)) == -2.0); + assert(fmin(F.infinity, F(2.0)) == 2.0); + assert(fmin(F.nan, F(2.0)) == 2.0); + assert(fmin(F(2.0), F.nan) == 2.0); + } +} + +/************************************** + * Returns (x * y) + z, rounding only once according to the + * current rounding mode. + * + * BUGS: Not currently implemented - rounds twice. + */ +pragma(inline, true) +real fma(real x, real y, real z) @safe pure nothrow @nogc { return (x * y) + z; } + +/// +@safe pure nothrow @nogc unittest +{ + assert(fma(0.0, 2.0, 2.0) == 2.0); + assert(fma(2.0, 2.0, 2.0) == 6.0); + assert(fma(real.infinity, 2.0, 2.0) == real.infinity); + assert(fma(real.nan, 2.0, 2.0) is real.nan); + assert(fma(2.0, 2.0, real.nan) is real.nan); +} + +/************************************** + * 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)) +{ + import std.math : floatTraits, RealFormat; + import core.math : fabs; + + /* Public Domain. Author: Don Clugston, 18 Aug 2005. + */ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ieeeSingle + || F.realFormat == RealFormat.ieeeDouble + || F.realFormat == RealFormat.ieeeExtended + || F.realFormat == RealFormat.ieeeExtended53 + || 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; + } + else + { + static assert(false, "Not implemented for this architecture"); + } +} + +/// +@safe pure unittest +{ + assert(feqrel(2.0, 2.0) == 53); + assert(feqrel(2.0f, 2.0f) == 24); + assert(feqrel(2.0, double.nan) == 0); + + // Test that numbers are within n digits of each + // other by testing if feqrel > n * log2(10) + + // five digits + assert(feqrel(2.0, 2.00001) > 16); + // ten digits + assert(feqrel(2.0, 2.00000000001) > 33); +} + +@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)(); +} + +/** + Computes whether a values is approximately equal to a reference value, + admitting a maximum relative difference, and a maximum absolute difference. + + Warning: + This template is considered out-dated. It will be removed from + Phobos in 2.106.0. Please use $(LREF isClose) instead. To achieve + a similar behaviour to `approxEqual(a, b)` use + `isClose(a, b, 1e-2, 1e-5)`. In case of comparing to 0.0, + `isClose(a, b, 0.0, eps)` should be used, where `eps` + represents the accepted deviation from 0.0." + + Params: + value = Value to compare. + reference = Reference value. + maxRelDiff = Maximum allowable difference relative to `reference`. + Setting to 0.0 disables this check. Defaults to `1e-2`. + maxAbsDiff = Maximum absolute difference. This is mainly usefull + for comparing values to zero. Setting to 0.0 disables this check. + Defaults to `1e-5`. + + Returns: + `true` if `value` is approximately equal to `reference` under + either criterium. It is sufficient, when `value ` satisfies + one of the two criteria. + + 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. + + See_Also: + Use $(LREF feqrel) to get the number of equal bits in the mantissa. + */ +deprecated("approxEqual will be removed in 2.106.0. Please use isClose instead.") +bool approxEqual(T, U, V)(T value, U reference, V maxRelDiff = 1e-2, V maxAbsDiff = 1e-5) +{ + import core.math : fabs; + import std.range.primitives : empty, front, isInputRange, popFront; + static if (isInputRange!T) + { + static if (isInputRange!U) + { + // Two ranges + for (;; value.popFront(), reference.popFront()) + { + if (value.empty) return reference.empty; + if (reference.empty) return value.empty; + if (!approxEqual(value.front, reference.front, maxRelDiff, maxAbsDiff)) + return false; + } + } + else static if (isIntegral!U) + { + // convert reference to real + return approxEqual(value, real(reference), maxRelDiff, maxAbsDiff); + } + else + { + // value is range, reference is number + for (; !value.empty; value.popFront()) + { + if (!approxEqual(value.front, reference, maxRelDiff, maxAbsDiff)) + return false; + } + return true; + } + } + else + { + static if (isInputRange!U) + { + // value is number, reference is range + for (; !reference.empty; reference.popFront()) + { + if (!approxEqual(value, reference.front, maxRelDiff, maxAbsDiff)) + return false; + } + return true; + } + else static if (isIntegral!T || isIntegral!U) + { + // convert both value and reference to real + return approxEqual(real(value), real(reference), maxRelDiff, maxAbsDiff); + } + else + { + // two numbers + //static assert(is(T : real) && is(U : real)); + if (reference == 0) + { + return fabs(value) <= maxAbsDiff; + } + static if (is(typeof(value.infinity)) && is(typeof(reference.infinity))) + { + if (value == value.infinity && reference == reference.infinity || + value == -value.infinity && reference == -reference.infinity) return true; + } + return fabs((value - reference) / reference) <= maxRelDiff + || maxAbsDiff != 0 && fabs(value - reference) <= maxAbsDiff; + } + } +} + +deprecated @safe pure nothrow unittest +{ + assert(approxEqual(1.0, 1.0099)); + assert(!approxEqual(1.0, 1.011)); + assert(approxEqual(0.00001, 0.0)); + assert(!approxEqual(0.00002, 0.0)); + + assert(approxEqual(3.0, [3, 3.01, 2.99])); // several reference values is strange + assert(approxEqual([3, 3.01, 2.99], 3.0)); // better + + float[] arr1 = [ 1.0, 2.0, 3.0 ]; + double[] arr2 = [ 1.001, 1.999, 3 ]; + assert(approxEqual(arr1, arr2)); +} + +deprecated @safe pure nothrow unittest +{ + // relative comparison depends on reference, make sure proper + // side is used when comparing range to single value. Based on + // https://issues.dlang.org/show_bug.cgi?id=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)); +} + +deprecated @safe pure nothrow @nogc unittest +{ + assert(!approxEqual(0.0,1e-15,1e-9,0.0)); + assert(approxEqual(0.0,1e-15,1e-9,1e-9)); + assert(!approxEqual(1.0,3.0,0.0,1.0)); + + assert(approxEqual(1.00000000099,1.0,1e-9,0.0)); + assert(!approxEqual(1.0000000011,1.0,1e-9,0.0)); +} + +deprecated @safe pure nothrow @nogc unittest +{ + // maybe unintuitive behavior + assert(approxEqual(1000.0,1010.0)); + assert(approxEqual(9_090_000_000.0,9_000_000_000.0)); + assert(approxEqual(0.0,1e30,1.0)); + assert(approxEqual(0.00001,1e-30)); + assert(!approxEqual(-1e-30,1e-30,1e-2,0.0)); +} + +deprecated @safe pure nothrow @nogc unittest +{ + int a = 10; + assert(approxEqual(10, a)); + + assert(!approxEqual(3, 0)); + assert(approxEqual(3, 3)); + assert(approxEqual(3.0, 3)); + assert(approxEqual(3, 3.0)); + + assert(approxEqual(0.0,0.0)); + assert(approxEqual(-0.0,0.0)); + assert(approxEqual(0.0f,0.0)); +} + +deprecated @safe pure nothrow @nogc unittest +{ + real num = real.infinity; + assert(num == real.infinity); + assert(approxEqual(num, real.infinity)); + num = -real.infinity; + assert(num == -real.infinity); + assert(approxEqual(num, -real.infinity)); + + assert(!approxEqual(1,real.nan)); + assert(!approxEqual(real.nan,real.max)); + assert(!approxEqual(real.nan,real.nan)); +} + +deprecated @safe pure nothrow unittest +{ + assert(!approxEqual([1.0,2.0,3.0],[1.0,2.0])); + assert(!approxEqual([1.0,2.0],[1.0,2.0,3.0])); + + assert(approxEqual!(real[],real[])([],[])); + assert(approxEqual(cast(real[])[],cast(real[])[])); +} + + +/** + 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 relative difference. + Setting to 0.0 disables this check. Default depends on the type of + `lhs` and `rhs`: It is approximately half the number of decimal digits of + precision of the smaller type. + maxAbsDiff = Maximum absolute difference. This is mainly usefull + for comparing values to zero. Setting to 0.0 disables this check. + Defaults to `0.0`. + + Returns: + `true` if the two items are approximately equal under either criterium. + It is sufficient, when `value ` satisfies one of the two criteria. + + If one item is a range, and the other is a single value, then + the result is the logical and-ing of calling `isClose` on + each element of the ranged item against the single item. If + both items are ranges, then `isClose` returns `true` if + and only if the ranges have the same number of elements and if + `isClose` evaluates to `true` for each pair of elements. + + See_Also: + Use $(LREF feqrel) to get the number of equal bits in the mantissa. + */ +bool isClose(T, U, V = CommonType!(FloatingPointBaseType!T,FloatingPointBaseType!U)) + (T lhs, U rhs, V maxRelDiff = CommonDefaultFor!(T,U), V maxAbsDiff = 0.0) +{ + import std.range.primitives : empty, front, isInputRange, popFront; + import std.complex : Complex; + 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 (!isClose(lhs.front, rhs.front, maxRelDiff, maxAbsDiff)) + return false; + } + } + else + { + // lhs is range, rhs is number + for (; !lhs.empty; lhs.popFront()) + { + if (!isClose(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 (!isClose(lhs, rhs.front, maxRelDiff, maxAbsDiff)) + return false; + } + return true; + } + else static if (is(T TE == Complex!TE)) + { + static if (is(U UE == Complex!UE)) + { + // Two complex numbers + return isClose(lhs.re, rhs.re, maxRelDiff, maxAbsDiff) + && isClose(lhs.im, rhs.im, maxRelDiff, maxAbsDiff); + } + else + { + // lhs is complex, rhs is number + return isClose(lhs.re, rhs, maxRelDiff, maxAbsDiff) + && isClose(lhs.im, 0.0, maxRelDiff, maxAbsDiff); + } + } + else static if (is(U UE == Complex!UE)) + { + // lhs is number, rhs is complex + return isClose(lhs, rhs.re, maxRelDiff, maxAbsDiff) + && isClose(0.0, rhs.im, maxRelDiff, maxAbsDiff); + } + else + { + // two numbers + if (lhs == rhs) return true; + + static if (is(typeof(lhs.infinity)) && is(typeof(rhs.infinity))) + { + if (lhs == lhs.infinity || rhs == rhs.infinity || + lhs == -lhs.infinity || rhs == -rhs.infinity) return false; + } + + import std.math.algebraic : abs; + + auto diff = abs(lhs - rhs); + + return diff <= maxRelDiff*abs(lhs) + || diff <= maxRelDiff*abs(rhs) + || diff <= maxAbsDiff; + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(isClose(1.0,0.999_999_999)); + assert(isClose(0.001, 0.000_999_999_999)); + assert(isClose(1_000_000_000.0,999_999_999.0)); + + assert(isClose(17.123_456_789, 17.123_456_78)); + assert(!isClose(17.123_456_789, 17.123_45)); + + // use explicit 3rd parameter for less (or more) accuracy + assert(isClose(17.123_456_789, 17.123_45, 1e-6)); + assert(!isClose(17.123_456_789, 17.123_45, 1e-7)); + + // use 4th parameter when comparing close to zero + assert(!isClose(1e-100, 0.0)); + assert(isClose(1e-100, 0.0, 0.0, 1e-90)); + assert(!isClose(1e-10, -1e-10)); + assert(isClose(1e-10, -1e-10, 0.0, 1e-9)); + assert(!isClose(1e-300, 1e-298)); + assert(isClose(1e-300, 1e-298, 0.0, 1e-200)); + + // different default limits for different floating point types + assert(isClose(1.0f, 0.999_99f)); + assert(!isClose(1.0, 0.999_99)); + static if (real.sizeof > double.sizeof) + assert(!isClose(1.0L, 0.999_999_999L)); +} + +/// +@safe pure nothrow unittest +{ + assert(isClose([1.0, 2.0, 3.0], [0.999_999_999, 2.000_000_001, 3.0])); + assert(!isClose([1.0, 2.0], [0.999_999_999, 2.000_000_001, 3.0])); + assert(!isClose([1.0, 2.0, 3.0], [0.999_999_999, 2.000_000_001])); + + assert(isClose([2.0, 1.999_999_999, 2.000_000_001], 2.0)); + assert(isClose(2.0, [2.0, 1.999_999_999, 2.000_000_001])); +} + +@safe pure nothrow unittest +{ + assert(!isClose([1.0, 2.0, 3.0], [0.999_999_999, 3.0, 3.0])); + assert(!isClose([2.0, 1.999_999, 2.000_000_001], 2.0)); + assert(!isClose(2.0, [2.0, 1.999_999_999, 2.000_000_999])); +} + +@safe pure nothrow @nogc unittest +{ + immutable a = 1.00001f; + const b = 1.000019; + assert(isClose(a,b)); + + assert(isClose(1.00001f,1.000019f)); + assert(isClose(1.00001f,1.000019)); + assert(isClose(1.00001,1.000019f)); + assert(!isClose(1.00001,1.000019)); + + real a1 = 1e-300L; + real a2 = a1.nextUp; + assert(isClose(a1,a2)); +} + +@safe pure nothrow unittest +{ + float[] arr1 = [ 1.0, 2.0, 3.0 ]; + double[] arr2 = [ 1.00001, 1.99999, 3 ]; + assert(isClose(arr1, arr2)); +} + +@safe pure nothrow @nogc unittest +{ + assert(!isClose(1000.0,1010.0)); + assert(!isClose(9_090_000_000.0,9_000_000_000.0)); + assert(isClose(0.0,1e30,1.0)); + assert(!isClose(0.00001,1e-30)); + assert(!isClose(-1e-30,1e-30,1e-2,0.0)); +} + +@safe pure nothrow @nogc unittest +{ + assert(!isClose(3, 0)); + assert(isClose(3, 3)); + assert(isClose(3.0, 3)); + assert(isClose(3, 3.0)); + + assert(isClose(0.0,0.0)); + assert(isClose(-0.0,0.0)); + assert(isClose(0.0f,0.0)); +} + +@safe pure nothrow @nogc unittest +{ + real num = real.infinity; + assert(num == real.infinity); + assert(isClose(num, real.infinity)); + num = -real.infinity; + assert(num == -real.infinity); + assert(isClose(num, -real.infinity)); + + assert(!isClose(1,real.nan)); + assert(!isClose(real.nan,real.max)); + assert(!isClose(real.nan,real.nan)); +} + +@safe pure nothrow @nogc unittest +{ + assert(isClose!(real[],real[],real)([],[])); + assert(isClose(cast(real[])[],cast(real[])[])); +} + +@safe pure nothrow @nogc unittest +{ + import std.conv : to; + + float f = 31.79f; + double d = 31.79; + double f2d = f.to!double; + + assert(isClose(f,f2d)); + assert(!isClose(d,f2d)); +} + +@safe pure nothrow @nogc unittest +{ + import std.conv : to; + + double d = 31.79; + float f = d.to!float; + double f2d = f.to!double; + + assert(isClose(f,f2d)); + assert(!isClose(d,f2d)); + assert(isClose(d,f2d,1e-4)); +} + +package(std.math) template CommonDefaultFor(T,U) +{ + import std.algorithm.comparison : min; + + alias baseT = FloatingPointBaseType!T; + alias baseU = FloatingPointBaseType!U; + + enum CommonType!(baseT, baseU) CommonDefaultFor = 10.0L ^^ -((min(baseT.dig, baseU.dig) + 1) / 2 + 1); +} + +private template FloatingPointBaseType(T) +{ + import std.range.primitives : ElementType; + static if (isFloatingPoint!T) + { + alias FloatingPointBaseType = Unqual!T; + } + else static if (isFloatingPoint!(ElementType!(Unqual!T))) + { + alias FloatingPointBaseType = Unqual!(ElementType!(Unqual!T)); + } + else + { + alias FloatingPointBaseType = real; + } +} + +/*********************************** + * 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 `x` precedes `y` in the order specified above; + * 0 if `x` and `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) +{ + import std.math : floatTraits, RealFormat; + + 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 + + import std.math.traits : signbit, isNaN; + + 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; + static 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); + } + }} +} + +package(std): // not yet public + +struct FloatingPointBitpattern(T) +if (isFloatingPoint!T) +{ + static if (T.mant_dig <= 64) + { + ulong mantissa; + } + else + { + ulong mantissa_lsb; + ulong mantissa_msb; + } + + int exponent; + bool negative; +} + +FloatingPointBitpattern!T extractBitpattern(T)(T val) @trusted +if (isFloatingPoint!T) +{ + import std.math : floatTraits, RealFormat; + + FloatingPointBitpattern!T ret; + + alias F = floatTraits!T; + static if (F.realFormat == RealFormat.ieeeExtended) + { + if (__ctfe) + { + import core.math : fabs, ldexp; + import std.math.rounding : floor; + import std.math.traits : isInfinity, isNaN, signbit; + import std.math.exponential : log2; + + if (isNaN(val) || isInfinity(val)) + ret.exponent = 32767; + else if (fabs(val) < real.min_normal) + ret.exponent = 0; + else if (fabs(val) >= nextUp(real.max / 2)) + ret.exponent = 32766; + else + ret.exponent = cast(int) (val.fabs.log2.floor() + 16383); + + if (ret.exponent == 32767) + { + // NaN or infinity + ret.mantissa = isNaN(val) ? ((1L << 63) - 1) : 0; + } + else + { + auto delta = 16382 + 64 // bias + bits of ulong + - (ret.exponent == 0 ? 1 : ret.exponent); // -1 in case of subnormals + val = ldexp(val, delta); // val *= 2^^delta + + ulong tmp = cast(ulong) fabs(val); + if (ret.exponent != 32767 && ret.exponent > 0 && tmp <= ulong.max / 2) + { + // correction, due to log2(val) being rounded up: + ret.exponent--; + val *= 2; + tmp = cast(ulong) fabs(val); + } + + ret.mantissa = tmp & long.max; + } + + ret.negative = (signbit(val) == 1); + } + else + { + ushort* vs = cast(ushort*) &val; + ret.mantissa = (cast(ulong*) vs)[0] & long.max; + ret.exponent = vs[4] & short.max; + ret.negative = (vs[4] >> 15) & 1; + } + } + else + { + static if (F.realFormat == RealFormat.ieeeSingle) + { + ulong ival = *cast(uint*) &val; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + ulong ival = *cast(ulong*) &val; + } + else + { + static assert(false, "Floating point type `" ~ F.realFormat ~ "` not supported."); + } + + import std.math.exponential : log2; + enum log2_max_exp = cast(int) log2(T.max_exp); + + ret.mantissa = ival & ((1L << (T.mant_dig - 1)) - 1); + ret.exponent = (ival >> (T.mant_dig - 1)) & ((1L << (log2_max_exp + 1)) - 1); + ret.negative = (ival >> (T.mant_dig + log2_max_exp)) & 1; + } + + // add leading 1 for normalized values and correct exponent for denormalied values + if (ret.exponent != 0 && ret.exponent != 2 * T.max_exp - 1) + ret.mantissa |= 1L << (T.mant_dig - 1); + else if (ret.exponent == 0) + ret.exponent = 1; + + ret.exponent -= T.max_exp - 1; + + return ret; +} + +@safe pure unittest +{ + float f = 1.0f; + auto bp = extractBitpattern(f); + assert(bp.mantissa == 0x80_0000); + assert(bp.exponent == 0); + assert(bp.negative == false); + + f = float.max; + bp = extractBitpattern(f); + assert(bp.mantissa == 0xff_ffff); + assert(bp.exponent == 127); + assert(bp.negative == false); + + f = -1.5432e-17f; + bp = extractBitpattern(f); + assert(bp.mantissa == 0x8e_55c8); + assert(bp.exponent == -56); + assert(bp.negative == true); + + // using double literal due to https://issues.dlang.org/show_bug.cgi?id=20361 + f = 2.3822073893521890206e-44; + bp = extractBitpattern(f); + assert(bp.mantissa == 0x00_0011); + assert(bp.exponent == -126); + assert(bp.negative == false); + + f = -float.infinity; + bp = extractBitpattern(f); + assert(bp.mantissa == 0); + assert(bp.exponent == 128); + assert(bp.negative == true); + + f = float.nan; + bp = extractBitpattern(f); + assert(bp.mantissa != 0); // we don't guarantee payloads + assert(bp.exponent == 128); + assert(bp.negative == false); +} + +@safe pure unittest +{ + double d = 1.0; + auto bp = extractBitpattern(d); + assert(bp.mantissa == 0x10_0000_0000_0000L); + assert(bp.exponent == 0); + assert(bp.negative == false); + + d = double.max; + bp = extractBitpattern(d); + assert(bp.mantissa == 0x1f_ffff_ffff_ffffL); + assert(bp.exponent == 1023); + assert(bp.negative == false); + + d = -1.5432e-222; + bp = extractBitpattern(d); + assert(bp.mantissa == 0x11_d9b6_a401_3b04L); + assert(bp.exponent == -737); + assert(bp.negative == true); + + d = 0.0.nextUp; + bp = extractBitpattern(d); + assert(bp.mantissa == 0x00_0000_0000_0001L); + assert(bp.exponent == -1022); + assert(bp.negative == false); + + d = -double.infinity; + bp = extractBitpattern(d); + assert(bp.mantissa == 0); + assert(bp.exponent == 1024); + assert(bp.negative == true); + + d = double.nan; + bp = extractBitpattern(d); + assert(bp.mantissa != 0); // we don't guarantee payloads + assert(bp.exponent == 1024); + assert(bp.negative == false); +} + +@safe pure unittest +{ + import std.math : floatTraits, RealFormat; + + alias F = floatTraits!real; + static if (F.realFormat == RealFormat.ieeeExtended) + { + real r = 1.0L; + auto bp = extractBitpattern(r); + assert(bp.mantissa == 0x8000_0000_0000_0000L); + assert(bp.exponent == 0); + assert(bp.negative == false); + + r = real.max; + bp = extractBitpattern(r); + assert(bp.mantissa == 0xffff_ffff_ffff_ffffL); + assert(bp.exponent == 16383); + assert(bp.negative == false); + + r = -1.5432e-3333L; + bp = extractBitpattern(r); + assert(bp.mantissa == 0xc768_a2c7_a616_cc22L); + assert(bp.exponent == -11072); + assert(bp.negative == true); + + r = 0.0L.nextUp; + bp = extractBitpattern(r); + assert(bp.mantissa == 0x0000_0000_0000_0001L); + assert(bp.exponent == -16382); + assert(bp.negative == false); + + r = -float.infinity; + bp = extractBitpattern(r); + assert(bp.mantissa == 0); + assert(bp.exponent == 16384); + assert(bp.negative == true); + + r = float.nan; + bp = extractBitpattern(r); + assert(bp.mantissa != 0); // we don't guarantee payloads + assert(bp.exponent == 16384); + assert(bp.negative == false); + + r = nextDown(0x1p+16383L); + bp = extractBitpattern(r); + assert(bp.mantissa == 0xffff_ffff_ffff_ffffL); + assert(bp.exponent == 16382); + assert(bp.negative == false); + } +} + +@safe pure unittest +{ + import std.math : floatTraits, RealFormat; + import std.math.exponential : log2; + + alias F = floatTraits!real; + + // log2 is broken for x87-reals on some computers in CTFE + // the following test excludes these computers from the test + // (issue 21757) + enum test = cast(int) log2(3.05e2312L); + static if (F.realFormat == RealFormat.ieeeExtended && test == 7681) + { + enum r1 = 1.0L; + enum bp1 = extractBitpattern(r1); + static assert(bp1.mantissa == 0x8000_0000_0000_0000L); + static assert(bp1.exponent == 0); + static assert(bp1.negative == false); + + enum r2 = real.max; + enum bp2 = extractBitpattern(r2); + static assert(bp2.mantissa == 0xffff_ffff_ffff_ffffL); + static assert(bp2.exponent == 16383); + static assert(bp2.negative == false); + + enum r3 = -1.5432e-3333L; + enum bp3 = extractBitpattern(r3); + static assert(bp3.mantissa == 0xc768_a2c7_a616_cc22L); + static assert(bp3.exponent == -11072); + static assert(bp3.negative == true); + + enum r4 = 0.0L.nextUp; + enum bp4 = extractBitpattern(r4); + static assert(bp4.mantissa == 0x0000_0000_0000_0001L); + static assert(bp4.exponent == -16382); + static assert(bp4.negative == false); + + enum r5 = -real.infinity; + enum bp5 = extractBitpattern(r5); + static assert(bp5.mantissa == 0); + static assert(bp5.exponent == 16384); + static assert(bp5.negative == true); + + enum r6 = real.nan; + enum bp6 = extractBitpattern(r6); + static assert(bp6.mantissa != 0); // we don't guarantee payloads + static assert(bp6.exponent == 16384); + static assert(bp6.negative == false); + + enum r7 = nextDown(0x1p+16383L); + enum bp7 = extractBitpattern(r7); + static assert(bp7.mantissa == 0xffff_ffff_ffff_ffffL); + static assert(bp7.exponent == 16382); + static assert(bp7.negative == false); + } +} diff --git a/libphobos/src/std/math/package.d b/libphobos/src/std/math/package.d new file mode 100644 index 0000000..7443b0d --- /dev/null +++ b/libphobos/src/std/math/package.d @@ -0,0 +1,494 @@ +// 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 `std.mathspecial`. + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Members) ) +$(TR $(TDNW $(SUBMODULE Constants, constants)) $(TD + $(SUBREF constants, E) + $(SUBREF constants, PI) + $(SUBREF constants, PI_2) + $(SUBREF constants, PI_4) + $(SUBREF constants, M_1_PI) + $(SUBREF constants, M_2_PI) + $(SUBREF constants, M_2_SQRTPI) + $(SUBREF constants, LN10) + $(SUBREF constants, LN2) + $(SUBREF constants, LOG2) + $(SUBREF constants, LOG2E) + $(SUBREF constants, LOG2T) + $(SUBREF constants, LOG10E) + $(SUBREF constants, SQRT2) + $(SUBREF constants, SQRT1_2) +)) +$(TR $(TDNW $(SUBMODULE Algebraic, algebraic)) $(TD + $(SUBREF algebraic, abs) + $(SUBREF algebraic, fabs) + $(SUBREF algebraic, sqrt) + $(SUBREF algebraic, cbrt) + $(SUBREF algebraic, hypot) + $(SUBREF algebraic, poly) + $(SUBREF algebraic, nextPow2) + $(SUBREF algebraic, truncPow2) +)) +$(TR $(TDNW $(SUBMODULE Trigonometry, trigonometry)) $(TD + $(SUBREF trigonometry, sin) + $(SUBREF trigonometry, cos) + $(SUBREF trigonometry, tan) + $(SUBREF trigonometry, asin) + $(SUBREF trigonometry, acos) + $(SUBREF trigonometry, atan) + $(SUBREF trigonometry, atan2) + $(SUBREF trigonometry, sinh) + $(SUBREF trigonometry, cosh) + $(SUBREF trigonometry, tanh) + $(SUBREF trigonometry, asinh) + $(SUBREF trigonometry, acosh) + $(SUBREF trigonometry, atanh) +)) +$(TR $(TDNW $(SUBMODULE Rounding, rounding)) $(TD + $(SUBREF rounding, ceil) + $(SUBREF rounding, floor) + $(SUBREF rounding, round) + $(SUBREF rounding, lround) + $(SUBREF rounding, trunc) + $(SUBREF rounding, rint) + $(SUBREF rounding, lrint) + $(SUBREF rounding, nearbyint) + $(SUBREF rounding, rndtol) + $(SUBREF rounding, quantize) +)) +$(TR $(TDNW $(SUBMODULE Exponentiation & Logarithms, exponential)) $(TD + $(SUBREF exponential, pow) + $(SUBREF exponential, powmod) + $(SUBREF exponential, exp) + $(SUBREF exponential, exp2) + $(SUBREF exponential, expm1) + $(SUBREF exponential, ldexp) + $(SUBREF exponential, frexp) + $(SUBREF exponential, log) + $(SUBREF exponential, log2) + $(SUBREF exponential, log10) + $(SUBREF exponential, logb) + $(SUBREF exponential, ilogb) + $(SUBREF exponential, log1p) + $(SUBREF exponential, scalbn) +)) +$(TR $(TDNW $(SUBMODULE Remainder, remainder)) $(TD + $(SUBREF remainder, fmod) + $(SUBREF remainder, modf) + $(SUBREF remainder, remainder) + $(SUBREF remainder, remquo) +)) +$(TR $(TDNW $(SUBMODULE Floating-point operations, operations)) $(TD + $(SUBREF operations, approxEqual) + $(SUBREF operations, feqrel) + $(SUBREF operations, fdim) + $(SUBREF operations, fmax) + $(SUBREF operations, fmin) + $(SUBREF operations, fma) + $(SUBREF operations, isClose) + $(SUBREF operations, nextDown) + $(SUBREF operations, nextUp) + $(SUBREF operations, nextafter) + $(SUBREF operations, NaN) + $(SUBREF operations, getNaNPayload) + $(SUBREF operations, cmp) +)) +$(TR $(TDNW $(SUBMODULE Introspection, traits)) $(TD + $(SUBREF traits, isFinite) + $(SUBREF traits, isIdentical) + $(SUBREF traits, isInfinity) + $(SUBREF traits, isNaN) + $(SUBREF traits, isNormal) + $(SUBREF traits, isSubnormal) + $(SUBREF traits, signbit) + $(SUBREF traits, sgn) + $(SUBREF traits, copysign) + $(SUBREF traits, isPowerOf2) +)) +$(TR $(TDNW $(SUBMODULE Hardware Control, hardware)) $(TD + $(SUBREF hardware, IeeeFlags) + $(SUBREF hardware, ieeeFlags) + $(SUBREF hardware, resetIeeeFlags) + $(SUBREF hardware, 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. + * + * Macros: + * SUBMODULE = $(MREF_ALTTEXT $1, std, math, $2) + * SUBREF = $(REF_ALTTEXT $(TT $2), $2, std, math, $1)$(NBSP) + * + * Copyright: Copyright The D Language Foundation 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/package.d) + */ +module std.math; + +public import std.math.algebraic; +public import std.math.constants; +public import std.math.exponential; +public import std.math.operations; +public import std.math.hardware; +public import std.math.remainder; +public import std.math.rounding; +public import std.math.traits; +public import std.math.trigonometry; + +// @@@DEPRECATED_2.102@@@ +// Note: Exposed accidentally, should be deprecated / removed +deprecated("std.meta.AliasSeq was unintentionally available from std.math " + ~ "and will be removed after 2.102. Please import std.meta instead") +public import std.meta : AliasSeq; + +package(std): // 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); +} +do +{ + // 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + // 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)); +} + + +// 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 == 113, + "Only 64-bit and 128-bit reals are supported for BigEndian CPUs."); +} + +// 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) +{ + import std.traits : Unqual; + + // 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 Unqual!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; + + // For IBM doubledouble the larger magnitude double comes first. + // It's really a double[2] and arrays don't index differently + // between little and big-endian targets. + enum DOUBLEPAIR_MSB = 0; + enum DOUBLEPAIR_LSB = 1; + + // The exponent/sign byte is for most significant part. + version (LittleEndian) + { + enum EXPPOS_SHORT = 3; + enum SIGNPOS_BYTE = 7; + } + else + { + enum EXPPOS_SHORT = 0; + 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; +} diff --git a/libphobos/src/std/math/remainder.d b/libphobos/src/std/math/remainder.d new file mode 100644 index 0000000..8766713 --- /dev/null +++ b/libphobos/src/std/math/remainder.d @@ -0,0 +1,155 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains several versions of remainder calculation. + +Copyright: Copyright The D Language Foundation 2000 - 2011. +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/remainder.d) + +Macros: + TABLE_SV = + + $0
Special Values
+ NAN = $(RED NAN) + PLUSMN = ± + PLUSMNINF = ±∞ + */ + +module std.math.remainder; + +static import core.stdc.math; + +/************************************ + * 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); +} + +/// +@safe unittest +{ + import std.math.operations : feqrel; + import std.math.traits : isIdentical, isNaN; + + assert(isIdentical(fmod(0.0, 1.0), 0.0)); + assert(fmod(5.0, 3.0).feqrel(2.0) > 16); + assert(isNaN(fmod(5.0, 0.0))); +} + +/************************************ + * 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) + { + import std.math.traits : copysign, isInfinity; + import std.math.rounding : trunc; + + i = trunc(x); + return copysign(isInfinity(x) ? 0.0 : x - i, x); + } + else + return core.stdc.math.modfl(x,&i); +} + +/// +@safe unittest +{ + import std.math.operations : feqrel; + + real frac; + real intpart; + + frac = modf(3.14159, intpart); + assert(intpart.feqrel(3.0) > 16); + assert(frac.feqrel(0.14159) > 16); +} + +/**************************************************** + * 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 $(PLUSMN)$(NAN)) $(TD ?) $(TD yes)) + * $(TR $(TD != $(PLUSMNINF)) $(TD $(PLUSMNINF)) $(TD x) $(TD ?) $(TD no)) + * ) + */ +real remainder(real x, real y) @trusted nothrow @nogc +{ + return core.stdc.math.remainderl(x, y); +} + +/// ditto +real remquo(real x, real y, out int n) @trusted nothrow @nogc /// ditto +{ + return core.stdc.math.remquol(x, y, &n); +} + +/// +@safe @nogc nothrow unittest +{ + import std.math.operations : feqrel; + import std.math.traits : isNaN; + + assert(remainder(5.1, 3.0).feqrel(-0.9) > 16); + assert(remainder(-5.1, 3.0).feqrel(0.9) > 16); + assert(remainder(0.0, 3.0) == 0.0); + + assert(isNaN(remainder(1.0, 0.0))); + assert(isNaN(remainder(-1.0, 0.0))); +} + +/// +@safe @nogc nothrow unittest +{ + import std.math.operations : feqrel; + + int n; + + assert(remquo(5.1, 3.0, n).feqrel(-0.9) > 16 && n == 2); + assert(remquo(-5.1, 3.0, n).feqrel(0.9) > 16 && n == -2); + assert(remquo(0.0, 3.0, n) == 0.0 && n == 0); +} diff --git a/libphobos/src/std/math/rounding.d b/libphobos/src/std/math/rounding.d new file mode 100644 index 0000000..5c8d708 --- /dev/null +++ b/libphobos/src/std/math/rounding.d @@ -0,0 +1,1004 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains several functions for rounding floating point numbers. + +Copyright: Copyright The D Language Foundation 2000 - 2011. + D implementations of 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/rounding.d) + */ + +/* NOTE: This file has been patched from the original DMD distribution to + * work with the GDC compiler. + */ +module std.math.rounding; + +static import core.math; +static import core.stdc.math; + +import std.traits : isFloatingPoint, isIntegral, Unqual; + +version (D_InlineAsm_X86) version = InlineAsm_X86_Any; +version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; + +version (InlineAsm_X86_Any) version = InlineAsm_X87; +version (InlineAsm_X87) +{ + static assert(real.mant_dig == 64); + version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC; +} + +/************************************** + * Returns the value of x rounded upward to the next integer + * (toward positive infinity). + */ +real ceil(real x) @trusted pure nothrow @nogc +{ + version (InlineAsm_X87_MSVC) + { + version (X86_64) + { + 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 + { + 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 + { + import std.math.traits : isInfinity, isNaN; + + // 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 +{ + import std.math.traits : isNaN; + + 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 +{ + import std.math.traits : isInfinity, isNaN; + + // 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 +{ + import std.math.traits : isNaN; + + 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 +{ + import std.math.traits : isInfinity, isNaN; + + // 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 +{ + import std.math.traits : isNaN; + + 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 (InlineAsm_X87_MSVC) + { + version (X86_64) + { + 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 + { + 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 + { + import std.math.traits : isInfinity, isNaN; + + // Special cases. + if (isNaN(x) || isInfinity(x) || x == 0.0) + return x; + + return floorImpl(x); + } +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN; + + assert(floor(+123.456L) == +123); + assert(floor(-123.456L) == -124); + assert(floor(+123.0L) == +123); + assert(floor(-124.0L) == -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 +{ + import std.math.traits : isInfinity, isNaN; + + // Special cases. + if (isNaN(x) || isInfinity(x) || x == 0.0) + return x; + + return floorImpl(x); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN; + + assert(floor(+123.456) == +123); + assert(floor(-123.456) == -124); + assert(floor(+123.0) == +123); + assert(floor(-124.0) == -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 +{ + import std.math.traits : isInfinity, isNaN; + + // Special cases. + if (isNaN(x) || isInfinity(x) || x == 0.0) + return x; + + return floorImpl(x); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.traits : isNaN; + + assert(floor(+123.456f) == +123); + assert(floor(-123.456f) == -124); + assert(floor(+123.0f) == +123); + assert(floor(-124.0f) == -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))); +} + +// https://issues.dlang.org/show_bug.cgi?id=6381 +// floor/ceil should be usable in pure function. +@safe pure nothrow unittest +{ + auto x = floor(1.2); + auto y = ceil(1.2); +} + +/** + * 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) +{ + import std.math.traits : isInfinity; + + 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 +{ + import std.math.operations : isClose; + + assert(isClose(12345.6789L.quantize(0.01L), 12345.68L)); + assert(isClose(12345.6789L.quantize!floor(0.01L), 12345.67L)); + assert(isClose(12345.6789L.quantize(22.0L), 12342.0L)); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + import std.math.traits : isNaN; + + assert(isClose(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) +{ + import std.math.exponential : pow; + + // 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) +{ + import std.math.exponential : pow; + + enum unit = cast(F) pow(base, exp); + return quantize!rfunc(val, unit); +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.math.operations : isClose; + + assert(isClose(12345.6789L.quantize!10(-2), 12345.68L)); + assert(isClose(12345.6789L.quantize!(10, -2), 12345.68L)); + assert(isClose(12345.6789L.quantize!(10, floor)(-2), 12345.67L)); + assert(isClose(12345.6789L.quantize!(10, -2, floor), 12345.67L)); + + assert(isClose(12345.6789L.quantize!22(1), 12342.0L)); + assert(isClose(12345.6789L.quantize!22, 12342.0L)); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.exponential : log10, pow; + import std.math.operations : isClose; + import std.meta : AliasSeq; + + static foreach (F; AliasSeq!(real, double, float)) + {{ + const maxL10 = cast(int) F.max.log10.floor; + const maxR10 = pow(cast(F) 10, maxL10); + assert(isClose((cast(F) 0.9L * maxR10).quantize!10(maxL10), maxR10)); + assert(isClose((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. + */ +pragma(inline, true) +real nearbyint(real x) @safe pure nothrow @nogc +{ + return core.stdc.math.nearbyintl(x); +} + +/// +@safe pure unittest +{ + import std.math.traits : isNaN; + + assert(nearbyint(0.4) == 0); + assert(nearbyint(0.5) == 0); + assert(nearbyint(0.6) == 1); + assert(nearbyint(100.0) == 100); + + assert(isNaN(nearbyint(real.nan))); + assert(nearbyint(real.infinity) == real.infinity); + assert(nearbyint(-real.infinity) == -real.infinity); +} + +/********************************** + * 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. + * + * $(LREF nearbyint) performs the same operation, but does + * not set the FE_INEXACT exception. + */ +pragma(inline, true) +real rint(real x) @safe pure nothrow @nogc +{ + return core.math.rint(x); +} +///ditto +pragma(inline, true) +double rint(double x) @safe pure nothrow @nogc +{ + return core.math.rint(x); +} +///ditto +pragma(inline, true) +float rint(float x) @safe pure nothrow @nogc +{ + return core.math.rint(x); +} + +/// +@safe unittest +{ + import std.math.traits : isNaN; + + version (IeeeFlagsSupport) resetIeeeFlags(); + assert(rint(0.4) == 0); + version (GNU) { /* inexact bit not set with enabled optimizations */ } else + version (IeeeFlagsSupport) assert(ieeeFlags.inexact); + + assert(rint(0.5) == 0); + assert(rint(0.6) == 1); + assert(rint(100.0) == 100); + + assert(isNaN(rint(real.nan))); + assert(rint(real.infinity) == real.infinity); + assert(rint(-real.infinity) == -real.infinity); +} + +@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_X87) + { + 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 + { + import std.math : floatTraits, RealFormat, MANTISSA_MSB, MANTISSA_LSB; + + 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + long result; + + // Rounding limit when casting from real(80-bit) to ulong. + static if (F.realFormat == RealFormat.ieeeExtended) + enum real OF = 9.22337203685477580800E18L; + else + enum real OF = 4.50359962737049600000E15L; + + 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 exp = (vu[F.EXPPOS_SHORT] & F.EXPMASK) - (F.EXPBIAS + 1); + const implicitOne = 1UL << 48; + auto vl = cast(ulong*)(&x); + vl[MANTISSA_MSB] &= implicitOne - 1; + vl[MANTISSA_MSB] |= implicitOne; + + long result; + + 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. + * + * Returns: + * A `real`. + */ +auto round(real x) @trusted nothrow @nogc +{ + version (CRuntime_Microsoft) + { + import std.math.hardware : FloatingPointControl; + + auto old = FloatingPointControl.getControlState(); + FloatingPointControl.setControlState( + (old & (-1 - FloatingPointControl.roundingMask)) | FloatingPointControl.roundToZero + ); + x = core.math.rint((x >= 0) ? x + 0.5 : x - 0.5); + FloatingPointControl.setControlState(old); + return x; + } + else + { + return core.stdc.math.roundl(x); + } +} + +/// +@safe nothrow @nogc unittest +{ + assert(round(4.5) == 5); + assert(round(5.4) == 5); + assert(round(-4.5) == -5); + assert(round(-5.1) == -5); +} + +// assure purity on Posix +version (Posix) +{ + @safe pure nothrow @nogc unittest + { + assert(round(4.5) == 5); + } +} + +/********************************************** + * 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 not implemented for Digital Mars C runtime.) + */ +long lround(real x) @trusted nothrow @nogc +{ + version (CRuntime_DigitalMars) + assert(0, "lround not implemented"); + else + return core.stdc.math.llroundl(x); +} + +/// +@safe nothrow @nogc unittest +{ + version (CRuntime_DigitalMars) {} + else + { + 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. + `pure` on all platforms. + */ +real trunc(real x) @trusted nothrow @nogc pure +{ + version (InlineAsm_X87_MSVC) + { + version (X86_64) + { + 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 + { + 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); + } +} + +/// +@safe pure unittest +{ + assert(trunc(0.01) == 0); + assert(trunc(0.49) == 0); + assert(trunc(0.5) == 0); + assert(trunc(1.5) == 1); +} + +/***************************************** + * 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. + */ +pragma(inline, true) +long rndtol(real x) @nogc @safe pure nothrow { return core.math.rndtol(x); } +//FIXME +///ditto +pragma(inline, true) +long rndtol(double x) @safe pure nothrow @nogc { return rndtol(cast(real) x); } +//FIXME +///ditto +pragma(inline, true) +long rndtol(float x) @safe pure nothrow @nogc { return rndtol(cast(real) x); } + +/// +@safe unittest +{ + assert(rndtol(1.0) == 1L); + assert(rndtol(1.2) == 1L); + assert(rndtol(1.7) == 2L); + assert(rndtol(1.0001) == 1L); +} + +@safe unittest +{ + long function(real) prndtol = &rndtol; + assert(prndtol != null); +} + +// Helper for floor/ceil +T floorImpl(T)(const T x) @trusted pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat; + + 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; + + // Other kinds of extractors for real formats. + static if (F.realFormat == RealFormat.ieeeSingle) + int vi; + } + floatBits y = void; + y.rv = x; + + // Find the exponent (power of 2) + // Do this by shifting the raw value so that the exponent lies in the low bits, + // then mask out the sign bit, and subtract the bias. + static if (F.realFormat == RealFormat.ieeeSingle) + { + int exp = ((y.vi >> (T.mant_dig - 1)) & 0xff) - 0x7f; + } + 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + 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; + } + + static if (F.realFormat == RealFormat.ieeeSingle) + { + if (exp < (T.mant_dig - 1)) + { + // Clear all bits representing the fraction part. + const uint fraction_mask = F.MANTISSAMASK_INT >> exp; + + if ((y.vi & fraction_mask) != 0) + { + // If 'x' is negative, then first substract 1.0 from the value. + if (y.vi < 0) + y.vi += 0x00800000 >> exp; + y.vi &= ~fraction_mask; + } + } + } + else + { + static if (F.realFormat == RealFormat.ieeeExtended53) + exp = (T.mant_dig + 11 - 1) - exp; // mant_dig is really 64 + else + 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; +} diff --git a/libphobos/src/std/math/traits.d b/libphobos/src/std/math/traits.d new file mode 100644 index 0000000..2841bad --- /dev/null +++ b/libphobos/src/std/math/traits.d @@ -0,0 +1,853 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains several functions for introspection on numerical values. + +Copyright: Copyright The D Language Foundation 2000 - 2011. +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/traits.d) + +Macros: + NAN = $(RED NAN) + PLUSMN = ± + INFIN = ∞ + */ + +module std.math.traits; + +import std.traits : isFloatingPoint, isIntegral, isNumeric, isSigned; + +/********************************* + * Determines if $(D_PARAM x) is NaN. + * Params: + * x = a floating point number. + * Returns: + * `true` if $(D_PARAM x) is Nan. + */ +bool isNaN(X)(X x) @nogc @trusted pure nothrow +if (isFloatingPoint!(X)) +{ + version (all) + { + return x != x; + } + else + { + /* + Code kept for historical context. At least on Intel, the simple test + x != x uses one dedicated instruction (ucomiss/ucomisd) that runs in one + cycle. Code for 80- and 128-bits is larger but still smaller than the + integrals-based solutions below. Future revisions may enable the code + below conditionally depending on hardware. + */ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ieeeSingle) + { + const uint p = *cast(uint *)&x; + // Sign bit (MSB) is irrelevant so mask it out. + // Next 8 bits should be all set. + // At least one bit among the least significant 23 bits should be set. + return (p & 0x7FFF_FFFF) > 0x7F80_0000; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + const ulong p = *cast(ulong *)&x; + // Sign bit (MSB) is irrelevant so mask it out. + // Next 11 bits should be all set. + // At least one bit among the least significant 52 bits should be set. + return (p & 0x7FFF_FFFF_FFFF_FFFF) > 0x7FF0_0000_0000_0000; + } + else static if (F.realFormat == RealFormat.ieeeExtended || + F.realFormat == RealFormat.ieeeExtended53) + { + 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; + + static 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: + * `true` if $(D_PARAM x) is finite. + */ +bool isFinite(X)(X x) @trusted pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat; + + static if (__traits(isFloating, X)) + if (__ctfe) + return x == x && x != X.infinity && x != -X.infinity; + 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)); + + //CTFE + static assert(isFinite(1.23)); + static assert(isFinite(double.max)); + static assert(isFinite(double.min_normal)); + static assert(!isFinite(double.nan)); + static assert(!isFinite(double.infinity)); + + static assert(isFinite(1.23L)); + static assert(isFinite(real.max)); + static assert(isFinite(real.min_normal)); + static assert(!isFinite(real.nan)); + static 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: + * `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 +{ + import std.math : floatTraits, RealFormat; + + static if (__traits(isFloating, X)) + if (__ctfe) + return (x <= -X.min_normal && x != -X.infinity) || (x >= X.min_normal && x != X.infinity); + alias F = floatTraits!(X); + 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)); + +} + +@safe pure nothrow @nogc unittest +{ + // CTFE + enum float f = 3; + enum double d = 500; + enum real e = 10e+48; + + static assert(isNormal(f)); + static assert(isNormal(d)); + static assert(isNormal(e)); + + static assert(!isNormal(0.0f)); + static assert(!isNormal(0.0)); + static assert(!isNormal(0.0L)); + static assert(!isNormal(real.infinity)); + static assert(isNormal(-real.max)); + static 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: + * `true` if $(D_PARAM x) is a denormal number. + */ +bool isSubnormal(X)(X x) @trusted pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat, MANTISSA_MSB, MANTISSA_LSB; + + static if (__traits(isFloating, X)) + if (__ctfe) + return -X.min_normal < x && x < X.min_normal; + /* + 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + ushort* pe = cast(ushort *)&x; + long* ps = cast(long *)&x; + + return (pe[F.EXPPOS_SHORT] & F.EXPMASK) == 0 && *ps > 0; + } + else + { + static assert(false, "Not implemented for this architecture"); + } +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + + static foreach (T; AliasSeq!(float, double, real)) + {{ + T f; + for (f = 1.0; !isSubnormal(f); f /= 2) + assert(f != 0); + }} +} + +@safe pure nothrow @nogc unittest +{ + static bool subnormalTest(T)() + { + T f; + for (f = 1.0; !isSubnormal(f); f /= 2) + if (f == 0) + return false; + return true; + } + static assert(subnormalTest!float()); + static assert(subnormalTest!double()); + static assert(subnormalTest!real()); +} + +/********************************* + * Determines if $(D_PARAM x) is $(PLUSMN)$(INFIN). + * Params: + * x = a floating point number. + * Returns: + * `true` if $(D_PARAM x) is $(PLUSMN)$(INFIN). + */ +bool isInfinity(X)(X x) @nogc @trusted pure nothrow +if (isFloatingPoint!(X)) +{ + import std.math : floatTraits, RealFormat, MANTISSA_MSB, MANTISSA_LSB; + + 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 || + F.realFormat == RealFormat.ieeeExtended53) + { + 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.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)); +} + +@nogc @safe pure nothrow unittest +{ + import std.meta : AliasSeq; + static bool foo(T)(inout T x) { return isInfinity(x); } + foreach (T; AliasSeq!(float, double, real)) + { + assert(!foo(T(3.14f))); + assert(foo(T.infinity)); + } +} + +/********************************* + * Is the binary representation of x identical to y? + */ +bool isIdentical(real x, real y) @trusted pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat; + + // 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) + { + return pxs[0] == pys[0] && pxs[1] == pys[1]; + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + ushort* pxe = cast(ushort *)&x; + ushort* pye = cast(ushort *)&y; + return pxe[4] == pye[4] && pxs[0] == pys[0]; + } + else + { + assert(0, "isIdentical not implemented"); + } +} + +/// +@safe @nogc pure nothrow unittest +{ + assert( isIdentical(0.0, 0.0)); + assert( isIdentical(1.0, 1.0)); + assert( isIdentical(real.infinity, real.infinity)); + assert( isIdentical(-real.infinity, -real.infinity)); + + assert(!isIdentical(0.0, -0.0)); + assert(!isIdentical(real.nan, -real.nan)); + assert(!isIdentical(real.infinity, -real.infinity)); +} + +/********************************* + * Return 1 if sign bit of e is set, 0 if not. + */ +int signbit(X)(X x) @nogc @trusted pure nothrow +{ + import std.math : floatTraits, RealFormat; + + if (__ctfe) + { + double dval = cast(double) x; // Precision can increase or decrease but sign won't change (even NaN). + return 0 > *cast(long*) &dval; + } + + 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)); +} + +@nogc @safe pure nothrow unittest +{ + // CTFE + static assert(!signbit(float.nan)); + static assert(signbit(-float.nan)); + static assert(!signbit(168.1234f)); + static assert(signbit(-168.1234f)); + static assert(!signbit(0.0f)); + static assert(signbit(-0.0f)); + static assert(signbit(-float.max)); + static assert(!signbit(float.max)); + + static assert(!signbit(double.nan)); + static assert(signbit(-double.nan)); + static assert(!signbit(168.1234)); + static assert(signbit(-168.1234)); + static assert(!signbit(0.0)); + static assert(signbit(-0.0)); + static assert(signbit(-double.max)); + static assert(!signbit(double.max)); + + static assert(!signbit(real.nan)); + static assert(signbit(-real.nan)); + static assert(!signbit(168.1234L)); + static assert(signbit(-168.1234L)); + static assert(!signbit(0.0L)); + static assert(signbit(-0.0L)); + static assert(signbit(-real.max)); + static assert(!signbit(real.max)); +} + +/** +Params: + to = the numeric value to use + from = the sign value to use +Returns: + 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)) +{ + import std.math : floatTraits, RealFormat; + + if (__ctfe) + { + return signbit(to) == signbit(from) ? to : -to; + } + 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 +{ + assert(copysign(1.0, 1.0) == 1.0); + assert(copysign(1.0, -0.0) == -1.0); + assert(copysign(1UL, -1.0) == -1.0); + assert(copysign(-1.0, -1.0) == -1.0); + + assert(copysign(real.infinity, -1.0) == -real.infinity); + assert(copysign(real.nan, 1.0) is real.nan); + assert(copysign(-real.nan, 1.0) is real.nan); + assert(copysign(real.nan, -1.0) is -real.nan); +} + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + + static foreach (X; AliasSeq!(float, double, real, int, long)) + { + static foreach (Y; AliasSeq!(float, double, real)) + {{ + 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)); + } + }} + } + // CTFE + static foreach (X; AliasSeq!(float, double, real, int, long)) + { + static foreach (Y; AliasSeq!(float, double, real)) + {{ + enum X x = 21; + enum Y y = 23.8; + + assert(21.0 == copysign(x, y)); + assert(21.0 == copysign(-x, y)); + assert(-21.0 == copysign(x, -y)); + assert(-21.0 == copysign(-x, -y)); + + static if (isFloatingPoint!X) + { + static assert(isNaN(copysign(X.nan, y)) && !signbit(copysign(X.nan, y))); + assert(isNaN(copysign(X.nan, -y)) && signbit(copysign(X.nan, -y))); + } + }} + } +} + +/********************************* +Returns `-1` if $(D x < 0), `x` if $(D x == 0), `1` if +$(D x > 0), and $(NAN) if x==$(NAN). + */ +F sgn(F)(F x) @safe pure nothrow @nogc +if (isFloatingPoint!F || isIntegral!F) +{ + // @@@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); +} + +/** +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) +{ + import std.math.exponential : frexp; + + 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 +{ + import std.math.exponential : pow; + + 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.math.exponential : pow; + import std.meta : AliasSeq; + + enum smallP2 = pow(2.0L, -62); + enum bigP2 = pow(2.0L, 50); + enum smallP7 = pow(7.0L, -35); + enum bigP7 = pow(7.0L, 30); + + static foreach (X; AliasSeq!(float, double, real)) + {{ + immutable min_sub = X.min_normal * X.epsilon; + + foreach (x; [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; [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)); + } + }} + + static 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)); + }} + + // CTFE + static foreach (X; AliasSeq!(float, double, real)) + {{ + enum min_sub = X.min_normal * X.epsilon; + + static foreach (x; [smallP2, min_sub, X.min_normal, .25L, 0.5L, 1.0L, + 2.0L, 8.0L, pow(2.0L, X.max_exp - 1), bigP2]) + { + static assert( isPowerOf2(cast(X) x)); + static assert(!isPowerOf2(cast(X)-x)); + } + + static foreach (x; [0.0L, 3 * min_sub, smallP7, 0.1L, 1337.0L, bigP7, X.max, real.nan, real.infinity]) + { + static assert(!isPowerOf2(cast(X) x)); + static assert(!isPowerOf2(cast(X)-x)); + } + }} + + static foreach (X; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + {{ + static foreach (x; [1, 2, 4, 8, (X.max >>> 1) + 1]) + { + static assert( isPowerOf2(cast(X) x)); + static if (isSigned!X) + static assert(!isPowerOf2(cast(X)-x)); + } + + static foreach (x; [0, 3, 5, 13, 77, X.min, X.max]) + static assert(!isPowerOf2(cast(X) x)); + }} +} + diff --git a/libphobos/src/std/math/trigonometry.d b/libphobos/src/std/math/trigonometry.d new file mode 100644 index 0000000..06a7cb1 --- /dev/null +++ b/libphobos/src/std/math/trigonometry.d @@ -0,0 +1,1425 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, math). + +It contains several trigonometric functions. + +Copyright: Copyright The D Language Foundation 2000 - 2011. + D implementations of tan, atan, and atan2 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/trigonometry.d) + +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 + POWER = $1$2 + NAN = $(RED NAN) + PLUSMN = ± + INFIN = ∞ + PLUSMNINF = ±∞ + */ + +module std.math.trigonometry; + +static import core.math; + +version (D_InlineAsm_X86) version = InlineAsm_X86_Any; +version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; + +version (InlineAsm_X86_Any) version = InlineAsm_X87; +version (InlineAsm_X87) +{ + static assert(real.mant_dig == 64); + version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC; +} + +/*********************************** + * 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). + */ +pragma(inline, true) +real cos(real x) @safe pure nothrow @nogc { return core.math.cos(x); } +///ditto +pragma(inline, true) +double cos(double x) @safe pure nothrow @nogc { return core.math.cos(x); } +///ditto +pragma(inline, true) +float cos(float x) @safe pure nothrow @nogc { return core.math.cos(x); } + +/// +@safe unittest +{ + import std.math.operations : isClose; + + assert(cos(0.0) == 1.0); + assert(cos(1.0).isClose(0.5403023059)); + assert(cos(3.0).isClose(-0.9899924966)); +} + +@safe unittest +{ + real function(real) pcos = &cos; + assert(pcos != null); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.algebraic : fabs; + + 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); +} + +/*********************************** + * 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). + */ +pragma(inline, true) +real sin(real x) @safe pure nothrow @nogc { return core.math.sin(x); } +///ditto +pragma(inline, true) +double sin(double x) @safe pure nothrow @nogc { return core.math.sin(x); } +///ditto +pragma(inline, true) +float sin(float x) @safe pure nothrow @nogc { return core.math.sin(x); } + +/// +@safe unittest +{ + import std.math.constants : 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); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.algebraic : fabs; + + 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); +} + +/**************************************************************************** + * 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)) + * ) + */ +pragma(inline, true) +real tan(real x) @safe pure nothrow @nogc +{ + version (InlineAsm_X87) + { + if (!__ctfe) + return tanAsm(x); + } + return tanImpl(x); +} + +/// ditto +pragma(inline, true) +double tan(double x) @safe pure nothrow @nogc { return __ctfe ? cast(double) tan(cast(real) x) : tanImpl(x); } + +/// ditto +pragma(inline, true) +float tan(float x) @safe pure nothrow @nogc { return __ctfe ? cast(float) tan(cast(real) x) : tanImpl(x); } + +/// +@safe unittest +{ + import std.math.operations : isClose; + import std.math.traits : isIdentical; + import std.math.constants : PI; + import std.math.algebraic : sqrt; + + assert(isIdentical(tan(0.0), 0.0)); + assert(tan(PI).isClose(0, 0.0, 1e-10)); + assert(tan(PI / 3).isClose(sqrt(3.0))); +} + +version (InlineAsm_X87) +private real tanAsm(real x) @trusted pure nothrow @nogc +{ + // Separating `return real.nan` from the asm block on LDC produces unintended + // behaviour as additional instructions are generated, invalidating the asm + // logic inside the previous block. To circumvent this, we can push rnan + // manually by creating an immutable variable in the stack. + immutable rnan = real.nan; + + version (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 + fld rnan ; // return rnan + jmp Lret ; +Clear1: + fstp ST(0) ; // dump X, which is always 1 +Lret: + ; + } + } + else version (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 + fld rnan ; // return rnan + jmp Lret ; +Clear1: + fstp ST(0) ; // dump X, which is always 1 +Lret: + ; + } + } + else + static assert(0); +} + +private T tanImpl(T)(T x) @safe pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat; + import std.math.constants : PI, PI_4; + import std.math.rounding : floor; + import std.math.algebraic : poly; + import std.math.traits : isInfinity, isNaN, signbit; + + // Coefficients for tan(x) and PI/4 split into three parts. + enum realFormat = floatTraits!T.realFormat; + static if (realFormat == RealFormat.ieeeQuadruple) + { + static immutable T[6] P = [ + 2.883414728874239697964612246732416606301E10L, + -2.307030822693734879744223131873392503321E9L, + 5.160188250214037865511600561074819366815E7L, + -4.249691853501233575668486667664718192660E5L, + 1.272297782199996882828849455156962260810E3L, + -9.889929415807650724957118893791829849557E-1L + ]; + static immutable T[7] Q = [ + 8.650244186622719093893836740197250197602E10L, + -4.152206921457208101480801635640958361612E10L, + 2.758476078803232151774723646710890525496E9L, + -5.733709132766856723608447733926138506824E7L, + 4.529422062441341616231663543669583527923E5L, + -1.317243702830553658702531997959756728291E3L, + 1.0 + ]; + + enum T P1 = + 7.853981633974483067550664827649598009884357452392578125E-1L; + enum T P2 = + 2.8605943630549158983813312792950660807511260829685741796657E-18L; + enum T P3 = + 2.1679525325309452561992610065108379921905808E-35L; + } + else static if (realFormat == RealFormat.ieeeExtended || + realFormat == RealFormat.ieeeDouble) + { + static immutable T[3] P = [ + -1.7956525197648487798769E7L, + 1.1535166483858741613983E6L, + -1.3093693918138377764608E4L, + ]; + static immutable T[5] Q = [ + -5.3869575592945462988123E7L, + 2.5008380182335791583922E7L, + -1.3208923444021096744731E6L, + 1.3681296347069295467845E4L, + 1.0000000000000000000000E0L, + ]; + + enum T P1 = 7.853981554508209228515625E-1L; + enum T P2 = 7.946627356147928367136046290398E-9L; + enum T P3 = 3.061616997868382943065164830688E-17L; + } + else static if (realFormat == RealFormat.ieeeSingle) + { + static immutable T[6] P = [ + 3.33331568548E-1, + 1.33387994085E-1, + 5.34112807005E-2, + 2.44301354525E-2, + 3.11992232697E-3, + 9.38540185543E-3, + ]; + + enum T P1 = 0.78515625; + enum T P2 = 2.4187564849853515625E-4; + enum T P3 = 3.77489497744594108E-8; + } + else + static assert(0, "no coefficients for tan()"); + + // Special cases. + if (x == cast(T) 0.0 || isNaN(x)) + return x; + if (isInfinity(x)) + return T.nan; + + // Make argument positive but save the sign. + bool sign = false; + if (signbit(x)) + { + sign = true; + x = -x; + } + + // Compute x mod PI/4. + static if (realFormat == RealFormat.ieeeSingle) + { + enum T FOPI = 4 / PI; + int j = cast(int) (FOPI * x); + T y = j; + T z; + } + else + { + T y = floor(x / cast(T) PI_4); + // Strip high bits of integer part. + enum T highBitsFactor = (realFormat == RealFormat.ieeeDouble ? 0x1p3 : 0x1p4); + enum T highBitsInv = 1.0 / highBitsFactor; + T z = y * highBitsInv; + // Compute y - 2^numHighBits * (y / 2^numHighBits). + z = y - highBitsFactor * floor(z); + + // Integer and fraction part modulo one octant. + int j = cast(int)(z); + } + + // Map zeros and singularities to origin. + if (j & 1) + { + j += 1; + y += cast(T) 1.0; + } + + z = ((x - y * P1) - y * P2) - y * P3; + const T zz = z * z; + + enum T zzThreshold = (realFormat == RealFormat.ieeeSingle ? 1.0e-4L : + realFormat == RealFormat.ieeeDouble ? 1.0e-14L : 1.0e-20L); + if (zz > zzThreshold) + { + static if (realFormat == RealFormat.ieeeSingle) + y = z + z * (zz * poly(zz, P)); + else + y = z + z * (zz * poly(zz, P) / poly(zz, Q)); + } + else + y = z; + + if (j & 2) + y = (cast(T) -1.0) / y; + + return (sign) ? -y : y; +} + +@safe @nogc nothrow unittest +{ + static void testTan(T)() + { + import std.math.operations : CommonDefaultFor, isClose, NaN; + import std.math.traits : isIdentical, isNaN; + import std.math.constants : PI, PI_4; + + // ±0 + const T zero = 0.0; + assert(isIdentical(tan(zero), zero)); + assert(isIdentical(tan(-zero), -zero)); + // ±∞ + const T inf = T.infinity; + assert(isNaN(tan(inf))); + assert(isNaN(tan(-inf))); + // NaN + const T specialNaN = NaN(0x0123L); + assert(isIdentical(tan(specialNaN), specialNaN)); + + static immutable T[2][] vals = + [ + // angle, tan + [ .5, .54630248984], + [ 1, 1.5574077247], + [ 1.5, 14.101419947], + [ 2, -2.1850398633], + [ 2.5,-.74702229724], + [ 3, -.14254654307], + [ 3.5, .37458564016], + [ 4, 1.1578212823], + [ 4.5, 4.6373320546], + [ 5, -3.3805150062], + [ 5.5,-.99558405221], + [ 6, -.29100619138], + [ 6.5, .22027720035], + [ 10, .64836082746], + + // special angles + [ PI_4, 1], + //[ PI_2, T.infinity], // PI_2 is not _exactly_ pi/2. + [ 3*PI_4, -1], + [ PI, 0], + [ 5*PI_4, 1], + //[ 3*PI_2, -T.infinity], + [ 7*PI_4, -1], + [ 2*PI, 0], + ]; + + foreach (ref val; vals) + { + T x = val[0]; + T r = val[1]; + T t = tan(x); + + //printf("tan(%Lg) = %Lg, should be %Lg\n", cast(real) x, cast(real) t, cast(real) r); + assert(isClose(r, t, CommonDefaultFor!(T,T), CommonDefaultFor!(T,T))); + + x = -x; + r = -r; + t = tan(x); + //printf("tan(%Lg) = %Lg, should be %Lg\n", cast(real) x, cast(real) t, cast(real) r); + assert(isClose(r, t, CommonDefaultFor!(T,T), CommonDefaultFor!(T,T))); + } + } + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(real, double, float)) + testTan!T(); + + import std.math.operations : isClose; + import std.math.constants : PI; + import std.math.algebraic : sqrt; + assert(isClose(tan(PI / 3), sqrt(3.0L), real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +@safe pure nothrow @nogc unittest +{ + import std.math.algebraic : fabs; + import std.math.traits : isNaN; + + 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(-0x1p63))); + static if (real.mant_dig >= 64) + { + assert(!isNaN(tan(0x1p300L))); + assert(!isNaN(tan(-0x1p300L))); + } +} + +/*************** + * 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 +{ + import core.math : sqrt; + + 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); } + +/// +@safe unittest +{ + import std.math.operations : isClose; + import std.math.traits : isNaN; + import std.math.constants : PI; + + assert(acos(0.0).isClose(1.570796327)); + assert(acos(0.5).isClose(PI / 3)); + assert(acos(PI).isNaN); +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + + assert(isClose(acos(0.5), PI / 3, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*************** + * 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 +{ + import core.math : sqrt; + + 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); } + +/// +@safe unittest +{ + import std.math.operations : isClose; + import std.math.traits : isIdentical, isNaN; + import std.math.constants : PI; + + assert(isIdentical(asin(0.0), 0.0)); + assert(asin(0.5).isClose(PI / 6)); + assert(asin(PI).isNaN); +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + + assert(isClose(asin(0.5), PI / 6, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*************** + * 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)) + * ) + */ +pragma(inline, true) +real atan(real x) @safe pure nothrow @nogc +{ + version (InlineAsm_X87) + { + if (!__ctfe) + return atan2Asm(x, 1.0L); + } + return atanImpl(x); +} + +/// ditto +pragma(inline, true) +double atan(double x) @safe pure nothrow @nogc { return __ctfe ? cast(double) atan(cast(real) x) : atanImpl(x); } + +/// ditto +pragma(inline, true) +float atan(float x) @safe pure nothrow @nogc { return __ctfe ? cast(float) atan(cast(real) x) : atanImpl(x); } + +/// +@safe unittest +{ + import std.math.operations : isClose; + import std.math.traits : isIdentical; + import std.math.constants : PI; + import std.math.algebraic : sqrt; + + assert(isIdentical(atan(0.0), 0.0)); + assert(atan(sqrt(3.0)).isClose(PI / 3)); +} + +private T atanImpl(T)(T x) @safe pure nothrow @nogc +{ + import std.math : floatTraits, RealFormat; + import std.math.traits : copysign, isInfinity, signbit; + import std.math.constants : PI_2, PI_4; + import std.math.algebraic : poly; + + // Coefficients for atan(x) + enum realFormat = floatTraits!T.realFormat; + static if (realFormat == RealFormat.ieeeQuadruple) + { + static immutable T[9] P = [ + -6.880597774405940432145577545328795037141E2L, + -2.514829758941713674909996882101723647996E3L, + -3.696264445691821235400930243493001671932E3L, + -2.792272753241044941703278827346430350236E3L, + -1.148164399808514330375280133523543970854E3L, + -2.497759878476618348858065206895055957104E2L, + -2.548067867495502632615671450650071218995E1L, + -8.768423468036849091777415076702113400070E-1L, + -6.635810778635296712545011270011752799963E-4L + ]; + static immutable T[9] Q = [ + 2.064179332321782129643673263598686441900E3L, + 8.782996876218210302516194604424986107121E3L, + 1.547394317752562611786521896296215170819E4L, + 1.458510242529987155225086911411015961174E4L, + 7.928572347062145288093560392463784743935E3L, + 2.494680540950601626662048893678584497900E3L, + 4.308348370818927353321556740027020068897E2L, + 3.566239794444800849656497338030115886153E1L, + 1.0 + ]; + } + else static if (realFormat == RealFormat.ieeeExtended) + { + static immutable T[5] P = [ + -5.0894116899623603312185E1L, + -9.9988763777265819915721E1L, + -6.3976888655834347413154E1L, + -1.4683508633175792446076E1L, + -8.6863818178092187535440E-1L, + ]; + static immutable T[6] Q = [ + 1.5268235069887081006606E2L, + 3.9157570175111990631099E2L, + 3.6144079386152023162701E2L, + 1.4399096122250781605352E2L, + 2.2981886733594175366172E1L, + 1.0000000000000000000000E0L, + ]; + } + else static if (realFormat == RealFormat.ieeeDouble) + { + static immutable T[5] P = [ + -6.485021904942025371773E1L, + -1.228866684490136173410E2L, + -7.500855792314704667340E1L, + -1.615753718733365076637E1L, + -8.750608600031904122785E-1L, + ]; + static immutable T[6] Q = [ + 1.945506571482613964425E2L, + 4.853903996359136964868E2L, + 4.328810604912902668951E2L, + 1.650270098316988542046E2L, + 2.485846490142306297962E1L, + 1.000000000000000000000E0L, + ]; + + enum T MOREBITS = 6.123233995736765886130E-17L; + } + else static if (realFormat == RealFormat.ieeeSingle) + { + static immutable T[4] P = [ + -3.33329491539E-1, + 1.99777106478E-1, + -1.38776856032E-1, + 8.05374449538E-2, + ]; + } + else + static assert(0, "no coefficients for atan()"); + + // tan(PI/8) + enum T TAN_PI_8 = 0.414213562373095048801688724209698078569672L; + // tan(3 * PI/8) + enum T TAN3_PI_8 = 2.414213562373095048801688724209698078569672L; + + // Special cases. + if (x == cast(T) 0.0) + return x; + if (isInfinity(x)) + return copysign(cast(T) PI_2, x); + + // Make argument positive but save the sign. + bool sign = false; + if (signbit(x)) + { + sign = true; + x = -x; + } + + static if (realFormat == RealFormat.ieeeDouble) // special case for double precision + { + short flag = 0; + T y; + if (x > TAN3_PI_8) + { + y = PI_2; + flag = 1; + x = -(1.0 / x); + } + else if (x <= 0.66) + { + y = 0.0; + } + else + { + y = PI_4; + flag = 2; + x = (x - 1.0)/(x + 1.0); + } + + T z = x * x; + z = z * poly(z, P) / poly(z, Q); + z = x * z + x; + if (flag == 2) + z += 0.5 * MOREBITS; + else if (flag == 1) + z += MOREBITS; + y = y + z; + } + else + { + // Range reduction. + T y; + if (x > TAN3_PI_8) + { + y = PI_2; + x = -((cast(T) 1.0) / x); + } + else if (x > TAN_PI_8) + { + y = PI_4; + x = (x - cast(T) 1.0)/(x + cast(T) 1.0); + } + else + y = 0.0; + + // Rational form in x^^2. + const T z = x * x; + static if (realFormat == RealFormat.ieeeSingle) + y += poly(z, P) * z * x + x; + else + y = y + (poly(z, P) / poly(z, Q)) * z * x + x; + } + + return (sign) ? -y : y; +} + +@safe @nogc nothrow unittest +{ + static void testAtan(T)() + { + import std.math.operations : CommonDefaultFor, isClose, NaN; + import std.math.traits : isIdentical; + import std.math.constants : PI_2, PI_4; + + // ±0 + const T zero = 0.0; + assert(isIdentical(atan(zero), zero)); + assert(isIdentical(atan(-zero), -zero)); + // ±∞ + const T inf = T.infinity; + assert(isClose(atan(inf), cast(T) PI_2)); + assert(isClose(atan(-inf), cast(T) -PI_2)); + // NaN + const T specialNaN = NaN(0x0123L); + assert(isIdentical(atan(specialNaN), specialNaN)); + + static immutable T[2][] vals = + [ + // x, atan(x) + [ 0.25, 0.24497866313 ], + [ 0.5, 0.46364760900 ], + [ 1, PI_4 ], + [ 1.5, 0.98279372325 ], + [ 10, 1.47112767430 ], + ]; + + foreach (ref val; vals) + { + T x = val[0]; + T r = val[1]; + T a = atan(x); + + //printf("atan(%Lg) = %Lg, should be %Lg\n", cast(real) x, cast(real) a, cast(real) r); + assert(isClose(r, a, CommonDefaultFor!(T,T), CommonDefaultFor!(T,T))); + + x = -x; + r = -r; + a = atan(x); + //printf("atan(%Lg) = %Lg, should be %Lg\n", cast(real) x, cast(real) a, cast(real) r); + assert(isClose(r, a, CommonDefaultFor!(T,T), CommonDefaultFor!(T,T))); + } + } + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(real, double, float)) + testAtan!T(); + + import std.math.operations : isClose; + import std.math.algebraic : sqrt; + import std.math.constants : PI; + assert(isClose(atan(sqrt(3.0L)), PI / 3, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*************** + * 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)) + * ) + */ +pragma(inline, true) +real atan2(real y, real x) @trusted pure nothrow @nogc // TODO: @safe +{ + version (InlineAsm_X87) + { + if (!__ctfe) + return atan2Asm(y, x); + } + return atan2Impl(y, x); +} + +/// ditto +pragma(inline, true) +double atan2(double y, double x) @safe pure nothrow @nogc +{ + return __ctfe ? cast(double) atan2(cast(real) y, cast(real) x) : atan2Impl(y, x); +} + +/// ditto +pragma(inline, true) +float atan2(float y, float x) @safe pure nothrow @nogc +{ + return __ctfe ? cast(float) atan2(cast(real) y, cast(real) x) : atan2Impl(y, x); +} + +/// +@safe unittest +{ + import std.math.operations : isClose; + import std.math.constants : PI; + import std.math.algebraic : sqrt; + + assert(atan2(1.0, sqrt(3.0)).isClose(PI / 6)); +} + +version (InlineAsm_X87) +private real atan2Asm(real y, real x) @trusted pure nothrow @nogc +{ + 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; + } + } +} + +private T atan2Impl(T)(T y, T x) @safe pure nothrow @nogc +{ + import std.math.traits : copysign, isInfinity, isNaN, signbit; + import std.math.constants : PI, PI_2, PI_4; + + // Special cases. + if (isNaN(x) || isNaN(y)) + return T.nan; + if (y == cast(T) 0.0) + { + if (x >= 0 && !signbit(x)) + return copysign(0, y); + else + return copysign(cast(T) PI, y); + } + if (x == cast(T) 0.0) + return copysign(cast(T) PI_2, y); + if (isInfinity(x)) + { + if (signbit(x)) + { + if (isInfinity(y)) + return copysign(3 * cast(T) PI_4, y); + else + return copysign(cast(T) PI, y); + } + else + { + if (isInfinity(y)) + return copysign(cast(T) PI_4, y); + else + return copysign(cast(T) 0.0, y); + } + } + if (isInfinity(y)) + return copysign(cast(T) PI_2, y); + + // Call atan and determine the quadrant. + T z = atan(y / x); + + if (signbit(x)) + { + if (signbit(y)) + z = z - cast(T) PI; + else + z = z + cast(T) PI; + } + + if (z == cast(T) 0.0) + return copysign(z, y); + + return z; +} + +@safe @nogc nothrow unittest +{ + static void testAtan2(T)() + { + import std.math.operations : isClose; + import std.math.traits : isIdentical, isNaN; + import std.math.constants : PI, PI_2, PI_4; + + // NaN + const T nan = T.nan; + assert(isNaN(atan2(nan, cast(T) 1))); + assert(isNaN(atan2(cast(T) 1, nan))); + + const T inf = T.infinity; + static immutable T[3][] vals = + [ + // y, x, atan2(y, x) + + // ±0 + [ 0.0, 1.0, 0.0 ], + [ -0.0, 1.0, -0.0 ], + [ 0.0, 0.0, 0.0 ], + [ -0.0, 0.0, -0.0 ], + [ 0.0, -1.0, PI ], + [ -0.0, -1.0, -PI ], + [ 0.0, -0.0, PI ], + [ -0.0, -0.0, -PI ], + [ 1.0, 0.0, PI_2 ], + [ 1.0, -0.0, PI_2 ], + [ -1.0, 0.0, -PI_2 ], + [ -1.0, -0.0, -PI_2 ], + + // ±∞ + [ 1.0, inf, 0.0 ], + [ -1.0, inf, -0.0 ], + [ 1.0, -inf, PI ], + [ -1.0, -inf, -PI ], + [ inf, 1.0, PI_2 ], + [ inf, -1.0, PI_2 ], + [ -inf, 1.0, -PI_2 ], + [ -inf, -1.0, -PI_2 ], + [ inf, inf, PI_4 ], + [ -inf, inf, -PI_4 ], + [ inf, -inf, 3 * PI_4 ], + [ -inf, -inf, -3 * PI_4 ], + + [ 1.0, 1.0, PI_4 ], + [ -2.0, 2.0, -PI_4 ], + [ 3.0, -3.0, 3 * PI_4 ], + [ -4.0, -4.0, -3 * PI_4 ], + + [ 0.75, 0.25, 1.249045772398 ], + [ -0.5, 0.375, -0.927295218002 ], + [ 0.5, -0.125, 1.815774989922 ], + [ -0.75, -0.5, -2.158798930342 ], + ]; + + foreach (ref val; vals) + { + const T y = val[0]; + const T x = val[1]; + const T r = val[2]; + const T a = atan2(y, x); + + //printf("atan2(%Lg, %Lg) = %Lg, should be %Lg\n", cast(real) y, cast(real) x, cast(real) a, cast(real) r); + if (r == 0) + assert(isIdentical(r, a)); // check sign + else + assert(isClose(r, a)); + } + } + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(real, double, float)) + testAtan2!T(); + + import std.math.operations : isClose; + import std.math.algebraic : sqrt; + import std.math.constants : PI; + assert(isClose(atan2(1.0L, sqrt(3.0L)), PI / 6, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*********************************** + * 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 +{ + import std.math.exponential : exp; + + // 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); } + +/// +@safe unittest +{ + import std.math.constants : E; + import std.math.operations : isClose; + + assert(cosh(0.0) == 1.0); + assert(cosh(1.0).isClose((E + 1.0 / E) / 2)); +} + +@safe @nogc nothrow unittest +{ + import std.math.constants : E; + import std.math.operations : isClose; + + assert(isClose(cosh(1.0), (E + 1.0 / E) / 2, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*********************************** + * 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 { return _sinh(x); } + +/// ditto +double sinh(double x) @safe pure nothrow @nogc { return _sinh(x); } + +/// ditto +float sinh(float x) @safe pure nothrow @nogc { return _sinh(x); } + +/// +@safe unittest +{ + import std.math.constants : E; + import std.math.operations : isClose; + import std.math.traits : isIdentical; + + enum sinh1 = (E - 1.0 / E) / 2; + import std.meta : AliasSeq; + static foreach (F; AliasSeq!(float, double, real)) + { + assert(isIdentical(sinh(F(0.0)), F(0.0))); + assert(sinh(F(1.0)).isClose(F(sinh1))); + } +} + +private F _sinh(F)(F x) +{ + import std.math.traits : copysign; + import std.math.exponential : exp, expm1; + import core.math : fabs; + import std.math.constants : LN2; + + // 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) > F.mant_dig * F(LN2)) + { + return copysign(F(0.5) * exp(fabs(x)), x); + } + + const y = expm1(x); + return F(0.5) * y / (y+1) * (y+2); +} + +@safe @nogc nothrow unittest +{ + import std.math.constants : E; + import std.math.operations : isClose; + + assert(isClose(sinh(1.0L), real((E - 1.0 / E) / 2), real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} +/*********************************** + * 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 { return _tanh(x); } + +/// ditto +double tanh(double x) @safe pure nothrow @nogc { return _tanh(x); } + +/// ditto +float tanh(float x) @safe pure nothrow @nogc { return _tanh(x); } + +/// +@safe unittest +{ + import std.math.operations : isClose; + import std.math.traits : isIdentical; + + assert(isIdentical(tanh(0.0), 0.0)); + assert(tanh(1.0).isClose(sinh(1.0) / cosh(1.0))); +} + +private F _tanh(F)(F x) +{ + import std.math.traits : copysign; + import std.math.exponential : expm1; + import core.math : fabs; + import std.math.constants : LN2; + + // tanh(x) = (exp(x) - exp(-x))/(exp(x)+exp(-x)) + if (fabs(x) > F.mant_dig * F(LN2)) + { + return copysign(1, x); + } + + const y = expm1(2*x); + return y / (y + 2); +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + + assert(isClose(tanh(1.0L), sinh(1.0L) / cosh(1.0L), real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*********************************** + * 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 { return _acosh(x); } + +/// ditto +double acosh(double x) @safe pure nothrow @nogc { return _acosh(x); } + +/// ditto +float acosh(float x) @safe pure nothrow @nogc { return _acosh(x); } + +/// +@safe @nogc nothrow unittest +{ + import std.math.traits : isIdentical, isNaN; + + assert(isNaN(acosh(0.9))); + assert(isNaN(acosh(real.nan))); + assert(isIdentical(acosh(1.0), 0.0)); + assert(acosh(real.infinity) == real.infinity); + assert(isNaN(acosh(0.5))); +} + +private F _acosh(F)(F x) @safe pure nothrow @nogc +{ + import std.math.constants : LN2; + import std.math.exponential : log; + import core.math : sqrt; + + if (x > 1/F.epsilon) + return F(LN2) + log(x); + else + return log(x + sqrt(x*x - 1)); +} + +@safe @nogc nothrow unittest +{ + import std.math.operations : isClose; + + assert(isClose(acosh(cosh(3.0L)), 3.0L, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*********************************** + * 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 _asinh(x); } + +/// ditto +double asinh(double x) @safe pure nothrow @nogc { return _asinh(x); } + +/// ditto +float asinh(float x) @safe pure nothrow @nogc { return _asinh(x); } + +/// +@safe @nogc nothrow unittest +{ + import std.math.traits : isIdentical, isNaN; + + 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))); +} + +private F _asinh(F)(F x) +{ + import std.math.traits : copysign; + import core.math : fabs, sqrt; + import std.math.exponential : log, log1p; + import std.math.constants : LN2; + + return (fabs(x) > 1 / F.epsilon) + // beyond this point, x*x + 1 == x*x + ? copysign(F(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); +} + +@safe unittest +{ + import std.math.operations : isClose; + + assert(isClose(asinh(sinh(3.0L)), 3.0L, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} + +/*********************************** + * 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 +{ + import std.math.exponential : log1p; + + // 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); } + +/// +@safe @nogc nothrow unittest +{ + import std.math.traits : isIdentical, isNaN; + + 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); +} + +@safe unittest +{ + import std.math.operations : isClose; + + assert(isClose(atanh(tanh(0.5L)), 0.5, real.sizeof > double.sizeof ? 1e-15 : 1e-14)); +} diff --git a/libphobos/src/std/mathspecial.d b/libphobos/src/std/mathspecial.d index e35c74c..64ab9bf 100644 --- a/libphobos/src/std/mathspecial.d +++ b/libphobos/src/std/mathspecial.d @@ -50,7 +50,7 @@ * 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) + * Source: $(PHOBOSSRC std/mathspecial.d) */ module std.mathspecial; import std.internal.math.errorfunction; @@ -119,6 +119,7 @@ real logGamma(real x) */ real sgnGamma(real x) { + import core.math : rndtol; /* Author: Don Clugston. */ if (isNaN(x)) return x; if (x > 0) return 1.0; @@ -202,13 +203,14 @@ real logmdigammaInverse(real x) /** Incomplete beta integral * - * Returns incomplete beta integral of the arguments, evaluated + * Returns regularized 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. + * and is the same as the cumulative distribution function of the Beta + * distribution. * * The domain of definition is 0 <= x <= 1. In this * implementation a and b are restricted to positive values. @@ -253,21 +255,25 @@ real betaIncompleteInverse(real a, real b, real y ) * values of a and x. */ real gammaIncomplete(real a, real x ) -in { +in +{ assert(x >= 0); assert(a > 0); } -body { +do +{ return std.internal.math.gammafunction.gammaIncomplete(a, x); } /** ditto */ real gammaIncompleteCompl(real a, real x ) -in { +in +{ assert(x >= 0); assert(a > 0); } -body { +do +{ return std.internal.math.gammafunction.gammaIncompleteCompl(a, x); } @@ -278,11 +284,13 @@ body { * gammaIncompleteCompl( a, x ) = p. */ real gammaIncompleteComplInverse(real a, real p) -in { +in +{ assert(p >= 0 && p <= 1); assert(a > 0); } -body { +do +{ return std.internal.math.gammafunction.gammaIncompleteComplInv(a, p); } @@ -321,7 +329,7 @@ real erfc(real x) } -/** Normal distribution function. +/** Standard normal distribution function. * * The normal (or Gaussian, or bell-shaped) distribution is * defined as: @@ -343,7 +351,7 @@ real normalDistribution(real x) return std.internal.math.errorfunction.normalDistributionImpl(x); } -/** Inverse of Normal distribution function +/** Inverse of Standard normal distribution function * * Returns the argument, x, for which the area under the * Normal probability density function (integrated from @@ -352,10 +360,11 @@ real normalDistribution(real x) * Note: This function is only implemented to 80 bit precision. */ real normalDistributionInverse(real p) -in { +in +{ assert(p >= 0.0L && p <= 1.0L, "Domain error"); } -body +do { return std.internal.math.errorfunction.normalDistributionInvImpl(p); } diff --git a/libphobos/src/std/meta.d b/libphobos/src/std/meta.d index 308e50f..1209987 100644 --- a/libphobos/src/std/meta.d +++ b/libphobos/src/std/meta.d @@ -1,14 +1,18 @@ // Written in the D programming language. /** - * Templates to manipulate template argument lists (also known as type lists). + * Templates to manipulate + * $(DDSUBLINK spec/template, variadic-templates, template parameter sequences) + * (also known as $(I alias sequences)). * - * 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. + * Some operations on alias sequences are built into the language, + * such as `S[i]`, which accesses the element at index `i` in the + * sequence. `S[low .. high]` returns a new alias + * sequence that is a slice of the old one. * - * Several templates in this module use or operate on eponymous templates that + * For more information, see $(DDLINK ctarguments, Compile-time Sequences, Compile-time Sequences). + * + * $(B Note:) 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). * @@ -54,6 +58,7 @@ * $(TR $(TD Template instantiation) $(TD * $(LREF ApplyLeft) * $(LREF ApplyRight) + * $(LREF Instantiate) * )) * )) * @@ -62,26 +67,26 @@ * $(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. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/meta.d) */ module std.meta; +import std.traits : isAggregateType, Unqual, isIterable; +import std.range.primitives : isInfinite; + /** * 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; -} +alias AliasSeq(TList...) = TList; /// @safe unittest @@ -105,70 +110,50 @@ template AliasSeq(TList...) } -/** - Returns an `AliasSeq` expression of `Func` being - applied to every variadic template argument. - */ - /// @safe unittest { - auto ref ArgCall(alias Func, alias arg)() + // Creates a compile-time sequence of function call expressions + // that each call `func` with the next variadic template argument + template Map(alias func, args...) { - return Func(arg); - } + auto ref lazyItem() {return func(args[0]);} - template Map(alias Func, args...) - { - static if (args.length > 1) + static if (args.length == 1) { - alias Map = AliasSeq!(ArgCall!(Func, args[0]), Map!(Func, args[1 .. $])); + alias Map = lazyItem; } else { - alias Map = ArgCall!(Func, args[0]); + // recurse + alias Map = AliasSeq!(lazyItem, Map!(func, args[1 .. $])); } } - 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!(i => i ^^ 2, a, b)); + assert(a == 2); + assert(b == 4); - test(Map!(refSquare, a, b)); + test(Map!((ref i) => i *= i, a, b)); assert(a == 4); assert(b == 16); - testRef(Map!(refRetSquare, a, b)); + static void testRef(ref int a, ref int b) + { + assert(a++ == 16); + assert(b++ == 256); + } + + testRef(Map!(function ref(ref i) => i *= i, a, b)); assert(a == 17); assert(b == 257); } @@ -178,15 +163,15 @@ template AliasSeq(TList...) * * Not everything can be directly aliased. An alias cannot be declared * of - for example - a literal: - * - * `alias a = 4; //Error` - * + * --- + * alias a = 4; //Error + * --- * With this template any single entity can be aliased: - * - * `alias b = Alias!4; //OK` - * + * --- + * alias b = Alias!4; //OK + * --- * See_Also: - * To alias more than one thing at once, use $(LREF AliasSeq) + * To alias more than one thing at once, use $(LREF AliasSeq). */ alias Alias(alias a) = a; @@ -198,8 +183,8 @@ alias Alias(T) = T; { // 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 Head(Args...) = Alias!(Args[0]); + alias Tail(Args...) = Args[1 .. $]; alias Blah = AliasSeq!(3, int, "hello"); static assert(Head!Blah == 3); @@ -247,8 +232,6 @@ package template OldAlias(alias a) static assert(0, "Cannot alias " ~ a.stringof); } -import std.traits : isAggregateType, Unqual; - package template OldAlias(T) if (!isAggregateType!T || is(Unqual!T == T)) { @@ -258,7 +241,7 @@ if (!isAggregateType!T || is(Unqual!T == T)) @safe unittest { static struct Foo {} - static assert(is(OldAlias!(const(Foo)) == Foo)); + //static assert(is(OldAlias!(const(Foo)) == const Foo)); static assert(is(OldAlias!(const(int)) == const(int))); static assert(OldAlias!123 == 123); enum abc = 123; @@ -266,19 +249,21 @@ if (!isAggregateType!T || is(Unqual!T == T)) } /** - * 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. + * Returns the index of the first occurrence of `args[0]` in the + * sequence `args[1 .. $]`. `args` may be types or compile-time values. + * If not found, `-1` is returned. */ -template staticIndexOf(T, TList...) -{ - enum staticIndexOf = genericIndexOf!(T, TList).index; -} - -/// Ditto -template staticIndexOf(alias T, TList...) +template staticIndexOf(args...) +if (args.length >= 1) { - enum staticIndexOf = genericIndexOf!(T, TList).index; + enum staticIndexOf = + { + static foreach (idx, arg; args[1 .. $]) + static if (isSame!(args[0], arg)) + // `if (__ctfe)` is redundant here but avoids the "Unreachable code" warning. + if (__ctfe) return idx; + return -1; + }(); } /// @@ -294,34 +279,6 @@ template staticIndexOf(alias T, TList...) } } -// [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); @@ -345,18 +302,17 @@ if (args.length >= 1) } /** - * Returns an `AliasSeq` created from TList with the first occurrence, - * if any, of T removed. + * Returns an `AliasSeq` created from `args[1 .. $]` with the first occurrence, + * if any, of `args[0]` removed. */ -template Erase(T, TList...) -{ - alias Erase = GenericErase!(T, TList).result; -} - -/// Ditto -template Erase(alias T, TList...) +template Erase(args...) +if (args.length >= 1) { - alias Erase = GenericErase!(T, TList).result; + private enum pos = staticIndexOf!(args[0], args[1 .. $]); + static if (pos < 0) + alias Erase = args[1 .. $]; + else + alias Erase = AliasSeq!(args[1 .. pos + 1], args[pos + 2 .. $]); } /// @@ -367,29 +323,6 @@ template Erase(alias T, TList...) 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, @@ -403,54 +336,23 @@ if (args.length >= 1) /** - * Returns an `AliasSeq` created from TList with the all occurrences, - * if any, of T removed. + * Returns an `AliasSeq` created from `args[1 .. $]` with all occurrences, + * if any, of `args[0]` removed. */ -template EraseAll(T, TList...) -{ - alias EraseAll = GenericEraseAll!(T, TList).result; -} - -/// Ditto -template EraseAll(alias T, TList...) +template EraseAll(args...) +if (args.length >= 1) { - alias EraseAll = GenericEraseAll!(T, TList).result; + alias EraseAll = AliasSeq!(); + static foreach (arg; args[1 .. $]) + static if (!isSame!(args[0], arg)) + EraseAll = AliasSeq!(EraseAll, arg); } /// @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!(); - } + static assert(is(EraseAll!(long, Types) == AliasSeq!(int, int))); } @safe unittest @@ -464,39 +366,33 @@ if (args.length >= 1) equals!(real, 3, 4, 5, 9)); } +/* + * Returns `items[0 .. $ - 1]` if `item[$ - 1]` found in `items[0 .. $ - 1]`, and + * `items` otherwise. + * + * Params: + * items = list to be processed + * + * See_Also: $(LREF NoDuplicates) + */ +private template AppendUnique(items...) +{ + alias head = items[0 .. $ - 1]; + static if (staticIndexOf!(items[$ - 1], head) >= 0) + alias AppendUnique = head; + else + alias AppendUnique = items; +} /** - * Returns an `AliasSeq` created from TList with the all duplicate + * Returns an `AliasSeq` created from `args` with all duplicate * types removed. */ -template NoDuplicates(TList...) +template NoDuplicates(args...) { - 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 .. $]))); - } + alias NoDuplicates = AliasSeq!(); + static foreach (arg; args) + NoDuplicates = AppendUnique!(NoDuplicates, arg); } /// @@ -510,9 +406,19 @@ template NoDuplicates(TList...) @safe unittest { - // Bugzilla 14561: huge enums + import std.range : iota; + + // https://issues.dlang.org/show_bug.cgi?id=14561: huge enums alias LongList = Repeat!(1500, int); static assert(NoDuplicates!LongList.length == 1); + // https://issues.dlang.org/show_bug.cgi?id=17995: huge enums, revisited + + alias a = NoDuplicates!(AliasSeq!(1, Repeat!(1000, 3))); + alias b = NoDuplicates!(AliasSeq!(1, Repeat!(10, 3))); + static assert(a.length == b.length); + + static assert(NoDuplicates!(aliasSeqOf!(iota(7)), aliasSeqOf!(iota(7))) == aliasSeqOf!(iota(7))); + static assert(NoDuplicates!(aliasSeqOf!(iota(8)), aliasSeqOf!(iota(8))) == aliasSeqOf!(iota(8))); } @safe unittest @@ -526,7 +432,7 @@ template NoDuplicates(TList...) /** * Returns an `AliasSeq` created from TList with the first occurrence - * of type T, if found, replaced with type U. + * of T, if found, replaced with U. */ template Replace(T, U, TList...) { @@ -606,7 +512,7 @@ if (args.length >= 2) /** * Returns an `AliasSeq` created from TList with all occurrences - * of type T, if found, replaced with type U. + * of T, if found, replaced with U. */ template ReplaceAll(T, U, TList...) { @@ -685,44 +591,34 @@ if (args.length >= 2) } /** - * Returns an `AliasSeq` created from TList with the order reversed. + * Returns an `AliasSeq` created from `args` with the order reversed. */ -template Reverse(TList...) +template Reverse(args...) { - static if (TList.length <= 1) - { - alias Reverse = TList; - } - else - { - alias Reverse = - AliasSeq!( - Reverse!(TList[$/2 .. $ ]), - Reverse!(TList[ 0 .. $/2])); - } + alias Reverse = AliasSeq!(); + static foreach_reverse (arg; args) + Reverse = AliasSeq!(Reverse, arg); } /// @safe unittest { - alias Types = AliasSeq!(int, long, long, int, float); + alias Types = AliasSeq!(int, long, long, int, float, byte, ubyte, short, ushort, uint); alias TL = Reverse!(Types); - static assert(is(TL == AliasSeq!(float, int, long, long, int))); + static assert(is(TL == AliasSeq!(uint, ushort, short, ubyte, byte, 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. + * Returns the type from `TList` that is the most derived from type `T`. + * If no such type is 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 .. $]); + import std.traits : Select; + alias MostDerived = T; + static foreach (U; TList) + MostDerived = Select!(is(U : MostDerived), U, MostDerived); } /// @@ -738,19 +634,13 @@ template MostDerived(T, TList...) } /** - * Returns the `AliasSeq` TList with the types sorted so that the most + * Returns an `AliasSeq` with the elements of TList 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 .. $]))); + private enum cmp(T, U) = is(T : U); + alias DerivedToFront = staticSort!(cmp, TList); } /// @@ -763,36 +653,42 @@ template DerivedToFront(TList...) alias TL = DerivedToFront!(Types); static assert(is(TL == AliasSeq!(C, B, A))); + + alias TL2 = DerivedToFront!(A, A, A, B, B, B, C, C, C); + static assert(is(TL2 == AliasSeq!(C, C, C, B, B, B, A, A, A))); } +private enum staticMapExpandFactor = 150; +private string generateCases() +{ + string[staticMapExpandFactor] chunks; + chunks[0] = q{}; + static foreach (enum i; 0 .. staticMapExpandFactor - 1) + chunks[i + 1] = chunks[i] ~ `F!(Args[` ~ i.stringof ~ `]),`; + string ret = `AliasSeq!(`; + foreach (chunk; chunks) + ret ~= `q{alias staticMap = AliasSeq!(` ~ chunk ~ `);},`; + return ret ~ `)`; +} +private alias staticMapBasicCases = AliasSeq!(mixin(generateCases())); + /** Evaluates to $(D AliasSeq!(F!(T[0]), F!(T[1]), ..., F!(T[$ - 1]))). */ -template staticMap(alias F, T...) +template staticMap(alias F, Args ...) { - static if (T.length == 0) - { - alias staticMap = AliasSeq!(); - } - else static if (T.length == 1) - { - alias staticMap = AliasSeq!(F!(T[0])); - } + static if (Args.length < staticMapExpandFactor) + mixin(staticMapBasicCases[Args.length]); else - { - alias staticMap = - AliasSeq!( - staticMap!(F, T[ 0 .. $/2]), - staticMap!(F, T[$/2 .. $ ])); - } + alias staticMap = AliasSeq!(staticMap!(F, Args[0 .. $/2]), staticMap!(F, Args[$/2 .. $])); } /// @safe unittest { import std.traits : Unqual; - alias TL = staticMap!(Unqual, int, const int, immutable int); - static assert(is(TL == AliasSeq!(int, int, int))); + alias TL = staticMap!(Unqual, int, const int, immutable int, uint, ubyte, byte, short, ushort); + static assert(is(TL == AliasSeq!(int, int, int, uint, ubyte, byte, short, ushort))); } @safe unittest @@ -807,8 +703,17 @@ template staticMap(alias F, T...) 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))); + alias T = staticMap!(Unqual, int, const int, immutable int, uint, ubyte, byte, short, ushort, long); + static assert(is(T == AliasSeq!(int, int, int, uint, ubyte, byte, short, ushort, long))); +} + +// regression test for https://issues.dlang.org/show_bug.cgi?id=21088 +@system unittest // typeid opEquals is @system +{ + enum getTypeId(T) = typeid(T); + alias A = staticMap!(getTypeId, int); + + assert(A == typeid(int)); } /** @@ -820,20 +725,8 @@ 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 .. $ ]); - } + import core.internal.traits : allSat = allSatisfy; + alias allSatisfy = allSat!(F, T); } /// @@ -854,20 +747,8 @@ 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 .. $ ]); - } + import core.internal.traits : anySat = anySatisfy; + alias anySatisfy = anySat!(F, T); } /// @@ -879,30 +760,169 @@ template anySatisfy(alias F, T...) static assert( anySatisfy!(isIntegral, int, double)); } +private alias FilterShortCode = AliasSeq!( + q{ + alias Filter = Nothing; + }, + q{ + static if (pred!(TList[0])) + alias Filter = AliasSeq!(TList[0]); + else + alias Filter = Nothing; + }, + q{ + static if (pred!(TList[0])) + { + static if (pred!(TList[1])) + alias Filter = AliasSeq!(TList[0], TList[1]); + else + alias Filter = AliasSeq!(TList[0]); + } + else + { + static if (pred!(TList[1])) + alias Filter = AliasSeq!(TList[1]); + else + alias Filter = Nothing; + } + }, + q{ + static if (pred!(TList[0])) + { + static if (pred!(TList[1])) + { + static if (pred!(TList[2])) + alias Filter = AliasSeq!(TList[0], TList[1], TList[2]); + else + alias Filter = AliasSeq!(TList[0], TList[1]); + } + else + { + static if (pred!(TList[2])) + alias Filter = AliasSeq!(TList[0], TList[2]); + else + alias Filter = AliasSeq!(TList[0]); + } + } + else + { + static if (pred!(TList[1])) + { + static if (pred!(TList[2])) + alias Filter = AliasSeq!(TList[1], TList[2]); + else + alias Filter = AliasSeq!(TList[1]); + } + else + { + static if (pred!(TList[2])) + alias Filter = AliasSeq!(TList[2]); + else + alias Filter = Nothing; + } + } + }, + q{ + static if (pred!(TList[0])) + { + static if (pred!(TList[1])) + { + static if (pred!(TList[2])) + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[0], TList[1], TList[2], TList[3]); + else + alias Filter = AliasSeq!(TList[0], TList[1], TList[2]); + } + else + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[0], TList[1], TList[3]); + else + alias Filter = AliasSeq!(TList[0], TList[1]); + } + } + else + { + static if (pred!(TList[2])) + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[0], TList[2], TList[3]); + else + alias Filter = AliasSeq!(TList[0], TList[2]); + } + else + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[0], TList[3]); + else + alias Filter = AliasSeq!(TList[0]); + } + } + } + else + { + static if (pred!(TList[1])) + { + static if (pred!(TList[2])) + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[1], TList[2], TList[3]); + else + alias Filter = AliasSeq!(TList[1], TList[2]); + } + else + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[1], TList[3]); + else + alias Filter = AliasSeq!(TList[1]); + } + } + else + { + static if (pred!(TList[2])) + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[2], TList[3]); + else + alias Filter = AliasSeq!(TList[2]); + } + else + { + static if (pred!(TList[3])) + alias Filter = AliasSeq!(TList[3]); + else + alias Filter = Nothing; + } + } + } + } +); + +private enum filterExpandFactor = FilterShortCode.length; +package alias Nothing = AliasSeq!(); // yes, this really does speed up compilation! /** - * Filters an $(D AliasSeq) using a template predicate. Returns a - * $(D AliasSeq) of the elements which satisfy the predicate. + * Filters an `AliasSeq` using a template predicate. Returns an + * `AliasSeq` of the elements which satisfy the predicate. */ -template Filter(alias pred, TList...) +template Filter(alias pred, TList ...) { - static if (TList.length == 0) + static if (TList.length < filterExpandFactor) { - alias Filter = AliasSeq!(); - } - else static if (TList.length == 1) - { - static if (pred!(TList[0])) - alias Filter = AliasSeq!(TList[0]); - else - alias Filter = AliasSeq!(); + mixin(FilterShortCode[TList.length]); } else { - alias Filter = - AliasSeq!( - Filter!(pred, TList[ 0 .. $/2]), - Filter!(pred, TList[$/2 .. $ ])); + template MaybeNothing(Q ...) + { + static if (pred!(Q[0])) + alias MaybeNothing = AliasSeq!(Q[0]); + else + alias MaybeNothing = Nothing; + } + alias Filter = staticMap!(MaybeNothing, TList); } } @@ -928,9 +948,15 @@ template Filter(alias pred, TList...) static assert(is(Filter!isPointer == AliasSeq!())); } +@safe unittest +{ + enum Yes(T) = true; + static struct S {} + static assert(is(Filter!(Yes, const(int), const(S)) == AliasSeq!(const(int), const(S)))); +} // Used in template predicate unit tests below. -private version (unittest) +private version (StdUnittest) { template testAlways(T...) { @@ -969,7 +995,7 @@ template templateNot(alias pred) @safe unittest { - foreach (T; AliasSeq!(int, staticMap, 42)) + static foreach (T; AliasSeq!(int, staticMap, 42)) { static assert(!Instantiate!(templateNot!testAlways, T)); static assert(Instantiate!(templateNot!testNever, T)); @@ -1013,14 +1039,14 @@ template templateAnd(Preds...) static assert(storesNegativeNumbers!int); static assert(!storesNegativeNumbers!string && !storesNegativeNumbers!uint); - // An empty list of predicates always yields true. + // An empty sequence of predicates always yields true. alias alwaysTrue = templateAnd!(); static assert(alwaysTrue!int); } @safe unittest { - foreach (T; AliasSeq!(int, staticMap, 42)) + static foreach (T; AliasSeq!(int, staticMap, 42)) { static assert( Instantiate!(templateAnd!(), T)); static assert( Instantiate!(templateAnd!(testAlways), T)); @@ -1071,14 +1097,14 @@ template templateOr(Preds...) static assert( isPtrOrUnsigned!uint && isPtrOrUnsigned!(short*)); static assert(!isPtrOrUnsigned!int && !isPtrOrUnsigned!(string)); - // An empty list of predicates never yields true. + // An empty sequence of predicates never yields true. alias alwaysFalse = templateOr!(); static assert(!alwaysFalse!int); } @safe unittest { - foreach (T; AliasSeq!(int, staticMap, 42)) + static foreach (T; AliasSeq!(int, staticMap, 42)) { static assert( Instantiate!(templateOr!(testAlways), T)); static assert( Instantiate!(templateOr!(testAlways, testAlways), T)); @@ -1097,41 +1123,26 @@ template templateOr(Preds...) } /** - * Converts an input range $(D range) to an alias sequence. + * Converts any foreach-iterable entity (e.g. an input range) to an alias sequence. + * + * Params: + * iter = the entity to convert into an `AliasSeq`. It must be able to be able to be iterated over using + * a $(LINK2 https://dlang.org/spec/statement.html#foreach-statement, foreach-statement). + * + * Returns: + * An `AliasSeq` containing the values produced by iterating over `iter`. */ -template aliasSeqOf(alias range) +template aliasSeqOf(alias iter) +if (isIterable!(typeof(iter)) && !isInfinite!(typeof(iter))) { - import std.traits : isArray, isNarrowString; + import std.array : array; - 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 + struct Impl { - 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."); - } + static foreach (size_t i, el; iter.array) + mixin(`auto e` ~ i.stringof ~ ` = el;`); } + enum aliasSeqOf = Impl.init.tupleof; } /// @@ -1190,6 +1201,34 @@ template aliasSeqOf(alias range) } } +@safe unittest +{ + struct S + { + int opApply(scope int delegate(ref int) dg) + { + foreach (int i; 3 .. 5) + if (auto r = dg(i)) + return r; + return 0; + } + } + static assert(aliasSeqOf!(S.init) == AliasSeq!(3, 4)); +} + +@safe unittest +{ + struct Infinite + { + int front(); + void popFront(); + enum empty = false; + } + enum infinite = Infinite(); + static assert(isInfinite!Infinite); + static assert(!__traits(compiles, aliasSeqOf!infinite)); +} + /** * $(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially applies) * $(D_PARAM Template) by binding its first (left) or last (right) arguments @@ -1289,7 +1328,7 @@ private template SmartAlias(T...) } else { - alias SmartAlias = AliasSeq!T; + alias SmartAlias = T; } } @@ -1313,36 +1352,39 @@ private template SmartAlias(T...) } /** - * Creates an `AliasSeq` which repeats a type or an `AliasSeq` exactly `n` times. + * Creates an `AliasSeq` which repeats `items` exactly `n` times. */ -template Repeat(size_t n, TList...) -if (n > 0) +template Repeat(size_t n, items...) { - static if (n == 1) + static if (n == 0) { - alias Repeat = AliasSeq!TList; - } - else static if (n == 2) - { - alias Repeat = AliasSeq!(TList, TList); + alias Repeat = AliasSeq!(); } else { - alias R = Repeat!((n - 1) / 2, TList); - static if ((n - 1) % 2 == 0) + alias Repeat = items; + enum log2n = { - alias Repeat = AliasSeq!(TList, R, R); - } - else + uint result = 0; + auto x = n; + while (x >>= 1) + ++result; + return result; + }(); + static foreach (i; 0 .. log2n) { - alias Repeat = AliasSeq!(TList, TList, R, R); + Repeat = AliasSeq!(Repeat, Repeat); } + Repeat = AliasSeq!(Repeat, Repeat!(n - (1u << log2n), items)); } } /// @safe unittest { + alias ImInt0 = Repeat!(0, int); + static assert(is(ImInt0 == AliasSeq!())); + alias ImInt1 = Repeat!(1, immutable(int)); static assert(is(ImInt1 == AliasSeq!(immutable(int)))); @@ -1356,6 +1398,11 @@ if (n > 0) alias Composite = AliasSeq!(uint, int); alias Composite2 = Repeat!(2, Composite); static assert(is(Composite2 == AliasSeq!(uint, int, uint, int))); + + alias ImInt10 = Repeat!(10, int); + static assert(is(ImInt10 == AliasSeq!(int, int, int, int, int, int, int, int, int, int))); + + alias Big = Repeat!(1_000_000, int); } @@ -1374,11 +1421,11 @@ if (n > 0) } /** - * Sorts a $(LREF AliasSeq) using $(D cmp). + * Sorts an $(LREF AliasSeq) using `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) + * cmp = A template that returns a `bool` (if its first argument is less than the second one) + * or an `int` (-1 means less than, 0 means equal, 1 means greater than) * * Seq = The $(LREF AliasSeq) to sort * @@ -1455,30 +1502,24 @@ if (Seq.length == 2) } /** - * Checks if an $(LREF AliasSeq) is sorted according to $(D cmp). + * Checks if an $(LREF AliasSeq) is sorted according to `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) + * cmp = A template that returns a `bool` (if its first argument is less than the second one) + * or an `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(alias cmp, items...) = { - enum staticIsSorted = - isLessEq!(cmp, Seq[($ / 2) - 1], Seq[$ / 2]) && - staticIsSorted!(cmp, Seq[0 .. $ / 2]) && - staticIsSorted!(cmp, Seq[$ / 2 .. $]); - } -} + static if (items.length > 1) + static foreach (i, item; items[1 .. $]) + static if (!isLessEq!(cmp, items[i], item)) + if (__ctfe) return false; + return true; + }(); /// @safe unittest @@ -1498,35 +1539,28 @@ template staticIsSorted(alias cmp, Seq...) } /** -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. +Selects a subset of `Args` by stepping with fixed `stepSize` over the sequence. +A negative `stepSize` starts iteration with the last element. Params: stepSize = Number of elements to increment on each iteration. Can't be `0`. - Args = Template arguments + Args = Template arguments. -Returns: A template argument list filtered by the selected stride. +Returns: An `AliasSeq` filtered by the selected stride. */ template Stride(int stepSize, Args...) if (stepSize != 0) { - static if (Args.length == 0) + alias Stride = AliasSeq!(); + static if (stepSize > 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 .. $])); + static foreach (i; 0 .. (Args.length + stepSize - 1) / stepSize) + Stride = AliasSeq!(Stride, Args[i * stepSize]); } else { - static if (-stepSize >= Args.length) - alias Stride = AliasSeq!(Args[$ - 1]); - else - alias Stride = AliasSeq!(Args[$ - 1], Stride!(stepSize, Args[0 .. $ + stepSize])); + static foreach (i; 0 .. (Args.length - stepSize - 1) / -stepSize) + Stride = AliasSeq!(Stride, Args[$ - 1 + i * stepSize]); } } @@ -1551,6 +1585,42 @@ if (stepSize != 0) static assert(!__traits(compiles, Stride!(0, int))); } +/** + * Instantiates the given template with the given 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). + * + * Params: + * Template = The template to instantiate. + * Params = The parameters with which to instantiate the template. + * Returns: + * The instantiated template. + */ +alias Instantiate(alias Template, Params...) = Template!Params; + +/// +@safe unittest +{ + // ApplyRight combined with Instantiate can be used to apply various + // templates to the same parameters. + import std.string : leftJustify, center, rightJustify; + alias functions = staticMap!(ApplyRight!(Instantiate, string), + leftJustify, center, rightJustify); + string result = ""; + static foreach (f; functions) + { + { + auto x = &f; // not a template, but a function instantiation + result ~= x("hello", 7); + result ~= ";"; + } + } + + assert(result == "hello ; hello ; hello;"); +} + // : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : // private: @@ -1559,40 +1629,42 @@ private: * 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.) + * a == b - at least one rvalue (literals, enums, function calls) + * __traits(isSame, a, b) - other cases (types, variables, functions, templates, etc.) */ -private template isSame(ab...) -if (ab.length == 2) +private template isSame(alias a, alias b) { - static if (__traits(compiles, expectType!(ab[0]), - expectType!(ab[1]))) + static if (!is(typeof(&a && &b)) // at least one is an rvalue + && __traits(compiles, { enum isSame = a == b; })) // c-t comparable { - 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]); + enum isSame = a == b; } else { - enum isSame = __traits(isSame, ab[0], ab[1]); + enum isSame = __traits(isSame, a, b); } } -private template expectType(T) {} -private template expectBool(bool b) {} +// TODO: remove after https://github.com/dlang/dmd/pull/11320 and https://issues.dlang.org/show_bug.cgi?id=21889 are fixed +private template isSame(A, B) +{ + enum isSame = is(A == B); +} @safe unittest { + static assert(!isSame!(Object, const Object)); + static assert(!isSame!(Object, immutable Object)); + + static struct S {} + static assert(!isSame!(S, const S)); + static assert( isSame!(S(), S())); + + static class C {} + static assert(!isSame!(C, const C)); + static assert( isSame!(int, int)); + static assert(!isSame!(int, const int)); + static assert(!isSame!(const int, immutable int)); static assert(!isSame!(int, short)); enum a = 1, b = 1, c = 2, s = "a", t = "a"; @@ -1617,6 +1689,8 @@ private template expectBool(bool b) {} static assert( isSame!(X, X)); static assert(!isSame!(X, Y)); static assert(!isSame!(Y, Z)); + static assert( isSame!(X, 1)); + static assert( isSame!(1, X)); int foo(); int bar(); @@ -1637,28 +1711,12 @@ private template expectBool(bool b) {} } /* - * [internal] Confines a tuple within a template. + * [internal] Wraps a sequence in a template. Used only in unittests. */ 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; - } - } + alias Expand = T; + enum equals(U...) = isSame!(Pack!T, Pack!U); } @safe unittest @@ -1666,14 +1724,3 @@ private template Pack(T...) 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 index 453e2ec..e4000d4 100644 --- a/libphobos/src/std/mmfile.d +++ b/libphobos/src/std/mmfile.d @@ -2,15 +2,15 @@ /** * Read and write memory mapped files. - * Copyright: Copyright Digital Mars 2004 - 2009. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/mmfile.d) * * $(SCRIPT inhibitQuickIndex = 1;) */ -/* Copyright Digital Mars 2004 - 2009. +/* Copyright The D Language Foundation 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) @@ -31,7 +31,8 @@ import std.internal.cstring; version (Windows) { - import core.sys.windows.windows; + import core.sys.windows.winbase; + import core.sys.windows.winnt; import std.utf; import std.windows.syserror; } @@ -67,7 +68,8 @@ class MmFile * Open memory mapped file filename for reading. * File is closed when the object instance is deleted. * Throws: - * std.file.FileException + * - On POSIX, $(REF ErrnoException, std, exception). + * - On Windows, $(REF WindowsException, std, windows, syserror). */ this(string filename) { @@ -88,7 +90,7 @@ class MmFile int oflag; int fmode; - switch (mode) + final switch (mode) { case Mode.read: flags = MAP_SHARED; @@ -118,9 +120,6 @@ class MmFile oflag = O_RDWR; fmode = 0; break; - - default: - assert(0); } fd = fildes; @@ -166,7 +165,8 @@ class MmFile * with 0 meaning map the entire file. The window size must be a * multiple of the memory allocation page size. * Throws: - * std.file.FileException + * - On POSIX, $(REF ErrnoException, std, exception). + * - On Windows, $(REF WindowsException, std, windows, syserror). */ this(string filename, Mode mode, ulong size, void* address, size_t window = 0) @@ -184,7 +184,7 @@ class MmFile uint dwCreationDisposition; uint flProtect; - switch (mode) + final switch (mode) { case Mode.read: dwDesiredAccess2 = GENERIC_READ; @@ -218,9 +218,6 @@ class MmFile flProtect = PAGE_WRITECOPY; dwDesiredAccess = FILE_MAP_COPY; break; - - default: - assert(0); } if (filename != null) @@ -281,7 +278,7 @@ class MmFile int oflag; int fmode; - switch (mode) + final switch (mode) { case Mode.read: flags = MAP_SHARED; @@ -311,9 +308,6 @@ class MmFile oflag = O_RDWR; fmode = 0; break; - - default: - assert(0); } if (filename.length) @@ -343,7 +337,6 @@ class MmFile else { fd = -1; - version (CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON; flags |= MAP_ANON; } this.size = size; @@ -440,6 +433,11 @@ class MmFile } /** + * Forwards `length`. + */ + alias opDollar = length; + + /** * Read-only property returning the file mode. */ Mode mode() @@ -648,9 +646,10 @@ private: win = sysinfo.dwAllocationGranularity; +/ } - else version (linux) + else version (Posix) { - // getpagesize() is not defined in the unix D headers so use the guess + import core.sys.posix.unistd; + win = cast(size_t) sysconf(_SC_PAGESIZE); } string test_file = std.file.deleteme ~ "-testing.txt"; MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew, @@ -671,7 +670,6 @@ private: assert( data2[$-1] == 'b' ); destroy(mf); - GC.free(&mf); std.file.remove(test_file); // Create anonymous mapping @@ -679,7 +677,7 @@ private: } version (linux) -@system unittest // Issue 14868 +@system unittest // https://issues.dlang.org/show_bug.cgi?id=14868 { import std.file : deleteme; import std.typecons : scoped; @@ -702,7 +700,9 @@ version (linux) assert(.close(fd) == -1); } -@system unittest // Issue 14994, 14995 +// https://issues.dlang.org/show_bug.cgi?id=14994 +// https://issues.dlang.org/show_bug.cgi?id=14995 +@system unittest { import std.file : deleteme; import std.typecons : scoped; @@ -719,3 +719,101 @@ version (linux) scope(exit) std.file.remove(fn); verifyThrown(scoped!MmFile(fn, MmFile.Mode.readWrite, 0, null)); } + +@system unittest +{ + MmFile shar = new MmFile(null, MmFile.Mode.readWrite, 10, null, 0); + void[] output = shar[0 .. $]; +} + +@system unittest +{ + import std.file : deleteme; + auto name = std.file.deleteme ~ "-test.tmp"; + scope(exit) std.file.remove(name); + + std.file.write(name, "abcd"); + { + scope MmFile mmf = new MmFile(name); + string p; + + assert(mmf[0] == 'a'); + p = cast(string) mmf[]; + assert(p[1] == 'b'); + p = cast(string) mmf[0 .. 4]; + assert(p[2] == 'c'); + } + { + scope MmFile mmf = new MmFile(name, MmFile.Mode.read, 0, null); + string p; + + assert(mmf[0] == 'a'); + p = cast(string) mmf[]; + assert(mmf.length == 4); + assert(p[1] == 'b'); + p = cast(string) mmf[0 .. 4]; + assert(p[2] == 'c'); + } + std.file.remove(name); + { + scope MmFile mmf = new MmFile(name, MmFile.Mode.readWriteNew, 4, null); + char[] p = cast(char[]) mmf[]; + p[] = "1234"; + mmf[3] = '5'; + assert(mmf[2] == '3'); + assert(mmf[3] == '5'); + } + { + string p = cast(string) std.file.read(name); + assert(p[] == "1235"); + } + { + scope MmFile mmf = new MmFile(name, MmFile.Mode.readWriteNew, 4, null); + char[] p = cast(char[]) mmf[]; + p[] = "5678"; + mmf[3] = '5'; + assert(mmf[2] == '7'); + assert(mmf[3] == '5'); + assert(cast(string) mmf[] == "5675"); + } + { + string p = cast(string) std.file.read(name); + assert(p[] == "5675"); + } + { + scope MmFile mmf = new MmFile(name, MmFile.Mode.readWrite, 4, null); + char[] p = cast(char[]) mmf[]; + assert(cast(char[]) mmf[] == "5675"); + p[] = "9102"; + mmf[2] = '5'; + assert(cast(string) mmf[] == "9152"); + } + { + string p = cast(string) std.file.read(name); + assert(p[] == "9152"); + } + std.file.remove(name); + { + scope MmFile mmf = new MmFile(name, MmFile.Mode.readWrite, 4, null); + char[] p = cast(char[]) mmf[]; + p[] = "abcd"; + mmf[2] = '5'; + assert(cast(string) mmf[] == "ab5d"); + } + { + string p = cast(string) std.file.read(name); + assert(p[] == "ab5d"); + } + { + scope MmFile mmf = new MmFile(name, MmFile.Mode.readCopyOnWrite, 4, null); + char[] p = cast(char[]) mmf[]; + assert(cast(string) mmf[] == "ab5d"); + p[] = "9102"; + mmf[2] = '5'; + assert(cast(string) mmf[] == "9152"); + } + { + string p = cast(string) std.file.read(name); + assert(p[] == "ab5d"); + } +} diff --git a/libphobos/src/std/net/curl.d b/libphobos/src/std/net/curl.d index 445f996..7ea2ceb 100644 --- a/libphobos/src/std/net/curl.d +++ b/libphobos/src/std/net/curl.d @@ -1,7 +1,7 @@ // Written in the D programming language. /** -Networking client functionality as provided by $(HTTP _curl.haxx.se/libcurl, +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. @@ -28,19 +28,21 @@ 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). +$(LINK2 http://downloads.dlang.org/other/index.html, download archive page). + +This module is not available for iOS, tvOS or watchOS. 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) +of the language. Futhermore it provides $(MREF_ALTTEXT range, std,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 +$(LREF byLineAsync) and $(LREF byChunkAsync) provides asynchronous +$(MREF_ALTTEXT range, std,range) 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. @@ -86,9 +88,9 @@ 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)) +$(TR $(TDNW $(LREF HTTP)) $(TD `HTTP` struct for advanced usage)) +$(TR $(TDNW $(LREF FTP)) $(TD `FTP` struct for advanced usage)) +$(TR $(TDNW $(LREF SMTP)) $(TD `SMTP` struct for advanced usage)) ) @@ -139,13 +141,13 @@ 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) +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). +Credits: The functionally is based on $(HTTP curl.haxx.se/libcurl, libcurl). LibCurl is licensed under an MIT/X derivative license. */ /* @@ -156,36 +158,39 @@ Distributed under the Boost Software License, Version 1.0. */ module std.net.curl; -import core.thread; -import etc.c.curl; -import std.concurrency; -import std.encoding; -import std.exception; -import std.meta; +public import etc.c.curl : CurlOption; +import core.time : dur; +import etc.c.curl : CURLcode; import std.range.primitives; -import std.socket : InternetAddress; -import std.traits; -import std.typecons; +import std.encoding : EncodingScheme; +import std.traits : isSomeChar; +import std.typecons : Flag, Yes, No, Tuple; -import std.internal.cstring; +version (iOS) + version = iOSDerived; +else version (TVOS) + version = iOSDerived; +else version (WatchOS) + version = iOSDerived; -public import etc.c.curl : CurlOption; +version (iOSDerived) {} +else: -version (unittest) +version (StdUnittest) { - // 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, SocketShutdown, TcpSocket; + import std.socket : Socket, SocketShutdown; private struct TestServer { + import std.concurrency : Tid; + + import std.socket : Socket, TcpSocket; + string addr() { return _addr; } void handle(void function(Socket s) dg) { + import std.concurrency : send; tid.send(dg); } @@ -196,6 +201,9 @@ version (unittest) static void loop(shared TcpSocket listener) { + import std.concurrency : OwnerTerminated, receiveOnly; + import std.stdio : stderr; + try while (true) { void function(Socket) handler = void; @@ -207,13 +215,17 @@ version (unittest) } catch (Throwable e) { - stderr.writeln(e); // Bugzilla 7018 + // https://issues.dlang.org/show_bug.cgi?id=7018 + stderr.writeln(e); } } } private TestServer startServer() { + import std.concurrency : spawn; + import std.socket : INADDR_LOOPBACK, InternetAddress, TcpSocket; + tlsInit = true; auto sock = new TcpSocket; sock.bind(new InternetAddress(INADDR_LOOPBACK, InternetAddress.PORT_ANY)); @@ -223,11 +235,14 @@ version (unittest) return TestServer(addr, tid, sock); } + /** Test server */ __gshared TestServer server; + /** Thread-local storage init */ bool tlsInit; private ref TestServer testServer() { + import std.concurrency : initOnce; return initOnce!server(startServer()); } @@ -365,7 +380,7 @@ CALLBACK_PARAMS = $(TABLE , * --- * import std.net.curl; * // Two requests below will do the same. - * string content; + * char[] content; * * // Explicit connection provided * content = get!HTTP("dlang.org"); @@ -407,7 +422,7 @@ private template isCurlConn(Conn) * Example: * ---- * import std.net.curl; - * download("d-lang.appspot.com/testUrl2", "/tmp/downloaded-http-file"); + * download("https://httpbin.org/get", "/tmp/downloaded-http-file"); * ---- */ void download(Conn = AutoProtocol)(const(char)[] url, string saveToPath, Conn conn = Conn()) @@ -464,7 +479,7 @@ if (isCurlConn!Conn) * ---- * import std.net.curl; * upload("/tmp/downloaded-ftp-file", "ftp.digitalmars.com/sieve.ds"); - * upload("/tmp/downloaded-http-file", "d-lang.appspot.com/testUrl2"); + * upload("/tmp/downloaded-http-file", "https://httpbin.org/post"); * ---- */ void upload(Conn = AutoProtocol)(string loadFromPath, const(char)[] url, Conn conn = Conn()) @@ -531,16 +546,16 @@ if (isCurlConn!Conn) * 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 + * The template parameter `T` specifies the type to return. Possible values + * are `char` and `ubyte` to return `char[]` or `ubyte[]`. If asking + * for `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"); + * auto content = get("https://httpbin.org/get"); * ---- * * Returns: @@ -548,7 +563,7 @@ if (isCurlConn!Conn) * * Throws: * - * $(D CurlException) on error. + * `CurlException` on error. * * See_Also: $(LREF HTTP.Method) */ @@ -595,15 +610,15 @@ if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) ) * 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 + * of `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 + * T = The template parameter `T` specifies the type to return. Possible values + * are `char` and `ubyte` to return `char[]` or `ubyte[]`. If asking + * for `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. * @@ -611,8 +626,8 @@ if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) ) * ---- * 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]); + * auto content1 = post("https://httpbin.org/post", ["name1" : "value1", "name2" : "value2"]); + * auto content2 = post("https://httpbin.org/post", [1,2,3,4]); * ---- * * Returns: @@ -668,19 +683,27 @@ if (is(T == char) || is(T == ubyte)) { import std.uri : urlEncode; - return post(url, urlEncode(postDict), conn); + return post!T(url, urlEncode(postDict), conn); } @system unittest { + import std.algorithm.searching : canFind; + import std.meta : AliasSeq; + + static immutable expected = ["name1=value1&name2=value2", "name2=value2&name1=value1"]; + 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" || res == "name2=value2&name1=value1"); + foreach (T; AliasSeq!(char, ubyte)) + { + testServer.handle((s) { + auto req = s.recvReq!char; + s.send(httpOK(req.bdy)); + }); + auto res = post!T(host ~ "/path", ["name1" : "value1", "name2" : "value2"]); + assert(canFind(expected, res)); + } } } @@ -694,16 +717,16 @@ if (is(T == char) || is(T == ubyte)) * 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 + * The template parameter `T` specifies the type to return. Possible values + * are `char` and `ubyte` to return `char[]` or `ubyte[]`. If asking + * for `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", + * auto content = put("https://httpbin.org/put", * "Putting this data"); * ---- * @@ -762,7 +785,7 @@ if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) ) * Example: * ---- * import std.net.curl; - * del("d-lang.appspot.com/testUrl2"); + * del("https://httpbin.org/delete"); * ---- * * See_Also: $(LREF HTTP.Method) @@ -779,6 +802,7 @@ if (isCurlConn!Conn) { import std.algorithm.searching : findSplitAfter; import std.conv : text; + import std.exception : enforce; auto trimmed = url.findSplitAfter("ftp://")[1]; auto t = trimmed.findSplitAfter("/"); @@ -824,14 +848,14 @@ if (isCurlConn!Conn) * 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[]). + * The template parameter `T` specifies the type to return. Possible values + * are `char` and `ubyte` to return `char[]` or `ubyte[]`. * * Example: * ---- * import std.net.curl; * auto http = HTTP(); - * options("d-lang.appspot.com/testUrl2", http); + * options("https://httpbin.org/headers", http); * writeln("Allow set to " ~ http.responseHeaders["Allow"]); * ---- * @@ -868,13 +892,13 @@ if (is(T == char) || is(T == ubyte)) * 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[]). + * The template parameter `T` specifies the type to return. Possible values + * are `char` and `ubyte` to return `char[]` or `ubyte[]`. * * Example: * ---- * import std.net.curl; - * trace("d-lang.appspot.com/testUrl1"); + * trace("https://httpbin.org/headers"); * ---- * * Returns: @@ -909,13 +933,13 @@ if (is(T == char) || is(T == ubyte)) * 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[]). + * The template parameter `T` specifies the type to return. Possible values + * are `char` and `ubyte` to return `char[]` or `ubyte[]`. * * Example: * ---- * import std.net.curl; - * connect("d-lang.appspot.com/testUrl1"); + * connect("https://httpbin.org/headers"); * ---- * * Returns: @@ -953,14 +977,14 @@ if (is(T == char) || is(T == 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[]). + * The template parameter `T` specifies the type to return. Possible values + * are `char` and `ubyte` to return `char[]` or `ubyte[]`. * * Example: * ---- * auto http = HTTP(); * http.addRequestHeader("Content-Type", "application/json"); - * auto content = patch("d-lang.appspot.com/testUrl2", `{"title": "Patched Title"}`, http); + * auto content = patch("https://httpbin.org/patch", `{"title": "Patched Title"}`, http); * ---- * * Returns: @@ -1001,6 +1025,8 @@ private auto _basicHTTP(T)(const(char)[] url, const(void)[] sendData, HTTP clien { import std.algorithm.comparison : min; import std.format : format; + import std.exception : enforce; + import etc.c.curl : CurlSeek, CurlSeekPos; immutable doSend = sendData !is null && (client.method == HTTP.Method.post || @@ -1077,6 +1103,7 @@ private auto _basicHTTP(T)(const(char)[] url, const(void)[] sendData, HTTP clien @system unittest { import std.algorithm.searching : canFind; + import std.exception : collectException; testServer.handle((s) { auto req = s.recvReq; @@ -1088,7 +1115,8 @@ private auto _basicHTTP(T)(const(char)[] url, const(void)[] sendData, HTTP clien assert(e.status == 404); } -// Bugzilla 14760 - content length must be reset after post +// Content length must be reset after post +// https://issues.dlang.org/show_bug.cgi?id=14760 @system unittest { import std.algorithm.searching : canFind; @@ -1197,6 +1225,7 @@ private auto _decodeContent(T)(ubyte[] content, string encoding) } else { + import std.exception : enforce; import std.format : format; // Optimally just return the utf8 encoded content @@ -1252,7 +1281,7 @@ struct ByLineBuffer(Char) /** 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 + * other request properties is to be customized then set the `conn` parameter * with a HTTP/FTP instance that has these properties set. * * Example: @@ -1264,7 +1293,7 @@ struct ByLineBuffer(Char) * * Params: * url = The url to receive content from - * keepTerminator = $(D Yes.keepTerminator) signals that the line terminator should be + * keepTerminator = `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. @@ -1302,6 +1331,7 @@ if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) @property @safe Char[] front() { + import std.exception : enforce; enforce!CurlException(currentValid, "Cannot call front() on empty range"); return current; } @@ -1309,6 +1339,7 @@ if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) void popFront() { import std.algorithm.searching : findSplitAfter, findSplit; + import std.exception : enforce; enforce!CurlException(currentValid, "Cannot call popFront() on empty range"); if (lines.empty) @@ -1361,7 +1392,7 @@ if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) /** 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 + * other request properties is to be customized then set the `conn` parameter * with a HTTP/FTP instance that has these properties set. * * Example: @@ -1458,6 +1489,8 @@ private T[] _getForRange(T,Conn)(const(char)[] url, Conn conn) */ private mixin template WorkerThreadProtocol(Unit, alias units) { + import core.time : Duration; + @property bool empty() { tryEnsureUnits(); @@ -1476,7 +1509,9 @@ private mixin template WorkerThreadProtocol(Unit, alias units) void popFront() { + import std.concurrency : send; import std.format : format; + tryEnsureUnits(); assert(state == State.gotUnits, format("Expected %s but got $s", @@ -1492,7 +1527,9 @@ private mixin template WorkerThreadProtocol(Unit, alias units) */ bool wait(Duration d) { + import core.time : dur; import std.datetime.stopwatch : StopWatch; + import std.concurrency : receiveTimeout; if (state == State.gotUnits) return true; @@ -1543,6 +1580,7 @@ private mixin template WorkerThreadProtocol(Unit, alias units) void tryEnsureUnits() { + import std.concurrency : receive; while (true) { final switch (state) @@ -1574,41 +1612,14 @@ private mixin template WorkerThreadProtocol(Unit, alias units) } } -// @@@@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 + * properties is to be customized then set the `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 + * If `postData` is non-_null the method will be set to `post` for HTTP * requests. * * The background thread will buffer up to transmitBuffers number of lines @@ -1617,8 +1628,8 @@ private static struct AsyncLineInputRange(Char) * 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 + * until data becomes available. An exception to this is the `wait(Duration)` method on + * the $(LREF LineInputRange). This method will wait at maximum for the * specified duration and return true if data is available. * * Example: @@ -1649,7 +1660,7 @@ private static struct AsyncLineInputRange(Char) * Params: * url = The url to receive content from * postData = Data to HTTP Post - * keepTerminator = $(D Yes.keepTerminator) signals that the line terminator should be + * keepTerminator = `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 @@ -1677,17 +1688,18 @@ if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) } else { + import std.concurrency : OnCrowding, send, setMaxMailboxSize, spawn, thisTid, Tid; // 50 is just an arbitrary number for now setMaxMailboxSize(thisTid, 50, OnCrowding.block); - auto tid = spawn(&_spawnAsync!(Conn, Char, Terminator)); + auto tid = spawn(&_async!().spawn!(Conn, Char, Terminator)); tid.send(thisTid); tid.send(terminator); tid.send(keepTerminator == Yes.keepTerminator); - _asyncDuplicateConnection(url, conn, postData, tid); + _async!().duplicateConnection(url, conn, postData, tid); - return AsyncLineInputRange!Char(tid, transmitBuffers, - Conn.defaultAsyncStringBufferSize); + return _async!().LineInputRange!Char(tid, transmitBuffers, + Conn.defaultAsyncStringBufferSize); } } @@ -1727,41 +1739,14 @@ auto byLineAsync(Conn = AutoProtocol, Terminator = char, Char = char) } } -// @@@@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 + * properties is to be customized then set the `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 + * If `postData` is non-_null the method will be set to `post` for HTTP * requests. * * The background thread will buffer up to transmitBuffers number of chunks @@ -1770,8 +1755,8 @@ private static struct AsyncChunkInputRange * 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 + * until data becomes available. An exception to this is the `wait(Duration)` + * method on the $(LREF ChunkInputRange). This method will wait at maximum for the specified * duration and return true if data is available. * * Example: @@ -1827,14 +1812,15 @@ if (isCurlConn!(Conn)) } else { + import std.concurrency : OnCrowding, send, setMaxMailboxSize, spawn, thisTid, Tid; // 50 is just an arbitrary number for now setMaxMailboxSize(thisTid, 50, OnCrowding.block); - auto tid = spawn(&_spawnAsync!(Conn, ubyte)); + auto tid = spawn(&_async!().spawn!(Conn, ubyte)); tid.send(thisTid); - _asyncDuplicateConnection(url, conn, postData, tid); + _async!().duplicateConnection(url, conn, postData, tid); - return AsyncChunkInputRange(tid, transmitBuffers, chunkSize); + return _async!().ChunkInputRange(tid, transmitBuffers, chunkSize); } } @@ -1876,51 +1862,6 @@ if (isCurlConn!(Conn)) } -/* 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 @@ -1930,8 +1871,11 @@ private void _asyncDuplicateConnection(Conn, PostData) */ private mixin template Protocol() { + import etc.c.curl : CurlReadFunc, RawCurlProxy = CurlProxy; + import core.time : Duration; + import std.socket : InternetAddress; - /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + /// Value to return from `onSend`/`onReceive` delegates in order to /// pause a request alias requestPause = CurlReadFunc.pause; @@ -2012,7 +1956,7 @@ private mixin template Protocol() } /// Type of proxy - alias CurlProxy = etc.c.curl.CurlProxy; + alias CurlProxy = RawCurlProxy; /** Proxy type * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) @@ -2155,7 +2099,7 @@ private mixin template Protocol() http.setAuthentication("user", "pass"); http.perform(); - // Bugzilla 17540 + // https://issues.dlang.org/show_bug.cgi?id=17540 http.setNoProxy("www.example.com"); } @@ -2180,15 +2124,15 @@ private mixin template Protocol() /** * 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 + * length of the `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 + * The special value `.abortRequest` can be returned in order to abort the * current request. - * The special value $(D .pauseRequest) can be returned in order to pause the + * The special value `.pauseRequest` can be returned in order to pause the * current request. * * Example: @@ -2259,10 +2203,11 @@ private mixin template Protocol() * ---- * 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) + * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t uln) * { * writeln("Progress: downloaded ", dln, " of ", dl); * writeln("Progress: uploaded ", uln, " of ", ul); + * return 0; * }; * client.perform(); * ---- @@ -2275,8 +2220,8 @@ private mixin template Protocol() } /* - Decode $(D ubyte[]) array using the provided EncodingScheme up to maxChars - Returns: Tuple of ubytes read and the $(D Char[]) characters decoded. + Decode `ubyte[]` array using the provided EncodingScheme up to maxChars + Returns: Tuple of ubytes read and the `Char[]` characters decoded. Not all ubytes are guaranteed to be read in case of decoding error. */ private Tuple!(size_t,Char[]) @@ -2284,6 +2229,7 @@ decodeString(Char = char)(const(ubyte)[] data, EncodingScheme scheme, size_t maxChars = size_t.max) { + import std.encoding : INVALID_SEQUENCE; Char[] res; immutable startLen = data.length; size_t charsDecoded = 0; @@ -2301,7 +2247,7 @@ decodeString(Char = char)(const(ubyte)[] data, } /* - Decode $(D ubyte[]) array using the provided $(D EncodingScheme) until a the + Decode `ubyte[]` array using the provided `EncodingScheme` until a the line terminator specified is found. The basesrc parameter is effectively prepended to src as the first thing. @@ -2324,6 +2270,8 @@ private bool decodeLineInto(Terminator, Char = char)(ref const(ubyte)[] basesrc, Terminator terminator) { import std.algorithm.searching : endsWith; + import std.encoding : INVALID_SEQUENCE; + import std.exception : enforce; // if there is anything in the basesrc then try to decode that // first. @@ -2374,17 +2322,28 @@ private bool decodeLineInto(Terminator, Char = char)(ref const(ubyte)[] basesrc, * HTTP client functionality. * * Example: + * + * Get with custom data receivers: + * * --- * import std.net.curl, std.stdio; * - * // Get with custom data receivers - * auto http = HTTP("dlang.org"); + * auto http = HTTP("https://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 + */ + +/** + * Put with data senders: + * + * --- + * import std.net.curl, std.stdio; + * + * auto http = HTTP("https://dlang.org"); * auto msg = "Hello world"; * http.contentLength = msg.length; * http.onSend = (void[] data) @@ -2397,10 +2356,19 @@ private bool decodeLineInto(Terminator, Char = char)(ref const(ubyte)[] basesrc, * return len; * }; * http.perform(); + * --- * - * // Track progress + */ + +/** + * Tracking progress: + * + * --- + * import std.net.curl, std.stdio; + * + * auto http = HTTP(); * http.method = HTTP.Method.get; - * http.url = "http://upload.wikimedia.org/wikipedia/commons/" + * 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, @@ -2412,7 +2380,7 @@ private bool decodeLineInto(Terminator, Char = char)(ref const(ubyte)[] basesrc, * http.perform(); * --- * - * See_Also: $(_HTTP www.ietf.org/rfc/rfc2616.txt, RFC2616) + * See_Also: $(LINK2 http://www.ietf.org/rfc/rfc2616.txt, RFC2616) * */ struct HTTP @@ -2420,6 +2388,8 @@ struct HTTP mixin Protocol; import std.datetime.systime : SysTime; + import std.typecons : RefCounted; + import etc.c.curl : CurlAuth, CurlInfo, curl_slist, CURLVERSION_NOW, curl_off_t; /// Authentication method equal to $(REF CurlAuth, etc,c,curl) alias AuthMethod = CurlAuth; @@ -2505,9 +2475,10 @@ struct HTTP } private RefCounted!Impl p; + import etc.c.curl : CurlTimeCond; /// Parse status line, as received from / generated by cURL. - private static bool parseStatusLine(in char[] header, out StatusLine status) @safe + private static bool parseStatusLine(const char[] header, out StatusLine status) @safe { import std.conv : to; import std.regex : regex, match; @@ -2600,7 +2571,7 @@ struct HTTP 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 + `perform()` method will start performing the request towards the specified server. Params: @@ -2676,7 +2647,9 @@ struct HTTP // docs mixed in. version (StdDdoc) { - /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + static import etc.c.curl; + + /// Value to return from `onSend`/`onReceive` delegates in order to /// pause a request alias requestPause = CurlReadFunc.pause; @@ -2801,15 +2774,15 @@ struct HTTP /** * 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 + * length of the `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 + * The special value `.abortRequest` can be returned in order to abort the * current request. - * The special value $(D .pauseRequest) can be returned in order to pause the + * The special value `.pauseRequest` can be returned in order to pause the * current request. * * Example: @@ -2870,10 +2843,11 @@ struct HTTP * ---- * 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) + * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t uln) * { * writeln("Progress: downloaded ", dln, " of ", dl); * writeln("Progress: uploaded ", uln, " of ", ul); + * return 0; * }; * client.perform(); * ---- @@ -2908,6 +2882,7 @@ struct HTTP void addRequestHeader(const(char)[] name, const(char)[] value) { import std.format : format; + import std.internal.cstring : tempCString; import std.uni : icmp; if (icmp(name, "User-Agent") == 0) @@ -2947,7 +2922,7 @@ struct HTTP /** 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 + * defaultUserAgent) even if `setUserAgent` was never called. Pass * an empty string to suppress the "User-Agent" field altogether. */ void setUserAgent(const(char)[] userAgent) @@ -2957,23 +2932,23 @@ struct HTTP /** * 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). + * The value is usable only if the return value is equal to `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). + * `etc.c.curl.CurlInfo.namelookup_time`, + * `etc.c.curl.CurlInfo.connect_time`, + * `etc.c.curl.CurlInfo.pretransfer_time`, + * `etc.c.curl.CurlInfo.starttransfer_time`, + * `etc.c.curl.CurlInfo.redirect_time`, + * `etc.c.curl.CurlInfo.appconnect_time`, + * `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). + * should be used only if the return value is `etc.c.curl.CurlInfo.ok`. * * Example: * --- @@ -2986,7 +2961,7 @@ struct HTTP * double val; * CurlCode code; * - * code = http.getTiming(CurlInfo.namelookup_time, val); + * code = client.getTiming(CurlInfo.namelookup_time, val); * assert(code == CurlError.ok); * --- */ @@ -3060,7 +3035,7 @@ struct HTTP Set time condition on the request. Params: - cond = $(D CurlTimeCond.{none,ifmodsince,ifunmodsince,lastmod}) + cond = `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) @@ -3166,7 +3141,7 @@ struct HTTP * 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. + * `const(char)[]` arrays are not valid after the delegate has returned. * * Example: * ---- @@ -3187,7 +3162,7 @@ struct HTTP Callback for each received StatusLine. Notice that several callbacks can be done for each call to - $(D perform()) due to redirections. + `perform()` due to redirections. See_Also: $(LREF StatusLine) */ @@ -3310,11 +3285,11 @@ struct HTTP @system unittest // charset/Charset/CHARSET/... { - import std.meta : AliasSeq; + import etc.c.curl; - foreach (c; AliasSeq!("charset", "Charset", "CHARSET", "CharSet", "charSet", - "ChArSeT", "cHaRsEt")) - { + static foreach (c; ["charset", "Charset", "CHARSET", "CharSet", "charSet", + "ChArSeT", "cHaRsEt"]) + {{ testServer.handle((s) { s.send("HTTP/1.1 200 OK\r\n"~ "Content-Length: 0\r\n"~ @@ -3326,7 +3301,7 @@ struct HTTP http.perform(); assert(http.p.charset == "foo"); - // Bugzilla 16736 + // https://issues.dlang.org/show_bug.cgi?id=16736 double val; CurlCode code; @@ -3344,7 +3319,7 @@ struct HTTP assert(code == CurlError.ok); code = http.getTiming(CurlInfo.appconnect_time, val); assert(code == CurlError.ok); - } + }} } /** @@ -3357,6 +3332,9 @@ struct FTP mixin Protocol; + import std.typecons : RefCounted; + import etc.c.curl : CurlError, CurlInfo, curl_off_t, curl_slist; + private struct Impl { ~this() @@ -3449,7 +3427,9 @@ struct FTP // docs mixed in. version (StdDdoc) { - /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + static import etc.c.curl; + + /// Value to return from `onSend`/`onReceive` delegates in order to /// pause a request alias requestPause = CurlReadFunc.pause; @@ -3574,15 +3554,15 @@ struct FTP /** * 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 + * length of the `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 + * The special value `.abortRequest` can be returned in order to abort the * current request. - * The special value $(D .pauseRequest) can be returned in order to pause the + * The special value `.pauseRequest` can be returned in order to pause the * current request. * */ @@ -3642,6 +3622,7 @@ struct FTP */ void addCommand(const(char)[] command) { + import std.internal.cstring : tempCString; p.commands = Curl.curl.slist_append(p.commands, command.tempCString().buffPtr); p.curl.set(CurlOption.postquote, p.commands); @@ -3670,23 +3651,23 @@ struct FTP /** * 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). + * The value is usable only if the return value is equal to `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). + * `etc.c.curl.CurlInfo.namelookup_time`, + * `etc.c.curl.CurlInfo.connect_time`, + * `etc.c.curl.CurlInfo.pretransfer_time`, + * `etc.c.curl.CurlInfo.starttransfer_time`, + * `etc.c.curl.CurlInfo.redirect_time`, + * `etc.c.curl.CurlInfo.appconnect_time`, + * `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). + * should be used only if the return value is `etc.c.curl.CurlInfo.ok`. * * Example: * --- @@ -3701,7 +3682,7 @@ struct FTP * double val; * CurlCode code; * - * code = http.getTiming(CurlInfo.namelookup_time, val); + * code = client.getTiming(CurlInfo.namelookup_time, val); * assert(code == CurlError.ok); * --- */ @@ -3755,6 +3736,8 @@ struct FTP struct SMTP { mixin Protocol; + import std.typecons : RefCounted; + import etc.c.curl : CurlUseSSL, curl_slist; private struct Impl { @@ -3840,6 +3823,7 @@ struct SMTP @property void url(const(char)[] url) { import std.algorithm.searching : startsWith; + import std.exception : enforce; import std.uni : toLower; auto lowered = url.toLower(); @@ -3869,7 +3853,9 @@ struct SMTP // docs mixed in. version (StdDdoc) { - /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + static import etc.c.curl; + + /// Value to return from `onSend`/`onReceive` delegates in order to /// pause a request alias requestPause = CurlReadFunc.pause; @@ -3994,15 +3980,15 @@ struct SMTP /** * 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 + * length of the `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 + * The special value `.abortRequest` can be returned in order to abort the * current request. - * The special value $(D .pauseRequest) can be returned in order to pause the + * The special value `.pauseRequest` can be returned in order to pause the * current request. */ @property void onSend(size_t delegate(void[]) callback); @@ -4048,6 +4034,7 @@ struct SMTP */ void mailTo()(const(char)[][] recipients...) { + import std.internal.cstring : tempCString; assert(!recipients.empty, "Recipient must not be empty"); curl_slist* recipients_list = null; foreach (recipient; recipients) @@ -4069,6 +4056,20 @@ struct SMTP } } +@system unittest +{ + 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(); +} + + /++ Exception thrown on errors in std.net.curl functions. +/ @@ -4143,14 +4144,16 @@ class HTTPStatusException : CurlException /// 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 { + import etc.c.curl : CurlGlobal; static struct API { + import etc.c.curl : curl_version_info, curl_version_info_data, + CURL, CURLcode, CURLINFO, CURLoption, CURLversion, curl_slist; extern(C): import core.stdc.config : c_long; CURLcode function(c_long flags) global_init; @@ -4179,6 +4182,8 @@ private struct CurlAPI static void* loadAPI() { + import std.exception : enforce; + version (Posix) { import core.sys.posix.dlfcn : dlsym, dlopen, dlclose, RTLD_LAZY; @@ -4186,7 +4191,7 @@ private struct CurlAPI } else version (Windows) { - import core.sys.windows.windows : GetProcAddress, GetModuleHandleA, + import core.sys.windows.winbase : GetProcAddress, GetModuleHandleA, LoadLibraryA; alias loadSym = GetProcAddress; } @@ -4207,7 +4212,12 @@ private struct CurlAPI version (Posix) dlclose(handle); - version (OSX) + version (LibcurlPath) + { + import std.string : strip; + static immutable names = [strip(import("LibcurlPathFile"))]; + } + else version (OSX) static immutable names = ["libcurl.4.dylib"]; else version (Posix) { @@ -4251,7 +4261,7 @@ private struct CurlAPI } else version (Windows) { - import core.sys.windows.windows : FreeLibrary; + import core.sys.windows.winbase : FreeLibrary; FreeLibrary(_handle); } else @@ -4269,7 +4279,7 @@ private struct CurlAPI /** 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 + It is recommended to use the `HTTP`/`FTP` etc. structs instead unless raw access to libcurl is needed. Warning: This struct uses interior pointers for callbacks. Only allocate it @@ -4279,6 +4289,11 @@ private struct CurlAPI */ struct Curl { + import etc.c.curl : CURL, CurlError, CurlPause, CurlSeek, CurlSeekPos, + curl_socket_t, CurlSockType, + CurlReadFunc, CurlInfo, curlsocktype, curl_off_t, + LIBCURL_VERSION_MAJOR, LIBCURL_VERSION_MINOR, LIBCURL_VERSION_PATCH; + alias OutData = void[]; alias InData = ubyte[]; private bool _stopped; @@ -4288,7 +4303,7 @@ struct Curl // 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) + // May also return `CURL_READFUNC_ABORT` or `CURL_READFUNC_PAUSE` private size_t delegate(OutData) _onSend; private size_t delegate(InData) _onReceive; private void delegate(in char[]) _onReceiveHeader; @@ -4305,6 +4320,7 @@ struct Curl */ void initialize() { + import std.exception : enforce; enforce!CurlException(!handle, "Curl instance already initialized"); handle = curl.easy_init(); enforce!CurlException(handle, "Curl instance couldn't be initialized"); @@ -4328,6 +4344,7 @@ struct Curl */ Curl dup() { + import std.meta : AliasSeq; Curl copy; copy.handle = curl.easy_duphandle(handle); copy._stopped = false; @@ -4378,6 +4395,7 @@ struct Curl private void _check(CurlCode code) { + import std.exception : enforce; enforce!CurlTimeoutException(code != CurlError.operation_timedout, errorString(code)); @@ -4397,6 +4415,7 @@ struct Curl private void throwOnStopped(string message = null) { + import std.exception : enforce; auto def = "Curl instance called after being cleaned up"; enforce!CurlException(!stopped, message == null ? def : message); @@ -4404,7 +4423,7 @@ struct Curl /** Stop and invalidate this curl instance. - Warning: Do not call this from inside a callback handler e.g. $(D onReceive). + Warning: Do not call this from inside a callback handler e.g. `onReceive`. */ void shutdown() { @@ -4433,6 +4452,7 @@ struct Curl */ void set(CurlOption option, const(char)[] value) { + import std.internal.cstring : tempCString; throwOnStopped(); _check(curl.easy_setopt(this.handle, option, value.tempCString().buffPtr)); } @@ -4506,7 +4526,7 @@ struct Curl 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). + `etc.c.curl.CurlError.ok`. */ CurlCode getTiming(CurlInfo timing, ref double val) { @@ -4519,7 +4539,7 @@ struct Curl * The event handler that receives incoming data. * * Params: - * callback = the callback that receives the $(D ubyte[]) data. + * callback = the callback that receives the `ubyte[]` data. * Be sure to copy the incoming data and not store * a slice. * @@ -4587,14 +4607,14 @@ struct Curl * 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 + * callback = the callback that has a `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 + * The special value `Curl.abortRequest` can be returned in * order to abort the current request. - * The special value $(D Curl.pauseRequest) can be returned in order to + * The special value `Curl.pauseRequest` can be returned in order to * pause the current request. * * Example: @@ -4666,7 +4686,7 @@ struct Curl /** * 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 + * but a `connect()` call has not yet been done. This makes it possible to set * misc. socket options. * * Params: @@ -4715,16 +4735,17 @@ struct Curl * * Example: * ---- - * import std.net.curl; + * import std.net.curl, std.stdio; * 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) + * curl.onProgress = delegate int(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) * { * writeln("Progress: downloaded bytes ", dlnow, " of ", dltotal); * writeln("Progress: uploaded bytes ", ulnow, " of ", ultotal); - * curl.perform(); + * return 0; * }; + * curl.perform(); * ---- */ @property void onProgress(int delegate(size_t dlTotal, @@ -4865,6 +4886,7 @@ private struct Pool(Data) @safe Data pop() { + import std.exception : enforce; enforce!Exception(root != null, "pop() called on empty pool"); auto d = root.data; auto n = root.next; @@ -4875,276 +4897,397 @@ private struct Pool(Data) } } -// 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) +// Lazily-instantiated namespace to avoid importing std.concurrency until needed. +private struct _async() { - immutable datalen = data.length; - - // Copy data to fill active buffer - while (!data.empty) +static: + // https://issues.dlang.org/show_bug.cgi?id=15831 + // this should be inside byLineAsync + // Range that reads one chunk at a time asynchronously. + private struct ChunkInputRange { + import std.concurrency : Tid, send; - // 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[]; - } + private ubyte[] chunk; + mixin WorkerThreadProtocol!(ubyte, chunk); - // Copy data - auto copyBytes = outdata.length < data.length ? - outdata.length : data.length; + private Tid workerTid; + private State running; - outdata[0 .. copyBytes] = data[0 .. copyBytes]; - outdata = outdata[copyBytes..$]; - data = data[copyBytes..$]; + private this(Tid tid, size_t transmitBuffers, size_t chunkSize) + { + workerTid = tid; + state = State.needUnits; - if (outdata.empty) - fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer)); + // 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); + } + } } - return datalen; -} - -// ditto -private static void _finalizeAsyncChunks(ubyte[] outdata, ref ubyte[] buffer, - Tid fromTid) -{ - if (!outdata.empty) + // https://issues.dlang.org/show_bug.cgi?id=15831 + // this should be inside byLineAsync + // Range that reads one line at a time asynchronously. + private static struct LineInputRange(Char) { - // Resize the last buffer - buffer.length = buffer.length - outdata.length; - fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer)); - } -} + private Char[] line; + mixin WorkerThreadProtocol!(Char, line); + private Tid workerTid; + private State running; -// 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; + private this(Tid tid, size_t transmitBuffers, size_t bufferSize) + { + import std.concurrency : send; - immutable datalen = data.length; + workerTid = tid; + state = State.needUnits; - // Terminator is specified and buffers should be resized as determined by - // the terminator + // 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); + } + } + } - // Copy data to active buffer until terminator is found. + import std.concurrency : Tid; - // Decode as many lines as possible - while (true) + // Shared function for reading incoming chunks of data and + // sending the to a parent thread + private size_t receiveChunks(ubyte[] data, ref ubyte[] outdata, + Pool!(ubyte[]) freeBuffers, + ref ubyte[] buffer, Tid fromTid, + ref bool aborted) { + import std.concurrency : receive, send, thisTid; + + immutable datalen = data.length; - // Make sure a buffer is present - while (!bufferValid && freeBuffers.empty) + // Copy data to fill active buffer + while (!data.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; + + // 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)); } - if (!bufferValid) + + return datalen; + } + + // ditto + private void finalizeChunks(ubyte[] outdata, ref ubyte[] buffer, + Tid fromTid) + { + import std.concurrency : send, thisTid; + if (!outdata.empty) { - buffer = freeBuffers.pop(); - bufferValid = true; + // Resize the last buffer + buffer.length = buffer.length - outdata.length; + fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer)); } + } - // Try to read a line from left over bytes from last onReceive plus the - // newly received bytes. - try + + // Shared function for reading incoming lines of data and sending the to a + // parent thread + private static size_t receiveLines(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.concurrency : prioritySend, receive, send, thisTid; + import std.exception : enforce; + import std.format : format; + import std.traits : isArray; + + 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) { - if (decodeLineInto(leftOverBytes, data, buffer, - encodingScheme, terminator)) + + // Make sure a buffer is present + while (!bufferValid && freeBuffers.empty) { - if (keepTerminator) + // 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)) { - fromTid.send(thisTid, - curlMessage(cast(immutable(Unit)[])buffer)); + 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 { - 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])); + // 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; } - bufferValid = false; } - else + catch (CurlException ex) { - // 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; + prioritySend(fromTid, cast(immutable(CurlException))ex); + return cast(size_t) 0; } } - catch (CurlException ex) - { - prioritySend(fromTid, cast(immutable(CurlException))ex); - return cast(size_t) 0; - } + return datalen; } - 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; + // ditto + private static + void finalizeLines(Unit)(bool bufferValid, Unit[] buffer, Tid fromTid) + { + import std.concurrency : send, thisTid; + if (bufferValid && buffer.length != 0) + fromTid.send(thisTid, curlMessage(cast(immutable(Unit)[])buffer[0..$])); + } - EncodingScheme encodingScheme; - static if ( !is(Terminator == void)) + /* Used by byLineAsync/byChunkAsync to duplicate an existing connection + * that can be used exclusively in a spawned thread. + */ + private void duplicateConnection(Conn, PostData) + (const(char)[] url, Conn conn, PostData postData, Tid tid) { - // Only lines reading will receive a terminator - const terminator = receiveOnly!Terminator(); - const keepTerminator = receiveOnly!bool(); + import std.concurrency : send; + import std.exception : enforce; + + // no move semantic available in std.concurrency ie. must use casting. + auto connDup = conn.dup(); + connDup.url = url; - // 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; + 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 } - else + + // 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 spawn(Conn, Unit, Terminator = void)() { - Unit[] outdata; - } + import std.concurrency : Tid, prioritySend, receiveOnly, send, thisTid; + import etc.c.curl : CURL, CurlError; + Tid fromTid = receiveOnly!Tid(); - // 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; + // Get buffer to read into + Pool!(Unit[]) freeBuffers; // Free list of buffer objects - // receive a method for both ftp and http but just use it for http - auto method = receiveOnly!(HTTP.Method)(); + // Number of bytes filled into active buffer + Unit[] buffer; + bool aborted = false; - 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); + 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 - return _receiveAsyncLines(data, encodingScheme, - keepTerminator, terminator, leftOverBytes, - bufferValid, freeBuffers, buffer, - fromTid, aborted); - }; + { + Unit[] outdata; + } - static if ( is(Conn == HTTP) ) - { - client.method = method; - // register dummy header handler - client.onReceiveHeader = (in char[] key, in char[] value) + // 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 (key == "content-type") - encodingScheme = EncodingScheme.create(client.p.charset); + // If no terminator is specified the chunk size is fixed. + static if ( is(Terminator == void) ) + return receiveChunks(data, outdata, freeBuffers, buffer, + fromTid, aborted); + else + return receiveLines(data, encodingScheme, + keepTerminator, terminator, leftOverBytes, + bufferValid, freeBuffers, buffer, + fromTid, aborted); }; - } - 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; - } + 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); + } - if (code != CurlError.ok) - { - if (aborted && (code == CurlError.aborted_by_callback || - code == CurlError.write_error)) + // 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; } - prioritySend(fromTid, cast(immutable(CurlException)) - new CurlException(client.p.curl.errorString(code))); - 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))); - // 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 + return; + } - fromTid.send(thisTid, curlMessage(true)); // signal done + // Send remaining data that is not a full chunk size + static if ( is(Terminator == void) ) + finalizeChunks(outdata, buffer, fromTid); + else + finalizeLines(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 index cd6aa41..f2a8ff3 100644 --- a/libphobos/src/std/net/isemail.d +++ b/libphobos/src/std/net/isemail.d @@ -5,6 +5,7 @@ * 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) + * Dominic Sayers graciously granted permission to use the Boost license via email on Feb 22, 2011. * Version: 3.0.13 - Version 3.0 of the original PHP implementation: $(LINK http://www.dominicsayers.com/isemail) * * Standards: @@ -20,13 +21,11 @@ * $(LI $(LINK http://tools.ietf.org/html/rfc5322)) * ) * - * Source: $(PHOBOSSRC std/net/_isemail.d) + * Source: $(PHOBOSSRC std/net/isemail.d) */ module std.net.isemail; -// FIXME -import std.range.primitives; // : ElementType; -import std.regex; +import std.range.primitives : back, front, ElementType, popFront, popBack; import std.traits; import std.typecons : Flag, Yes, No; @@ -41,7 +40,7 @@ import std.typecons : Flag, Yes, No; * * Params: * email = The email address to check - * checkDNS = If $(D Yes.checkDns) then a DNS check for MX records will be made + * checkDNS = If `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. @@ -73,10 +72,6 @@ if (isSomeChar!(Char)) 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; @@ -398,12 +393,11 @@ if (isSomeChar!(Char)) auto maxGroups = 8; size_t index = -1; auto addressLiteral = parseData[EmailPart.componentLiteral]; - auto matchesIp = addressLiteral.matchAll(ipRegex).map!(a => a.hit).array; + const(Char)[] ipSuffix = matchIPSuffix(addressLiteral); - if (!matchesIp.empty) + if (ipSuffix.length) { - index = addressLiteral.lastIndexOf(matchesIp.front); - + index = addressLiteral.length - ipSuffix.length; if (index != 0) addressLiteral = addressLiteral[0 .. index] ~ "0:0"; } @@ -417,7 +411,7 @@ if (isSomeChar!(Char)) else { auto ipV6 = addressLiteral[5 .. $]; - matchesIp = ipV6.split(Token.colon); + auto matchesIp = ipV6.split(Token.colon); immutable groupCount = matchesIp.length; index = ipV6.indexOf(Token.doubleColon); @@ -452,7 +446,7 @@ if (isSomeChar!(Char)) returnStatus ~= EmailStatusCode.rfc5322IpV6ColonEnd; else if (!matchesIp - .filter!(a => a.matchFirst(fourChars).empty) + .filter!(a => !isUpToFourHexChars(a)) .empty) returnStatus ~= EmailStatusCode.rfc5322IpV6BadChar; @@ -1273,9 +1267,9 @@ if (isSomeChar!(Char)) /** * 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. + * If set to `CheckDns.no`, isEmail does not perform DNS checking. * - * Otherwise if set to $(D CheckDns.yes), isEmail performs DNS checking. + * Otherwise if set to `CheckDns.yes`, isEmail performs DNS checking. */ alias CheckDns = Flag!"checkDns"; @@ -1309,37 +1303,37 @@ struct EmailStatus } /// Returns: If the email address is valid or not. - @property bool valid() const @safe @nogc pure nothrow + @property bool valid() const @safe @nogc pure nothrow scope { 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 + @property string localPart() const @safe @nogc pure nothrow return scope { 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 + @property string domainPart() const @safe @nogc pure nothrow return scope { return domainPart_; } /// Returns: The email status code - @property EmailStatusCode statusCode() const @safe @nogc pure nothrow + @property EmailStatusCode statusCode() const @safe @nogc pure nothrow scope { return statusCode_; } /// Returns: A describing string of the status code - @property string status() const @safe @nogc pure nothrow + @property string status() const @safe @nogc pure nothrow scope { return statusCodeDescription(statusCode_); } /// Returns: A textual representation of the email status - string toString() const @safe pure + string toString() const @safe pure scope { import std.format : format; return format("EmailStatus\n{\n\tvalid: %s\n\tlocalPart: %s\n\tdomainPart: %s\n\tstatusCode: %s\n}", valid, @@ -1789,7 +1783,7 @@ enum AsciiToken * ) */ 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)) +if (is(immutable ElementType!(S1) == immutable dchar) && is(immutable ElementType!(S2) == immutable dchar)) { import std.uni : icmp; auto s1End = length <= s1.length ? length : s1.length; @@ -1862,3 +1856,96 @@ const(T)[] get (T) (const(T)[] str, size_t index, dchar c) assert("abc".get(1, 'b') == "b"); assert("löv".get(1, 'ö') == "ö"); } + +/+ +Replacement for: +--- +static fourChars = ctRegex!(`^[0-9A-Fa-f]{0,4}$`.to!(const(Char)[])); +... +a => a.matchFirst(fourChars).empty +--- ++/ +bool isUpToFourHexChars(Char)(scope const(Char)[] s) +{ + import std.ascii : isHexDigit; + if (s.length > 4) return false; + foreach (c; s) + if (!isHexDigit(c)) return false; + return true; +} + +@nogc nothrow pure @safe unittest +{ + assert(!isUpToFourHexChars("12345")); + assert(!isUpToFourHexChars("defg")); + assert(isUpToFourHexChars("1A0a")); +} + +/+ +Replacement for: +--- +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)[])); +... +auto matchesIp = addressLiteral.matchAll(ipRegex).map!(a => a.hit).array; +---- +Note that only the first item of "matchAll" was ever used in practice +so we can return `const(Char)[]` instead of `const(Char)[][]` using a +zero-length string to indicate no match. ++/ +const(Char)[] matchIPSuffix(Char)(return const(Char)[] s) @nogc nothrow pure @safe +{ + size_t end = s.length; + if (end < 7) return null; + // Check the first three `[.]\d{1,3}` + foreach (_; 0 .. 3) + { + size_t start = void; + if (end >= 2 && s[end-2] == '.') + start = end - 2; + else if (end >= 3 && s[end-3] == '.') + start = end - 3; + else if (end >= 4 && s[end-4] == '.') + start = end - 4; + else + return null; + uint x = 0; + foreach (i; start + 1 .. end) + { + uint c = cast(uint) s[i] - '0'; + if (c > 9) return null; + x = x * 10 + c; + } + if (x > 255) return null; + end = start; + } + // Check the final `\d{1,3}`. + if (end < 1) return null; + size_t start = end - 1; + uint x = cast(uint) s[start] - '0'; + if (x > 9) return null; + if (start > 0 && cast(uint) s[start-1] - '0' <= 9) + { + --start; + x += 10 * (cast(uint) s[start] - '0'); + if (start > 0 && cast(uint) s[start-1] - '0' <= 9) + { + --start; + x += 100 * (cast(uint) s[start] - '0'); + } + } + if (x > 255) return null; + // Must either be at start of string or preceded by a non-word character. + // (TO DETERMINE: is the definition of "word character" ASCII only?) + if (start == 0) return s; + const b = s[start - 1]; + import std.ascii : isAlphaNum; + if (isAlphaNum(b) || b == '_') return null; + return s[start .. $]; +} + +@nogc nothrow pure @safe unittest +{ + assert(matchIPSuffix("255.255.255.255") == "255.255.255.255"); + assert(matchIPSuffix("babaev 176.16.0.1") == "176.16.0.1"); +} diff --git a/libphobos/src/std/numeric.d b/libphobos/src/std/numeric.d index 307406e..fd532b2 100644 --- a/libphobos/src/std/numeric.d +++ b/libphobos/src/std/numeric.d @@ -2,7 +2,7 @@ /** 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, +header in Alexander Stepanov's $(LINK2 https://en.wikipedia.org/wiki/Standard_Template_Library, Standard Template Library), with a few additions. Macros: @@ -10,7 +10,7 @@ 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) +Source: $(PHOBOSSRC std/numeric.d) */ /* Copyright Andrei Alexandrescu 2008 - 2009. @@ -22,14 +22,11 @@ module std.numeric; import std.complex; import std.math; +import core.math : fabs, ldexp, sin, sqrt; import std.range.primitives; import std.traits; import std.typecons; -version (unittest) -{ - import std.stdio; -} /// Format flags for CustomFloat. public enum CustomFloatFlags { @@ -39,7 +36,7 @@ public enum CustomFloatFlags /** * 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). + * instead of 0. i.e. `1.nnnn` instead of `0.nnnn`. * True for all $(LINK2 https://en.wikipedia.org/wiki/IEEE_floating_point, IEE754) types */ storeNormalized = 2, @@ -109,7 +106,7 @@ private template CustomFloatParams(uint precision, uint exponentWidth, CustomFlo /** * 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 + * extracting them to `real` first. After the operation is completed the * result can be stored in a custom floating-point value via assignment. */ template CustomFloat(uint bits) @@ -128,7 +125,7 @@ if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && precision + /// @safe unittest { - import std.math : sin, cos; + import std.math.trigonometry : sin, cos; // Define a 16-bit floating point values CustomFloat!16 x; // Using the number of bits @@ -149,13 +146,33 @@ if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && precision + auto p = Probability(0.5); } +// Facilitate converting numeric types to custom float +private 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; +} + /// 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) +if (isCorrectCustomFloat(precision, exponentWidth, flags)) { import std.bitmanip : bitfields; import std.meta : staticIndexOf; @@ -180,31 +197,15 @@ private: 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)) + if (shift >= T.sizeof*8) + { + // avoid illegal shift + sig = 0; + } + else if (sig << (T.sizeof*8 - shift) == cast(T) 1uL << (T.sizeof*8 - 1)) { // round to even sig >>= shift; @@ -386,7 +387,7 @@ public: { CustomFloat value; static if (flags & Flags.signed) - value.sign = 0; + value.sign = 0; value.significand = 0; value.exponent = exponent_max; return value; @@ -398,7 +399,7 @@ public: { CustomFloat value; static if (flags & Flags.signed) - value.sign = 0; + value.sign = 0; value.significand = cast(typeof(significand_max)) 1L << (precision-1); value.exponent = exponent_max; return value; @@ -407,32 +408,18 @@ public: /// 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); + auto shiftcnt = precision - ((flags&Flags.storeNormalized) == 0); + return shiftcnt == 64 ? 19 : cast(size_t) log10(1uL << shiftcnt); } /// 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; + CustomFloat one = CustomFloat(1); + CustomFloat onePlusEpsilon = one; + onePlusEpsilon.significand = onePlusEpsilon.significand | 1; // |= does not work here - 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; + return CustomFloat(onePlusEpsilon - one); } /// the number of bits in mantissa @@ -442,32 +429,33 @@ public: 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); + enum max_exp = exponent_max - bias - ((flags & (Flags.infinity | Flags.nan)) != 0) + 1; /// 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); + enum min_exp = cast(T_signed_exp) -(cast(long) 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.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() { + static @property CustomFloat min_normal() + { CustomFloat value; static if (flags & Flags.signed) - value.sign = 0; - value.exponent = 1; - static if (flags&Flags.storeNormalized) + value.sign = 0; + value.exponent = (flags & Flags.allowDenorm) != 0; + static if (flags & Flags.storeNormalized) value.significand = 0; else value.significand = cast(T_sig) 1uL << (precision - 1); @@ -480,7 +468,7 @@ public: /// Returns: imaginary part static @property CustomFloat im() { return CustomFloat(0.0f); } - /// Initialize from any $(D real) compatible type. + /// Initialize from any `real` compatible type. this(F)(F input) if (__traits(compiles, cast(real) input )) { this = input; @@ -490,18 +478,18 @@ public: void opAssign(F:CustomFloat)(F input) { static if (flags & Flags.signed) - sign = input.sign; + sign = input.sign; exponent = input.exponent; significand = input.significand; } - /// Assigns from any $(D real) compatible type. + /// Assigns from any `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) + static if (staticIndexOf!(immutable F, immutable float, immutable double, immutable real) >= 0) auto value = ToBinary!(Unqual!F)(input); else auto value = ToBinary!(real )(input); @@ -529,9 +517,9 @@ public: significand = cast(T_sig) sig; } - /// Fetches the stored value either as a $(D float), $(D double) or $(D real). + /// Fetches the stored value either as a `float`, `double` or `real`. @property F get(F)() - if (staticIndexOf!(Unqual!F, float, double, real) >= 0) + if (staticIndexOf!(immutable F, immutable float, immutable double, immutable real) >= 0) { import std.conv : text; @@ -555,9 +543,9 @@ public: } ///ditto - T opCast(T)() if (__traits(compiles, get!T )) { return get!T; } + alias opCast = get; - /// Convert the CustomFloat to a real and perform the relavent operator on the result + /// Convert the CustomFloat to a real and perform the relevant operator on the result real opUnary(string op)() if (__traits(compiles, mixin(op~`(get!real)`)) || op=="++" || op=="--") { @@ -572,8 +560,19 @@ public: } /// ditto + // Define an opBinary `CustomFloat op CustomFloat` so that those below + // do not match equally, which is disallowed by the spec: + // https://dlang.org/spec/operatoroverloading.html#binary real opBinary(string op,T)(T b) - if (__traits(compiles, mixin(`get!real`~op~`b`))) + if (__traits(compiles, mixin(`get!real`~op~`b.get!real`))) + { + return mixin(`get!real`~op~`b.get!real`); + } + + /// ditto + real opBinary(string op,T)(T b) + if ( __traits(compiles, mixin(`get!real`~op~`b`)) && + !__traits(compiles, mixin(`get!real`~op~`b.get!real`))) { return mixin(`get!real`~op~`b`); } @@ -581,7 +580,8 @@ public: /// ditto real opBinaryRight(string op,T)(T a) if ( __traits(compiles, mixin(`a`~op~`get!real`)) && - !__traits(compiles, mixin(`get!real`~op~`b`))) + !__traits(compiles, mixin(`get!real`~op~`b`)) && + !__traits(compiles, mixin(`get!real`~op~`b.get!real`))) { return mixin(`a`~op~`get!real`); } @@ -605,9 +605,10 @@ public: /// 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) + import std.format.spec : FormatSpec; + import std.format.write : formatValue; + // Needs to be a template because of https://issues.dlang.org/show_bug.cgi?id=13737. + void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) { sink.formatValue(get!real, fmt); } @@ -621,7 +622,7 @@ public: AliasSeq!( CustomFloat!(5, 10), CustomFloat!(5, 11, CustomFloatFlags.ieee ^ CustomFloatFlags.signed), - CustomFloat!(1, 15, CustomFloatFlags.ieee ^ CustomFloatFlags.signed), + CustomFloat!(1, 7, CustomFloatFlags.ieee ^ CustomFloatFlags.signed), CustomFloat!(4, 3, CustomFloatFlags.ieee | CustomFloatFlags.probability ^ CustomFloatFlags.signed) ); @@ -658,26 +659,320 @@ public: assert(y.to!string == "0.125"); } +@safe unittest +{ + alias cf = CustomFloat!(5, 2); + + auto a = cf.infinity; + assert(a.sign == 0); + assert(a.exponent == 3); + assert(a.significand == 0); + + auto b = cf.nan; + assert(b.exponent == 3); + assert(b.significand != 0); + + assert(cf.dig == 1); + + auto c = cf.epsilon; + assert(c.sign == 0); + assert(c.exponent == 0); + assert(c.significand == 1); + + assert(cf.mant_dig == 6); + + assert(cf.max_10_exp == 0); + assert(cf.max_exp == 2); + assert(cf.min_10_exp == 0); + assert(cf.min_exp == 1); + + auto d = cf.max; + assert(d.sign == 0); + assert(d.exponent == 2); + assert(d.significand == 31); + + auto e = cf.min_normal; + assert(e.sign == 0); + assert(e.exponent == 1); + assert(e.significand == 0); + + assert(e.re == e); + assert(e.im == cf(0.0)); +} + +// check whether CustomFloats identical to float/double behave like float/double +@safe unittest +{ + import std.conv : to; + + alias myFloat = CustomFloat!(23, 8); + + static assert(myFloat.dig == float.dig); + static assert(myFloat.mant_dig == float.mant_dig); + assert(myFloat.max_10_exp == float.max_10_exp); + static assert(myFloat.max_exp == float.max_exp); + assert(myFloat.min_10_exp == float.min_10_exp); + static assert(myFloat.min_exp == float.min_exp); + assert(to!float(myFloat.epsilon) == float.epsilon); + assert(to!float(myFloat.max) == float.max); + assert(to!float(myFloat.min_normal) == float.min_normal); + + alias myDouble = CustomFloat!(52, 11); + + static assert(myDouble.dig == double.dig); + static assert(myDouble.mant_dig == double.mant_dig); + assert(myDouble.max_10_exp == double.max_10_exp); + static assert(myDouble.max_exp == double.max_exp); + assert(myDouble.min_10_exp == double.min_10_exp); + static assert(myDouble.min_exp == double.min_exp); + assert(to!double(myDouble.epsilon) == double.epsilon); + assert(to!double(myDouble.max) == double.max); + assert(to!double(myDouble.min_normal) == double.min_normal); +} + +// testing .dig +@safe unittest +{ + static assert(CustomFloat!(1, 6).dig == 0); + static assert(CustomFloat!(9, 6).dig == 2); + static assert(CustomFloat!(10, 5).dig == 3); + static assert(CustomFloat!(10, 6, CustomFloatFlags.none).dig == 2); + static assert(CustomFloat!(11, 5, CustomFloatFlags.none).dig == 3); + static assert(CustomFloat!(64, 7).dig == 19); +} + +// testing .mant_dig +@safe unittest +{ + static assert(CustomFloat!(10, 5).mant_dig == 11); + static assert(CustomFloat!(10, 6, CustomFloatFlags.none).mant_dig == 10); +} + +// testing .max_exp +@safe unittest +{ + static assert(CustomFloat!(1, 6).max_exp == 2^^5); + static assert(CustomFloat!(2, 6, CustomFloatFlags.none).max_exp == 2^^5); + static assert(CustomFloat!(5, 10).max_exp == 2^^9); + static assert(CustomFloat!(6, 10, CustomFloatFlags.none).max_exp == 2^^9); + static assert(CustomFloat!(2, 6, CustomFloatFlags.nan).max_exp == 2^^5); + static assert(CustomFloat!(6, 10, CustomFloatFlags.nan).max_exp == 2^^9); +} + +// testing .min_exp +@safe unittest +{ + static assert(CustomFloat!(1, 6).min_exp == -2^^5+3); + static assert(CustomFloat!(5, 10).min_exp == -2^^9+3); + static assert(CustomFloat!(2, 6, CustomFloatFlags.none).min_exp == -2^^5+1); + static assert(CustomFloat!(6, 10, CustomFloatFlags.none).min_exp == -2^^9+1); + static assert(CustomFloat!(2, 6, CustomFloatFlags.nan).min_exp == -2^^5+2); + static assert(CustomFloat!(6, 10, CustomFloatFlags.nan).min_exp == -2^^9+2); + static assert(CustomFloat!(2, 6, CustomFloatFlags.allowDenorm).min_exp == -2^^5+2); + static assert(CustomFloat!(6, 10, CustomFloatFlags.allowDenorm).min_exp == -2^^9+2); +} + +// testing .max_10_exp +@safe unittest +{ + assert(CustomFloat!(1, 6).max_10_exp == 9); + assert(CustomFloat!(5, 10).max_10_exp == 154); + assert(CustomFloat!(2, 6, CustomFloatFlags.none).max_10_exp == 9); + assert(CustomFloat!(6, 10, CustomFloatFlags.none).max_10_exp == 154); + assert(CustomFloat!(2, 6, CustomFloatFlags.nan).max_10_exp == 9); + assert(CustomFloat!(6, 10, CustomFloatFlags.nan).max_10_exp == 154); +} + +// testing .min_10_exp +@safe unittest +{ + assert(CustomFloat!(1, 6).min_10_exp == -9); + assert(CustomFloat!(5, 10).min_10_exp == -153); + assert(CustomFloat!(2, 6, CustomFloatFlags.none).min_10_exp == -9); + assert(CustomFloat!(6, 10, CustomFloatFlags.none).min_10_exp == -154); + assert(CustomFloat!(2, 6, CustomFloatFlags.nan).min_10_exp == -9); + assert(CustomFloat!(6, 10, CustomFloatFlags.nan).min_10_exp == -153); + assert(CustomFloat!(2, 6, CustomFloatFlags.allowDenorm).min_10_exp == -9); + assert(CustomFloat!(6, 10, CustomFloatFlags.allowDenorm).min_10_exp == -153); +} + +// testing .epsilon +@safe unittest +{ + assert(CustomFloat!(1,6).epsilon.sign == 0); + assert(CustomFloat!(1,6).epsilon.exponent == 30); + assert(CustomFloat!(1,6).epsilon.significand == 0); + assert(CustomFloat!(2,5).epsilon.sign == 0); + assert(CustomFloat!(2,5).epsilon.exponent == 13); + assert(CustomFloat!(2,5).epsilon.significand == 0); + assert(CustomFloat!(3,4).epsilon.sign == 0); + assert(CustomFloat!(3,4).epsilon.exponent == 4); + assert(CustomFloat!(3,4).epsilon.significand == 0); + // the following epsilons are only available, when denormalized numbers are allowed: + assert(CustomFloat!(4,3).epsilon.sign == 0); + assert(CustomFloat!(4,3).epsilon.exponent == 0); + assert(CustomFloat!(4,3).epsilon.significand == 4); + assert(CustomFloat!(5,2).epsilon.sign == 0); + assert(CustomFloat!(5,2).epsilon.exponent == 0); + assert(CustomFloat!(5,2).epsilon.significand == 1); +} + +// testing .max +@safe unittest +{ + static assert(CustomFloat!(5,2).max.sign == 0); + static assert(CustomFloat!(5,2).max.exponent == 2); + static assert(CustomFloat!(5,2).max.significand == 31); + static assert(CustomFloat!(4,3).max.sign == 0); + static assert(CustomFloat!(4,3).max.exponent == 6); + static assert(CustomFloat!(4,3).max.significand == 15); + static assert(CustomFloat!(3,4).max.sign == 0); + static assert(CustomFloat!(3,4).max.exponent == 14); + static assert(CustomFloat!(3,4).max.significand == 7); + static assert(CustomFloat!(2,5).max.sign == 0); + static assert(CustomFloat!(2,5).max.exponent == 30); + static assert(CustomFloat!(2,5).max.significand == 3); + static assert(CustomFloat!(1,6).max.sign == 0); + static assert(CustomFloat!(1,6).max.exponent == 62); + static assert(CustomFloat!(1,6).max.significand == 1); + static assert(CustomFloat!(3,5, CustomFloatFlags.none).max.exponent == 31); + static assert(CustomFloat!(3,5, CustomFloatFlags.none).max.significand == 7); +} + +// testing .min_normal +@safe unittest +{ + static assert(CustomFloat!(5,2).min_normal.sign == 0); + static assert(CustomFloat!(5,2).min_normal.exponent == 1); + static assert(CustomFloat!(5,2).min_normal.significand == 0); + static assert(CustomFloat!(4,3).min_normal.sign == 0); + static assert(CustomFloat!(4,3).min_normal.exponent == 1); + static assert(CustomFloat!(4,3).min_normal.significand == 0); + static assert(CustomFloat!(3,4).min_normal.sign == 0); + static assert(CustomFloat!(3,4).min_normal.exponent == 1); + static assert(CustomFloat!(3,4).min_normal.significand == 0); + static assert(CustomFloat!(2,5).min_normal.sign == 0); + static assert(CustomFloat!(2,5).min_normal.exponent == 1); + static assert(CustomFloat!(2,5).min_normal.significand == 0); + static assert(CustomFloat!(1,6).min_normal.sign == 0); + static assert(CustomFloat!(1,6).min_normal.exponent == 1); + static assert(CustomFloat!(1,6).min_normal.significand == 0); + static assert(CustomFloat!(3,5, CustomFloatFlags.none).min_normal.exponent == 0); + static assert(CustomFloat!(3,5, CustomFloatFlags.none).min_normal.significand == 4); +} + +@safe unittest +{ + import std.math.traits : isNaN; + + alias cf = CustomFloat!(5, 2); + + auto f = cf.nan.get!float(); + assert(isNaN(f)); + + cf a; + a = real.max; + assert(a == cf.infinity); + + a = 0.015625; + assert(a.exponent == 0); + assert(a.significand == 0); + + a = 0.984375; + assert(a.exponent == 1); + assert(a.significand == 0); +} + +@system unittest +{ + import std.exception : assertThrown; + import core.exception : AssertError; + + alias cf = CustomFloat!(3, 5, CustomFloatFlags.none); + + cf a; + assertThrown!AssertError(a = real.max); +} + +@system unittest +{ + import std.exception : assertThrown; + import core.exception : AssertError; + + alias cf = CustomFloat!(3, 5, CustomFloatFlags.nan); + + cf a; + assertThrown!AssertError(a = real.max); +} + +@system unittest +{ + import std.exception : assertThrown; + import core.exception : AssertError; + + alias cf = CustomFloat!(24, 8, CustomFloatFlags.none); + + cf a; + assertThrown!AssertError(a = float.infinity); +} + +private bool isCorrectCustomFloat(uint precision, uint exponentWidth, CustomFloatFlags flags) @safe pure nothrow @nogc +{ + // Restrictions from bitfield + // due to CustomFloat!80 support hack precision with 64 bits is handled specially + auto length = (flags & flags.signed) + exponentWidth + ((precision == 64) ? 0 : precision); + if (length != 8 && length != 16 && length != 32 && length != 64) return false; + + // mantissa needs to fit into real mantissa + if (precision > real.mant_dig - 1 && precision != 64) return false; + + // exponent needs to fit into real exponent + if (1L << exponentWidth - 1 > real.max_exp) return false; + + // mantissa should have at least one bit + if (precision == 0) return false; + + // exponent should have at least one bit, in some cases two + if (exponentWidth <= ((flags & (flags.allowDenorm | flags.infinity | flags.nan)) != 0)) return false; + + return true; +} + +@safe pure nothrow @nogc unittest +{ + assert(isCorrectCustomFloat(3,4,CustomFloatFlags.ieee)); + assert(isCorrectCustomFloat(3,5,CustomFloatFlags.none)); + assert(!isCorrectCustomFloat(3,3,CustomFloatFlags.ieee)); + assert(isCorrectCustomFloat(64,7,CustomFloatFlags.ieee)); + assert(!isCorrectCustomFloat(64,4,CustomFloatFlags.ieee)); + assert(!isCorrectCustomFloat(508,3,CustomFloatFlags.ieee)); + assert(!isCorrectCustomFloat(3,100,CustomFloatFlags.ieee)); + assert(!isCorrectCustomFloat(0,7,CustomFloatFlags.ieee)); + assert(!isCorrectCustomFloat(6,1,CustomFloatFlags.ieee)); + assert(isCorrectCustomFloat(7,1,CustomFloatFlags.none)); + assert(!isCorrectCustomFloat(8,0,CustomFloatFlags.none)); +} + /** 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 +calculation intended to ultimately yield a result of type `F` +(where `F` must be one of `float`, `double`, or $(D real)). When doing a multi-step computation, you may want to store -intermediate results as $(D FPTemporary!F). +intermediate results as `FPTemporary!F`. -The necessity of $(D FPTemporary) stems from the optimized +The necessity of `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 +in fact be done in `real` precision internally. In that case, +storing the intermediate `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 +from `real` to `double` is performed every pass through the +loop. This being a lose-lose situation, `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). +`F`. There is no need to define a type for the $(I most accurate) +calculations, as that is always `real`. -Finally, there is no guarantee that using $(D FPTemporary!F) will +Finally, there is no guarantee that using `FPTemporary!F` will always be fastest, as the speed of floating-point calculations depends on very many factors. */ @@ -693,7 +988,7 @@ if (isFloatingPoint!F) /// @safe unittest { - import std.math : approxEqual; + import std.math.operations : isClose; // Average numbers in an array double avg(in double[] a) @@ -705,14 +1000,14 @@ if (isFloatingPoint!F) } auto a = [1.0, 2.0, 3.0]; - assert(approxEqual(avg(a), 2)); + assert(isClose(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). +root of the function `fun` starting from points $(D [xn_1, x_n]) +(ideally close to the root). `Num` may be `float`, `double`, +or `real`. */ template secantMethod(alias fun) { @@ -723,7 +1018,7 @@ template secantMethod(alias fun) typeof(fxn) fxn_1; xn = xn_1; - while (!approxEqual(d, 0) && isFinite(d)) + while (!isClose(d, 0, 0.0, 1e-5) && isFinite(d)) { xn_1 = xn; xn -= d; @@ -738,29 +1033,31 @@ template secantMethod(alias fun) /// @safe unittest { - import std.math : approxEqual, cos; + import std.math.operations : isClose; + import std.math.trigonometry : cos; float f(float x) { return cos(x) - x*x*x; } auto x = secantMethod!(f)(0f, 1f); - assert(approxEqual(x, 0.865474)); + assert(isClose(x, 0.865474)); } @system unittest { // @system because of __gshared stderr + import std.stdio; 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)); + assert(isClose(x, 0.865474)); auto d = &f; immutable y = secantMethod!(d)(0f, 1f); - assert(approxEqual(y, 0.865474)); + assert(isClose(y, 0.865474)); } @@ -799,7 +1096,7 @@ public: * www.netlib.org,www.netlib.org) as algorithm TOMS478. * */ -T findRoot(T, DF, DT)(scope DF f, in T a, in T b, +T findRoot(T, DF, DT)(scope DF f, const T a, const T b, scope DT tolerance) //= (T a, T b) => false) if ( isFloatingPoint!T && @@ -819,7 +1116,7 @@ if ( } ///ditto -T findRoot(T, DF)(scope DF f, in T a, in T b) +T findRoot(T, DF)(scope DF f, const T a, const T b) { return findRoot(f, a, b, (T a, T b) => false); } @@ -837,16 +1134,16 @@ T findRoot(T, DF)(scope DF f, in T a, in T b) * bx = Right bound of initial range of `f` known to contain the * root. * - * fax = Value of $(D f(ax)). + * fax = Value of `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.) + * fbx = Value of `f(bx)`. `fax` and `fbx` should have opposite signs. + * (`f(ax)` and `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), + * delegate must return `true` when these bounds are + * acceptable. If this function always returns `false`, * full machine precision will be achieved. * * Returns: @@ -857,7 +1154,8 @@ T findRoot(T, DF)(scope DF f, in T a, in T b) * 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, +Tuple!(T, T, R, R) findRoot(T, R, DF, DT)(scope DF f, + const T ax, const T bx, const R fax, const R fbx, scope DT tolerance) // = (T a, T b) => false) if ( isFloatingPoint!T && @@ -869,7 +1167,7 @@ in assert(!ax.isNaN() && !bx.isNaN(), "Limits must not be NaN"); assert(signbit(fax) != signbit(fbx), "Parameters must bracket the root."); } -body +do { // Author: Don Clugston. This code is (heavily) modified from TOMS748 // (www.netlib.org). The changes to improve the worst-cast performance are @@ -1164,13 +1462,14 @@ whileloop: } ///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) +Tuple!(T, T, R, R) findRoot(T, R, DF)(scope DF f, + const T ax, const T bx, const R fax, const 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, +T findRoot(T, R)(scope R delegate(T) f, const T a, const 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); @@ -1186,7 +1485,7 @@ T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, //numCalls=0; //++numProblems; assert(!x1.isNaN() && !x2.isNaN()); - assert(signbit(x1) != signbit(x2)); + assert(signbit(f(x1)) != signbit(f(x2))); auto result = findRoot(f, x1, x2, f(x1), f(x2), (real lo, real hi) { return false; }); @@ -1204,16 +1503,16 @@ T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, //++numCalls; if (x>float.max) x = float.max; - if (x<-double.max) - x = -double.max; + if (x<-float.max) + x = -float.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); + testFindRoot( &multisine, 6, 90); + testFindRoot(&cubicfn, -100, 100); + testFindRoot( &cubicfn, -double.max, real.max); /* Tests from the paper: @@ -1246,7 +1545,7 @@ T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, foreach (k; power_nvals) { n = k; - //testFindRoot(&power, -1, 10); + testFindRoot(&power, -1, 10); } int powerProblems = numProblems; @@ -1307,17 +1606,17 @@ T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, } numProblems=0; - //testFindRoot(&alefeld0, PI_2, PI); + testFindRoot(&alefeld0, PI_2, PI); for (n=1; n <= 10; ++n) { - //testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); + testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); } ale_a = -40; ale_b = -1; - //testFindRoot(&alefeld1, -9, 31); + testFindRoot(&alefeld1, -9, 31); ale_a = -100; ale_b = -2; - //testFindRoot(&alefeld1, -9, 31); + testFindRoot(&alefeld1, -9, 31); ale_a = -200; ale_b = -3; - //testFindRoot(&alefeld1, -9, 31); + 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]; @@ -1325,48 +1624,48 @@ T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, 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); + 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); + testFindRoot(&alefeld3, 0, 1); } foreach (i; nvals_3) { n=i; - //testFindRoot(&alefeld4, 0, 1); + testFindRoot(&alefeld4, 0, 1); } foreach (i; nvals_5) { n=i; - //testFindRoot(&alefeld5, 0, 1); + testFindRoot(&alefeld5, 0, 1); } foreach (i; nvals_6) { n=i; - //testFindRoot(&alefeld6, 0, 1); + testFindRoot(&alefeld6, 0, 1); } foreach (i; nvals_7) { n=i; - //testFindRoot(&alefeld7, 0.01L, 1); + 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); + 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); + findRoot((double x){ return 0.0; }, -double.max, double.max); + findRoot((float x){ return 0.0f; }, -float.max, float.max); /* int grandtotal=0; @@ -1381,7 +1680,7 @@ T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, printf("POWER TOTAL = %d avg = %f ", powercalls, (1.0*powercalls)/powerProblems); */ - //Issue 14231 + // https://issues.dlang.org/show_bug.cgi?id=14231 auto xp = findRoot((float x) => x, 0f, 1f); auto xn = findRoot((float x) => x, -1f, -0f); } @@ -1413,8 +1712,8 @@ Params: 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). + `relTolerance` shall be normal positive real. $(BR) + `absTolerance` shall be normal positive real no less then `T.epsilon*2`. Returns: A tuple consisting of `x`, `y = f(x)` and `error = 3 * (absTolerance * fabs(x) + relTolerance)`. @@ -1431,10 +1730,10 @@ 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), + const T ax, + const T bx, + const T relTolerance = sqrt(T.epsilon), + const T absTolerance = sqrt(T.epsilon), ) if (isFloatingPoint!T && __traits(compiles, {T _ = DF.init(T.init);})) @@ -1451,7 +1750,7 @@ out (result) { assert(isFinite(result.x)); } -body +do { alias R = Unqual!(CommonType!(ReturnType!DF, T)); // c is the squared inverse of the golden ratio @@ -1553,14 +1852,14 @@ body // update a, b, v, w, and x if (fu <= fx) { - u < x ? b : a = x; + (u < x ? b : a) = x; v = w; fv = fw; w = x; fw = fx; x = u; fx = fu; } else { - u < x ? a : b = u; + (u < x ? a : b) = u; if (fu <= fw || w == x) { v = w; fv = fw; @@ -1578,27 +1877,27 @@ body /// @safe unittest { - import std.math : approxEqual; + import std.math.operations : isClose; auto ret = findLocalMin((double x) => (x-4)^^2, -1e7, 1e7); - assert(ret.x.approxEqual(4.0)); - assert(ret.y.approxEqual(0.0)); + assert(ret.x.isClose(4.0)); + assert(ret.y.isClose(0.0, 0.0, 1e-10)); } @safe unittest { import std.meta : AliasSeq; - foreach (T; AliasSeq!(double, float, real)) + static 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))); + assert(ret.x.isClose(T(4))); + assert(ret.y.isClose(T(0), 0.0, T.epsilon)); } { 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(isClose(ret.x, T(1))); + assert(isClose(ret.y, T(0), 0.0, T.epsilon)); assert(ret.error <= 10 * T.epsilon); } { @@ -1620,19 +1919,19 @@ body } { 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))); + assert(ret.x.fabs.isClose(T(1))); + assert(ret.y.fabs.isClose(T(1))); + assert(ret.error.isClose(T(0), 0.0, 100*T.epsilon)); } } } /** 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 +Euclidean distance) between input ranges `a` and +`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 +equal to `limit` (this is useful to save computation if a small distance is sought). */ CommonType!(ElementType!(Range1), ElementType!(Range2)) @@ -1677,20 +1976,21 @@ if (isInputRange!(Range1) && isInputRange!(Range2)) @safe unittest { import std.meta : AliasSeq; - foreach (T; AliasSeq!(double, const double, immutable double)) - { + static 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, 6) == 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 +dot product) of input ranges `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. @@ -1765,30 +2065,55 @@ dotProduct(F1, F2)(in F1[] avector, in F2[] bvector) return sum0; } +/// ditto +F dotProduct(F, uint N)(const ref scope F[N] a, const ref scope F[N] b) +if (N <= 16) +{ + F sum0 = 0; + F sum1 = 0; + static foreach (i; 0 .. N / 2) + { + sum0 += a[i*2] * b[i*2]; + sum1 += a[i*2+1] * b[i*2+1]; + } + static if (N % 2 == 1) + { + sum0 += a[N-1] * b[N-1]; + } + return sum0 + sum1; +} + @system unittest { // @system due to dotProduct and assertCTFEable import std.exception : assertCTFEable; import std.meta : AliasSeq; - foreach (T; AliasSeq!(double, const double, immutable double)) - { + static 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); - } + // Test with fixed-length arrays. + T[2] c = [ 1.0, 2.0, ]; + T[2] d = [ 4.0, 6.0, ]; + assert(dotProduct(c, d) == 16); + T[3] e = [1, 3, -5]; + T[3] f = [4, -2, -1]; + assert(dotProduct(e, f) == 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]; + [1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]; 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); }); + [2.0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]; + assertCTFEable!({ assert(dotProduct(x, y) == 4048); }); } /** Computes the $(LINK2 https://en.wikipedia.org/wiki/Cosine_similarity, -cosine similarity) of input ranges $(D a) and $(D +cosine similarity) of input ranges `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. @@ -1815,25 +2140,25 @@ if (isInputRange!(Range1) && isInputRange!(Range2)) @safe unittest { import std.meta : AliasSeq; - foreach (T; AliasSeq!(double, const double, immutable double)) - { + static foreach (T; AliasSeq!(double, const double, immutable double)) + {{ T[] a = [ 1.0, 2.0, ]; T[] b = [ 4.0, 3.0, ]; - assert(approxEqual( + assert(isClose( 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 +Normalizes values in `range` by multiplying each element with a +number chosen such that values sum up to `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. +all. Normalization makes sense only if all elements in `range` are +positive. `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. +Returns: `true` if normalization completed normally, `false` if +all elements in `range` were zero or if `range` is empty. */ bool normalize(R)(R range, ElementType!(R) sum = 1) if (isForwardRange!(R)) @@ -1883,13 +2208,14 @@ if (isForwardRange!(R)) a = [ 1.0, 3.0 ]; assert(normalize(a)); assert(a == [ 0.25, 0.75 ]); + assert(normalize!(typeof(a))(a, 50)); // a = [12.5, 37.5] 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). +Compute the sum of binary logarithms of the input range `r`. The error of this method is much smaller than with a naive sum of log2. */ ElementType!Range sumOfLog2s(Range)(Range r) @@ -1916,7 +2242,7 @@ if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) /// @safe unittest { - import std.math : isNaN; + import std.math.traits : isNaN; assert(sumOfLog2s(new double[0]) == 0); assert(sumOfLog2s([0.0L]) == -real.infinity); @@ -1932,12 +2258,12 @@ if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) /** 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 +_entropy) of input range `r` in bits. This +function assumes (without checking) that the values in `r` are all +in $(D [0, 1]). For the entropy to be meaningful, often `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). +result is greater than or equal to `max`. */ ElementType!Range entropy(Range)(Range r) if (isInputRange!Range) @@ -1969,25 +2295,25 @@ if (isInputRange!Range && @safe unittest { import std.meta : AliasSeq; - foreach (T; AliasSeq!(double, const double, immutable double)) - { + static 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 +`a` and `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, +kullbackLeiblerDivergence). If any element `bi` is zero and the +corresponding element `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. @@ -2015,7 +2341,7 @@ if (isInputRange!(Range1) && isInputRange!(Range2)) /// @safe unittest { - import std.math : approxEqual; + import std.math.operations : isClose; double[] p = [ 0.0, 0, 0, 1 ]; assert(kullbackLeiblerDivergence(p, p) == 0); @@ -2024,21 +2350,21 @@ if (isInputRange!(Range1) && isInputRange!(Range2)) 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)); + assert(isClose(kullbackLeiblerDivergence(p1, p2), 0.0719281, 1e-5)); + assert(isClose(kullbackLeiblerDivergence(p2, p1), 0.0780719, 1e-5)); } /** Computes the $(LINK2 https://en.wikipedia.org/wiki/Jensen%E2%80%93Shannon_divergence, -Jensen-Shannon divergence) between $(D a) and $(D +Jensen-Shannon divergence) between `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, +checked by `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). +or equal to `limit`. */ CommonType!(ElementType!Range1, ElementType!Range2) jensenShannonDivergence(Range1, Range2)(Range1 a, Range2 b) @@ -2099,33 +2425,33 @@ if (isInputRange!Range1 && isInputRange!Range2 && /// @safe unittest { - import std.math : approxEqual; + import std.math.operations : isClose; 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)); + assert(isClose(jensenShannonDivergence(p1, p), 0.548795, 1e-5)); 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)); + assert(isClose(jensenShannonDivergence(p1, p2), 0.0186218, 1e-5)); + assert(isClose(jensenShannonDivergence(p2, p1), 0.0186218, 1e-5)); + assert(isClose(jensenShannonDivergence(p2, p1, 0.005), 0.00602366, 1e-5)); } /** The so-called "all-lengths gap-weighted string kernel" computes a -similarity measure between $(D s) and $(D t) based on all of their +similarity measure between `s` and `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 +"world"]). In that case, `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 +$(OL $(LI three matches of length 1, namely `"Hello"`, `"new"`, +and `"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")).)) @@ -2156,12 +2482,12 @@ 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 +strings. This is where values of `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 +number of gaps) with base `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 +string adds `lambda` to the return value; ...; a match with a total +of `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 @@ -2174,11 +2500,11 @@ 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 +`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 +numbers. `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. @@ -2242,25 +2568,25 @@ if (isRandomAccessRange!(R1) && hasLength!(R1) && } /** -The similarity per $(D gapWeightedSimilarity) has an issue in that it +The similarity per `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) +"world", "world", "world",...]) as more instances of `"world"` are +appended. To prevent that, `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 +lambda))). The function `gapWeightedSimilarityNormalized` (a +so-called normalized kernel) is bounded in $(D [0, 1]), reaches `0` +only for ranges that don't match in any position, and `1` only for identical ranges. -The optional parameters $(D sSelfSim) and $(D tSelfSim) are meant for +The optional parameters `sSelfSim` and `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. +as `sSelfSim` and `tSelfSim`, respectively. */ Select!(isFloatingPoint!(F), F, double) gapWeightedSimilarityNormalized(alias comp = "a == b", R1, R2, F) @@ -2289,19 +2615,20 @@ if (isRandomAccessRange!(R1) && hasLength!(R1) && /// @system unittest { - import std.math : approxEqual, sqrt; + import std.math.operations : isClose; + import std.math.algebraic : 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), + assert(isClose(gapWeightedSimilarityNormalized(s, t, 1), 7.0 / sqrt(15.0 * 7), 0.01)); } /** -Similar to $(D gapWeightedSimilarity), just works in an incremental +Similar to `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 @@ -2327,8 +2654,8 @@ private: 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) +Constructs an object given two ranges `s` and `t` and a penalty +`lambda`. Constructor completes in $(BIGOH s.length * t.length) time and computes all matches of length 1. */ this(Range s, Range t, F lambda) @@ -2391,7 +2718,7 @@ time and computes all matches of length 1. } /** - Returns: $(D this). + Returns: `this`. */ ref GapWeightedSimilarityIncremental opSlice() { @@ -2484,7 +2811,7 @@ time and computes all matches of length 1. /** Returns: The gapped similarity at the current match length (initially - 1, grows with each call to $(D popFront)). + 1, grows with each call to `popFront`). */ @property F front() { return currentValue; } @@ -2598,61 +2925,63 @@ GapWeightedSimilarityIncremental!(R, F) gapWeightedSimilarityIncremental(R, F) } /** -Computes the greatest common divisor of $(D a) and $(D b) by using +Computes the greatest common divisor of `a` and `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 + a = Integer value of 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. + b = Integer value of any equivalent numerical type. + Returns: The greatest common divisor of the given arguments. */ -T gcd(T)(T a, T b) - if (isIntegral!T) +typeof(Unqual!(T).init % Unqual!(U).init) gcd(T, U)(T a, U b) +if (isIntegral!T && isIntegral!U) { - 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; - } + // Operate on a common type between the two arguments. + alias UCT = Unsigned!(CommonType!(Unqual!T, Unqual!U)); + + // `std.math.abs` doesn't support unsigned integers, and `T.min` is undefined. + static if (is(T : immutable short) || is(T : immutable byte)) + UCT ax = (isUnsigned!T || a >= 0) ? a : cast(UCT) -int(a); else - { - if (a == 0) - return b; - if (b == 0) - return a; + UCT ax = (isUnsigned!T || a >= 0) ? a : -UCT(a); - import core.bitop : bsf; - import std.algorithm.mutation : swap; + static if (is(U : immutable short) || is(U : immutable byte)) + UCT bx = (isUnsigned!U || b >= 0) ? b : cast(UCT) -int(b); + else + UCT bx = (isUnsigned!U || b >= 0) ? b : -UCT(b); - immutable uint shift = bsf(a | b); - a >>= a.bsf; + // Special cases. + if (ax == 0) + return bx; + if (bx == 0) + return ax; - do - { - b >>= b.bsf; - if (a > b) - swap(a, b); - b -= a; - } while (b); + return gcdImpl(ax, bx); +} - return a << shift; - } +private typeof(T.init % T.init) gcdImpl(T)(T a, T b) +if (isIntegral!T) +{ + pragma(inline, true); + 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; } /// @@ -2663,16 +2992,114 @@ T gcd(T)(T a, T b) assert(gcd(a, b) == 13); } +@safe unittest +{ + import std.meta : AliasSeq; + static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, + const byte, const short, const int, const long, + immutable ubyte, immutable ushort, immutable uint, immutable ulong)) + { + static foreach (U; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, + const ubyte, const ushort, const uint, const ulong, + immutable byte, immutable short, immutable int, immutable long)) + { + // Signed and unsigned tests. + static if (T.max > byte.max && U.max > byte.max) + assert(gcd(T(200), U(200)) == 200); + static if (T.max > ubyte.max) + { + assert(gcd(T(2000), U(20)) == 20); + assert(gcd(T(2011), U(17)) == 1); + } + static if (T.max > ubyte.max && U.max > ubyte.max) + assert(gcd(T(1071), U(462)) == 21); + + assert(gcd(T(0), U(13)) == 13); + assert(gcd(T(29), U(0)) == 29); + assert(gcd(T(0), U(0)) == 0); + assert(gcd(T(1), U(2)) == 1); + assert(gcd(T(9), U(6)) == 3); + assert(gcd(T(3), U(4)) == 1); + assert(gcd(T(32), U(24)) == 8); + assert(gcd(T(5), U(6)) == 1); + assert(gcd(T(54), U(36)) == 18); + + // Int and Long tests. + static if (T.max > short.max && U.max > short.max) + assert(gcd(T(46391), U(62527)) == 2017); + static if (T.max > ushort.max && U.max > ushort.max) + assert(gcd(T(63245986), U(39088169)) == 1); + static if (T.max > uint.max && U.max > uint.max) + { + assert(gcd(T(77160074263), U(47687519812)) == 1); + assert(gcd(T(77160074264), U(47687519812)) == 4); + } + + // Negative tests. + static if (T.min < 0) + { + assert(gcd(T(-21), U(28)) == 7); + assert(gcd(T(-3), U(4)) == 1); + } + static if (U.min < 0) + { + assert(gcd(T(1), U(-2)) == 1); + assert(gcd(T(33), U(-44)) == 11); + } + static if (T.min < 0 && U.min < 0) + { + assert(gcd(T(-5), U(-6)) == 1); + assert(gcd(T(-50), U(-60)) == 10); + } + } + } +} + +// https://issues.dlang.org/show_bug.cgi?id=21834 +@safe unittest +{ + assert(gcd(-120, 10U) == 10); + assert(gcd(120U, -10) == 10); + assert(gcd(int.min, 0L) == 1L + int.max); + assert(gcd(0L, int.min) == 1L + int.max); + assert(gcd(int.min, 0L + int.min) == 1L + int.max); + assert(gcd(int.min, 1L + int.max) == 1L + int.max); + assert(gcd(short.min, 1U + short.max) == 1U + short.max); +} + // 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 && +auto 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; + static if (!is(T == Unqual!T)) + { + return gcd!(Unqual!T)(a, b); + } + else + { + // Ensure arguments are unsigned. + a = a >= 0 ? a : -a; + b = b >= 0 ? b : -b; + + // Special cases. + if (a == 0) + return b; + if (b == 0) + return a; + + return gcdImpl(a, b); + } +} +private auto gcdImpl(T)(T a, T b) +if (!isIntegral!T) +{ + pragma(inline, true); + import std.algorithm.mutation : swap; enum canUseBinaryGcd = is(typeof(() { T t, u; t <<= 1; @@ -2682,8 +3109,6 @@ T gcd(T)(T a, T b) swap(t, u); })); - assert(a >= 0 && b >= 0); - static if (canUseBinaryGcd) { uint shift = 0; @@ -2694,6 +3119,8 @@ T gcd(T)(T a, T b) shift++; } + if ((a & 1) == 0) swap(a, b); + do { assert((a & 1) != 0); @@ -2719,13 +3146,16 @@ T gcd(T)(T a, T b) } } -// Issue 7102 +// https://issues.dlang.org/show_bug.cgi?id=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")); + + assert(gcd(BigInt(0), BigInt(1234567)) == BigInt(1234567)); + assert(gcd(BigInt(1234567), BigInt(0)) == BigInt(1234567)); } @safe pure nothrow unittest @@ -2739,16 +3169,149 @@ T gcd(T)(T a, T b) { return CrippledInt(impl % i.impl); } + CrippledInt opUnary(string op : "-")() + { + return CrippledInt(-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)); + assert(gcd(CrippledInt(-120), CrippledInt(10U)) == CrippledInt(10)); + assert(gcd(CrippledInt(120U), CrippledInt(-10)) == CrippledInt(10)); +} + +// https://issues.dlang.org/show_bug.cgi?id=19514 +@system pure unittest +{ + import std.bigint : BigInt; + assert(gcd(BigInt(2), BigInt(1)) == BigInt(1)); +} + +// Issue 20924 +@safe unittest +{ + import std.bigint : BigInt; + const a = BigInt("123143238472389492934020"); + const b = BigInt("902380489324729338420924"); + assert(__traits(compiles, gcd(a, b))); +} + +// https://issues.dlang.org/show_bug.cgi?id=21834 +@safe unittest +{ + import std.bigint : BigInt; + assert(gcd(BigInt(-120), BigInt(10U)) == BigInt(10)); + assert(gcd(BigInt(120U), BigInt(-10)) == BigInt(10)); + assert(gcd(BigInt(int.min), BigInt(0L)) == BigInt(1L + int.max)); + assert(gcd(BigInt(0L), BigInt(int.min)) == BigInt(1L + int.max)); + assert(gcd(BigInt(int.min), BigInt(0L + int.min)) == BigInt(1L + int.max)); + assert(gcd(BigInt(int.min), BigInt(1L + int.max)) == BigInt(1L + int.max)); + assert(gcd(BigInt(short.min), BigInt(1U + short.max)) == BigInt(1U + short.max)); +} + + +/** +Computes the least common multiple of `a` and `b`. +Arguments are the same as $(MYREF gcd). + +Returns: + The least common multiple of the given arguments. + */ +typeof(Unqual!(T).init % Unqual!(U).init) lcm(T, U)(T a, U b) +if (isIntegral!T && isIntegral!U) +{ + // Operate on a common type between the two arguments. + alias UCT = Unsigned!(CommonType!(Unqual!T, Unqual!U)); + + // `std.math.abs` doesn't support unsigned integers, and `T.min` is undefined. + static if (is(T : immutable short) || is(T : immutable byte)) + UCT ax = (isUnsigned!T || a >= 0) ? a : cast(UCT) -int(a); + else + UCT ax = (isUnsigned!T || a >= 0) ? a : -UCT(a); + + static if (is(U : immutable short) || is(U : immutable byte)) + UCT bx = (isUnsigned!U || b >= 0) ? b : cast(UCT) -int(b); + else + UCT bx = (isUnsigned!U || b >= 0) ? b : -UCT(b); + + // Special cases. + if (ax == 0) + return ax; + if (bx == 0) + return bx; + + return (ax / gcdImpl(ax, bx)) * bx; +} + +/// +@safe unittest +{ + assert(lcm(1, 2) == 2); + assert(lcm(3, 4) == 12); + assert(lcm(5, 6) == 30); +} + +@safe unittest +{ + import std.meta : AliasSeq; + static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, + const byte, const short, const int, const long, + immutable ubyte, immutable ushort, immutable uint, immutable ulong)) + { + static foreach (U; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, + const ubyte, const ushort, const uint, const ulong, + immutable byte, immutable short, immutable int, immutable long)) + { + assert(lcm(T(21), U(6)) == 42); + assert(lcm(T(41), U(0)) == 0); + assert(lcm(T(0), U(7)) == 0); + assert(lcm(T(0), U(0)) == 0); + assert(lcm(T(1U), U(2)) == 2); + assert(lcm(T(3), U(4U)) == 12); + assert(lcm(T(5U), U(6U)) == 30); + static if (T.min < 0) + assert(lcm(T(-42), U(21U)) == 42); + } + } +} + +/// ditto +auto lcm(T)(T a, T b) +if (!isIntegral!T && + is(typeof(T.init % T.init)) && + is(typeof(T.init == 0 || T.init > 0))) +{ + // Ensure arguments are unsigned. + a = a >= 0 ? a : -a; + b = b >= 0 ? b : -b; + + // Special cases. + if (a == 0) + return a; + if (b == 0) + return b; + + return (a / gcdImpl(a, b)) * b; +} + +@safe unittest +{ + import std.bigint : BigInt; + assert(lcm(BigInt(21), BigInt(6)) == BigInt(42)); + assert(lcm(BigInt(41), BigInt(0)) == BigInt(0)); + assert(lcm(BigInt(0), BigInt(7)) == BigInt(0)); + assert(lcm(BigInt(0), BigInt(0)) == BigInt(0)); + assert(lcm(BigInt(1U), BigInt(2)) == BigInt(2)); + assert(lcm(BigInt(3), BigInt(4U)) == BigInt(12)); + assert(lcm(BigInt(5U), BigInt(6U)) == BigInt(30)); + assert(lcm(BigInt(-42), BigInt(21U)) == BigInt(42)); } // 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 +// they pass the "isClose(inverseFft(fft(arr)), arr)" test even for // size 2 ^^ 22. private alias lookup_t = float; @@ -2785,7 +3348,7 @@ private: assert(range.length >= 4); assert(isPowerOf2(range.length)); } - body + do { auto recurseRange = range; recurseRange.doubleSteps(); @@ -2819,7 +3382,7 @@ private: assert(range.length >= 4); assert(isPowerOf2(range.length)); } - body + do { alias E = ElementType!R; @@ -2927,7 +3490,7 @@ private: { assert(isPowerOf2(buf.length)); } - body + do { immutable n = buf.length; immutable localLookup = negSinLookup[bsf(n)]; @@ -2998,7 +3561,9 @@ private: // // Also, this is unsafe because the memSpace buffer will be cast // to immutable. - public this(lookup_t[] memSpace) // Public b/c of bug 4636. + // + // Public b/c of https://issues.dlang.org/show_bug.cgi?id=4636. + public this(lookup_t[] memSpace) { immutable size = memSpace.length / 2; @@ -3056,8 +3621,8 @@ private: } 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 + /**Create an `Fft` object for computing fast Fourier transforms of + * power of two sizes of `size` or smaller. `size` must be a * power of two. */ this(size_t size) @@ -3074,11 +3639,11 @@ public: } /**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 + * Cooley-Tukey Algorithm. `range` must be a random-access range with + * slicing and a length equal to `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. + * properties or members `.re` and `.im` that can be read. * * Note: Pure real FFTs are automatically detected and the relevant * optimizations are performed. @@ -3217,7 +3782,7 @@ private enum string MakeLocalFft = q{ auto fftObj = scoped!Fft(lookupBuf); }; -/**Convenience functions that create an $(D Fft) object, run the FFT or inverse +/**Convenience functions that create an `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 @@ -3260,37 +3825,37 @@ void inverseFft(Ret, R)(R range, Ret buf) // 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])); + assert(isClose(map!"a.re"(fft1), + [36.0, -4, -4, -4, -4, -4, -4, -4], 1e-4)); + assert(isClose(map!"a.im"(fft1), + [0, 9.6568, 4, 1.6568, 0, -1.6568, -4, -9.6568], 1e-4)); 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])); + assert(isClose(map!"a.re"(fft1Retro), + [36.0, 4, 4, 4, 4, 4, 4, 4], 1e-4)); + assert(isClose(map!"a.im"(fft1Retro), + [0, -9.6568, -4, -1.6568, 0, 1.6568, 4, 9.6568], 1e-4)); 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))); + assert(isClose(map!"a.re"(fft1), map!"a.re"(fft1Float))); + assert(isClose(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])); + assert(isClose(map!"a.re"(fft2), + [64.0, -27.3137, -16, -11.3137, -8, -4.6862, 0, 11.3137], 1e-4)); + assert(isClose(map!"a.im"(fft2), + [72, 11.3137, 0, -4.686, -8, -11.3137, -16, -27.3137], 1e-4)); auto inv1 = inverseFft(fft1); - assert(approxEqual(map!"a.re"(inv1), arr)); + assert(isClose(map!"a.re"(inv1), arr, 1e-6)); 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))); + assert(isClose(map!"a.re"(inv2), map!"a.re"(arr2))); + assert(isClose(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; @@ -3305,21 +3870,21 @@ void inverseFft(Ret, R)(R range, Ret buf) auto oneInv = inverseFft(oneFft); assert(oneInv.length == 1); - assert(approxEqual(oneInv[0].re, 4.5)); - assert(approxEqual(oneInv[0].im, 0)); + assert(isClose(oneInv[0].re, 4.5)); + assert(isClose(oneInv[0].im, 0, 0.0, 1e-10)); 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)); + assert(isClose(twoFft[0].re, 12)); + assert(isClose(twoFft[0].im, 0, 0.0, 1e-10)); + assert(isClose(twoFft[1].re, 4)); + assert(isClose(twoFft[1].im, 0, 0.0, 1e-10)); 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)); + assert(isClose(twoInv[0].re, 8)); + assert(isClose(twoInv[0].im, 0, 0.0, 1e-10)); + assert(isClose(twoInv[1].re, 4)); + assert(isClose(twoInv[1].im, 0, 0.0, 1e-10)); } // Swaps the real and imaginary parts of a complex number. This is useful @@ -3329,6 +3894,89 @@ C swapRealImag(C)(C input) return C(input.im, input.re); } +/** This function transforms `decimal` value into a value in the factorial number +system stored in `fac`. + +A factorial number is constructed as: +$(D fac[0] * 0! + fac[1] * 1! + ... fac[20] * 20!) + +Params: + decimal = The decimal value to convert into the factorial number system. + fac = The array to store the factorial number. The array is of size 21 as + `ulong.max` requires 21 digits in the factorial number system. +Returns: + A variable storing the number of digits of the factorial number stored in + `fac`. +*/ +size_t decimalToFactorial(ulong decimal, ref ubyte[21] fac) + @safe pure nothrow @nogc +{ + import std.algorithm.mutation : reverse; + size_t idx; + + for (ulong i = 1; decimal != 0; ++i) + { + auto temp = decimal % i; + decimal /= i; + fac[idx++] = cast(ubyte)(temp); + } + + if (idx == 0) + { + fac[idx++] = cast(ubyte) 0; + } + + reverse(fac[0 .. idx]); + + // first digit of the number in factorial will always be zero + assert(fac[idx - 1] == 0); + + return idx; +} + +/// +@safe pure @nogc unittest +{ + ubyte[21] fac; + size_t idx = decimalToFactorial(2982, fac); + + assert(fac[0] == 4); + assert(fac[1] == 0); + assert(fac[2] == 4); + assert(fac[3] == 1); + assert(fac[4] == 0); + assert(fac[5] == 0); + assert(fac[6] == 0); +} + +@safe pure unittest +{ + ubyte[21] fac; + size_t idx = decimalToFactorial(0UL, fac); + assert(idx == 1); + assert(fac[0] == 0); + + fac[] = 0; + idx = 0; + idx = decimalToFactorial(ulong.max, fac); + assert(idx == 21); + auto t = [7, 11, 12, 4, 3, 15, 3, 5, 3, 5, 0, 8, 3, 5, 0, 0, 0, 2, 1, 1, 0]; + foreach (i, it; fac[0 .. 21]) + { + assert(it == t[i]); + } + + fac[] = 0; + idx = decimalToFactorial(2982, fac); + + assert(idx == 7); + t = [4, 0, 4, 1, 0, 0, 0]; + foreach (i, it; fac[0 .. idx]) + { + assert(it == t[i]); + } +} + 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 diff --git a/libphobos/src/std/outbuffer.d b/libphobos/src/std/outbuffer.d index d76ead2..9590238 100644 --- a/libphobos/src/std/outbuffer.d +++ b/libphobos/src/std/outbuffer.d @@ -1,18 +1,19 @@ // Written in the D programming language. /** -Serialize data to $(D ubyte) arrays. +Serialize data to `ubyte` arrays. - * Copyright: Copyright Digital Mars 2000 - 2015. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/outbuffer.d) * * $(SCRIPT inhibitQuickIndex = 1;) */ module std.outbuffer; -import core.stdc.stdarg; // : va_list; +import core.stdc.stdarg; +import std.traits : isSomeString; /********************************************* * OutBuffer provides a way to build up an array of bytes out @@ -41,7 +42,7 @@ class OutBuffer /********************************* * Convert to array of bytes. */ - ubyte[] toBytes() { return data[0 .. offset]; } + inout(ubyte)[] toBytes() scope inout { return data[0 .. offset]; } /*********************************** * Preallocate nbytes more to the size of the internal buffer. @@ -59,7 +60,7 @@ class OutBuffer { assert(offset + nbytes <= data.length); } - body + do { if (data.length < offset + nbytes) { @@ -78,19 +79,19 @@ class OutBuffer * Append data to the internal buffer. */ - void write(const(ubyte)[] bytes) + void write(scope const(ubyte)[] bytes) { reserve(bytes.length); data[offset .. offset + bytes.length] = bytes[]; offset += bytes.length; } - void write(in wchar[] chars) @trusted + void write(scope const(wchar)[] chars) @trusted { write(cast(ubyte[]) chars); } - void write(const(dchar)[] chars) @trusted + void write(scope const(dchar)[] chars) @trusted { write(cast(ubyte[]) chars); } @@ -161,12 +162,12 @@ class OutBuffer offset += real.sizeof; } - void write(in char[] s) @trusted /// ditto + void write(scope const(char)[] s) @trusted /// ditto { write(cast(ubyte[]) s); } - void write(OutBuffer buf) /// ditto + void write(scope const OutBuffer buf) /// ditto { write(buf.toBytes()); } @@ -195,7 +196,7 @@ class OutBuffer { assert((offset & (alignsize - 1)) == 0); } - body + do { auto nbytes = offset & (alignsize - 1); if (nbytes) @@ -245,13 +246,13 @@ class OutBuffer * Append output of C's vprintf() to internal buffer. */ - void vprintf(string format, va_list args) @trusted nothrow + void vprintf(scope string format, va_list args) @trusted nothrow { import core.stdc.stdio : vsnprintf; import core.stdc.stdlib : alloca; import std.string : toStringz; - version (unittest) + version (StdUnittest) char[3] buffer = void; // trigger reallocation else char[128] buffer = void; @@ -290,7 +291,7 @@ class OutBuffer * Append output of C's printf() to internal buffer. */ - void printf(string format, ...) @trusted + void printf(scope string format, ...) @trusted { va_list ap; va_start(ap, format); @@ -309,9 +310,9 @@ class OutBuffer * $(REF _writef, std,stdio); * $(REF formattedWrite, std,format); */ - void writef(Char, A...)(in Char[] fmt, A args) + void writef(Char, A...)(scope const(Char)[] fmt, A args) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; formattedWrite(this, fmt, args); } @@ -323,6 +324,25 @@ class OutBuffer assert(b.toString() == "a16b"); } + /// ditto + 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); + } + + /// + @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. @@ -335,9 +355,9 @@ class OutBuffer * $(REF _writefln, std,stdio); * $(REF formattedWrite, std,format); */ - void writefln(Char, A...)(in Char[] fmt, A args) + void writefln(Char, A...)(scope const(Char)[] fmt, A args) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; formattedWrite(this, fmt, args); put('\n'); } @@ -350,6 +370,25 @@ class OutBuffer assert(b.toString() == "a16b\n"); } + /// ditto + 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); + } + + /// + @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. @@ -360,7 +399,7 @@ class OutBuffer { assert(index <= offset); } - body + do { reserve(nbytes); diff --git a/libphobos/src/std/package.d b/libphobos/src/std/package.d new file mode 100644 index 0000000..a1d0444 --- /dev/null +++ b/libphobos/src/std/package.d @@ -0,0 +1,82 @@ +/++ +Convenience file that allows to import entire Phobos in one import. ++/ +module std; + +/// +@safe unittest +{ + import std; + + int len; + const r = 6.iota + .filter!(a => a % 2) // 1 3 5 + .map!(a => a * 2) // 2 6 10 + .tee!(_ => len++) + .substitute(6, -6) // 2 -6 10 + .sum + .reverseArgs!format("Sum: %d"); + + assert(len == 3); + assert(r == "Sum: 6"); +} + +/// +@safe unittest +{ + import std; + assert(10.iota.map!(a => pow(2, a)).sum == 1023); +} + +public import + std.algorithm, + std.array, + std.ascii, + std.base64, + std.bigint, + std.bitmanip, + std.compiler, + std.complex, + std.concurrency, + std.container, + std.conv, + std.csv, + std.datetime, + std.demangle, + std.digest, + std.encoding, + std.exception, + std.file, + std.format, + std.functional, + std.getopt, + std.json, + std.math, + std.mathspecial, + std.meta, + std.mmfile, + std.net.curl, + std.net.isemail, + std.numeric, + std.parallelism, + std.path, + std.process, + std.random, + std.range, + std.regex, + std.signals, + std.socket, + std.stdint, + std.stdio, + std.string, + std.sumtype, + std.system, + std.traits, + std.typecons, + std.uni, + std.uri, + std.utf, + std.uuid, + std.variant, + std.zip, + std.zlib; diff --git a/libphobos/src/std/parallelism.d b/libphobos/src/std/parallelism.d index 61d5cea..664330a 100644 --- a/libphobos/src/std/parallelism.d +++ b/libphobos/src/std/parallelism.d @@ -1,39 +1,39 @@ /** -$(D std._parallelism) implements high-level primitives for SMP _parallelism. +`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 +and future/promise parallelism. `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). +`std.concurrency`. -$(D std._parallelism) is based on the concept of a $(D Task). A $(D Task) is an +`std.parallelism` is based on the concept of a `Task`. A `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) +executed in parallel with any other `Task`. Using `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 +supported parallelism paradigms (parallel foreach, map, reduce, pipelining) +represent an additional level of abstraction over `Task`. They +automatically create one or more `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 +After creation, a `Task` may be executed in a new thread, or submitted +to a `TaskPool` for execution. A `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 +number of `Task`s onto a smaller number of threads. A task queue is a +FIFO queue of `Task` objects that have been submitted to the +`TaskPool` and are awaiting execution. A worker thread is a thread that +is associated with exactly one task queue. It executes the `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 +more worker threads. If the result of a `Task` is needed before execution +by a worker thread has begun, the `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 +Warning: Unless marked as `@trusted` or `@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) +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) @@ -53,7 +53,7 @@ else version (WatchOS) @system unittest { import std.algorithm.iteration : map; - import std.math : approxEqual; + import std.math.operations : isClose; import std.parallelism : taskPool; import std.range : iota; @@ -82,7 +82,7 @@ else version (WatchOS) immutable pi = 4.0 * taskPool.reduce!"a + b"(n.iota.map!getTerm); - assert(pi.approxEqual(3.1415926)); + assert(pi.isClose(3.14159, 1e-5)); } import core.atomic; @@ -389,32 +389,32 @@ private struct AbstractTask } /** -$(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 +`Task` represents the fundamental unit of work. A `Task` may be +executed in parallel with any other `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) +A call to `workForce`, `yieldForce`, or `spinForce` is used to +ensure that the `Task` has finished executing and to obtain the return +value, if any. These functions and `done` also act as full memory barriers, +meaning that any memory writes made in the thread that executed the `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. +be used to create an instance of this struct. See `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 +Function results are returned from `yieldForce`, `spinForce` and +`workForce` by ref. If `fun` returns by ref, the reference will point +to the returned reference of `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. +Bugs: Changes to `ref` and `out` arguments are not propagated to the + call site, only to `args` in this struct. */ struct Task(alias fun, Args...) { @@ -435,7 +435,7 @@ struct Task(alias fun, Args...) { fun(myCastedTask._args); } - else static if (is(typeof(addressOf(fun(myCastedTask._args))))) + else static if (is(typeof(&(fun(myCastedTask._args))))) { myCastedTask.returnVal = addressOf(fun(myCastedTask._args)); } @@ -451,8 +451,8 @@ struct Task(alias fun, Args...) Args _args; /** - The arguments the function was called with. Changes to $(D out) and - $(D ref) arguments will be visible here. + The arguments the function was called with. Changes to `out` and + `ref` arguments will be visible here. */ static if (__traits(isSame, fun, run)) { @@ -472,7 +472,7 @@ struct Task(alias fun, Args...) static if (isFunctionPointer!(_args[0])) { private enum bool isPure = - functionAttributes!(Args[0]) & FunctionAttribute.pure_; + (functionAttributes!(Args[0]) & FunctionAttribute.pure_) != 0; } else { @@ -493,8 +493,8 @@ struct Task(alias fun, Args...) /** - The return type of the function called by this $(D Task). This can be - $(D void). + The return type of the function called by this `Task`. This can be + `void`. */ alias ReturnType = typeof(fun(_args)); @@ -536,7 +536,8 @@ struct Task(alias fun, Args...) } } - // Work around DMD bug 6588, allow immutable elements. + // Work around DMD bug https://issues.dlang.org/show_bug.cgi?id=6588, + // allow immutable elements. static if (allSatisfy!(isAssignable, Args)) { typeof(this) opAssign(typeof(this) rhs) @@ -550,20 +551,17 @@ struct Task(alias fun, Args...) } else { - @disable typeof(this) opAssign(typeof(this) rhs) - { - assert(0); - } + @disable typeof(this) opAssign(typeof(this) rhs); } /** - If the $(D Task) isn't started yet, execute it in the current thread. + If the `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 + `Task` to be available on a timescale shorter than that of an OS context switch. */ @property ref ReturnType spinForce() @trusted @@ -586,7 +584,7 @@ struct Task(alias fun, Args...) } /** - If the $(D Task) isn't started yet, execute it in the current thread. + If the `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. @@ -631,13 +629,13 @@ struct Task(alias fun, Args...) } /** - If this $(D Task) was not started yet, execute it in the current + If this `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 + execute any other `Task` from the `TaskPool` instance that + this `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. + If no other tasks are available or this `Task` was executed using + `executeInNewThread`, wait on a condition variable. */ @property ref ReturnType workForce() @trusted { @@ -705,10 +703,10 @@ struct Task(alias fun, Args...) } /** - Returns $(D true) if the $(D Task) is finished executing. + Returns `true` if the `Task` is finished executing. Throws: Rethrows any exception thrown during the execution of the - $(D Task). + `Task`. */ @property bool done() @trusted { @@ -717,11 +715,11 @@ struct Task(alias fun, Args...) } /** - Create a new thread for executing this $(D Task), execute it in the + Create a new thread for executing this `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 + to the `Task`. If one is provided, its value is forwarded to + `core.thread.Thread.priority`. See $(REF task, std,parallelism) for usage example. */ void executeInNewThread() @trusted @@ -748,8 +746,8 @@ struct Task(alias fun, Args...) //@disable this(this) {} } -// Calls $(D fpOrDelegate) with $(D args). This is an -// adapter that makes $(D Task) work with delegates, function pointers and +// Calls `fpOrDelegate` with `args`. This is an +// adapter that makes `Task` work with delegates, function pointers and // functors instead of just aliases. ReturnType!F run(F, Args...)(F fpOrDelegate, ref Args args) { @@ -757,12 +755,12 @@ ReturnType!F run(F, Args...)(F fpOrDelegate, ref Args 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 +Creates a `Task` on the GC heap that calls an alias. This may be executed +via `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). +`TaskPool` is provided by $(REF taskPool, std,parallelism). -Returns: A pointer to the $(D Task). +Returns: A pointer to the `Task`. Example: --- @@ -828,7 +826,7 @@ auto task(alias fun, Args...)(Args args) } /** -Creates a $(D Task) on the GC heap that calls a function pointer, delegate, or +Creates a `Task` on the GC heap that calls a function pointer, delegate, or class/struct with overloaded opCall. Example: @@ -842,7 +840,7 @@ void main() { // Create and execute a Task for reading // foo.txt. - auto file1Task = task(&read, "foo.txt"); + auto file1Task = task(&read!string, "foo.txt", size_t.max); file1Task.executeInNewThread(); // Read bar.txt in parallel. @@ -855,7 +853,7 @@ void main() 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 + on the stack that have scoped destruction, see `scopedTask`, which takes a scope delegate. */ auto task(F, Args...)(F delegateOrFp, Args args) @@ -865,24 +863,24 @@ if (is(typeof(delegateOrFp(args))) && !isSafeTask!F) } /** -Version of $(D task) usable from $(D @safe) code. Usage mechanics are +Version of `task` usable from `@safe` code. Usage mechanics are identical to the non-@safe case, but safety introduces some restrictions: -1. $(D fun) must be @safe or @trusted. +1. `fun` must be @safe or @trusted. -2. $(D F) must not have any unshared aliasing as defined by +2. `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 + with overloaded `opCall`. This also precludes accepting template alias parameters. -3. $(D Args) must not have unshared aliasing. +3. `Args` must not have unshared aliasing. -4. $(D fun) must not return by reference. +4. `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). +5. The return type must not have unshared aliasing unless `fun` is + `pure` or the `Task` is executed via `executeInNewThread` instead + of using a `TaskPool`. */ @trusted auto task(F, Args...)(F fun, Args args) @@ -892,25 +890,25 @@ if (is(typeof(fun(args))) && isSafeTask!F) } /** -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) +These functions allow the creation of `Task` objects on the stack rather +than the GC heap. The lifetime of a `Task` created by `scopedTask` cannot exceed the lifetime of the scope it was created in. -$(D scopedTask) might be preferred over $(D task): +`scopedTask` might be preferred over `task`: -1. When a $(D Task) that calls a delegate is being created and a closure +1. When a `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) + destruction. The delegate overload of `scopedTask` takes a `scope` delegate. 2. As a micro-optimization, to avoid the heap allocation associated with - $(D task) or with the creation of a closure. + `task` or with the creation of a closure. -Usage is otherwise identical to $(D task). +Usage is otherwise identical to `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. +Notes: `Task` objects created using `scopedTask` will automatically +call `Task.yieldForce` in their destructor if necessary to ensure +the `Task` is complete before the stack frame they reside on is destroyed. */ auto scopedTask(alias fun, Args...)(Args args) { @@ -1053,22 +1051,28 @@ shared static ~this() /** 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 +is to efficiently map a large number of `Task`s onto a smaller number of +threads. A task queue is a FIFO queue of `Task` objects that have been +submitted to the `TaskPool` and are awaiting execution. A worker thread is a +thread that executes the `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): +Occasionally it is useful to explicitly instantiate a `TaskPool`: -1. When you want $(D TaskPool) instances with multiple priorities, for example +1. When you want `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. + +Note: The worker threads in this pool will not stop until + `stop` or `finish` is called, even if the main thread + has finished already. This may lead to programs that + never end. If you do not want this behaviour, you can set `isDaemon` + to true. */ final class TaskPool { @@ -1091,7 +1095,7 @@ private: Mutex waiterMutex; // For waiterCondition // The instanceStartIndex of the next instance that will be created. - __gshared static size_t nextInstanceIndex = 1; + __gshared size_t nextInstanceIndex = 1; // The index of the current thread. static size_t threadIndex; @@ -1214,7 +1218,7 @@ private: assert(returned.prev is null); } } - body + do { if (isSingleTask) return null; @@ -1258,7 +1262,7 @@ private: assert(tail.prev.next is tail, text(tail.prev, '\t', tail.next)); } } - body + do { // Not using enforce() to save on function call overhead since this // is a performance critical function. @@ -1452,7 +1456,7 @@ private: // Disabled until writing code to support // running thread with specified priority - // See https://d.puremagic.com/issues/show_bug.cgi?id=8960 + // See https://issues.dlang.org/show_bug.cgi?id=8960 /*if (priority != int.max) { @@ -1478,11 +1482,11 @@ public: } /** - Default constructor that initializes a $(D TaskPool) with - $(D totalCPUs) - 1 worker threads. The minus 1 is included because the + Default constructor that initializes a `TaskPool` with + `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) + Note: On single-core machines, the primitives provided by `TaskPool` operate transparently in single-threaded mode. */ this() @trusted @@ -1522,17 +1526,17 @@ public: /** 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 + creating and submitting one `Task` to the `TaskPool` for each worker + thread. A work unit is a set of consecutive elements of `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 + `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 + `workUnitSize` should be. For very expensive loop bodies, + `workUnitSize` should be 1. An overload that chooses a default work unit size is also available. Example: @@ -1566,18 +1570,18 @@ public: Notes: The memory usage of this implementation is guaranteed to be constant - in $(D range.length). + in `range.length`. Breaking from a parallel foreach loop via a break, labeled break, labeled continue, return or goto statement throws a - $(D ParallelForeachError). + `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 + to an array of size `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). + over a range returned by `asyncBuf` or `map`, the copying is elided + and the buffers are simply swapped. In this case `workUnitSize` is + ignored and the work unit size is set to the buffer size of `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. @@ -1585,10 +1589,10 @@ public: $(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 + the submission of additional `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 + were thrown by any work unit are chained using `Throwable.next` and rethrown. The order of the exception chaining is non-deterministic. */ ParallelForeach!R parallel(R)(R range, size_t workUnitSize) @@ -1623,9 +1627,9 @@ public: { /** Eager parallel map. The eagerness of this function means it has less - overhead than the lazily evaluated $(D TaskPool.map) and should be + overhead than the lazily evaluated `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 + `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 @@ -1633,7 +1637,7 @@ public: 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. + objects in their `.init` state. --- auto numbers = iota(100_000_000.0); @@ -1648,7 +1652,7 @@ public: --- 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 + may be provided. Work units as used by `amap` are identical to those defined for parallel foreach. If no work unit size is provided, the default work unit size is used. @@ -1693,21 +1697,21 @@ public: 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). + whatever argument is provided to it) to `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 + the submission of additional `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 + were thrown from any work unit are chained using `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; + import core.internal.lifetime : emplaceRef; alias fun = adjoin!(staticMap!(unaryFun, functions)); @@ -1829,8 +1833,8 @@ public: { /** 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 + functions are evaluated for the first `bufSize` elements and stored in a + buffer and made available to `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 @@ -1838,36 +1842,37 @@ public: 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) + `map` has more overhead than the simpler procedure used by `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 + source = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + to be mapped. If `source` is not random + access it will be lazily buffered to an array of size `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 + `Task`. Must be less than or equal to `bufSize`, and + should be a fraction of `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. + has a length iff `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 + If a range returned by `map` or `asyncBuf` is used as an input to + `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). + though the ranges returned by `map` and `asyncBuf` are non-random + access ranges. This means that the `bufSize` parameter passed to the + current call to `map` will be ignored and the size of the buffer + will be the buffer size of `source`. Example: --- @@ -1890,11 +1895,11 @@ public: $(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, + Any exceptions thrown while iterating over `source` + or computing the map function are re-thrown on a call to `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). + the exceptions are chained as in `TaskPool.amap`. */ auto map(S)(S source, size_t bufSize = 100, size_t workUnitSize = size_t.max) @@ -2092,7 +2097,8 @@ public: { assert(nextBufTask.prev is null); assert(nextBufTask.next is null); - } body + } + do { // Hack to reuse the task object. @@ -2171,13 +2177,13 @@ public: } /** - 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, + Given a `source` range that is expensive to iterate over, returns an + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that + asynchronously buffers the contents of `source` into a buffer of `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 + `bufSize`, available via the range interface of the returned + object. The returned range has a length iff `hasLength!S`. + `asyncBuf` is useful, for example, when performing expensive operations on the elements of ranges that represent data on a disk or network. Example: @@ -2209,8 +2215,8 @@ public: $(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 + Any exceptions thrown while iterating over `source` are re-thrown on a + call to `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) @@ -2278,7 +2284,8 @@ public: { assert(nextBufTask.prev is null); assert(nextBufTask.next is null); - } body + } + do { // Hack to reuse the task object. @@ -2351,30 +2358,30 @@ public: } /** - 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 + Given a callable object `next` that writes to a user-provided buffer and + a second callable object `empty` that determines whether more data is + available to write via `next`, returns an input range that + asynchronously calls `next` with a set of size `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 + input range overload of `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 + with mutable elements. When called, `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). + implicitly convertible to `bool`. This is used to signify + that no more data is available to be obtained by calling `next`. - initialBufSize = The initial size of each buffer. If $(D next) takes its + initialBufSize = The initial size of each buffer. If `next` takes its array by reference, it may resize the buffers. - nBuffers = The number of buffers to cycle through when calling $(D next). + nBuffers = The number of buffers to cycle through when calling `next`. Example: --- @@ -2403,8 +2410,8 @@ public: $(B Exception Handling): - Any exceptions thrown while iterating over $(D range) are re-thrown on a - call to $(D popFront). + Any exceptions thrown while iterating over `range` are re-thrown on a + call to `popFront`. Warning: @@ -2428,23 +2435,26 @@ public: { /** 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) + usage is similar to $(REF _reduce, std,algorithm,iteration). There is + also $(LREF fold) which does the same thing with a different parameter + order. + + 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, `functions` must be associative. For notational simplicity, let # be an - infix operator representing $(D functions). Then, (a # b) # c must equal + infix operator representing `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, + Note that, since `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 @@ -2485,7 +2495,7 @@ public: 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 + dominate computation time. If `TaskPool.size` for this instance is zero, this parameter is ignored and one work unit is used. --- // Use a work unit size of 100. @@ -2496,7 +2506,7 @@ public: --- Parallel reduce supports multiple functions, like - $(D std.algorithm.reduce). + `std.algorithm.reduce`. --- // Find both the min and max of nums. auto minMax = taskPool.reduce!(min, max)(nums); @@ -2507,13 +2517,19 @@ public: $(B Exception Handling): After this function is finished executing, any exceptions thrown - are chained together via $(D Throwable.next) and rethrown. The chaining + are chained together via `Throwable.next` and rethrown. The chaining order is non-deterministic. + + See_Also: + + $(LREF fold) is functionally equivalent to $(LREF _reduce) except the + range parameter comes first and there is no need to use + $(REF_ALTTEXT `tuple`,tuple,std,typecons) for multiple seeds. */ auto reduce(Args...)(Args args) { import core.exception : OutOfMemoryError; - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; import std.exception : enforce; alias fun = reduceAdjoin!functions; @@ -2685,8 +2701,8 @@ public: 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(). + // Can't use alloca() due to https://issues.dlang.org/show_bug.cgi?id=3753 + // Use a fixed buffer backed by malloc(). enum maxStack = 2_048; byte[maxStack] buf = void; immutable size_t nBytesNeeded = nWorkUnits * RTask.sizeof; @@ -2787,7 +2803,7 @@ public: // done or in progress. Force all of them. E result = seed; - Throwable firstException, lastException; + Throwable firstException; foreach (ref task; tasks) { @@ -2797,7 +2813,10 @@ public: } catch (Throwable e) { - addToChain(e, firstException, lastException); + /* Chain e to front because order doesn't matter and because + * e is not likely to be a chain itself (so fewer traversals) + */ + firstException = Throwable.chainTogether(e, firstException); continue; } @@ -2810,10 +2829,132 @@ public: } } + /// + template fold(functions...) + { + /** Implements the homonym function (also known as `accumulate`, `compress`, + `inject`, or `foldl`) present in various programming languages of + functional flavor. + + `fold` is functionally equivalent to $(LREF reduce) except the range + parameter comes first and there is no need to use $(REF_ALTTEXT + `tuple`,tuple,std,typecons) for multiple seeds. + + There may be one or more callable entities (`functions` argument) to + apply. + + Params: + args = Just the range to _fold over; or the range and one seed + per function; or the range, one seed per function, and + the work unit size + + Returns: + The accumulated result as a single value for single function and + as a tuple of values for multiple functions + + See_Also: + Similar to $(REF _fold, std,algorithm,iteration), `fold` is a wrapper around $(LREF reduce). + + Example: + --- + static int adder(int a, int b) + { + return a + b; + } + static int multiplier(int a, int b) + { + return a * b; + } + + // Just the range + auto x = taskPool.fold!adder([1, 2, 3, 4]); + assert(x == 10); + + // The range and the seeds (0 and 1 below; also note multiple + // functions in this example) + auto y = taskPool.fold!(adder, multiplier)([1, 2, 3, 4], 0, 1); + assert(y[0] == 10); + assert(y[1] == 24); + + // The range, the seed (0), and the work unit size (20) + auto z = taskPool.fold!adder([1, 2, 3, 4], 0, 20); + assert(z == 10); + --- + */ + auto fold(Args...)(Args args) + { + static assert(isInputRange!(Args[0]), "First argument must be an InputRange"); + + alias range = args[0]; + + static if (Args.length == 1) + { + // Just the range + return reduce!functions(range); + } + else static if (Args.length == 1 + functions.length || + Args.length == 1 + functions.length + 1) + { + static if (functions.length == 1) + { + alias seeds = args[1]; + } + else + { + auto seeds() + { + import std.typecons : tuple; + return tuple(args[1 .. functions.length+1]); + } + } + + static if (Args.length == 1 + functions.length) + { + // The range and the seeds + return reduce!functions(seeds, range); + } + else static if (Args.length == 1 + functions.length + 1) + { + // The range, the seeds, and the work unit size + static assert(isIntegral!(Args[$-1]), "Work unit size must be an integral type"); + return reduce!functions(seeds, range, args[$-1]); + } + } + else + { + import std.conv : text; + static assert(0, "Invalid number of arguments (" ~ Args.length.text ~ "): Should be an input range, " + ~ functions.length.text ~ " optional seed(s), and an optional work unit size."); + } + } + } + + // This test is not included in the documentation because even though these + // examples are for the inner fold() template, with their current location, + // they would appear under the outer one. (We can't move this inside the + // outer fold() template because then dmd runs out of memory possibly due to + // recursive template instantiation, which is surprisingly not caught.) + @system unittest + { + // Just the range + auto x = taskPool.fold!"a + b"([1, 2, 3, 4]); + assert(x == 10); + + // The range and the seeds (0 and 1 below; also note multiple + // functions in this example) + auto y = taskPool.fold!("a + b", "a * b")([1, 2, 3, 4], 0, 1); + assert(y[0] == 10); + assert(y[1] == 24); + + // The range, the seed (0), and the work unit size (20) + auto z = taskPool.fold!"a + b"([1, 2, 3, 4], 0, 20); + assert(z == 10); + } + /** - Gets the index of the current thread relative to this $(D TaskPool). Any + Gets the index of the current thread relative to this `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 pool receive unique indices of 1 through `this.size`. This function is useful for maintaining worker-local resources. @@ -2860,22 +3001,22 @@ public: /** 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 + `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 + any worker thread in the `TaskPool` that created it, and one thread + outside this `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: + The main uses cases for `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. + functional, programming style. In this case, it's useful to treat + `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. @@ -2971,13 +3112,13 @@ public: 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 + Note that calling `get` from any thread + outside the `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 + If assertions are enabled and `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. @@ -3012,7 +3153,7 @@ public: 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 + longer worker-local, and attempting to use the `get` method again will result in an assertion failure if assertions are enabled. */ WorkerLocalStorageRange!T toRange() @property @@ -3042,9 +3183,9 @@ public: 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 + `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 + a length equal to the number of worker threads in the `TaskPool` that created it plus 1. */ static struct WorkerLocalStorageRange(T) @@ -3128,9 +3269,9 @@ public: /** Creates an instance of worker-local storage, initialized with a given - value. The value is $(D lazy) so that you can, for example, easily + value. The value is `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. + see the `WorkerLocalStorage` struct. */ WorkerLocalStorage!T workerLocalStorage(T)(lazy T initialVal = T.init) { @@ -3151,12 +3292,12 @@ public: /** 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) + with their current `Task`, or immediately if they are not executing a + `Task`. `Task`s that were in queue will not be executed unless + a call to `Task.workForce`, `Task.yieldForce` or `Task.spinForce` causes them to be executed. - Use only if you have waited on every $(D Task) and therefore know the + Use only if you have waited on every `Task` and therefore know the queue is empty, or if you speculatively executed some tasks and no longer need the results. */ @@ -3173,13 +3314,13 @@ public: 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 + task results are never consumed-- e.g. when `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. + thread that is a member of the same `TaskPool` that + `finish` is being called on will result in a deadlock. */ void finish(bool blocking = false) @trusted { @@ -3218,7 +3359,7 @@ public: } /** - Put a $(D Task) object on the back of the task queue. The $(D Task) + Put a `Task` object on the back of the task queue. The `Task` object may be passed by pointer or reference. Example: @@ -3234,20 +3375,20 @@ public: 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). + @trusted overloads of this function are called for `Task`s if + $(REF hasUnsharedAliasing, std,traits) is false for the `Task`'s + return type or the function the `Task` executes is `pure`. + `Task` objects that meet all other requirements specified in the + `@trusted` overloads of `task` and `scopedTask` may be created + and executed from `@safe` code via `Task.executeInNewThread` but + not via `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 + `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). + complete and no longer referenced by a `TaskPool`. */ void put(alias fun, Args...)(ref Task!(fun, Args) task) if (!isSafeReturn!(typeof(task))) @@ -3286,11 +3427,11 @@ public: 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. + If any `TaskPool` with non-daemon threads is active, either `stop` + or `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 + The worker treads in the `TaskPool` instance returned by the + `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 @@ -3316,12 +3457,12 @@ public: /** 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). + the worker threads in this `TaskPool`. They forward to + `core.thread.Thread.priority`, so a given priority value here means the + same thing as an identical priority value in `core.thread`. Note: For a size zero pool, the getter arbitrarily returns - $(D core.thread.Thread.PRIORITY_MIN) and the setter has no effect. + `core.thread.Thread.PRIORITY_MIN` and the setter has no effect. */ int priority() @property @trusted { @@ -3342,11 +3483,33 @@ public: } } +@system unittest +{ + import std.algorithm.iteration : sum; + import std.range : iota; + import std.typecons : tuple; + + enum N = 100; + auto r = iota(1, N + 1); + const expected = r.sum(); + + // Just the range + assert(taskPool.fold!"a + b"(r) == expected); + + // Range and seeds + assert(taskPool.fold!"a + b"(r, 0) == expected); + assert(taskPool.fold!("a + b", "a + b")(r, 0, 0) == tuple(expected, expected)); + + // Range, seeds, and work unit size + assert(taskPool.fold!"a + b"(r, 0, 42) == expected); + assert(taskPool.fold!("a + b", "a + b")(r, 0, 0, 42) == tuple(expected, expected)); +} + /** -Returns a lazily initialized global instantiation of $(D TaskPool). +Returns a lazily initialized global instantiation of `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 +is not necessary to call `TaskPool.stop` or `TaskPool.finish` before terminating the main thread. */ @property TaskPool taskPool() @trusted @@ -3363,10 +3526,10 @@ terminating the main thread. private shared uint _defaultPoolThreads = uint.max; /** -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). +These properties get and set the number of worker threads in the `TaskPool` +instance returned by `taskPool`. The default value is `totalCPUs` - 1. +Calling the setter after the first call to `taskPool` does not changes +number of worker threads in the instance returned by `taskPool`. */ @property uint defaultPoolThreads() @trusted { @@ -3381,7 +3544,7 @@ number of worker threads in the instance returned by $(D taskPool). } /** -Convenience functions that forwards to $(D taskPool.parallel). The +Convenience functions that forwards to `taskPool.parallel`. The purpose of these is to make parallel foreach less verbose and more readable. @@ -3410,6 +3573,23 @@ ParallelForeach!R parallel(R)(R range, size_t workUnitSize) return taskPool.parallel(range, workUnitSize); } +// `each` should be usable with parallel +// https://issues.dlang.org/show_bug.cgi?id=17019 +@system unittest +{ + import std.algorithm.iteration : each, sum; + import std.range : iota; + + // check behavior with parallel + auto arr = new int[10]; + parallel(arr).each!((ref e) => e += 1); + assert(arr.sum == 10); + + auto arrIndex = new int[10]; + parallel(arrIndex).each!((i, ref e) => e += i); + assert(arrIndex.sum == 10.iota.sum); +} + // Thrown when a parallel foreach loop is broken from. class ParallelForeachError : Error { @@ -3441,9 +3621,10 @@ private void submitAndExecute( // 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 + // to https://issues.dlang.org/show_bug.cgi?id=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; @@ -3519,7 +3700,7 @@ private void submitAndExecute( } } - Throwable firstException, lastException; + Throwable firstException; foreach (i, ref task; tasks) { @@ -3529,7 +3710,10 @@ private void submitAndExecute( } catch (Throwable e) { - addToChain(e, firstException, lastException); + /* Chain e to front because order doesn't matter and because + * e is not likely to be a chain itself (so fewer traversals) + */ + firstException = Throwable.chainTogether(e, firstException); continue; } } @@ -3829,38 +4013,6 @@ enum string parallelApplyMixinInputRange = q{ 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) { @@ -3947,7 +4099,7 @@ private struct RoundRobinBuffer(C1, C2) { assert(!empty); } - body + do { scope(success) primed = true; nextDel(bufs[index]); @@ -3959,7 +4111,7 @@ private struct RoundRobinBuffer(C1, C2) { assert(!empty); } - body + do { if (!primed) prime(); return bufs[index]; @@ -3983,12 +4135,10 @@ private struct RoundRobinBuffer(C1, C2) } } -version (unittest) +version (StdUnittest) { // This was the only way I could get nested maps to work. - __gshared TaskPool poolInstance; - - import std.stdio; + private __gshared TaskPool poolInstance; } // These test basic functionality but don't stress test for threading bugs. @@ -4000,9 +4150,12 @@ version (unittest) import std.array : split; import std.conv : text; import std.exception : assertThrown; - import std.math : approxEqual, sqrt, log, abs; + import std.math.operations : isClose; + import std.math.algebraic : sqrt, abs; + import std.math.exponential : log; import std.range : indexed, iota, join; import std.typecons : Tuple, tuple; + import std.stdio; poolInstance = new TaskPool(2); scope(exit) poolInstance.stop(); @@ -4132,7 +4285,7 @@ version (unittest) foreach (i, elem; logs) { - assert(approxEqual(elem, cast(double) log(i + 1))); + assert(isClose(elem, cast(double) log(i + 1))); } assert(poolInstance.amap!"a * a"([1,2,3,4,5]) == [1,4,9,16,25]); @@ -4206,7 +4359,7 @@ version (unittest) pool2.finish(true); // blocking assert(tSlow2.done); - // Test fix for Bug 8582 by making pool size zero. + // Test fix for https://issues.dlang.org/show_bug.cgi?id=8582 by making pool size zero. auto pool3 = new TaskPool(0); auto tSlow3 = task!slowFun(); pool3.put(tSlow3); @@ -4230,25 +4383,25 @@ version (unittest) assert(equal(nums, iota(1000))); assert(equal( - poolInstance.map!"a * a"(iota(30_000_001), 10_000), - map!"a * a"(iota(30_000_001)) + poolInstance.map!"a * a"(iota(3_000_001), 10_000), + map!"a * a"(iota(3_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) + filter!"a == a"(iota(3_000_001) ), 10_000, 1000), - map!"a * a"(iota(30_000_001)) + map!"a * a"(iota(3_000_001)) )); assert( reduce!"a + b"(0UL, - poolInstance.map!"a * a"(iota(3_000_001), 10_000) + poolInstance.map!"a * a"(iota(300_001), 10_000) ) == reduce!"a + b"(0UL, - map!"a * a"(iota(3_000_001)) + map!"a * a"(iota(300_001)) ) ); @@ -4311,7 +4464,7 @@ version (unittest) int ii; foreach ( elem; (lmchain)) { - if (!approxEqual(elem, ii)) + if (!isClose(elem, ii)) { stderr.writeln(ii, '\t', elem); } @@ -4405,8 +4558,12 @@ version (unittest) // tons of stuff and should not be run every time make unittest is run. version (parallelismStressTest) { - @safe unittest + @system unittest { + import std.stdio : stderr, writeln, readln; + import std.range : iota; + import std.algorithm.iteration : filter, reduce; + size_t attempt; for (; attempt < 10; attempt++) foreach (poolSize; [0, 4]) @@ -4488,8 +4645,16 @@ version (parallelismStressTest) // These unittests are intended more for actual testing and not so much // as examples. - @safe unittest - { + @system unittest + { + import std.stdio : stderr; + import std.range : iota; + import std.algorithm.iteration : filter, reduce; + import std.math.algebraic : sqrt; + import std.math.operations : isClose; + import std.math.traits : isNaN; + import std.conv : text; + foreach (attempt; 0 .. 10) foreach (poolSize; [0, 4]) { @@ -4559,7 +4724,7 @@ version (parallelismStressTest) foreach (j, elem; row) { real shouldBe = sqrt( cast(real) i * j); - assert(approxEqual(shouldBe, elem)); + assert(isClose(shouldBe, elem)); sqrtMatrix[i][j] = shouldBe; } } @@ -4586,10 +4751,11 @@ version (parallelismStressTest) ) ); - assert(approxEqual(sumSqrt, 4.437e8)); + assert(isClose(sumSqrt, 4.437e8, 1e-2)); stderr.writeln("Done sum of square roots."); // Test whether tasks work with function pointers. + /+ // This part is buggy and needs to be fixed... auto nanTask = task(&isNaN, 1.0L); poolInstance.put(nanTask); assert(nanTask.spinForce == false); @@ -4616,6 +4782,7 @@ version (parallelismStressTest) uselessTask.workForce(); } } + +/ // Test the case of non-random access + ref returns. int[] nums = [1,2,3,4,5]; @@ -4650,9 +4817,9 @@ version (parallelismStressTest) } } -version (unittest) +@system unittest { - struct __S_12733 + static struct __S_12733 { invariant() { assert(checksum == 1_234_567_890); } this(ulong u){n = u;} @@ -4662,10 +4829,6 @@ version (unittest) } 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); diff --git a/libphobos/src/std/path.d b/libphobos/src/std/path.d index 4a435ef..2d64684 100644 --- a/libphobos/src/std/path.d +++ b/libphobos/src/std/path.d @@ -1,15 +1,15 @@ // Written in the D programming language. -/** This module is used to manipulate _path strings. +/** 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 + 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. + path actually exists in the file system. To differentiate between these cases, use $(REF isDir, std,file) and $(REF exists, std,file). @@ -21,9 +21,9 @@ 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 + 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. @@ -91,29 +91,37 @@ $(TR $(TD Other) $(TD License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) Source: - $(PHOBOSSRC std/_path.d) + $(PHOBOSSRC std/path.d) */ module std.path; -// FIXME -import std.file; //: getcwd; +import std.file : getcwd; static import std.meta; import std.range.primitives; import std.traits; -version (unittest) +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (StdUnittest) { private: struct TestAliasedString { - string get() @safe @nogc pure nothrow { return _s; } + string get() @safe @nogc pure nothrow return scope { return _s; } alias get this; @disable this(this); string _s; } - bool testAliasedString(alias func, Args...)(string s, Args args) + bool testAliasedString(alias func, Args...)(scope string s, scope Args args) { return func(TestAliasedString(s), args) == func(s, args); } @@ -151,6 +159,21 @@ bool isDirSeparator(dchar c) @safe pure nothrow @nogc return false; } +/// +@safe pure nothrow @nogc unittest +{ + version (Windows) + { + assert( '/'.isDirSeparator); + assert( '\\'.isDirSeparator); + } + else + { + assert( '/'.isDirSeparator); + assert(!'\\'.isDirSeparator); + } +} + /* Determines whether the given character is a drive separator. @@ -201,7 +224,7 @@ version (Windows) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) in { assert(isUNC(path)); } - body + do { ptrdiff_t i = 3; while (i < path.length && !isDirSeparator(path[i])) ++i; @@ -257,7 +280,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) || } } -@system unittest +@safe unittest { import std.array; import std.utf : byDchar; @@ -286,7 +309,7 @@ if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || } } -@system unittest +@safe unittest { import std.array; import std.utf : byDchar; @@ -304,7 +327,7 @@ if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || return ltrimDirSeparators(rtrimDirSeparators(path)); } -@system unittest +@safe unittest { import std.array; import std.utf : byDchar; @@ -315,10 +338,7 @@ if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc"); } - - - -/** This $(D enum) is used as a template argument to functions which +/** This `enum` is used as a template argument to functions which compare file names, and determines whether the comparison is case sensitive or not. */ @@ -331,18 +351,28 @@ enum CaseSensitive : bool 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.). + That is, `no` on Windows and Mac OS X, and `yes` on all + POSIX systems except Darwin (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); +/// +@safe unittest +{ + assert(baseName!(CaseSensitive.no)("dir/file.EXT", ".ext") == "file"); + assert(baseName!(CaseSensitive.yes)("dir/file.EXT", ".ext") != "file"); + version (Posix) + assert(relativePath!(CaseSensitive.no)("/FOO/bar", "/foo/baz") == "../bar"); + else + assert(relativePath!(CaseSensitive.no)(`c:\FOO\bar`, `c:\foo\baz`) == `..\bar`); +} +version (Windows) private enum osDefaultCaseSensitivity = false; +else version (Darwin) private enum osDefaultCaseSensitivity = false; +else version (Posix) private enum osDefaultCaseSensitivity = true; +else static assert(0); /** Params: @@ -353,27 +383,12 @@ else static assert(0); 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 + If `suffix` is specified, it will be compared to `path` + 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. - 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. @@ -391,42 +406,22 @@ else static assert(0); the POSIX requirements for the 'basename' shell utility) (with suitable adaptations for Windows paths). */ -auto baseName(R)(R path) +auto baseName(R)(return scope R path) if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R) { return _baseName(path); } /// ditto -auto baseName(C)(C[] path) +auto baseName(C)(return scope 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) + (return scope inout(C)[] path, in C1[] suffix) @safe pure //TODO: nothrow (because of filenameCmp()) if (isSomeChar!C && isSomeChar!C1) { @@ -439,6 +434,22 @@ if (isSomeChar!C && isSomeChar!C1) else return p; } +/// +@safe unittest +{ + 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"); + } +} + @safe unittest { assert(baseName("").empty); @@ -511,14 +522,35 @@ if (isSomeChar!C && isSomeChar!C1) assert(sa.baseName == "test"); } -/** Returns the directory part of a path. On Windows, this - includes the drive letter if present. +private R _baseName(R)(return scope 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]; +} + +/** Returns the parent directory of `path`. On Windows, this + includes the drive letter if present. If `path` is a relative path and + the parent directory is the current working directory, returns `"."`. Params: path = A path name. Returns: - A slice of $(D path) or ".". + A slice of `path` or `"."`. Standards: This function complies with @@ -526,67 +558,19 @@ if (isSomeChar!C && isSomeChar!C1) the POSIX requirements for the 'dirname' shell utility) (with suitable adaptations for Windows paths). */ -auto dirName(R)(R path) +auto dirName(R)(return scope R path) if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R) { return _dirName(path); } /// ditto -auto dirName(C)(C[] path) +auto dirName(C)(return scope 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 { @@ -636,7 +620,7 @@ if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementT assert(sa.dirName == "file/path/to"); } -@system unittest +@safe unittest { static assert(dirName("dir/file") == "dir"); @@ -678,51 +662,72 @@ if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementT //static assert(dirName("dir/file".byChar).array == "dir"); } +private auto _dirName(R)(return scope R path) +{ + 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])); +} -/** Returns the root directory of the specified path, or $(D null) if the +/** Returns the root directory of the specified path, or `null` if the path is not rooted. Params: path = A path name. Returns: - A slice of $(D path). + A slice of `path`. */ auto rootName(R)(R path) -if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) || - isNarrowString!R) && - !isConvertibleToString!R) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!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"); + return _rootName(path); +} - 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]; +/// ditto +auto rootName(C)(C[] path) +if (isSomeChar!C) +{ + return _rootName(path); } /// @@ -745,6 +750,12 @@ Lnull: @safe unittest { assert(testAliasedString!rootName("/foo/bar")); + + enum S : string { a = "/foo/bar" } + assert(S.a.rootName == "/"); + + char[S.a.length] sa = S.a[]; + assert(sa.rootName == "/"); } @safe unittest @@ -766,12 +777,37 @@ Lnull: } } -auto rootName(R)(R path) -if (isConvertibleToString!R) +private auto _rootName(R)(R path) { - return rootName!(StringTypeOf!R)(path); -} + 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]; +} /** Get the drive portion of a path. @@ -780,28 +816,23 @@ if (isConvertibleToString!R) path = string or range of characters Returns: - A slice of $(D _path) that is the drive, or an empty range if the drive + A slice of `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]; +auto driveName(R)(R path) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R) +{ + return _driveName(path); +} + +/// ditto +auto driveName(C)(C[] path) +if (isSomeChar!C) +{ + return _driveName(path); } /// @@ -823,15 +854,20 @@ if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(Element } } -auto driveName(R)(auto ref R path) -if (isConvertibleToString!R) -{ - return driveName!(StringTypeOf!R)(path); -} - @safe unittest { - assert(testAliasedString!driveName(`d:\file`)); + assert(testAliasedString!driveName("d:/file")); + + version (Posix) + immutable result = ""; + else version (Windows) + immutable result = "d:"; + + enum S : string { a = "d:/file" } + assert(S.a.driveName == result); + + char[S.a.length] sa = S.a[]; + assert(sa.driveName == result); } @safe unittest @@ -854,6 +890,20 @@ if (isConvertibleToString!R) } } +private auto _driveName(R)(R path) +{ + 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]; +} /** Strips the drive from a Windows path. On POSIX, the path is returned unaltered. @@ -864,16 +914,16 @@ if (isConvertibleToString!R) 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) +if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!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; + return _stripDrive(path); +} + +/// ditto +auto stripDrive(C)(C[] path) +if (isSomeChar!C) +{ + return _stripDrive(path); } /// @@ -886,15 +936,20 @@ if ((isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || } } -auto stripDrive(R)(auto ref R path) -if (isConvertibleToString!R) -{ - return stripDrive!(StringTypeOf!R)(path); -} - @safe unittest { - assert(testAliasedString!stripDrive(`d:\dir\file`)); + assert(testAliasedString!stripDrive("d:/dir/file")); + + version (Posix) + immutable result = "d:/dir/file"; + else version (Windows) + immutable result = "/dir/file"; + + enum S : string { a = "d:/dir/file" } + assert(S.a.stripDrive == result); + + char[S.a.length] sa = S.a[]; + assert(sa.stripDrive == result); } @safe unittest @@ -921,6 +976,16 @@ if (isConvertibleToString!R) } } +private auto _stripDrive(R)(R path) +{ + 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; +} + /* Helper function that returns the position of the filename/extension separator dot in path. @@ -983,7 +1048,7 @@ if (isRandomAccessRange!R && hasLength!R && isSomeChar!(ElementType!R) || 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. + If there is no _extension, `null` is returned. */ auto extension(R)(R path) if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || @@ -1038,12 +1103,16 @@ if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || 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) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R) +{ + return _stripExtension(path); +} + +/// Ditto +auto stripExtension(C)(C[] path) +if (isSomeChar!C) { - auto i = extSeparatorPos(path); - return (i == -1) ? path : path[0 .. i]; + return _stripExtension(path); } /// @@ -1058,15 +1127,15 @@ if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(Element 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")); + + enum S : string { a = "foo.bar" } + assert(S.a.stripExtension == "foo"); + + char[S.a.length] sa = S.a[]; + assert(sa.stripExtension == "foo"); } @safe unittest @@ -1082,12 +1151,17 @@ if (isConvertibleToString!R) assert(stripExtension("file.ext1.ext2"d.byDchar).array == "file.ext1"); } +private auto _stripExtension(R)(R path) +{ + immutable i = extSeparatorPos(path); + return i == -1 ? path : path[0 .. i]; +} /** 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. + in `ext` is optional. If the extension is empty, this function is equivalent to $(LREF stripExtension). @@ -1100,14 +1174,14 @@ if (isConvertibleToString!R) 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). + Returns: A string containing the path given by `path`, but where + the extension has been set to `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)) +immutable(C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext) +if (isSomeChar!C1 && !is(C1 == immutable) && is(immutable C1 == immutable C2)) { try { @@ -1122,7 +1196,7 @@ if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2)) ///ditto immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext) -if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) +if (isSomeChar!C1 && is(immutable C1 == immutable C2)) { if (ext.length == 0) return stripExtension(path); @@ -1164,7 +1238,7 @@ if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) static assert(setExtension("file"w.dup, "ext"w) == "file.ext"); static assert(setExtension("file.old"d.dup, "new"d) == "file.new"); - // Issue 10601 + // https://issues.dlang.org/show_bug.cgi?id=10601 assert(setExtension("file", "") == "file"); assert(setExtension("file.ext", "") == "file"); } @@ -1176,25 +1250,23 @@ if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) * 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. + * Range with `path`'s extension (if any) replaced with `ext`. + * The element encoding type of the returned range will be the same as `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) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && + !isSomeString!R && isSomeChar!C) { - import std.range : only, chain; - import std.utf : byUTF; + return _withExtension(path, ext); +} - 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); +/// Ditto +auto withExtension(C1, C2)(C1[] path, C2[] ext) +if (isSomeChar!C1 && isSomeChar!C2) +{ + return _withExtension(path, ext); } /// @@ -1211,22 +1283,36 @@ if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(Element assert(withExtension("file.ext"w.byWchar, ".").array == "file."w); } -auto withExtension(R, C)(auto ref R path, C[] ext) -if (isConvertibleToString!R) +@safe unittest { - return withExtension!(StringTypeOf!R)(path, ext); + import std.algorithm.comparison : equal; + + assert(testAliasedString!withExtension("file", "ext")); + + enum S : string { a = "foo.bar" } + assert(equal(S.a.withExtension(".txt"), "foo.txt")); + + char[S.a.length] sa = S.a[]; + assert(equal(sa.withExtension(".txt"), "foo.txt")); } -@safe unittest +private auto _withExtension(R, C)(R path, C[] ext) { - assert(testAliasedString!withExtension("file", "ext")); + 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); } /** 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) + Returns: The path given by `path`, with the extension given by `ext` appended if the path doesn't already have one. Including the dot in the extension is optional. @@ -1234,8 +1320,8 @@ if (isConvertibleToString!R) 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)) +immutable(C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext) +if (isSomeChar!C1 && is(immutable C1 == immutable C2)) { import std.conv : to; return withDefaultExtension(path, ext).to!(typeof(return)); @@ -1265,7 +1351,7 @@ if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) /******************************** - * Set the extension of $(D path) to $(D ext) if $(D path) doesn't have one. + * Set the extension of `path` to `ext` if `path` doesn't have one. * * Params: * path = filespec as string or range @@ -1274,29 +1360,17 @@ if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) * 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) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && + !isSomeString!R && isSomeChar!C) { - import std.range : only, chain; - import std.utf : byUTF; + return _withDefaultExtension(path, ext); +} - 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); +/// Ditto +auto withDefaultExtension(C1, C2)(C1[] path, C2[] ext) +if (isSomeChar!C1 && isSomeChar!C2) +{ + return _withDefaultExtension(path, ext); } /// @@ -1315,15 +1389,39 @@ if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(Element assert(withDefaultExtension("file".byChar, "").array == "file."); } -auto withDefaultExtension(R, C)(auto ref R path, C[] ext) -if (isConvertibleToString!R) +@safe unittest { - return withDefaultExtension!(StringTypeOf!R, C)(path, ext); + import std.algorithm.comparison : equal; + + assert(testAliasedString!withDefaultExtension("file", "ext")); + + enum S : string { a = "foo" } + assert(equal(S.a.withDefaultExtension(".txt"), "foo.txt")); + + char[S.a.length] sa = S.a[]; + assert(equal(sa.withDefaultExtension(".txt"), "foo.txt")); } -@safe unittest +private auto _withDefaultExtension(R, C)(R path, C[] ext) { - assert(testAliasedString!withDefaultExtension("file", "ext")); + import std.range : only, chain; + import std.utf : byUTF; + + alias CR = Unqual!(ElementEncodingType!R); + auto dot = only(CR('.')); + immutable 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); } /** Combines one or more path segments. @@ -1341,15 +1439,16 @@ if (isConvertibleToString!R) 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 + allocation, as is the range version if `paths` is a forward range. Params: - segments = An input range of segments to assemble the path from. + segments = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of segments to assemble the path from. Returns: The assembled path. */ immutable(ElementEncodingType!(ElementType!Range))[] - buildPath(Range)(Range segments) + buildPath(Range)(scope Range segments) if (isInputRange!Range && !isInfinite!Range && isSomeString!(ElementType!Range)) { if (segments.empty) return null; @@ -1638,7 +1737,7 @@ if (Ranges.length >= 2 && /** Performs the same task as $(LREF buildPath), while at the same time resolving current/parent directory - symbols ($(D ".") and $(D "..")) and removing superfluous + symbols (`"."` and `".."`) and removing superfluous directory separators. It will return "." if the path leads to the starting directory. On Windows, slashes are replaced with backslashes. @@ -1656,21 +1755,23 @@ if (Ranges.length >= 2 && Returns: The assembled path. */ immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...) - @trusted pure nothrow + @safe pure nothrow if (isSomeChar!C) { import std.array : array; + import std.exception : assumeUnique; - const(C)[] result; + const(C)[] chained; foreach (path; paths) { - if (result) - result = chainPath(result, path).array; + if (chained) + chained = chainPath(chained, path).array; else - result = path; + chained = path; } - result = asNormalizedPath(result).array; - return cast(typeof(return)) result; + auto result = asNormalizedPath(chained); + // .array returns a copy, so it is unique + return () @trusted { return assumeUnique(result.array); } (); } /// @@ -1790,7 +1891,7 @@ if (isSomeChar!C) /** Normalize a path by resolving current/parent directory - symbols ($(D ".") and $(D "..")) and removing superfluous + symbols (`"."` and `".."`) and removing superfluous directory separators. It will return "." if the path leads to the starting directory. On Windows, slashes are replaced with backslashes. @@ -1803,13 +1904,13 @@ if (isSomeChar!C) Use $(LREF buildNormalizedPath) to allocate memory and return a string. Params: - path = string or random access range representing the _path to normalize + path = string or random access range representing the path to normalize Returns: normalized path as a forward range */ -auto asNormalizedPath(R)(R path) +auto asNormalizedPath(R)(return scope R path) if (isSomeChar!(ElementEncodingType!R) && (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) && !isConvertibleToString!R) @@ -1976,7 +2077,7 @@ if (isSomeChar!(ElementEncodingType!R) && } } -auto asNormalizedPath(R)(auto ref R path) +auto asNormalizedPath(R)(return scope auto ref R path) if (isConvertibleToString!R) { return asNormalizedPath!(StringTypeOf!R)(path); @@ -2460,35 +2561,17 @@ if (isConvertibleToString!R) /** Determines whether a path starts at a root directory. - Params: path = A path name. - Returns: 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) || @@ -2499,6 +2582,27 @@ if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || else version (Windows) return isAbsolute!(BaseOf!R)(path); } +/// +@safe unittest +{ + version (Posix) + { + 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")); + } +} @safe unittest { @@ -2524,9 +2628,6 @@ if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || assert(!isRooted(DirEntry("foo"))); } - - - /** Determines whether a path is absolute or not. Params: path = A path name. @@ -2535,7 +2636,7 @@ if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || Example: On POSIX, an absolute path starts at the root directory. - (In fact, $(D _isAbsolute) is just an alias for $(LREF isRooted).) + (In fact, `_isAbsolute` is just an alias for $(LREF isRooted).) --- version (Posix) { @@ -2548,7 +2649,7 @@ if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || 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 + where `d` is the drive letter. Alternatively, it may be a network path, i.e. a path starting with a double (back)slash. --- version (Windows) @@ -2620,14 +2721,14 @@ else version (Posix) -/** Transforms $(D path) into an absolute _path. +/** Transforms `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 + $(LI If `path` is empty, return `null`.) + $(LI If `path` is already absolute, return it.) + $(LI Otherwise, append `path` to `base` and return + the result. If `base` is not specified, the current working directory is used.) ) The function allocates memory if and only if it gets to the third stage @@ -2641,7 +2742,7 @@ else version (Posix) string of transformed path Throws: - $(D Exception) if the specified _base directory is not absolute. + `Exception` if the specified _base directory is not absolute. See_Also: $(LREF asAbsolutePath) which does not allocate @@ -2693,13 +2794,13 @@ string absolutePath(string path, lazy string base = getcwd()) assertThrown(absolutePath("bar", "foo")); } -/** Transforms $(D path) into an absolute _path. +/** Transforms `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, + $(LI If `path` is empty, return `null`.) + $(LI If `path` is already absolute, return it.) + $(LI Otherwise, append `path` to the current working directory, which allocates memory.) ) @@ -2751,27 +2852,27 @@ if (isConvertibleToString!R) assert(testAliasedString!asAbsolutePath(null)); } -/** Translates $(D path) into a relative _path. +/** Translates `path` into a relative path. - The returned _path is relative to $(D base), which is by default + The returned path is relative to `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 + `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 $(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 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 $(D `../`) or $(D `..\`) as necessary to reach the common root from base path.) - $(LI Append the remaining segments of $(D path) to the string + $(LI Append the remaining segments of `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 + 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. @@ -2789,7 +2890,7 @@ if (isConvertibleToString!R) $(LREF asRelativePath) which does not allocate memory Throws: - $(D Exception) if the specified _base directory is not absolute. + `Exception` if the specified _base directory is not absolute. */ string relativePath(CaseSensitive cs = CaseSensitive.osDefault) (string path, lazy string base = getcwd()) @@ -2805,7 +2906,7 @@ string relativePath(CaseSensitive cs = CaseSensitive.osDefault) } /// -@system unittest +@safe unittest { assert(relativePath("foo") == "foo"); @@ -2828,7 +2929,7 @@ string relativePath(CaseSensitive cs = CaseSensitive.osDefault) } } -@system unittest +@safe unittest { import std.exception; assert(relativePath("foo") == "foo"); @@ -2847,11 +2948,11 @@ string relativePath(CaseSensitive cs = CaseSensitive.osDefault) else static assert(0); } -/** Transforms `path` into a _path relative to `base`. +/** Transforms `path` into a path relative to `base`. - The returned _path is relative to `base`, which is usually + 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 + `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 `'.'`. @@ -2872,13 +2973,13 @@ string relativePath(CaseSensitive cs = CaseSensitive.osDefault) $(LREF filenameCmp) documentation for details. Params: - path = _path to transform + 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 + a random access range of the transformed path See_Also: $(LREF relativePath) @@ -2936,7 +3037,7 @@ if ((isNarrowString!R1 || } /// -@system unittest +@safe unittest { import std.array; version (Posix) @@ -2962,6 +3063,19 @@ if ((isNarrowString!R1 || static assert(0); } +@safe unittest +{ + version (Posix) + { + assert(isBidirectionalRange!(typeof(asRelativePath("foo/bar/baz", "/foo/woo/wee")))); + } + + version (Windows) + { + assert(isBidirectionalRange!(typeof(asRelativePath(`c:\foo\bar`, `c:\foo\baz`)))); + } +} + auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2) (auto ref R1 path, auto ref R2 base) if (isConvertibleToString!R1 || isConvertibleToString!R2) @@ -2971,7 +3085,7 @@ if (isConvertibleToString!R1 || isConvertibleToString!R2) return asRelativePath!(cs, Types)(path, base); } -@system unittest +@safe unittest { import std.array; version (Posix) @@ -2985,7 +3099,7 @@ if (isConvertibleToString!R1 || isConvertibleToString!R2) assert(asRelativePath("foo"d.byDchar, TestAliasedString("bar")).array == "foo"); } -@system unittest +@safe unittest { import std.array, std.utf : bCU=byCodeUnit; version (Posix) @@ -3005,8 +3119,8 @@ if (isConvertibleToString!R1 || isConvertibleToString!R2) /** 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). + comparison. This is controlled through the `cs` template parameter + which, if not specified, is given by $(LREF CaseSensitive)`.osDefault`. On Windows, the backslash and slash characters ($(D `\`) and $(D `/`)) are considered equal. @@ -3018,7 +3132,7 @@ if (isConvertibleToString!R1 || isConvertibleToString!R2) Returns: $(D < 0) if $(D a < b), - $(D 0) if $(D a == b), and + `0` if $(D a == b), and $(D > 0) if $(D a > b). */ int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b) @@ -3078,8 +3192,8 @@ int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b /** Compares file names and returns - Individual characters are compared using $(D filenameCharCmp!cs), - where $(D cs) is an optional template parameter determining whether + Individual characters are compared using `filenameCharCmp!cs`, + where `cs` is an optional template parameter determining whether the comparison is case sensitive or not. Treatment of invalid UTF encodings is implementation defined. @@ -3091,7 +3205,7 @@ int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b Returns: $(D < 0) if $(D filename1 < filename2), - $(D 0) if $(D filename1 == filename2) and + `0` if $(D filename1 == filename2) and $(D > 0) if $(D filename1 > filename2). See_Also: @@ -3204,22 +3318,22 @@ if (isConvertibleToString!Range1 || isConvertibleToString!Range2) $(I meta-characters)) and can't be escaped. These are: $(BOOKTABLE, - $(TR $(TD $(D *)) + $(TR $(TD `*`) $(TD Matches 0 or more instances of any character.)) - $(TR $(TD $(D ?)) + $(TR $(TD `?`) $(TD Matches exactly one instance of any character.)) - $(TR $(TD $(D [)$(I chars)$(D ])) + $(TR $(TD `[`$(I chars)`]`) $(TD Matches one instance of any character that appears between the brackets.)) - $(TR $(TD $(D [!)$(I chars)$(D ])) + $(TR $(TD `[!`$(I chars)`]`) $(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 })) + $(TR $(TD `{`$(I string1)`,`$(I string2)`,`…`}`) $(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 + Individual characters are compared using `filenameCharCmp!cs`, + where `cs` is an optional template parameter determining whether the comparison is case sensitive or not. See the $(LREF filenameCharCmp) documentation for details. @@ -3233,7 +3347,7 @@ if (isConvertibleToString!Range1 || isConvertibleToString!Range2) pattern = The glob pattern Returns: - $(D true) if pattern matches path, $(D false) otherwise. + `true` if pattern matches path, `false` otherwise. See_also: $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming)) @@ -3243,7 +3357,7 @@ bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range) @safe pure nothrow if (isForwardRange!Range && !isInfinite!Range && isSomeChar!(ElementEncodingType!Range) && !isConvertibleToString!Range && - isSomeChar!C && is(Unqual!C == Unqual!(ElementEncodingType!Range))) + isSomeChar!C && is(immutable C == immutable ElementEncodingType!Range)) in { // Verify that pattern[] is valid @@ -3251,7 +3365,7 @@ in assert(balancedParens(pattern, '[', ']', 0)); assert(balancedParens(pattern, '{', '}', 0)); } -body +do { alias RC = Unqual!(ElementEncodingType!Range); @@ -3483,31 +3597,31 @@ if (isConvertibleToString!Range) /** 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 + The maximum length of `filename` is given by the constant + `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.) + `filename` is a string of `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 + $(LI `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 '.')).) + $(LI `filename` must not contain any of the following $(I reserved + characters): `<>:"/\|?*`) + $(LI `filename` may not end with a space ($(D ' ')) or a period + (`'.'`).) ) - On POSIX, $(D filename) may not contain a forward slash ($(D '/')) or - the null character ($(D '\0')). + On POSIX, `filename` may not contain a forward slash (`'/'`) or + the null character (`'\0'`). Params: filename = string to check Returns: - $(D true) if and only if $(D filename) is not + `true` if and only if `filename` is not empty, not too long, and does not contain invalid characters. */ @@ -3590,7 +3704,7 @@ unittest else static assert(0); import std.meta : AliasSeq; - foreach (T; AliasSeq!(char[], const(char)[], string, wchar[], + static foreach (T; AliasSeq!(char[], const(char)[], string, wchar[], const(wchar)[], wstring, dchar[], const(dchar)[], dstring)) { foreach (fn; valid) @@ -3623,28 +3737,28 @@ unittest -/** Checks whether $(D path) is a valid _path. +/** Checks whether `path` is a valid path. - Generally, this function checks that $(D path) is not empty, and that + Generally, this function checks that `path` is not empty, and that each component of the path either satisfies $(LREF isValidFilename) - or is equal to $(D ".") or $(D ".."). + or is equal to `"."` or `".."`. - $(B It does $(I not) check whether the _path points to an existing file + $(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 ':')), + $(LI If the second character of `path` is a colon (`':'`), 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)\...`) + $(LI If `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 + $(LI If `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 + $(LI If `path` starts with $(D `\\.\`) (Win32 device namespace) + this function returns `false`; such paths are beyond the scope of this module.) ) @@ -3652,7 +3766,7 @@ unittest path = string or Range of characters to check Returns: - true if $(D path) is a valid _path. + true if `path` is a valid path. */ bool isValidPath(Range)(Range path) if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) || @@ -3807,16 +3921,16 @@ if (isConvertibleToString!Range) 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, + environment variable `HOME`. The second way is putting + a username after the tilde (i.e. `~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 + (i.e. `/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 + Note that using the `~user` syntax may give different + values from just `~` 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 @@ -3831,9 +3945,9 @@ if (isConvertibleToString!Range) inputPath = The path name to expand. Returns: - $(D inputPath) with the tilde expanded, or just $(D inputPath) + `inputPath` with the tilde expanded, or just `inputPath` if it could not be expanded. - For Windows, $(D expandTilde) merely returns its argument $(D inputPath). + For Windows, `expandTilde` merely returns its argument `inputPath`. Example: ----- @@ -3845,7 +3959,7 @@ if (isConvertibleToString!Range) } ----- */ -string expandTilde(string inputPath) nothrow +string expandTilde(string inputPath) @safe nothrow { version (Posix) { @@ -3859,9 +3973,10 @@ string expandTilde(string inputPath) nothrow 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 + static string combineCPathWithDPath(char* c_path, string path, size_t char_pos) @trusted nothrow { import core.stdc.string : strlen; + import std.exception : assumeUnique; assert(c_path != null); assert(path.length > 0); @@ -3870,24 +3985,33 @@ string expandTilde(string inputPath) nothrow // 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--; + const cPathEndsWithDirSep = end && isDirSeparator(c_path[end - 1]); - // (this is the only GC allocation done in expandTilde()) string cp; if (char_pos < path.length) + { + // Remove trailing path separator, if any (with special care for root /) + if (cPathEndsWithDirSep && (end > 1 || isDirSeparator(path[char_pos]))) + end--; + // Append something from path - cp = cast(string)(c_path[0 .. end] ~ path[char_pos .. $]); + cp = assumeUnique(c_path[0 .. end] ~ path[char_pos .. $]); + } else + { + // Remove trailing path separator, if any (except for root /) + if (cPathEndsWithDirSep && end > 1) + end--; + // 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 + static string expandFromEnvironment(string path) @safe nothrow { import core.stdc.stdlib : getenv; @@ -3895,7 +4019,7 @@ string expandTilde(string inputPath) nothrow assert(path[0] == '~'); // Get HOME and use that to replace the tilde. - auto home = getenv("HOME"); + auto home = () @trusted { return getenv("HOME"); } (); if (home == null) return path; @@ -3903,7 +4027,7 @@ string expandTilde(string inputPath) nothrow } // Replaces the tilde from path with the path from the user database. - static string expandFromDatabase(string path) nothrow + static string expandFromDatabase(string path) @safe nothrow { // bionic doesn't really support this, as getpwnam_r // isn't provided and getpwnam is basically just a stub @@ -3923,10 +4047,7 @@ string expandTilde(string inputPath) nothrow 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); + char[] username = new char[username_len * char.sizeof]; if (last_char == -1) { @@ -3942,28 +4063,31 @@ string expandTilde(string inputPath) nothrow assert(last_char > 1); // Reserve C memory for the getpwnam_r() function. - version (unittest) + version (StdUnittest) uint extra_memory_size = 2; else uint extra_memory_size = 5 * 1024; - char* extra_memory; - scope(exit) free(extra_memory); + char[] extra_memory; passwd result; while (1) { - extra_memory = cast(char*) realloc(extra_memory, extra_memory_size * char.sizeof); - if (extra_memory == null) - onOutOfMemoryError(); + extra_memory.length += extra_memory_size; // Obtain info from database. passwd *verify; errno = 0; - if (getpwnam_r(username, &result, extra_memory, extra_memory_size, - &verify) == 0) + auto passResult = () @trusted { return getpwnam_r( + &username[0], + &result, + &extra_memory[0], + extra_memory.length, + &verify + ); } (); + if (passResult == 0) { // Succeeded if verify points at result - if (verify == &result) + if (verify == () @trusted { return &result; } ()) // username is found path = combineCPathWithDPath(result.pw_dir, path, last_char); break; @@ -4004,12 +4128,32 @@ string expandTilde(string inputPath) nothrow } } +/// +@system unittest +{ + version (Posix) + { + import std.process : environment; + + auto oldHome = environment["HOME"]; + scope(exit) environment["HOME"] = oldHome; + + environment["HOME"] = "dmd/test"; + assert(expandTilde("~/") == "dmd/test/"); + assert(expandTilde("~") == "dmd/test"); + } +} -version (unittest) import std.process : environment; @system unittest { version (Posix) { + static if (__traits(compiles, { import std.process : executeShell; })) + import std.process : executeShell; + + import std.process : environment; + import std.string : strip; + // Retrieve the current home variable. auto oldHome = environment.get("HOME"); @@ -4028,30 +4172,33 @@ version (unittest) import std.process : environment; assert(expandTilde("~/") == "dmd/test/"); assert(expandTilde("~") == "dmd/test"); + // The same, but with a variable set to root. + environment["HOME"] = "/"; + assert(expandTilde("~/") == "/"); + assert(expandTilde("~") == "/"); + // 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) + static if (is(typeof(executeShell))) { - 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/")); + immutable tildeUser = "~" ~ environment.get("USER"); + immutable path = executeShell("echo " ~ tildeUser).output.strip(); + immutable expTildeUser = expandTilde(tildeUser); + assert(expTildeUser == path, expTildeUser); + immutable expTildeUserSlash = expandTilde(tildeUser ~ "/"); + immutable pathSlash = path[$-1] == '/' ? path : path ~ "/"; + assert(expTildeUserSlash == pathSlash, expTildeUserSlash); } + assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey"); } } -version (unittest) +version (StdUnittest) { +private: /* Define a mock RandomAccessRange to use for unittesting. */ @@ -4064,7 +4211,7 @@ version (unittest) @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; } + alias opDollar = length; C opIndex(size_t i) { return array[i]; } } void popFront() { array = array[1 .. $]; } @@ -4078,11 +4225,6 @@ version (unittest) C[] array; } - static assert( isRandomAccessRange!(MockRange!(const(char))) ); -} - -version (unittest) -{ /* Define a mock BidirectionalRange to use for unittesting. */ @@ -4103,6 +4245,11 @@ version (unittest) const(C)[] array; } +} + +@safe unittest +{ + static assert( isRandomAccessRange!(MockRange!(const(char))) ); static assert( isBidirectionalRange!(MockBiRange!(const(char))) ); } diff --git a/libphobos/src/std/process.d b/libphobos/src/std/process.d index 1e977aa..7ec8fa1 100644 --- a/libphobos/src/std/process.d +++ b/libphobos/src/std/process.d @@ -2,51 +2,51 @@ /** Functions for starting and interacting with other processes, and for -working with the current _process' execution environment. +working with the current process' execution environment. Process_handling: $(UL $(LI - $(LREF spawnProcess) spawns a new _process, optionally assigning it an + $(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 + 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).) + spawn processes are built around `spawnProcess`.) $(LI - $(LREF wait) makes the parent _process wait for a child _process to + $(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. + 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.) + documentation for examples. $(LREF tryWait) is similar to `wait`, + but does not block if the process has not yet terminated.) $(LI - $(LREF pipeProcess) also spawns a child _process which runs + $(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.) + This function corresponds roughly to C's `popen` function.) $(LI - $(LREF execute) starts a new _process and waits for it + $(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 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, + `spawnProcess`, `pipeProcess` and `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.) + `executeShell` corresponds roughly to C's `system` function.) $(LI - $(LREF kill) attempts to terminate a running _process.) + $(LREF kill) attempts to terminate a running process.) ) -The following table compactly summarises the different _process creation +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) + $(TR $(TD Low-level process creation) $(TD $(LREF spawnProcess)) $(TD $(LREF spawnShell))) $(TR $(TD Automatic input/output redirection using pipes) @@ -62,7 +62,7 @@ $(UL $(LI $(LREF pipe) is used to create unidirectional pipes.) $(LI - $(LREF environment) is an interface through which the current _process' + $(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 @@ -78,13 +78,19 @@ Copyright: License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Source: - $(PHOBOSSRC std/_process.d) + $(PHOBOSSRC std/process.d) Macros: - OBJECTREF=$(D $(LINK2 object.html#$0,$0)) - LREF=$(D $(LINK2 #.$0,$0)) + OBJECTREF=$(REF1 $0, object) + +Note: +Most of the functionality in this module is not available on iOS, tvOS +and watchOS. The only functions available on those platforms are: +$(LREF environment), $(LREF thisProcessID) and $(LREF thisThreadID). */ module std.process; +import core.thread : ThreadID; + version (Posix) { import core.sys.posix.sys.wait; @@ -93,7 +99,8 @@ version (Posix) version (Windows) { import core.stdc.stdio; - import core.sys.windows.windows; + import core.sys.windows.winbase; + import core.sys.windows.winnt; import std.utf; import std.windows.syserror; } @@ -101,7 +108,25 @@ version (Windows) import std.internal.cstring; import std.range.primitives; import std.stdio; +import std.traits : isSomeChar; +version (OSX) + version = Darwin; +else version (iOS) +{ + version = Darwin; + version = iOSDerived; +} +else version (TVOS) +{ + version = Darwin; + version = iOSDerived; +} +else version (WatchOS) +{ + version = Darwin; + version = iOSDerived; +} // When the DMC runtime is used, we have to use some custom functions // to convert between Windows file handles and FILE*s. @@ -129,7 +154,7 @@ private // POSIX API declarations. version (Posix) { - version (OSX) + version (Darwin) { extern(C) char*** _NSGetEnviron() nothrow; const(char**) getEnvironPtr() @trusted @@ -149,3593 +174,3897 @@ private @system unittest { + import core.thread : Thread; new Thread({assert(getEnvironPtr !is null);}).start(); } } } // private - // ============================================================================= -// Functions and classes for process management. +// Environment variable manipulation. // ============================================================================= - /** -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. +Manipulates _environment variables using an associative-array-like +interface. -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. +This class contains only static methods, and cannot be instantiated. +See below for examples of use. */ -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 +abstract final class environment { - noerror, - exec, - chdir, - getrlimit, - doubleFork, -} + static import core.sys.posix.stdlib; + import core.stdc.errno : errno, EINVAL; -/* -Implementation of spawnProcess() for POSIX. +static: + /** + Retrieves the value of the environment variable with the given `name`. + --- + auto path = environment["PATH"]; + --- -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; + 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). - if (args.empty) throw new RangeError(); - const(char)[] name = args[0]; - if (any!isDirSeparator(name)) + See_also: + $(LREF environment.get), which doesn't throw on failure. + */ + string opIndex(scope const(char)[] name) @safe { - if (!isExecutable(name)) - throw new ProcessException(text("Not an executable file: ", name)); + import std.exception : enforce; + return get(name, null).enforce("Environment variable not found: "~name); } - else + + /** + Retrieves the value of the environment variable with the given `name`, + or a default value if the variable doesn't exist. + + Unlike $(LREF environment.opIndex), this function never throws on Posix. + --- + 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) { - name = searchPathFor(name); - if (name is null) - throw new ProcessException(text("Executable file not found: ", args[0])); + // 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. } + --- + Params: + name = name of the environment variable to retrieve + defaultValue = default value to return if the environment variable doesn't exist. - // 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)); + Returns: + the value of the environment variable if found, otherwise + `null` if the environment doesn't exist. - // 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) + Throws: + $(REF UTFException, std,utf) if the variable contains invalid UTF-16 + characters (Windows only). + */ + string get(scope const(char)[] name, string defaultValue = null) @safe { - 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); + string value; + getImpl(name, (result) { value = result ? cachedToString(result) : defaultValue; }); + return value; } - 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); + /** + Assigns the given `value` to the environment variable with the given + `name`. + If `value` is null the variable is removed from environment. - // 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]); + If the variable does not exist, it will be created. If it already exists, + it will be overwritten. + --- + environment["foo"] = "bar"; + --- - /* - 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. + 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). */ - int[2] pidPipe; - if (config & Config.detached) + inout(char)[] opIndexAssign(return inout char[] value, scope const(char)[] name) @trusted { - if (core.sys.posix.unistd.pipe(pidPipe) != 0) - throw ProcessException.newFromErrno("Could not create pipe to get process pid"); - setCLOEXEC(pidPipe[1], true); + 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); } - 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); - } + /** + Removes the environment variable with the given `name`. - void closePipeWriteEnds() + 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(scope const(char)[] name) @trusted nothrow @nogc { - close(forkPipe[1]); - if (config & Config.detached) - close(pidPipe[1]); + version (Windows) SetEnvironmentVariableW(name.tempCStringW(), null); + else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString()); + else static assert(0); } - auto id = core.sys.posix.unistd.fork(); - if (id < 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")(scope const(char)[] name) @trusted { - closePipeWriteEnds(); - throw ProcessException.newFromErrno("Failed to spawn new process"); + 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 == NO_ERROR) + return true; // zero-length environment variable on Wine / XP + if (err == ERROR_ENVVAR_NOT_FOUND) + return false; + // Some other Windows error, throw. + throw new WindowsException(err); + } + else static assert(0); } - void forkChild() nothrow @nogc + /** + 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. `PATH`). + + Throws: + $(OBJECTREF Exception) if the environment variables could not + be retrieved (Windows only). + */ + string[string] toAA() @trusted { - static import core.sys.posix.stdio; - pragma(inline, true); + import std.conv : to; + string[string] aa; + version (Posix) + { + auto environ = getEnvironPtr; + for (int i=0; environ[i] != null; ++i) + { + import std.string : indexOf; - // Child process + immutable varDef = to!string(environ[i]); + immutable eq = indexOf(varDef, '='); + assert(eq >= 0); - // 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]; + immutable name = varDef[0 .. eq]; + immutable value = varDef[eq+1 .. $]; - // Set the working directory. - if (workDirFD >= 0) + // 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) { - if (fchdir(workDirFD) < 0) + 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) { - // Fail. It is dangerous to run a program - // in an unexpected working directory. - abortOnError(forkPipeOut, InternalError.chdir, .errno); + 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; } - close(workDirFD); } + else static assert(0); + return aa; + } - void execProcess() +private: + version (Windows) alias OSChar = WCHAR; + else version (Posix) alias OSChar = char; + + // Retrieves the environment variable. Calls `sink` with a + // temporary buffer of OS characters, or `null` if the variable + // doesn't exist. + void getImpl(scope const(char)[] name, scope void delegate(const(OSChar)[]) @safe sink) @trusted + { + version (Windows) { - // 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); + // 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. - // 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); + import std.conv : to; + const namezTmp = name.tempCStringW(); + WCHAR[] buf; - if (!(config & Config.inheritFDs)) + // 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) { - import core.stdc.stdlib : malloc; - import core.sys.posix.poll : pollfd, poll, POLLNVAL; - import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE; + immutable err = GetLastError(); + if (err == ERROR_ENVVAR_NOT_FOUND) + return sink(null); + if (err != NO_ERROR) // Some other Windows error, throw. + throw new WindowsException(err); + } + if (len <= 1) + return sink(""); + buf.length = len; - // Get the maximum number of file descriptors that could be open. - rlimit r; - if (getrlimit(RLIMIT_NOFILE, &r) != 0) + 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) { - abortOnError(forkPipeOut, InternalError.getrlimit, .errno); + immutable err = GetLastError(); + if (err == NO_ERROR) // sucessfully read a 0-length variable + return sink(""); + if (err == ERROR_ENVVAR_NOT_FOUND) // variable didn't exist + return sink(null); + // some other windows error + throw new WindowsException(err); } - 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); + assert(lenRead != buf.length, "impossible according to msft docs"); + if (lenRead < buf.length) // the buffer was long enough + return sink(buf[0 .. lenRead]); + // resize and go around again, because the environment variable grew + buf.length = lenRead; } } - else + else version (Posix) { - execProcess(); - } - } + import core.stdc.string : strlen; - if (id == 0) - { - forkChild(); - assert(0); + const vz = core.sys.posix.stdlib.getenv(name.tempCString()); + if (vz == null) return sink(null); + return sink(vz[0 .. strlen(vz)]); + } + else static 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"); + string cachedToString(C)(scope const(C)[] v) @safe + { + import std.algorithm.comparison : equal; - bool owned = true; - if (status != InternalError.noerror) + // Cache the last call's result. + static string lastResult; + if (v.empty) { - 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); + // Return non-null array for blank result to distinguish from + // not-present result. + lastResult = ""; } - else if (config & Config.detached) + else if (!v.equal(lastResult)) { - owned = false; - if (read(pidPipe[0], &id, id.sizeof) != id.sizeof) - throw ProcessException.newFromErrno("Could not read from pipe to get detached process id"); + import std.conv : to; + lastResult = v.to!string; } - - // 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); + return lastResult; } } -/* -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 +@safe unittest { - import core.exception : RangeError; + import std.exception : assertThrown; + // New variable + environment["std_process"] = "foo"; + assert(environment["std_process"] == "foo"); + assert("std_process" in environment); - if (commandLine.empty) throw new RangeError("Command line is empty"); + // Set variable again (also tests length 1 case) + environment["std_process"] = "b"; + assert(environment["std_process"] == "b"); + assert("std_process" in environment); - // Prepare environment. - auto envz = createEnv(env, !(config & Config.newEnv)); + // Remove variable + environment.remove("std_process"); + assert("std_process" !in environment); - // Startup info for CreateProcessW(). - STARTUPINFO_W startinfo; - startinfo.cb = startinfo.sizeof; - static int getFD(ref File f) { return f.isOpen ? f.fileno : -1; } + // Remove again, should succeed + environment.remove("std_process"); + assert("std_process" !in environment); - // 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); + // Throw on not found. + assertThrown(environment["std_process"]); - 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 ); + // get() without default value + assert(environment.get("std_process") is null); - 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; + // get() with default value + assert(environment.get("std_process", "baz") == "baz"); - // 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"); + // 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); - // 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(); + // Important to do the following round-trip after the previous test + // because it tests toAA with an empty var - // close the thread handle in the process info structure - CloseHandle(pi.hThread); - if (config & Config.detached) + // Convert to associative array + auto aa = environment.toAA(); + assert(aa.length > 0); + foreach (n, v; aa) { - CloseHandle(pi.hProcess); - return new Pid(pi.dwProcessId); + // 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]); } - 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; - } + // ... and back again. + foreach (n, v; aa) + environment[n] = v; - // 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; + // Complete the roundtrip + auto aa2 = environment.toAA(); + import std.conv : text; + assert(aa == aa2, text(aa, " != ", aa2)); + assert("std_process" in environment); - // 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; + // Setting null must have the same effect as remove + environment["std_process"] = null; + assert("std_process" !in environment); } -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); +// ============================================================================= +// Functions and classes for process management. +// ============================================================================= - 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")); +/** + * 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(); } -// 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) +/** + * 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 { - 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) + version (Windows) + return GetCurrentThreadId(); + else + version (Posix) { - auto uk = toUpper(k); - put(uk, v); - if (uk in parentEnv) parentEnv.remove(uk); + import core.sys.posix.pthread : pthread_self; + return pthread_self(); } - - // 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) -package(std) string searchPathFor(in char[] executable) - @trusted //TODO: @safe nothrow +@system unittest { - 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; -} + int pidA, pidB; + ThreadID tidA, tidB; + pidA = thisProcessID; + tidA = thisThreadID; -// 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); -} + import core.thread; + auto t = new Thread({ + pidB = thisProcessID; + tidB = thisThreadID; + }); + t.start(); + t.join(); -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?"); + assert(pidA == pidB); + assert(tidA != tidB); } -// 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(). +package(std) string uniqueTempPath() @safe { - 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); + 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()); } -// 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); +version (iOSDerived) {} +else: - // 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); +/** +Spawns a new process, optionally assigning it an arbitrary set of standard +input, output, and error streams. - // 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; - } - } +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 +`Config.detached` flag, as detailed in the documentation for `wait`. - // 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; - } +Command_line: +There are four overloads of this function. The first two take an array +of strings, `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, +`program`, which specifies the program name. - // 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; - } +Unless a directory is specified in `args[0]` or `program`, +`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. +... - 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 -} +// 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!"); +--- -@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_variables: +By default, the child process inherits the environment of the parent +process, along with any additional variables specified in the `env` +parameter. If the same variable exists in both the parent's environment +and in `env`, the latter takes precedence. - 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); +If the $(LREF Config.newEnv) flag is set in `config`, the child +process will $(I not) inherit the parent's environment. Its entire +environment will then be determined by `env`. +--- +wait(spawnProcess("myapp", ["foo" : "bar"], Config.newEnv)); +--- - environment["std_process_unittest1"] = "1"; - assert(wait(spawnProcess(envProg.path)) == 1); - assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); +Standard_streams: +The optional arguments `stdin`, `stdout` and `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."); +--- - auto env = ["std_process_unittest2" : "2"]; - assert(wait(spawnProcess(envProg.path, env)) == 3); - assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 2); +Note that if you pass a `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. - env["std_process_unittest1"] = "4"; - assert(wait(spawnProcess(envProg.path, env)) == 6); - assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); +Beware of buffering issues when passing `File` objects to +`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. - environment.remove("std_process_unittest1"); - assert(wait(spawnProcess(envProg.path, env)) == 6); - assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); -} +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. -@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"; +Returns: +A $(LREF Pid) object that corresponds to the spawned process. - // Pipes - void testPipes(Config config) +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 `args` is empty. +*/ +Pid spawnProcess(scope const(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, + scope const char[] workDir = null) + @safe +{ + version (Windows) { - 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); + const commandLine = escapeShellArguments(args); + const program = args.length ? args[0] : null; + return spawnProcessWin(commandLine, program, stdin, stdout, stderr, env, config, workDir); } - - // Files - void testFiles(Config config) + else version (Posix) { - 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); + return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir); } - - testPipes(Config.none); - testFiles(Config.none); - testPipes(Config.detached); - testFiles(Config.detached); + else + static assert(0); } -@system unittest // Error handling in spawnProcess() +/// ditto +Pid spawnProcess(scope const(char[])[] args, + const string[string] env, + Config config = Config.none, + scope const(char)[] workDir = null) + @trusted // TODO: Should be @safe { - 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)); - } + return spawnProcess(args, + std.stdio.stdin, + std.stdio.stdout, + std.stdio.stderr, + env, + config, + workDir); } -@system unittest // Specifying a working directory. +/// ditto +Pid spawnProcess(scope const(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, + scope const(char)[] workDir = null) + @trusted { - 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"))); + return spawnProcess((&program)[0 .. 1], + stdin, stdout, stderr, env, config, workDir); } -@system unittest // Specifying a bad working directory. +/// ditto +Pid spawnProcess(scope const(char)[] program, + const string[string] env, + Config config = Config.none, + scope const(char)[] workDir = null) + @trusted { - 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) - { - if (core.sys.posix.unistd.getuid() != 0) - { - 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)); - } - } + return spawnProcess((&program)[0 .. 1], env, config, workDir); } -@system unittest // Specifying empty working directory. +version (Posix) private enum InternalError : ubyte { - TestScript prog = ""; - - string directory = ""; - assert(directory.ptr && !directory.length); - spawnProcess([prog.path], null, Config.none, directory).wait(); + noerror, + exec, + chdir, + getrlimit, + doubleFork, + malloc, + preExec, } -@system unittest // Reopening the standard streams (issue 13258) +/* +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 spawnProcessPosix(scope const(char[])[] args, + File stdin, + File stdout, + File stderr, + scope const string[string] env, + Config config, + scope const(char)[] workDir) + @trusted // TODO: Should be @safe { - import std.string; - void fun() + 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)) { - spawnShell("echo foo").wait(); - spawnShell("echo bar").wait(); + name = searchPathFor(name); + if (name is null) + throw new ProcessException(text("Executable file not found: ", args[0])); } - auto tmpFile = uniqueTempPath(); - scope(exit) if (exists(tmpFile)) remove(tmpFile); + // 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; - { - auto oldOut = std.stdio.stdout; - scope(exit) std.stdio.stdout = oldOut; + // Prepare environment. + auto envz = createEnv(env, !(config.flags & Config.Flags.newEnv)); - std.stdio.stdout = File(tmpFile, "w"); - fun(); - std.stdio.stdout.close(); + // 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); } - 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(); + static int getFD(ref File f) { return core.stdc.stdio.fileno(f.getFP()); } - auto data = readText(fn); - assert(data == "AAAAAAAAAABBBBB\r\n", data); -} + // 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); -/** -A variation on $(LREF spawnProcess) that runs the given _command through -the current user's preferred _command interpreter (aka. shell). + // 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]); -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). + /* + 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.flags & Config.Flags.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.flags & Config.Flags.detached) close(pidPipe[0]); -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); ---- + 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); + } -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) + void closePipeWriteEnds() { - // 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 ~ `"`; + close(forkPipe[1]); + if (config.flags & Config.Flags.detached) + close(pidPipe[1]); } - else version (Posix) + + auto id = core.sys.posix.unistd.fork(); + if (id < 0) { - const(char)[][3] args; - args[0] = shellPath; - args[1] = shellSwitch; - args[2] = command; + closePipeWriteEnds(); + throw ProcessException.newFromErrno("Failed to spawn new process"); } - 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); -} + void forkChild() nothrow @nogc + { + static import core.sys.posix.stdio; -@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"); -} + // Child process -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)); -} + // no need for the read end of pipe on child side + if (config.flags & Config.Flags.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); + } -/** -Flags that control the behaviour of process creation functions in this -module. Most flags only apply to $(LREF spawnProcess) and -$(LREF spawnShell). + 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); -Use bitwise OR to combine flags. + // 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); -Example: ---- -auto logFile = File("myapp_error.log", "w"); + if (!(config.flags & Config.Flags.inheritFDs)) + { + // NOTE: malloc() and getrlimit() are not on the POSIX async + // signal safe functions list, but practically this should + // not be a problem. Java VM and CPython also use malloc() + // in its own implementation via opendir(). + import core.stdc.stdlib : malloc; + import core.sys.posix.poll : pollfd, poll, POLLNVAL; + import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE; -// 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, - - /** - By default, the $(LREF execute) and $(LREF executeShell) functions - will capture child processes' both stdout and stderr. This can be - undesirable if the standard output is to be processed or otherwise - used by the invoking program, as `execute`'s result would then - contain a mix of output and warning/error messages. - - Specify this flag when calling `execute` or `executeShell` to - cause invoked processes' stderr stream to be sent to $(REF stderr, - std,stdio), and only capture and return standard output. - - This flag has no effect on $(LREF spawnProcess) or $(LREF spawnShell). - */ - stderrPassThrough = 128, -} - - -/// 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(). + // 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; - 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. + // The above, less stdin, stdout, and stderr + immutable maxToClose = maxDescriptors - 3; - 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) + // Call poll() to see which ones are actually open: + auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose); + if (pfds is null) { - throw new ProcessException( - "Process does not exist or is not a child process."); + abortOnError(forkPipeOut, InternalError.malloc, .errno); + } + 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 { - // waitpid() was interrupted by a signal. We simply - // restart it. - assert(errno == EINTR); - continue; + // Fall back to closing everything. + foreach (i; 3 .. maxDescriptors) + { + if (i == forkPipeOut) continue; + close(i); + } } } - if (!block && check == 0) return 0; - if (WIFEXITED(status)) + else // This is already done if we don't inherit descriptors. { - exitCode = WEXITSTATUS(status); - break; + // 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); } - else if (WIFSIGNALED(status)) + + if (config.preExecFunction !is null) { - exitCode = -WTERMSIG(status); - break; + if (config.preExecFunction() != true) + { + abortOnError(forkPipeOut, InternalError.preExec, .errno); + } } - // 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. + // 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); } - // Mark Pid as terminated, and cache and return exit code. - _processID = terminated; - _exitCode = exitCode; - return exitCode; - } - else version (Windows) - { - int performWait(bool block) @trusted + + if (config.flags & Config.Flags.detached) { - 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 secondFork = core.sys.posix.unistd.fork(); + if (secondFork == 0) { - auto result = WaitForSingleObject(_handle, INFINITE); - if (result != WAIT_OBJECT_0) - throw ProcessException.newFromLastError("Wait failed."); + close(pidPipeOut); + execProcess(); } - 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) + else if (secondFork == -1) { - CloseHandle(_handle); - _handle = INVALID_HANDLE_VALUE; + 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(); + } } - // 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) + if (id == 0) { - 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; - } + forkChild(); + assert(0); } else { - this(int id, bool owned) @safe pure nothrow + 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.flags & Config.Flags.detached) { - _processID = id; - this.owned = owned; + // 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"); -/** -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. + 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 '" ~ cast(string) name ~ "'"; + break; + case InternalError.doubleFork: + // Can happen only when starting detached process + assert(config.flags & Config.Flags.detached); + errorMsg = "Failed to fork twice"; + break; + case InternalError.malloc: + errorMsg = "Failed to allocate memory"; + break; + case InternalError.preExec: + errorMsg = "Failed to execute preExecFunction"; + break; + case InternalError.noerror: + assert(false); + } + if (readExecResult == error.sizeof) + throw ProcessException.newFromErrno(error, errorMsg); + throw new ProcessException(errorMsg); + } + else if (config.flags & Config.Flags.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"); + } -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. + // Parent process: Close streams and return. + if (!(config.flags & Config.Flags.retainStdin ) && stdinFD > STDERR_FILENO + && stdinFD != getFD(std.stdio.stdin )) + stdin.close(); + if (!(config.flags & Config.Flags.retainStdout) && stdoutFD > STDERR_FILENO + && stdoutFD != getFD(std.stdio.stdout)) + stdout.close(); + if (!(config.flags & Config.Flags.retainStderr) && stderrFD > STDERR_FILENO + && stderrFD != getFD(std.stdio.stderr)) + stderr.close(); + return new Pid(id, owned); + } +} -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). +version (Posix) +@system unittest +{ + import std.concurrency : ownerTid, receiveTimeout, send, spawn; + import std.datetime : seconds; + + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss, SIGINT); + pthread_sigmask(SIG_BLOCK, &ss, null); + + Config config = { + preExecFunction: () @trusted @nogc nothrow { + // Reset signal handlers + sigset_t ss; + if (sigfillset(&ss) != 0) + { + return false; + } + if (sigprocmask(SIG_UNBLOCK, &ss, null) != 0) + { + return false; + } + return true; + }, + }; + + auto pid = spawnProcess(["sleep", "10000"], + std.stdio.stdin, + std.stdio.stdout, + std.stdio.stderr, + null, + config, + null); + scope(failure) + { + kill(pid, SIGKILL); + wait(pid); + } -Throws: -$(LREF ProcessException) on failure or on attempt to wait for detached process. + // kill the spawned process with SIGINT + // and send its return code + spawn((shared Pid pid) { + auto p = cast() pid; + kill(p, SIGINT); + auto code = wait(p); + assert(code < 0); + send(ownerTid, code); + }, cast(shared) pid); + + auto received = receiveTimeout(3.seconds, (int) {}); + assert(received); +} -Example: -See the $(LREF spawnProcess) documentation. +/* +Implementation of spawnProcess() for Windows. -See_also: -$(LREF tryWait), for a non-blocking function. +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". */ -int wait(Pid pid) @safe +version (Windows) +private Pid spawnProcessWin(scope const(char)[] commandLine, + scope const(char)[] program, + File stdin, + File stdout, + File stderr, + scope const string[string] env, + Config config, + scope const(char)[] workDir) + @trusted { - assert(pid !is null, "Called wait on a null Pid."); - return pid.performWait(true); -} + import core.exception : RangeError; + import std.conv : text; + if (commandLine.empty) throw new RangeError("Command line is empty"); -@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); -} + // Prepare environment. + auto envz = createEnv(env, !(config.flags & Config.Flags.newEnv)); + // Startup info for CreateProcessW(). + STARTUPINFO_W startinfo; + startinfo.cb = startinfo.sizeof; + static int getFD(ref File f) { return f.isOpen ? f.fileno : -1; } -/** -A non-blocking version of $(LREF wait). + // 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) + { + enum _NO_CONSOLE_FILENO = cast(HANDLE)-2; + 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 == _NO_CONSOLE_FILENO) + handle = GetStdHandle(stdHandle); -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). + 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 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). + 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; -Returns: -An $(D std.typecons.Tuple!(bool, "terminated", int, "status")). + // Create process. + PROCESS_INFORMATION pi; + DWORD dwCreationFlags = + CREATE_UNICODE_ENVIRONMENT | + ((config.flags & Config.Flags.suppressConsole) ? CREATE_NO_WINDOW : 0); + // workaround until https://issues.dlang.org/show_bug.cgi?id=14696 is fixed + auto pworkDir = workDir.tempCStringW(); + if (!CreateProcessW(null, commandLine.tempCStringW().buffPtr, + null, null, true, dwCreationFlags, + envz, workDir.length ? pworkDir : null, &startinfo, &pi)) + throw ProcessException.newFromLastError("Failed to spawn process \"" ~ cast(string) program ~ '"'); -Throws: -$(LREF ProcessException) on failure or on attempt to wait for detached process. + // figure out if we should close any of the streams + if (!(config.flags & Config.Flags.retainStdin ) && stdinFD > STDERR_FILENO + && stdinFD != getFD(std.stdio.stdin )) + stdin.close(); + if (!(config.flags & Config.Flags.retainStdout) && stdoutFD > STDERR_FILENO + && stdoutFD != getFD(std.stdio.stdout)) + stdout.close(); + if (!(config.flags & Config.Flags.retainStderr) && stderrFD > STDERR_FILENO + && stderrFD != getFD(std.stdio.stderr)) + stderr.close(); -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); + // close the thread handle in the process info structure + CloseHandle(pi.hThread); + if (config.flags & Config.Flags.detached) + { + CloseHandle(pi.hProcess); + return new Pid(pi.dwProcessId); + } + return new Pid(pi.dwProcessId, pi.hProcess); } -// unittest: This function is tested together with kill() below. +// 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; + } -/** -Attempts to terminate the process associated with $(D pid). + // 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; -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. + // 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; +} -Always call $(LREF wait) to wait for a process to complete, even if $(D kill) -has been called on it. +version (Posix) @system unittest +{ + auto e1 = createEnv(null, false); + assert(e1 != null && *e1 == null); -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) + auto e2 = createEnv(null, true); + assert(e2 != null); + int i = 0; + auto environ = getEnvironPtr; + for (; environ[i] != null; ++i) { - import core.sys.posix.signal : SIGTERM; - kill(pid, SIGTERM); + assert(e2[i] != null); + import core.stdc.string; + assert(strcmp(e2[i], environ[i]) == 0); } -} + assert(e2[i] == null); -/// 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(); - } + 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")); } -@system unittest // tryWait() and kill() + +// 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) { - import core.thread; - import std.exception : assertThrown; - // The test script goes into an infinite loop. - version (Windows) + 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) { - TestScript prog = ":loop - goto loop"; + envz.put(var); + envz.put('='); + envz.put(val); + envz.put(cast(wchar) '\0'); } - else version (Posix) + + // Add the variables in childEnv, removing them from parentEnv + // if they exist there too. + auto parentEnv = mergeWithParentEnv ? environment.toAA() : null; + foreach (k, v; childEnv) { - import core.sys.posix.signal : SIGTERM, SIGKILL; - TestScript prog = "while true; do sleep 1; done"; + auto uk = toUpper(k); + put(uk, v); + if (uk in parentEnv) parentEnv.remove(uk); } - 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)); + // 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; } -@system unittest // wait() and kill() detached process +version (Windows) @system unittest { - 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)); + 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) +package(std) string searchPathFor(scope const(char)[] executable) + @safe +{ + import std.algorithm.iteration : splitter; + import std.conv : text; + import std.path : chainPath; -/** -Creates a unidirectional _pipe. + string result; -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); ---- + environment.getImpl("PATH", + (scope const(char)[] path) + { + if (!path) + return; -Returns: -A $(LREF Pipe) object that corresponds to the created _pipe. + foreach (dir; splitter(path, ":")) + { + auto execPath = chainPath(dir, executable); + if (isExecutable(execPath)) + { + result = text(execPath); + return; + } + } + }); -Throws: -$(REF StdioException, std,stdio) on failure. -*/ + return result; +} + +// Checks whether the file exists and can be executed by the +// current user. version (Posix) -Pipe pipe() @trusted //TODO: @safe +private bool isExecutable(R)(R path) @trusted nothrow @nogc +if (isInputRange!R && isSomeChar!(ElementEncodingType!R)) { - 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; + return (access(path.tempCString(), X_OK) == 0); } -else version (Windows) -Pipe pipe() @trusted //TODO: @safe + +version (Posix) @safe unittest { - // 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); - } + 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?"); +} - scope(failure) +// 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) { - CloseHandle(readHandle); - CloseHandle(writeHandle); + if (on) flags |= FD_CLOEXEC; + else flags &= ~(cast(typeof(flags)) FD_CLOEXEC); + flags = fcntl(fd, F_SETFD, flags); } + assert(flags != -1 || .errno == EBADF); +} - 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); - } +@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); } - -/// An interface to a pipe created by the $(LREF pipe) function. -struct Pipe +// 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 { - /// The read end of the pipe. - @property File readEnd() @safe nothrow { return _read; } - + 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; - /// The write end of the pipe. - @property File writeEnd() @safe nothrow { return _write; } + 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); - /** - Closes both ends of the pipe. + // Try a few different methods to check whether there are any + // incorrectly-open files. + void testFDs() + { + // 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; + } + } - 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. + // 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; + } - 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.) + // 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; + } - Throws: - $(REF ErrnoException, std,exception) if an error occurs. - */ - void close() @safe - { - _read.close(); - _write.close(); + std.stdio.stderr.writeln(__FILE__, ':', __LINE__, + ": Warning: Couldn't find any way to check open files"); } - -private: - File _read, _write; + testFDs(); } -@system unittest +@system unittest // Environment variables in spawnProcess(). { - 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); -} - + // 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))`; -/** -Starts a new process, creating pipes to redirect its standard -input, output and/or error streams. + 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); -$(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). + environment["std_process_unittest1"] = "1"; + assert(wait(spawnProcess(envProg.path)) == 1); + assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); -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. + auto env = ["std_process_unittest2" : "2"]; + assert(wait(spawnProcess(envProg.path, env)) == 3); + assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 2); -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). + env["std_process_unittest1"] = "4"; + assert(wait(spawnProcess(envProg.path, env)) == 6); + assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); -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. + environment.remove("std_process_unittest1"); + assert(wait(spawnProcess(envProg.path, env)) == 6); + assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); +} -Throws: -$(LREF ProcessException) on failure to start the process.$(BR) -$(REF StdioException, std,stdio) on failure to redirect any of the streams.$(BR) +@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 + echo done > %3"; + else version (Posix) TestScript prog = + "read INPUT + echo $INPUT output $1 + echo $INPUT error $2 >&2 + echo done > \"$3\""; -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); + // Pipes + void testPipes(Config config) + { + import std.file, std.uuid, core.thread, std.exception; + auto pipei = pipe(); + auto pipeo = pipe(); + auto pipee = pipe(); + auto done = buildPath(tempDir(), randomUUID().toString()); + auto pid = spawnProcess([prog.path, "foo", "bar", done], + 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.flags & Config.Flags.detached) + while (!done.exists) Thread.sleep(10.msecs); + else + wait(pid); + while (remove(done).collectException) Thread.sleep(10.msecs); + } -// Store lines of output. -string[] output; -foreach (line; pipes.stdout.byLine) output ~= line.idup; + // Files + void testFiles(Config config) + { + import std.ascii, std.file, std.uuid, core.thread, std.exception; + 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 done = buildPath(tempDir(), randomUUID().toString()); + auto pid = spawnProcess([prog.path, "bar", "baz", done], filei, fileo, filee, null, config); + if (config.flags & Config.Flags.detached) + while (!done.exists) Thread.sleep(10.msecs); + else + wait(pid); + assert(readText(patho).chomp() == "INPUT output bar"); + assert(readText(pathe).chomp().stripRight() == "INPUT error baz"); + while (remove(pathi).collectException) Thread.sleep(10.msecs); + while (remove(patho).collectException) Thread.sleep(10.msecs); + while (remove(pathe).collectException) Thread.sleep(10.msecs); + while (remove(done).collectException) Thread.sleep(10.msecs); + } -// Store lines of errors. -string[] errors; -foreach (line; pipes.stderr.byLine) errors ~= line.idup; + testPipes(Config.none); + testFiles(Config.none); + testPipes(Config.detached); + testFiles(Config.detached); +} +@system unittest // Error handling in spawnProcess() +{ + import std.algorithm.searching : canFind; + import std.exception : assertThrown, collectExceptionMsg; -// 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); + static void testNotFoundException(string program) + { + assert(collectExceptionMsg!ProcessException(spawnProcess(program)).canFind(program)); + assert(collectExceptionMsg!ProcessException(spawnProcess(program, null, Config.detached)).canFind(program)); + } + testNotFoundException("ewrgiuhrifuheiohnmnvqweoijwf"); + testNotFoundException("./rgiuhrifuheiohnmnvqweoijwf"); -// a single period tells sendmail we are finished -pipes.stdin.writeln("."); + // can't execute malformed file with executable permissions + version (Posix) + { + import std.path : buildPath; + import std.file : remove, write, setAttributes, tempDir; + import core.sys.posix.sys.stat : S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH; + import std.conv : to; + 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)); + } +} -// but at this point sendmail might not see it, we need to flush -pipes.stdin.flush(); +@system unittest // Specifying a working directory. +{ + import std.path; + import std.file; + TestScript prog = "echo foo>bar"; -// 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); + auto directory = uniqueTempPath(); + mkdir(directory); + scope(exit) rmdirRecurse(directory); ---- -*/ -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); + auto pid = spawnProcess([prog.path], null, Config.none, directory); + wait(pid); + assert(exists(buildPath(directory, "bar"))); } -/// ditto -ProcessPipes pipeProcess(in char[] program, - Redirect redirect = Redirect.all, - const string[string] env = null, - Config config = Config.none, - in char[] workDir = null) - @safe +@system unittest // Specifying a bad working directory. { - return pipeProcessImpl!spawnProcess(program, redirect, env, config, workDir); + import std.exception : assertThrown; + import std.file; + 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) + { + if (core.sys.posix.unistd.getuid() != 0) + { + 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)); + } + } } -/// 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 +@system unittest // Specifying empty working directory. { - return pipeProcessImpl!spawnShell(command, - redirect, - env, - config, - workDir, - shellPath); + TestScript prog = ""; + + string directory = ""; + assert(directory.ptr && !directory.length); + spawnProcess([prog.path], null, Config.none, directory).wait(); } -// 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 +// Reopening the standard streams (https://issues.dlang.org/show_bug.cgi?id=13258) +@system unittest { - File childStdin, childStdout, childStderr; - ProcessPipes pipes; - pipes._redirectFlags = redirectFlags; - - if (redirectFlags & Redirect.stdin) - { - auto p = pipe(); - childStdin = p.readEnd; - pipes._stdin = p.writeEnd; - } - else + import std.string; + import std.file; + void fun() { - childStdin = std.stdio.stdin; + spawnShell("echo foo").wait(); + spawnShell("echo bar").wait(); } - 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; - } + auto tmpFile = uniqueTempPath(); + scope(exit) if (exists(tmpFile)) remove(tmpFile); - 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; - } + auto oldOut = std.stdio.stdout; + scope(exit) std.stdio.stdout = oldOut; - 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; + std.stdio.stdout = File(tmpFile, "w"); + fun(); + std.stdio.stdout.close(); } - config &= ~(Config.retainStdin | Config.retainStdout | Config.retainStderr); - pipes._pid = spawnFunc(command, childStdin, childStdout, childStderr, - env, config, workDir, extraArgs); - return pipes; + auto lines = readText(tmpFile).splitLines(); + assert(lines == ["foo", "bar"]); } - -/** -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 +// MSVCRT workaround (https://issues.dlang.org/show_bug.cgi?id=14422) +version (Windows) +@system unittest { - /// 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, + auto fn = uniqueTempPath(); + scope(exit) if (exists(fn)) remove(fn); + std.file.write(fn, "AAAAAAAAAA"); - /** - Redirect the standard error stream into the standard output stream. - This can not be combined with $(D Redirect.stderr). - */ - stderrToStdout = 8, + auto f = File(fn, "a"); + spawnProcess(["cmd", "/c", "echo BBBBB"], std.stdio.stdin, f).wait(); - /** - Redirect the standard output stream into the standard error stream. - This can not be combined with $(D Redirect.stdout). - */ - stdoutToStderr = 16, + auto data = readText(fn); + assert(data == "AAAAAAAAAABBBBB\r\n", data); } -@system unittest +// https://issues.dlang.org/show_bug.cgi?id=20765 +// Test that running processes with relative path works in conjunction +// with indicating a workDir. +version (Posix) @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 + import std.file : mkdir, write, setAttributes, rmdirRecurse; + import std.conv : octal; - :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); -} + auto dir = uniqueTempPath(); + mkdir(dir); + scope(exit) rmdirRecurse(dir); + write(dir ~ "/program", "#!/bin/sh\necho Hello"); + setAttributes(dir ~ "/program", octal!700); -@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); + assert(execute(["./program"], null, Config.none, size_t.max, dir).output == "Hello\n"); } /** -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. +A variation on $(LREF spawnProcess) that runs the given _command through +the current user's preferred _command interpreter (aka. shell). - 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; - } +The string `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). - /** - An $(REF File, std,stdio) that allows reading from the child process' - standard output stream. +In all other respects this function works just like `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); +--- - Throws: - $(OBJECTREF Error) if the child process' standard output stream hasn't - been redirected. - */ - @property File stdout() @safe nothrow +See_also: +$(LREF escapeShellCommand), which may be helpful in constructing a +properly quoted and escaped shell _command line for the current platform. +*/ +Pid spawnShell(scope const(char)[] command, + File stdin = std.stdio.stdin, + File stdout = std.stdio.stdout, + File stderr = std.stdio.stderr, + scope const string[string] env = null, + Config config = Config.none, + scope const(char)[] workDir = null, + scope string shellPath = nativeShell) + @trusted // See reason below +{ + version (Windows) { - if ((_redirectFlags & Redirect.stdout) == 0) - throw new Error("Child process' standard output stream hasn't " - ~"been redirected."); - return _stdout; + // 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. + const commandLine = escapeShellFileName(shellPath) + ~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`; + return spawnProcessWin(commandLine, shellPath, stdin, stdout, stderr, env, config, workDir); } - - /** - 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 + else version (Posix) { - if ((_redirectFlags & Redirect.stderr) == 0) - throw new Error("Child process' standard error stream hasn't " - ~"been redirected."); - return _stderr; + const(char)[][3] args; + args[0] = shellPath; + args[1] = shellSwitch; + args[2] = command; + /* The passing of args converts the static array, which is initialized with `scope` pointers, + * to a dynamic array, which is also a scope parameter. So, it is a scope pointer to a + * scope pointer, which although is safely used here, D doesn't allow transitive scope. + * See https://github.com/dlang/dmd/pull/10951 + */ + return spawnProcessPosix(args, stdin, stdout, stderr, env, config, workDir); } + else + static assert(0); +} -private: - Redirect _redirectFlags; - Pid _pid; - File _stdin, _stdout, _stderr; +/// ditto +Pid spawnShell(scope const(char)[] command, + scope const string[string] env, + Config config = Config.none, + scope const(char)[] workDir = null, + scope 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; + import std.conv : text; + 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)); +} /** -Executes the given program or shell command and returns its exit -code and output. +Options that control the behaviour of process creation functions in this +module. Most options only apply to $(LREF spawnProcess) and +$(LREF spawnShell). -$(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. +Example: --- -auto dmd = execute(["dmd", "myapp.d"]); -if (dmd.status != 0) writeln("Compilation failed:\n", dmd.output); +auto logFile = File("myapp_error.log", "w"); -auto ls = executeShell("ls -l"); -if (ls.status != 0) writeln("Failed to retrieve file listing"); -else writeln(ls.output); +// 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(); +} --- +*/ +struct Config +{ + /** + Flag options. + Use bitwise OR to combine flags. + **/ + enum Flags + { + 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, `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 `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 `Pid` is invalid. + */ + detached = 64, + + /** + By default, the $(LREF execute) and $(LREF executeShell) functions + will capture child processes' both stdout and stderr. This can be + undesirable if the standard output is to be processed or otherwise + used by the invoking program, as `execute`'s result would then + contain a mix of output and warning/error messages. + + Specify this flag when calling `execute` or `executeShell` to + cause invoked processes' stderr stream to be sent to $(REF stderr, + std,stdio), and only capture and return standard output. + + This flag has no effect on $(LREF spawnProcess) or $(LREF spawnShell). + */ + stderrPassThrough = 128, + } + Flags flags; /// ditto -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")). + /** + For backwards compatibility, and cases when only flags need to + be specified in the `Config`, these allow building `Config` + instances using flag names only. + */ + enum Config none = Config.init; + enum Config newEnv = Config(Flags.newEnv); /// ditto + enum Config retainStdin = Config(Flags.retainStdin); /// ditto + enum Config retainStdout = Config(Flags.retainStdout); /// ditto + enum Config retainStderr = Config(Flags.retainStderr); /// ditto + enum Config suppressConsole = Config(Flags.suppressConsole); /// ditto + enum Config inheritFDs = Config(Flags.inheritFDs); /// ditto + enum Config detached = Config(Flags.detached); /// ditto + enum Config stderrPassThrough = Config(Flags.stderrPassThrough); /// ditto + Config opUnary(string op)() + if (is(typeof(mixin(op ~ q{flags})))) + { + return Config(mixin(op ~ q{flags})); + } /// ditto + Config opBinary(string op)(Config other) + if (is(typeof(mixin(q{flags} ~ op ~ q{other.flags})))) + { + return Config(mixin(q{flags} ~ op ~ q{other.flags})); + } /// ditto + Config opOpAssign(string op)(Config other) + if (is(typeof(mixin(q{flags} ~ op ~ q{=other.flags})))) + { + return Config(mixin(q{flags} ~ op ~ q{=other.flags})); + } /// ditto -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.) + version (StdDdoc) + { + /** + A function that is called before `exec` in $(LREF spawnProcess). + It returns `true` if succeeded and otherwise returns `false`. -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); -} + $(RED Warning: + Please note that the code in this function must only use + async-signal-safe functions.) -/// 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); + On Windows, this member is not available. + */ + bool function() nothrow @nogc @safe preExecFunction; + } + else version (Posix) + { + bool function() nothrow @nogc @safe preExecFunction; + } } -/// 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 +// https://issues.dlang.org/show_bug.cgi?id=22125 +@safe unittest { - return executeImpl!pipeShell(command, - env, - config, - maxOutput, - workDir, - shellPath); + Config c = Config.retainStdin; + c |= Config.retainStdout; + c |= Config.retainStderr; + c &= ~Config.retainStderr; + assert(c == (Config.retainStdin | Config.retainStdout)); } -// 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) +/// A handle that corresponds to a spawned process. +final class Pid { - import std.algorithm.comparison : min; - import std.array : appender; - import std.typecons : Tuple; - - auto redirect = (config & Config.stderrPassThrough) - ? Redirect.stdout - : Redirect.stdout | Redirect.stderrToStdout; + /** + The process ID number. - auto p = pipeFunc(commandLine, redirect, - env, config, workDir, extraArgs); + 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; + } - auto a = appender!(ubyte[])(); - enum size_t defaultChunkSize = 4096; - immutable chunkSize = min(maxOutput, defaultChunkSize); + /** + 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 `core.sys.posix.sys.types.pid_t` + with the same value as $(LREF Pid.processID), while on Windows it returns + a `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() @nogc @safe pure nothrow + { + return _handle; + } + else version (Posix) + @property pid_t osHandle() @nogc @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 : enforce; + enforce!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(const bool block, const DWORD timeout = INFINITE) @trusted + { + import std.exception : enforce; + enforce!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, timeout); + if (result != WAIT_OBJECT_0) + { + // Wait time exceeded `timeout` milliseconds? + if (result == WAIT_TIMEOUT && timeout != INFINITE) + return 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; + } + + int performWait(Duration timeout) @safe + { + import std.exception : enforce; + const msecs = timeout.total!"msecs"; + + // Limit this implementation the maximum wait time offered by + // WaitForSingleObject. One could theoretically break up larger + // durations into multiple waits but (DWORD.max - 1).msecs + // (> 7 weeks, 17 hours) should be enough for the usual case. + // DWORD.max is reserved for INFINITE + enforce!ProcessException(msecs < DWORD.max, "Timeout exceeds maximum wait time!"); + return performWait(true, cast(DWORD) msecs); + } + + ~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 `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 `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 `core.sys.posix.signal` module +(which corresponds to the `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); +} + +private import std.typecons : Tuple; + +/** +Waits until either the process associated with `pid` terminates or the +elapsed time exceeds the given timeout. + +If the process terminates within the given duration it behaves exactly like +`wait`, except that it returns a tuple `(true, exit code)`. + +If the process does not terminate within the given duration it will stop +waiting and return `(false, 0).` + +The timeout may not exceed `(uint.max - 1).msecs` (~ 7 weeks, 17 hours). + +$(BLUE This function is Windows-Only.) + +Returns: +An $(D std.typecons.Tuple!(bool, "terminated", int, "status")). + +Throws: +$(LREF ProcessException) on failure or on attempt to wait for detached process. + +Example: +See the $(LREF spawnProcess) documentation. + +See_also: +$(LREF wait), for a blocking function without timeout. +$(LREF tryWait), for a non-blocking function without timeout. +*/ +version (StdDdoc) +Tuple!(bool, "terminated", int, "status") waitTimeout(Pid pid, Duration timeout) @safe; + +else version (Windows) +Tuple!(bool, "terminated", int, "status") waitTimeout(Pid pid, Duration timeout) @safe +{ + assert(pid !is null, "Called wait on a null Pid."); + auto code = pid.performWait(timeout); + return typeof(return)(pid._processID == Pid.terminated, code); +} + +version (Windows) +@system unittest // Pid and waitTimeout() +{ + import std.exception : collectException; + import std.typecons : tuple; + + TestScript prog = ":Loop\ngoto Loop;"; + auto pid = spawnProcess(prog.path); + + // Doesn't block longer than one second + assert(waitTimeout(pid, 1.seconds) == tuple(false, 0)); + + kill(pid); + assert(waitTimeout(pid, 1.seconds) == tuple(true, 1)); // exit 1 because the process is killed + + // Rejects timeouts exceeding the Windows API capabilities + const dur = DWORD.max.msecs; + const ex = collectException!ProcessException(waitTimeout(pid, dur)); + assert(ex); + assert(ex.msg == "Timeout exceeds maximum wait time!"); +} + +/** +A non-blocking version of $(LREF wait). + +If the process associated with `pid` has already terminated, +`tryWait` has the exact same effect as `wait`. +In this case, it returns a tuple where the `terminated` field +is set to `true` and the `status` field has the same +interpretation as the return value of `wait`. + +If the process has $(I not) yet terminated, this function differs +from `wait` in that does not wait for this to happen, but instead +returns immediately. The `terminated` field of the returned +tuple will then be set to `false`, while the `status` field +will always be 0 (zero). `wait` or `tryWait` should then be +called again on the same `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 `wait` call will have no +effect if the process has already terminated by the time `tryWait` +is called. In the opposite case, however, the `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 `pid`. + +The effect of this function, as well as the meaning of `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 `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 `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 `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 `core.sys.posix.signal`, which corresponds to the +$(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html, +`signal.h` POSIX header). If `codeOrSignal` is omitted, the +`SIGTERM` signal will be sent. (This matches the behaviour of the +$(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/kill.html, +`_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 : enforce; + enforce!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!"msecs"(500)); + kill(pid); + version (Windows) assert(wait(pid) == 1); + else version (Posix) assert(wait(pid) == -SIGTERM); + + pid = spawnProcess(prog.path); + Thread.sleep(dur!"msecs"(500)); + 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(500.msecs); + 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); + } - // Store up to maxOutput bytes in a. - foreach (ubyte[] chunk; p.stdout.byChunk(chunkSize)) + try { - immutable size_t remain = maxOutput - a.data.length; + 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); + } +} - if (chunk.length < remain) a.put(chunk); - else - { - a.put(chunk[0 .. remain]); - break; - } + +/// 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(); } - // 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); +private: + File _read, _write; } @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"); + 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); } -@safe unittest + +/** +Starts a new process, creating pipes to redirect its standard +input, output and/or error streams. + +`pipeProcess` and `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 `wait`. + +The `args`/`program`/`command`, `env` and `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 + `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(scope const(char[])[] args, + Redirect redirect = Redirect.all, + const string[string] env = null, + Config config = Config.none, + scope const(char)[] workDir = null) + @safe { - 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); + return pipeProcessImpl!spawnProcess(args, redirect, env, config, workDir); } -@system unittest +/// ditto +ProcessPipes pipeProcess(scope const(char)[] program, + Redirect redirect = Redirect.all, + const string[string] env = null, + Config config = Config.none, + scope const(char)[] workDir = null) + @safe { - // Temporarily disable output to stderr so as to not spam the build log. - import std.stdio : stderr; - import std.typecons : Tuple; - import std.file : readText; - import std.traits : ReturnType; + return pipeProcessImpl!spawnProcess(program, redirect, env, config, workDir); +} - ReturnType!executeShell r; - auto tmpname = uniqueTempPath; - scope(exit) if (exists(tmpname)) remove(tmpname); - auto t = stderr; - // Open a new scope to minimize code ran with stderr redirected. - { - stderr.open(tmpname, "w"); - scope(exit) stderr = t; - r = executeShell("echo D rox>&2", null, Config.stderrPassThrough); - } - assert(r.status == 0); - assert(r.output.empty); - auto witness = readText(tmpname); - import std.ascii : newline; - assert(witness == "D rox" ~ newline, "'" ~ witness ~ "'"); +/// ditto +ProcessPipes pipeShell(scope const(char)[] command, + Redirect redirect = Redirect.all, + const string[string] env = null, + Config config = Config.none, + scope const(char)[] workDir = null, + string shellPath = nativeShell) + @safe +{ + return pipeProcessImpl!spawnShell(command, + redirect, + env, + config, + workDir, + shellPath); } -@safe unittest +// Implementation of the pipeProcess() family of functions. +private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd, ExtraSpawnFuncArgs...) + (scope Cmd command, + Redirect redirectFlags, + const string[string] env = null, + Config config = Config.none, + scope const(char)[] workDir = null, + ExtraSpawnFuncArgs extraArgs = ExtraSpawnFuncArgs.init) + @trusted //TODO: @safe { - import std.typecons : Tuple; - void foo() //Just test the compilation + 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 { - auto ret1 = execute(["dummy", "arg"]); - auto ret2 = executeShell("dummy arg"); - static assert(is(typeof(ret1) == typeof(ret2))); - - Tuple!(int, string) ret3 = execute(["dummy", "arg"]); + childStdout = std.stdio.stdout; } -} - -/// 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__) + if (redirectFlags & Redirect.stderr) { - import core.stdc.errno : errno; - return newFromErrno(errno, customMsg, file, line); + 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; } - - // ditto, but error number is provided by caller - static ProcessException newFromErrno(int error, - string customMsg = null, - string file = __FILE__, - size_t line = __LINE__) + else { - import std.exception : errnoString; - auto errnoMsg = errnoString(error); - auto msg = customMsg.empty ? errnoMsg - : customMsg ~ " (" ~ errnoMsg ~ ')'; - return new ProcessException(msg, file, line); + childStderr = std.stdio.stderr; } - // Creates a new ProcessException based on GetLastError() (Windows only). - version (Windows) - static ProcessException newFromLastError(string customMsg = null, - string file = __FILE__, - size_t line = __LINE__) + if (redirectFlags & Redirect.stdoutToStderr) { - auto lastMsg = sysErrorString(GetLastError()); - auto msg = customMsg.empty ? lastMsg - : customMsg ~ " (" ~ lastMsg ~ ')'; - return new ProcessException(msg, file, line); + 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; } -} - - -/** -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); + config.flags &= ~(Config.Flags.retainStdin | Config.Flags.retainStdout | Config.Flags.retainStderr); + pipes._pid = spawnFunc(command, childStdin, childStdout, childStderr, + env, config, workDir, extraArgs); + return pipes; } -/** -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. +/** +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. */ -@property string nativeShell() @safe @nogc pure nothrow +enum Redirect { - 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"; - + /// Redirect the standard input, output or error streams, respectively. + stdin = 1, + stdout = 2, /// ditto + stderr = 4, /// ditto -/** - * 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(); -} + /** + 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 `Redirect.stderr`. + */ + stderrToStdout = 8, -/** - * 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(); - } + /** + Redirect the standard output stream into the standard error stream. + This can not be combined with `Redirect.stdout`. + */ + stdoutToStderr = 16, } - @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); -} - + 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 -// 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); - } - } + :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); - ~this() - { - import std.file : remove, exists; - if (!path.empty && exists(path)) - { - try { remove(path); } - catch (Exception e) - { - debug std.stdio.stderr.writeln(e.msg); - } - } - } + 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); - string path; + 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); } -package(std) string uniqueTempPath() @safe +@system unittest { - 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()); + 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); } - -// ============================================================================= -// 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. +/** +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; + } -/** -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")); ---- + /** + An $(REF File, std,stdio) that allows writing to the child process' + standard input stream. -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 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; + } -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) + /** + 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 { - // 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; + if ((_redirectFlags & Redirect.stdout) == 0) + throw new Error("Child process' standard output stream hasn't " + ~"been redirected."); + return _stdout; } - version (Posix) + + /** + 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 { - return escapeShellCommandString(escapeShellArguments(args)); + 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; } -@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 ); -} +/** +Executes the given program or shell command and returns its exit +code and output. -private string escapeShellCommandString(string command) @safe pure -{ - version (Windows) - return escapeWindowsShellCommand(command); - else - return command; -} +`execute` and `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); -private string escapeWindowsShellCommand(in char[] command) @safe pure -{ - import std.array : appender; - auto result = appender!string(); - result.reserve(command.length); +auto ls = executeShell("ls -l"); +if (ls.status != 0) writeln("Failed to retrieve file listing"); +else writeln(ls.output); +--- - 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; -} +The `args`/`program`/`command`, `env` and `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 + `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). -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]; - } - } +Returns: +An $(D std.typecons.Tuple!(int, "status", string, "output")). - foreach (arg; args) - escapeShellArgument!allocator(arg); - return assumeUnique(buf); -} +POSIX_specific: +If the process is terminated by a signal, the `status` field of +the return value will contain a negative number whose absolute +value is the signal number. (See $(LREF wait) for details.) -private auto escapeShellArgument(alias allocator)(in char[] arg) @safe nothrow +Throws: +$(LREF ProcessException) on failure to start the process.$(BR) +$(REF StdioException, std,stdio) on failure to capture output. +*/ +auto execute(scope const(char[])[] args, + const string[string] env = null, + Config config = Config.none, + size_t maxOutput = size_t.max, + scope const(char)[] workDir = null) + @safe { - // The unittest for this function requires special - // preparation - see below. - - version (Windows) - return escapeWindowsArgumentImpl!allocator(arg); - else - return escapePosixArgumentImpl!allocator(arg); + return executeImpl!pipeProcess(args, env, config, maxOutput, workDir); } -/** -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 +/// ditto +auto execute(scope const(char)[] program, + const string[string] env = null, + Config config = Config.none, + size_t maxOutput = size_t.max, + scope const(char)[] workDir = null) + @safe { - // 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); + return executeImpl!pipeProcess(program, env, config, maxOutput, workDir); } - -private char[] charAllocator(size_t size) @safe pure nothrow +/// ditto +auto executeShell(scope const(char)[] command, + const string[string] env = null, + Config config = Config.none, + size_t maxOutput = size_t.max, + scope const(char)[] workDir = null, + string shellPath = nativeShell) + @safe { - return new char[size]; + return executeImpl!pipeShell(command, + env, + config, + maxOutput, + workDir, + shellPath); } - -private char[] escapeWindowsArgumentImpl(alias allocator)(in char[] arg) - @safe nothrow -if (is(typeof(allocator(size_t.init)[0] = char.init))) +// 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, + scope const(char)[] workDir = null, + ExtraPipeFuncArgs extraArgs = ExtraPipeFuncArgs.init) + @trusted //TODO: @safe { - // 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 + import std.algorithm.comparison : min; + import std.array : appender; + import std.typecons : Tuple; - // Check if the string needs to be escaped, - // and calculate the total string size. + auto redirect = (config.flags & Config.Flags.stderrPassThrough) + ? Redirect.stdout + : Redirect.stdout | Redirect.stderrToStdout; - // 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; + auto p = pipeFunc(commandLine, redirect, + env, config, workDir, extraArgs); - foreach_reverse (char c; arg) + auto a = appender!string; + enum size_t defaultChunkSize = 4096; + immutable chunkSize = min(maxOutput, defaultChunkSize); + + // Store up to maxOutput bytes in a. + foreach (ubyte[] chunk; p.stdout.byChunk(chunkSize)) { - if (c == '"') - { - needEscape = true; - escaping = true; - size++; - } - else - if (c == '\\') - { - if (escaping) - size++; - } + immutable size_t remain = maxOutput - a.data.length; + + if (chunk.length < remain) a.put(chunk); else { - if (c == ' ' || c == '\t') - needEscape = true; - escaping = false; + a.put(chunk[0 .. remain]); + break; } } + // Exhaust the stream, if necessary. + foreach (ubyte[] chunk; p.stdout.byChunk(defaultChunkSize)) { } - 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 Tuple!(int, "status", string, "output")(wait(p.pid), a.data); +} - return buf; +@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"); } -version (Windows) version (unittest) +@safe unittest { - import core.stdc.stddef; - import core.stdc.wchar_ : wcslen; - import core.sys.windows.shellapi : CommandLineToArgvW; - import core.sys.windows.windows; - import std.array; + 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); +} - 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 +{ + // Temporarily disable output to stderr so as to not spam the build log. + import std.stdio : stderr; + import std.typecons : Tuple; + import std.file : readText, exists, remove; + import std.traits : ReturnType; - @system unittest + ReturnType!executeShell r; + auto tmpname = uniqueTempPath; + scope(exit) if (exists(tmpname)) remove(tmpname); + auto t = stderr; + // Open a new scope to minimize code ran with stderr redirected. { - 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]); - } + stderr.open(tmpname, "w"); + scope(exit) stderr = t; + r = executeShell("echo D rox>&2", null, Config.stderrPassThrough); } + assert(r.status == 0); + assert(r.output.empty); + auto witness = readText(tmpname); + import std.ascii : newline; + assert(witness == "D rox" ~ newline, "'" ~ witness ~ "'"); } -private string escapePosixArgument(in char[] arg) @trusted pure nothrow +@safe unittest { - import std.exception : assumeUnique; - auto buf = escapePosixArgumentImpl!charAllocator(arg); - return assumeUnique(buf); + 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"]); + } } -private char[] escapePosixArgumentImpl(alias allocator)(in char[] arg) - @safe nothrow -if (is(typeof(allocator(size_t.init)[0] = char.init))) +/// An exception that signals a problem with starting or waiting for a process. +class ProcessException : Exception { - // '\'' means: close quoted part of argument, append an escaped - // single quote, and reopen quotes - - // Below code is equivalent to: - // return `'` ~ std.array.replace(arg, `'`, `'\''`) ~ `'`; + import std.exception : basicExceptionCtors; + mixin basicExceptionCtors; - size_t size = 1 + arg.length + 1; - foreach (char c; arg) - if (c == '\'') - size += 3; + // 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); + } - 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); + // 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); + } - return buf; + // 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); + } } -/** -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. +/** +Determines the path to the current user's preferred command interpreter. - if (fileName.length && fileName[0] == '&') - return cast(string)(`".\` ~ fileName ~ '"'); +On Windows, this function returns the contents of the COMSPEC environment +variable, if it exists. Otherwise, it returns the result of $(LREF nativeShell). - return cast(string)('"' ~ fileName ~ '"'); - } - else - return escapePosixArgument(fileName); +On POSIX, `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); } -// Loop generating strings with random characters -//version = unittest_burnin; +/** +The platform-specific native shell path. -version (unittest_burnin) -@system unittest +This function returns `"cmd.exe"` on Windows, `"/bin/sh"` on POSIX, and +`"/system/bin/sh"` on Android. +*/ +@property string nativeShell() @safe @nogc pure nothrow { - // 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 + version (Windows) return "cmd.exe"; + else version (Android) return "/system/bin/sh"; + else version (Posix) return "/bin/sh"; +} - auto helper = absolutePath("std_process_unittest_helper"); - assert(executeShell(helper ~ " hello").output.split("\0")[1..$] == ["hello"], "Helper malfunction"); +// 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"; - void test(string[] s, string fn) +// 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 (StdUnittest) +private struct TestScript +{ + this(string code) @system { - string e; - string[] g; - - e = escapeShellCommand(helper ~ s); + // @system due to chmod + import std.ascii : newline; + import std.file : write; + version (Windows) { - 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..$]; + auto ext = ".cmd"; + auto firstLine = "@echo off"; } - 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); + else version (Posix) { - 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..$]; + auto ext = ""; + auto firstLine = "#!" ~ nativeShell; } - 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)) + path = uniqueTempPath()~ext; + write(path, firstLine ~ newline ~ code ~ newline); + version (Posix) { - 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; + import core.sys.posix.sys.stat : chmod; + import std.conv : octal; + chmod(path.tempCString(), octal!777); } + } - // generate filename - string fn; - foreach (l; 0 .. uniform(1, 10)) + ~this() + { + import std.file : remove, exists; + if (!path.empty && exists(path)) { - dchar c; - while (true) + try { remove(path); } + catch (Exception e) { - 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; + debug std.stdio.stderr.writeln(e.msg); } - - fn ~= c; } - fn = fn[0..$/2] ~ "_testfile_" ~ fn[$/2..$]; - - test(args, fn); } + + string path; } // ============================================================================= -// Environment variable manipulation. +// 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. +*/ + /** -Manipulates _environment variables using an associative-array-like -interface. +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")); +--- -This class contains only static methods, and cannot be instantiated. -See below for examples of use. +Concatenate multiple `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). */ -abstract final class environment +string escapeShellCommand(scope const(char[])[] args...) @safe pure { -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 + if (args.empty) + return null; + version (Windows) { - import std.exception : enforce; - string value; - enforce(getImpl(name, value), "Environment variable not found: "~name); - return value; + // 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; } - - /** - 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) + version (Posix) { - // 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. + return escapeShellCommandString(escapeShellArguments(args)); } - --- +} - 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; - } +@safe unittest +{ + // This is a simple unit test without any special requirements, + // in addition to the unittest_burnin one below which requires + // special preparation. - /** - 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. + 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' ''\''"^\'` + }, + ]; - If the variable does not exist, it will be created. If it already exists, - it will be overwritten. - --- - environment["foo"] = "bar"; - --- + foreach (test; tests) + version (Windows) + assert(escapeShellCommand(test.args) == test.windows); + else + assert(escapeShellCommand(test.args) == test.posix ); +} - Throws: - $(OBJECTREF Exception) if the environment variable could not be added - (e.g. if the name is invalid). +private string escapeShellCommandString(return scope string command) @safe pure +{ + version (Windows) + return escapeWindowsShellCommand(command); + else + return command; +} - 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) +private string escapeWindowsShellCommand(scope const(char)[] command) @safe pure +{ + import std.array : appender; + auto result = appender!string(); + result.reserve(command.length); + + foreach (c; command) + switch (c) { - 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); + 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); } - else version (Windows) + return result.data; +} + +private string escapeShellArguments(scope const(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 { - import std.exception : enforce; - enforce( - SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW()), - sysErrorString(GetLastError()) - ); - return value; + auto p = buf.length; + buf.length = buf.length + 1 + size; + buf[p++] = ' '; + return buf[p .. p+size]; } - else static assert(0); } - /** - Removes the environment variable with the given $(D name). + foreach (arg; args) + escapeShellArgument!allocator(arg); + return assumeUnique(buf); +} + +private auto escapeShellArgument(alias allocator)(scope const(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(scope const(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); +} - 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); - } +private char[] charAllocator(size_t size) @safe pure nothrow +{ + return new char[size]; +} - /** - 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. +private char[] escapeWindowsArgumentImpl(alias allocator)(scope const(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 - Example: - ------------- - // good usage - if ("MY_ENV_FLAG" in environment) - doSomething(); + // Check if the string needs to be escaped, + // and calculate the total string size. - // bad usage - if ("MY_ENV_VAR" in environment) - doSomething(environment["MY_ENV_VAR"]); + // 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; - // do this instead - if (auto var = environment.get("MY_ENV_VAR")) - doSomething(var); - ------------- - */ - bool opBinaryRight(string op : "in")(in char[] name) @trusted + foreach_reverse (char c; arg) { - version (Posix) - return core.sys.posix.stdlib.getenv(name.tempCString()) !is null; - else version (Windows) + if (c == '"') { - 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); + needEscape = true; + escaping = true; + size++; } - 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) + else + if (c == '\\') { - 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; - } + if (escaping) + size++; } - else version (Windows) + else { - import std.exception : enforce; - import std.uni : toUpper; - auto envBlock = GetEnvironmentStringsW(); - enforce(envBlock, "Failed to retrieve environment variables."); - scope(exit) FreeEnvironmentStringsW(envBlock); + if (c == ' ' || c == '\t') + needEscape = true; + escaping = false; + } + } - for (int i=0; envBlock[i] != '\0'; ++i) - { - auto start = i; - while (envBlock[i] != '=') ++i; - immutable name = toUTF8(toUpper(envBlock[start .. i])); + 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; - start = i+1; - while (envBlock[i] != '\0') ++i; + if (!needEscape) + return allocator(arg.length)[] = arg; - // 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; + // Construct result string. - // 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; + 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 (StdUnittest) +{ private: - // Retrieves the environment variable, returns false on failure. - bool getImpl(in char[] name, out string value) @trusted + import core.stdc.stddef; + import core.stdc.wchar_ : wcslen; + import core.sys.windows.shellapi : CommandLineToArgvW; + import core.sys.windows.winbase; + import core.sys.windows.winnt; + import std.array; + + string[] parseCommandLine(string line) { - 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.algorithm.iteration : map; + import std.array : array; + import std.conv : to; + auto lpCommandLine = (to!(WCHAR[])(line) ~ '\0').ptr; + int numArgs; + auto args = CommandLineToArgvW(lpCommandLine, &numArgs); + scope(exit) LocalFree(args); + return args[0 .. numArgs] + .map!(arg => to!string(arg[0 .. wcslen(arg)])) + .array(); + } - import std.conv : to; - const namezTmp = name.tempCStringW(); - WCHAR[] buf; + @system unittest + { + import std.conv : text; + string[] testStrings = [ + `Hello`, + `Hello, world`, + `Hello, "world"`, + `C:\`, + `C:\dmd`, + `C:\Program Files\`, + ]; - // 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; + 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("_", ""); - 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) + foreach (s; testStrings) { - 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; + 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]); } - else static assert(0); } } -@safe unittest +private string escapePosixArgument(scope const(char)[] arg) @trusted pure nothrow { - import std.exception : assertThrown; - // New variable - environment["std_process"] = "foo"; - assert(environment["std_process"] == "foo"); - assert("std_process" in environment); + import std.exception : assumeUnique; + auto buf = escapePosixArgumentImpl!charAllocator(arg); + return assumeUnique(buf); +} - // Set variable again (also tests length 1 case) - environment["std_process"] = "b"; - assert(environment["std_process"] == "b"); - assert("std_process" in environment); +private char[] escapePosixArgumentImpl(alias allocator)(scope const(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 - // Remove variable - environment.remove("std_process"); - assert("std_process" !in environment); + // Below code is equivalent to: + // return `'` ~ std.array.replace(arg, `'`, `'\''`) ~ `'`; - // Remove again, should succeed - environment.remove("std_process"); - assert("std_process" !in environment); + size_t size = 1 + arg.length + 1; + foreach (char c; arg) + if (c == '\'') + size += 3; - // Throw on not found. - assertThrown(environment["std_process"]); + 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); - // get() without default value - assert(environment.get("std_process") is null); + return buf; +} - // get() with default value - assert(environment.get("std_process", "baz") == "baz"); +/** +Escapes a filename to be used for shell redirection with $(LREF spawnShell), +$(LREF pipeShell) or $(LREF executeShell). +*/ +string escapeShellFileName(scope const(char)[] fileName) @trusted pure nothrow +{ + // The unittest for this function requires special + // preparation - see below. - // 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); + 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. - // Important to do the following round-trip after the previous test - // because it tests toAA with an empty var + if (fileName.length && fileName[0] == '&') + return cast(string)(`".\` ~ fileName ~ '"'); - // Convert to associative array - auto aa = environment.toAA(); - assert(aa.length > 0); - foreach (n, v; aa) + 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) { - // 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). + string e; + string[] g; - assert(v == environment[n]); + 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])); } - // ... and back again. - foreach (n, v; aa) - environment[n] = v; + 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(); - // Complete the roundtrip - auto aa2 = environment.toAA(); - import std.conv : text; - assert(aa == aa2, text(aa, " != ", aa2)); - assert("std_process" in environment); + 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; + } - // Setting null must have the same effect as remove - environment["std_process"] = null; - assert("std_process" !in environment); -} + // 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); + } +} // ============================================================================= // Everything below this line was part of the old std.process, and most of @@ -3744,7 +4073,7 @@ private: /* -Copyright: Copyright Digital Mars 2007 - 2009. +Copyright: Copyright The D Language Foundation 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), @@ -3752,7 +4081,7 @@ Authors: $(HTTP digitalmars.com, Walter Bright), Source: $(PHOBOSSRC std/_process.d) */ /* - Copyright Digital Mars 2007 - 2009. + Copyright The D Language Foundation 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) @@ -3772,11 +4101,6 @@ 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) { @@ -3807,7 +4131,7 @@ private void toAStringz(in string[] a, const(char)**az) // 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 **); +version (Windows) extern(C) int spawnvp(int, scope const(char) *, scope const(char*)*); alias P_WAIT = _P_WAIT; alias P_NOWAIT = _P_NOWAIT; @@ -3816,14 +4140,14 @@ alias P_NOWAIT = _P_NOWAIT; version (StdDdoc) { /** - Replaces the current process by executing a command, $(D pathname), with - the arguments in $(D argv). + Replaces the current process by executing a command, `pathname`, with + the arguments in `argv`. - $(BLUE This functions is Posix-Only.) + $(BLUE This function is Posix-Only.) - Typically, the first element of $(D argv) is + Typically, the first element of `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 + versions of `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. @@ -3835,7 +4159,7 @@ version (StdDdoc) 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) + to approximate the effect of `execv*` by using $(LREF spawnProcess) and terminating the current process once the child process has returned. For example: --- @@ -3847,20 +4171,20 @@ version (StdDdoc) } else version (Windows) { - import core.stdc.stdlib : _exit; - _exit(wait(spawnProcess(commandLine))); + import core.stdc.stdlib : _Exit; + _Exit(wait(spawnProcess(commandLine))); } --- - This is, however, NOT equivalent to POSIX' $(D execv*). For one thing, the + This is, however, NOT equivalent to POSIX' `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)) + $(LINK2 http://msdn.microsoft.com/en-us/library/431x4c1w.aspx,`__exec`) functions in Microsoft's C runtime library, and it is how D's now-deprecated - Windows $(D execv*) functions work. Example: + Windows `execv*` functions work. Example: --- auto commandLine = [ "program", "arg1", "arg2" ]; version (Posix) @@ -3907,15 +4231,18 @@ else version (Posix) // 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 **); + int execv(scope const(char) *, scope const(char *)*); + int execve(scope const(char)*, scope const(char*)*, scope const(char*)*); + int execvp(scope const(char)*, scope const(char*)*); + version (Windows) int execvpe(scope const(char)*, scope const(char*)*, scope const(char*)*); } private int execv_(in string pathname, in string[] argv) { + import core.exception : OutOfMemoryError; + import std.exception : enforce; auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); scope(exit) core.stdc.stdlib.free(argv_); toAStringz(argv, argv_); @@ -3925,9 +4252,13 @@ private int execv_(in string pathname, in string[] argv) private int execve_(in string pathname, in string[] argv, in string[] envp) { + import core.exception : OutOfMemoryError; + import std.exception : enforce; auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); scope(exit) core.stdc.stdlib.free(argv_); auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); + enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process."); scope(exit) core.stdc.stdlib.free(envp_); toAStringz(argv, argv_); @@ -3938,7 +4269,10 @@ private int execve_(in string pathname, in string[] argv, in string[] envp) private int execvp_(in string pathname, in string[] argv) { + import core.exception : OutOfMemoryError; + import std.exception : enforce; auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); scope(exit) core.stdc.stdlib.free(argv_); toAStringz(argv, argv_); @@ -3985,9 +4319,13 @@ version (Posix) } else version (Windows) { + import core.exception : OutOfMemoryError; + import std.exception : enforce; auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + enforce!OutOfMemoryError(argv_ !is null, "Out of memory in std.process."); scope(exit) core.stdc.stdlib.free(argv_); auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); + enforce!OutOfMemoryError(envp_ !is null, "Out of memory in std.process."); scope(exit) core.stdc.stdlib.free(envp_); toAStringz(argv, argv_); @@ -4006,88 +4344,98 @@ version (StdDdoc) /**************************************** * Start up the browser and set it to viewing the page at url. */ - void browse(const(char)[] url); + void browse(scope const(char)[] url); } else version (Windows) { - import core.sys.windows.windows; + import core.sys.windows.shellapi, core.sys.windows.winuser; pragma(lib,"shell32.lib"); - void browse(const(char)[] url) + void browse(scope const(char)[] url) nothrow @nogc @trusted { ShellExecuteW(null, "open", url.tempCStringW(), null, null, SW_SHOWNORMAL); } } -else version (OSX) +else version (Posix) { import core.stdc.stdio; import core.stdc.string; import core.sys.posix.unistd; - void browse(const(char)[] url) nothrow @nogc + void browse(scope const(char)[] url) nothrow @nogc @safe { - const(char)*[5] args; + const(char)*[3] args; - auto curl = url.tempCString(); - const(char)* browser = core.stdc.stdlib.getenv("BROWSER"); + // Trusted because it's called with a zero-terminated literal + const(char)* browser = (() @trusted => core.stdc.stdlib.getenv("BROWSER"))(); if (browser) - { browser = strdup(browser); + { + // String already zero-terminated + browser = (() @trusted => strdup(browser))(); args[0] = browser; - args[1] = curl; - args[2] = null; } else { - args[0] = "open".ptr; - args[1] = curl; - args[2] = null; + version (OSX) + { + args[0] = "open"; + } + else + { + //args[0] = "x-www-browser"; // doesn't work on some systems + args[0] = "xdg-open"; + } } + const buffer = url.tempCString(); // Retain buffer until end of scope + args[1] = buffer; + 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 + // Trusted because args and all entries are always zero-terminated + (() @trusted => + core.sys.posix.unistd.execvp(args[0], &args[0]) || + 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; + // Trusted because it's allocated via strdup above + (() @trusted => free(cast(void*) browser))(); - auto childpid = core.sys.posix.unistd.fork(); - if (childpid == 0) + version (StdUnittest) { - core.sys.posix.unistd.execvp(args[0], cast(char**) args.ptr); - perror(args[0]); // failed to execute - return; + // Verify that the test script actually suceeds + int status; + const check = (() @trusted => waitpid(childpid, &status, 0))(); + assert(check != -1); + assert(status == 0); } - if (browser) - free(cast(void*) browser); } } else static assert(0, "os not supported"); + +// Verify attributes are consistent between all implementations +@safe @nogc nothrow unittest +{ + if (false) + browse(""); +} + +version (Windows) { /* Doesn't use BROWSER */ } +else +@system unittest +{ + import std.conv : text; + import std.range : repeat; + immutable string url = text("http://", repeat('x', 249)); + + TestScript prog = `if [ "$1" != "` ~ url ~ `" ]; then exit 1; fi`; + environment["BROWSER"] = prog.path; + browse(url); +} diff --git a/libphobos/src/std/random.d b/libphobos/src/std/random.d index f4c64d4..441bc5d 100644 --- a/libphobos/src/std/random.d +++ b/libphobos/src/std/random.d @@ -3,7 +3,59 @@ /** Facilities for random number generation. -$(RED Disclaimer:) The _random number generators and API provided in this +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Uniform sampling) $(TD + $(LREF uniform) + $(LREF uniform01) + $(LREF uniformDistribution) +)) +$(TR $(TD Element sampling) $(TD + $(LREF choice) + $(LREF dice) +)) +$(TR $(TD Range sampling) $(TD + $(LREF randomCover) + $(LREF randomSample) +)) +$(TR $(TD Default Random Engines) $(TD + $(LREF rndGen) + $(LREF Random) + $(LREF unpredictableSeed) +)) +$(TR $(TD Linear Congruential Engines) $(TD + $(LREF MinstdRand) + $(LREF MinstdRand0) + $(LREF LinearCongruentialEngine) +)) +$(TR $(TD Mersenne Twister Engines) $(TD + $(LREF Mt19937) + $(LREF Mt19937_64) + $(LREF MersenneTwisterEngine) +)) +$(TR $(TD Xorshift Engines) $(TD + $(LREF Xorshift) + $(LREF XorshiftEngine) + $(LREF Xorshift32) + $(LREF Xorshift64) + $(LREF Xorshift96) + $(LREF Xorshift128) + $(LREF Xorshift160) + $(LREF Xorshift192) +)) +$(TR $(TD Shuffle) $(TD + $(LREF partialShuffle) + $(LREF randomShuffle) +)) +$(TR $(TD Traits) $(TD + $(LREF isSeedable) + $(LREF isUniformRNG) +)) +)) + +$(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 @@ -18,7 +70,7 @@ is the $(D_PARAM Mt19937) generator, which derives its name from 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 +linear congruential generators) such as `MinstdRand0` and `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. @@ -28,7 +80,7 @@ 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) +Source: $(PHOBOSSRC std/random.d) Macros: @@ -37,7 +89,7 @@ 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)) + 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 @@ -55,9 +107,24 @@ module std.random; import std.range.primitives; import std.traits; +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (D_InlineAsm_X86) version = InlineAsm_X86_Any; +version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; + /// @safe unittest { + import std.algorithm.comparison : among, equal; + import std.range : iota; + // seed a random generator with a constant auto rnd = Random(42); @@ -70,16 +137,51 @@ import std.traits; auto r = uniform(0.0L, 100.0L, rnd); assert(r >= 0 && r < 100); + // Sample from a custom type + enum Fruit { apple, mango, pear } + auto f = rnd.uniform!Fruit; + with(Fruit) + assert(f.among(apple, mango, pear)); + // Generate a 32-bit random number auto u = uniform!uint(rnd); static assert(is(typeof(u) == uint)); + + // Generate a random number in the range in the range [0, 1) + auto u2 = uniform01(rnd); + assert(u2 >= 0 && u2 < 1); + + // Select an element randomly + auto el = 10.iota.choice(rnd); + assert(0 <= el && el < 10); + + // Throw a dice with custom proportions + // 0: 20%, 1: 10%, 2: 60% + auto val = rnd.dice(0.2, 0.1, 0.6); + assert(0 <= val && val <= 2); + + auto rnd2 = MinstdRand0(42); + + // Select a random subsample from a range + assert(10.iota.randomSample(3, rnd2).equal([7, 8, 9])); + + // Cover all elements in an array in random order + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(10.iota.randomCover(rnd2).equal([7, 4, 2, 0, 1, 6, 8, 3, 9, 5])); + + // Shuffle an array + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert([0, 1, 2, 4, 5].randomShuffle(rnd2).equal([2, 0, 4, 5, 1])); } -version (unittest) +version (StdUnittest) { static import std.meta; + package alias Xorshift64_64 = XorshiftEngine!(ulong, 64, -12, 25, -27); + package alias Xorshift128_64 = XorshiftEngine!(ulong, 128, 23, -18, -5); package alias PseudoRngTypes = std.meta.AliasSeq!(MinstdRand0, MinstdRand, Mt19937, Xorshift32, Xorshift64, - Xorshift96, Xorshift128, Xorshift160, Xorshift192); + Xorshift96, Xorshift128, Xorshift160, Xorshift192, + Xorshift64_64, Xorshift128_64); } // Segments of the code in this file Copyright (c) 1997 by Rick Booth @@ -143,12 +245,8 @@ version (unittest) */ template isUniformRNG(Rng, ElementType) { - enum bool isUniformRNG = isInputRange!Rng && - is(typeof(Rng.front) == ElementType) && - is(typeof( - { - static assert(Rng.isUniformRandom); //tag - })); + enum bool isUniformRNG = .isUniformRNG!Rng && + is(std.range.primitives.ElementType!Rng == ElementType); } /** @@ -163,6 +261,43 @@ template isUniformRNG(Rng) })); } +/// +@safe unittest +{ + struct NoRng + { + @property uint front() {return 0;} + @property bool empty() {return false;} + void popFront() {} + } + static assert(!isUniformRNG!(NoRng)); + + 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)); +} + +@safe unittest +{ + // two-argument predicate should not require @property on `front` + // https://issues.dlang.org/show_bug.cgi?id=19837 + struct Rng + { + float front() {return 0;} + void popFront() {} + enum empty = false; + enum isUniformRandom = true; + } + static assert(isUniformRNG!(Rng, float)); +} + /** * Test if Rng is seedable. The overload * taking a SeedType also makes sure that the Rng can be seeded with SeedType. @@ -178,7 +313,8 @@ template isSeedable(Rng, SeedType) is(typeof( { Rng r = void; // can define a Rng object - r.seed(SeedType.init); // can seed a Rng + SeedType s = void; + r.seed(s); // can seed a Rng })); } @@ -189,11 +325,40 @@ template isSeedable(Rng) is(typeof( { Rng r = void; // can define a Rng object - r.seed(typeof(r.front).init); // can seed a Rng + alias SeedType = typeof(r.front); + SeedType s = void; + r.seed(s); // can seed a Rng })); } -@safe pure nothrow unittest +/// +@safe unittest +{ + struct validRng + { + @property uint front() {return 0;} + @property bool empty() {return false;} + void popFront() {} + + enum isUniformRandom = true; + } + 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(isSeedable!(seedRng, uint)); + static assert(!isSeedable!(seedRng, ulong)); + static assert(isSeedable!(seedRng)); +} + +@safe @nogc pure nothrow unittest { struct NoRng { @@ -255,11 +420,12 @@ template isSeedable(Rng) static assert(isUniformRNG!(seedRng, uint)); static assert(isUniformRNG!(seedRng)); static assert(isSeedable!(seedRng, uint)); + static assert(!isSeedable!(seedRng, ulong)); static assert(isSeedable!(seedRng)); } /** -Linear Congruential generator. +Linear Congruential generator. When m = 0, no modulus is used. */ struct LinearCongruentialEngine(UIntType, UIntType a, UIntType c, UIntType m) if (isUnsigned!UIntType) @@ -268,7 +434,7 @@ if (isUnsigned!UIntType) 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). + /// Lowest generated value (`1` if $(D c == 0), `0` otherwise). enum UIntType min = ( c == 0 ? 1 : 0 ); /// Highest generated value ($(D modulus - 1)). enum UIntType max = m - 1; @@ -360,9 +526,9 @@ The parameters of this distribution. The random number is $(D_PARAM x /** Constructs a $(D_PARAM LinearCongruentialEngine) generator seeded with -$(D x0). +`x0`. */ - this(UIntType x0) @safe pure + this(UIntType x0) @safe pure nothrow @nogc { seed(x0); } @@ -370,15 +536,15 @@ $(D x0). /** (Re)seeds the generator. */ - void seed(UIntType x0 = 1) @safe pure + void seed(UIntType x0 = 1) @safe pure nothrow @nogc { + _x = modulus ? (x0 % modulus) : x0; static if (c == 0) { - import std.exception : enforce; - enforce(x0, "Invalid (zero) seed for " - ~ LinearCongruentialEngine.stringof); + //Necessary to prevent generator from outputting an endless series of zeroes. + if (_x == 0) + _x = max; } - _x = modulus ? (x0 % modulus) : x0; popFront(); } @@ -427,33 +593,76 @@ $(D x0). } /// - @property typeof(this) save() @safe pure nothrow @nogc + @property typeof(this) save() const @safe pure nothrow @nogc { return this; } /** -Always $(D false) (random generators are infinite ranges). +Always `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 + // https://issues.dlang.org/show_bug.cgi?id=21610 + static if (m) + { + private UIntType _x = (a + c) % m; + } + else { - return _x == rhs._x; + private UIntType _x = a + c; } +} + +/// Declare your own linear congruential engine +@safe unittest +{ + alias CPP11LCG = LinearCongruentialEngine!(uint, 48271, 0, 2_147_483_647); + + // seed with a constant + auto rnd = CPP11LCG(42); + auto n = rnd.front; // same for each run + assert(n == 2027382); +} + +/// Declare your own linear congruential engine +@safe unittest +{ + // glibc's LCG + alias GLibcLCG = LinearCongruentialEngine!(uint, 1103515245, 12345, 2_147_483_648); + + // Seed with an unpredictable value + auto rnd = GLibcLCG(unpredictableSeed); + auto n = rnd.front; // different across runs +} + +/// Declare your own linear congruential engine +@safe unittest +{ + // Visual C++'s LCG + alias MSVCLCG = LinearCongruentialEngine!(uint, 214013, 2531011, 0); + + // seed with a constant + auto rnd = MSVCLCG(1); + auto n = rnd.front; // same for each run + assert(n == 2745024); +} + +// Ensure that unseeded LCGs produce correct values +@safe unittest +{ + alias LGE = LinearCongruentialEngine!(uint, 10000, 19682, 19683); - private UIntType _x = m ? (a + c) % m : (a + c); + LGE rnd; + assert(rnd.front == 9999); } /** Define $(D_PARAM LinearCongruentialEngine) generators with well-chosen -parameters. $(D MinstdRand0) implements Park and Miller's "minimal +parameters. `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) +generator) that uses 16807 for the multiplier. `MinstdRand` implements a variant that has slightly better spectral behavior by using the multiplier 48271. Both generators are rather simplistic. */ @@ -462,17 +671,20 @@ alias MinstdRand0 = LinearCongruentialEngine!(uint, 16_807, 0, 2_147_483_647); alias MinstdRand = LinearCongruentialEngine!(uint, 48_271, 0, 2_147_483_647); /// -@safe unittest +@safe @nogc unittest { // seed with a constant auto rnd0 = MinstdRand0(1); - auto n = rnd0.front; // same for each run + auto n = rnd0.front; + // same for each run + assert(n == 16807); + // Seed with an unpredictable value rnd0.seed(unpredictableSeed); n = rnd0.front; // different across runs } -@safe unittest +@safe @nogc unittest { import std.range; static assert(isForwardRange!MinstdRand); @@ -487,7 +699,7 @@ alias MinstdRand = LinearCongruentialEngine!(uint, 48_271, 0, 2_147_483_647); // The correct numbers are taken from The Database of Integer Sequences // http://www.research.att.com/~njas/sequences/eisBTfry00128.txt - auto checking0 = [ + enum ulong[20] checking0 = [ 16807UL,282475249,1622650073,984943658,1144108930,470211272, 101027544,1457850878,1458777923,2007237709,823564440,1115438165, 1784484492,74243042,114807987,1137522503,1441282327,16531729, @@ -508,7 +720,7 @@ alias MinstdRand = LinearCongruentialEngine!(uint, 48_271, 0, 2_147_483_647); assert(rnd0.front == 1043618065); // Test MinstdRand - auto checking = [48271UL,182605794,1291394886,1914720637,2078669041, + enum ulong[6] checking = [48271UL,182605794,1291394886,1914720637,2078669041, 407355683]; //auto rnd = MinstdRand(1); MinstdRand rnd; @@ -526,15 +738,26 @@ alias MinstdRand = LinearCongruentialEngine!(uint, 48_271, 0, 2_147_483_647); assert(rnd.front == 399268537); // Check .save works - foreach (Type; std.meta.AliasSeq!(MinstdRand0, MinstdRand)) - { - auto rnd1 = Type(unpredictableSeed); - auto rnd2 = rnd1.save; + static foreach (Type; std.meta.AliasSeq!(MinstdRand0, MinstdRand)) + {{ + auto rnd1 = Type(123_456_789); + rnd1.popFront(); + // https://issues.dlang.org/show_bug.cgi?id=15853 + auto rnd2 = ((const ref Type a) => a.save())(rnd1); 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()); - } + for (auto i = 0; i < 3; i++, rnd1.popFront, rnd2.popFront) + assert(rnd1.front() == rnd2.front()); + }} +} + +@safe @nogc unittest +{ + auto rnd0 = MinstdRand0(MinstdRand0.modulus); + auto n = rnd0.front; + rnd0.popFront(); + assert(n != rnd0.front); } /** @@ -551,7 +774,7 @@ if (isUnsigned!UIntType) 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); + static assert(n <= ptrdiff_t.max); ///Mark this as a Rng enum bool isUniformRandom = true; @@ -675,7 +898,7 @@ Parameters for the generator. Implementation of the seeding mechanism, which can be used with an arbitrary `State` instance */ - private static void seedImpl(UIntType value, ref State mtState) + private static void seedImpl(UIntType value, ref State mtState) @nogc { mtState.data[$ - 1] = value; static if (this.max != UIntType.max) @@ -705,10 +928,10 @@ Parameters for the generator. Seeds a MersenneTwisterEngine object using an InputRange. Throws: - $(D Exception) if the InputRange didn't provide enough elements to seed the generator. + `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)) + void seed(T)(T range) if (isInputRange!T && is(immutable ElementType!T == immutable UIntType)) { this.seedImpl(range, this.state); } @@ -718,12 +941,12 @@ Parameters for the generator. 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)) + if (isInputRange!T && is(immutable ElementType!T == immutable UIntType)) { size_t j; for (j = 0; j < n && !range.empty; ++j, range.popFront()) { - sizediff_t idx = n - j - 1; + ptrdiff_t idx = n - j - 1; mtState.data[idx] = range.front; } @@ -735,7 +958,7 @@ Parameters for the generator. UnsignedStringBuf buf = void; string s = "MersenneTwisterEngine.seed: Input range didn't provide enough elements: Need "; - s ~= unsignedToTempString(n, buf, 10) ~ " elements."; + s ~= unsignedToTempString(n, buf) ~ " elements."; throw new Exception(s); } @@ -758,7 +981,7 @@ Parameters for the generator. Internal implementation of `popFront()`, which can be used with an arbitrary `State` instance */ - private static void popFrontImpl(ref State mtState) + private static void popFrontImpl(ref State mtState) @nogc { /* This function blends two nominally independent processes: (i) calculation of the next random @@ -772,12 +995,12 @@ Parameters for the generator. 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; + ptrdiff_t index = mtState.index; + ptrdiff_t next = index - 1; if (next < 0) next = n - 1; auto z = mtState.z; - sizediff_t conj = index - m; + ptrdiff_t conj = index - m; if (conj < 0) conj = index - m + n; @@ -819,23 +1042,36 @@ Parameters for the generator. } /// - @property typeof(this) save() @safe pure nothrow @nogc + @property typeof(this) save() @safe const pure nothrow @nogc { return this; } /** -Always $(D false). +Always `false`. */ enum bool empty = false; } +/// +@safe unittest +{ + // seed with a constant + Mt19937 gen; + auto n = gen.front; // same for each run + assert(n == 3499211612); + + // Seed with an unpredictable value + gen.seed(unpredictableSeed); + n = gen.front; // different across runs +} + /** -A $(D MersenneTwisterEngine) instantiated with the parameters of the +A `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 +generation unless memory is severely restricted, in which case a $(LREF LinearCongruentialEngine) would be the generator of choice. */ alias Mt19937 = MersenneTwisterEngine!(uint, 32, 624, 397, 31, @@ -844,11 +1080,13 @@ alias Mt19937 = MersenneTwisterEngine!(uint, 32, 624, 397, 31, 0xefc60000, 18, 1_812_433_253); /// -@safe unittest +@safe @nogc unittest { // seed with a constant Mt19937 gen; auto n = gen.front; // same for each run + assert(n == 3499211612); + // Seed with an unpredictable value gen.seed(unpredictableSeed); n = gen.front; // different across runs @@ -874,7 +1112,7 @@ alias Mt19937 = MersenneTwisterEngine!(uint, 32, 624, 397, 31, } /** -A $(D MersenneTwisterEngine) instantiated with the parameters of the +A `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. @@ -885,13 +1123,15 @@ alias Mt19937_64 = MersenneTwisterEngine!(ulong, 64, 312, 156, 31, 0xfff7eee000000000, 43, 6_364_136_223_846_793_005); /// -@safe unittest +@safe @nogc unittest { // Seed with a constant auto gen = Mt19937_64(12345); auto n = gen.front; // same for each run + assert(n == 6597103971274460346); + // Seed with an unpredictable value - gen.seed(unpredictableSeed); + gen.seed(unpredictableSeed!ulong); n = gen.front; // different across runs } @@ -903,13 +1143,7 @@ alias Mt19937_64 = MersenneTwisterEngine!(ulong, 64, 312, 156, 31, 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))))); + static assert(isSeedable!(Mt19937_64, typeof(map!((a) => unpredictableSeed!ulong)(repeat(0))))); Mt19937_64 gen; assert(gen.front == 14514284786278117030uL); popFrontN(gen, 9999); @@ -928,14 +1162,14 @@ alias Mt19937_64 = MersenneTwisterEngine!(ulong, 64, 312, 156, 31, Mt19937 gen; - assertThrown(gen.seed(map!((a) => unpredictableSeed)(repeat(0, 623)))); + assertThrown(gen.seed(map!((a) => 123_456_789U)(repeat(0, 623)))); - gen.seed(map!((a) => unpredictableSeed)(repeat(0, 624))); + gen.seed(123_456_789U.repeat(624)); //infinite Range - gen.seed(map!((a) => unpredictableSeed)(repeat(0))); + gen.seed(123_456_789U.repeat); } -@safe pure nothrow unittest +@safe @nogc pure nothrow unittest { uint a, b; { @@ -951,125 +1185,271 @@ alias Mt19937_64 = MersenneTwisterEngine!(ulong, 64, 312, 156, 31, assert(a != b); } -@safe unittest +@safe @nogc unittest { - import std.range; // Check .save works - foreach (Type; std.meta.AliasSeq!(Mt19937, Mt19937_64)) - { - auto gen1 = Type(unpredictableSeed); - auto gen2 = gen1.save; + static foreach (Type; std.meta.AliasSeq!(Mt19937, Mt19937_64)) + {{ + auto gen1 = Type(123_456_789); + gen1.popFront(); + // https://issues.dlang.org/show_bug.cgi?id=15853 + auto gen2 = ((const ref Type a) => a.save())(gen1); 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()); - } + for (auto i = 0; i < 100; i++, gen1.popFront, gen2.popFront) + assert(gen1.front() == gen2.front()); + }} } -@safe pure nothrow unittest //11690 +// https://issues.dlang.org/show_bug.cgi?id=11690 +@safe @nogc pure nothrow unittest { alias MT(UIntType, uint w) = MersenneTwisterEngine!(UIntType, w, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253); - ulong[] expectedFirstValue = [3499211612uL, 3499211612uL, + static immutable ulong[] expectedFirstValue = [3499211612uL, 3499211612uL, 171143175841277uL, 1145028863177033374uL]; - ulong[] expected10kValue = [4123659995uL, 4123659995uL, + static immutable ulong[] expected10kValue = [4123659995uL, 4123659995uL, 51991688252792uL, 3031481165133029945uL]; - foreach (i, R; std.meta.AliasSeq!(MT!(uint, 32), MT!(ulong, 32), MT!(ulong, 48), MT!(ulong, 64))) - { + static 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. +Implemented according to $(HTTP www.jstatsoft.org/v08/i14/paper, Xorshift RNGs) +(Marsaglia, 2003) when the size is small. For larger sizes the generator +uses Sebastino Vigna's optimization of using an index to avoid needing +to rotate the internal array. -/** - * 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) +Period is `2 ^^ nbits - 1` except for a legacy 192-bit uint version (see +note below). + +Params: + UIntType = Word size of this xorshift generator and the return type + of `opCall`. + nbits = The number of bits of state of this generator. This must be + a positive multiple of the size in bits of UIntType. If + nbits is large this struct may occupy slightly more memory + than this so it can use a circular counter instead of + shifting the entire array. + sa = The direction and magnitude of the 1st shift. Positive + means left, negative means right. + sb = The direction and magnitude of the 2nd shift. Positive + means left, negative means right. + sc = The direction and magnitude of the 3rd shift. Positive + means left, negative means right. + +Note: +For historical compatibility when `nbits == 192` and `UIntType` is `uint` +a legacy hybrid PRNG is used consisting of a 160-bit xorshift combined +with a 32-bit counter. This combined generator has period equal to the +least common multiple of `2^^160 - 1` and `2^^32`. + +Previous versions of `XorshiftEngine` did not provide any mechanism to specify +the directions of the shifts, taking each shift as an unsigned magnitude. +For backwards compatibility, because three shifts in the same direction +cannot result in a full-period XorshiftEngine, when all three of `sa`, `sb`, +`sc, are positive `XorshiftEngine` treats them as unsigned magnitudes and +uses shift directions to match the old behavior of `XorshiftEngine`. + +Not every set of shifts results in a full-period xorshift generator. +The template does not currently at compile-time perform a full check +for maximum period but in a future version might reject parameters +resulting in shorter periods. ++/ +struct XorshiftEngine(UIntType, uint nbits, int sa, int sb, int sc) +if (isUnsigned!UIntType && !(sa > 0 && sb > 0 && sc > 0)) { - 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."); + static assert(nbits > 0 && nbits % (UIntType.sizeof * 8) == 0, + "nbits must be an even multiple of "~UIntType.stringof + ~".sizeof * 8, not "~nbits.stringof~"."); + + static assert(!((sa >= 0) == (sb >= 0) && (sa >= 0) == (sc >= 0)) + && (sa * sb * sc != 0), + "shifts cannot be zero and cannot all be in same direction: cannot be [" + ~sa.stringof~", "~sb.stringof~", "~sc.stringof~"]."); + + static assert(sa != sb && sb != sc, + "consecutive shifts with the same magnitude and direction would partially or completely cancel!"); + + static assert(UIntType.sizeof == uint.sizeof || UIntType.sizeof == ulong.sizeof, + "XorshiftEngine currently does not support type " ~ UIntType.sizeof + ~ " because it does not have a `seed` implementation for arrays " + ~ " of element types other than uint and ulong."); public: ///Mark this as a Rng enum bool isUniformRandom = true; - /// Always $(D false) (random generators are infinite ranges). + /// Always `false` (random generators are infinite ranges). enum empty = false; /// Smallest generated value. - enum UIntType min = 0; + enum UIntType min = _state.length == 1 ? 1 : 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]; + // Legacy 192 bit uint hybrid counter/xorshift. + enum bool isLegacy192Bit = UIntType.sizeof == uint.sizeof && nbits == 192; + + // Shift magnitudes. + enum a = (sa < 0 ? -sa : sa); + enum b = (sb < 0 ? -sb : sb); + enum c = (sc < 0 ? -sc : sc); + + // Shift expressions to mix in. + enum shiftA(string expr) = `((`~expr~`) `~(sa > 0 ? `<< a)` : ` >>> a)`); + enum shiftB(string expr) = `((`~expr~`) `~(sb > 0 ? `<< b)` : ` >>> b)`); + enum shiftC(string expr) = `((`~expr~`) `~(sc > 0 ? `<< c)` : ` >>> c)`); + + enum N = nbits / (UIntType.sizeof * 8); + + // For N <= 2 it is strictly worse to use an index. + // Informal third-party benchmarks suggest that for `ulong` it is + // faster to use an index when N == 4. For `uint` we err on the side + // of not increasing the struct's size and only switch to the other + // implementation when N > 4. + enum useIndex = !isLegacy192Bit && (UIntType.sizeof >= ulong.sizeof ? N > 3 : N > 4); + static if (useIndex) + { + enum initialIndex = N - 1; + uint _index = initialIndex; + } + + static if (N == 1 && UIntType.sizeof <= uint.sizeof) + { + UIntType[N] _state = [cast(UIntType) 2_463_534_242]; + } + else static if (isLegacy192Bit) + { + UIntType[N] _state = [123_456_789, 362_436_069, 521_288_629, 88_675_123, 5_783_321, 6_615_241]; UIntType value_; } + else static if (N <= 5 && UIntType.sizeof <= uint.sizeof) + { + UIntType[N] _state = [ + cast(UIntType) 123_456_789, + cast(UIntType) 362_436_069, + cast(UIntType) 521_288_629, + cast(UIntType) 88_675_123, + cast(UIntType) 5_783_321][0 .. N]; + } else { - static assert(false, "Phobos Error: Xorshift has no instantiation rule for " - ~ to!string(bits) ~ " bits."); + UIntType[N] _state = () + { + static if (UIntType.sizeof < ulong.sizeof) + { + uint x0 = 123_456_789; + enum uint m = 1_812_433_253U; + } + else static if (UIntType.sizeof <= ulong.sizeof) + { + ulong x0 = 123_456_789; + enum ulong m = 6_364_136_223_846_793_005UL; + } + else + { + static assert(0, "Phobos Error: Xorshift has no instantiation rule for " + ~ UIntType.stringof); + } + enum uint rshift = typeof(x0).sizeof * 8 - 2; + UIntType[N] result = void; + foreach (i, ref e; result) + { + e = cast(UIntType) (x0 = (m * (x0 ^ (x0 >>> rshift)) + i + 1)); + if (e == 0) + e = cast(UIntType) (i + 1); + } + return result; + }(); } public: - /** - * Constructs a $(D XorshiftEngine) generator seeded with $(D_PARAM x0). - */ - this(UIntType x0) @safe pure nothrow @nogc + /++ + Constructs a `XorshiftEngine` generator seeded with $(D_PARAM x0). + + Params: + x0 = value used to deterministically initialize internal state + +/ + this()(UIntType x0) @safe pure nothrow @nogc { seed(x0); } - /** - * (Re)seeds the generator. - */ - void seed(UIntType x0) @safe pure nothrow @nogc + /++ + (Re)seeds the generator. + + Params: + x0 = value used to deterministically initialize internal state + +/ + 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); + static if (useIndex) + _index = initialIndex; - // All seeds must not be 0. - sanitizeSeeds(seeds_); + static if (UIntType.sizeof == uint.sizeof) + { + // Initialization routine from MersenneTwisterEngine. + foreach (uint i, ref e; _state) + { + e = (x0 = (1_812_433_253U * (x0 ^ (x0 >> 30)) + i + 1)); + // Xorshift requires merely that not every word of the internal + // array is 0. For historical compatibility the 32-bit word version + // has the stronger requirement that not any word of the state + // array is 0 after initial seeding. + if (e == 0) + e = (i + 1); + } + } + else static if (UIntType.sizeof == ulong.sizeof) + { + static if (N > 1) + { + // Initialize array using splitmix64 as recommended by Sebastino Vigna. + // By construction the array will not be all zeroes. + // http://xoroshiro.di.unimi.it/splitmix64.c + foreach (ref e; _state) + { + ulong z = (x0 += 0x9e37_79b9_7f4a_7c15UL); + z = (z ^ (z >>> 30)) * 0xbf58_476d_1ce4_e5b9UL; + z = (z ^ (z >>> 27)) * 0x94d0_49bb_1331_11ebUL; + e = z ^ (z >>> 31); + } + } + else + { + // Apply a transformation when N == 1 instead of just copying x0 + // directly because it's not unlikely that a user might initialize + // a PRNG with small counting numbers (e.g. 1, 2, 3) that have the + // statistically rare property of having only 1 or 2 non-zero bits. + // Additionally we need to ensure that the internal state is not + // entirely zero. + if (x0 != 0) + _state[0] = x0 * 6_364_136_223_846_793_005UL; + else + _state[0] = typeof(this).init._state[0]; + } + } + else + { + static assert(0, "Phobos Error: Xorshift has no `seed` implementation for " + ~ UIntType.stringof); + } popFront(); } @@ -1081,10 +1461,12 @@ if (isUnsigned!UIntType) @property UIntType front() const @safe pure nothrow @nogc { - static if (bits == 192) + static if (isLegacy192Bit) return value_; + else static if (!useIndex) + return _state[N-1]; else - return seeds_[size - 1]; + return _state[_index]; } @@ -1093,58 +1475,42 @@ if (isUnsigned!UIntType) */ 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) + alias s = _state; + static if (isLegacy192Bit) { - 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); + auto x = _state[0] ^ mixin(shiftA!`s[0]`); + static foreach (i; 0 .. N-2) + s[i] = s[i + 1]; + s[N-2] = s[N-2] ^ mixin(shiftC!`s[N-2]`) ^ x ^ mixin(shiftB!`x`); + value_ = s[N-2] + (s[N-1] += 362_437); } - else static if (bits == 160) + else static if (N == 1) { - 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); + s[0] ^= mixin(shiftA!`s[0]`); + s[0] ^= mixin(shiftB!`s[0]`); + s[0] ^= mixin(shiftC!`s[0]`); } - else static if (bits == 192) + else static if (!useIndex) { - 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); + auto x = s[0] ^ mixin(shiftA!`s[0]`); + static foreach (i; 0 .. N-1) + s[i] = s[i + 1]; + s[N-1] = s[N-1] ^ mixin(shiftC!`s[N-1]`) ^ x ^ mixin(shiftB!`x`); } else { - static assert(false, "Phobos Error: Xorshift has no popFront() update for " - ~ to!string(bits) ~ " bits."); + assert(_index < N); // Invariant. + const sIndexMinus1 = s[_index]; + static if ((N & (N - 1)) == 0) + _index = (_index + 1) & typeof(_index)(N - 1); + else + { + if (++_index >= N) + _index = 0; + } + auto x = s[_index]; + x ^= mixin(shiftA!`x`); + s[_index] = sIndexMinus1 ^ mixin(shiftC!`sIndexMinus1`) ^ x ^ mixin(shiftB!`x`); } } @@ -1153,49 +1519,43 @@ if (isUnsigned!UIntType) * Captures a range state. */ @property - typeof(this) save() @safe pure nothrow @nogc + typeof(this) save() const @safe pure nothrow @nogc { return this; } +private: + // Workaround for a DScanner bug. If we remove this `private:` DScanner + // gives erroneous warnings about missing documentation for public symbols + // later in the module. +} - /** - * 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); +/// ditto +template XorshiftEngine(UIntType, int bits, int a, int b, int c) +if (isUnsigned!UIntType && a > 0 && b > 0 && c > 0) +{ + // Compatibility with old parameterizations without explicit shift directions. + static if (bits == UIntType.sizeof * 8) + alias XorshiftEngine = .XorshiftEngine!(UIntType, bits, a, -b, c);//left, right, left + else static if (bits == 192 && UIntType.sizeof == uint.sizeof) + alias XorshiftEngine = .XorshiftEngine!(UIntType, bits, -a, b, c);//right, left, left + else + alias XorshiftEngine = .XorshiftEngine!(UIntType, bits, a, -b, -c);//left, right, right +} - assert(seeds == [1, 2, 3, 4]); - } - } +/// +@safe unittest +{ + alias Xorshift96 = XorshiftEngine!(uint, 96, 10, 5, 26); + auto rnd = Xorshift96(42); + auto num = rnd.front; // same for each run + assert(num == 2704588748); } /** - * 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. + * Define `XorshiftEngine` generators with well-chosen parameters. See each bits examples of "Xorshift RNGs". + * `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 @@ -1206,18 +1566,19 @@ alias Xorshift192 = XorshiftEngine!(uint, 192, 2, 1, 4); /// ditto alias Xorshift = Xorshift128; /// ditto /// -@safe unittest +@safe @nogc unittest { // Seed with a constant auto rnd = Xorshift(1); auto num = rnd.front; // same for each run + assert(num == 1405313047); // Seed with an unpredictable value rnd.seed(unpredictableSeed); num = rnd.front; // different across rnd } -@safe unittest +@safe @nogc unittest { import std.range; static assert(isForwardRange!Xorshift); @@ -1226,8 +1587,10 @@ alias Xorshift = Xorshift128; /// ditto static assert(isSeedable!Xorshift); static assert(isSeedable!(Xorshift, uint)); + static assert(Xorshift32.min == 1); + // Result from reference implementation. - auto checking = [ + static ulong[][] checking = [ [2463534242UL, 901999875, 3371835698, 2675058524, 1053936272, 3811264849, 472493137, 3856898176, 2131710969, 2312157505], [362436069UL, 2113136921, 19051112, 3010520417, 951284840, 1213972223, @@ -1239,10 +1602,19 @@ alias Xorshift = Xorshift128; /// ditto [5783321UL, 393427209, 1947109840, 565829276, 1006220149, 971147905, 1436324242, 2800460115, 1484058076, 3823330032], [0UL, 246875399, 3690007200, 1264581005, 3906711041, 1866187943, 2481925219, - 2464530826, 1604040631, 3653403911] + 2464530826, 1604040631, 3653403911], + [16749904790159980466UL, 14489774923612894650UL, 148813570191443371UL, + 6529783008780612886UL, 10182425759614080046UL, 16549659571055687844UL, + 542957868271744939UL, 9459127085596028450UL, 16001049981702441780UL, + 7351634712593111741], + [14750058843113249055UL, 17731577314455387619UL, 1314705253499959044UL, + 3113030620614841056UL, 9468075444678629182UL, 13962152036600088141UL, + 9030205609946043947UL, 1856726150434672917UL, 8098922200110395314UL, + 2772699174618556175UL], ]; - alias XorshiftTypes = std.meta.AliasSeq!(Xorshift32, Xorshift64, Xorshift96, Xorshift128, Xorshift160, Xorshift192); + alias XorshiftTypes = std.meta.AliasSeq!(Xorshift32, Xorshift64, Xorshift96, + Xorshift128, Xorshift160, Xorshift192, Xorshift64_64, Xorshift128_64); foreach (I, Type; XorshiftTypes) { @@ -1258,12 +1630,15 @@ alias Xorshift = Xorshift128; /// ditto // Check .save works foreach (Type; XorshiftTypes) { - auto rnd1 = Type(unpredictableSeed); - auto rnd2 = rnd1.save; + auto rnd1 = Type(123_456_789); + rnd1.popFront(); + // https://issues.dlang.org/show_bug.cgi?id=15853 + auto rnd2 = ((const ref Type a) => a.save())(rnd1); 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()); + for (auto i = 0; i <= Type.sizeof / 4; i++, rnd1.popFront, rnd2.popFront) + assert(rnd1.front() == rnd2.front()); } } @@ -1273,15 +1648,124 @@ alias Xorshift = Xorshift128; /// ditto * object is compatible with all the pseudo-random number generators * available. It is enabled only in unittest mode. */ -@safe unittest +@safe @nogc unittest { foreach (Rng; PseudoRngTypes) { static assert(isUniformRNG!Rng); - auto rng = Rng(unpredictableSeed); + auto rng = Rng(123_456_789); + } +} + +version (CRuntime_Bionic) + version = SecureARC4Random; // ChaCha20 +version (Darwin) + version = SecureARC4Random; // AES +version (OpenBSD) + version = SecureARC4Random; // ChaCha20 +version (NetBSD) + version = SecureARC4Random; // ChaCha20 + +version (CRuntime_UClibc) + version = LegacyARC4Random; // ARC4 +version (FreeBSD) + version = LegacyARC4Random; // ARC4 +version (DragonFlyBSD) + version = LegacyARC4Random; // ARC4 +version (BSD) + version = LegacyARC4Random; // Unknown implementation + +// For the current purpose of unpredictableSeed the difference between +// a secure arc4random implementation and a legacy implementation is +// unimportant. The source code documents this distinction in case in the +// future Phobos is altered to require cryptographically secure sources +// of randomness, and also so other people reading this source code (as +// Phobos is often looked to as an example of good D programming practices) +// do not mistakenly use insecure versions of arc4random in contexts where +// cryptographically secure sources of randomness are needed. + +// Performance note: ChaCha20 is about 70% faster than ARC4, contrary to +// what one might assume from it being more secure. + +version (SecureARC4Random) + version = AnyARC4Random; +version (LegacyARC4Random) + version = AnyARC4Random; + +version (AnyARC4Random) +{ + extern(C) private @nogc nothrow + { + uint arc4random() @safe; + void arc4random_buf(scope void* buf, size_t nbytes) @system; } } +else +{ + private ulong bootstrapSeed() @nogc nothrow + { + // https://issues.dlang.org/show_bug.cgi?id=19580 + // previously used `ulong result = void` to start with an arbitary value + // but using an uninitialized variable's value is undefined behavior + // and enabled unwanted optimizations on non-DMD compilers. + ulong result; + enum ulong m = 0xc6a4_a793_5bd1_e995UL; // MurmurHash2_64A constant. + void updateResult(ulong x) + { + x *= m; + x = (x ^ (x >>> 47)) * m; + result = (result ^ x) * m; + } + import core.thread : getpid, Thread; + import core.time : MonoTime; + + updateResult(cast(ulong) cast(void*) Thread.getThis()); + updateResult(cast(ulong) getpid()); + updateResult(cast(ulong) MonoTime.currTime.ticks); + result = (result ^ (result >>> 47)) * m; + return result ^ (result >>> 47); + } + // If we don't have arc4random and we don't have RDRAND fall back to this. + private ulong fallbackSeed() @nogc nothrow + { + // Bit avalanche function from MurmurHash3. + static ulong fmix64(ulong k) @nogc nothrow pure @safe + { + k = (k ^ (k >>> 33)) * 0xff51afd7ed558ccd; + k = (k ^ (k >>> 33)) * 0xc4ceb9fe1a85ec53; + return k ^ (k >>> 33); + } + // Using SplitMix algorithm with constant gamma. + // Chosen gamma is the odd number closest to 2^^64 + // divided by the silver ratio (1.0L + sqrt(2.0L)). + enum gamma = 0x6a09e667f3bcc909UL; + import core.atomic : has64BitCAS; + static if (has64BitCAS) + { + import core.atomic : MemoryOrder, atomicLoad, atomicOp, atomicStore, cas; + shared static ulong seed; + shared static bool initialized; + if (0 == atomicLoad!(MemoryOrder.raw)(initialized)) + { + cas(&seed, 0UL, fmix64(bootstrapSeed())); + atomicStore!(MemoryOrder.rel)(initialized, true); + } + return fmix64(atomicOp!"+="(seed, gamma)); + } + else + { + static ulong seed; + static bool initialized; + if (!initialized) + { + seed = fmix64(bootstrapSeed()); + initialized = true; + } + return fmix64(seed += gamma); + } + } +} /** A "good" seed for initializing random number engines. Initializing @@ -1290,24 +1774,135 @@ random number sequences every run. Returns: A single unsigned integer seed value, different on each successive call +Note: +In general periodically 'reseeding' a PRNG does not improve its quality +and in some cases may harm it. For an extreme example the Mersenne +Twister has `2 ^^ 19937 - 1` distinct states but after `seed(uint)` is +called it can only be in one of `2 ^^ 32` distinct states regardless of +how excellent the source of entropy is. */ -@property uint unpredictableSeed() @trusted +@property uint unpredictableSeed() @trusted nothrow @nogc { - import core.thread : Thread, getpid, MonoTime; - static bool seeded; - static MinstdRand0 rand; - if (!seeded) + version (AnyARC4Random) { - uint threadID = cast(uint) cast(void*) Thread.getThis(); - rand.seed((getpid() + threadID) ^ cast(uint) MonoTime.currTime.ticks); - seeded = true; + return arc4random(); + } + else + { + version (InlineAsm_X86_Any) + { + import core.cpuid : hasRdrand; + if (hasRdrand) + { + uint result; + asm @nogc nothrow + { + db 0x0f, 0xc7, 0xf0; // rdrand EAX + jnc LnotUsingRdrand; + // Some AMD CPUs shipped with bugs where RDRAND could fail + // but still set the carry flag to 1. In those cases the + // output will be -1. + cmp EAX, 0xffff_ffff; + je LnotUsingRdrand; + mov result, EAX; + } + return result; + } + LnotUsingRdrand: + } + return cast(uint) fallbackSeed(); } - rand.popFront(); - return cast(uint) (MonoTime.currTime.ticks ^ rand.front); +} + +/// ditto +template unpredictableSeed(UIntType) +if (isUnsigned!UIntType) +{ + static if (is(UIntType == uint)) + alias unpredictableSeed = .unpredictableSeed; + else static if (!is(Unqual!UIntType == UIntType)) + alias unpredictableSeed = .unpredictableSeed!(Unqual!UIntType); + else + /// ditto + @property UIntType unpredictableSeed() @nogc nothrow @trusted + { + version (AnyARC4Random) + { + static if (UIntType.sizeof <= uint.sizeof) + { + return cast(UIntType) arc4random(); + } + else + { + UIntType result = void; + arc4random_buf(&result, UIntType.sizeof); + return result; + } + } + else + { + version (InlineAsm_X86_Any) + { + import core.cpuid : hasRdrand; + if (hasRdrand) + { + static if (UIntType.sizeof <= uint.sizeof) + { + uint result; + asm @nogc nothrow + { + db 0x0f, 0xc7, 0xf0; // rdrand EAX + jnc LnotUsingRdrand; + // Some AMD CPUs shipped with bugs where RDRAND could fail + // but still set the carry flag to 1. In those cases the + // output will be -1. + cmp EAX, 0xffff_ffff; + je LnotUsingRdrand; + mov result, EAX; + } + return cast(UIntType) result; + } + else version (D_InlineAsm_X86_64) + { + ulong result; + asm @nogc nothrow + { + db 0x48, 0x0f, 0xc7, 0xf0; // rdrand RAX + jnc LnotUsingRdrand; + // Some AMD CPUs shipped with bugs where RDRAND could fail + // but still set the carry flag to 1. In those cases the + // output will be -1. + cmp RAX, 0xffff_ffff_ffff_ffff; + je LnotUsingRdrand; + mov result, RAX; + } + return result; + } + else + { + uint resultLow, resultHigh; + asm @nogc nothrow + { + db 0x0f, 0xc7, 0xf0; // rdrand EAX + jnc LnotUsingRdrand; + mov resultLow, EAX; + db 0x0f, 0xc7, 0xf0; // rdrand EAX + jnc LnotUsingRdrand; + mov resultHigh, EAX; + } + if (resultLow != uint.max || resultHigh != uint.max) // Protect against AMD RDRAND bug. + return ((cast(ulong) resultHigh) << 32) ^ resultLow; + } + } + LnotUsingRdrand: + } + return cast(UIntType) fallbackSeed(); + } + } } /// -@safe unittest +@safe @nogc unittest { auto rnd = Random(unpredictableSeed); auto n = rnd.front; @@ -1324,7 +1919,7 @@ method being used. alias Random = Mt19937; -@safe unittest +@safe @nogc unittest { static assert(isUniformRNG!Random); static assert(isUniformRNG!(Random, uint)); @@ -1340,17 +1935,18 @@ and initialized to an unpredictable value for each thread. Returns: A singleton instance of the default random number generator */ -@property ref Random rndGen() @safe +@property ref Random rndGen() @safe nothrow @nogc { - 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))); + static if (isSeedable!(Random, ulong)) + result.seed(unpredictableSeed!ulong); // Avoid unnecessary copy. + else static if (is(Random : MersenneTwisterEngine!Params, Params...)) + initMTEngine(result); + else static if (isSeedable!(Random, uint)) + result.seed(unpredictableSeed!uint); // Avoid unnecessary copy. else result = Random(unpredictableSeed); initialized = true; @@ -1358,23 +1954,73 @@ A singleton instance of the default random number generator return result; } +/// +@safe nothrow @nogc unittest +{ + import std.algorithm.iteration : sum; + import std.range : take; + auto rnd = rndGen; + assert(rnd.take(3).sum > 0); +} + +/+ +Initialize a 32-bit MersenneTwisterEngine from 64 bits of entropy. +This is private and accepts no seed as a parameter, freeing the internal +implementaton from any need for stability across releases. ++/ +private void initMTEngine(MTEngine)(scope ref MTEngine mt) +if (is(MTEngine : MersenneTwisterEngine!Params, Params...)) +{ + pragma(inline, false); // Called no more than once per thread by rndGen. + ulong seed = unpredictableSeed!ulong; + static if (is(typeof(mt.seed(seed)))) + { + mt.seed(seed); + } + else + { + alias UIntType = typeof(mt.front()); + if (seed == 0) seed = -1; // Any number but 0 is fine. + uint s0 = cast(uint) seed; + uint s1 = cast(uint) (seed >> 32); + foreach (ref e; mt.state.data) + { + //http://xoshiro.di.unimi.it/xoroshiro64starstar.c + const tmp = s0 * 0x9E3779BB; + e = ((tmp << 5) | (tmp >> (32 - 5))) * 5; + static if (MTEngine.max != UIntType.max) { e &= MTEngine.max; } + + const tmp1 = s0 ^ s1; + s0 = ((s0 << 26) | (s0 >> (32 - 26))) ^ tmp1 ^ (tmp1 << 9); + s1 = (tmp1 << 13) | (tmp1 >> (32 - 13)); + } + + mt.state.index = mt.state.data.length - 1; + // double popFront() to guarantee both `mt.state.z` + // and `mt.state.front` are derived from the newly + // set values in `mt.state.data`. + mt.popFront(); + mt.popFront(); + } +} + /** -Generates a number between $(D a) and $(D b). The $(D boundaries) +Generates a number between `a` and `b`. The `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 +either side). Valid values for `boundaries` are `"[]"`, $(D +"$(LPAREN)]"), `"[$(RPAREN)"`, and `"()"`. 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). +take `urng` uses the default generator `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) + if not specified, defaults to `rndGen` Returns: A single random variate drawn from the _uniform distribution - between $(D a) and $(D b), whose type is the common type of + between `a` and `b`, whose type is the common type of these parameters */ auto uniform(string boundaries = "[)", T1, T2) @@ -1387,11 +2033,34 @@ if (!is(CommonType!(T1, T2) == void)) /// @safe unittest { - auto gen = Random(unpredictableSeed); + auto rnd = Random(unpredictableSeed); + // Generate an integer in [0, 1023] - auto a = uniform(0, 1024, gen); + auto a = uniform(0, 1024, rnd); + assert(0 <= a && a < 1024); + // Generate a float in [0, 1) - auto b = uniform(0.0f, 1.0f, gen); + auto b = uniform(0.0f, 1.0f, rnd); + assert(0 <= b && b < 1); + + // Generate a float in [0, 1] + b = uniform!"[]"(0.0f, 1.0f, rnd); + assert(0 <= b && b <= 1); + + // Generate a float in (0, 1) + b = uniform!"()"(0.0f, 1.0f, rnd); + assert(0 < b && b < 1); +} + +/// Create an array of random numbers using range functions and UFCS +@safe unittest +{ + import std.array : array; + import std.range : generate, takeExactly; + + int[] arr = generate!(() => uniform(0, 100)).takeExactly(10).array; + assert(arr.length == 10); + assert(arr[0] >= 0 && arr[0] < 100); } @safe unittest @@ -1435,7 +2104,7 @@ if (isFloatingPoint!(CommonType!(T1, T2)) && isUniformRNG!UniformRandomNumberGen alias NumberType = Unqual!(CommonType!(T1, T2)); static if (boundaries[0] == '(') { - import std.math : nextafter; + import std.math.operations : nextafter; NumberType _a = nextafter(cast(NumberType) a, NumberType.infinity); } else @@ -1444,7 +2113,7 @@ if (isFloatingPoint!(CommonType!(T1, T2)) && isUniformRNG!UniformRandomNumberGen } static if (boundaries[1] == ')') { - import std.math : nextafter; + import std.math.operations : nextafter; NumberType _b = nextafter(cast(NumberType) b, -NumberType.infinity); } else @@ -1504,22 +2173,22 @@ 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))` @@ -1589,7 +2258,7 @@ if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) && @safe unittest { import std.conv : to; - auto gen = Mt19937(unpredictableSeed); + auto gen = Mt19937(123_456_789); static assert(isForwardRange!(typeof(gen))); auto a = uniform(0, 1024, gen); @@ -1599,9 +2268,9 @@ if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) && auto c = uniform(0.0, 1.0); assert(0 <= c && c < 1); - foreach (T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short, ushort, + static 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 @@ -1649,19 +2318,19 @@ if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) && 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, + static 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; @@ -1721,18 +2390,64 @@ if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) && } } +/+ +Generates an unsigned integer in the half-open range `[0, k)`. +Non-public because we locally guarantee `k > 0`. + +Params: + k = unsigned exclusive upper bound; caller guarantees this is non-zero + rng = random number generator to use + +Returns: + Pseudo-random unsigned integer strictly less than `k`. ++/ +private UInt _uniformIndex(UniformRNG, UInt = size_t)(const UInt k, ref UniformRNG rng) +if (isUnsigned!UInt && isUniformRNG!UniformRNG) +{ + alias ResultType = UInt; + alias UpperType = Unsigned!(typeof(k - 0)); + alias upperDist = k; + + assert(upperDist != 0); + + // For backwards compatibility use same algorithm as uniform(0, k, rng). + 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) offset; +} + +pure @safe unittest +{ + // For backwards compatibility check that _uniformIndex(k, rng) + // has the same result as uniform(0, k, rng). + auto rng1 = Xorshift(123_456_789); + auto rng2 = rng1.save(); + const size_t k = (1U << 31) - 1; + assert(_uniformIndex(k, rng1) == uniform(0, k, rng2)); +} + /** 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). +T.max]) for any integral or character type `T`. If no random +number generator is passed, uses the default `rndGen`. + +If an `enum` is used as type, the random variate is drawn with +equal probability from any of the possible values of the enum `E`. Params: urng = (optional) random number generator to use; - if not specified, defaults to $(D rndGen) + if not specified, defaults to `rndGen` Returns: Random variate drawn from the _uniform distribution across all - possible values of the integral or character type $(D T). + possible values of the integral, character or enum type `T`. */ auto uniform(T, UniformRandomNumberGenerator) (ref UniformRandomNumberGenerator urng) @@ -1741,9 +2456,9 @@ if (!is(T == enum) && (isIntegral!T || isSomeChar!T) && isUniformRNG!UniformRand /* dchar does not use its full bit range, so we must * revert to the uniform with specified bounds */ - static if (is(T == dchar)) + static if (is(immutable T == immutable dchar)) { - return uniform!"[]"(T.min, T.max); + return uniform!"[]"(T.min, T.max, urng); } else { @@ -1770,11 +2485,36 @@ if (!is(T == enum) && (isIntegral!T || isSomeChar!T)) return uniform!T(rndGen); } +/// +@safe unittest +{ + auto rnd = MinstdRand0(42); + + assert(rnd.uniform!ubyte == 102); + assert(rnd.uniform!ulong == 4838462006927449017); + + enum Fruit { apple, mango, pear } + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(rnd.uniform!Fruit == Fruit.mango); +} + +@safe unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=21383 + auto rng1 = Xorshift32(123456789); + auto rng2 = rng1.save; + assert(rng1.uniform!dchar == rng2.uniform!dchar); + // https://issues.dlang.org/show_bug.cgi?id=21384 + assert(rng1.uniform!(const shared dchar) <= dchar.max); + // https://issues.dlang.org/show_bug.cgi?id=8671 + double t8671 = 1.0 - uniform(0.0, 1.0); +} + @safe unittest { - foreach (T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short, ushort, + static 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) {} @@ -1787,21 +2527,10 @@ if (!is(T == enum) && (isIntegral!T || isSomeChar!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). - */ +/// ditto auto uniform(E, UniformRandomNumberGenerator) (ref UniformRandomNumberGenerator urng) if (is(E == enum) && isUniformRNG!UniformRandomNumberGenerator) @@ -1817,13 +2546,6 @@ 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 } @@ -1838,20 +2560,20 @@ if (is(E == enum)) /** * 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 + * `T` in the range [0, 1$(RPAREN). If no random number generator is + * specified, the default RNG `rndGen` will be used as the source * of randomness. * - * $(D uniform01) offers a faster generation of random variates than + * `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) + * if not specified, defaults to `rndGen` * * Returns: - * Floating-point random variate of type $(D T) drawn from the _uniform + * Floating-point random variate of type `T` drawn from the _uniform * distribution across the half-open interval [0, 1$(RPAREN). * */ @@ -1869,7 +2591,7 @@ out (result) assert(0 <= result); assert(result < 1); } -body +do { alias R = typeof(rng.front); static if (isIntegral!R) @@ -1890,8 +2612,7 @@ body 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)) + static if (isIntegral!R && T.mant_dig >= (8 * 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 @@ -1917,15 +2638,34 @@ body assert(false); } -@safe unittest +/// +@safe @nogc unittest +{ + import std.math.operations : feqrel; + + auto rnd = MinstdRand0(42); + + // Generate random numbers in the range in the range [0, 1) + auto u1 = uniform01(rnd); + assert(u1 >= 0 && u1 < 1); + + auto u2 = rnd.uniform01!float; + assert(u2 >= 0 && u2 < 1); + + // Confirm that the random values with the initial seed 42 are 0.000328707 and 0.524587 + assert(u1.feqrel(0.000328707) > 20); + assert(u2.feqrel(0.524587) > 20); +} + +@safe @nogc unittest { import std.meta; - foreach (UniformRNG; PseudoRngTypes) - { + static foreach (UniformRNG; PseudoRngTypes) + {{ - foreach (T; std.meta.AliasSeq!(float, double, real)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 - UniformRNG rng = UniformRNG(unpredictableSeed); + static foreach (T; std.meta.AliasSeq!(float, double, real)) + {{ + UniformRNG rng = UniformRNG(123_456_789); auto a = uniform01(); assert(is(typeof(a) == double)); @@ -1948,14 +2688,14 @@ body 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. +Generates a uniform probability distribution of size `n`, i.e., an +array of size `n` of positive numbers of type `F` that sum to +`1`. If `useThis` is provided, it is used as storage. */ F[] uniformDistribution(F = double)(size_t n, F[] useThis = null) if (isFloatingPoint!F) @@ -1970,17 +2710,19 @@ if (isFloatingPoint!F) return useThis; } +/// @safe unittest { - import std.algorithm; - import std.math; - static assert(is(CommonType!(double, int) == double)); + import std.algorithm.iteration : reduce; + import std.math.operations : isClose; + auto a = uniformDistribution(5); assert(a.length == 5); - assert(approxEqual(reduce!"a + b"(a), 1)); + assert(isClose(reduce!"a + b"(a), 1)); + a = uniformDistribution(10, a); assert(a.length == 10); - assert(approxEqual(reduce!"a + b"(a), 1)); + assert(isClose(reduce!"a + b"(a), 1)); } /** @@ -1998,8 +2740,7 @@ Returns: 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) +auto ref choice(Range, RandomGen = Random)(auto ref Range range, ref RandomGen urng) if (isRandomAccessRange!Range && hasLength!Range && isUniformRNG!RandomGen) { assert(range.length > 0, @@ -2008,22 +2749,20 @@ if (isRandomAccessRange!Range && hasLength!Range && isUniformRNG!RandomGen) return range[uniform(size_t(0), $, urng)]; } +/// ditto +auto ref choice(Range)(auto ref Range range) +{ + return choice(range, rndGen); +} + /// @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); + auto rnd = MinstdRand0(42); - assert(canFind(array, elem), - "Choice did not return a valid element from the given Range"); + auto elem = [1, 2, 3, 4, 5].choice(rnd); + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(elem == 3); } @safe unittest @@ -2067,38 +2806,58 @@ if (isRandomAccessRange!Range && hasLength!Range && isUniformRNG!RandomGen) } /** -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) +Shuffles elements of `r` using `gen` as a shuffler. `r` must be +a random-access range with length. If no RNG is specified, `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) - */ + specified, defaults to `rndGen` +Returns: + The shuffled random-access range. +*/ -void randomShuffle(Range, RandomGen)(Range r, ref RandomGen gen) +Range randomShuffle(Range, RandomGen)(Range r, ref RandomGen gen) if (isRandomAccessRange!Range && isUniformRNG!RandomGen) { - return partialShuffle!(Range, RandomGen)(r, r.length, gen); + import std.algorithm.mutation : swapAt; + const n = r.length; + foreach (i; 0 .. n) + { + r.swapAt(i, i + _uniformIndex(n - i, gen)); + } + return r; } /// ditto -void randomShuffle(Range)(Range r) +Range randomShuffle(Range)(Range r) if (isRandomAccessRange!Range) { return randomShuffle(r, rndGen); } +/// +@safe unittest +{ + auto rnd = MinstdRand0(42); + + auto arr = [1, 2, 3, 4, 5].randomShuffle(rnd); + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(arr == [3, 5, 2, 4, 1]); +} + @safe unittest { + int[10] sa = void; + int[10] sb = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 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); + sa[] = sb[]; + auto a = sa[]; + auto b = sb[]; + auto gen = RandomGen(123_456_789); randomShuffle(a, gen); sort(a); assert(a == b); @@ -2106,27 +2865,47 @@ if (isRandomAccessRange!Range) sort(a); assert(a == b); } + // For backwards compatibility verify randomShuffle(r, gen) + // is equivalent to partialShuffle(r, 0, r.length, gen). + auto gen1 = Xorshift(123_456_789); + auto gen2 = gen1.save(); + sa[] = sb[]; + // @nogc std.random.randomShuffle. + // https://issues.dlang.org/show_bug.cgi?id=19156 + () @nogc nothrow pure { randomShuffle(sa[], gen1); }(); + partialShuffle(sb[], sb.length, gen2); + assert(sa[] == sb[]); +} + +// https://issues.dlang.org/show_bug.cgi?id=18501 +@safe unittest +{ + import std.algorithm.comparison : among; + auto r = randomShuffle([0,1]); + assert(r.among([0,1],[1,0])); } /** -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]) +Partially shuffles the elements of `r` such that upon returning $(D r[0 .. n]) +is a random subset of `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. +`partialShuffle` returns will not be independent of their order before +`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. +`r` must be a random-access range with length. `n` must be less than +or equal to `r.length`. If no RNG is specified, `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) + n = number of elements of `r` to shuffle (counting from the beginning); + must be less than `r.length` gen = (optional) random number generator to use; if not - specified, defaults to $(D rndGen) + specified, defaults to `rndGen` +Returns: + The shuffled random-access range. */ -void partialShuffle(Range, RandomGen)(Range r, in size_t n, ref RandomGen gen) +Range partialShuffle(Range, RandomGen)(Range r, in size_t n, ref RandomGen gen) if (isRandomAccessRange!Range && isUniformRNG!RandomGen) { import std.algorithm.mutation : swapAt; @@ -2136,15 +2915,36 @@ if (isRandomAccessRange!Range && isUniformRNG!RandomGen) { r.swapAt(i, uniform(i, r.length, gen)); } + return r; } /// ditto -void partialShuffle(Range)(Range r, in size_t n) +Range partialShuffle(Range)(Range r, in size_t n) if (isRandomAccessRange!Range) { return partialShuffle(r, n, rndGen); } +/// +@safe unittest +{ + auto rnd = MinstdRand0(42); + + auto arr = [1, 2, 3, 4, 5, 6]; + arr = arr.dup.partialShuffle(1, rnd); + + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(arr == [2, 1, 3, 4, 5, 6]); // 1<->2 + + arr = arr.dup.partialShuffle(2, rnd); + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(arr == [1, 4, 3, 2, 5, 6]); // 1<->2, 2<->4 + + arr = arr.dup.partialShuffle(3, rnd); + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(arr == [5, 4, 6, 2, 1, 3]); // 1<->5, 2<->4, 3<->6 +} + @safe unittest { import std.algorithm; @@ -2187,11 +2987,11 @@ if (isRandomAccessRange!Range) /** Rolls a dice with relative probabilities stored in $(D -proportions). Returns the index in $(D proportions) that was chosen. +proportions). Returns the index in `proportions` that was chosen. Params: rnd = (optional) random number generator to use; if not - specified, defaults to $(D rndGen) + specified, defaults to `rndGen` proportions = forward range or list of individual values whose elements correspond to the probabilities with which to choose the corresponding index @@ -2199,9 +2999,9 @@ Params: 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]). + [0, ... `proportions.length` - 1], with the probability + of getting an individual index value `i` being proportional to + `proportions[i]`. */ size_t dice(Rng, Num)(ref Rng rnd, Num[] proportions...) if (isNumeric!Num && isForwardRange!Rng) @@ -2239,6 +3039,16 @@ if (isNumeric!Num) // and 2 10% of the time } +/// +@safe unittest +{ + auto rnd = MinstdRand0(42); + auto z = rnd.dice(70, 20, 10); + assert(z == 0); + z = rnd.dice(30, 20, 40, 10); + assert(z == 2); +} + private size_t diceImpl(Rng, Range)(ref Rng rng, scope Range proportions) if (isForwardRange!Range && isNumeric!(ElementType!Range) && isForwardRange!Rng) in @@ -2246,7 +3056,7 @@ in import std.algorithm.searching : all; assert(proportions.save.all!"a >= 0"); } -body +do { import std.algorithm.iteration : reduce; import std.exception : enforce; @@ -2267,9 +3077,10 @@ body assert(false); } +/// @safe unittest { - auto rnd = Random(unpredictableSeed); + auto rnd = Xorshift(123_456_789); auto i = dice(rnd, 0.0, 100.0); assert(i == 1); i = dice(rnd, 100.0, 0.0); @@ -2279,62 +3090,131 @@ body 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. +/+ @nogc bool array designed for RandomCover. +- constructed with an invariable length +- small length means 0 alloc and bit field (if up to 32(x86) or 64(x64) choices to cover) +- bigger length means non-GC heap allocation(s) and dealloc. +/ +private struct RandomCoverChoices +{ + private size_t* buffer; + private immutable size_t _length; + private immutable bool hasPackedBits; + private enum BITS_PER_WORD = typeof(buffer[0]).sizeof * 8; -If no random number generator is passed to $(D randomCover), the -thread-global RNG rndGen will be used internally. + void opAssign(T)(T) @disable; -Params: - r = random-access range to cover - rng = (optional) random number generator to use; - if not specified, defaults to $(D rndGen) + this(this) pure nothrow @nogc @trusted + { + import core.stdc.string : memcpy; + import std.internal.memory : enforceMalloc; -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. + if (!hasPackedBits && buffer !is null) + { + const nBytesToAlloc = size_t.sizeof * (_length / BITS_PER_WORD + int(_length % BITS_PER_WORD != 0)); + void* nbuffer = enforceMalloc(nBytesToAlloc); + buffer = cast(size_t*) memcpy(nbuffer, buffer, nBytesToAlloc); + } + } -Example: ----- -int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; -foreach (e; randomCover(a)) -{ - writeln(e); -} ----- + this(size_t numChoices) pure nothrow @nogc @trusted + { + import std.internal.memory : enforceCalloc; + + _length = numChoices; + hasPackedBits = _length <= size_t.sizeof * 8; + if (!hasPackedBits) + { + const nWordsToAlloc = _length / BITS_PER_WORD + int(_length % BITS_PER_WORD != 0); + buffer = cast(size_t*) enforceCalloc(nWordsToAlloc, BITS_PER_WORD / 8); + } + } -$(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. + size_t length() const pure nothrow @nogc @safe @property {return _length;} -Example: ----- -int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; -foreach (e; randomCover(a, Random(unpredictableSeed))) // correct! -{ - writeln(e); + ~this() pure nothrow @nogc @trusted + { + import core.memory : pureFree; + + if (!hasPackedBits && buffer !is null) + pureFree(buffer); + } + + bool opIndex(size_t index) const pure nothrow @nogc @trusted + { + assert(index < _length); + import core.bitop : bt; + if (!hasPackedBits) + return cast(bool) bt(buffer, index); + else + return ((cast(size_t) buffer) >> index) & size_t(1); + } + + void opIndexAssign(bool value, size_t index) pure nothrow @nogc @trusted + { + assert(index < _length); + if (!hasPackedBits) + { + import core.bitop : btr, bts; + if (value) + bts(buffer, index); + else + btr(buffer, index); + } + else + { + if (value) + (*cast(size_t*) &buffer) |= size_t(1) << index; + else + (*cast(size_t*) &buffer) &= ~(size_t(1) << index); + } + } } -foreach (e; randomCover(a, rndGen)) // DANGEROUS!! rndGen gets copied by value +@safe @nogc nothrow unittest { - writeln(e); + static immutable lengths = [3, 32, 65, 256]; + foreach (length; lengths) + { + RandomCoverChoices c = RandomCoverChoices(length); + assert(c.hasPackedBits == (length <= size_t.sizeof * 8)); + c[0] = true; + c[2] = true; + assert(c[0]); + assert(!c[1]); + assert(c[2]); + c[0] = false; + c[1] = true; + c[2] = false; + assert(!c[0]); + assert(c[1]); + assert(!c[2]); + } } -foreach (e; randomCover(a, rndGen)) // ... so this second random cover -{ // will output the same sequence as - writeln(e); // the previous one. -} ----- - */ +/** +Covers a given range `r` in a random manner, i.e. goes through each +element of `r` once and only once, just in a random order. `r` +must be a random-access range with length. + +If no random number generator is passed to `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 `rndGen` + +Returns: + Range whose elements consist of the elements of `r`, + in random order. Will be a forward range if both `r` and + `rng` are forward ranges, an + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) otherwise. +*/ struct RandomCover(Range, UniformRNG = void) if (isRandomAccessRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void))) { private Range _input; - private bool[] _chosen; + private RandomCoverChoices _chosen; private size_t _current; private size_t _alreadyChosen = 0; private bool _isEmpty = false; @@ -2344,14 +3224,14 @@ if (isRandomAccessRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == vo this(Range input) { _input = input; - _chosen.length = _input.length; + _chosen = RandomCoverChoices(_input.length); if (_input.empty) { _isEmpty = true; } else { - _current = uniform(0, _chosen.length); + _current = _uniformIndex(_chosen.length, rndGen); } } } @@ -2363,14 +3243,14 @@ if (isRandomAccessRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == vo { _input = input; _rng = rng; - _chosen.length = _input.length; + _chosen = RandomCoverChoices(_input.length); if (_input.empty) { _isEmpty = true; } else { - _current = uniform(0, _chosen.length, rng); + _current = _uniformIndex(_chosen.length, rng); } } @@ -2413,11 +3293,11 @@ if (isRandomAccessRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == vo // Roll a dice with k faces static if (is(UniformRNG == void)) { - auto chooseMe = uniform(0, k) == 0; + auto chooseMe = _uniformIndex(k, rndGen) == 0; } else { - auto chooseMe = uniform(0, k, _rng) == 0; + auto chooseMe = _uniformIndex(k, _rng) == 0; } assert(k > 1 || chooseMe); if (chooseMe) @@ -2443,7 +3323,7 @@ if (isRandomAccessRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == vo } } - @property bool empty() { return _isEmpty; } + @property bool empty() const { return _isEmpty; } } /// Ditto @@ -2460,14 +3340,48 @@ if (isRandomAccessRange!Range) return RandomCover!(Range, void)(r); } +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + auto rnd = MinstdRand0(42); + + version (X86_64) // https://issues.dlang.org/show_bug.cgi?id=15147 + assert(10.iota.randomCover(rnd).equal([7, 4, 2, 0, 1, 6, 8, 3, 9, 5])); +} + +@safe unittest // cover RandomCoverChoices postblit for heap storage +{ + import std.array : array; + import std.range : iota; + auto a = 1337.iota.randomCover().array; + assert(a.length == 1337); +} + +@nogc nothrow pure @safe unittest +{ + // Optionally @nogc std.random.randomCover + // https://issues.dlang.org/show_bug.cgi?id=14001 + auto rng = Xorshift(123_456_789); + static immutable int[] sa = [1, 2, 3, 4, 5]; + auto r = randomCover(sa, rng); + assert(!r.empty); + const x = r.front; + r.popFront(); + assert(!r.empty); + const y = r.front; + assert(x != y); +} + @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 foreach (UniformRNG; std.meta.AliasSeq!(void, PseudoRngTypes)) + {{ static if (is(UniformRNG == void)) { auto rc = randomCover(a); @@ -2476,11 +3390,11 @@ if (isRandomAccessRange!Range) } else { - auto rng = UniformRNG(unpredictableSeed); + auto rng = UniformRNG(123_456_789); 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)); + auto rc2 = RandomCover!(int[], UniformRNG)(a, UniformRNG(987_654_321)); static assert(isForwardRange!(typeof(rc2))); auto rcEmpty = randomCover(c, rng); assert(rcEmpty.length == 0); @@ -2495,18 +3409,18 @@ if (isRandomAccessRange!Range) } sort(b); assert(a == b, text(b)); - } + }} } @safe unittest { - // Bugzilla 12589 + // https://issues.dlang.org/show_bug.cgi?id=12589 int[] r = []; auto rc = randomCover(r); assert(rc.length == 0); assert(rc.empty); - // Bugzilla 16724 + // https://issues.dlang.org/show_bug.cgi?id=16724 import std.range : iota; auto range = iota(10); auto randy = range.randomCover; @@ -2520,84 +3434,50 @@ if (isRandomAccessRange!Range) // RandomSample /** -Selects a random subsample out of $(D r), containing exactly $(D n) +Selects a random subsample out of `r`, containing exactly `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 +range. The total length of `r` must be known. If `total` is passed in, the total number of sample is considered to be $(D -total). Otherwise, $(D RandomSample) uses $(D r.length). +total). Otherwise, `RandomSample` uses `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) + of elements in `r` and/or the parameter + `total` (if provided) + total = (semi-optional) number of elements of `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) + the total number of elements in `r` itself. + May be omitted if `r` has the `.length` property and the sample is to be drawn from - all elements of $(D r). + all elements of `r`. rng = (optional) random number generator to use; - if not specified, defaults to $(D rndGen) + if not specified, defaults to `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. + the elements of `r`, in the same order as these elements + appear in `r` itself. Will be a forward range if both `r` + and `rng` are forward ranges, an input range otherwise. -$(D RandomSample) implements Jeffrey Scott Vitter's Algorithm 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, +of size `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 +RandomSample will throw an exception if `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 +If no random number generator is passed to `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))) @@ -2637,7 +3517,7 @@ if (isInputRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void))) static if (hasLength!Range) { - this(Range input, size_t howMany, ref UniformRNG rng) + this(Range input, size_t howMany, ref scope UniformRNG rng) { _rng = rng; _input = input; @@ -2650,7 +3530,7 @@ if (isInputRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void))) } } - this(Range input, size_t howMany, size_t total, ref UniformRNG rng) + this(Range input, size_t howMany, size_t total, ref scope UniformRNG rng) { _rng = rng; _input = input; @@ -2741,17 +3621,36 @@ if (isInputRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void))) /// Ditto static if (isForwardRange!Range && isForwardRange!UniformRNG) { - @property typeof(this) save() + static if (is(typeof(((const UniformRNG* p) => (*p).save)(null)) : UniformRNG) + && is(typeof(((const Range* p) => (*p).save)(null)) : Range)) { - auto ret = this; - ret._input = _input.save; - ret._rng = _rng.save; - return ret; + @property typeof(this) save() const + { + auto ret = RandomSample.init; + foreach (fieldIndex, ref val; this.tupleof) + { + static if (is(typeof(val) == const(Range)) || is(typeof(val) == const(UniformRNG))) + ret.tupleof[fieldIndex] = val.save; + else + ret.tupleof[fieldIndex] = val; + } + return ret; + } + } + else + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + ret._rng = _rng.save; + return ret; + } } } /// Ditto - @property size_t length() + @property size_t length() const { return _toSelect; } @@ -2871,7 +3770,8 @@ Variable names are chosen to match those in Vitter's paper. */ private size_t skipD() { - import std.math : isNaN, trunc; + import std.math.traits : isNaN; + import std.math.rounding : trunc; // Confirm that the check in Step D1 is valid and we // haven't been sent here by mistake assert((_alphaInverse * _toSelect) <= _available); @@ -3014,6 +3914,15 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) return RandomSample!(Range, UniformRNG)(r, n, r.length, rng); } +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + auto rnd = MinstdRand0(42); + assert(10.iota.randomSample(3, rnd).equal([7, 8, 9])); +} + @system unittest { // @system because it takes the address of a local @@ -3031,10 +3940,11 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) static assert(isInputRange!TestInputRange); static assert(!isForwardRange!TestInputRange); - int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; + const(int)[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; foreach (UniformRNG; PseudoRngTypes) - { + (){ // avoid workaround optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 auto rng = UniformRNG(1234); /* First test the most general case: randomSample of input range, with and * without a specified random number generator. @@ -3047,7 +3957,7 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) { auto sample = RandomSample!(TestInputRange, UniformRNG) - (TestInputRange(), 5, 10, UniformRNG(unpredictableSeed)); + (TestInputRange(), 5, 10, UniformRNG(987_654_321)); static assert(isInputRange!(typeof(sample))); static assert(!isForwardRange!(typeof(sample))); } @@ -3063,7 +3973,7 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) { auto sample = RandomSample!(typeof(TestInputRange().takeExactly(10)), UniformRNG) - (TestInputRange().takeExactly(10), 5, 10, UniformRNG(unpredictableSeed)); + (TestInputRange().takeExactly(10), 5, 10, UniformRNG(654_321_987)); static assert(isInputRange!(typeof(sample))); static assert(!isForwardRange!(typeof(sample))); } @@ -3076,8 +3986,8 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) // ... and test with range initialized directly { auto sample = - RandomSample!(int[], UniformRNG) - (a, 5, UniformRNG(unpredictableSeed)); + RandomSample!(const(int)[], UniformRNG) + (a, 5, UniformRNG(321_987_654)); static assert(isForwardRange!(typeof(sample))); } } @@ -3088,8 +3998,8 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) // ... and test with range initialized directly { auto sample = - RandomSample!(int[], UniformRNG) - (a, 5, UniformRNG(unpredictableSeed)); + RandomSample!(const(int)[], UniformRNG) + (a, 5, UniformRNG(789_123_456)); static assert(isInputRange!(typeof(sample))); static assert(!isForwardRange!(typeof(sample))); } @@ -3218,7 +4128,7 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) } /* Check that it also works if .index is called before .front. - * See: http://d.puremagic.com/issues/show_bug.cgi?id=10322 + * See: https://issues.dlang.org/show_bug.cgi?id=10322 */ auto sample3 = randomSample(TestInputRange(), 654, 654_321); for (; !sample3.empty; sample3.popFront()) @@ -3242,7 +4152,7 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) */ { size_t count0, count1, count99; - foreach (_; 0 .. 100_000) + foreach (_; 0 .. 50_000) { auto sample = randomSample(iota(100), 5, &rng); sample.popFront(); @@ -3277,9 +4187,9 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) * 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.")); + assert(count1 < 150, text("1: ", count1, " > 150.")); + assert(2_200 < count99, text("99: ", count99, " < 2200.")); + assert(count99 < 2_800, text("99: ", count99, " > 2800.")); } /* Odd corner-cases: RandomSample has 2 constructors that are not called @@ -3326,11 +4236,12 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) static if (isForwardRange!UniformRNG) { auto sample1 = randomSample(a, 5, rng); - auto sample2 = sample1.save; + // https://issues.dlang.org/show_bug.cgi?id=15853 + auto sample2 = ((const ref typeof(sample1) a) => a.save)(sample1); assert(sample1.array() == sample2.array()); } - // Bugzilla 8314 + // https://issues.dlang.org/show_bug.cgi?id=8314 { auto sample(RandomGen)(uint seed) { return randomSample(a, 1, RandomGen(seed)).front; } @@ -3340,5 +4251,5 @@ if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) 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 index 7207776..4f8eba7 100644 --- a/libphobos/src/std/range/interfaces.d +++ b/libphobos/src/std/range/interfaces.d @@ -4,10 +4,11 @@ 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. +provides a number of object and `interface` definitions that can be used to +wrap around range objects created by the $(MREF std, range) templates. $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TD $(LREF InputRange)) $(TD Wrapper for input ranges. @@ -40,33 +41,35 @@ $(BOOKTABLE , $(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. + $(TD Class that implements the `OutputRange` interface and wraps the + `put` methods in virtual functions. + )) $(TR $(TD $(LREF outputRangeObject)) - Convenience function for creating an $(D OutputRangeObject) with a base + $(TD Convenience function for creating an `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. + $(TD Class that implements the `InputRange` interface and wraps the + input range methods in virtual functions. )) $(TR $(TD $(LREF inputRangeObject)) - $(TD Convenience function for creating an $(D InputRangeObject) + $(TD Convenience function for creating an `InputRangeObject` of the proper type. )) $(TR $(TD $(LREF MostDerivedInputRange)) - $(TD Returns the interface type that best matches the range.) + $(TD Returns the interface type that best matches the range. )) -) +)) -Source: $(PHOBOSSRC std/range/_interfaces.d) +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). +Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, and + $(HTTP jmdavisprog.com, 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; @@ -80,11 +83,11 @@ import std.traits; * 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. + * not for implementation of these `interface` types. * * Limitations: * - * These interfaces are not capable of forwarding $(D ref) access to elements. + * These interfaces are not capable of forwarding `ref` access to elements. * * Infiniteness of the wrapped range is not propagated. * @@ -115,7 +118,7 @@ interface InputRange(E) { * InputRangeObject, range primitives: 877 milliseconds (3.15x penalty) */ - /**$(D foreach) iteration uses opApply, since one delegate call per loop + /**`foreach` iteration uses opApply, since one delegate call per loop * iteration is faster than three virtual function calls. */ int opApply(scope int delegate(E)); @@ -145,13 +148,13 @@ interface InputRange(E) { useRange(squaresWrapped); } -/**Interface for a forward range of type $(D E).*/ +/**Interface for a forward range of type `E`.*/ interface ForwardRange(E) : InputRange!E { /// @property ForwardRange!E save(); } -/**Interface for a bidirectional range of type $(D E).*/ +/**Interface for a bidirectional range of type `E`.*/ interface BidirectionalRange(E) : ForwardRange!(E) { /// @property BidirectionalRange!E save(); @@ -166,7 +169,7 @@ interface BidirectionalRange(E) : ForwardRange!(E) { void popBack(); } -/**Interface for a finite random access range of type $(D E).*/ +/**Interface for a finite random access range of type `E`.*/ interface RandomAccessFinite(E) : BidirectionalRange!(E) { /// @property RandomAccessFinite!E save(); @@ -192,7 +195,7 @@ interface RandomAccessFinite(E) : BidirectionalRange!(E) { } } -/**Interface for an infinite random access range of type $(D E).*/ +/**Interface for an infinite random access range of type `E`.*/ interface RandomAccessInfinite(E) : ForwardRange!E { /// E moveAt(size_t); @@ -241,16 +244,16 @@ interface RandomFiniteAssignable(E) : RandomAccessFinite!E, BidirectionalAssigna 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 for an output range of type `E`. Usage is similar to the + * `InputRange` interface and descendants.*/ interface OutputRange(E) { /// void put(E); } +// https://issues.dlang.org/show_bug.cgi?id=6973 @safe unittest { - // 6973 static assert(isOutputRange!(OutputRange!int, int)); } @@ -271,8 +274,8 @@ private string putMethods(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. +/**Implements the `OutputRange` interface for all types E and wraps the + * `put` method for each type `E` in a virtual function. */ class OutputRangeObject(R, E...) : staticMap!(OutputRange, E) { // @BUG 4689: There should be constraints on this template class, but @@ -288,7 +291,7 @@ class OutputRangeObject(R, E...) : staticMap!(OutputRange, E) { } -/**Returns the interface type that best matches $(D R).*/ +/**Returns the interface type that best matches `R`.*/ template MostDerivedInputRange(R) if (isInputRange!(Unqual!R)) { @@ -344,9 +347,9 @@ if (isInputRange!(Unqual!R)) } } -/**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. +/**Implements the most derived interface that `R` works with and wraps + * all relevant range primitives in virtual functions. If `R` is already + * derived from the `InputRange` interface, aliases itself away. */ template InputRangeObject(R) if (isInputRange!(Unqual!R)) @@ -480,7 +483,7 @@ if (isInputRange!(Unqual!R)) } } -/**Convenience function for creating an $(D InputRangeObject) of the proper type. +/**Convenience function for creating an `InputRangeObject` of the proper type. * See $(LREF InputRange) for an example. */ InputRangeObject!R inputRangeObject(R)(R range) @@ -496,8 +499,8 @@ if (isInputRange!R) } } -/**Convenience function for creating an $(D OutputRangeObject) with a base range - * of type $(D R) that accepts types $(D E). +/**Convenience function for creating an `OutputRangeObject` with a base range + * of type `R` that accepts types `E`. */ template outputRangeObject(E...) { diff --git a/libphobos/src/std/range/package.d b/libphobos/src/std/range/package.d index deedb68..86bd4a1 100644 --- a/libphobos/src/std/range/package.d +++ b/libphobos/src/std/range/package.d @@ -14,7 +14,7 @@ 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) + $(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.) @@ -22,7 +22,7 @@ $(UL 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.) + 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 @@ -33,23 +33,24 @@ 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, 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 +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 +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;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TD $(LREF chain)) - $(TD Concatenates several ranges into a single _range. + $(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. @@ -58,174 +59,173 @@ $(BOOKTABLE , $(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. + $(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 + $(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. + $(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. + $(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. + $(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. + $(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. + $(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. + $(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. + $(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. + $(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 + $(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 + $(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. + $(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 + $(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. + $(TD Iterates $(I n) ranges in lockstep, for use in a `foreach` + loop. Similar to `zip`, except that `lockstep` is designed + especially for `foreach` loops. )) - $(TR $(TD $(LREF NullSink)) - $(TD An output _range that discards the data it receives. + $(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. + $(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. + $(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. + $(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 + $(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 + $(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 + $(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. + $(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. + $(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. + $(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 + $(TD Similar to `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, + $(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). + $(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. + $(TD Return a range advanced to within `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. + $(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. + $(TD Like `take`, but assumes the given range actually has $(I n) + elements, and therefore also defines the `length` property. )) $(TR $(TD $(LREF takeNone)) - $(TD Creates a random-access _range consisting of zero elements of the - given _range. + $(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. + $(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 + $(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. + $(TD Transposes a range of ranges. )) $(TR $(TD $(LREF transversal)) - $(TD Creates a _range that iterates over the $(I n)'th elements of the + $(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 + $(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 +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. +range operations that take advantage of the fact that the range is sorted. -Source: $(PHOBOSSRC std/_range/_package.d) +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). +Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, + $(HTTP jmdavisprog.com, 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; @@ -234,14 +234,15 @@ 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 +import std.internal.attributes : betterC; +import std.meta : allSatisfy, staticMap; +import std.traits : CommonType, isCallable, isFloatingPoint, isIntegral, + isPointer, isSomeFunction, isStaticArray, Unqual, isInstanceOf; /** Iterates a bidirectional range backwards. The original range can be -accessed by using the $(D source) property. Applying retro twice to +accessed by using the `source` property. Applying retro twice to the same range yields the original range. Params: @@ -348,15 +349,7 @@ if (isBidirectionalRange!(Unqual!Range)) } } - static if (hasLength!R) - { - @property auto length() - { - return source.length; - } - - alias opDollar = length; - } + mixin ImplementLength!source; } return Result!()(r); @@ -472,7 +465,7 @@ pure @safe nothrow @nogc unittest assert(equal(r, excepted[])); } -// Issue 12662 +// https://issues.dlang.org/show_bug.cgi?id=12662 pure @safe nothrow @nogc unittest { int[3] src = [1,2,3]; @@ -483,14 +476,14 @@ pure @safe nothrow @nogc unittest /** -Iterates range $(D r) with stride $(D n). If the range is a +Iterates range `r` with stride `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 +moves by successive calls to `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. +product of the two applications. It is an error for `n` to be 0. Params: - r = the input range to stride over + r = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to stride over n = the number of elements to skip over Returns: @@ -504,7 +497,7 @@ in { assert(n != 0, "stride cannot have step zero."); } -body +do { import std.algorithm.comparison : min; @@ -663,12 +656,14 @@ body static if (hasSlicing!R && hasLength!R) typeof(this) opSlice(size_t lower, size_t upper) { - assert(upper >= lower && upper <= length); + assert(upper >= lower && upper <= length, + "Attempt to get out-of-bounds slice of `stride` range"); immutable translatedUpper = (upper == 0) ? 0 : (upper * _n - (_n - 1)); immutable translatedLower = min(lower * _n, translatedUpper); - assert(translatedLower <= translatedUpper); + assert(translatedLower <= translatedUpper, + "Overflow when calculating slice of `stride` range"); return typeof(this)(source[translatedLower .. translatedUpper], _n); } @@ -713,7 +708,7 @@ debug pure nothrow @system unittest scope (success) assert(passed); import core.exception : AssertError; //std.exception.assertThrown won't do because it can't infer nothrow - // @@@BUG@@@ 12647 + // https://issues.dlang.org/show_bug.cgi?id=12647 try { auto unused = testArr[].stride(0); @@ -764,7 +759,7 @@ pure @safe nothrow unittest // assert(s2[$ .. $].empty); assert(s2[s2.opDollar .. s2.opDollar].empty); - // Test fix for Bug 5035 + // Test fix for https://issues.dlang.org/show_bug.cgi?id=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])); @@ -870,19 +865,19 @@ pure @safe nothrow unittest } /** -Spans multiple ranges in sequence. The function $(D chain) takes any +Spans multiple ranges in sequence. The function `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 +result is a range that offers the `front`, `popFront`, and $(D empty) primitives. If all input ranges offer random access and $(D -length), $(D Chain) offers them as well. +length), `Chain` offers them as well. -If only one range is offered to $(D Chain) or $(D chain), the $(D +If only one range is offered to `Chain` or `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 + rs = the $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) to chain together Returns: An input range at minimum. If all of the ranges in `rs` provide @@ -913,16 +908,8 @@ if (Ranges.length > 0 && } enum bool allSameType = allSatisfy!(sameET, R); + alias ElementType = RvalueElementType; - // 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) @@ -945,7 +932,8 @@ if (Ranges.length > 0 && public: this(R input) { - foreach (i, v; input) + // Must be static foreach because of https://issues.dlang.org/show_bug.cgi?id=21209 + static foreach (i, v; input) { source[i] = v; } @@ -973,12 +961,21 @@ if (Ranges.length > 0 && static if (allSatisfy!(isForwardRange, R)) @property auto save() { - typeof(this) result = this; - foreach (i, Unused; R) + auto saveSource(size_t len)() { - result.source[i] = result.source[i].save; + import std.typecons : tuple; + static assert(len > 0); + static if (len == 1) + { + return tuple(source[0].save); + } + else + { + return saveSource!(len - 1)() ~ + tuple(source[len - 1].save); + } } - return result; + return Result(saveSource!(R.length).expand); } void popFront() @@ -989,6 +986,7 @@ if (Ranges.length > 0 && source[i].popFront(); return; } + assert(false, "Attempt to `popFront` of empty `chain` range"); } @property auto ref front() @@ -998,7 +996,7 @@ if (Ranges.length > 0 && if (source[i].empty) continue; return fixRef(source[i].front); } - assert(false); + assert(false, "Attempt to get `front` of empty `chain` range"); } static if (allSameType && allSatisfy!(hasAssignableElements, R)) @@ -1014,7 +1012,7 @@ if (Ranges.length > 0 && source[i].front = v; return; } - assert(false); + assert(false, "Attempt to set `front` of empty `chain` range"); } } @@ -1027,7 +1025,7 @@ if (Ranges.length > 0 && if (source[i].empty) continue; return source[i].moveFront(); } - assert(false); + assert(false, "Attempt to `moveFront` of empty `chain` range"); } } @@ -1040,7 +1038,7 @@ if (Ranges.length > 0 && if (source[i].empty) continue; return fixRef(source[i].back); } - assert(false); + assert(false, "Attempt to get `back` of empty `chain` range"); } void popBack() @@ -1051,6 +1049,7 @@ if (Ranges.length > 0 && source[i].popBack(); return; } + assert(false, "Attempt to `popBack` of empty `chain` range"); } static if (allSatisfy!(hasMobileElements, R)) @@ -1062,7 +1061,7 @@ if (Ranges.length > 0 && if (source[i].empty) continue; return source[i].moveBack(); } - assert(false); + assert(false, "Attempt to `moveBack` of empty `chain` range"); } } @@ -1076,7 +1075,7 @@ if (Ranges.length > 0 && source[i].back = v; return; } - assert(false); + assert(false, "Attempt to set `back` of empty `chain` range"); } } } @@ -1113,7 +1112,7 @@ if (Ranges.length > 0 && index -= length; } } - assert(false); + assert(false, "Attempt to access out-of-bounds index of `chain` range"); } static if (allSatisfy!(hasMobileElements, R)) @@ -1133,7 +1132,7 @@ if (Ranges.length > 0 && index -= length; } } - assert(false); + assert(false, "Attempt to move out-of-bounds index of `chain` range"); } } @@ -1157,12 +1156,12 @@ if (Ranges.length > 0 && index -= length; } } - assert(false); + assert(false, "Attempt to write out-of-bounds index of `chain` range"); } } static if (allSatisfy!(hasLength, R) && allSatisfy!(hasSlicing, R)) - auto opSlice(size_t begin, size_t end) + auto opSlice(size_t begin, size_t end) return scope { auto result = this; foreach (i, Unused; R) @@ -1242,6 +1241,38 @@ pure @safe nothrow unittest assert(arr3.equal([7, 8, 9])); } +/** +Due to safe type promotion in D, chaining together different +character ranges results in a `uint` range. + +Use $(REF_ALTTEXT byChar, byChar,std,utf), $(REF_ALTTEXT byWchar, byWchar,std,utf), +and $(REF_ALTTEXT byDchar, byDchar,std,utf) on the ranges +to get the type you need. + */ +pure @safe nothrow unittest +{ + import std.utf : byChar, byCodeUnit; + + auto s1 = "string one"; + auto s2 = "string two"; + // s1 and s2 front is dchar because of auto-decoding + static assert(is(typeof(s1.front) == dchar) && is(typeof(s2.front) == dchar)); + + auto r1 = s1.chain(s2); + // chains of ranges of the same character type give that same type + static assert(is(typeof(r1.front) == dchar)); + + auto s3 = "string three".byCodeUnit; + static assert(is(typeof(s3.front) == immutable char)); + auto r2 = s1.chain(s3); + // chaining ranges of mixed character types gives `dchar` + static assert(is(typeof(r2.front) == dchar)); + + // use byChar on character ranges to correctly convert them to UTF-8 + auto r3 = s1.byChar.chain(s3); + static assert(is(typeof(r3.front) == immutable char)); +} + pure @safe nothrow unittest { import std.algorithm.comparison : equal; @@ -1281,7 +1312,8 @@ pure @safe nothrow unittest assert(c.moveAt(5) == 1); } - // Make sure bug 3311 is fixed. ChainImpl should compile even if not all + + // Make sure https://issues.dlang.org/show_bug.cgi?id=3311 is fixed. // elements are mutable. assert(equal(chain(iota(0, 3), iota(0, 3)), [0, 1, 2, 0, 1, 2])); @@ -1301,7 +1333,8 @@ pure @safe nothrow unittest // pair of DummyRange types, in either order. foreach (DummyType1; AllDummyRanges) - { + (){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 DummyType1 dummy1; foreach (DummyType2; AllDummyRanges) { @@ -1338,7 +1371,7 @@ pure @safe nothrow unittest static assert(!hasLvalueElements!(typeof(myChain))); } } - } + }(); } pure @safe nothrow @nogc unittest @@ -1349,226 +1382,411 @@ pure @safe nothrow @nogc unittest assert(chain(a, b).empty); } +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + string s = "foo"; + auto r = refRange(&s).chain("bar"); + assert(equal(r.save, "foobar")); + assert(equal(r, "foobar")); +} + /** 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). +`CommonType` must exist for the two element types). The result is a range +that offers the weakest capabilities of the two (e.g. `ForwardRange` if $(D +R1) is a random-access range and `R2` is a forward range). Params: - condition = which range to choose: $(D r1) if $(D true), $(D r2) otherwise + condition = which range to choose: `r1` if `true`, `r2` otherwise r1 = the "true" range r2 = the "false" range Returns: - A range type dependent on $(D R1) and $(D R2). - -Bugs: - $(BUGZILLA 14660) + A range type dependent on `R1` and `R2`. */ -auto choose(R1, R2)(bool condition, R1 r1, R2 r2) +auto choose(R1, R2)(bool condition, return scope R1 r1, return scope R2 r2) if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) && !is(CommonType!(ElementType!(Unqual!R1), ElementType!(Unqual!R2)) == void)) { - static struct Result + return ChooseResult!(R1, R2)(condition, r1, r2); +} + +/// +@safe nothrow pure @nogc unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter, map; + + auto data1 = only(1, 2, 3, 4).filter!(a => a != 3); + auto data2 = only(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(only(1, 2, 4))); + + result = chooseRange(false); + assert(result.equal(only(6, 7, 8, 9))); +} + + +private struct ChooseResult(R1, R2) +{ + import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; + + private union { - import std.algorithm.comparison : max; - import std.algorithm.internal : addressOf; - import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; + R1 r1; + R2 r2; + } + private bool r1Chosen; - private union + private static auto ref actOnChosen(alias foo, ExtraArgs ...)(ref ChooseResult r, + auto ref ExtraArgs extraArgs) + { + if (r.r1Chosen) { - void[max(R1.sizeof, R2.sizeof)] buffer = void; - void* forAlignmentOnly = void; + ref get1(return ref ChooseResult r) @trusted { return r.r1; } + return foo(get1(r), extraArgs); } - private bool condition; - private @property ref R1 r1() + else { - assert(condition); - return *cast(R1*) buffer.ptr; + ref get2(return ref ChooseResult r) @trusted { return r.r2; } + return foo(get2(r), extraArgs); } - private @property ref R2 r2() + } + + this(bool r1Chosen, return scope R1 r1, return scope R2 r2) @trusted + { + // @trusted because of assignment of r1 and r2 which overlap each other + import core.lifetime : emplace; + + // This should be the only place r1Chosen is ever assigned + // independently + this.r1Chosen = r1Chosen; + if (r1Chosen) { - assert(!condition); - return *cast(R2*) buffer.ptr; + this.r2 = R2.init; + emplace(&this.r1, r1); } - - this(bool condition, R1 r1, R2 r2) + else { - this.condition = condition; - import std.conv : emplace; - if (condition) emplace(addressOf(this.r1), r1); - else emplace(addressOf(this.r2), r2); + this.r1 = R1.init; + emplace(&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 + void opAssign(ChooseResult r) + { + static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2) + if (r1Chosen != r.r1Chosen) { - static if (hasElaborateCopyConstructor!R2) r2.__postblit(); + // destroy the current item + actOnChosen!((ref r) => destroy(r))(this); } + r1Chosen = r.r1Chosen; + if (r1Chosen) + { + ref get1(return ref ChooseResult r) @trusted { return r.r1; } + get1(this) = get1(r); } + else + { + ref get2(return ref ChooseResult r) @trusted { return r.r2; } + get2(this) = get2(r); + } + } - static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2) - ~this() + // Carefully defined postblit to postblit the appropriate range + static if (hasElaborateCopyConstructor!R1 + || hasElaborateCopyConstructor!R2) + this(this) + { + actOnChosen!((ref r) { + static if (hasElaborateCopyConstructor!(typeof(r))) r.__postblit(); + })(this); + } + + static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2) + ~this() + { + actOnChosen!((ref r) => destroy(r))(this); + } + + static if (isInfinite!R1 && isInfinite!R2) + // Propagate infiniteness. + enum bool empty = false; + else + @property bool empty() { - if (condition) destroy(r1); - else destroy(r2); + return actOnChosen!(r => r.empty)(this); } - 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() + { + static auto ref getFront(R)(ref R r) { return r.front; } + return actOnChosen!getFront(this); + } - @property auto ref front() + void popFront() + { + return actOnChosen!((ref r) { r.popFront; })(this); + } + + static if (isForwardRange!R1 && isForwardRange!R2) + @property auto save() return scope + { + if (r1Chosen) { - return condition ? r1.front : r2.front; + ref R1 getR1() @trusted { return r1; } + return ChooseResult(r1Chosen, getR1.save, R2.init); } + else + { + ref R2 getR2() @trusted { return r2; } + return ChooseResult(r1Chosen, R1.init, getR2.save); + } + } - void popFront() + @property void front(T)(T v) + if (is(typeof({ r1.front = v; r2.front = v; }))) + { + actOnChosen!((ref r, T v) { r.front = v; })(this, v); + } + + static if (hasMobileElements!R1 && hasMobileElements!R2) + auto moveFront() { - return condition ? r1.popFront : r2.popFront; + return actOnChosen!((ref r) => r.moveFront)(this); } - static if (isForwardRange!R1 && isForwardRange!R2) - @property auto save() - { - auto result = this; - if (condition) r1 = r1.save; - else r2 = r2.save; - return result; - } + static if (isBidirectionalRange!R1 && isBidirectionalRange!R2) + { + @property auto ref back() + { + static auto ref getBack(R)(ref R r) { return r.back; } + return actOnChosen!getBack(this); + } - @property void front(T)(T v) - if (is(typeof({ r1.front = v; r2.front = v; }))) + void popBack() { - if (condition) r1.front = v; else r2.front = v; + actOnChosen!((ref r) { r.popBack; })(this); } static if (hasMobileElements!R1 && hasMobileElements!R2) - auto moveFront() + auto moveBack() { - return condition ? r1.moveFront : r2.moveFront; + return actOnChosen!((ref r) => r.moveBack)(this); } - static if (isBidirectionalRange!R1 && isBidirectionalRange!R2) + @property void back(T)(T v) + if (is(typeof({ r1.back = v; r2.back = v; }))) { - @property auto ref back() - { - return condition ? r1.back : r2.back; - } + actOnChosen!((ref r, T v) { r.back = v; })(this, v); + } + } - void popBack() - { - return condition ? r1.popBack : r2.popBack; - } + static if (hasLength!R1 && hasLength!R2) + { + @property size_t length() + { + return actOnChosen!(r => r.length)(this); + } + alias opDollar = length; + } - static if (hasMobileElements!R1 && hasMobileElements!R2) - auto moveBack() - { - return condition ? r1.moveBack : r2.moveBack; - } + static if (isRandomAccessRange!R1 && isRandomAccessRange!R2) + { + auto ref opIndex(size_t index) + { + static auto ref get(R)(ref R r, size_t index) { return r[index]; } + return actOnChosen!get(this, index); + } - @property void back(T)(T v) - if (is(typeof({ r1.back = v; r2.back = v; }))) + static if (hasMobileElements!R1 && hasMobileElements!R2) + auto moveAt(size_t index) { - if (condition) r1.back = v; else r2.back = v; + return actOnChosen!((ref r, size_t index) => r.moveAt(index)) + (this, index); } - } - static if (hasLength!R1 && hasLength!R2) + void opIndexAssign(T)(T v, size_t index) + if (is(typeof({ r1[1] = v; r2[1] = v; }))) { - @property size_t length() - { - return condition ? r1.length : r2.length; - } - alias opDollar = length; + return actOnChosen!((ref r, size_t index, T v) { r[index] = v; }) + (this, index, v); } + } - static if (isRandomAccessRange!R1 && isRandomAccessRange!R2) + static if (hasSlicing!R1 && hasSlicing!R2) + auto opSlice(size_t begin, size_t end) { - auto ref opIndex(size_t index) - { - return condition ? r1[index] : r2[index]; - } + alias Slice1 = typeof(R1.init[0 .. 1]); + alias Slice2 = typeof(R2.init[0 .. 1]); + return actOnChosen!((r, size_t begin, size_t end) { + static if (is(typeof(r) == Slice1)) + return choose(true, r[begin .. end], Slice2.init); + else + return choose(false, Slice1.init, r[begin .. end]); + })(this, begin, end); + } +} - static if (hasMobileElements!R1 && hasMobileElements!R2) - auto moveAt(size_t index) - { - return condition ? r1.moveAt(index) : r2.moveAt(index); - } +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + string s = "foo"; + auto r = choose(true, refRange(&s), "bar"); + assert(equal(r.save, "foo")); + assert(equal(r, "foo")); +} - 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; - } - } +@safe unittest +{ + static void* p; + static struct R + { + void* q; + int front; + bool empty; + void popFront() {} + @property R save() { p = q; return this; } + // `p = q;` is only there to prevent inference of `scope return`. + } + R r; + choose(true, r, r).save; +} - // 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; - } +// Make sure ChooseResult.save doesn't trust @system user code. +@system unittest // copy is @system +{ + static struct R + { + int front; + bool empty; + void popFront() {} + this(this) @system {} + @property R save() { return R(front, empty); } } - return Result(condition, r1, r2); + choose(true, R(), R()).save; + choose(true, [0], R()).save; + choose(true, R(), [0]).save; } -/// -@system unittest +@safe unittest // copy is @system { - import std.algorithm.comparison : equal; - import std.algorithm.iteration : filter, map; + static struct R + { + int front; + bool empty; + void popFront() {} + this(this) @system {} + @property R save() { return R(front, empty); } + } + static assert(!__traits(compiles, choose(true, R(), R()).save)); + static assert(!__traits(compiles, choose(true, [0], R()).save)); + static assert(!__traits(compiles, choose(true, R(), [0]).save)); +} - auto data1 = [ 1, 2, 3, 4 ].filter!(a => a != 3); - auto data2 = [ 5, 6, 7, 8 ].map!(a => a + 1); +@system unittest // .save is @system +{ + static struct R + { + int front; + bool empty; + void popFront() {} + @property R save() @system { return this; } + } + choose(true, R(), R()).save; + choose(true, [0], R()).save; + choose(true, R(), [0]).save; +} - // 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))); +@safe unittest // .save is @system +{ + static struct R + { + int front; + bool empty; + void popFront() {} + @property R save() @system { return this; } + } + static assert(!__traits(compiles, choose(true, R(), R()).save)); + static assert(!__traits(compiles, choose(true, [0], R()).save)); + static assert(!__traits(compiles, choose(true, R(), [0]).save)); +} - auto chooseRange(bool pickFirst) +//https://issues.dlang.org/show_bug.cgi?id=19738 +@safe nothrow pure @nogc unittest +{ + static struct EvilRange { - // 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); + enum empty = true; + int front; + void popFront() @safe {} + auto opAssign(const ref EvilRange other) + { + *(cast(uint*) 0xcafebabe) = 0xdeadbeef; + return this; + } + } - // 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 + static assert(!__traits(compiles, () @safe + { + auto c1 = choose(true, EvilRange(), EvilRange()); + auto c2 = c1; + c1 = c2; + })); +} + + +// https://issues.dlang.org/show_bug.cgi?id=20495 +@safe unittest +{ + static struct KillableRange + { + int *item; + ref int front() { return *item; } + bool empty() { return *item > 10; } + void popFront() { ++(*item); } + this(this) + { + assert(item is null || cast(size_t) item > 1000); + item = new int(*item); + } + KillableRange save() { return this; } } - auto result = chooseRange(true); - assert(result.equal([ 1, 2, 4 ])); + auto kr = KillableRange(new int(1)); + int[] x = [1,2,3,4,5]; // length is first - result = chooseRange(false); - assert(result.equal([ 6, 7, 8, 9 ])); + auto chosen = choose(true, x, kr); + auto chosen2 = chosen.save; } /** 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). +result is a range that offers the weakest capabilities of all `Ranges`. Params: index = which range to choose, must be less than the number of ranges @@ -1578,7 +1796,7 @@ 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) +auto chooseAmong(Ranges...)(size_t index, return scope Ranges rs) if (Ranges.length >= 2 && allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) && !is(CommonType!(staticMap!(ElementType, Ranges)) == void)) @@ -1590,84 +1808,125 @@ if (Ranges.length >= 2 } /// -@system unittest +@safe nothrow pure @nogc unittest { - import std.algorithm.comparison : equal; + auto test() + { + import std.algorithm.comparison : equal; - int[] arr1 = [ 1, 2, 3, 4 ]; - int[] arr2 = [ 5, 6 ]; - int[] arr3 = [ 7 ]; + int[4] sarr1 = [1, 2, 3, 4]; + int[2] sarr2 = [5, 6]; + int[1] sarr3 = [7]; + auto arr1 = sarr1[]; + auto arr2 = sarr2[]; + auto arr3 = sarr3[]; - { - 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); + { + auto s = chooseAmong(0, arr1, arr2, arr3); + auto t = s.save; + assert(s.length == 4); + assert(s[2] == 3); + s.popFront(); + assert(equal(t, only(1, 2, 3, 4))); + } + { + auto s = chooseAmong(1, arr1, arr2, arr3); + assert(s.length == 2); + s.front = 8; + assert(equal(s, only(8, 6))); + } + { + auto s = chooseAmong(1, arr1, arr2, arr3); + assert(s.length == 2); + s[1] = 9; + assert(equal(s, only(8, 9))); + } + { + auto s = chooseAmong(1, arr2, arr1, arr3)[1 .. 3]; + assert(s.length == 2); + assert(equal(s, only(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, only(1, 2, 5))); + s.back = 3; + assert(equal(s, only(1, 2, 3))); + } + { + uint[5] foo = [1, 2, 3, 4, 5]; + uint[5] 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(0, cycle(arr2), cycle(arr3)); + assert(isInfinite!(typeof(s))); + assert(!s.empty); + assert(s[100] == 8); + assert(s[101] == 9); + assert(s[0 .. 3].equal(only(8, 9, 8))); + } + return 0; } + // works at runtime + auto a = test(); + // and at compile time + static b = test(); } -@system unittest +@safe nothrow pure @nogc unittest { - int[] a = [1, 2, 3]; - long[] b = [4, 5, 6]; - auto c = chooseAmong(0, a, b); + int[3] a = [1, 2, 3]; + long[3] b = [4, 5, 6]; + auto c = chooseAmong(0, a[], b[]); c[0] = 42; assert(c[0] == 42); } +@safe nothrow pure @nogc unittest +{ + static struct RefAccessRange + { + int[] r; + ref front() @property { return r[0]; } + ref back() @property { return r[$ - 1]; } + void popFront() { r = r[1 .. $]; } + void popBack() { r = r[0 .. $ - 1]; } + auto empty() @property { return r.empty; } + ref opIndex(size_t i) { return r[i]; } + auto length() @property { return r.length; } + alias opDollar = length; + auto save() { return this; } + } + static assert(isRandomAccessRange!RefAccessRange); + static assert(isRandomAccessRange!RefAccessRange); + int[4] a = [4, 3, 2, 1]; + int[2] b = [6, 5]; + auto c = chooseAmong(0, RefAccessRange(a[]), RefAccessRange(b[])); + + void refFunc(ref int a, int target) { assert(a == target); } + + refFunc(c[2], 2); + refFunc(c.front, 4); + refFunc(c.back, 1); +} + /** -$(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) +$(D roundRobin(r1, r2, r3)) yields `r1.front`, then `r2.front`, +then `r3.front`, after which it pops off one element from each and +continues again from `r1`. For example, if two ranges are involved, +it alternately yields elements off the two ranges. `roundRobin` stops after it has consumed all ranges (skipping over the ones that finish early). */ @@ -1743,12 +2002,21 @@ if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs))) static if (allSatisfy!(isForwardRange, staticMap!(Unqual, Rs))) @property auto save() { - Result result = this; - foreach (i, Unused; Rs) + auto saveSource(size_t len)() { - result.source[i] = result.source[i].save; + import std.typecons : tuple; + static assert(len > 0); + static if (len == 1) + { + return tuple(source[0].save); + } + else + { + return saveSource!(len - 1)() ~ + tuple(source[len - 1].save); + } } - return result; + return Result(saveSource!(Rs.length).expand, _current); } static if (allSatisfy!(hasLength, Rs)) @@ -1806,6 +2074,15 @@ if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs))) assert(interleave([1, 2, 3], 0).equal([1, 0, 2, 0, 3])); } +pure @safe unittest +{ + import std.algorithm.comparison : equal; + string f = "foo", b = "bar"; + auto r = roundRobin(refRange(&f), refRange(&b)); + assert(equal(r.save, "fboaor")); + assert(equal(r.save, "fboaor")); +} + /** Iterates a random-access range starting from a given point and progressively extending left and right from that point. If no initial @@ -1916,7 +2193,8 @@ 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 + input = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + to iterate over up to `n` times n = the number of elements to take Returns: @@ -1998,7 +2276,8 @@ if (isInputRange!(Unqual!Range) && 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. + // This has to return auto instead of void because of + // https://issues.dlang.org/show_bug.cgi?id=4706 source.front = v; } @@ -2083,7 +2362,8 @@ if (isInputRange!(Unqual!Range) && /// ditto @property void back(ElementType!R v) { - // This has to return auto instead of void because of Bug 4706. + // This has to return auto instead of void because of + // https://issues.dlang.org/show_bug.cgi?id=4706 assert(!empty, "Attempting to assign to the back of an empty " ~ Take.stringof); @@ -2133,10 +2413,7 @@ if (isInputRange!(Unqual!Range) && } } -/** -This template simply aliases itself to R and is useful for consistency in -generic code. -*/ +/// ditto template Take(R) if (isInputRange!(Unqual!R) && ((!isInfinite!(Unqual!R) && hasSlicing!(Unqual!R)) || is(R T == Take!T))) @@ -2251,7 +2528,8 @@ 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.) + // take(). + // See https://issues.dlang.org/show_bug.cgi?id=4464 int[] r1; Take!(int[]) t1; t1 = take(r1, 1); @@ -2277,7 +2555,8 @@ pure @safe nothrow @nogc unittest static assert(isBidirectionalRange!TR2); } -pure @safe nothrow @nogc unittest //12731 +// https://issues.dlang.org/show_bug.cgi?id=12731 +pure @safe nothrow @nogc unittest { auto a = repeat(1); auto s = a[1 .. 5]; @@ -2287,7 +2566,8 @@ pure @safe nothrow @nogc unittest //12731 assert(s[1] == 1); } -pure @safe nothrow @nogc unittest //13151 +// https://issues.dlang.org/show_bug.cgi?id=13151 +pure @safe nothrow @nogc unittest { import std.algorithm.comparison : equal; @@ -2297,16 +2577,16 @@ pure @safe nothrow @nogc unittest //13151 /** -Similar to $(LREF take), but assumes that $(D range) has at least $(D +Similar to $(LREF take), but assumes that `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). +always defines the `length` property (and initializes it to `n`) +even when `range` itself does not define `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. +The result of `takeExactly` is identical to that of $(LREF take) in +cases where the original range defines `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. +`n` elements to `takeExactly`; this will cause an assertion failure. */ auto takeExactly(R)(R range, size_t n) if (isInputRange!R) @@ -2346,9 +2626,9 @@ if (isInputRange!R) @property size_t length() const { return _n; } alias opDollar = length; - @property Take!R _takeExactly_Result_asTake() + @property auto _takeExactly_Result_asTake() { - return typeof(return)(_input, _n); + return take(_input, _n); } alias _takeExactly_Result_asTake this; @@ -2498,18 +2778,34 @@ pure @safe nothrow unittest assert(equal(t, te)); } +// https://issues.dlang.org/show_bug.cgi?id=18092 +// can't combine take and takeExactly +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + static foreach (Range; AllDummyRanges) + {{ + Range r; + assert(r.take(6).takeExactly(2).equal([1, 2])); + assert(r.takeExactly(6).takeExactly(2).equal([1, 2])); + assert(r.takeExactly(6).take(2).equal([1, 2])); + }} +} + /** 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. +42). Calling `popFront()` off that range renders it empty. -In effect $(D takeOne(r)) is somewhat equivalent to $(D take(r, 1)) but in +In effect `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 +The type returned by `takeOne` is a random-access range with length +regardless of `R`'s capabilities, as long as it is a forward range. +(another feature that distinguishes `takeOne` from `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. */ @@ -2562,8 +2858,15 @@ if (isInputRange!R) } 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); + assert( + m <= n, + "Attempting to slice a takeOne range with a larger first argument than the second." + ); + assert( + n <= length, + "Attempting to slice using an out of bounds index on a takeOne range." + ); + return n > m ? this : Result(_source, true); } // Non-standard property @property R source() { return _source; } @@ -2602,10 +2905,92 @@ pure @safe nothrow @nogc unittest static assert(!isForwardRange!NonForwardRange); auto s = takeOne(NonForwardRange()); + assert(s.length == 1); + assert(!s.empty); + assert(s.front == 42); + assert(s.back == 42); + assert(s[0] == 42); + + auto t = s[0 .. 0]; + assert(t.empty); + assert(t.length == 0); + + auto u = s[1 .. 1]; + assert(u.empty); + assert(u.length == 0); + + auto v = s[0 .. 1]; + s.popFront(); + assert(s.length == 0); + assert(s.empty); + assert(!v.empty); + assert(v.front == 42); + v.popBack(); + assert(v.empty); + assert(v.length == 0); +} + +pure @safe nothrow @nogc unittest +{ + struct NonSlicingForwardRange + { + enum empty = false; + int front() { return 42; } + void popFront() {} + @property auto save() { return this; } + } + + static assert(isForwardRange!NonSlicingForwardRange); + static assert(!hasSlicing!NonSlicingForwardRange); + + auto s = takeOne(NonSlicingForwardRange()); + assert(s.length == 1); + assert(!s.empty); assert(s.front == 42); + assert(s.back == 42); + assert(s[0] == 42); + auto t = s.save; + s.popFront(); + assert(s.length == 0); + assert(s.empty); + assert(!t.empty); + assert(t.front == 42); + t.popBack(); + assert(t.empty); + assert(t.length == 0); +} + +// Test that asserts trigger correctly +@system unittest +{ + import std.exception : assertThrown; + import core.exception : AssertError; + + struct NonForwardRange + { + enum empty = false; + int front() { return 42; } + void popFront() {} + } + + auto s = takeOne(NonForwardRange()); + + assertThrown!AssertError(s[1]); + assertThrown!AssertError(s[0 .. 2]); + + size_t one = 1; // Avoid style warnings triggered by literals + size_t zero = 0; + assertThrown!AssertError(s[one .. zero]); + + s.popFront; + assert(s.empty); + assertThrown!AssertError(s.front); + assertThrown!AssertError(s.back); + assertThrown!AssertError(s.popFront); + assertThrown!AssertError(s.popBack); } -//guards against issue 16999 +// https://issues.dlang.org/show_bug.cgi?id=16999 pure @safe unittest { auto myIota = new class @@ -2626,7 +3011,7 @@ pure @safe unittest /++ 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 + guaranteed to have `length` and be random access regardless of `R`'s capabilities. +/ auto takeNone(R)() @@ -2664,7 +3049,7 @@ if (isInputRange!R) //member version if it's defined. static if (is(typeof(R.takeNone))) auto retval = range.takeNone(); - //@@@BUG@@@ 8339 + // https://issues.dlang.org/show_bug.cgi?id=8339 else static if (isDynamicArray!R)/+ || (is(R == struct) && __traits(compiles, {auto r = R.init;}) && R.init.empty))+/ { @@ -2676,7 +3061,8 @@ if (isInputRange!R) else auto retval = takeExactly(range, 0); - //@@@BUG@@@ 7892 prevents this from being done in an out block. + // https://issues.dlang.org/show_bug.cgi?id=7892 prevents this from being + // done in an out block. assert(retval.empty); return retval; } @@ -2772,13 +3158,13 @@ pure @safe nothrow unittest import std.format : format; - foreach (range; AliasSeq!([1, 2, 3, 4, 5], + static 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]), + // https://issues.dlang.org/show_bug.cgi?id=8339 + // forces this to be takeExactly `InitStruct([1, 2, 3]), TakeNoneStruct([1, 2, 3]))) { static assert(takeNone(range).empty, typeof(range).stringof); @@ -2786,7 +3172,7 @@ pure @safe nothrow unittest static assert(is(typeof(range) == typeof(takeNone(range))), typeof(range).stringof); } - foreach (range; AliasSeq!(NormalStruct([1, 2, 3]), + static foreach (range; AliasSeq!(NormalStruct([1, 2, 3]), InitStruct([1, 2, 3]))) { static assert(takeNone(range).empty, typeof(range).stringof); @@ -2809,28 +3195,29 @@ pure @safe nothrow unittest auto filtered = filter!"true"([1, 2, 3, 4, 5]); assert(takeNone(filtered).empty); - //@@@BUG@@@ 8339 and 5941 force this to be takeExactly + // https://issues.dlang.org/show_bug.cgi?id=8339 and + // https://issues.dlang.org/show_bug.cgi?id=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). + + Return a range advanced to within `_n` elements of the end of + + `range`. + - + Intended as the _range equivalent of the Unix + + 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 + + of `range` is less than or equal to `_n`, `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. + + length. Completes in $(BIGOH range.length) time for all other ranges. + + Params: - + range = _range to get _tail of + + 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 + + Returns the _tail of `range` augmented with length information +/ auto tail(Range)(Range range, size_t n) if (isInputRange!Range && !isInfinite!Range && @@ -2889,7 +3276,7 @@ pure @safe nothrow unittest .assumeWontThrow); } -// @nogc prevented by @@@BUG@@@ 15408 +// @nogc prevented by https://issues.dlang.org/show_bug.cgi?id=15408 pure nothrow @safe /+@nogc+/ unittest { import std.algorithm.comparison : equal; @@ -2929,27 +3316,27 @@ pure @safe nothrow @nogc unittest /++ Convenience function which calls - $(REF popFrontN, std, _range, primitives)`(range, n)` and returns `range`. + $(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)` + $(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 + range = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 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) + $(REF popFront, std, range, primitives), $(REF popBackN, std, range, primitives) +/ R drop(R)(R range, size_t n) if (isInputRange!R) @@ -2957,13 +3344,6 @@ 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 @@ -2976,6 +3356,15 @@ if (isBidirectionalRange!R) assert("hello world".take(6).drop(3).equal("lo ")); } +/// ditto +R dropBack(R)(R range, size_t n) +if (isBidirectionalRange!R) +{ + range.popBackN(n); + return range; +} + +/// @safe unittest { import std.algorithm.comparison : equal; @@ -3018,28 +3407,28 @@ if (isBidirectionalRange!R) } /++ - Similar to $(LREF drop) and $(D dropBack) but they call - $(D range.$(LREF popFrontExactly)(n)) and $(D range.popBackExactly(n)) + Similar to $(LREF drop) and `dropBack` but they call + $(D range.$(LREF popFrontExactly)(n)) and `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) + Note: Unlike `drop`, `dropExactly` will assume that the + range holds at least `n` elements. This makes `dropExactly` + faster than `drop`, but it also means that if `range` does + not contain at least `n` elements, it will attempt to call `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. + `popFrontExactly` when it is guaranteed that `range` holds at least + `n` elements. Params: - range = the input range to drop from + range = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 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) + $(REF popFrontExcatly, std, range, primitives), + $(REF popBackExcatly, std, range, primitives) +/ R dropExactly(R)(R range, size_t n) if (isInputRange!R) @@ -3076,13 +3465,13 @@ if (isBidirectionalRange!R) /++ Convenience function which calls - $(D range.popFront()) and returns $(D range). $(D dropOne) + `range.popFront()` and returns `range`. `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. + whereas `popFront` would require multiple statements. - $(D dropBackOne) provides the same functionality but instead calls - $(D range.popBack()). + `dropBackOne` provides the same functionality but instead calls + `range.popBack()`. +/ R dropOne(R)(R range) if (isInputRange!R) @@ -3123,13 +3512,17 @@ pure @safe nothrow unittest } /** -Create a range which repeats one value forever. +Create a range which repeats one value. Params: - value = the value to repeat + value = the _value to repeat + n = the number of times to repeat `value` Returns: - An infinite random access range with slicing. + If `n` is not defined, an infinite random access range + with slicing. + + If `n` is defined, a random access range with slicing. */ struct Repeat(T) { @@ -3177,7 +3570,7 @@ public: "Attempting to slice a Repeat with a larger first argument than the second." ); } - body + do { return this.takeExactly(j - i); } @@ -3198,7 +3591,7 @@ pure @safe nothrow unittest { import std.algorithm.comparison : equal; - assert(equal(5.repeat().take(4), [ 5, 5, 5, 5 ])); + assert(5.repeat().take(4).equal([5, 5, 5, 5])); } pure @safe nothrow unittest @@ -3222,24 +3615,22 @@ pure @safe nothrow unittest assert(r2.front == 5); } -/** - Repeats $(D value) exactly $(D n) times. Equivalent to $(D - take(repeat(value), n)). -*/ +/// ditto Take!(Repeat!T) repeat(T)(T value, size_t n) { return take(repeat(value), n); } /// -pure @safe nothrow @nogc unittest +pure @safe nothrow unittest { import std.algorithm.comparison : equal; - assert(equal(5.repeat(4), 5.repeat().take(4))); + assert(5.repeat(4).equal([5, 5, 5, 5])); } -pure @safe nothrow unittest //12007 +// https://issues.dlang.org/show_bug.cgi?id=12007 +pure @safe nothrow unittest { static class C{} Repeat!(immutable int) ri; @@ -3295,7 +3686,7 @@ if (isCallable!fun) } /// -@safe pure unittest +@safe pure nothrow unittest { import std.algorithm.comparison : equal; import std.algorithm.iteration : map; @@ -3306,7 +3697,7 @@ if (isCallable!fun) } /// -@safe pure unittest +@safe pure nothrow unittest { import std.algorithm.comparison : equal; @@ -3334,6 +3725,7 @@ private struct Generator(Fun...) { static assert(Fun.length == 1); static assert(isInputRange!Generator); + import std.traits : FunctionAttribute, functionAttributes, ReturnType; private: static if (is(Fun[0])) @@ -3378,7 +3770,7 @@ public: } } -@safe unittest +@safe nothrow unittest { import std.algorithm.comparison : equal; @@ -3404,7 +3796,7 @@ public: } // verify ref mechanism works -@system unittest +@system nothrow unittest { int[10] arr; int idx; @@ -3436,12 +3828,12 @@ public: /** 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 +infinite (fact that would make `Cycle` the identity application), +`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 +If the original range has random access, `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, +`index`. `Cycle` works with static arrays in addition to ranges, mostly for performance reasons. Note: The input range must not be empty. @@ -3543,7 +3935,7 @@ if (isForwardRange!R && !isInfinite!R) { assert(i <= j); } - body + do { return this[i .. $].takeExactly(j - i); } @@ -3567,6 +3959,12 @@ if (isForwardRange!R && !isInfinite!R) _current = input.save; } + private this(R original, R current) + { + _original = original; + _current = current; + } + /// ditto @property auto ref front() { @@ -3606,10 +4004,7 @@ if (isForwardRange!R && !isInfinite!R) @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; + return Cycle(_original, _current.save); } } } @@ -3621,7 +4016,7 @@ if (isInfinite!R) alias Cycle = R; } -/// +/// ditto struct Cycle(R) if (isStaticArray!R) { @@ -3688,7 +4083,7 @@ nothrow: "Attempting to slice a Repeat with a larger first argument than the second." ); } - body + do { return this[i .. $].takeExactly(j - i); } @@ -3744,7 +4139,7 @@ if (isStaticArray!R) return Cycle!R(input, index); } -@safe unittest +@safe nothrow unittest { import std.algorithm.comparison : equal; import std.internal.test.dummyrange : AllDummyRanges; @@ -3806,7 +4201,7 @@ if (isStaticArray!R) } } -@system unittest // For static arrays. +@system nothrow unittest // For static arrays. { import std.algorithm.comparison : equal; @@ -3824,7 +4219,7 @@ if (isStaticArray!R) static assert(is(typeof(cConst[1 .. $]) == const(C))); } -@safe unittest // For infinite ranges +@safe nothrow unittest // For infinite ranges { struct InfRange { @@ -3868,7 +4263,7 @@ if (isStaticArray!R) } } -@system unittest +@system @nogc nothrow unittest { import std.algorithm.comparison : equal; @@ -3899,7 +4294,8 @@ if (isStaticArray!R) assert(cleS.front == 0); } -@system unittest //10845 +// https://issues.dlang.org/show_bug.cgi?id=10845 +@system unittest { import std.algorithm.comparison : equal; import std.algorithm.iteration : filter; @@ -3908,12 +4304,13 @@ if (isStaticArray!R) assert(equal(cycle(a).take(10), [0, 1, 2, 0, 1, 2, 0, 1, 2, 0])); } -@safe unittest // 12177 +// https://issues.dlang.org/show_bug.cgi?id=12177 +@safe unittest { static assert(__traits(compiles, recurrence!q{a[n - 1] ~ a[n - 2]}("1", "0"))); } -// Issue 13390 +// https://issues.dlang.org/show_bug.cgi?id=13390 @system unittest { import core.exception : AssertError; @@ -3921,12 +4318,22 @@ if (isStaticArray!R) assertThrown!AssertError(cycle([0, 1, 2][0 .. 0])); } +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + string s = "foo"; + auto r = refRange(&s).cycle.take(4); + assert(equal(r.save, "foof")); + assert(equal(r.save, "foof")); +} + 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]). + that allows accessing the current element in the `n`th range by + using `e[n]`. `zip` is similar to $(LREF lockstep), but `lockstep` doesn't bundle its elements and uses the `opApply` protocol. @@ -3934,7 +4341,7 @@ private alias lengthType(R) = typeof(R.init.length.init); `foreach` iterations. Params: - sp = controls what `zip` will do if the _ranges are different lengths + 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 @@ -3943,8 +4350,13 @@ private alias lengthType(R) = typeof(R.init.length.init); 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 + An `Exception` if all of the ranges are not the same length and `sp` is set to `StoppingPolicy.requireSameLength`. + + Limitations: The `@nogc` and `nothrow` attributes cannot be inferred for + the `Zip` struct because $(LREF StoppingPolicy) can vary at runtime. This + limitation is not shared by the anonymous range returned by the `zip` + function when not given an explicit `StoppingPolicy` as an argument. */ struct Zip(Ranges...) if (Ranges.length && allSatisfy!(isInputRange, Ranges)) @@ -3968,7 +4380,7 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) } /** - Returns $(D true) if the range is at end. The test depends on the + Returns `true` if the range is at end. The test depends on the stopping policy. */ static if (allSatisfy!(isInfinite, R)) @@ -4102,7 +4514,7 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) { //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();} + @property tryMoveBack(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].moveBack();} //ElementType(tryMoveBack!0, tryMoveBack!1, ...) return mixin(q{ElementType(%(tryMoveBack!%s, %))}.format(iota(0, R.length))); } @@ -4162,7 +4574,7 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) } /** - Calls $(D popBack) for all controlled ranges. + Calls `popBack` for all controlled ranges. */ static if (allSatisfy!(isBidirectionalRange, R)) { @@ -4199,7 +4611,7 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) /** Returns the length of this range. Defined only if all ranges define - $(D length). + `length`. */ static if (allSatisfy!(hasLength, R)) { @@ -4242,7 +4654,7 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) } /** - Returns the $(D n)th element in the composite range. Defined if all + Returns the `n`th element in the composite range. Defined if all ranges offer random access. */ static if (allSatisfy!(isRandomAccessRange, R)) @@ -4257,7 +4669,7 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) } /** - Assigns to the $(D n)th element in the composite range. Defined if + Assigns to the `n`th element in the composite range. Defined if all ranges offer random access. */ static if (allSatisfy!(hasAssignableElements, R)) @@ -4273,7 +4685,7 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) } /** - Destructively reads the $(D n)th element in the composite + Destructively reads the `n`th element in the composite range. Defined if all ranges offer random access. */ static if (allSatisfy!(hasMobileElements, R)) @@ -4294,22 +4706,75 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) auto zip(Ranges...)(Ranges ranges) if (Ranges.length && allSatisfy!(isInputRange, Ranges)) { - return Zip!Ranges(ranges); + import std.meta : anySatisfy, templateOr; + static if (allSatisfy!(isInfinite, Ranges) || Ranges.length == 1) + { + return ZipShortest!(Ranges)(ranges); + } + else static if (allSatisfy!(isBidirectionalRange, Ranges)) + { + static if (allSatisfy!(templateOr!(isInfinite, hasLength), Ranges) + && allSatisfy!(templateOr!(isInfinite, hasSlicing), Ranges) + && allSatisfy!(isBidirectionalRange, staticMap!(Take, Ranges))) + { + // If all the ranges are bidirectional, if possible slice them to + // the same length to simplify the implementation. + static assert(anySatisfy!(hasLength, Ranges)); + static foreach (i, Range; Ranges) + static if (hasLength!Range) + { + static if (!is(typeof(minLen) == size_t)) + size_t minLen = ranges[i].length; + else + {{ + const x = ranges[i].length; + if (x < minLen) minLen = x; + }} + } + import std.format : format; + static if (!anySatisfy!(isInfinite, Ranges)) + return mixin(`ZipShortest!(Yes.allKnownSameLength, staticMap!(Take, Ranges))`~ + `(%(ranges[%s][0 .. minLen]%|, %))`.format(iota(0, Ranges.length))); + else + return mixin(`ZipShortest!(Yes.allKnownSameLength, staticMap!(Take, Ranges))`~ + `(%(take(ranges[%s], minLen)%|, %))`.format(iota(0, Ranges.length))); + } + else static if (allSatisfy!(isRandomAccessRange, Ranges)) + { + // We can't slice but we can still use random access to ensure + // "back" is retrieving the same index for each range. + return ZipShortest!(Ranges)(ranges); + } + else + { + // If bidirectional range operations would not be supported by + // ZipShortest that might have actually been a bug since Zip + // supported `back` without verifying that each range had the + // same length, but for the sake of backwards compatibility + // use the old Zip to continue supporting them. + return Zip!Ranges(ranges); + } + } + else + { + return ZipShortest!(Ranges)(ranges); + } } /// -pure @safe unittest +@nogc nothrow 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])); + auto arr = only(0, 1, 2); + auto part1 = zip(arr, arr.dropOne).map!"a[0] + a[1]"; + assert(part1.equal(only(1, 3))); } /// -pure @safe unittest +nothrow pure @safe unittest { import std.conv : to; @@ -4334,8 +4799,8 @@ pure @safe unittest } } -/// $(D zip) is powerful - the following code sorts two arrays in parallel: -pure @safe unittest +/// `zip` is powerful - the following code sorts two arrays in parallel: +nothrow pure @safe unittest { import std.algorithm.sorting : sort; @@ -4356,8 +4821,8 @@ if (Ranges.length && allSatisfy!(isInputRange, Ranges)) } /** - Dictates how iteration in a $(D Zip) should stop. By default stop at - the end of the shortest of all ranges. + Dictates how iteration in a $(LREF zip) and $(LREF lockstep) should stop. + By default stop at the end of the shortest of all ranges. */ enum StoppingPolicy { @@ -4369,90 +4834,426 @@ enum StoppingPolicy requireSameLength, } -@system unittest +/// +pure @safe 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.exception : assertThrown; + import std.range.primitives; 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); + auto a = [1, 2, 3]; + auto b = [4, 5, 6, 7]; - // Test infiniteness propagation. - static assert(isInfinite!(typeof(zip(repeat(1), repeat(1))))); + auto shortest = zip(StoppingPolicy.shortest, a, b); + assert(shortest.equal([ + tuple(1, 4), + tuple(2, 5), + tuple(3, 6) + ])); - // 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))); + auto longest = zip(StoppingPolicy.longest, a, b); + assert(longest.equal([ + tuple(1, 4), + tuple(2, 5), + tuple(3, 6), + tuple(0, 7) + ])); - alias FOO = Zip!(immutable(int)[], immutable(float)[]); + auto same = zip(StoppingPolicy.requireSameLength, a, b); + same.popFrontN(3); + assertThrown!Exception(same.popFront); +} - foreach (t; stuff.expand) +/+ +Non-public. Like $(LREF Zip) with `StoppingPolicy.shortest` +except it properly implements `back` and `popBack` in the +case of uneven ranges or disables those operations when +it is not possible to guarantee they are correct. ++/ +package template ZipShortest(Ranges...) +if (Ranges.length && __traits(compiles, { - 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.*/ } + static assert(allSatisfy!(isInputRange, Ranges)); + })) +{ + alias ZipShortest = .ZipShortest!( + Ranges.length == 1 || allSatisfy!(isInfinite, Ranges) + ? Yes.allKnownSameLength + : No.allKnownSameLength, + Ranges); +} +/+ non-public, ditto +/ +package struct ZipShortest(Flag!"allKnownSameLength" allKnownSameLength, Ranges...) +if (Ranges.length && allSatisfy!(isInputRange, Ranges)) +{ + import std.format : format; //for generic mixins + import std.meta : anySatisfy, templateOr; + import std.typecons : Tuple; - auto zLongest = zip(StoppingPolicy.longest, arr1, arr2); - assert(!zLongest.ranges[0].empty); - assert(!zLongest.ranges[1].empty); + deprecated("Use of an undocumented alias R.") + alias R = Ranges; // Unused here but defined in case library users rely on it. + private Ranges ranges; + alias ElementType = Tuple!(staticMap!(.ElementType, Ranges)); - zLongest.popFront(); - zLongest.popFront(); - assert(!zLongest.empty); - assert(zLongest.ranges[0].empty); - assert(!zLongest.ranges[1].empty); + /+ + Builds an object. Usually this is invoked indirectly by using the + $(LREF zip) function. + +/ + this(Ranges rs) + { + ranges[] = rs[]; + } + + /+ + Returns `true` if the range is at end. + +/ + static if (allKnownSameLength ? anySatisfy!(isInfinite, Ranges) + : allSatisfy!(isInfinite, Ranges)) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + static if (allKnownSameLength) + { + return ranges[0].empty; + } + else + { + static foreach (i; 0 .. Ranges.length) + { + if (ranges[i].empty) + return true; + } + return false; + } + } + } + + /+ + Forward range primitive. Only present if each constituent range is a + forward range. + +/ + static if (allSatisfy!(isForwardRange, Ranges)) + @property typeof(this) save() + { + return mixin(`typeof(return)(%(ranges[%s].save%|, %))`.format(iota(0, Ranges.length))); + } + + /+ + Returns the current iterated element. + +/ + @property ElementType front() + { + return mixin(`typeof(return)(%(ranges[%s].front%|, %))`.format(iota(0, Ranges.length))); + } + + /+ + Sets the front of all iterated ranges. Only present if each constituent + range has assignable elements. + +/ + static if (allSatisfy!(hasAssignableElements, Ranges)) + @property void front()(ElementType v) + { + static foreach (i; 0 .. Ranges.length) + ranges[i].front = v[i]; + } + + /+ + Moves out the front. Present if each constituent range has mobile elements. + +/ + static if (allSatisfy!(hasMobileElements, Ranges)) + ElementType moveFront()() + { + return mixin(`typeof(return)(%(ranges[%s].moveFront()%|, %))`.format(iota(0, Ranges.length))); + } + + private enum bool isBackWellDefined = allSatisfy!(isBidirectionalRange, Ranges) + && (allKnownSameLength + || allSatisfy!(isRandomAccessRange, Ranges) + // Could also add the case where there is one non-infinite bidirectional + // range that defines `length` and all others are infinite random access + // ranges. Adding this would require appropriate branches in + // back/moveBack/popBack. + ); + + /+ + Returns the rightmost element. Present if all constituent ranges are + bidirectional and either there is a compile-time guarantee that all + ranges have the same length (in `allKnownSameLength`) or all ranges + provide random access to elements. + +/ + static if (isBackWellDefined) + @property ElementType back() + { + static if (allKnownSameLength) + { + return mixin(`typeof(return)(%(ranges[%s].back()%|, %))`.format(iota(0, Ranges.length))); + } + else + { + const backIndex = length - 1; + return mixin(`typeof(return)(%(ranges[%s][backIndex]%|, %))`.format(iota(0, Ranges.length))); + } + } + + /+ + Moves out the back. Present if `back` is defined and + each constituent range has mobile elements. + +/ + static if (isBackWellDefined && allSatisfy!(hasMobileElements, Ranges)) + ElementType moveBack()() + { + static if (allKnownSameLength) + { + return mixin(`typeof(return)(%(ranges[%s].moveBack()%|, %))`.format(iota(0, Ranges.length))); + } + else + { + const backIndex = length - 1; + return mixin(`typeof(return)(%(ranges[%s].moveAt(backIndex)%|, %))`.format(iota(0, Ranges.length))); + } + } + + /+ + Sets the rightmost element. Only present if `back` is defined and + each constituent range has assignable elements. + +/ + static if (isBackWellDefined && allSatisfy!(hasAssignableElements, Ranges)) + @property void back()(ElementType v) + { + static if (allKnownSameLength) + { + static foreach (i; 0 .. Ranges.length) + ranges[i].back = v[i]; + } + else + { + const backIndex = length - 1; + static foreach (i; 0 .. Ranges.length) + ranges[i][backIndex] = v[i]; + } + } + + /+ + Calls `popFront` on each constituent range. + +/ + void popFront() + { + static foreach (i; 0 .. Ranges.length) + ranges[i].popFront(); + } + + /+ + Pops the rightmost element. Present if `back` is defined. + +/ + static if (isBackWellDefined) + void popBack() + { + static if (allKnownSameLength) + { + static foreach (i; 0 .. Ranges.length) + ranges[i].popBack; + } + else + { + const len = length; + static foreach (i; 0 .. Ranges.length) + static if (!isInfinite!(Ranges[i])) + if (ranges[i].length == len) + ranges[i].popBack(); + } + } + + /+ + Returns the length of this range. Defined if at least one + constituent range defines `length` and the other ranges all also + define `length` or are infinite, or if at least one constituent + range defines `length` and there is a compile-time guarantee that + all ranges have the same length (in `allKnownSameLength`). + +/ + static if (allKnownSameLength + ? anySatisfy!(hasLength, Ranges) + : (anySatisfy!(hasLength, Ranges) + && allSatisfy!(templateOr!(isInfinite, hasLength), Ranges))) + { + @property size_t length() + { + static foreach (i, Range; Ranges) + { + static if (hasLength!Range) + { + static if (!is(typeof(minLen) == size_t)) + size_t minLen = ranges[i].length; + else static if (!allKnownSameLength) + {{ + const x = ranges[i].length; + if (x < minLen) minLen = x; + }} + } + } + return minLen; + } + + alias opDollar = length; + } + + /+ + Returns a slice of the range. Defined if all constituent ranges + support slicing. + +/ + static if (allSatisfy!(hasSlicing, Ranges)) + { + // Note: we will know that all elements of the resultant range + // will have the same length but we cannot change `allKnownSameLength` + // because the `hasSlicing` predicate tests that the result returned + // by `opSlice` has the same type as the receiver. + auto opSlice()(size_t from, size_t to) + { + //(ranges[0][from .. to], ranges[1][from .. to], ...) + enum sliceArgs = `(%(ranges[%s][from .. to]%|, %))`.format(iota(0, Ranges.length)); + static if (__traits(compiles, mixin(`typeof(this)`~sliceArgs))) + return mixin(`typeof(this)`~sliceArgs); + else + // The type is different anyway so we might as well + // explicitly set allKnownSameLength. + return mixin(`ZipShortest!(Yes.allKnownSameLength, staticMap!(Take, Ranges))` + ~sliceArgs); + } + } + + /+ + Returns the `n`th element in the composite range. Defined if all + constituent ranges offer random access. + +/ + static if (allSatisfy!(isRandomAccessRange, Ranges)) + ElementType opIndex()(size_t n) + { + return mixin(`typeof(return)(%(ranges[%s][n]%|, %))`.format(iota(0, Ranges.length))); + } + + /+ + Sets the `n`th element in the composite range. Defined if all + constituent ranges offer random access and have assignable elements. + +/ + static if (allSatisfy!(isRandomAccessRange, Ranges) + && allSatisfy!(hasAssignableElements, Ranges)) + void opIndexAssign()(ElementType v, size_t n) + { + static foreach (i; 0 .. Ranges.length) + ranges[i][n] = v[i]; + } + + /+ + Destructively reads the `n`th element in the composite + range. Defined if all constituent ranges offer random + access and have mobile elements. + +/ + static if (allSatisfy!(isRandomAccessRange, Ranges) + && allSatisfy!(hasMobileElements, Ranges)) + ElementType moveAt()(size_t n) + { + return mixin(`typeof(return)(%(ranges[%s].moveAt(n)%|, %))`.format(iota(0, Ranges.length))); + } +} + +pure @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]); + + auto 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 + // https://issues.dlang.org/show_bug.cgi?id=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)]); + // https://issues.dlang.org/show_bug.cgi?id=18524 + // moveBack instead performs moveFront + { + auto r = zip([1,2,3]); + assert(r.moveBack()[0] == 3); + assert(r.moveFront()[0] == 1); + } + // Doesn't work yet. Issues w/ emplace. // static assert(is(Zip!(immutable int[], immutable float[]))); @@ -4491,7 +5292,7 @@ enum StoppingPolicy +/ } -pure @safe unittest +nothrow pure @safe unittest { import std.algorithm.sorting : sort; @@ -4505,7 +5306,7 @@ pure @safe unittest assert(b == [6, 5, 2, 1, 3]); } -@safe pure unittest +nothrow pure @safe unittest { import std.algorithm.comparison : equal; import std.typecons : tuple; @@ -4520,7 +5321,7 @@ pure @safe unittest assert(equal(z2, [tuple(7, 0L)])); } -// Text for Issue 11196 +// Test for https://issues.dlang.org/show_bug.cgi?id=11196 @safe pure unittest { import std.exception : assertThrown; @@ -4531,7 +5332,8 @@ pure @safe unittest assertThrown(zip(StoppingPolicy.longest, cast(S[]) null, new int[1]).front); } -@safe pure unittest //12007 +// https://issues.dlang.org/show_bug.cgi?id=12007 +@nogc nothrow @safe pure unittest { static struct R { @@ -4546,7 +5348,7 @@ pure @safe unittest assert(z.save == z); } -pure @system unittest +nothrow pure @system unittest { import std.typecons : tuple; @@ -4559,6 +5361,98 @@ pure @system unittest assert(z2.front == tuple(0,1)); } +@nogc nothrow pure @safe unittest +{ + // Test zip's `back` and `length` with non-equal ranges. + static struct NonSliceableRandomAccess + { + private int[] a; + @property ref front() + { + return a.front; + } + @property ref back() + { + return a.back; + } + ref opIndex(size_t i) + { + return a[i]; + } + void popFront() + { + a.popFront(); + } + void popBack() + { + a.popBack(); + } + auto moveFront() + { + return a.moveFront(); + } + auto moveBack() + { + return a.moveBack(); + } + auto moveAt(size_t i) + { + return a.moveAt(i); + } + bool empty() const + { + return a.empty; + } + size_t length() const + { + return a.length; + } + typeof(this) save() + { + return this; + } + } + static assert(isRandomAccessRange!NonSliceableRandomAccess); + static assert(!hasSlicing!NonSliceableRandomAccess); + static foreach (iteration; 0 .. 2) + {{ + int[5] data = [101, 102, 103, 201, 202]; + static if (iteration == 0) + { + auto r1 = NonSliceableRandomAccess(data[0 .. 3]); + auto r2 = NonSliceableRandomAccess(data[3 .. 5]); + } + else + { + auto r1 = data[0 .. 3]; + auto r2 = data[3 .. 5]; + } + auto z = zip(r1, r2); + static assert(isRandomAccessRange!(typeof(z))); + assert(z.length == 2); + assert(z.back[0] == 102 && z.back[1] == 202); + z.back = typeof(z.back)(-102, -202);// Assign to back. + assert(z.back[0] == -102 && z.back[1] == -202); + z.popBack(); + assert(z.length == 1); + assert(z.back[0] == 101 && z.back[1] == 201); + z.front = typeof(z.front)(-101, -201); + assert(z.moveBack() == typeof(z.back)(-101, -201)); + z.popBack(); + assert(z.empty); + }} +} + +@nogc nothrow pure @safe unittest +{ + // Test opSlice on infinite `zip`. + auto z = zip(repeat(1), repeat(2)); + assert(hasSlicing!(typeof(z))); + auto slice = z[10 .. 20]; + assert(slice.length == 10); + static assert(!is(typeof(z) == typeof(slice))); +} + /* Generate lockstep's opApply function as a mixin string. If withIndex is true prepend a size_t index to the delegate. @@ -4647,21 +5541,25 @@ private string lockstepMixin(Ranges...)(bool withIndex, bool reverse) } /** - Iterate multiple ranges in lockstep using a $(D foreach) loop. In contrast to + Iterate multiple ranges in lockstep using a `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) + range is passed in, the `Lockstep` aliases itself away. If the + ranges are of different lengths and `s` == `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 + lengths and `s` == `StoppingPolicy.requireSameLength`, throw an + exception. `s` may not be `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. + Iterating over `Lockstep` in reverse and with an index is only possible + when `s` == `StoppingPolicy.requireSameLength`, in order to preserve + indexes. If an attempt is made at iterating in reverse when `s` == + `StoppingPolicy.shortest`, an exception will be thrown. - By default $(D StoppingPolicy) is set to $(D StoppingPolicy.shortest). + By default `StoppingPolicy` is set to `StoppingPolicy.shortest`. + + Limitations: The `pure`, `@safe`, `@nogc`, or `nothrow` attributes cannot be + inferred for `lockstep` iteration. $(LREF zip) can infer the first two due to + a different implementation. See_Also: $(LREF zip) @@ -4710,48 +5608,6 @@ private: 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)) @@ -4789,7 +5645,8 @@ if (allSatisfy!(isInputRange, Ranges)) } } -@system unittest // Bugzilla 15860: foreach_reverse on lockstep +// https://issues.dlang.org/show_bug.cgi?id=15860: foreach_reverse on lockstep +@system unittest { auto arr1 = [0, 1, 2, 3]; auto arr2 = [4, 5, 6, 7]; @@ -4871,8 +5728,9 @@ if (allSatisfy!(isInputRange, Ranges)) 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. + // Just make sure 1-range case instantiates. This hangs the compiler + // when no explicit stopping policy is specified due to + // https://issues.dlang.org/show_bug.cgi?id=4652 auto stuff = lockstep([1,2,3,4,5], StoppingPolicy.shortest); foreach (i, a; stuff) { @@ -4937,15 +5795,57 @@ if (allSatisfy!(isInputRange, Ranges)) })); } +private 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; +} + /** 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 +range. The type `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 +When calling `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 @@ -4956,17 +5856,17 @@ 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 +where `n` will be the index of the current value, and `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 +`state[i]`, where valid values of `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 +If the function is passed in string form, the state has name `"a"` +and the zero-based index in the recurrence has name `"n"`. The +given string must return the desired value for `a[n]` given +`a[n - 1]`, `a[n - 2]`, `a[n - 3]`,..., `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 +to `recurrence`. The `Recurrence` struct itself takes care of managing the recurrence's state and shifting it appropriately. */ struct Recurrence(alias fun, StateType, size_t stateSize) @@ -5006,7 +5906,7 @@ struct Recurrence(alias fun, StateType, size_t stateSize) } /// -@safe unittest +pure @safe nothrow unittest { import std.algorithm.comparison : equal; @@ -5042,7 +5942,7 @@ recurrence(alias fun, State...)(State initial) return typeof(return)(state); } -@safe unittest +pure @safe nothrow unittest { import std.algorithm.comparison : equal; @@ -5064,15 +5964,15 @@ recurrence(alias fun, State...)(State initial) } /** - $(D Sequence) is similar to $(D Recurrence) except that iteration is + `Sequence` is similar to `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 + closed form). This means that the `n`th element in the series is + computable directly from the initial values and `n` itself. This + implies that the interface offered by `Sequence` is a random-access + range, as opposed to the regular `Recurrence`, which only offers forward iteration. - The state of the sequence is stored as a $(D Tuple) so it can be + The state of the sequence is stored as a `Tuple` so it can be heterogeneous. */ struct Sequence(alias fun, State) @@ -5114,7 +6014,7 @@ public: "Attempting to slice a Sequence with a larger first argument than the second." ); } - body + do { return typeof(this)(_state, _n + lower).take(upper - lower); } @@ -5143,7 +6043,7 @@ auto sequence(alias fun, State...)(State args) } /// Odd numbers, using function in string form: -@safe unittest +pure @safe nothrow @nogc unittest { auto odds = sequence!("a[0] + n * a[1]")(1, 2); assert(odds.front == 1); @@ -5154,7 +6054,7 @@ auto sequence(alias fun, State...)(State args) } /// Triangular numbers, using function in lambda form: -@safe unittest +pure @safe nothrow @nogc unittest { auto tri = sequence!((a,n) => n*(n+1)/2)(); @@ -5167,9 +6067,11 @@ auto sequence(alias fun, State...)(State args) } /// Fibonacci numbers, using function in explicit form: -@safe unittest +@safe nothrow @nogc unittest { - import std.math : pow, round, sqrt; + import std.math.exponential : pow; + import std.math.rounding : round; + import std.math.algebraic : sqrt; static ulong computeFib(S)(S state, size_t n) { // Binet's formula @@ -5189,7 +6091,7 @@ auto sequence(alias fun, State...)(State args) assert(fib[9] == 55); } -@safe unittest +pure @safe nothrow @nogc unittest { import std.typecons : Tuple, tuple; auto y = Sequence!("a[0] + n * a[1]", Tuple!(int, int))(tuple(0, 4)); @@ -5210,7 +6112,7 @@ auto sequence(alias fun, State...)(State args) } } -@safe unittest +pure @safe nothrow @nogc unittest { import std.algorithm.comparison : equal; @@ -5221,21 +6123,21 @@ auto sequence(alias fun, State...)(State args) //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])); + assert(equal(odds[0 .. 5], only(1, 3, 5, 7, 9))); + assert(equal(odds[3 .. 7], only(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 .. 3], only(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])); + assert(equal(odds.take(3), only(21, 23, 25))); } -// Issue 5036 -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=5036 +pure @safe nothrow unittest { auto s = sequence!((a, n) => new int)(0); assert(s.front != s.front); // no caching @@ -5253,21 +6155,21 @@ auto sequence(alias fun, State...)(State args) 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). + A range that goes through the numbers `begin`, $(D begin + step), + $(D begin + 2 * step), `...`, up to and excluding `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 + user-defined types that support `++`, the range is an input range. - An integral iota also supports $(D in) operator from the right. It takes + An integral iota also supports `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. + `contains` does the same as in, but from lefthand side. Example: --- @@ -5429,7 +6331,8 @@ if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) { if (current < pastLast) { - assert(unsigned(pastLast - current) <= size_t.max); + assert(unsigned(pastLast - current) <= size_t.max, + "`iota` range is too long"); this.current = current; this.pastLast = pastLast; @@ -5442,17 +6345,34 @@ if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) } @property bool empty() const { return current == pastLast; } - @property inout(Value) front() inout { assert(!empty); return current; } - void popFront() { assert(!empty); ++current; } + @property inout(Value) front() inout + { + assert(!empty, "Attempt to access `front` of empty `iota` range"); + return current; + } + void popFront() + { + assert(!empty, "Attempt to `popFront` of empty `iota` range"); + ++current; + } - @property inout(Value) back() inout { assert(!empty); return cast(inout(Value))(pastLast - 1); } - void popBack() { assert(!empty); --pastLast; } + @property inout(Value) back() inout + { + assert(!empty, "Attempt to access `back` of empty `iota` range"); + return cast(inout(Value))(pastLast - 1); + } + void popBack() + { + assert(!empty, "Attempt to `popBack` of empty `iota` range"); + --pastLast; + } @property auto save() { return this; } inout(Value) opIndex(size_t n) inout { - assert(n < this.length); + assert(n < this.length, + "Attempt to read out-of-bounds index of `iota` range"); // Just cast to Value here because doing so gives overflow behavior // consistent with calling popFront() n times. @@ -5467,7 +6387,8 @@ if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) inout(Result) opSlice() inout { return this; } inout(Result) opSlice(ulong lower, ulong upper) inout { - assert(upper >= lower && upper <= this.length); + assert(upper >= lower && upper <= this.length, + "Attempt to get out-of-bounds slice of `iota` range"); return cast(inout Result) Result(cast(Value)(current + lower), cast(Value)(pastLast - (length - upper))); @@ -5500,7 +6421,7 @@ in assert(step != 0, "iota: step must not be 0"); assert((end - begin) / step >= 0, "iota: incorrect startup parameters"); } -body +do { alias Value = Unqual!(CommonType!(B, E, S)); static struct Result @@ -5579,10 +6500,10 @@ body } /// -@safe unittest +pure @safe unittest { import std.algorithm.comparison : equal; - import std.math : approxEqual; + import std.math.operations : isClose; auto r = iota(0, 10, 1); assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); @@ -5596,11 +6517,12 @@ body 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])); + assert(isClose(rf, [0.0, 0.1, 0.2, 0.3, 0.4])); } -nothrow @nogc @safe unittest +pure nothrow @nogc @safe unittest { + import std.traits : Signed; //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); @@ -5620,7 +6542,7 @@ debug @system unittest assertThrown!AssertError(iota(0f,1f,-0.1f)); } -@system unittest +pure @system nothrow unittest { int[] a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; auto r1 = iota(a.ptr, a.ptr + a.length, 1); @@ -5629,7 +6551,7 @@ debug @system unittest assert(&a[4] in r1); } -@safe unittest +pure @safe nothrow @nogc unittest { assert(iota(1UL, 0UL).length == 0); assert(iota(1UL, 0UL, 1).length == 0); @@ -5639,11 +6561,11 @@ debug @system unittest assert(iota(ulong.max, 0).length == 0); } -@safe unittest +pure @safe unittest { import std.algorithm.comparison : equal; import std.algorithm.searching : count; - import std.math : approxEqual, nextUp, nextDown; + import std.math.operations : isClose, nextUp, nextDown; import std.meta : AliasSeq; static assert(is(ElementType!(typeof(iota(0f))) == float)); @@ -5701,7 +6623,7 @@ debug @system unittest 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(isClose(rf, [0.0, 0.1, 0.2, 0.3, 0.4][])); assert(rf.length == 5); rf.popFront(); @@ -5709,35 +6631,35 @@ debug @system unittest auto rfSlice = rf[1 .. 4]; assert(rfSlice.length == 3); - assert(approxEqual(rfSlice, [0.2, 0.3, 0.4])); + assert(isClose(rfSlice, [0.2, 0.3, 0.4])); rfSlice.popFront(); - assert(approxEqual(rfSlice[0], 0.3)); + assert(isClose(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)); + assert(isClose(rfSlice, [0.3, 0.4])); + assert(isClose(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][])); + assert(isClose(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(isClose(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][])); + assert(isClose(rf, [0.0, -0.1, -0.2, -0.3, -0.4][])); rfSlice = rf[2 .. 5]; - assert(approxEqual(rfSlice, [-0.2, -0.3, -0.4])); + assert(isClose(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][])); + assert(isClose(rf, [0.0, -0.1, -0.2, -0.3, -0.4, -0.5][])); // iota of longs auto rl = iota(5_000_000L); @@ -5752,14 +6674,15 @@ debug @system unittest 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) + // iota of unsigned zero length (https://issues.dlang.org/show_bug.cgi?id=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). + // https://issues.dlang.org/show_bug.cgi?id=7982 + // unsigned reverse iota can be buggy if `.length` doesn't + // take them into account assert(iota(10u, 0u, -1).length == 10); assert(iota(10u, 0u, -2).length == 5); assert(iota(uint.max, uint.max-10, -1).length == 10); @@ -5775,17 +6698,17 @@ debug @system unittest assert(!(int.max in iota(20u, 10u, -1))); - // Issue 8920 - foreach (Type; AliasSeq!(byte, ubyte, short, ushort, + // https://issues.dlang.org/show_bug.cgi?id=8920 + static 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 +pure @safe nothrow unittest { import std.algorithm.mutation : copy; auto idx = new size_t[100]; @@ -5795,11 +6718,11 @@ debug @system unittest @safe unittest { import std.meta : AliasSeq; - foreach (range; AliasSeq!(iota(2, 27, 4), + static 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; @@ -5808,7 +6731,7 @@ debug @system unittest const s1 = cRange[]; const s2 = cRange[0 .. 3]; const l = cRange.length; - } + }} } @system unittest @@ -5840,8 +6763,7 @@ debug @system unittest } } -@nogc nothrow pure @safe -unittest +@nogc nothrow pure @safe unittest { { ushort start = 0, end = 10, step = 2; @@ -5864,7 +6786,7 @@ unittest * 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 ==). + * as they can be incremented with `++` and compared with `<` or `==`. */ /// ditto auto iota(B, E)(B begin, E end) @@ -5952,7 +6874,30 @@ enum TransverseOptions checking was already done from the outside of the range. */ assumeNotJagged, - } +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.exception : assertThrown; + + auto arr = [[1, 2], [3, 4, 5]]; + + auto r1 = arr.frontTransversal!(TransverseOptions.assumeJagged); + assert(r1.equal([1, 3])); + + // throws on construction + assertThrown!Exception(arr.frontTransversal!(TransverseOptions.enforceNotJagged)); + + auto r2 = arr.frontTransversal!(TransverseOptions.assumeNotJagged); + assert(r2.equal([1, 3])); + + // either assuming or checking for equal lengths makes + // the result a random access range + assert(r2[0] == 1); + static assert(!__traits(compiles, r1[0])); +} /** Given a range of ranges, iterate transversally through the first @@ -6059,7 +7004,7 @@ struct FrontTransversal(Ror, } /** - Duplicates this $(D frontTransversal). Note that only the encapsulating + Duplicates this `frontTransversal`. Note that only the encapsulating range of range will be duplicated. Underlying ranges will not be duplicated. */ @@ -6139,19 +7084,10 @@ struct FrontTransversal(Ror, _input[n].front = val; } } - /// Ditto - static if (hasLength!RangeOfRanges) - { - @property size_t length() - { - return _input.length; - } - - alias opDollar = length; - } + mixin ImplementLength!_input; /** - Slicing if offered if $(D RangeOfRanges) supports slicing and all the + Slicing if offered if `RangeOfRanges` supports slicing and all the conditions for supporting indexing are met. */ static if (hasSlicing!RangeOfRanges) @@ -6179,7 +7115,7 @@ FrontTransversal!(RangeOfRanges, opt) frontTransversal( } /// -@safe unittest +pure @safe nothrow unittest { import std.algorithm.comparison : equal; int[][] x = new int[][2]; @@ -6256,8 +7192,8 @@ FrontTransversal!(RangeOfRanges, opt) frontTransversal( } } -// Issue 16363 -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=16363 +pure @safe nothrow unittest { import std.algorithm.comparison : equal; @@ -6268,21 +7204,28 @@ FrontTransversal!(RangeOfRanges, opt) frontTransversal( static assert(isRandomAccessRange!(typeof(ft))); } -// Bugzilla 16442 -@safe unittest +// https://issues.dlang.org/show_bug.cgi?id=16442 +pure @safe nothrow unittest { int[][] arr = [[], []]; - auto ft1 = frontTransversal!(TransverseOptions.assumeNotJagged)(arr); - assert(ft1.empty); + auto ft = frontTransversal!(TransverseOptions.assumeNotJagged)(arr); + assert(ft.empty); +} + +// ditto +pure @safe unittest +{ + int[][] arr = [[], []]; - auto ft2 = frontTransversal!(TransverseOptions.enforceNotJagged)(arr); - assert(ft2.empty); + auto ft = frontTransversal!(TransverseOptions.enforceNotJagged)(arr); + assert(ft.empty); } /** Given a range of ranges, iterate transversally through the - `n`th element of each of the enclosed ranges. + `n`th element of each of the enclosed ranges. This function + is similar to `unzip` in other languages. Params: opt = Controls the assumptions the function makes about the lengths @@ -6471,19 +7414,10 @@ struct Transversal(Ror, } } - /// Ditto - static if (hasLength!RangeOfRanges) - { - @property size_t length() - { - return _input.length; - } - - alias opDollar = length; - } + mixin ImplementLength!_input; /** - Slicing if offered if $(D RangeOfRanges) supports slicing and all the + Slicing if offered if `RangeOfRanges` supports slicing and all the conditions for supporting indexing are met. */ static if (hasSlicing!RangeOfRanges) @@ -6518,7 +7452,17 @@ Transversal!(RangeOfRanges, opt) transversal x[0] = [1, 2]; x[1] = [3, 4]; auto ror = transversal(x, 1); - assert(equal(ror, [ 2, 4 ][])); + assert(equal(ror, [ 2, 4 ])); +} + +/// The following code does a full unzip +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + int[][] y = [[1, 2, 3], [4, 5, 6]]; + auto z = y.front.walkLength.iota.map!(i => transversal(y, i)); + assert(equal!equal(z, [[1, 4], [2, 5], [3, 6]])); } @safe unittest @@ -6581,16 +7525,26 @@ Transversal!(RangeOfRanges, opt) transversal assert(mat1[1] == 10); } -struct Transposed(RangeOfRanges) +struct Transposed(RangeOfRanges, + TransverseOptions opt = TransverseOptions.assumeJagged) if (isForwardRange!RangeOfRanges && isInputRange!(ElementType!RangeOfRanges) && hasAssignableElements!RangeOfRanges) { - //alias ElementType = typeof(map!"a.front"(RangeOfRanges.init)); - this(RangeOfRanges input) { this._input = input; + static if (opt == TransverseOptions.enforceNotJagged) + { + import std.exception : enforce; + + if (empty) return; + immutable commonLength = _input.front.length; + foreach (e; _input) + { + enforce(e.length == commonLength); + } + } } @property auto front() @@ -6618,10 +7572,13 @@ if (isForwardRange!RangeOfRanges && } } - // ElementType opIndex(size_t n) - // { - // return _input[n].front; - // } + static if (isRandomAccessRange!(ElementType!RangeOfRanges)) + { + auto ref opIndex(size_t n) + { + return transversal!opt(_input, n); + } + } @property bool empty() { @@ -6633,11 +7590,6 @@ if (isForwardRange!RangeOfRanges && return true; } - @property Transposed save() - { - return Transposed(_input.save); - } - auto opSlice() { return this; } private: @@ -6651,7 +7603,7 @@ private: assert(transposed(ror).empty); } -// Issue 9507 +// https://issues.dlang.org/show_bug.cgi?id=9507 @safe unittest { import std.algorithm.comparison : equal; @@ -6663,16 +7615,61 @@ private: ])); } +// https://issues.dlang.org/show_bug.cgi?id=17742 +@safe unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.comparison : equal; + auto ror = 5.iota.map!(y => 5.iota.map!(x => x * y).array).array; + assert(ror[3][2] == 6); + auto result = transposed!(TransverseOptions.assumeNotJagged)(ror); + assert(result[2][3] == 6); + + auto x = [[1,2,3],[4,5,6]]; + auto y = transposed!(TransverseOptions.assumeNotJagged)(x); + assert(y.front.equal([1,4])); + assert(y[0].equal([1,4])); + assert(y[0][0] == 1); + assert(y[1].equal([2,5])); + assert(y[1][1] == 5); + + auto yy = transposed!(TransverseOptions.enforceNotJagged)(x); + assert(yy.front.equal([1,4])); + assert(yy[0].equal([1,4])); + assert(yy[0][0] == 1); + assert(yy[1].equal([2,5])); + assert(yy[1][1] == 5); + + auto z = x.transposed; // assumeJagged + assert(z.front.equal([1,4])); + assert(z[0].equal([1,4])); + assert(!is(typeof(z[0][0]))); +} + +@safe unittest +{ + import std.exception : assertThrown; + + auto r = [[1,2], [3], [4,5], [], [6]]; + assertThrown(r.transposed!(TransverseOptions.enforceNotJagged)); +} + /** 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. + +Params: + opt = Controls the assumptions the function makes about the lengths of the ranges (i.e. jagged or not) + rr = Range of ranges */ -Transposed!RangeOfRanges transposed(RangeOfRanges)(RangeOfRanges rr) +Transposed!(RangeOfRanges, opt) transposed +(TransverseOptions opt = TransverseOptions.assumeJagged, RangeOfRanges) +(RangeOfRanges rr) if (isForwardRange!RangeOfRanges && isInputRange!(ElementType!RangeOfRanges) && hasAssignableElements!RangeOfRanges) { - return Transposed!RangeOfRanges(rr); + return Transposed!(RangeOfRanges, opt)(rr); } /// @@ -6707,11 +7704,11 @@ if (isForwardRange!RangeOfRanges && } } -// Issue 8764 +// https://issues.dlang.org/show_bug.cgi?id=8764 @safe unittest { import std.algorithm.comparison : equal; - ulong[1] t0 = [ 123 ]; + ulong[] t0 = [ 123 ]; assert(!hasAssignableElements!(typeof(t0[].chunks(1)))); assert(!is(typeof(transposed(t0[].chunks(1))))); @@ -6722,13 +7719,13 @@ if (isForwardRange!RangeOfRanges && } /** -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 +This struct takes two ranges, `source` and `indices`, and creates a view +of `source` as if its elements were reordered according to `indices`. +`indices` may include only a subset of the elements of `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 +`Source` must be a random access range. The returned range will be +bidirectional or random-access if `Indices` is bidirectional or random-access, respectively. */ struct Indexed(Source, Indices) @@ -6837,16 +7834,7 @@ if (isRandomAccessRange!Source && isInputRange!Indices && } } - static if (hasLength!Indices) - { - /// Ditto - @property size_t length() - { - return _indices.length; - } - - alias opDollar = length; - } + mixin ImplementLength!_indices; static if (isRandomAccessRange!Indices) { @@ -6910,7 +7898,7 @@ if (isRandomAccessRange!Source && isInputRange!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. + an `Indexed` without adding another layer of indirection. */ size_t physicalIndex(size_t logicalIndex) { @@ -6981,13 +7969,13 @@ Indexed!(Source, Indices) indexed(Source, Indices)(Source source, Indices indice } /** -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. +This range iterates over fixed-sized chunks of size `chunkSize` of a +`source` range. `Source` must be an $(REF_ALTTEXT input range, isInputRange, std,range,primitives). +`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 `!isInfinite!Source` and `source.walkLength` is not evenly +divisible by `chunkSize`, the back element of this range will contain +fewer than `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 @@ -7049,7 +8037,7 @@ if (isInputRange!Source) static if (hasLength!Source) { - /// Length. Only if $(D hasLength!Source) is $(D true) + /// Length. Only if `hasLength!Source` is `true` @property size_t length() { // Note: _source.length + _chunkSize may actually overflow. @@ -7070,7 +8058,7 @@ if (isInputRange!Source) /** Indexing and slicing operations. Provided only if - $(D hasSlicing!Source) is $(D true). + `hasSlicing!Source` is `true`. */ auto opIndex(size_t index) { @@ -7180,7 +8168,7 @@ if (isInputRange!Source) { /** Bidirectional range primitives. Provided only if both - $(D hasSlicing!Source) and $(D hasLength!Source) are $(D true). + `hasSlicing!Source` and `hasLength!Source` are `true`. */ @property auto back() { @@ -7399,16 +8387,16 @@ if (isInputRange!Source) /** -This range splits a $(D source) range into $(D chunkCount) chunks of -approximately equal length. $(D Source) must be a forward range with +This range splits a `source` range into `chunkCount` chunks of +approximately equal length. `Source` must be a forward range with known length. -Unlike $(LREF chunks), $(D evenChunks) takes a chunk count (not size). +Unlike $(LREF chunks), `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. +`chunkCount` must not be zero, unless `source` is also empty. */ struct EvenChunks(Source) if (isForwardRange!Source && hasLength!Source) @@ -7460,7 +8448,7 @@ if (isForwardRange!Source && hasLength!Source) { /** Indexing, slicing and bidirectional operations and range primitives. - Provided only if $(D hasSlicing!Source) is $(D true). + Provided only if `hasSlicing!Source` is `true`. */ auto opIndex(size_t index) { @@ -7590,46 +8578,117 @@ if (isForwardRange!Source && hasLength!Source) 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. +The `Source` range must be at least a $(REF_ALTTEXT ForwardRange, isForwardRange, std,range,primitives) +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. + f = Whether the last element has fewer elements than `windowSize` + it should be be ignored (`No.withPartial`) or added (`Yes.withPartial`) 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. + forwarding, random access, and slicing. + +Note: To avoid performance overhead, $(REF_ALTTEXT bi-directionality, isBidirectionalRange, std,range,primitives) + is only available when $(REF hasSlicing, std,range,primitives) + and $(REF hasLength, std,range,primitives) are true. See_Also: $(LREF chunks) */ -// Explicitly set to private to delay the release until 2.076 -private -auto slide(Flag!"withFewerElements" f = Yes.withFewerElements, +auto slide(Flag!"withPartial" f = Yes.withPartial, Source)(Source source, size_t windowSize, size_t stepSize = 1) - if (isForwardRange!Source) +if (isForwardRange!Source) { return Slides!(f, Source)(source, windowSize, stepSize); } -private struct Slides(Flag!"withFewerElements" withFewerElements = Yes.withFewerElements, Source) - if (isForwardRange!Source) +/// Iterate over ranges with windows +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + 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]] + )); +} + +/// set a custom stepsize (default 1) +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert(6.iota.slide(1, 2).equal!equal( + [[0], [2], [4]] + )); + + assert(6.iota.slide(2, 4).equal!equal( + [[0, 1], [4, 5]] + )); + + assert(iota(7).slide(2, 2).equal!equal( + [[0, 1], [2, 3], [4, 5], [6]] + )); + + assert(iota(12).slide(2, 4).equal!equal( + [[0, 1], [4, 5], [8, 9]] + )); +} + +/// Allow the last slide to have fewer elements than windowSize +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert(3.iota.slide!(No.withPartial)(4).empty); + assert(3.iota.slide!(Yes.withPartial)(4).equal!equal( + [[0, 1, 2]] + )); +} + +/// Count all the possible substrings of length 2 +@safe pure nothrow unittest +{ + import std.algorithm.iteration : each; + + int[dstring] d; + "AGAGA"d.slide!(Yes.withPartial)(2).each!(a => d[a]++); + assert(d == ["AG"d: 2, "GA"d: 2]); +} + +/// withPartial only has an effect if last element in the range doesn't have the full size +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert(5.iota.slide!(Yes.withPartial)(3, 4).equal!equal([[0, 1, 2], [4]])); + assert(6.iota.slide!(Yes.withPartial)(3, 4).equal!equal([[0, 1, 2], [4, 5]])); + assert(7.iota.slide!(Yes.withPartial)(3, 4).equal!equal([[0, 1, 2], [4, 5, 6]])); + + assert(5.iota.slide!(No.withPartial)(3, 4).equal!equal([[0, 1, 2]])); + assert(6.iota.slide!(No.withPartial)(3, 4).equal!equal([[0, 1, 2]])); + assert(7.iota.slide!(No.withPartial)(3, 4).equal!equal([[0, 1, 2], [4, 5, 6]])); +} + +private struct Slides(Flag!"withPartial" withPartial = Yes.withPartial, Source) +if (isForwardRange!Source) { private: - Source _source; - size_t _windowSize; - size_t _stepSize; + Source source; + size_t windowSize; + size_t stepSize; static if (hasLength!Source) { @@ -7637,17 +8696,18 @@ private: } else { - // if there's no information about the length, track needs to be kept manually - Source _nextSource; + // 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); - } + + static if (withPartial) + bool hasShownPartialBefore; public: /// Standard constructor @@ -7655,82 +8715,124 @@ public: { assert(windowSize > 0, "windowSize must be greater than zero"); assert(stepSize > 0, "stepSize must be greater than zero"); - _source = source; - _windowSize = windowSize; - _stepSize = stepSize; + this.source = source; + this.windowSize = windowSize; + this.stepSize = stepSize; static if (needsEndTracker) { - // _nextSource is used to "look into the future" and check for the end - _nextSource = source.save; - _nextSource.popFrontN(windowSize); + // `nextSource` is used to "look one step into the future" and check for the end + // this means `nextSource` is advanced by `stepSize` on every `popFront` + nextSource = source.save.drop(windowSize); + } + + if (source.empty) + { + _empty = true; + return; } - static if (!withFewerElements) + static if (withPartial) + { + static if (needsEndTracker) + { + if (nextSource.empty) + hasShownPartialBefore = true; + } + else + { + if (source.length <= windowSize) + hasShownPartialBefore = true; + } + + } + else { // empty source range is needed, s.t. length, slicing etc. works properly static if (needsEndTracker) { - if (_nextSource.empty) - _source = _nextSource; + if (nextSource.empty) + _empty = true; } 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); - } - } + if (source.length < windowSize) + _empty = true; } } - - _empty = _source.empty; } /// Forward range primitives. Always present. @property auto front() { - assert(!empty, "Attempting to access front on an empty slide"); + 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)]; + static if (withPartial) + { + import std.algorithm.comparison : min; + return source[0 .. min(windowSize, source.length)]; + } + else + { + assert(windowSize <= source.length, "The last element is smaller than the current windowSize."); + return source[0 .. windowSize]; + } } else { - return _source.save.take(_windowSize); + static if (withPartial) + return source.save.take(windowSize); + else + return source.save.takeExactly(windowSize); } } /// Ditto void popFront() { - assert(!empty, "Attempting to call popFront() on an empty slide"); - _source.popFrontN(_stepSize); + 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 (source.empty) { - if (_nextSource.empty) + _empty = true; + return; + } + + static if (withPartial) + { + if (hasShownPartialBefore) _empty = true; + } + + static if (needsEndTracker) + { + // Check the upcoming slide + auto poppedElements = nextSource.popFrontN(stepSize); + static if (withPartial) + { + if (poppedElements < stepSize || nextSource.empty) + hasShownPartialBefore = true; + } else - _nextSource.popFrontN(_stepSize); + { + if (poppedElements < stepSize) + _empty = true; + } } else { - if (_source.length < _windowSize) - _empty = true; + static if (withPartial) + { + if (source.length <= windowSize) + hasShownPartialBefore = true; + } + else + { + if (source.length < windowSize) + _empty = true; + } } } @@ -7751,24 +8853,109 @@ public: /// Ditto @property typeof(this) save() { - return typeof(this)(_source.save, _windowSize, _stepSize); + return typeof(this)(source.save, windowSize, stepSize); } - static if (hasLength!Source) - { - /// Length. Only if $(D hasLength!Source) is $(D true) + static if (hasLength!Source) + { + // gaps between the last element and the end of the range + private size_t gap() + { + /* + * Note: + * - In the following `end` 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 + */ + return (source.length - windowSize) % stepSize; + } + + private size_t numberOfFullFrames() + { + /** + 5.iota.slides(2, 1) => [0, 1], [1, 2], [2, 3], [3, 4] (4) + 7.iota.slides(2, 2) => [0, 1], [2, 3], [4, 5], [6] (3) + 7.iota.slides(2, 3) => [0, 1], [3, 4], [6] (2) + 6.iota.slides(3, 2) => [0, 1, 2], [2, 3, 4], [4, 5] (2) + 7.iota.slides(3, 3) => [0, 1, 2], [3, 4, 5], [6] (2) + + As the last window is only added iff its complete, + we don't count the last window except if it's full due to integer rounding. + */ + return 1 + (source.length - windowSize) / stepSize; + } + + // Whether the last slide frame size is less than windowSize + private bool hasPartialElements() + { + static if (withPartial) + return gap != 0 && source.length > numberOfFullFrames * stepSize; + else + return 0; + } + + /// Length. Only if `hasLength!Source` is `true` @property size_t length() { - if (_source.length < _windowSize) + if (source.length < windowSize) { - static if (withFewerElements) - return 1; + static if (withPartial) + return source.length > 0; else return 0; } else { - return (_source.length - _windowSize + _stepSize) / _stepSize; + /*** + We bump the pointer by stepSize for every element. + If withPartial, we don't count the last element if its size + isn't windowSize + + At most: + [p, p + stepSize, ..., p + stepSize * n] + + 5.iota.slides(2, 1) => [0, 1], [1, 2], [2, 3], [3, 4] (4) + 7.iota.slides(2, 2) => [0, 1], [2, 3], [4, 5], [6] (4) + 7.iota.slides(2, 3) => [0, 1], [3, 4], [6] (3) + 7.iota.slides(3, 2) => [0, 1, 2], [2, 3, 4], [4, 5, 6] (3) + 7.iota.slides(3, 3) => [0, 1, 2], [3, 4, 5], [6] (3) + */ + return numberOfFullFrames + hasPartialElements; } } } @@ -7781,22 +8968,22 @@ public: */ auto opIndex(size_t index) { - immutable start = index * _stepSize; + immutable start = index * stepSize; static if (isInfinite!Source) { - immutable end = start + _windowSize; + immutable end = start + windowSize; } else { import std.algorithm.comparison : min; - immutable len = _source.length; + immutable len = source.length; assert(start < len, "slide index out of bounds"); - immutable end = min(start + _windowSize, len); + immutable end = min(start + windowSize, len); } - return _source[start .. end]; + return source[start .. end]; } static if (!isInfinite!Source) @@ -7805,39 +8992,83 @@ public: 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; + assert(upper <= length, "slide slicing index out of bounds"); + assert(lower <= upper, "slide slicing index out of bounds"); - immutable len = _source.length; + lower *= stepSize; + upper *= stepSize; - /* - * 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); + immutable len = source.length; + + static if (withPartial) + { + import std.algorithm.comparison : max; + + if (lower == upper) + return this[$ .. $]; + + /* + A) If `stepSize` >= `windowSize` => `rightPos = upper` + + [0, 1, 2, 3, 4, 5, 6].slide(2, 3) -> s = [[0, 1], [3, 4], [6]] + rightPos for s[0 .. 2]: (upper=2) * (stepSize=3) = 6 + 6.iota.slide(2, 3) = [[0, 1], [3, 4]] + + B) If `stepSize` < `windowSize` => add `windowSize - stepSize` to `upper` + + [0, 1, 2, 3].slide(2) = [[0, 1], [1, 2], [2, 3]] + rightPos for s[0 .. 1]: = (upper=1) * (stepSize=1) = 1 + 1.iota.slide(2) = [[0]] + + rightPos for s[0 .. 1]: = (upper=1) * (stepSize=1) + (windowSize-stepSize=1) = 2 + 1.iota.slide(2) = [[0, 1]] + + More complex: + + 20.iota.slide(7, 6)[0 .. 2] + rightPos: (upper=2) * (stepSize=6) = 12.iota + 12.iota.slide(7, 6) = [[0, 1, 2, 3, 4, 5, 6], [6, 7, 8, 9, 10, 11]] + + Now we add up for the difference between `windowSize` and `stepSize`: + + rightPos: (upper=2) * (stepSize=6) + (windowSize-stepSize=1) = 13.iota + 13.iota.slide(7, 6) = [[0, 1, 2, 3, 4, 5, 6], [6, 7, 8, 9, 10, 11, 12]] + */ + immutable rightPos = min(len, upper + max(0, windowSize - stepSize)); + } + else + { + /* + After we have normalized `lower` and `upper` by `stepSize`, + we only need to look at the case of `stepSize=1`. + As `leftPos`, is equal to `lower`, we will only look `rightPos`. + Notice that starting from `upper`, + we only need to move for `windowSize - 1` to the right: + + - [0, 1, 2, 3].slide(2) -> s = [[0, 1], [1, 2], [2, 3]] + rightPos for s[0 .. 3]: (upper=3) + (windowSize=2) - 1 = 4 + + - [0, 1, 2, 3].slide(3) -> s = [[0, 1, 2], [1, 2, 3]] + rightPos for s[0 .. 2]: (upper=2) + (windowSize=3) - 1 = 4 + + - [0, 1, 2, 3, 4].slide(4) -> s = [[0, 1, 2, 3], [1, 2, 3, 4]] + rightPos for s[0 .. 2]: (upper=2) + (windowSize=4) - 1 = 5 + */ + immutable rightPos = min(upper + windowSize - 1, len); + } + + return typeof(this)(source[min(lower, len) .. rightPos], windowSize, stepSize); } } else static if (hasSliceToEnd) { - //For slicing an infinite chunk, we need to slice the source to the infinite end. + // 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); + return typeof(this)(source[lower * stepSize .. $], windowSize, stepSize) + .takeExactly(upper - lower); } } @@ -7853,15 +9084,15 @@ public: //Slice to dollar typeof(this) opSlice(size_t lower, DollarToken) { - return typeof(this)(_source[lower * _stepSize .. $], _windowSize, _stepSize); + 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]; + // 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; @@ -7878,12 +9109,12 @@ public: { static if (hasSliceToEnd) { - return typeof(this)(_source[$ .. $], _windowSize, _stepSize); + return typeof(this)(source[$ .. $], windowSize, stepSize); } else { - immutable len = _source.length; - return typeof(this)(_source[len .. len], _windowSize, _stepSize); + immutable len = source.length; + return typeof(this)(source[len .. len], windowSize, stepSize); } } @@ -7892,15 +9123,15 @@ public: { import std.algorithm.comparison : min; assert(lower <= length, "slide slicing index out of bounds"); - lower *= _stepSize; + lower *= stepSize; static if (hasSliceToEnd) { - return typeof(this)(_source[min(lower, _source.length) .. $], _windowSize, _stepSize); + 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); + immutable len = source.length; + return typeof(this)(source[min(lower, len) .. len], windowSize, stepSize); } } @@ -7925,54 +9156,20 @@ public: 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; + immutable len = source.length; - // check for underflow - immutable start = (len > _windowSize + gap) ? len - _windowSize - gap : 0; + static if (withPartial) + { + if (source.length <= windowSize) + return source[0 .. source.length]; - return _source[start .. len - gap]; + if (hasPartialElements) + return source[numberOfFullFrames * stepSize .. len]; + } + + // check for underflow + immutable start = (len > windowSize + gap) ? len - windowSize - gap : 0; + return source[start .. len - gap]; } /// Ditto @@ -7980,258 +9177,236 @@ public: { assert(!empty, "Attempting to call popBack() on an empty slide"); - immutable end = _source.length > _stepSize ? _source.length - _stepSize : 0; - _source = _source[0 .. end]; + // Move by stepSize + immutable end = source.length > stepSize ? source.length - stepSize : 0; + + static if (withPartial) + { + if (hasShownPartialBefore || source.empty) + { + _empty = true; + return; + } + + // pop by stepSize, except for the partial frame at the end + if (hasPartialElements) + source = source[0 .. source.length - gap]; + else + source = source[0 .. end]; + } + else + { + source = source[0 .. end]; + } - if (_source.length < _windowSize) + 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 +// test @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)); + assert(4.iota.slide!(Yes.withPartial)(1).equal!equal(res1)); static immutable res2 = [[0, 1], [1, 2], [2, 3]]; - assert(4.iota.slide(2).equal!equal(res2)); + assert(4.iota.slide!(Yes.withPartial)(2).equal!equal(res2)); } -// different window sizes +// test different window sizes @safe pure nothrow unittest { - import std.algorithm.comparison : equal; import std.array : array; + import std.algorithm.comparison : equal; - 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([0, 1, 2, 3].slide!(Yes.withPartial)(1).array == [[0], [1], [2], [3]]); + assert([0, 1, 2, 3].slide!(Yes.withPartial)(2).array == [[0, 1], [1, 2], [2, 3]]); + assert([0, 1, 2, 3].slide!(Yes.withPartial)(3).array == [[0, 1, 2], [1, 2, 3]]); + assert([0, 1, 2, 3].slide!(Yes.withPartial)(4).array == [[0, 1, 2, 3]]); + assert([0, 1, 2, 3].slide!(No.withPartial)(5).walkLength == 0); + assert([0, 1, 2, 3].slide!(Yes.withPartial)(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]])); + assert(iota(2).slide!(Yes.withPartial)(2).front.equal([0, 1])); + assert(iota(3).slide!(Yes.withPartial)(2).equal!equal([[0, 1],[1, 2]])); + assert(iota(3).slide!(Yes.withPartial)(3).equal!equal([[0, 1, 2]])); + assert(iota(3).slide!(No.withPartial)(4).walkLength == 0); + assert(iota(3).slide!(Yes.withPartial)(4).equal!equal([[0, 1, 2]])); + assert(iota(1, 4).slide!(Yes.withPartial)(1).equal!equal([[1], [2], [3]])); + assert(iota(1, 4).slide!(Yes.withPartial)(3).equal!equal([[1, 2, 3]])); } -@safe unittest +// test combinations +@safe pure nothrow unittest { import std.algorithm.comparison : equal; + import std.typecons : tuple; - 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]] - )); + alias t = tuple; + auto list = [ + t(t(1, 1), [[0], [1], [2], [3], [4], [5]]), + t(t(1, 2), [[0], [2], [4]]), + t(t(1, 3), [[0], [3]]), + t(t(1, 4), [[0], [4]]), + t(t(1, 5), [[0], [5]]), + t(t(2, 1), [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]]), + t(t(2, 2), [[0, 1], [2, 3], [4, 5]]), + t(t(2, 3), [[0, 1], [3, 4]]), + t(t(2, 4), [[0, 1], [4, 5]]), + t(t(3, 1), [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5]]), + t(t(3, 3), [[0, 1, 2], [3, 4, 5]]), + t(t(4, 1), [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]), + t(t(4, 2), [[0, 1, 2, 3], [2, 3, 4, 5]]), + t(t(5, 1), [[0, 1, 2, 3, 4], [1, 2, 3, 4, 5]]), + ]; + + static foreach (Partial; [Yes.withPartial, No.withPartial]) + foreach (e; list) + assert(6.iota.slide!Partial(e[0].expand).equal!equal(e[1])); + + auto listSpecial = [ + t(t(2, 5), [[0, 1], [5]]), + t(t(3, 2), [[0, 1, 2], [2, 3, 4], [4, 5]]), + t(t(3, 4), [[0, 1, 2], [4, 5]]), + t(t(4, 3), [[0, 1, 2, 3], [3, 4, 5]]), + t(t(5, 2), [[0, 1, 2, 3, 4], [2, 3, 4, 5]]), + t(t(5, 3), [[0, 1, 2, 3, 4], [3, 4, 5]]), + ]; + foreach (e; listSpecial) + { + assert(6.iota.slide!(Yes.withPartial)(e[0].expand).equal!equal(e[1])); + assert(6.iota.slide!(No.withPartial)(e[0].expand).equal!equal(e[1].dropBackOne)); + } } -// emptyness, copyability, strings +// test emptiness and copyability @safe pure nothrow unittest { import std.algorithm.comparison : equal; - import std.algorithm.iteration : each, map; + import std.algorithm.iteration : map; // check with empty input int[] d; - assert(d.slide(2).empty); - assert(d.slide(2, 2).empty); + assert(d.slide!(Yes.withPartial)(2).empty); + assert(d.slide!(Yes.withPartial)(2, 2).empty); // is copyable? - auto e = iota(5).slide(2); + auto e = iota(5).slide!(Yes.withPartial)(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 +@safe pure nothrow unittest +{ + import std.algorithm.iteration : each; - // test with strings int[dstring] f; - "AGAGA"d.slide(3).each!(a => f[a]++); + "AGAGA"d.slide!(Yes.withPartial)(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]++); + "ABCDEFG"d.slide!(Yes.withPartial)(3, 3).each!(a => g[a]++); + assert(g == ["ABC"d:1, "DEF"d:1, "G": 1]); + g = null; + "ABCDEFG"d.slide!(No.withPartial)(3, 3).each!(a => g[a]++); assert(g == ["ABC"d:1, "DEF"d:1]); } -// test slicing, length -@safe pure nothrow unittest +// test with utf8 strings +@safe unittest { + import std.stdio; 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 + assert("ä.ö.ü.".slide!(Yes.withPartial)(3, 2).equal!equal(["ä.ö", "ö.ü", "ü."])); + assert("ä.ö.ü.".slide!(No.withPartial)(3, 2).equal!equal(["ä.ö", "ö.ü"])); + + "😄😅😆😇😈😄😅😆😇😈".slide!(Yes.withPartial)(2, 4).equal!equal(["😄😅", "😈😄", "😇😈"]); + "😄😅😆😇😈😄😅😆😇😈".slide!(No.withPartial)(2, 4).equal!equal(["😄😅", "😈😄", "😇😈"]); + "😄😅😆😇😈😄😅😆😇😈".slide!(Yes.withPartial)(3, 3).equal!equal(["😄😅😆", "😇😈😄", "😅😆😇", "😈"]); + "😄😅😆😇😈😄😅😆😇😈".slide!(No.withPartial)(3, 3).equal!equal(["😄😅😆", "😇😈😄", "😅😆😇"]); +} + +// test length +@safe pure nothrow unittest +{ + // Slides with fewer elements are empty or 1 for Yes.withPartial + static foreach (expectedLength, Partial; [No.withPartial, Yes.withPartial]) + {{ + assert(3.iota.slide!(Partial)(4, 2).walkLength == expectedLength); + assert(3.iota.slide!(Partial)(4).walkLength == expectedLength); + assert(3.iota.slide!(Partial)(4, 3).walkLength == expectedLength); + }} + + static immutable list = [ + // iota slide expected + [4, 2, 1, 3, 3], + [5, 3, 1, 3, 3], + [7, 2, 2, 4, 3], + [12, 2, 4, 3, 3], + [6, 1, 2, 3, 3], + [6, 2, 4, 2, 2], + [3, 2, 4, 1, 1], + [5, 2, 1, 4, 4], + [7, 2, 2, 4, 3], + [7, 2, 3, 3, 2], + [7, 3, 2, 3, 3], + [7, 3, 3, 3, 2], + ]; + foreach (e; list) + { + assert(e[0].iota.slide!(Yes.withPartial)(e[1], e[2]).length == e[3]); + assert(e[0].iota.slide!(No.withPartial)(e[1], e[2]).length == e[4]); + } +} + +// test index and slicing @safe pure nothrow unittest { - assert(iota(3).slide(4).length == 1); - assert(iota(3).slide(4, 4).length == 1); + import std.algorithm.comparison : equal; + import std.array : array; + + static foreach (Partial; [Yes.withPartial, No.withPartial]) + { + foreach (s; [5, 7, 10, 15, 20]) + foreach (windowSize; 1 .. 10) + foreach (stepSize; 1 .. 10) + { + auto r = s.iota.slide!Partial(windowSize, stepSize); + auto arr = r.array; + assert(r.length == arr.length); + + // test indexing + foreach (i; 0 .. arr.length) + assert(r[i] == arr[i]); - 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); + // test slicing + foreach (i; 0 .. arr.length) + { + foreach (j; i .. arr.length) + assert(r[i .. j].equal(arr[i .. j])); - 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(r[i .. $].equal(arr[i .. $])); + } - 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); + // test opDollar slicing + assert(r[$/2 .. $].equal(arr[$/2 .. $])); + assert(r[$ .. $].empty); + if (arr.empty) + { + assert(r[$ .. 0].empty); + assert(r[$/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 @@ -8239,27 +9414,30 @@ public: { 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]])); + static foreach (Partial; [Yes.withPartial, No.withPartial]) + {{ + // InfiniteRange without RandomAccess + auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1); + assert(fibs.slide!Partial(2).take(2).equal!equal([[1, 1], [1, 2]])); + assert(fibs.slide!Partial(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])); + // InfiniteRange with RandomAccess and slicing + auto odds = sequence!("a[0] + n * a[1]")(1, 2); + auto oddsByPairs = odds.slide!Partial(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]])); + 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]])); + auto oddsWithGaps = odds.slide!Partial(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 @@ -8267,188 +9445,157 @@ public: { 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)); + static foreach (Partial; [Yes.withPartial, No.withPartial]) + {{ + foreach (windowSize; 1 .. 15) + foreach (stepSize; 1 .. 15) + { + auto r = 20.iota.slide!Partial(windowSize, stepSize); + auto rArr = r.array.retro; + auto rRetro = r.retro; - auto e3 = iota(3).slide(4); - assert(e3.retro.equal!equal([[0, 1, 2]])); - assert(e3.retro.array.equal(e3.array.retro)); + assert(rRetro.length == rArr.length); + assert(rRetro.equal(rArr)); + assert(rRetro.array.retro.equal(r)); + } + }} } -// test reverse with different steps +// test with dummy ranges @safe pure nothrow unittest { import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + import std.meta : Filter; - 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]] - )); -} + static foreach (Range; Filter!(isForwardRange, AllDummyRanges)) + {{ + Range r; -// step size -@safe pure nothrow unittest -{ - import std.algorithm.comparison : equal; + static foreach (Partial; [Yes.withPartial, No.withPartial]) + { + assert(r.slide!Partial(1).equal!equal( + [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]] + )); + assert(r.slide!Partial(2).equal!equal( + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]] + )); + assert(r.slide!Partial(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!Partial(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(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]])); + // special cases + assert(r.slide!(Yes.withPartial)(15).equal!equal(iota(1, 11).only)); + assert(r.slide!(Yes.withPartial)(15).walkLength == 1); + assert(r.slide!(No.withPartial)(15).empty); + assert(r.slide!(No.withPartial)(15).walkLength == 0); + }} } // 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; + import std.internal.test.dummyrange : AllDummyRanges; + import std.meta : Filter; + import std.typecons : tuple; - 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) - ); + alias t = tuple; + static immutable list = [ + // iota slide expected + t(6, t(4, 2), [[1, 2, 3, 4], [3, 4, 5, 6]]), + t(6, t(4, 6), [[1, 2, 3, 4]]), + t(6, t(4, 1), [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]), + t(7, t(4, 1), [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7]]), + t(7, t(4, 3), [[1, 2, 3, 4], [4, 5, 6, 7]]), + t(8, t(4, 2), [[1, 2, 3, 4], [3, 4, 5, 6], [5, 6, 7, 8]]), + t(8, t(4, 1), [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8]]), + t(8, t(3, 4), [[1, 2, 3], [5, 6, 7]]), + t(10, t(3, 7), [[1, 2, 3], [8, 9, 10]]), + ]; - foreach (Range; AliasSeq!AllForwardDummyRanges) + static foreach (Range; Filter!(isForwardRange, AllDummyRanges)) + static foreach (Partial; [Yes.withPartial, No.withPartial]) + foreach (e; list) + assert(Range().take(e[0]).slide!Partial(e[1].expand).equal!equal(e[2])); + + static immutable listSpecial = [ + // iota slide expected + t(6, t(4, 3), [[1, 2, 3, 4], [4, 5, 6]]), + t(7, t(4, 5), [[1, 2, 3, 4], [6, 7]]), + t(7, t(4, 4), [[1, 2, 3, 4], [5, 6, 7]]), + t(7, t(4, 2), [[1, 2, 3, 4], [3, 4, 5, 6], [5, 6, 7]]), + t(8, t(4, 3), [[1, 2, 3, 4], [4, 5, 6, 7], [7, 8]]), + t(8, t(3, 3), [[1, 2, 3], [4, 5, 6], [7, 8]]), + t(8, t(3, 6), [[1, 2, 3], [7, 8]]), + t(10, t(7, 6), [[1, 2, 3, 4, 5, 6, 7], [7, 8, 9, 10]]), + t(10, t(3, 8), [[1, 2, 3], [9, 10]]), + ]; + static foreach (Range; Filter!(isForwardRange, AllDummyRanges)) + static foreach (Partial; [Yes.withPartial, No.withPartial]) + foreach (e; listSpecial) { 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), - ); + assert(r.take(e[0]).slide!(Yes.withPartial)(e[1].expand).equal!equal(e[2])); + assert(r.take(e[0]).slide!(No.withPartial)(e[1].expand).equal!equal(e[2].dropBackOne)); + } +} - foreach (Range; AliasSeq!BackwardsDummyRanges) - { +// test reverse with dummy ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + import std.meta : Filter, templateAnd; + import std.typecons : tuple; + alias t = tuple; + + static immutable list = [ + // slide expected + t(1, 1, [[10], [9], [8], [7], [6], [5], [4], [3], [2], [1]]), + t(2, 1, [[9, 10], [8, 9], [7, 8], [6, 7], [5, 6], [4, 5], [3, 4], [2, 3], [1, 2]]), + t(5, 1, [[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]]), + t(2, 2, [[9, 10], [7, 8], [5, 6], [3, 4], [1, 2]]), + t(2, 4, [[9, 10], [5, 6], [1, 2]]), + ]; + + static foreach (Range; Filter!(templateAnd!(hasSlicing, hasLength, isBidirectionalRange), AllDummyRanges)) + {{ 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) + static foreach (Partial; [Yes.withPartial, No.withPartial]) + { + foreach (e; list) + assert(r.slide!Partial(e[0], e[1]).retro.equal!equal(e[2])); + + // front = back + foreach (windowSize; 1 .. 10) foreach (stepSize; 1 .. 10) { - auto slider = r.slide(windowSize, stepSize); - assert(slider.retro.retro.equal!equal(slider)); + auto slider = r.slide!Partial(windowSize, stepSize); + auto sliderRetro = slider.retro.array; + assert(slider.length == sliderRetro.length); + assert(sliderRetro.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]])); + // special cases + assert(r.slide!(No.withPartial)(15).retro.walkLength == 0); + assert(r.slide!(Yes.withPartial)(15).retro.equal!equal(iota(1, 11).only)); + }} } // test different sliceable ranges @safe pure nothrow unittest { import std.algorithm.comparison : equal; - import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; + import std.internal.test.dummyrange : AllDummyRanges; import std.meta : AliasSeq; struct SliceableRange(Range, Flag!"withOpDollar" withOpDollar = No.withOpDollar, @@ -8481,7 +9628,7 @@ public: struct Dollar {} Dollar opDollar() const { return Dollar.init; } - //Slice to dollar + // Slice to dollar typeof(this) opSlice(size_t lower, Dollar) { return typeof(this)(arr[lower .. $]); @@ -8495,82 +9642,80 @@ public: } } - 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 + import std.meta : Filter, templateNot; + alias SliceableDummyRanges = Filter!(hasSlicing, AllDummyRanges); - static assert(isForwardRange!Range); - enum hasSliceToEnd = hasSlicing!Range && is(typeof(Range.init[0 .. $]) == Range); + static foreach (Partial; [Yes.withPartial, No.withPartial]) + {{ + static foreach (Range; SliceableDummyRanges) + {{ + Range r; + r.reinit; + r.arr[] -= 1; // use a 0-based array (for clarity) - assert(r.slide(2)[0].equal([0, 1])); - assert(r.slide(2)[1].equal([1, 2])); + assert(r.slide!Partial(2)[0].equal([0, 1])); + assert(r.slide!Partial(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]])); + // saveable + auto s = r.slide!Partial(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]])); - } + assert(r.slide!Partial(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), - ); + static foreach (Range; Filter!(templateNot!isInfinite, SliceableDummyRanges)) + {{ + Range r; + r.reinit; + r.arr[] -= 1; // use a 0-based array (for clarity) - foreach (Range; AliasSeq!SliceableDummyRangesWithoutInfinity) - { - static assert(hasSlicing!Range); - static assert(hasLength!Range); + assert(r.slide!(No.withPartial)(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.withPartial)(16).empty); - Range r; - r.arr = 10.iota.array; // for clarity + assert(r.slide!Partial(4)[0 .. $].equal(r.slide!Partial(4))); + assert(r.slide!Partial(2)[$/2 .. $].equal!equal([[4, 5], [5, 6], [6, 7], [7, 8], [8, 9]])); + assert(r.slide!Partial(2)[$ .. $].empty); - 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!Partial(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]] + )); + }} - 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); + alias T = int[]; - 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!Partial(2)[0].equal([0, 1])); + assert(infIndex.slide!Partial(2)[1].equal([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!Partial(2)[1 .. $].front.equal([1, 2])); + assert(infDollar.slide!Partial(4)[0 .. $].front.equal([0, 1, 2, 3])); + assert(infDollar.slide!Partial(4)[2 .. $].front.equal([2, 3, 4, 5])); + }} +} - 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])); +// https://issues.dlang.org/show_bug.cgi?id=19082 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + assert([1].map!(x => x).slide(2).equal!equal([[1]])); } -private struct OnlyResult(T, size_t arity) +private struct OnlyResult(Values...) +if (Values.length > 1) { - private this(Values...)(auto ref Values values) + private enum arity = Values.length; + + private this(return scope ref Values values) { - this.data = [values]; + this.values = values; this.backIndex = arity; } @@ -8579,10 +9724,10 @@ private struct OnlyResult(T, size_t arity) return frontIndex >= backIndex; } - T front() @property + CommonType!Values front() @property { assert(!empty, "Attempting to fetch the front of an empty Only range"); - return data[frontIndex]; + return this[0]; } void popFront() @@ -8591,10 +9736,10 @@ private struct OnlyResult(T, size_t arity) ++frontIndex; } - T back() @property + CommonType!Values back() @property { assert(!empty, "Attempting to fetch the back of an empty Only range"); - return data[backIndex - 1]; + return this[$ - 1]; } void popBack() @@ -8615,12 +9760,15 @@ private struct OnlyResult(T, size_t arity) alias opDollar = length; - T opIndex(size_t idx) + CommonType!Values 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]; + final switch (frontIndex + idx) + static foreach (i, T; Values) + case i: + return values[i]; } OnlyResult opSlice() @@ -8647,21 +9795,21 @@ private struct OnlyResult(T, size_t arity) private size_t frontIndex = 0; private size_t backIndex = 0; - // @@@BUG@@@ 10643 + // https://issues.dlang.org/show_bug.cgi?id=10643 version (none) { import std.traits : hasElaborateAssign; static if (hasElaborateAssign!T) - private T[arity] data; + private Values values; else - private T[arity] data = void; + private Values values = void; } else - private T[arity] data; + private Values values; } // Specialize for single-element results -private struct OnlyResult(T, size_t arity : 1) +private struct OnlyResult(T) { @property T front() { @@ -8688,7 +9836,7 @@ private struct OnlyResult(T, size_t arity : 1) } alias opDollar = length; - private this()(auto ref T value) + private this()(return scope auto ref T value) { this._value = value; this._empty = false; @@ -8725,7 +9873,7 @@ private struct OnlyResult(T, size_t arity : 1) } // Specialize for the empty range -private struct OnlyResult(T, size_t arity : 0) +private struct OnlyResult() { private static struct EmptyElementType {} @@ -8753,7 +9901,7 @@ private struct OnlyResult(T, size_t arity : 0) } /** -Assemble $(D values) into a range that carries all its +Assemble `values` into a range that carries all its elements in-situ. Useful when a single value or multiple disconnected values @@ -8772,10 +9920,10 @@ Returns: See_Also: $(LREF chain) to chain ranges */ -auto only(Values...)(auto ref Values values) +auto only(Values...)(return scope Values values) if (!is(CommonType!Values == void) || Values.length == 0) { - return OnlyResult!(CommonType!Values, Values.length)(values); + return OnlyResult!Values(values); } /// @@ -8799,15 +9947,14 @@ if (!is(CommonType!Values == void) || Values.length == 0) .equal("T.D.P.L")); } +// https://issues.dlang.org/show_bug.cgi?id=20314 @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)))); + import std.algorithm.iteration : joiner; + + const string s = "foo", t = "bar"; - static assert(is(typeof(only((const(char)[]).init, string.init)) == - typeof(only((const(char)[]).init, (const(char)[]).init)))); + assert([only(s, t), only(t, s)].joiner(only(", ")).join == "foobar, barfoo"); } // Tests the zero-element result @@ -8872,7 +10019,7 @@ if (!is(CommonType!Values == void) || Values.length == 0) assert(imm.front == 1); assert(imm.back == 1); assert(!imm.empty); - assert(imm.init.empty); // Issue 13441 + assert(imm.init.empty); // https://issues.dlang.org/show_bug.cgi?id=13441 assert(imm.length == 1); assert(equal(imm, imm[])); assert(equal(imm, imm[0 .. 1])); @@ -8923,8 +10070,8 @@ if (!is(CommonType!Values == void) || Values.length == 0) ["one two", "one two three", "one two three four"]; string[] joinedRange = joined; - foreach (argCount; AliasSeq!(2, 3, 4)) - { + static foreach (argCount; 2 .. 5) + {{ auto values = only(data[0 .. argCount]); alias Values = typeof(values); static assert(is(ElementType!Values == string)); @@ -8939,7 +10086,7 @@ if (!is(CommonType!Values == void) || Values.length == 0) 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); @@ -8961,23 +10108,60 @@ if (!is(CommonType!Values == void) || Values.length == 0) alias Imm = typeof(imm); static assert(is(ElementType!Imm == immutable(int))); assert(!imm.empty); - assert(imm.init.empty); // Issue 13441 + assert(imm.init.empty); // https://issues.dlang.org/show_bug.cgi?id=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 + static struct Test { int* a; } + immutable(Test) test; + cast(void) only(test, test); // Works with mutable indirection +} + +// https://issues.dlang.org/show_bug.cgi?id=21129 +@safe unittest +{ + auto range = () @safe { + const(char)[5] staticStr = "Hello"; + + // `only` must store a char[5] - not a char[]! + return only(staticStr, " World"); + } (); + + assert(range.join == "Hello World"); +} + +// https://issues.dlang.org/show_bug.cgi?id=21129 +@safe unittest +{ + struct AliasedString + { + const(char)[5] staticStr = "Hello"; + + @property const(char)[] slice() const + { + return staticStr[]; + } + alias slice this; + } + + auto range = () @safe { + auto hello = AliasedString(); + + // a copy of AliasedString is stored in the range. + return only(hello, " World"); + } (); + + assert(range.join == "Hello World"); } /** 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, in that order, where the index member is named `index` and the element member is named `value`. The index starts at `start` and is incremented by one on every iteration. @@ -8992,7 +10176,7 @@ Overflow: continue from `Enumerator.min`. Params: - range = the input range to attach indexes to + range = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to attach indexes to start = the number to start the index counter from Returns: @@ -9001,7 +10185,7 @@ Returns: primitives, which are propagated only if `range` has length. Example: -Useful for using $(D foreach) with an index loop variable: +Useful for using `foreach` with an index loop variable: ---- import std.stdio : stdin, stdout; import std.range : enumerate; @@ -9027,13 +10211,18 @@ in 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); - + alias signed_t = Largest!(Enumerator, Signed!LengthType); + signed_t signedLength; + //This is to trick the compiler because if length is enum + //the compiler complains about unreachable code. + auto getLength() + { + return range.length; + } + //Can length fit in the signed type + assert(getLength() < signed_t.max, + "a signed length type is required but the range's length() is too great"); + signedLength = range.length; auto result = adds(start, signedLength, overflow); } else @@ -9046,7 +10235,7 @@ in assert(!overflow && result <= Enumerator.max); } } -body +do { // TODO: Relax isIntegral!Enumerator to allow user-defined integral types static struct Result @@ -9056,7 +10245,7 @@ body private: alias ElemType = Tuple!(Enumerator, "index", ElementType!Range, "value"); Range range; - Enumerator index; + Unqual!Enumerator index; public: ElemType front() @property @@ -9092,12 +10281,7 @@ body static if (hasLength!Range) { - size_t length() @property - { - return range.length; - } - - alias opDollar = length; + mixin ImplementLength!range; static if (isBidirectionalRange!Range) { @@ -9165,6 +10349,14 @@ pure @safe nothrow unittest assert(aa[1]); } +// Make sure passing qualified types works +pure @safe nothrow unittest +{ + char[4] v; + immutable start = 2; + v[2 .. $].enumerate(start); +} + pure @safe nothrow unittest { import std.internal.test.dummyrange : AllDummyRanges; @@ -9183,8 +10375,8 @@ pure @safe nothrow unittest } } - foreach (DummyType; AliasSeq!(AllDummyRanges, HasSlicing)) - { + static foreach (DummyType; AliasSeq!(AllDummyRanges, HasSlicing)) + {{ alias R = typeof(enumerate(DummyType.init)); static assert(isInputRange!R); static assert(isForwardRange!R == isForwardRange!DummyType); @@ -9199,7 +10391,7 @@ pure @safe nothrow unittest } static assert(hasSlicing!R == hasSlicing!DummyType); - } + }} static immutable values = ["zero", "one", "two", "three"]; auto enumerated = values[].enumerate(); @@ -9249,8 +10441,8 @@ pure @safe nothrow unittest assert(shifted.empty); } - foreach (T; AliasSeq!(ubyte, byte, uint, int)) - { + static foreach (T; AliasSeq!(ubyte, byte, uint, int)) + {{ auto inf = 42.repeat().enumerate(T.max); alias Inf = typeof(inf); static assert(isInfinite!Inf); @@ -9269,7 +10461,7 @@ pure @safe nothrow unittest assert(window.front == inf.front); window.popFront(); assert(window.empty); - } + }} } pure @safe unittest @@ -9277,8 +10469,8 @@ 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)) - { + static 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))); @@ -9290,10 +10482,28 @@ pure @safe unittest static assert(is(typeof(enumerated.front.index) == T)); assert(offsetEnumerated.equal(subset.zip(subset))); } + }} +} +@nogc @safe unittest +{ + const val = iota(1, 100).enumerate(1); +} +@nogc @safe unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown; + struct RangePayload { + enum length = size_t.max; + void popFront() {} + int front() { return 0; } + bool empty() { return true; } } + RangePayload thePayload; + //Assertion won't happen when contracts are disabled for -release. + debug assertThrown!AssertError(enumerate(thePayload, -10)); } - -version (none) // @@@BUG@@@ 10939 +// https://issues.dlang.org/show_bug.cgi?id=10939 +version (none) { // Re-enable (or remove) if 10939 is resolved. /+pure+/ @safe unittest // Impure because of std.conv.to @@ -9319,7 +10529,7 @@ version (none) // @@@BUG@@@ 10939 } SignedLengthRange svalues; - foreach (Enumerator; AliasSeq!(ubyte, byte, ushort, short, uint, int, ulong, long)) + static 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)); @@ -9330,7 +10540,7 @@ version (none) // @@@BUG@@@ 10939 assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length + 1)); } - foreach (Enumerator; AliasSeq!(byte, short, int)) + static foreach (Enumerator; AliasSeq!(byte, short, int)) { assertThrown!RangeError(repeat(0, uint.max).enumerate!Enumerator()); } @@ -9340,32 +10550,45 @@ version (none) // @@@BUG@@@ 10939 } /** - Returns true if $(D fn) accepts variables of type T1 and T2 in any order. + Returns true if `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()); + (ref T1 a, ref T2 b) + { + fn(a, b); + fn(b, a); + } --- */ template isTwoWayCompatible(alias fn, T1, T2) { - enum isTwoWayCompatible = is(typeof( (){ - T1 foo(); - T2 bar(); - - fn(foo(), bar()); - fn(bar(), foo()); + enum isTwoWayCompatible = is(typeof((ref T1 a, ref T2 b) + { + cast(void) fn(a, b); + cast(void) fn(b, a); } )); } +/// +@safe unittest +{ + void func1(int a, int b); + void func2(int a, float b); + + static assert(isTwoWayCompatible!(func1, int, int)); + static assert(isTwoWayCompatible!(func1, short, int)); + static assert(!isTwoWayCompatible!(func2, int, float)); + + void func3(ref int a, ref int b); + static assert( isTwoWayCompatible!(func3, int, int)); + static assert(!isTwoWayCompatible!(func3, short, int)); +} + /** - Policy used with the searching primitives $(D lowerBound), $(D - upperBound), and $(D equalRange) of $(LREF SortedRange) below. + Policy used with the searching primitives `lowerBound`, $(D + upperBound), and `equalRange` of $(LREF SortedRange) below. */ enum SearchPolicy { @@ -9394,44 +10617,97 @@ enum SearchPolicy remaining interval is searched using binary search. A value is found in $(BIGOH log(n)) time. */ - gallop, + 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 + time but is less cache friendly than `gallop` for large + ranges. The `binarySearch` policy is used as the last step + of `trot`, `gallop`, `trotBackwards`, and $(D gallopBackwards) strategies. */ - binarySearch, + binarySearch, /** - Similar to $(D trot) but starts backwards. Use it when + Similar to `trot` but starts backwards. Use it when confident that the value is around the end of the range. */ - trotBackwards, + trotBackwards, /** - Similar to $(D gallop) but starts backwards. Use it when + Similar to `gallop` but starts backwards. Use it when confident that the value is around the end of the range. */ - gallopBackwards - } + gallopBackwards +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto a = assumeSorted([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + auto p1 = a.upperBound!(SearchPolicy.binarySearch)(3); + assert(p1.equal([4, 5, 6, 7, 8, 9])); + + auto p2 = a.lowerBound!(SearchPolicy.gallop)(4); + assert(p2.equal([0, 1, 2, 3])); +} + +/** + Options for $(LREF SortedRange) ranges (below). +*/ +enum SortedRangeOptions +{ + /** + Assume, that the range is sorted without checking. + */ + assumeSorted, + + /** + All elements of the range are checked to be sorted. + The check is performed in O(n) time. + */ + checkStrictly, + + /** + Some elements of the range are checked to be sorted. + For ranges with random order, this will almost surely + detect, that it is not sorted. For almost sorted ranges + it's more likely to fail. The checked elements are choosen + in a deterministic manner, which makes this check reproducable. + The check is performed in O(log(n)) time. + */ + checkRoughly, +} + +/// +@safe pure unittest +{ + // create a SortedRange, that's checked strictly + SortedRange!(int[],"a < b", SortedRangeOptions.checkStrictly)([ 1, 3, 5, 7, 9 ]); +} /** -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. + 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 `r`, use + $(REF sort, std,algorithm,sorting) which sorts `r` in place and returns the + corresponding `SortedRange`. To construct a `SortedRange` from a range + `r` that is known to be already sorted, use $(LREF assumeSorted). + + Params: + pred: The predicate used to define the sortedness + opt: Controls how strongly the range is checked for sortedness. + Will only be used for `RandomAccessRanges`. + Will not be used in CTFE. */ -struct SortedRange(Range, alias pred = "a < b") -if (isInputRange!Range) +struct SortedRange(Range, alias pred = "a < b", + SortedRangeOptions opt = SortedRangeOptions.assumeSorted) +if (isInputRange!Range && !isInstanceOf!(SortedRange, Range)) { import std.functional : binaryFun; @@ -9449,36 +10725,55 @@ if (isInputRange!Range) // 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 { + static if (opt == SortedRangeOptions.checkRoughly) + { + roughlyVerifySorted(input); + } + static if (opt == SortedRangeOptions.checkStrictly) + { + strictlyVerifySorted(input); + } this._input = input; } // Assertion only. - private void dbgVerifySorted() + static if (opt == SortedRangeOptions.checkRoughly) + private void roughlyVerifySorted(Range r) { if (!__ctfe) - debug { static if (isRandomAccessRange!Range && hasLength!Range) { import core.bitop : bsr; import std.algorithm.sorting : isSorted; + import std.exception : enforce; // Check the sortedness of the input - if (this._input.length < 2) return; + if (r.length < 2) return; + + immutable size_t msb = bsr(r.length) + 1; + assert(msb > 0 && msb <= r.length); + immutable step = r.length / msb; + auto st = stride(r, step); + + enforce(isSorted!pred(st), "Range is not sorted"); + } + } + } - 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); + // Assertion only. + static if (opt == SortedRangeOptions.checkStrictly) + private void strictlyVerifySorted(Range r) + { + if (!__ctfe) + { + static if (isRandomAccessRange!Range && hasLength!Range) + { + import std.algorithm.sorting : isSorted; + import std.exception : enforce; - assert(isSorted!pred(st), "Range is not sorted"); + enforce(isSorted!pred(r), "Range is not sorted"); } } } @@ -9535,7 +10830,7 @@ if (isInputRange!Range) /// Ditto static if (hasSlicing!Range) - auto opSlice(size_t a, size_t b) + auto opSlice(size_t a, size_t b) return scope { assert( a <= b, @@ -9546,25 +10841,33 @@ if (isInputRange!Range) return result; } - /// Ditto - static if (hasLength!Range) - { - @property size_t length() //const - { - return _input.length; - } - alias opDollar = length; - } + mixin ImplementLength!_input; /** - Releases the controlled range and returns it. + Releases the controlled range and returns it. + + This does the opposite of $(LREF assumeSorted): instead of turning a range + into a `SortedRange`, it extracts the original range back out of the `SortedRange` + using $(REF, move, std,algorithm,mutation). */ - auto release() + auto release() return scope { import std.algorithm.mutation : move; return move(_input); } + /// + static if (is(Range : int[])) + @safe unittest + { + import std.algorithm.sorting : sort; + int[3] data = [ 1, 2, 3 ]; + auto a = assumeSorted(data[]); + assert(a == sort!"a < b"(data[])); + int[] p = a.release(); + assert(p == [ 1, 2, 3 ]); + } + // 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. @@ -9655,13 +10958,12 @@ if (isInputRange!Range) // 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 + This function uses a search with policy `sp` to find the + largest left subrange on which $(D pred(x, value)) is `true` for + all `x` (e.g., if `pred` is "less than", returns the portion of + the range with elements strictly smaller than `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). + $(LREF SearchPolicy). */ auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) if (isTwoWayCompatible!(predFun, ElementType!Range, V) @@ -9671,6 +10973,7 @@ if (isInputRange!Range) } /// + static if (is(Range : int[])) @safe unittest { import std.algorithm.comparison : equal; @@ -9681,18 +10984,16 @@ if (isInputRange!Range) // 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 +This function searches with policy `sp` to find the largest right +subrange on which $(D pred(value, x)) is `true` for all `x` +(e.g., if `pred` is "less than", returns the portion of the range +with elements strictly greater than `value`). The search schedule and its complexity are documented in $(LREF SearchPolicy). -For ranges that do not offer random access, $(D SearchPolicy.linear) +For ranges that do not offer random access, `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). +policies are allowed, and `SearchPolicy.binarySearch` is the default. */ auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) if (isTwoWayCompatible!(predFun, ElementType!Range, V)) @@ -9715,6 +11016,7 @@ See_Also: STL's $(HTTP sgi.com/tech/stl/lower_bound.html,upper_bound). } /// + static if (is(Range : int[])) @safe unittest { import std.algorithm.comparison : equal; @@ -9726,17 +11028,16 @@ See_Also: STL's $(HTTP sgi.com/tech/stl/lower_bound.html,upper_bound). // 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 + Returns the subrange containing all elements `e` for which both $(D + pred(e, value)) and $(D pred(value, e)) evaluate to `false` (e.g., + if `pred` is "less than", returns the portion of the range with + elements equal to `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 + then uses `SearchPolicy.gallopBackwards` to find the left boundary + and `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). + small). Completes the entire search in $(BIGOH log(n)) time. */ auto equalRange(V)(V value) if (isTwoWayCompatible!(predFun, ElementType!Range, V) @@ -9778,6 +11079,7 @@ See_Also: STL's $(HTTP sgi.com/tech/stl/lower_bound.html,upper_bound). } /// + static if (is(Range : int[])) @safe unittest { import std.algorithm.comparison : equal; @@ -9788,9 +11090,9 @@ See_Also: STL's $(HTTP sgi.com/tech/stl/lower_bound.html,upper_bound). // 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 +Returns a tuple `r` such that `r[0]` is the same as the result +of `lowerBound(value)`, `r[1]` is the same as the result of $(D +equalRange(value)), and `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. @@ -9838,6 +11140,7 @@ equalRange). Completes the entire search in $(BIGOH log(n)) time. } /// + static if (is(Range : int[])) @safe unittest { import std.algorithm.comparison : equal; @@ -9850,10 +11153,9 @@ equalRange). Completes the entire search in $(BIGOH log(n)) time. // contains /** -Returns $(D true) if and only if $(D value) can be found in $(D +Returns `true` if and only if `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). +evaluations of `pred`. */ bool contains(V)(V value) @@ -9865,6 +11167,15 @@ sgi.com/tech/stl/binary_search.html, binary_search). return !predFun(value, _input[i]); } +/** +Like `contains`, but the value is specified before the range. +*/ + bool opBinaryRight(string op, V)(V value) + if (op == "in" && isRandomAccessRange!Range) + { + return contains(value); + } + // groupBy /** Returns a range of subranges of elements that are equivalent according to the @@ -9877,6 +11188,15 @@ sorting relation. } } +/// ditto +template SortedRange(Range, alias pred = "a < b", + SortedRangeOptions opt = SortedRangeOptions.assumeSorted) +if (isInstanceOf!(SortedRange, Range)) +{ + // Avoid nesting SortedRange types (see https://issues.dlang.org/show_bug.cgi?id=18933); + alias SortedRange = SortedRange!(Unqual!(typeof(Range._input)), pred, opt); +} + /// @safe unittest { @@ -9884,21 +11204,21 @@ sorting relation. auto a = [ 1, 2, 3, 42, 52, 64 ]; auto r = assumeSorted(a); assert(r.contains(3)); - assert(!r.contains(32)); + assert(!(32 in r)); auto r1 = sort!"a > b"(a); - assert(r1.contains(3)); + assert(3 in r1); assert(!r1.contains(32)); assert(r1.release() == [ 64, 52, 42, 3, 2, 1 ]); } /** -$(D SortedRange) could accept ranges weaker than random-access, but it +`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. +`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. +changed concurrently with its corresponding `SortedRange` in ways +that break its sorted-ness, `SortedRange` will work erratically. */ @safe unittest { @@ -9910,6 +11230,42 @@ that break its sorted-ness, $(D SortedRange) will work erratically. assert(!r.contains(42)); // passes although it shouldn't } +/** +`SortedRange` can be searched with predicates that do not take +two elements of the underlying range as arguments. + +This is useful, if a range of structs is sorted by a member and you +want to search in that range by only providing a value for that member. + +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + static struct S { int i; } + static bool byI(A, B)(A a, B b) + { + static if (is(A == S)) + return a.i < b; + else + return a < b.i; + } + auto r = assumeSorted!byI([S(1), S(2), S(3)]); + auto lessThanTwo = r.lowerBound(2); + assert(equal(lessThanTwo, [S(1)])); +} + +@safe unittest +{ + import std.exception : assertThrown, assertNotThrown; + + assertNotThrown(SortedRange!(int[])([ 1, 3, 10, 5, 7 ])); + assertThrown(SortedRange!(int[],"a < b", SortedRangeOptions.checkStrictly)([ 1, 3, 10, 5, 7 ])); + + // these two checks are implementation depended + assertNotThrown(SortedRange!(int[],"a < b", SortedRangeOptions.checkRoughly)([ 1, 3, 10, 5, 12, 2 ])); + assertThrown(SortedRange!(int[],"a < b", SortedRangeOptions.checkRoughly)([ 1, 3, 10, 5, 2, 12 ])); +} + @safe unittest { import std.algorithm.comparison : equal; @@ -9998,9 +11354,9 @@ that break its sorted-ness, $(D SortedRange) will work erratically. assert(!r.contains(42)); // passes although it shouldn't } -@safe unittest +@betterC @nogc nothrow @safe unittest { - immutable(int)[] arr = [ 1, 2, 3 ]; + static immutable(int)[] arr = [ 1, 2, 3 ]; auto s = assumeSorted(arr); } @@ -10034,48 +11390,67 @@ that break its sorted-ness, $(D SortedRange) will work erratically. f.close(); } +// https://issues.dlang.org/show_bug.cgi?id=19337 +@safe unittest +{ + import std.algorithm.sorting : sort; + auto a = [ 1, 2, 3, 42, 52, 64 ]; + a.sort.sort!"a > b"; +} + /** -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 +Assumes `r` is sorted by predicate `pred` and returns the +corresponding $(D SortedRange!(pred, R)) having `r` as support. +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); + // Avoid senseless `SortedRange!(SortedRange!(...), pred)` nesting. + static if (is(R == SortedRange!(RRange, RPred), RRange, alias RPred)) + { + static if (isInputRange!R && __traits(isSame, pred, RPred)) + // If the predicate is the same and we don't need to cast away + // constness for the result to be an input range. + return r; + else + return SortedRange!(Unqual!(typeof(r._input)), pred)(r._input); + } + else + { + 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])); + + int[] a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + auto p = assumeSorted(a); + + assert(equal(p.lowerBound(4), [0, 1, 2, 3])); + assert(equal(p.lowerBound(5), [0, 1, 2, 3, 4])); + assert(equal(p.lowerBound(6), [0, 1, 2, 3, 4, 5])); + assert(equal(p.lowerBound(6.9), [0, 1, 2, 3, 4, 5, 6])); } @safe unittest { import std.algorithm.comparison : equal; + static assert(isRandomAccessRange!(SortedRange!(int[]))); 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 ])); + + // https://issues.dlang.org/show_bug.cgi?id=18933 + // don't create senselessly nested SortedRange types. + assert(is(typeof(assumeSorted(a)) == typeof(assumeSorted(assumeSorted(a))))); + assert(is(typeof(assumeSorted(a)) == typeof(assumeSorted(assumeSorted!"a > b"(a))))); } @safe unittest @@ -10119,21 +11494,7 @@ if (isInputRange!(Unqual!R)) 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 +// https://issues.dlang.org/show_bug.cgi?id=15003 @nogc @safe unittest { static immutable a = [1, 2, 3, 4]; @@ -10149,15 +11510,15 @@ if (isInputRange!(Unqual!R)) consumed as if it were a reference type. Note: - `save` works as normal and operates on a new _range, so if + `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. + 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 + 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`. +/ @@ -10174,13 +11535,13 @@ public: /++ - 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). + This does not assign the pointer of `rhs` to this `RefRange`. + Rather it assigns the range pointed to by `rhs` to the range pointed + to by this `RefRange`. This is because $(I any) operation on a + `RefRange` is the same is if it occurred to the original range. The + one exception is when a `RefRange` is assigned `null` either + directly or because `rhs` is `null`. In that case, `RefRange` + no longer refers to the original range but is `null`. +/ auto opAssign(RefRange rhs) { @@ -10267,7 +11628,7 @@ public: version (StdDdoc) { /++ - Only defined if $(D isForwardRange!R) is $(D true). + Only defined if `isForwardRange!R` is `true`. +/ @property auto save() {assert(0);} /++ Ditto +/ @@ -10322,7 +11683,7 @@ public: private static string _genSave() @safe pure nothrow { - return `import std.conv : emplace;` ~ + return `import core.lifetime : emplace;` ~ `alias S = typeof((*_range).save);` ~ `static assert(isForwardRange!S, S.stringof ~ " is not a forward range.");` ~ `auto mem = new void[S.sizeof];` ~ @@ -10337,7 +11698,7 @@ public: version (StdDdoc) { /++ - Only defined if $(D isBidirectionalRange!R) is $(D true). + Only defined if `isBidirectionalRange!R` is `true`. +/ @property auto back() {assert(0);} /++ Ditto +/ @@ -10374,7 +11735,7 @@ public: version (StdDdoc) { /++ - Only defined if $(D isRandomAccesRange!R) is $(D true). + Only defined if `isRandomAccessRange!R` is `true`. +/ auto ref opIndex(IndexType)(IndexType index) {assert(0);} @@ -10398,8 +11759,8 @@ public: /++ - Only defined if $(D hasMobileElements!R) and $(D isForwardRange!R) are - $(D true). + Only defined if `hasMobileElements!R` and `isForwardRange!R` are + `true`. +/ static if (hasMobileElements!R && isForwardRange!R) auto moveFront() { @@ -10408,8 +11769,8 @@ public: /++ - Only defined if $(D hasMobileElements!R) and $(D isBidirectionalRange!R) - are $(D true). + Only defined if `hasMobileElements!R` and `isBidirectionalRange!R` + are `true`. +/ static if (hasMobileElements!R && isBidirectionalRange!R) auto moveBack() { @@ -10418,8 +11779,8 @@ public: /++ - Only defined if $(D hasMobileElements!R) and $(D isRandomAccessRange!R) - are $(D true). + Only defined if `hasMobileElements!R` and `isRandomAccessRange!R` + are `true`. +/ static if (hasMobileElements!R && isRandomAccessRange!R) auto moveAt(size_t index) { @@ -10429,15 +11790,11 @@ public: version (StdDdoc) { - /++ - Only defined if $(D hasLength!R) is $(D true). - +/ - @property auto length() {assert(0);} - - /++ Ditto +/ - @property auto length() const {assert(0);} - - /++ Ditto +/ + /// Only defined if `hasLength!R` is `true`. + @property size_t length(); + /// ditto + @property size_t length() const; + /// Ditto alias opDollar = length; } else static if (hasLength!R) @@ -10446,12 +11803,10 @@ public: { return (*_range).length; } - static if (is(typeof((*cast(const R*)_range).length))) @property auto length() const { return (*_range).length; } - alias opDollar = length; } @@ -10459,7 +11814,7 @@ public: version (StdDdoc) { /++ - Only defined if $(D hasSlicing!R) is $(D true). + Only defined if `hasSlicing!R` is `true`. +/ auto opSlice(IndexType1, IndexType2) (IndexType1 begin, IndexType2 end) {assert(0);} @@ -10492,7 +11847,7 @@ public: private static string _genOpSlice() @safe pure nothrow { - return `import std.conv : emplace;` ~ + return `import core.lifetime : emplace;` ~ `alias S = typeof((*_range)[begin .. end]);` ~ `static assert(hasSlicing!S, S.stringof ~ " is not sliceable.");` ~ `auto mem = new void[S.sizeof];` ~ @@ -10657,8 +12012,8 @@ private: } { - // Issue 16534 - opDollar should be defined if the - // wrapped range defines length. + // https://issues.dlang.org/show_bug.cgi?id=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); @@ -10821,7 +12176,7 @@ private: @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; } + @property auto save() @safe pure nothrow return scope { return this; } } S s; @@ -10836,7 +12191,7 @@ private: @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; } + @property auto save() @safe pure nothrow return scope { return this; } } static assert(isForwardRange!C); @@ -10846,7 +12201,8 @@ private: assert(cWrapper is c); } -@system unittest // issue 14373 +// https://issues.dlang.org/show_bug.cgi?id=14373 +@system unittest { static struct R { @@ -10859,7 +12215,8 @@ private: assert(r.empty); } -@system unittest // issue 14575 +// https://issues.dlang.org/show_bug.cgi?id=14575 +@system unittest { struct R { @@ -10899,9 +12256,8 @@ if (isInputRange!R) return *range; } -/*****************************************************************************/ - -@safe unittest // bug 9060 +// https://issues.dlang.org/show_bug.cgi?id=9060 +@safe unittest { import std.algorithm.iteration : map, joiner, group; import std.algorithm.searching : until; @@ -10946,6 +12302,7 @@ if (isInputRange!R) private struct Bitwise(R) if (isInputRange!R && isIntegral!(ElementType!R)) { + import std.traits : Unsigned; private: alias ElemType = ElementType!R; alias UnsignedElemType = Unsigned!ElemType; @@ -11087,11 +12444,11 @@ public: assert(n < length, "Index out of bounds"); } } - body + do { 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); + immutable ptrdiff_t sign = (remainingBits - n - 1) >> (ptrdiff_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. @@ -11126,12 +12483,12 @@ public: assert(n < length, "Index out of bounds"); } } - body + do { import core.bitop : bsf; immutable size_t remainingBits = bitsNum - maskPos + 1; - immutable sizediff_t sign = (remainingBits - n - 1) >> (sizediff_t.sizeof * 8 - 1); + immutable ptrdiff_t sign = (remainingBits - n - 1) >> (ptrdiff_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))); @@ -11153,19 +12510,19 @@ public: { assert(start < end, "Invalid bounds: end <= start"); } - body + do { import core.bitop : bsf; size_t remainingBits = bitsNum - maskPos + 1; - sizediff_t sign = (remainingBits - start - 1) >> (sizediff_t.sizeof * 8 - 1); + ptrdiff_t sign = (remainingBits - start - 1) >> (ptrdiff_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); + sign = (remainingBits - sliceLen - 1) >> (ptrdiff_t.sizeof * 8 - 1); immutable size_t endElemIndex = startElemIndex + sign * (((sliceLen - remainingBits) >> bitsNum.bsf) + 1); immutable size_t endElemMaskPos = (sign ^ 1) * (startElemMaskPos + sliceLen) @@ -11195,7 +12552,7 @@ 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 + R = an integral $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to iterate over range = range to consume bit by by Returns: @@ -11261,6 +12618,7 @@ if (isInputRange!R && isIntegral!(ElementType!R)) // Test all range types over all integral types @safe pure nothrow unittest { + import std.meta : AliasSeq; import std.internal.test.dummyrange; alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, @@ -11357,6 +12715,7 @@ if (isInputRange!R && isIntegral!(ElementType!R)) // Test opIndex and opSlice @system unittest { + import std.meta : AliasSeq; alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong); foreach (IntegralType; IntegralTypes) @@ -11403,17 +12762,49 @@ if (isInputRange!R && isIntegral!(ElementType!R)) */ struct NullSink { - void put(E)(E){} + void put(E)(scope const E) pure @safe @nogc nothrow {} +} + +/// ditto +auto ref nullSink() +{ + static NullSink sink; + return sink; } /// -@safe unittest +@safe nothrow unittest { import std.algorithm.iteration : map; import std.algorithm.mutation : copy; - [4, 5, 6].map!(x => x * 2).copy(NullSink()); // data is discarded + [4, 5, 6].map!(x => x * 2).copy(nullSink); // data is discarded +} + +/// +@safe unittest +{ + import std.csv : csvNextToken; + + string line = "a,b,c"; + + // ignore the first column + line.csvNextToken(nullSink, ',', '"'); + line.popFront; + + // look at the second column + Appender!string app; + line.csvNextToken(app, ',', '"'); + assert(app.data == "b"); } +@safe unittest +{ + auto r = 10.iota + .tee(nullSink) + .dropOne; + + assert(r.front == 1); +} /++ @@ -11421,11 +12812,11 @@ struct NullSink 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 + to `front` or `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 + in the case of the version of `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). @@ -11433,8 +12824,13 @@ struct NullSink 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`. + respectively, `fun`). Note that each `popFront()` call will mirror the + old `front` value, not the new one. This means that the last value will + not be forwarded if the range isn't iterated until empty. If + `No.pipeOnPop`, only elements for which `front` does get called will be + also sent to `outputRange`/`fun`. If `front` is called twice for the same + element, it will still be sent only once. If this caching is undesired, + consider using $(REF map, std,algorithm,iteration) instead. inputRange = The input range being passed through. outputRange = This range will receive elements of `inputRange` progressively as iteration proceeds. @@ -11462,13 +12858,7 @@ if (isInputRange!R1 && isOutputRange!(R2, ElementType!R1)) private bool _frontAccessed; } - static if (hasLength!R1) - { - @property auto length() - { - return _input.length; - } - } + mixin ImplementLength!_input; static if (isInfinite!R1) { @@ -11521,7 +12911,7 @@ if (is(typeof(fun) == void) || isSomeFunction!fun) 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). + it with `ElementType!R1`. */ static if (is(typeof(fun) == void)) alias _fun = fun!(ElementType!R1); @@ -11690,24 +13080,24 @@ if (is(typeof(fun) == void) || isSomeFunction!fun) auto result3 = txt.tee(asink3).array; assert(equal(txt, result3) && equal(result3, asink3)); - foreach (CharType; AliasSeq!(char, wchar, dchar)) - { + static 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)) - { + static foreach (StringType; AliasSeq!(string, wstring, dstring)) + {{ auto appSink = appender!StringType(); auto appResult = txt.tee(appSink).array; assert(equal(txt, appResult) && equal(appResult, appSink.data)); - } + }} } +// https://issues.dlang.org/show_bug.cgi?id=13483 @safe unittest { - // Issue 13483 static void func1(T)(T x) {} void func2(int x) {} @@ -11729,7 +13119,7 @@ $(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 + r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with a length, or a forward range e = element to pad the range with n = the length to pad to @@ -11815,7 +13205,7 @@ 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 + r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with a length e = element to pad the range with n = the length to pad to @@ -11836,14 +13226,27 @@ if ( private: R data; E element; - size_t counter; - static if (isBidirectionalRange!R && hasLength!R) size_t backPosition; - size_t maxSize; + static if (hasLength!R) + { + size_t padLength; + } + else + { + size_t minLength; + size_t consumed; + } public: bool empty() @property { - return data.empty && counter >= maxSize; + static if (hasLength!R) + { + return data.empty && padLength == 0; + } + else + { + return data.empty && consumed >= minLength; + } } auto front() @property @@ -11855,11 +13258,25 @@ if ( void popFront() { assert(!empty, "Attempting to popFront an empty padRight"); - ++counter; - if (!data.empty) + static if (hasLength!R) + { + if (!data.empty) + { + data.popFront; + } + else + { + --padLength; + } + } + else { - data.popFront; + ++consumed; + if (!data.empty) + { + data.popFront; + } } } @@ -11867,8 +13284,7 @@ if ( { size_t length() @property { - import std.algorithm.comparison : max; - return max(data.length, maxSize); + return data.length + padLength; } } @@ -11887,16 +13303,15 @@ if ( auto back() @property { assert(!empty, "Attempting to fetch the back of an empty padRight"); - return backPosition > data.length ? element : data.back; + return padLength > 0 ? element : data.back; } void popBack() { assert(!empty, "Attempting to popBack an empty padRight"); - if (backPosition > data.length) + if (padLength > 0) { - --backPosition; - --maxSize; + --padLength; } else { @@ -11910,8 +13325,7 @@ if ( E opIndex(size_t index) { assert(index <= this.length, "Index out of bounds"); - return (index > data.length && index <= maxSize) ? element : - data[index]; + return index >= data.length ? element : data[index]; } } @@ -11927,20 +13341,26 @@ if ( 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], + return Result( + a >= data.length ? data[0 .. 0] : b <= data.length ? data[a .. b] : data[a .. data.length], element, b - a); } alias opDollar = length; } - this(R r, E e, size_t max) + this(R r, E e, size_t n) { data = r; element = e; - maxSize = max; - static if (isBidirectionalRange!R && hasLength!R) - backPosition = max; + static if (hasLength!R) + { + padLength = n > data.length ? n - data.length : 0; + } + else + { + minLength = n; + } } @disable this(); @@ -11984,6 +13404,8 @@ pure @safe unittest RangeType r3; assert(r3.padRight(0, 12)[0] == 1); assert(r3.padRight(0, 12)[2] == 3); + assert(r3.padRight(0, 12)[9] == 10); + assert(r3.padRight(0, 12)[10] == 0); assert(r3.padRight(0, 12)[11] == 0); } @@ -11996,6 +13418,14 @@ pure @safe unittest .equal([1, 2, 3]) ); assert(r4 + .padRight(0, 12)[0 .. 10] + .equal([1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]) + ); + assert(r4 + .padRight(0, 12)[0 .. 11] + .equal([1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0]) + ); + assert(r4 .padRight(0, 12)[2 .. $] .equal([3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) ); @@ -12004,6 +13434,10 @@ pure @safe unittest .equal([1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) ); } + + // drop & dropBack test opslice ranges when available, popFront/popBack otherwise + RangeType r5; + foreach (i; 1 .. 13) assert(r5.padRight(0, 12).drop(i).walkLength == 12 - i); } } @@ -12016,3 +13450,54 @@ pure @safe unittest static immutable r2 = [1, 2, 3, 4, 0, 0]; assert(r1.padRight(0, 6).equal(r2)); } + +// Test back, popBack, and save +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + auto r1 = [1, 2, 3, 4].padRight(0, 6); + assert(r1.back == 0); + + r1.popBack; + auto r2 = r1.save; + assert(r1.equal([1, 2, 3, 4, 0])); + assert(r2.equal([1, 2, 3, 4, 0])); + + r1.popBackN(2); + assert(r1.back == 3); + assert(r1.length == 3); + assert(r2.length == 5); + assert(r2.equal([1, 2, 3, 4, 0])); + + r2.popFront; + assert(r2.length == 4); + assert(r2[0] == 2); + assert(r2[1] == 3); + assert(r2[2] == 4); + assert(r2[3] == 0); + assert(r2.equal([2, 3, 4, 0])); + + r2.popBack; + assert(r2.equal([2, 3, 4])); + + auto r3 = [1, 2, 3, 4].padRight(0, 6); + size_t len = 0; + while (!r3.empty) + { + ++len; + r3.popBack; + } + assert(len == 6); +} + +// https://issues.dlang.org/show_bug.cgi?id=19042 +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert([2, 5, 13].padRight(42, 10).chunks(5) + .equal!equal([[2, 5, 13, 42, 42], [42, 42, 42, 42, 42]])); + + assert([1, 2, 3, 4].padRight(0, 10)[7 .. 9].equal([0, 0])); +} diff --git a/libphobos/src/std/range/primitives.d b/libphobos/src/std/range/primitives.d index 193ee95..9c61ff5 100644 --- a/libphobos/src/std/range/primitives.d +++ b/libphobos/src/std/range/primitives.d @@ -1,70 +1,74 @@ /** This module is a submodule of $(MREF std, range). +It defines the bidirectional and forward range primitives for arrays: +$(LREF empty), $(LREF front), $(LREF back), $(LREF popFront), $(LREF popBack) and $(LREF save). + 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: +whether a given object is a range, and what kind of range it is: $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE , $(TR $(TD $(LREF isInputRange)) - $(TD Tests if something is an $(I input _range), defined to be + $(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). + primitives `front`, `popFront`, and `empty`. )) $(TR $(TD $(LREF isOutputRange)) - $(TD Tests if something is an $(I output _range), defined to be + $(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. + $(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 `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). + $(TD Tests if something is a $(I bidirectional range), that is, a + forward range that allows reverse traversal using the primitives $(D + back) and `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). + $(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 `opIndex`. )) -) +)) -It also provides number of templates that test for various _range capabilities: +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). + $(TD Tests if a given range's elements can be moved around using the + primitives `moveFront`, `moveBack`, or `moveAt`. )) $(TR $(TD $(LREF ElementType)) - $(TD Returns the element type of a given _range. + $(TD Returns the element type of a given range. )) $(TR $(TD $(LREF ElementEncodingType)) - $(TD Returns the encoding element type of a given _range. + $(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. + $(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. + $(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 + $(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. + $(TD Tests if a given range has the `length` attribute. )) $(TR $(TD $(LREF isInfinite)) - $(TD Tests if a given _range is an $(I infinite _range). + $(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 + $(TD Tests if a given range supports the array slicing operation $(D R[x .. y]). )) ) @@ -73,51 +77,52 @@ 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. + $(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 + $(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. + $(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 + $(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. + $(TD Removes the front element of a range. )) $(TR $(TD $(LREF moveBack)) - $(TD Removes the back element of a bidirectional _range. + $(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. + $(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. + $(TD Computes the length of any range in O(n) time. )) $(TR $(TD $(LREF put)) - $(TD Outputs element $(D e) to a _range. + $(TD Outputs element `e` to a range. )) ) -Source: $(PHOBOSSRC std/range/_primitives.d) +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). +Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, and + $(HTTP jmdavisprog.com, 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 +Returns `true` if `R` is an input range. An input range must +define the primitives `empty`, `popFront`, and `front`. The following code should compile for any input range. ---- @@ -155,11 +160,14 @@ Also, note that Phobos code assumes that the primitives `r.front` and running time. $(BIGOH) statements in the documentation of range functions are made with this assumption. +See_Also: + The header of $(MREF std,range) for tutorials on ranges. + Params: R = type to be tested Returns: - true if R is an InputRange, false if not + `true` if R is an input range, `false` if not */ enum bool isInputRange(R) = is(typeof(R.init) == R) @@ -246,13 +254,13 @@ enum bool isInputRange(R) = } /+ -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. +puts the whole raw element `e` into `r`. doPut will not attempt to +iterate, slice or transcode `e` in any way shape or form. It will $(B only) +call the correct primitive (`r.put(e)`, $(D r.front = e) or +`r(e)` 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 +This can be important when `e` needs to be placed in `r` unchanged. +Furthermore, it can be useful when working with `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) @@ -268,6 +276,20 @@ private void doPut(R, E)(ref R r, auto ref E e) "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); r.put(e); } + else static if (isNarrowString!R && is(const(E) == const(typeof(r[0])))) + { + // one character, we can put it + r[0] = e; + r = r[1 .. $]; + } + else static if (isNarrowString!R && isNarrowString!E && is(typeof(r[] = e))) + { + // slice assign. Note that this is a duplicate from put, but because + // putChar uses doPut exclusively, we have to copy it here. + immutable len = e.length; + r[0 .. len] = e; + r = r[len .. $]; + } else static if (isInputRange!R) { static assert(is(typeof(r.front = e)), @@ -304,7 +326,7 @@ private void doPut(R, E)(ref R r, auto ref E e) static assert( isNativeOutputRange!(int[4][], int)); //Scary! static assert( isNativeOutputRange!(int[4][], int[4])); - static assert(!isNativeOutputRange!( char[], char)); + static assert( isNativeOutputRange!( char[], char)); static assert(!isNativeOutputRange!( char[], dchar)); static assert( isNativeOutputRange!(dchar[], char)); static assert( isNativeOutputRange!(dchar[], dchar)); @@ -312,14 +334,14 @@ private void doPut(R, E)(ref R r, auto ref E e) } /++ -Outputs $(D e) to $(D r). The exact effect is dependent upon the two +Outputs `e` to `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)) +In this table "doPut" is a method that places `e` into `r`, using the +correct primitive: `r.put(e)` if `R` defines `put`, $(D r.front = e) +if `r` is an input range (followed by `r.popFront()`), or `r(e)` otherwise. $(BOOKTABLE , @@ -328,27 +350,27 @@ $(BOOKTABLE , $(TH Scenario) ) $(TR - $(TD $(D r.doPut(e);)) - $(TD $(D R) specifically accepts an $(D E).) + $(TD `r.doPut(e);`) + $(TD `R` specifically accepts an `E`.) ) $(TR $(TD $(D r.doPut([ e ]);)) - $(TD $(D R) specifically accepts an $(D E[]).) + $(TD `R` specifically accepts an `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.) + $(TD `r.putChar(e);`) + $(TD `R` accepts some form of string or character. put will + transcode the character `e` accordingly.) ) $(TR $(TD $(D for (; !e.empty; e.popFront()) put(r, e.front);)) - $(TD Copying range $(D E) into $(D R).) + $(TD Copying range `E` into `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. +Tip: `put` should $(I not) be used "UFCS-style", e.g. `r.put(e)`. +Doing this may call `R.put` directly, by-passing any transformation +feature provided by `Range.put`. $(D put(r, e)) is prefered. +/ void put(R, E)(ref R r, E e) { @@ -358,7 +380,10 @@ void put(R, E)(ref R r, E 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[]))) + else static if (isDynamicArray!R && + !isAutodecodableString!R && + isDynamicArray!E && + is(typeof(r[] = e[]))) { immutable len = e.length; r[0 .. len] = e[]; @@ -386,7 +411,7 @@ void put(R, E)(ref R r, E e) { //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 && ( + static if (isAutodecodableString!E && !isAggregateType!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)))) ) ) @@ -406,9 +431,78 @@ void put(R, E)(ref R r, E e) } } +/** + * When an output range's `put` method only accepts elements of type + * `T`, use the global `put` to handle outputting a `T[]` to the range + * or vice-versa. + */ +@safe pure unittest +{ + import std.traits : isSomeChar; + + static struct A + { + string data; + + void put(C)(C c) if (isSomeChar!C) + { + data ~= c; + } + } + static assert(isOutputRange!(A, char)); + + auto a = A(); + put(a, "Hello"); + assert(a.data == "Hello"); +} + +/** + * `put` treats dynamic arrays as array slices, and will call `popFront` + * on the slice after an element has been copied. + * + * Be sure to save the position of the array before calling `put`. + */ +@safe pure nothrow unittest +{ + int[] a = [1, 2, 3], b = [10, 20]; + auto c = a; + put(a, b); + assert(c == [10, 20, 3]); + // at this point, a was advanced twice, so it only contains + // its last element while c represents the whole array + assert(a == [3]); +} + +/** + * It's also possible to `put` any width strings or characters into narrow + * strings -- put does the conversion for you. + * + * Note that putting the same width character as the target buffer type is + * `nothrow`, but transcoding can throw a $(REF UTFException, std, utf). + */ +@safe pure unittest +{ + // the elements must be mutable, so using string or const(char)[] + // won't compile + char[] s1 = new char[13]; + auto r1 = s1; + put(r1, "Hello, World!"w); + assert(s1 == "Hello, World!"); +} + +@safe pure nothrow unittest +{ + // same thing, just using same character width. + char[] s1 = new char[13]; + auto r1 = s1; + put(r1, "Hello, World!"); + assert(s1 == "Hello, World!"); +} + + @safe pure nothrow @nogc unittest { - static struct R() { void put(in char[]) {} } + static struct R() { void put(scope const(char)[]) {} } R!() r; put(r, 'a'); } @@ -418,14 +512,14 @@ void put(R, E)(ref R r, E e) private void putChar(R, E)(ref R r, E e) if (isSomeChar!E) { - ////@@@9186@@@: Can't use (E[]).init + // https://issues.dlang.org/show_bug.cgi?id=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()))); + enum csCond = is(typeof(doPut(r, cstringInit()))); + enum wsCond = is(typeof(doPut(r, wstringInit()))); + enum dsCond = is(typeof(doPut(r, dstringInit()))); //Use "max" to avoid static type demotion enum ccCond = is(typeof(doPut(r, char.max))); @@ -469,7 +563,7 @@ pure @safe unittest @safe pure unittest { - static struct R() { void put(in char[]) {} } + static struct R() { void put(scope const(char)[]) {} } R!() r; putChar(r, 'a'); } @@ -488,15 +582,6 @@ pure @safe unittest @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))); @@ -505,7 +590,7 @@ pure @safe unittest @safe unittest { - void myprint(in char[] s) { } + void myprint(scope const(char)[] s) { } auto r = &myprint; put(r, 'a'); } @@ -531,9 +616,64 @@ pure @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"))); + //char[] is now an output range for char, wchar, dchar, and ranges of such. + static assert(__traits(compiles, putChar(a, 'a'))); + static assert(__traits(compiles, put(a, wchar('a')))); + static assert(__traits(compiles, put(a, dchar('a')))); + static assert(__traits(compiles, put(a, "ABC"))); + static assert(__traits(compiles, put(a, "ABC"w))); + static assert(__traits(compiles, put(a, "ABC"d))); +} + +@safe unittest +{ + // attempt putting into narrow strings by transcoding + char[] a = new char[10]; + auto b = a; + put(a, "ABC"w); + assert(b[0 .. 3] == "ABC"); + assert(a.length == 7); + + a = b; // reset + put(a, 'λ'); + assert(b[0 .. 2] == "λ"); + assert(a.length == 8); + + a = b; // reset + put(a, "ABC"d); + assert(b[0 .. 3] == "ABC"); + assert(a.length == 7); + + a = b; // reset + put(a, '𐐷'); + assert(b[0 .. 4] == "𐐷"); + assert(a.length == 6); + + wchar[] aw = new wchar[10]; + auto bw = aw; + put(aw, "ABC"); + assert(bw[0 .. 3] == "ABC"w); + assert(aw.length == 7); + + aw = bw; // reset + put(aw, 'λ'); + assert(bw[0 .. 1] == "λ"w); + assert(aw.length == 9); + + aw = bw; // reset + put(aw, "ABC"d); + assert(bw[0 .. 3] == "ABC"w); + assert(aw.length == 7); + + aw = bw; // reset + put(aw, '𐐷'); + assert(bw[0 .. 2] == "𐐷"w); + assert(aw.length == 8); + + aw = bw; // reset + put(aw, "𐐷"); // try transcoding from char[] + assert(bw[0 .. 2] == "𐐷"w); + assert(aw.length == 8); } @safe unittest @@ -578,8 +718,8 @@ pure @safe unittest void popFront(){ end = true; } } LockingTextWriter w; - RetroResult r; - put(w, r); + RetroResult re; + put(w, re); } @system unittest @@ -612,19 +752,19 @@ pure @safe unittest putChar(p, cast(dchar)'a'); //Source Char - foreach (SC; AliasSeq!(char, wchar, dchar)) - { + static 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)) + static 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 + static foreach (Type; AliasSeq!(PutC!TC, PutS!TC)) + {{ Type type; auto sink = new Type(); @@ -640,9 +780,9 @@ pure @safe unittest put(value, ss); assert(value.result == "I♥日本語!日本語が好きですか?"); } - }(); + }} } - } + }} } @safe unittest @@ -662,27 +802,27 @@ pure @safe unittest put(c, "hello"d); } +// https://issues.dlang.org/show_bug.cgi?id=9823 @system unittest { - // issue 9823 const(char)[] r; void delegate(const(char)[]) dg = (s) { r = s; }; put(dg, ["ABC"]); assert(r == "ABC"); } +// https://issues.dlang.org/show_bug.cgi?id=10571 @safe unittest { - // issue 10571 - import std.format; + import std.format.write : formattedWrite; string buf; - formattedWrite((in char[] s) { buf ~= s; }, "%s", "hello"); + formattedWrite((scope const(char)[] s) { buf ~= s; }, "%s", "hello"); assert(buf == "hello"); } @safe unittest { - import std.format; + import std.format.write : formattedWrite; import std.meta : AliasSeq; struct PutC(C) { @@ -716,8 +856,8 @@ pure @safe unittest } void foo() { - foreach (C; AliasSeq!(char, wchar, dchar)) - { + static 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); @@ -728,21 +868,21 @@ pure @safe unittest 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 +Returns `true` if `R` is a native output range for elements of type +`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. +is valid, then `put(r,e)` will have the same behavior. -The two guarantees isNativeOutputRange gives over the larger $(D isOutputRange) +The two guarantees isNativeOutputRange gives over the larger `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 +1: `e` is $(B exactly) what will be placed (not `[e]`, for example). +2: if `E` is a non $(empty) `InputRange`, then placing `e` is guaranteed to not overflow the range. +/ package(std) enum bool isNativeOutputRange(R, E) = @@ -763,9 +903,12 @@ package(std) enum bool isNativeOutputRange(R, E) = } /++ -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 +Returns `true` if `R` is an output range for elements of type +`E`. An output range is defined functionally as a range that supports the operation $(D put(r, e)) as defined above. + +See_Also: + The header of $(MREF std,range) for tutorials on ranges. +/ enum bool isOutputRange(R, E) = is(typeof(put(lvalueOf!R, lvalueOf!E))); @@ -773,10 +916,10 @@ enum bool isOutputRange(R, E) = /// @safe unittest { - void myprint(in char[] s) { } + void myprint(scope const(char)[] s) { } static assert(isOutputRange!(typeof(&myprint), char)); - static assert(!isOutputRange!(char[], char)); + static assert( isOutputRange!(char[], char)); static assert( isOutputRange!(dchar[], wchar)); static assert( isOutputRange!(dchar[], dchar)); } @@ -791,7 +934,7 @@ enum bool isOutputRange(R, E) = 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!(wchar[], wchar)); static assert( isOutputRange!(dchar[], char)); static assert( isOutputRange!(dchar[], string)); static assert( isOutputRange!(dchar[], wstring)); @@ -803,9 +946,9 @@ enum bool isOutputRange(R, E) = /** -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 +Returns `true` if `R` is a forward range. A forward range is an +input range `r` that can save "checkpoints" by saving `r.save` +to another value of type `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 @@ -821,14 +964,27 @@ 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 +Saving a range is not duplicating it; in the example above, `r1` +and `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. +object with `save` and using it later. + +`save` behaves in many ways like a copy constructor, and its +implementation typically is done using copy construction. + +The existence of a copy constructor, however, does not imply +the range is a forward range. For example, a range that reads +from a TTY consumes its input and cannot save its place and +read it again, and so cannot be a forward range and cannot +have a `save` function. + + +See_Also: + The header of $(MREF std,range) for tutorials on ranges. */ enum bool isForwardRange(R) = isInputRange!R && is(ReturnType!((R r) => r.save) == R); @@ -856,18 +1012,21 @@ enum bool isForwardRange(R) = isInputRange!R } /** -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 +Returns `true` if `R` is a bidirectional range. A bidirectional +range is a forward range that also offers the primitives `back` and +`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)): +compilation) are assumed to be the following (`r` is an object of +type `R`): + +$(UL $(LI `r.back` returns (possibly a reference to) the last +element in the range. Calling `r.back` is allowed only if calling +`r.empty` has, or would have, returned `false`.)) -$(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).)) +See_Also: + The header of $(MREF std,range) for tutorials on ranges. */ enum bool isBidirectionalRange(R) = isForwardRange!R && is(typeof((R r) => r.popBack)) @@ -912,27 +1071,30 @@ enum bool isBidirectionalRange(R) = isForwardRange!R } /** -Returns $(D true) if $(D R) is a random-access range. A random-access +Returns `true` if `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 +opIndex), OR an infinite forward range that offers `opIndex`. In +either case, the range must either offer `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.)) +compilation) are assumed to be the following (`r` is an object of +type `R`): $(UL $(LI `r.opIndex(n)` returns a reference to the +`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 +Although `char[]` and `wchar[]` (as well as their qualified +versions including `string` and `wstring`) are arrays, $(D +isRandomAccessRange) yields `false` for them because they use variable-length encodings (UTF-8 and UTF-16 respectively). These types are bidirectional ranges only. + +See_Also: + The header of $(MREF std,range) for tutorials on ranges. */ enum bool isRandomAccessRange(R) = is(typeof(lvalueOf!R[1]) == ElementType!R) - && !isNarrowString!R + && !(isAutodecodableString!R && !isAggregateType!R) && isForwardRange!R && (isBidirectionalRange!R || isInfinite!R) && (hasLength!R || isInfinite!R) @@ -942,7 +1104,7 @@ enum bool isRandomAccessRange(R) = /// @safe unittest { - import std.traits : isNarrowString; + import std.traits : isAggregateType, isAutodecodableString; alias R = int[]; @@ -954,7 +1116,7 @@ enum bool isRandomAccessRange(R) = 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(!(isAutodecodableString!R && !isAggregateType!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 $ @@ -1051,10 +1213,10 @@ enum bool isRandomAccessRange(R) = } /** -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 +Returns `true` iff `R` is an input range that supports the +`moveFront` primitive, as well as `moveBack` and `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) +may work via the default behavior of the module level functions `moveFront` and friends. The following code should compile for any range with mobile elements. @@ -1101,12 +1263,12 @@ enum bool hasMobileElements(R) = } /** -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). +The element type of `R`. `R` does not have to be a range. The +element type is determined as the type yielded by `r.front` for an +object `r` of type `R`. For example, `ElementType!(T[])` is +`T` if `T[]` isn't a narrow string; if it is, the element type is +`dchar`. If `R` doesn't have `front`, `ElementType!R` is +`void`. */ template ElementType(R) { @@ -1167,7 +1329,8 @@ template ElementType(R) static assert(is(ElementType!(char[0]) == dchar)); } -@safe unittest //11336 +// https://issues.dlang.org/show_bug.cgi?id=11336 +@safe unittest { static struct S { @@ -1176,7 +1339,8 @@ template ElementType(R) static assert(is(ElementType!(S[]) == S)); } -@safe unittest // 11401 +// https://issues.dlang.org/show_bug.cgi?id=11401 +@safe unittest { // ElementType should also work for non-@propety 'front' struct E { ushort id; } @@ -1188,11 +1352,11 @@ template ElementType(R) } /** -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). +The encoding element type of `R`. For narrow strings (`char[]`, +`wchar[]` and their qualified variants including `string` and +`wstring`), `ElementEncodingType` is the character type of the +string. For all other types, `ElementEncodingType` is the same as +`ElementType`. */ template ElementEncodingType(R) { @@ -1253,7 +1417,7 @@ template ElementEncodingType(R) } /** -Returns $(D true) if $(D R) is an input range and has swappable +Returns `true` if `R` is an input range and has swappable elements. The following code should compile for any range with swappable elements. @@ -1291,7 +1455,7 @@ template hasSwappableElements(R) } /** -Returns $(D true) if $(D R) is an input range and has mutable +Returns `true` if `R` is an input range and has mutable elements. The following code should compile for any range with assignable elements. @@ -1325,7 +1489,7 @@ enum bool hasAssignableElements(R) = isInputRange!R } /** -Tests whether the range $(D R) has lvalue elements. These are defined as +Tests whether the range `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. ---- @@ -1338,11 +1502,19 @@ static if (isRandomAccessRange!R) passByRef(r[0]); ---- */ enum bool hasLvalueElements(R) = isInputRange!R - && is(typeof(((ref x) => x)(lvalueOf!R.front))) + && is(typeof(isLvalue(lvalueOf!R.front))) && (!isBidirectionalRange!R - || is(typeof(((ref x) => x)(lvalueOf!R.back)))) + || is(typeof(isLvalue(lvalueOf!R.back)))) && (!isRandomAccessRange!R - || is(typeof(((ref x) => x)(lvalueOf!R[0])))); + || is(typeof(isLvalue(lvalueOf!R[0])))); + +/* Compile successfully if argument of type T is an lvalue + */ +private void isLvalue(T)(T) +if (0); + +private void isLvalue(T)(ref T) +if (1); /// @safe unittest @@ -1392,7 +1564,8 @@ length, use $(REF representation, std, string) or $(REF byCodeUnit, std, utf). template hasLength(R) { static if (is(typeof(((R* r) => r.length)(null)) Length)) - enum bool hasLength = is(Length == size_t) && !isNarrowString!R; + enum bool hasLength = is(Length == size_t) && + !(isAutodecodableString!R && !isAggregateType!R); else enum bool hasLength = false; } @@ -1411,7 +1584,7 @@ template hasLength(R) } // test combinations which are invalid on some platforms -unittest +@safe unittest { struct A { ulong length; } struct B { @property uint length() { return 0; } } @@ -1429,7 +1602,7 @@ unittest } // test combinations which are invalid on all platforms -unittest +@safe unittest { struct A { long length; } struct B { int length; } @@ -1442,9 +1615,9 @@ unittest } /** -Returns $(D true) if $(D R) is an infinite input range. An +Returns `true` if `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), +enumerated member called `empty` that is always `false`, for example: ---- @@ -1473,17 +1646,17 @@ template isInfinite(R) } /** -Returns $(D true) if $(D R) offers a slicing operator with integral boundaries +Returns `true` if `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 +For finite ranges, the result of `opSlice` must be of the same type as the +original range type. If the range defines `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 +For infinite ranges, when $(I not) using `opDollar`, the result of +`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 +when using `opDollar`, the result of `opSlice` must be that of the original range type. The following expression must be true for `hasSlicing` to be `true`: @@ -1503,7 +1676,7 @@ The following expression must be true for `hasSlicing` to be `true`: ---- */ enum bool hasSlicing(R) = isForwardRange!R - && !isNarrowString!R + && !(isAutodecodableString!R && !isAggregateType!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)) @@ -1560,23 +1733,23 @@ enum bool hasSlicing(R) = isForwardRange!R } /** -This is a best-effort implementation of $(D length) for any kind of +This is a best-effort implementation of `length` for any kind of range. -If $(D hasLength!Range), simply returns $(D range.length) without -checking $(D upTo) (when specified). +If `hasLength!Range`, simply returns `range.length` without +checking `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 +of elements seen. Performes $(BIGOH n) evaluations of `range.empty` +and `range.popFront()`, where `n` is the effective length of $(D range). -The $(D upTo) parameter is useful to "cut the losses" in case +The `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). +of elements. If the parameter `upTo` is specified, stops if $(D +upTo) steps have been taken and returns `upTo`. -Infinite ranges are compatible, provided the parameter $(D upTo) is +Infinite ranges are compatible, provided the parameter `upTo` is specified, in which case the implementation simply returns upTo. */ auto walkLength(Range)(Range range) @@ -1587,6 +1760,20 @@ if (isInputRange!Range && !isInfinite!Range) else { size_t result; + static if (autodecodeStrings && isNarrowString!Range) + { + import std.utf : codeUnitLimit; + result = range.length; + foreach (const i, const c; range) + { + if (c >= codeUnitLimit!Range) + { + result = i; + break; + } + } + range = range[result .. $]; + } for ( ; !range.empty ; range.popFront() ) ++result; return result; @@ -1603,12 +1790,38 @@ if (isInputRange!Range) else { size_t result; + static if (autodecodeStrings && isNarrowString!Range) + { + import std.utf : codeUnitLimit; + result = upTo > range.length ? range.length : upTo; + foreach (const i, const c; range[0 .. result]) + { + if (c >= codeUnitLimit!Range) + { + result = i; + break; + } + } + range = range[result .. $]; + } for ( ; result < upTo && !range.empty ; range.popFront() ) ++result; return result; } } +/// +@safe unittest +{ + import std.range : iota; + + assert(10.iota.walkLength == 10); + // iota has a length function, and therefore the + // doesn't have to be walked, and the upTo + // parameter is ignored + assert(10.iota.walkLength(5) == 10); +} + @safe unittest { import std.algorithm.iteration : filter; @@ -1637,18 +1850,18 @@ if (isInputRange!Range) } /** - 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), + `popFrontN` eagerly advances `r` itself (not a copy) up to `n` times + (by calling `r.popFront`). `popFrontN` takes `r` by `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. + `popBackN` behaves the same as `popFrontN` but instead removes + elements from the back of the (bidirectional) range instead of the front. - $(D popBackN) will behave the same but instead removes elements from - the back of the (bidirectional) range instead of the front. + Returns: + How much `r` was actually advanced, which may be less than `n` if + `r` did not have at least `n` elements. See_Also: $(REF drop, std, range), $(REF dropBack, std, range) */ @@ -1766,24 +1979,24 @@ if (isBidirectionalRange!Range) } /** - 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), + Eagerly advances `r` itself (not a copy) exactly `n` times (by + calling `r.popFront`). `popFrontExactly` takes `r` by `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) + Note: Unlike $(LREF popFrontN), `popFrontExactly` will assume that the + range holds at least `n` elements. This makes `popFrontExactly` + faster than `popFrontN`, but it also means that if `range` does + not contain at least `n` elements, it will attempt to call `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. + `popFrontExactly` when it is guaranteed that `range` holds at least + `n` elements. - $(D popBackExactly) will behave the same but instead removes elements from + `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) + See_Also: $(REF dropExactly, std, range), $(REF dropBackExactly, std, range) */ void popFrontExactly(Range)(ref Range r, size_t n) if (isInputRange!Range) @@ -1842,9 +2055,9 @@ if (isBidirectionalRange!Range) } /** - Moves the front of $(D r) out and returns it. Leaves $(D r.front) in a + Moves the front of `r` out and returns it. Leaves `r.front` in a destroyable state that does not allocate any resources (usually equal - to its $(D .init) value). + to its `.init` value). */ ElementType!R moveFront(R)(R r) { @@ -1900,9 +2113,9 @@ ElementType!R moveFront(R)(R r) } /** - Moves the back of $(D r) out and returns it. Leaves $(D r.back) in a + Moves the back of `r` out and returns it. Leaves `r.back` in a destroyable state that does not allocate any resources (usually equal - to its $(D .init) value). + to its `.init` value). */ ElementType!R moveBack(R)(R r) { @@ -1946,9 +2159,9 @@ ElementType!R moveBack(R)(R r) } /** - Moves element at index $(D i) of $(D r) out and returns it. Leaves $(D + Moves element at index `i` of `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). + (usually equal to its `.init` value). */ ElementType!R moveAt(R)(R r, size_t i) { @@ -2004,12 +2217,13 @@ ElementType!R moveAt(R)(R r, size_t i) } /** -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)). +Implements the range interface primitive `empty` for types that +obey $(LREF hasLength) property and for narrow strings. Due to the +fact that nonmember functions can be called with the first argument +using the dot notation, `a.empty` is equivalent to `empty(a)`. */ -@property bool empty(T)(in T[] a) @safe pure nothrow @nogc +@property bool empty(T)(auto ref scope T a) +if (is(typeof(a.length) : size_t)) { return !a.length; } @@ -2020,16 +2234,21 @@ equivalent to $(D empty(array)). auto a = [ 1, 2, 3 ]; assert(!a.empty); assert(a[3 .. $].empty); + + int[string] b; + assert(b.empty); + b["zero"] = 0; + assert(!b.empty); } /** -Implements the range interface primitive $(D save) for built-in +Implements the range interface primitive `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 +the first argument using the dot notation, `array.save` is +equivalent to `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 +@property inout(T)[] save(T)(return scope inout(T)[] a) @safe pure nothrow @nogc { return a; } @@ -2043,15 +2262,15 @@ content of the array, it simply returns its argument. } /** -Implements the range interface primitive $(D popFront) for built-in +Implements the range interface primitive `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 +the first argument using the dot notation, `array.popFront` is +equivalent to `popFront(array)`. For $(GLOSSARY narrow strings), +`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[])) +void popFront(T)(scope ref inout(T)[] a) @safe pure nothrow @nogc +if (!isAutodecodableString!(T[]) && !is(T[] == void[])) { assert(a.length, "Attempting to popFront() past the end of an array of " ~ T.stringof); a = a[1 .. $]; @@ -2065,7 +2284,7 @@ if (!isNarrowString!(T[]) && !is(T[] == void[])) assert(a == [ 2, 3 ]); } -version (unittest) +@safe unittest { static assert(!is(typeof({ int[4] a; popFront(a); }))); static assert(!is(typeof({ immutable int[] a; popFront(a); }))); @@ -2073,14 +2292,14 @@ version (unittest) } /// ditto -void popFront(C)(ref C[] str) @trusted pure nothrow -if (isNarrowString!(C[])) +void popFront(C)(scope ref inout(C)[] str) @trusted pure nothrow +if (isAutodecodableString!(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 if (is(immutable C == immutable char)) { static immutable ubyte[] charWidthTab = [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -2090,17 +2309,10 @@ if (isNarrowString!(C[])) ]; 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]; - } - + immutable charWidth = c < 192 ? 1 : charWidthTab.ptr[c - 192]; + str = str.ptr[min(str.length, charWidth) .. str.length]; } - else static if (is(Unqual!C == wchar)) + else static if (is(immutable C == immutable wchar)) { immutable u = str[0]; immutable seqLen = 1 + (u >= 0xD800 && u <= 0xDBFF); @@ -2113,8 +2325,8 @@ if (isNarrowString!(C[])) { import std.meta : AliasSeq; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ S s = "\xC2\xA9hello"; s.popFront(); assert(s == "hello"); @@ -2129,7 +2341,7 @@ if (isNarrowString!(C[])) 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) { @@ -2144,7 +2356,8 @@ if (isNarrowString!(C[])) static assert(checkCTFEW.empty); } -@safe unittest // issue 16090 +// https://issues.dlang.org/show_bug.cgi?id=16090 +@safe unittest { string s = "\u00E4"; assert(s.length == 2); @@ -2165,14 +2378,14 @@ if (isNarrowString!(C[])) } /** -Implements the range interface primitive $(D popBack) for built-in +Implements the range interface primitive `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 +the first argument using the dot notation, `array.popBack` is +equivalent to `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[])) +void popBack(T)(scope ref inout(T)[] a) @safe pure nothrow @nogc +if (!isAutodecodableString!(T[]) && !is(T[] == void[])) { assert(a.length); a = a[0 .. $ - 1]; @@ -2186,7 +2399,7 @@ if (!isNarrowString!(T[]) && !is(T[] == void[])) assert(a == [ 1, 2 ]); } -version (unittest) +@safe unittest { static assert(!is(typeof({ immutable int[] a; popBack(a); }))); static assert(!is(typeof({ int[4] a; popBack(a); }))); @@ -2194,8 +2407,8 @@ version (unittest) } /// ditto -void popBack(T)(ref T[] a) @safe pure -if (isNarrowString!(T[])) +void popBack(T)(scope ref inout(T)[] a) @safe pure +if (isAutodecodableString!(T[])) { import std.utf : strideBack; assert(a.length, "Attempting to popBack() past the front of an array of " ~ T.stringof); @@ -2206,8 +2419,8 @@ if (isNarrowString!(T[])) { import std.meta : AliasSeq; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ S s = "hello\xE2\x89\xA0"; s.popBack(); assert(s == "hello"); @@ -2227,19 +2440,34 @@ if (isNarrowString!(T[])) 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 +EXPERIMENTAL: to try out removing autodecoding, set the version +`NoAutodecodeStrings`. Most things are expected to fail with this version +currently. +*/ +version (NoAutodecodeStrings) +{ + enum autodecodeStrings = false; +} +else +{ + /// + enum autodecodeStrings = true; +} + +/** +Implements the range interface primitive `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 +the first argument using the dot notation, `array.front` is +equivalent to `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[])) +@property ref inout(T) front(T)(return scope inout(T)[] a) @safe pure nothrow @nogc +if (!isAutodecodableString!(T[]) && !is(T[] == void[])) { assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); return a[0]; @@ -2267,8 +2495,8 @@ if (!isNarrowString!(T[]) && !is(T[] == void[])) } /// ditto -@property dchar front(T)(T[] a) @safe pure -if (isNarrowString!(T[])) +@property dchar front(T)(scope const(T)[] a) @safe pure +if (isAutodecodableString!(T[])) { import std.utf : decode; assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); @@ -2277,15 +2505,15 @@ if (isNarrowString!(T[])) } /** -Implements the range interface primitive $(D back) for built-in +Implements the range interface primitive `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 +the first argument using the dot notation, `array.back` is +equivalent to `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[])) +@property ref inout(T) back(T)(return scope inout(T)[] a) @safe pure nothrow @nogc +if (!isAutodecodableString!(T[]) && !is(T[] == void[])) { assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof); return a[$ - 1]; @@ -2311,11 +2539,26 @@ if (!isNarrowString!(T[]) && !is(T[] == void[])) /// ditto // Specialization for strings -@property dchar back(T)(T[] a) @safe pure -if (isNarrowString!(T[])) +@property dchar back(T)(scope const(T)[] a) @safe pure +if (isAutodecodableString!(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); } + +/* +Implements `length` for a range by forwarding it to `member`. +*/ +package(std) mixin template ImplementLength(alias member) +{ + static if (hasLength!(typeof(member))) + { + @property auto length() + { + return member.length; + } + alias opDollar = length; + } +} diff --git a/libphobos/src/std/regex/internal/backtracking.d b/libphobos/src/std/regex/internal/backtracking.d index ffc9779..21766fc 100644 --- a/libphobos/src/std/regex/internal/backtracking.d +++ b/libphobos/src/std/regex/internal/backtracking.d @@ -9,760 +9,783 @@ package(std.regex): import core.stdc.stdlib, std.range.primitives, std.traits, std.typecons; import std.regex.internal.ir; +import core.memory : pureMalloc, pureFree; + /+ BacktrackingMatcher implements backtracking scheme of matching regular expressions. +/ -template BacktrackingMatcher(bool CTregex) +@trusted class BacktrackingMatcher(Char, Stream = Input!Char) : Matcher!Char +if (is(Char : dchar)) { - @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; + alias DataIndex = Stream.DataIndex; + struct State + {//top bit in pc is set if saved along with matches 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; + 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(BacktrackingMatcher) pure; + const RegEx re; // regex program + 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 + uint infiniteNesting; + size_t[] memory; + Trace[] merge; + static struct Trace + { + ulong mask; + size_t offset; - bool mark(size_t idx) + bool mark(size_t idx) + { + immutable d = idx - offset; + if (d < 64) // including overflow { - 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; - } + 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; + } + //local slice of matches, global for backref + Group!DataIndex[] matches, backrefed; + size_t _refCount; +final: - static if (__traits(hasMember,Stream, "search")) - { - enum kicked = true; - } - else - enum kicked = false; + override @property ref size_t refCount() { return _refCount; } + override @property ref const(RegEx) pattern(){ return re; } - static size_t initialMemory(const ref RegEx re) - { - return stackSize(re)*size_t.sizeof + re.hotspotTableSize*Trace.sizeof; - } + static if (__traits(hasMember,Stream, "search")) + { + enum kicked = true; + } + else + enum kicked = false; - static size_t stackSize(const ref RegEx re) - { - size_t itemSize = stateSize - + re.ngroup * (Group!DataIndex).sizeof / size_t.sizeof; - return initialStack * itemSize + 2; - } + 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 atStart(){ return index == 0; } - @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } + @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } - void next() - { - if (!s.nextChar(front, index)) - index = s.lastIndex; - } + void next() + { + if (!s.nextChar(front, index)) + index = s.lastIndex; + } - void search() + void search() + { + static if (kicked) { - static if (kicked) + if (!s.search(re.kickstart, front, index)) { - if (!s.search(re.kickstart, front, index)) - { - index = s.lastIndex; - } + index = s.lastIndex; } - else - next(); } + 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; - } + // + 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() + bool prevStack() + { + // pointer to previous block + size_t* prev = cast(size_t*) memory.ptr[-2]; + if (!prev) { - // 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; - } + // The last segment is freed in RegexMatch + return false; } - - void initExternalMemory(void[] memBlock) + else { - 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..$]; + import core.memory : pureFree; + // memory used in previous block + size_t size = memory.ptr[-1]; + pureFree(memory.ptr-2); + memory = prev[0 .. size]; + lastState = size; + return true; } + } - void initialize(ref RegEx program, Stream stream, void[] memBlock) - { - re = program; - s = stream; - exhausted = false; - initExternalMemory(memBlock); - backrefed = null; - } + 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..$]; + } - auto dupTo(void[] memory) - { - typeof(this) tmp = this; - tmp.initExternalMemory(memory); - return tmp; - } + void initialize(ref const RegEx program, Stream stream, void[] memBlock) + { + s = stream; + exhausted = false; + initExternalMemory(memBlock); + backrefed = null; + } - this(ref RegEx program, Stream stream, void[] memBlock, dchar ch, DataIndex idx) - { - initialize(program, stream, memBlock); - front = ch; - index = idx; - } + override void dupTo(Matcher!Char m, void[] memBlock) + { + auto backtracking = cast(BacktrackingMatcher) m; + backtracking.s = s; + backtracking.front = front; + backtracking.index = index; + backtracking.exhausted = exhausted; + backtracking.initExternalMemory(memBlock); + } - this(ref RegEx program, Stream stream, void[] memBlock) - { - initialize(program, stream, memBlock); - next(); - } + override Matcher!Char rearm(in Char[] data) + { + merge[] = Trace.init; + exhausted = false; + s = Stream(data); + next(); + return this; + } - 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; + this(ref const RegEx program, Stream stream, void[] memBlock, dchar ch, DataIndex idx) + { + _refCount = 1; + re = program; + nativeFn = null; + initialize(program, stream, memBlock); + front = ch; + index = idx; + } + + this(ref const RegEx program, MatchFn func, Stream stream, void[] memBlock) + { + _refCount = 1; + re = program; + initialize(program, stream, memBlock); + nativeFn = func; + next(); + } + + this(ref const RegEx program, Stream stream, void[] memBlock) + { + _refCount = 1; + re = program; + nativeFn = null; + initialize(program, stream, memBlock); + next(); + } + + auto fwdMatcher(ref const RegEx re, void[] memBlock) + { + alias BackMatcher = BacktrackingMatcher!(Char, Stream); + auto fwdMatcher = new BackMatcher(re, s, memBlock, front, index); + return fwdMatcher; + } + + auto bwdMatcher(ref const RegEx re, void[] memBlock) + { + alias BackMatcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index))); + auto fwdMatcher = + new BackMatcher(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; + } - auto bwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock) + //lookup next match, fill matches with indices into input + override int match(Group!DataIndex[] matches) + { + debug(std_regex_matcher) { - alias BackMatcherTempl = .BacktrackingMatcher!(CTregex); - alias BackMatcher = BackMatcherTempl!(Char, typeof(s.loopBack(index))); - auto fwdMatcher = - BackMatcher(matcher.re, s.loopBack(index), memBlock); - return fwdMatcher; + writeln("------------------------------------------"); } - - // - int matchFinalize() + if (exhausted) //all matches collected + return false; + this.matches = matches; + if (re.flags & RegexInfo.oneShot) { - immutable start = index; - immutable val = matchImpl(); - if (val) - {//stream is updated here + exhausted = true; + const DataIndex start = index; + immutable m = matchImpl(); + if (m) + { 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; + return m; } - - //lookup next match, fill matches with indices into input - int match(Group!DataIndex[] matches) + static if (kicked) { - 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) { - if (!re.kickstart.empty) + for (;;) { - for (;;) + immutable val = matchFinalize(); + if (val) + return val; + else { - immutable val = matchFinalize(); - if (val) - return val; - else + if (atEnd) + break; + search(); + if (atEnd) { - if (atEnd) - break; - search(); - if (atEnd) - { - exhausted = true; - return matchFinalize(); - } + exhausted = true; + return matchFinalize(); } } - exhausted = true; - return 0; //early return } + exhausted = true; + return 0; //early return } - //no search available - skip a char at a time - for (;;) + } + //no search available - skip a char at a time + for (;;) + { + immutable val = matchFinalize(); + if (val) + return val; + else { - immutable val = matchFinalize(); - if (val) - return val; - else + if (atEnd) + break; + next(); + if (atEnd) { - if (atEnd) - break; - next(); - if (atEnd) - { - exhausted = true; - return matchFinalize(); - } + exhausted = true; + return matchFinalize(); } } - exhausted = true; - return 0; } + exhausted = true; + return 0; + } - /+ - match subexpression against input, - results are stored in matches - +/ - int matchImpl() + /+ + match subexpression against input, + results are stored in matches + +/ + int matchImpl() pure + { + if (nativeFn) { - static if (CTregex && is(typeof(nativeFn(this)))) - { - debug(std_regex_ctr) writeln("using C-T matcher"); - return nativeFn(this); - } - else + debug(std_regex_ctr) writeln("using C-T matcher"); + return nativeFn(this); + } + else + { + pc = 0; + counter = 0; + lastState = 0; + infiniteNesting = 0; + matches[] = Group!DataIndex.init; + auto start = s._index; + debug(std_regex_matcher) + writeln("Try match starting at ", s[index .. s.lastIndex]); + for (;;) { - 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 (;;) + 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) { - 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) { - 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) + for (pc = pc+2; pc < end; pc++) + if (re.ir[pc].data == front) + break; + if (pc == end) goto L_backtrack; - pc += IRL!(IR.Char); - next(); + } + pc = end; + 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); + 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; - case IR.Trie: - if (atEnd || !re.matchers[re.ir[pc].data][front]) - goto L_backtrack; - next(); - pc += IRL!(IR.Trie); + } + else if (atEnd && s.loopBack(index).nextChar(back, bi) + && wordMatcher[back]) + { + pc += IRL!(IR.Wordboundary); 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]) + } + else if (s.loopBack(index).nextChar(back, bi)) + { + immutable af = wordMatcher[front]; + immutable ab = wordMatcher[back]; + if (af ^ ab) { 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; - 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 + 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; - 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 += 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; - 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) + } + 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 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 { + pushState(pc - len, counter + step); 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++; - } + } + 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; - 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); + } + 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 = pureMalloc(initialMemory(re))[0 .. initialMemory(re)]; + scope(exit) pureFree(mem.ptr); + auto slicedRe = re.withCode(re.ir[ + pc+IRL!(IR.LookaheadStart) .. pc+IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd) + ]); + static if (Stream.isLoopback) + { + auto matcher = bwdMatcher(slicedRe, mem); + } + else + { + auto matcher = fwdMatcher(slicedRe, mem); + } + matcher.matches = matches[ms .. me]; + matcher.backrefed = backrefed.empty ? matches : backrefed; + 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 = pureMalloc(initialMemory(re))[0 .. initialMemory(re)]; + scope(exit) pureFree(mem.ptr); + auto slicedRe = re.withCode(re.ir[ + pc + IRL!(IR.LookbehindStart) .. pc + IRL!(IR.LookbehindStart) + len + IRL!(IR.LookbehindEnd) + ]); + static if (Stream.isLoopback) + { + alias Matcher = BacktrackingMatcher!(Char, Stream); + auto matcher = new Matcher(slicedRe, s, mem, front, index); + } + else + { + alias Matcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index))); + auto matcher = new Matcher(slicedRe, s.loopBack(index), mem); + } + matcher.matches = matches[ms .. me]; + 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(); - 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; - } + 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); } + assert(0); + } - @property size_t stackAvail() - { - return memory.length - lastState; - } + @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) + 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 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)(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) + 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); + } + //helper function, saves engine state + void pushState(uint pc, uint counter) + { + if (stateSize + 2 * matches.length > stackAvail) { - 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); + 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]); + } - static if (!CTregex) + //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) { - //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; - } + 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; } } @@ -795,8 +818,6 @@ template BacktrackingMatcher(bool CTregex) return format; } -alias Sequence(int B, int E) = staticIota!(B, E); - struct CtContext { import std.conv : to, text; @@ -805,7 +826,7 @@ struct CtContext //to mark the portion of matches to save int match, total_matches; int reserved; - CodepointSet[] charsets; + const(CodepointInterval)[][] charsets; //state of codegenerator @@ -815,12 +836,15 @@ struct CtContext int addr; } - this(Char)(Regex!Char re) + this(Char)(ref const Regex!Char re) { match = 1; reserved = 1; //first match is skipped total_matches = re.ngroup; - charsets = re.charsets; + foreach (ref set; re.charsets) + { + charsets ~= set.intervals; + } } CtContext lookaround(uint s, uint e) @@ -876,7 +900,7 @@ struct CtContext } // - CtState ctGenBlock(Bytecode[] ir, int addr) + CtState ctGenBlock(const(Bytecode)[] ir, int addr) { CtState result; result.addr = addr; @@ -890,7 +914,7 @@ struct CtContext } // - CtState ctGenGroup(ref Bytecode[] ir, int addr) + CtState ctGenGroup(ref const(Bytecode)[] ir, int addr) { import std.algorithm.comparison : max; auto bailOut = "goto L_backtrack;"; @@ -932,10 +956,10 @@ struct CtContext 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)"; + string fwdType = "typeof(fwdMatcher(re, []))"; + string bwdType = "typeof(bwdMatcher(re, []))"; + string fwdCreate = "fwdMatcher(re, mem)"; + string bwdCreate = "bwdMatcher(re, 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 @@ -946,15 +970,15 @@ struct CtContext alias Lookaround = $$; else alias Lookaround = $$; - static bool matcher_$$(ref Lookaround matcher) @trusted + static bool matcher_$$(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); + auto mem = pureMalloc(initialMemory(re))[0 .. initialMemory(re)]; + scope(exit) pureFree(mem.ptr); static if (typeof(matcher.s).isLoopback) auto lookaround = $$; else @@ -992,7 +1016,7 @@ struct CtContext } //generate source for bytecode contained in OrStart ... OrEnd - CtState ctGenAlternation(Bytecode[] ir, int addr) + CtState ctGenAlternation(const(Bytecode)[] ir, int addr) { CtState[] pieces; CtState r; @@ -1032,11 +1056,11 @@ struct CtContext // 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) + string ctGenFixupCode(const(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 ctGenFixupCode(ref const(Bytecode)[] ir, int addr, int fixup) { string r; string testCode; @@ -1190,7 +1214,7 @@ struct CtContext } - string ctQuickTest(Bytecode[] ir, int id) + string ctQuickTest(const(Bytecode)[] ir, int id) { uint pc = 0; while (pc < ir.length && ir[pc].isAtom) @@ -1217,7 +1241,7 @@ struct CtContext } //process & generate source for simple bytecodes at front of ir using address addr - CtState ctGenAtom(ref Bytecode[] ir, int addr) + CtState ctGenAtom(ref const(Bytecode)[] ir, int addr) { CtState result; result.code = ctAtomCode(ir, addr); @@ -1227,7 +1251,7 @@ struct CtContext } //D code for atom at ir using address addr, addr < 0 means quickTest - string ctAtomCode(Bytecode[] ir, int addr) + string ctAtomCode(const(Bytecode)[] ir, int addr) { string code; string bailOut, nextInstr; @@ -1282,7 +1306,7 @@ struct CtContext if (charsets.length) { string name = `func_`~to!string(addr+1); - string funcCode = charsets[ir[0].data].toSourceCode(name); + string funcCode = CodepointSet.toSourceCode(charsets[ir[0].data], name); code ~= ctSub( ` static $$ if (atEnd || !$$(front)) @@ -1298,7 +1322,7 @@ struct CtContext $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); break; case IR.Trie: - if (charsets.length && charsets[ir[0].data].byInterval.length <= 8) + if (charsets.length && charsets[ir[0].data].length <= 8) goto case IR.CodepointSet; code ~= ctSub( ` if (atEnd || !re.matchers[$$][front]) @@ -1439,11 +1463,11 @@ struct CtContext } //generate D code for the whole regex - public string ctGenRegEx(Bytecode[] ir) + public string ctGenRegEx(const(Bytecode)[] ir) { auto bdy = ctGenBlock(ir, 0); auto r = ` - import core.stdc.stdlib; + import core.memory : pureMalloc, pureFree; with(matcher) { pc = 0; @@ -1488,7 +1512,7 @@ struct CtContext } -string ctGenRegExCode(Char)(Regex!Char re) +string ctGenRegExCode(Char)(const 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 index 6831e59..c1fe4cd 100644 --- a/libphobos/src/std/regex/internal/generator.d +++ b/libphobos/src/std/regex/internal/generator.d @@ -13,7 +13,7 @@ module std.regex.internal.generator; @trusted private struct SampleGenerator(Char) { import std.array : appender, Appender; - import std.format : formattedWrite; + import std.format.write : formattedWrite; import std.random : Xorshift; import std.regex.internal.ir : Regex, IR, IRL; import std.utf : isValidDchar, byChar; diff --git a/libphobos/src/std/regex/internal/ir.d b/libphobos/src/std/regex/internal/ir.d index 28b1998..ec0cb66 100644 --- a/libphobos/src/std/regex/internal/ir.d +++ b/libphobos/src/std/regex/internal/ir.d @@ -30,7 +30,9 @@ CharMatcher[CodepointSet] matcherCache; //accessor with caching @trusted CharMatcher getMatcher(CodepointSet set) -{// @@@BUG@@@ 6357 almost all properties of AA are not @safe +{ + // almost all properties of AA are not @safe + // https://issues.dlang.org/show_bug.cgi?id=6357 if (__ctfe || maxCachedMatchers == 0) return CharMatcher(set); else @@ -47,40 +49,15 @@ CharMatcher[CodepointSet] matcherCache; } } -@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() +@property ref wordMatcher()() { - return memoizeExpr!("unicode.Alphabetic | unicode.Mn | unicode.Mc - | unicode.Me | unicode.Nd | unicode.Pc")(); -} - -@property CharMatcher wordMatcher() -{ - return memoizeExpr!("CharMatcher(wordCharacter)")(); + static immutable CharMatcher matcher = CharMatcher(wordCharacter); + return matcher; } // 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 @@ -97,6 +74,20 @@ enum RegexOption: uint { //do not reorder this list alias RegexOptionNames = AliasSeq!('g', 'i', 'x', 'U', 'm', 's'); static assert( RegexOption.max < 0x80); + +package(std) string regexOptionsToString()(uint flags) nothrow pure @safe +{ + flags &= (RegexOption.max << 1) - 1; + if (!flags) + return ""; + char[RegexOptionNames.length] buffer = void; + size_t pos = 0; + foreach (i, flag; __traits(allMembers, RegexOption)) + if (flags & __traits(getMember, RegexOption, flag)) + buffer[pos++] = RegexOptionNames[i]; + return buffer[0 .. pos].idup; +} + // flags that allow guide execution of engine enum RegexInfo : uint { oneShot = 0x80 } @@ -173,7 +164,8 @@ template IRL(IR code) static assert(IRL!(IR.LookaheadStart) == 3); //how many parameters follow the IR, should be optimized fixing some IR bits -int immediateParamsIR(IR i){ +int immediateParamsIR(IR i) @safe pure nothrow @nogc +{ switch (i) { case IR.OrEnd,IR.InfiniteEnd,IR.InfiniteQEnd: @@ -190,49 +182,50 @@ int immediateParamsIR(IR i){ } //full length of IR instruction inlcuding all parameters that might follow it -int lengthOfIR(IR i) +int lengthOfIR(IR i) @safe pure nothrow @nogc { return 1 + immediateParamsIR(i); } //full length of the paired IR instruction inlcuding all parameters that might follow it -int lengthOfPairedIR(IR i) +int lengthOfPairedIR(IR i) @safe pure nothrow @nogc { return 1 + immediateParamsIR(pairedIR(i)); } //if the operation has a merge point (this relies on the order of the ops) -bool hasMerge(IR i) +bool hasMerge(IR i) @safe pure nothrow @nogc { return (i&0b11)==0b10 && i <= IR.RepeatQEnd; } //is an IR that opens a "group" -bool isStartIR(IR i) +bool isStartIR(IR i) @safe pure nothrow @nogc { return (i&0b11)==0b01; } //is an IR that ends a "group" -bool isEndIR(IR i) +bool isEndIR(IR i) @safe pure nothrow @nogc { return (i&0b11)==0b10; } //is a standalone IR -bool isAtomIR(IR i) +bool isAtomIR(IR i) @safe pure nothrow @nogc { return (i&0b11)==0b00; } //makes respective pair out of IR i, swapping start/end bits of instruction -IR pairedIR(IR i) +IR pairedIR(IR i) @safe pure nothrow @nogc { assert(isStartIR(i) || isEndIR(i)); - return cast(IR)(i ^ 0b11); + return cast(IR) (i ^ 0b11); } //encoded IR instruction +@safe pure struct Bytecode { uint raw; @@ -241,6 +234,7 @@ struct Bytecode enum maxData = 1 << 22; enum maxRaw = 1 << 31; +@safe pure: this(IR code, uint data) { assert(data < (1 << 22) && code < 256); @@ -262,8 +256,8 @@ struct Bytecode return t; } - //bit twiddling helpers - //0-arg template due to @@@BUG@@@ 10985 + // bit twiddling helpers + // 0-arg template due to https://issues.dlang.org/show_bug.cgi?id=10985 @property uint data()() const { return raw & 0x003f_ffff; } @property void data()(uint val) @@ -271,12 +265,12 @@ struct Bytecode raw = (raw & ~0x003f_ffff) | (val & 0x003f_ffff); } - //ditto - //0-arg template due to @@@BUG@@@ 10985 + // ditto + // 0-arg template due to https://issues.dlang.org/show_bug.cgi?id=10985 @property uint sequence()() const { return 2 + (raw >> 22 & 0x3); } - //ditto - //0-arg template due to @@@BUG@@@ 10985 + // ditto + // 0-arg template due to https://issues.dlang.org/show_bug.cgi?id=10985 @property IR code()() const { return cast(IR)(raw >> 24); } //ditto @@ -369,11 +363,20 @@ struct NamedGroup //holds pair of start-end markers for a submatch struct Group(DataIndex) { - DataIndex begin, end; + DataIndex begin = DataIndex.max; + DataIndex end = DataIndex.min; + + bool opCast(T : bool)() const + { + return begin <= end; + } + @trusted string toString()() const { + if (begin < end) + return "(unmatched)"; import std.array : appender; - import std.format : formattedWrite; + import std.format.write : formattedWrite; auto a = appender!string(); formattedWrite(a, "%s..%s", begin, end); return a.data; @@ -384,7 +387,7 @@ struct Group(DataIndex) @trusted string disassemble(in Bytecode[] irb, uint pc, in NamedGroup[] dict=[]) { import std.array : appender; - import std.format : formattedWrite; + import std.format.write : formattedWrite; auto output = appender!string(); formattedWrite(output,"%s", irb[pc].mnemonic); switch (irb[pc].code) @@ -452,9 +455,169 @@ struct Group(DataIndex) writeln("\t", disassemble(slice, pc, dict)); } +// Encapsulates memory management, explicit ref counting +// and the exact type of engine created +// there is a single instance per engine combination type x Char +// In future may also maintain a (TLS?) cache of memory +interface MatcherFactory(Char) +{ +@safe: + Matcher!Char create(const ref Regex!Char, in Char[] input) const; + Matcher!Char dup(Matcher!Char m, in Char[] input) const; + size_t incRef(Matcher!Char m) const; + size_t decRef(Matcher!Char m) const; +} + +// Only memory management, no compile-time vs run-time specialities +abstract class GenericFactory(alias EngineType, Char) : MatcherFactory!Char +{ + import core.memory : pureFree; + import std.internal.memory : enforceMalloc; + import core.memory : GC; + // round up to next multiple of size_t for alignment purposes + enum classSize = (__traits(classInstanceSize, EngineType!Char) + size_t.sizeof - 1) & ~(size_t.sizeof - 1); + + EngineType!Char construct(const ref Regex!Char re, in Char[] input, void[] memory) const; + + override EngineType!Char create(const ref Regex!Char re, in Char[] input) const @trusted + { + immutable size = EngineType!Char.initialMemory(re) + classSize; + auto memory = enforceMalloc(size)[0 .. size]; + scope(failure) pureFree(memory.ptr); + GC.addRange(memory.ptr, classSize); + auto engine = construct(re, input, memory); + assert(engine.refCount == 1); + assert(cast(void*) engine == memory.ptr); + return engine; + } + + override EngineType!Char dup(Matcher!Char engine, in Char[] input) const @trusted + { + immutable size = EngineType!Char.initialMemory(engine.pattern) + classSize; + auto memory = enforceMalloc(size)[0 .. size]; + scope(failure) pureFree(memory.ptr); + auto copy = construct(engine.pattern, input, memory); + GC.addRange(memory.ptr, classSize); + engine.dupTo(copy, memory[classSize .. size]); + assert(copy.refCount == 1); + return copy; + } + + override size_t incRef(Matcher!Char m) const + { + return ++m.refCount; + } + + override size_t decRef(Matcher!Char m) const @trusted + { + assert(m.refCount != 0); + auto cnt = --m.refCount; + if (cnt == 0) + { + void* ptr = cast(void*) m; + GC.removeRange(ptr); + pureFree(ptr); + } + return cnt; + } +} + +// A factory for run-time engines +class RuntimeFactory(alias EngineType, Char) : GenericFactory!(EngineType, Char) +{ + override EngineType!Char construct(const ref Regex!Char re, in Char[] input, void[] memory) const + { + import core.lifetime : emplace; + return emplace!(EngineType!Char)(memory[0 .. classSize], + re, Input!Char(input), memory[classSize .. $]); + } +} + +// A factory for compile-time engine +class CtfeFactory(alias EngineType, Char, alias func) : GenericFactory!(EngineType, Char) +{ + override EngineType!Char construct(const ref Regex!Char re, in Char[] input, void[] memory) const + { + import core.lifetime : emplace; + return emplace!(EngineType!Char)(memory[0 .. classSize], + re, &func, Input!Char(input), memory[classSize .. $]); + } +} + +private auto defaultFactoryImpl(Char)(const ref Regex!Char re) +{ + import std.regex.internal.backtracking : BacktrackingMatcher; + import std.regex.internal.thompson : ThompsonMatcher; + import std.algorithm.searching : canFind; + static MatcherFactory!Char backtrackingFactory; + static MatcherFactory!Char thompsonFactory; + if (re.backrefed.canFind!"a != 0") + { + if (backtrackingFactory is null) + backtrackingFactory = new RuntimeFactory!(BacktrackingMatcher, Char); + return backtrackingFactory; + } + else + { + if (thompsonFactory is null) + thompsonFactory = new RuntimeFactory!(ThompsonMatcher, Char); + return thompsonFactory; + } +} + +// Used to generate a pure wrapper for defaultFactoryImpl. Based on the example in +// the std.traits.SetFunctionAttributes documentation. +auto assumePureFunction(T)(T t) +if (isFunctionPointer!T) +{ + enum attrs = functionAttributes!T | FunctionAttribute.pure_; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; +} + +// A workaround for R-T enum re = regex(...) +template defaultFactory(Char) +{ + // defaultFactory is constructed as a safe, pure wrapper over defaultFactoryImpl. + // It can be faked as pure because the static mutable variables are used to cache + // the key and character matcher. The technique used avoids delegates and GC. + @property MatcherFactory!Char defaultFactory(const ref Regex!Char re) @safe pure + { + static auto impl(const ref Regex!Char re) + { + return defaultFactoryImpl(re); + } + + static auto pureImpl(const ref Regex!Char re) @trusted + { + auto p = assumePureFunction(&impl); + return p(re); + } + + return pureImpl(re); + } +} + +// Defining it as an interface has the undesired side-effect: +// casting any class to an interface silently adjusts pointer to point to a nested vtbl +abstract class Matcher(Char) +{ +abstract: + // Get a (next) match + int match(Group!size_t[] matches) pure; + // This only maintains internal ref-count, + // deallocation happens inside MatcherFactory + @property ref size_t refCount() @safe; + // Copy internal state to another engine, using memory arena 'memory' + void dupTo(Matcher!Char m, void[] memory); + // The pattern loaded + @property ref const(Regex!Char) pattern() @safe; + // Re-arm the engine with new Input + Matcher rearm(in Char[] stream); +} + /++ - $(D Regex) object holds regular expression pattern in compiled form. - Instances of this object are constructed via calls to $(D regex). + `Regex` object holds regular expression pattern in compiled form. + Instances of this object are constructed via calls to `regex`. This is an intended form for caching and storage of frequently used regular expressions. +/ @@ -466,17 +629,19 @@ struct Regex(Char) @safe @property bool empty() const nothrow { return ir is null; } - + /++ + `namedCaptures` returns a range of all named captures in a given regular expression. + +/ @safe @property auto namedCaptures() { static struct NamedGroupRange { private: - NamedGroup[] groups; + const(NamedGroup)[] groups; size_t start; size_t end; public: - this(NamedGroup[] g, size_t s, size_t e) + this(const(NamedGroup)[] g, size_t s, size_t e) { assert(s <= e); assert(e <= g.length); @@ -514,7 +679,7 @@ struct Regex(Char) package(std.regex): import std.regex.internal.kickstart : Kickstart; //TODO: get rid of this dependency - NamedGroup[] dict; // maps name -> user group number + const(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 @@ -524,6 +689,36 @@ package(std.regex): public const(BitTable)[] filters; // bloom filters for conditional loops uint[] backrefed; // bit array of backreferenced submatches Kickstart!Char kickstart; + MatcherFactory!Char factory; // produces optimal matcher for this pattern + immutable(Char)[] pattern; // copy of pattern to serve as cache key + + const(Regex) withFactory(MatcherFactory!Char factory) pure const @trusted + { + auto r = cast() this; + r.factory = factory; + return r; + } + + const(Regex) withFlags(uint newFlags) pure const @trusted + { + auto r = cast() this; + r.flags = newFlags; + return r; + } + + const(Regex) withCode(const(Bytecode)[] code) pure const @trusted + { + auto r = cast() this; + r.ir = code.dup; // TODO: sidestep const instead? + return r; + } + + const(Regex) withNGroup(uint nGroup) pure const @trusted + { + auto r = cast() this; + r.ngroup = nGroup; + return r; + } //bit access helper uint isBackref(uint n) @@ -564,26 +759,20 @@ package(std.regex): 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) + public string toString()() const { - _regex = re; - nativeFn = fn; - + import std.format : format; + static if (is(typeof(pattern) : string)) + alias patternString = pattern; + else + { + import std.conv : to; + auto patternString = conv.to!string(pattern); + } + auto quotedEscapedPattern = format("%(%s %)", [patternString]); + auto flagString = regexOptionsToString(flags); + return "Regex!" ~ Char.stringof ~ "(" ~ quotedEscapedPattern ~ ", \"" ~ flagString ~ "\")"; } - } // The stuff below this point is temporarrily part of IR module @@ -622,7 +811,7 @@ if (is(Char :dchar)) @property bool atEnd(){ return _index == _origin.length; } - bool search(Kickstart)(ref Kickstart kick, ref dchar res, ref size_t pos) + bool search(Kickstart)(ref const Kickstart kick, ref dchar res, ref size_t pos) { size_t idx = kick.search(_origin, _index); _index = idx; @@ -653,6 +842,11 @@ struct BackLooperImpl(Input) _origin = input._origin; _index = index; } + this(String input) + { + _origin = input; + _index = input.length; + } @trusted bool nextChar(ref dchar res,ref size_t pos) { pos = _index; @@ -690,10 +884,10 @@ template BackLooper(E) //both helpers below are internal, on its own are quite "explosive" //unsafe, no initialization of elements -@system T[] mallocArray(T)(size_t len) +@system pure T[] mallocArray(T)(size_t len) { - import core.stdc.stdlib : malloc; - return (cast(T*) malloc(len * T.sizeof))[0 .. len]; + import core.memory : pureMalloc; + return (cast(T*) pureMalloc(len * T.sizeof))[0 .. len]; } //very unsafe, no initialization @@ -705,7 +899,7 @@ template BackLooper(E) } // -@trusted uint lookupNamedGroup(String)(NamedGroup[] dict, String name) +@trusted uint lookupNamedGroup(String)(const(NamedGroup)[] dict, String name) {//equal is @system? import std.algorithm.comparison : equal; import std.algorithm.iteration : map; @@ -718,16 +912,15 @@ template BackLooper(E) return dict[fnd].group; } -//whether ch is one of unicode newline sequences -//0-arg template due to @@@BUG@@@ 10985 +// whether ch is one of unicode newline sequences +// 0-arg template due to https://issues.dlang.org/show_bug.cgi?id=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 +// 0-arg template due to https://issues.dlang.org/show_bug.cgi?id=10985 bool startOfLine()(dchar back, bool seenNl) { return ((back == '\r') ^ seenNl) || back == '\n' @@ -786,3 +979,215 @@ struct CharMatcher { return trie[ch]; } } + +// Internal non-resizeble array, switches between inline storage and CoW +// POD-only +struct SmallFixedArray(T, uint SMALL=3) +if (!hasElaborateDestructor!T) +{ + import std.internal.memory : enforceMalloc; + import core.memory : pureFree; + static struct Payload + { + size_t refcount; + T[0] placeholder; + inout(T)* ptr() inout { return placeholder.ptr; } + } + static assert(Payload.sizeof == size_t.sizeof); + union + { + Payload* big; + T[SMALL] small; + } + size_t _sizeMask; + enum BIG_MASK = size_t(1)<<(8*size_t.sizeof-1); + enum SIZE_MASK = ~BIG_MASK; + + @property bool isBig() const { return (_sizeMask & BIG_MASK) != 0; } + @property size_t length() const { return _sizeMask & SIZE_MASK; } + + this(size_t size) + { + if (size <= SMALL) + { + small[] = T.init; + _sizeMask = size; + } + else + { + big = cast(Payload*) enforceMalloc(Payload.sizeof + T.sizeof*size); + big.refcount = 1; + _sizeMask = size | BIG_MASK; + } + } + + private @trusted @property inout(T)[] internalSlice() inout + { + return isBig ? big.ptr[0 .. length] : small[0 .. length]; + } + + this(this) + { + if (isBig) + { + big.refcount++; + } + } + + bool opEquals(SmallFixedArray a) + { + return internalSlice[] == a.internalSlice[]; + } + + size_t toHash() const + { + return hashOf(internalSlice[]); + } + + ref inout(T) opIndex(size_t idx) inout + { + return internalSlice[idx]; + } + + // accesses big to test self-referencing so not @safe + @trusted ref opAssign(SmallFixedArray arr) + { + if (isBig) + { + if (arr.isBig) + { + if (big is arr.big) return this; // self-assign + else + { + abandonRef(); + _sizeMask = arr._sizeMask; + big = arr.big; + big.refcount++; + } + } + else + { + abandonRef(); + _sizeMask = arr._sizeMask; + small = arr.small; + } + } + else + { + if (arr.isBig) + { + _sizeMask = arr._sizeMask; + big = arr.big; + big.refcount++; + } + else + { + _sizeMask = arr._sizeMask; + small = arr.small; + } + } + return this; + } + + void mutate(scope void delegate(T[]) pure filler) + { + if (isBig && big.refcount != 1) // copy on write + { + auto oldSizeMask = _sizeMask; + auto newbig = cast(Payload*) enforceMalloc(Payload.sizeof + T.sizeof*length); + newbig.refcount = 1; + abandonRef(); + big = newbig; + _sizeMask = oldSizeMask; + } + filler(internalSlice); + } + + ~this() + { + if (isBig) + { + abandonRef(); + } + } + + @trusted private void abandonRef() + { + assert(isBig); + if (--big.refcount == 0) + { + pureFree(big); + _sizeMask = 0; + assert(!isBig); + } + } +} + +@system unittest +{ + alias SA = SmallFixedArray!(int, 2); + SA create(int[] data) + { + SA a = SA(data.length); + a.mutate((slice) { slice[] = data[]; }); + assert(a.internalSlice == data); + return a; + } + + { + SA a; + a = SA(1); + assert(a.length == 1); + a = SA.init; + assert(a.length == 0); + } + + { + SA a, b, c, d; + assert(a.length == 0); + assert(a.internalSlice == b.internalSlice); + a = create([1]); + assert(a.internalSlice == [1]); + b = create([2, 3]); + assert(b.internalSlice == [2, 3]); + c = create([3, 4, 5]); + d = create([5, 6, 7, 8]); + assert(c.isBig); + a = c; + assert(a.isBig); + assert(a.big is c.big); + assert(a.big.refcount == 2); + assert(a.internalSlice == [3, 4, 5]); + assert(c.internalSlice == [3, 4, 5]); + a = b; + assert(!a.isBig); + assert(a.internalSlice == [2, 3]); + assert(c.big.refcount == 1); + a = c; + assert(c.big.refcount == 2); + + // mutate copies on write if ref-count is not 1 + a.mutate((slice){ slice[] = 1; }); + assert(a.internalSlice == [1, 1, 1]); + assert(c.internalSlice == [3, 4, 5]); + assert(a.isBig && c.isBig); + assert(a.big.refcount == 1); + assert(c.big.refcount == 1); + + auto e = d; + assert(e.big.refcount == 2); + auto f = d; + f = a; + assert(f.isBig); + assert(f.internalSlice == [1, 1, 1]); + assert(f.big.refcount == 2); // a & f + assert(e.big.refcount == 2); // d & e + a = c; + assert(f.big.refcount == 1); // f + assert(e.big.refcount == 2); // d & e + a = a; + a = a; + a = a; + assert(a.big.refcount == 2); // a & c + } +} diff --git a/libphobos/src/std/regex/internal/kickstart.d b/libphobos/src/std/regex/internal/kickstart.d index f303b43..4529205 100644 --- a/libphobos/src/std/regex/internal/kickstart.d +++ b/libphobos/src/std/regex/internal/kickstart.d @@ -393,7 +393,7 @@ public: // 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) + @trusted size_t search(const(Char)[] haystack, size_t idx) const {//@BUG: apparently assumes little endian machines import core.stdc.string : memchr; import std.conv : text; @@ -517,8 +517,8 @@ public: import std.conv, std.regex; @trusted void test_fixed(alias Kick)() { - foreach (i, v; AliasSeq!(char, wchar, dchar)) - { + static foreach (i, v; AliasSeq!(char, wchar, dchar)) + {{ alias Char = v; alias String = immutable(v)[]; auto r = regex(to!String(`abc$`)); @@ -539,12 +539,12 @@ public: 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)) - { + static foreach (i, v; AliasSeq!(char, wchar, dchar)) + {{ alias Char = v; alias String = immutable(v)[]; auto r = regex(to!String(`abc[a-z]`)); @@ -570,7 +570,7 @@ public: assert(kick.search("ababx",0) == 2); assert(kick.search("abaacba",0) == 3);//expected inexact - } + }} } test_fixed!(ShiftOr)(); test_flex!(ShiftOr)(); diff --git a/libphobos/src/std/regex/internal/parser.d b/libphobos/src/std/regex/internal/parser.d index 8cabae5..41ca687 100644 --- a/libphobos/src/std/regex/internal/parser.d +++ b/libphobos/src/std/regex/internal/parser.d @@ -4,15 +4,19 @@ */ module std.regex.internal.parser; -static import std.ascii; +import std.regex.internal.ir; import std.range.primitives, std.uni, std.meta, std.traits, std.typecons, std.exception; -import std.regex.internal.ir; +static import std.ascii; // package relevant info from parser into a regex object auto makeRegex(S, CG)(Parser!(S, CG) p) { - Regex!(BasicElementOf!S) re; + import std.regex.internal.backtracking : BacktrackingMatcher; + import std.regex.internal.thompson : ThompsonMatcher; + import std.algorithm.searching : canFind; + alias Char = BasicElementOf!S; + Regex!Char re; auto g = p.g; with(re) { @@ -24,7 +28,14 @@ auto makeRegex(S, CG)(Parser!(S, CG) p) charsets = g.charsets; matchers = g.matchers; backrefed = g.backrefed; + re.pattern = p.origin.idup; re.postprocess(); + // check if we have backreferences, if so - use backtracking + if (__ctfe) factory = null; // allows us to use the awful enum re = regex(...); + else if (re.backrefed.canFind!"a != 0") + factory = new RuntimeFactory!(BacktrackingMatcher, Char); + else + factory = new RuntimeFactory!(ThompsonMatcher, Char); debug(std_regex_parser) { __ctfe || print(); @@ -157,98 +168,6 @@ if (isSomeString!S) 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 @@ -616,7 +535,7 @@ enum infinite = ~0u; struct Parser(R, Generator) if (isForwardRange!R && is(ElementType!R : dchar)) { - dchar _current; + dchar front; bool empty; R pat, origin; //keep full pattern for pretty printing error messages uint re_flags = 0; //global flags e.g. multiline + internal ones @@ -628,8 +547,8 @@ if (isForwardRange!R && is(ElementType!R : dchar)) pat = origin = pattern; //reserve slightly more then avg as sampled from unittests parseFlags(flags); - _current = ' ';//a safe default for freeform parsing - next(); + front = ' ';//a safe default for freeform parsing + popFront(); g.start(cast(uint) pat.length); try { @@ -642,61 +561,47 @@ if (isForwardRange!R && is(ElementType!R : dchar)) g.endPattern(1); } - @property dchar current(){ return _current; } - - bool _next() + void _popFront() { if (pat.empty) { empty = true; - return false; } - _current = pat.front; - pat.popFront(); - return true; + else + { + front = pat.front; + pat.popFront(); + } } void skipSpace() { - while (isWhite(current) && _next()){ } + while (!empty && isWhite(front)) _popFront(); } - bool next() + void popFront() { - if (re_flags & RegexOption.freeform) - { - immutable r = _next(); - skipSpace(); - return r; - } - else - return _next(); + _popFront(); + if (re_flags & RegexOption.freeform) skipSpace(); } + auto save(){ return this; } + //parsing number with basic overflow check uint parseDecimal() { uint r = 0; - while (std.ascii.isDigit(current)) + while (std.ascii.isDigit(front)) { if (r >= (uint.max/10)) error("Overflow in decimal number"); - r = 10*r + cast(uint)(current-'0'); - if (!next()) - break; + r = 10*r + cast(uint)(front-'0'); + popFront(); + if (empty) 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 @@ -730,73 +635,74 @@ if (isForwardRange!R && is(ElementType!R : dchar)) { debug(std_regex_parser) __ctfe || writeln("*LR*\nSource: ", pat, "\nStack: ",fixupStack.data); - switch (current) + switch (front) { case '(': - next(); - if (current == '?') + popFront(); + if (front == '?') { - next(); - switch (current) + popFront(); + switch (front) { case '#': for (;;) { - if (!next()) - error("Unexpected end of pattern"); - if (current == ')') + popFront(); + enforce(!empty, "Unexpected end of pattern"); + if (front == ')') { - next(); + popFront(); break; } } break; case ':': g.genLogicGroup(); - next(); + popFront(); break; case '=': g.genLookaround(IR.LookaheadStart); - next(); + popFront(); break; case '!': g.genLookaround(IR.NeglookaheadStart); - next(); + popFront(); break; case 'P': - next(); - if (current != '<') - error("Expected '<' in named group"); + popFront(); + enforce(front == '<', "Expected '<' in named group"); string name; - if (!next() || !(isAlpha(current) || current == '_')) + popFront(); + if (empty || !(isAlpha(front) || front == '_')) error("Expected alpha starting a named group"); - name ~= current; - while (next() && (isAlpha(current) || - current == '_' || std.ascii.isDigit(current))) + name ~= front; + popFront(); + while (!empty && (isAlpha(front) || + front == '_' || std.ascii.isDigit(front))) { - name ~= current; + name ~= front; + popFront(); } - if (current != '>') - error("Expected '>' closing named group"); - next(); + enforce(front == '>', "Expected '>' closing named group"); + popFront(); g.genNamedGroup(name); break; case '<': - next(); - if (current == '=') + popFront(); + if (front == '=') g.genLookaround(IR.LookbehindStart); - else if (current == '!') + else if (front == '!') g.genLookaround(IR.NeglookbehindStart); else error("'!' or '=' expected after '<'"); - next(); + popFront(); break; default: uint enableFlags, disableFlags; bool enable = true; do { - switch (current) + switch (front) { case 's': if (enable) @@ -830,9 +736,9 @@ if (isForwardRange!R && is(ElementType!R : dchar)) default: error(" 's', 'x', 'i', 'm' or '-' expected after '(?' "); } - next(); - }while (current != ')'); - next(); + popFront(); + }while (front != ')'); + popFront(); re_flags |= enableFlags; re_flags &= ~disableFlags; } @@ -844,13 +750,13 @@ if (isForwardRange!R && is(ElementType!R : dchar)) break; case ')': enforce(g.nesting, "Unmatched ')'"); - next(); + popFront(); auto pair = g.onClose(); if (pair[0]) parseQuantifier(pair[1]); break; case '|': - next(); + popFront(); g.fixAlternation(); break; default://no groups or whatever @@ -875,7 +781,7 @@ if (isForwardRange!R && is(ElementType!R : dchar)) if (empty) return g.fixRepetition(offset); uint min, max; - switch (current) + switch (front) { case '*': min = 0; @@ -890,28 +796,27 @@ if (isForwardRange!R && is(ElementType!R : dchar)) max = infinite; break; case '{': - enforce(next(), "Unexpected end of regex pattern"); - enforce(std.ascii.isDigit(current), "First number required in repetition"); + popFront(); + enforce(!empty, "Unexpected end of regex pattern"); + enforce(std.ascii.isDigit(front), "First number required in repetition"); min = parseDecimal(); - if (current == '}') + if (front == '}') max = min; - else if (current == ',') + else if (front == ',') { - next(); - if (std.ascii.isDigit(current)) + popFront(); + if (std.ascii.isDigit(front)) max = parseDecimal(); - else if (current == '}') + else if (front == '}') max = infinite; else error("Unexpected symbol in regex pattern"); skipSpace(); - if (current != '}') - error("Unmatched '{' in regex pattern"); + enforce(front == '}', "Unmatched '{' in regex pattern"); } else error("Unexpected symbol in regex pattern"); - if (min > max) - error("Illegal {n,m} quantifier"); + enforce(min <= max, "Illegal {n,m} quantifier"); break; default: g.fixRepetition(offset); @@ -919,10 +824,11 @@ if (isForwardRange!R && is(ElementType!R : dchar)) } bool greedy = true; //check only if we managed to get new symbol - if (next() && current == '?') + popFront(); + if (!empty && front == '?') { greedy = false; - next(); + popFront(); } g.fixRepetition(offset, min, max, greedy); } @@ -932,11 +838,10 @@ if (isForwardRange!R && is(ElementType!R : dchar)) { if (empty) return; - switch (current) + switch (front) { case '*', '?', '+', '|', '{', '}': error("'*', '+', '?', '{', '}' not allowed in atom"); - break; case '.': if (re_flags & RegexOption.singleline) g.put(Bytecode(IR.Any, 0)); @@ -945,13 +850,14 @@ if (isForwardRange!R && is(ElementType!R : dchar)) CodepointSet set; g.charsetToIr(set.add('\n','\n'+1).add('\r', '\r'+1).inverted); } - next(); + popFront(); break; case '[': parseCharset(); break; case '\\': - enforce(_next(), "Unfinished escape sequence"); + _popFront(); + enforce(!empty, "Unfinished escape sequence"); parseEscape(); break; case '^': @@ -959,20 +865,19 @@ if (isForwardRange!R && is(ElementType!R : dchar)) g.put(Bytecode(IR.Bol, 0)); else g.put(Bytecode(IR.Bof, 0)); - next(); + popFront(); break; case '$': if (re_flags & RegexOption.multiline) g.put(Bytecode(IR.Eol, 0)); else g.put(Bytecode(IR.Eof, 0)); - next(); + popFront(); break; default: - //FIXME: getCommonCasing in new std uni if (re_flags & RegexOption.casefold) { - auto range = simpleCaseFoldings(current); + auto range = simpleCaseFoldings(front); assert(range.length <= 5); if (range.length == 1) g.put(Bytecode(IR.Char, range.front)); @@ -981,507 +886,96 @@ if (isForwardRange!R && is(ElementType!R : dchar)) g.put(Bytecode(IR.OrChar, v, cast(uint) range.length)); } else - g.put(Bytecode(IR.Char, current)); - next(); + g.put(Bytecode(IR.Char, front)); + popFront(); } } - - - //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(); + bool casefold = cast(bool)(re_flags & RegexOption.casefold); + g.charsetToIr(unicode.parseSet(this, casefold)); re_flags = save; - // Last next() in parseCharsetImp is executed w/o freeform flag + // Last next() in parseCharset 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) + switch (front) { - 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 'f': popFront(); g.put(Bytecode(IR.Char, '\f')); break; + case 'n': popFront(); g.put(Bytecode(IR.Char, '\n')); break; + case 'r': popFront(); g.put(Bytecode(IR.Char, '\r')); break; + case 't': popFront(); g.put(Bytecode(IR.Char, '\t')); break; + case 'v': popFront(); g.put(Bytecode(IR.Char, '\v')); break; case 'd': - next(); + popFront(); g.charsetToIr(unicode.Nd); break; case 'D': - next(); + popFront(); 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 'b': popFront(); g.put(Bytecode(IR.Wordboundary, 0)); break; + case 'B': popFront(); g.put(Bytecode(IR.Notwordboundary, 0)); break; case 's': - next(); + popFront(); g.charsetToIr(unicode.White_Space); break; case 'S': - next(); + popFront(); g.charsetToIr(unicode.White_Space.inverted); break; case 'w': - next(); + popFront(); g.charsetToIr(wordCharacter); break; case 'W': - next(); + popFront(); g.charsetToIr(wordCharacter.inverted); break; case 'p': case 'P': - auto CodepointSet = parseUnicodePropertySpec(current == 'P'); - g.charsetToIr(CodepointSet); + bool casefold = cast(bool)(re_flags & RegexOption.casefold); + auto set = unicode.parsePropertySpec(this, front == 'P', casefold); + g.charsetToIr(set); break; case 'x': immutable code = parseUniHex(pat, 2); - next(); + popFront(); g.put(Bytecode(IR.Char,code)); break; case 'u': case 'U': - immutable code = parseUniHex(pat, current == 'u' ? 4 : 8); - next(); + immutable code = parseUniHex(pat, front == 'u' ? 4 : 8); + popFront(); g.put(Bytecode(IR.Char, code)); break; case 'c': //control codes - Bytecode code = Bytecode(IR.Char, parseControlCode()); - next(); + Bytecode code = Bytecode(IR.Char, unicode.parseControlCode(this)); + popFront(); g.put(code); break; case '0': - next(); + popFront(); g.put(Bytecode(IR.Char, 0));//NUL character break; case '1': .. case '9': - uint nref = cast(uint) current - '0'; + uint nref = cast(uint) front - '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)) + popFront(); + while (nref < maxBackref && !empty && std.ascii.isDigit(front)) { - nref = nref * 10 + current - '0'; + nref = nref * 10 + front - '0'; + popFront(); } if (nref >= maxBackref) nref /= 10; @@ -1497,57 +991,27 @@ if (isForwardRange!R && is(ElementType!R : dchar)) 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 (front == '\\' && !pat.empty) { - if (pat.front >= privateUseStart && current <= privateUseEnd) + if (pat.front >= privateUseStart && pat.front <= privateUseEnd) enforce(false, "invalid escape sequence"); } - if (current >= privateUseStart && current <= privateUseEnd) + if (front >= privateUseStart && front <= privateUseEnd) { - g.endPattern(current - privateUseStart + 1); + g.endPattern(front - privateUseStart + 1); break; } - auto op = Bytecode(IR.Char, current); - next(); + auto op = Bytecode(IR.Char, front); + popFront(); 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; + import std.format.write : formattedWrite; auto app = appender!string(); formattedWrite(app, "%s\nPattern with error: `%s` <--HERE-- `%s`", msg, origin[0..$-pat.length], pat); diff --git a/libphobos/src/std/regex/internal/tests.d b/libphobos/src/std/regex/internal/tests.d index fe75ce0..8a0fec1 100644 --- a/libphobos/src/std/regex/internal/tests.d +++ b/libphobos/src/std/regex/internal/tests.d @@ -8,9 +8,9 @@ package(std.regex): import std.conv, std.exception, std.meta, std.range, std.typecons, std.regex; -import std.regex.internal.ir : Escapables; // characters that need escaping +import std.uni : Escapables; // characters that need escaping -alias Sequence(int B, int E) = staticIota!(B, E); +debug(std_regex_test) import std.stdio; @safe unittest {//sanity checks @@ -353,8 +353,8 @@ alias Sequence(int B, int E) = staticIota!(B, E); void run_tests(alias matchFn)() { int i; - foreach (Char; AliasSeq!( char, wchar, dchar)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (Char; AliasSeq!( char, wchar, dchar)) + {{ alias String = immutable(Char)[]; String produceExpected(M,Range)(auto ref M m, Range fmt) { @@ -397,7 +397,7 @@ alias Sequence(int B, int E) = staticIota!(B, E); } } } - }(); + }} debug(std_regex_test) writeln("!!! FReD bulk test done "~matchFn.stringof~" !!!"); } @@ -408,28 +408,28 @@ alias Sequence(int B, int E) = staticIota!(B, E); version (std_regex_ct1) { pragma(msg, "Testing 1st part of ctRegex"); - alias Tests = Sequence!(0, 155); + enum Tests = iota(0, 155); } else version (std_regex_ct2) { pragma(msg, "Testing 2nd part of ctRegex"); - alias Tests = Sequence!(155, 174); + enum Tests = iota(155, 174); } //FIXME: #174-178 contains CTFE parser bug else version (std_regex_ct3) { pragma(msg, "Testing 3rd part of ctRegex"); - alias Tests = Sequence!(178, 220); + enum Tests = iota(178, 220); } else version (std_regex_ct4) { pragma(msg, "Testing 4th part of ctRegex"); - alias Tests = Sequence!(220, tv.length); + enum Tests = iota(220, tv.length); } else - alias Tests = AliasSeq!(Sequence!(0, 30), Sequence!(235, tv.length-5)); - foreach (a, v; Tests) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + enum Tests = chain(iota(0, 30), iota(235, tv.length-5)); + static foreach (v; Tests) + {{ enum tvd = tv[v]; static if (tvd.result == "c") { @@ -443,22 +443,20 @@ alias Sequence(int B, int E) = staticIota!(B, E); auto r = ctRegex!(tv[v].pattern, tv[v].flags); auto nr = regex(tvd.pattern, tvd.flags); assert(equal(r.ir, nr.ir), - text("!C-T regex! failed to compile pattern #", a ,": ", tvd.pattern)); + text("!C-T regex! failed to compile pattern #", v ,": ", tvd.pattern)); auto m = match(tvd.input, r); auto c = tvd.result[0]; bool ok = (c == 'y') ^ m.empty; assert(ok, text("ctRegex: failed to match pattern #", - a ,": ", tvd.pattern)); + v ,": ", tvd.pattern)); if (c == 'y') { - import std.stdio; auto result = produceExpected(m, tvd.format); - if (result != tvd.replace) - writeln("ctRegex mismatch pattern #", a, ": ", tvd.pattern," expected: ", - tvd.replace, " vs ", result); + assert(result == tvd.replace, text("ctRegex mismatch pattern #", v, + ": ", tvd.pattern," expected: ", tvd.replace, " vs ", result)); } } - }(); + }} debug(std_regex_test) writeln("!!! FReD C-T test done !!!"); } diff --git a/libphobos/src/std/regex/internal/tests2.d b/libphobos/src/std/regex/internal/tests2.d index 420f8d3..0c9f23d 100644 --- a/libphobos/src/std/regex/internal/tests2.d +++ b/libphobos/src/std/regex/internal/tests2.d @@ -7,7 +7,7 @@ package(std.regex): import std.conv, std.exception, std.meta, std.range, std.typecons, std.regex; -import std.regex.internal.ir : Escapables; // characters that need escaping +import std.uni : Escapables; // characters that need escaping @safe unittest { @@ -60,11 +60,11 @@ import std.regex.internal.ir : Escapables; // characters that need escaping { import std.algorithm.comparison : equal; auto rtr = regex("a|b|c"); - enum ctr = regex("a|b|c"); + static 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`); + static testCT = regex(`abc|(edf)|xyz`); auto testRT = regex(`abc|(edf)|xyz`); assert(equal(testCT.ir,testRT.ir)); } @@ -149,7 +149,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping import std.algorithm.iteration : map; void test_body(alias matchFn)() { - //issue 5857 + // https://issues.dlang.org/show_bug.cgi?id=5857 //matching goes out of control if ... in (...){x} has .*/.+ auto c = matchFn("axxxzayyyyyzd",regex("(a.*z){2}d")).captures; assert(c[0] == "axxxzayyyyyzd"); @@ -157,7 +157,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping auto c2 = matchFn("axxxayyyyyd",regex("(a.*){2}d")).captures; assert(c2[0] == "axxxayyyyyd"); assert(c2[1] == "ayyyyy"); - //issue 2108 + // https://issues.dlang.org/show_bug.cgi?id=2108 //greedy vs non-greedy auto nogreed = regex(""); assert(matchFn("texttext", nogreed).hit @@ -165,7 +165,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping auto greed = regex(""); assert(matchFn("texttext", greed).hit == "texttext"); - //issue 4574 + // https://issues.dlang.org/show_bug.cgi?id=4574 //empty successful match still advances the input string[] pres, posts, hits; foreach (m; matchFn("abcabc", regex("","g"))) @@ -195,7 +195,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping ]; assert(pres == array(retro(heads))); assert(posts == tails); - //issue 6076 + // https://issues.dlang.org/show_bug.cgi?id=6076 //regression on .* auto re = regex("c.*|d"); auto m = matchFn("mm", re); @@ -214,7 +214,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(!match(to!string(ch),regex(`[^\`~ch~`]`))); assert(match(to!string(ch),regex(`[\`~ch~`-\`~ch~`]`))); } - //bugzilla 7718 + // https://issues.dlang.org/show_bug.cgi?id=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)), @@ -231,8 +231,8 @@ import std.regex.internal.ir : Escapables; // characters that need escaping { import std.uni : toUpper; - foreach (i, v; AliasSeq!(string, wstring, dstring)) - { + static foreach (i, v; AliasSeq!(string, wstring, dstring)) + {{ auto baz(Cap)(Cap m) if (is(Cap == Captures!(Cap.String))) { @@ -251,7 +251,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping 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)(); @@ -293,24 +293,29 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(equal(split(s1, regex(", *")), w1[])); } +// https://issues.dlang.org/show_bug.cgi?id=7141 @safe unittest -{ // bugzilla 7141 +{ string pattern = `[a\--b]`; assert(match("-", pattern)); assert(match("b", pattern)); string pattern2 = `[&-z]`; assert(match("b", pattern2)); } + +// https://issues.dlang.org/show_bug.cgi?id=7111 @safe unittest -{//bugzilla 7111 +{ assert(match("", regex("^"))); } + +// https://issues.dlang.org/show_bug.cgi?id=7300 @safe unittest -{//bugzilla 7300 +{ assert(!match("a"d, "aa"d)); } -// bugzilla 7551 +// https://issues.dlang.org/show_bug.cgi?id=7551 @safe unittest { auto r = regex("[]abc]*"); @@ -320,25 +325,30 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert("]ac".matchFirst(r2).hit == "]"); } +// https://issues.dlang.org/show_bug.cgi?id=7674 @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\?"); } + +// https://issues.dlang.org/show_bug.cgi?id=7679 @safe unittest -{// bugzilla 7679 +{ import std.algorithm.comparison : equal; - foreach (S; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ 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")]); - }(); + }} } + +// https://issues.dlang.org/show_bug.cgi?id=8203 @safe unittest -{//bugzilla 8203 +{ string data = " NAME = XPAW01_STA:STATION NAME = XPAW01_STA @@ -353,13 +363,15 @@ import std.regex.internal.ir : Escapables; // characters that need escaping auto r2 = regex(`([а-яА-Я\-_]+\s*)+(?<=[\s\.,\^])`); match("аллея Театральная", r2); } + +// https://issues.dlang.org/show_bug.cgi?id=8637 purity of enforce @safe unittest -{// bugzilla 8637 purity of enforce +{ auto m = match("hello world", regex("world")); enforce(m); } -// bugzilla 8725 +// https://issues.dlang.org/show_bug.cgi?id=8725 @safe unittest { static italic = regex( r"\* @@ -372,7 +384,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping "this * is* interesting, very interesting"); } -// bugzilla 8349 +// https://issues.dlang.org/show_bug.cgi?id=8349 @safe unittest { enum peakRegexStr = r"\>(wgEncode.*Tfbs.*\.(?:narrow)|(?:broad)Peak.gz)"; @@ -381,7 +393,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(match(r"\>wgEncode-blah-Tfbs.narrow", peakRegex)); } -// bugzilla 9211 +// https://issues.dlang.org/show_bug.cgi?id=9211 @safe unittest { import std.algorithm.comparison : equal; @@ -393,7 +405,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(equal(m2.front, ["1234", "3", "4"])); } -// bugzilla 9280 +// https://issues.dlang.org/show_bug.cgi?id=9280 @safe unittest { string tomatch = "a!b@c"; @@ -406,7 +418,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping } -// bugzilla 9579 +// https://issues.dlang.org/show_bug.cgi?id=9579 @safe unittest { char[] input = ['a', 'b', 'c']; @@ -417,14 +429,14 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(r == "(a)bc"); } -// bugzilla 9634 +// https://issues.dlang.org/show_bug.cgi?id=9634 @safe unittest { auto re = ctRegex!"(?:a+)"; assert(match("aaaa", re).hit == "aaaa"); } -//bugzilla 10798 +// https://issues.dlang.org/show_bug.cgi?id=10798 @safe unittest { auto cr = ctRegex!("[abcd--c]*"); @@ -433,7 +445,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(m.hit == "ab"); } -// bugzilla 10913 +// https://issues.dlang.org/show_bug.cgi?id=10913 @system unittest { @system static string foo(const(char)[] s) @@ -452,7 +464,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping }(); } -// bugzilla 11262 +// https://issues.dlang.org/show_bug.cgi?id=11262 @safe unittest { enum reg = ctRegex!(r",", "g"); @@ -461,13 +473,13 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(str == "This-List"); } -// bugzilla 11775 +// https://issues.dlang.org/show_bug.cgi?id=11775 @safe unittest { assert(collectException(regex("a{1,0}"))); } -// bugzilla 11839 +// https://issues.dlang.org/show_bug.cgi?id=11839 @safe unittest { import std.algorithm.comparison : equal; @@ -478,7 +490,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(regex(`(?P<я>\w+)`).namedCaptures.equal(["я"])); } -// bugzilla 12076 +// https://issues.dlang.org/show_bug.cgi?id=12076 @safe unittest { auto RE = ctRegex!(r"(?abc)`); assert(collectException("abc".matchFirst(r)["b"])); } -// bugzilla 12691 +// https://issues.dlang.org/show_bug.cgi?id=12691 @safe unittest { assert(bmatch("e@", "^([a-z]|)*$").empty); assert(bmatch("e@", ctRegex!`^([a-z]|)*$`).empty); } -//bugzilla 12713 +// https://issues.dlang.org/show_bug.cgi?id=12713 @safe unittest { assertThrown(regex("[[a-z]([a-z]|(([[a-z])))")); } -//bugzilla 12747 +// https://issues.dlang.org/show_bug.cgi?id=12747 @safe unittest { assertThrown(regex(`^x(\1)`)); @@ -538,14 +550,44 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assertThrown(regex(`^((x)(?=\1))`)); } -// bugzilla 14504 +// https://issues.dlang.org/show_bug.cgi?id=13532 +version (none) // TODO: revist once we have proper benchmark framework +@safe unittest +{ + import std.datetime.stopwatch : StopWatch, AutoStart; + import std.math.algebraic : abs; + import std.conv : to; + enum re1 = ctRegex!`[0-9][0-9]`; + immutable static re2 = ctRegex!`[0-9][0-9]`; + immutable iterations = 1_000_000; + size_t result1 = 0, result2 = 0; + auto sw = StopWatch(AutoStart.yes); + foreach (_; 0 .. iterations) + { + result1 += matchFirst("12345678", re1).length; + } + const staticTime = sw.peek(); + sw.reset(); + foreach (_; 0 .. iterations) + { + result2 += matchFirst("12345678", re2).length; + } + const enumTime = sw.peek(); + assert(result1 == result2); + auto ratio = 1.0 * enumTime.total!"usecs" / staticTime.total!"usecs"; + // enum is faster or the diff is less < 30% + assert(ratio < 1.0 || abs(ratio - 1.0) < 0.75, + "enum regex to static regex ratio "~to!string(ratio)); +} + +// https://issues.dlang.org/show_bug.cgi?id=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 +// https://issues.dlang.org/show_bug.cgi?id=14529 @safe unittest { auto ctPat2 = regex(r"^[CDF]$", "i"); @@ -553,7 +595,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(matchAll(v, ctPat2).front.hit == v); } -// bugzilla 14615 +// https://issues.dlang.org/show_bug.cgi?id=14615 @safe unittest { import std.array : appender; @@ -572,14 +614,14 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(sink.data == "Hello, world!Hello, world!"); } -// bugzilla 15573 +// https://issues.dlang.org/show_bug.cgi?id=15573 @safe unittest { auto rx = regex("[c d]", "x"); assert("a b".matchFirst(rx)); } -// bugzilla 15864 +// https://issues.dlang.org/show_bug.cgi?id=15864 @safe unittest { regex(`((.+)`; @@ -601,14 +643,14 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(input.matchFirst(titleRegex).empty); } -// bugzilla 17212 +// https://issues.dlang.org/show_bug.cgi?id=17212 @safe unittest { auto r = regex(" [a] ", "x"); assert("a".matchFirst(r)); } -// bugzilla 17157 +// https://issues.dlang.org/show_bug.cgi?id=17157 @safe unittest { import std.algorithm.comparison : equal; @@ -625,7 +667,7 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(equal!equal(s.bmatch(r), outcomes)); } -// bugzilla 17667 +// https://issues.dlang.org/show_bug.cgi?id=17667 @safe unittest { import std.algorithm.searching : canFind; @@ -637,19 +679,19 @@ import std.regex.internal.ir : Escapables; // characters that need escaping 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"[a-\", r"123"], "no matching ']' found while parsing character class"); willThrow([r"\", r"123"], "invalid escape sequence"); } -// bugzilla 17668 +// https://issues.dlang.org/show_bug.cgi?id=17668 @safe unittest { import std.algorithm.searching; auto e = collectException!RegexException(regex(q"<[^]>")); - assert(e.msg.canFind("no operand for '^'")); + assert(e.msg.canFind("no operand for '^'"), e.msg); } -// bugzilla 17673 +// https://issues.dlang.org/show_bug.cgi?id=17673 @safe unittest { string str = `<">`; @@ -660,3 +702,12 @@ import std.regex.internal.ir : Escapables; // characters that need escaping assert(c.whichPattern == 2); } +// https://issues.dlang.org/show_bug.cgi?id=18692 +@safe unittest +{ + auto rx = regex("()()()"); + auto ma = "".matchFirst(rx); + auto ma2 = ma; + ma = ma2; + assert(ma[1] == ""); +} diff --git a/libphobos/src/std/regex/internal/thompson.d b/libphobos/src/std/regex/internal/thompson.d index 4d7deaa..ab28d37 100644 --- a/libphobos/src/std/regex/internal/thompson.d +++ b/libphobos/src/std/regex/internal/thompson.d @@ -70,7 +70,7 @@ struct ThreadList(DataIndex) this(ThreadList tlist){ ct = tlist.tip; } @property bool empty(){ return ct is null; } @property const(Thread!DataIndex)* front(){ return ct; } - @property popFront() + void popFront() { assert(ct); ct = ct.next; @@ -89,7 +89,7 @@ struct ThreadList(DataIndex) template ThompsonOps(E, S, bool withInput:true) { @trusted: - static bool op(IR code:IR.End)(E* e, S* state) + static bool op(IR code:IR.End)(E e, S* state) { with(e) with(state) { @@ -105,7 +105,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Wordboundary)(E* e, S* state) + static bool op(IR code:IR.Wordboundary)(E e, S* state) { with(e) with(state) { @@ -137,7 +137,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Notwordboundary)(E* e, S* state) + static bool op(IR code:IR.Notwordboundary)(E e, S* state) { with(e) with(state) { @@ -167,7 +167,7 @@ template ThompsonOps(E, S, bool withInput:true) return true; } - static bool op(IR code:IR.Bof)(E* e, S* state) + static bool op(IR code:IR.Bof)(E e, S* state) { with(e) with(state) { @@ -183,7 +183,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Bol)(E* e, S* state) + static bool op(IR code:IR.Bol)(E e, S* state) { with(e) with(state) { @@ -203,7 +203,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Eof)(E* e, S* state) + static bool op(IR code:IR.Eof)(E e, S* state) { with(e) with(state) { @@ -219,7 +219,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Eol)(E* e, S* state) + static bool op(IR code:IR.Eol)(E e, S* state) { with(e) with(state) { @@ -240,42 +240,42 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.InfiniteStart)(E* e, S* state) + 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) + 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) + 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) + 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) + 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) + static bool op(IR code)(E e, S* state) if (code == IR.RepeatEnd || code == IR.RepeatQEnd) { with(e) with(state) @@ -330,7 +330,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code)(E* e, S* state) + static bool op(IR code)(E e, S* state) if (code == IR.InfiniteEnd || code == IR.InfiniteQEnd) { with(e) with(state) @@ -365,7 +365,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code)(E* e, S* state) + static bool op(IR code)(E e, S* state) if (code == IR.InfiniteBloomEnd) { with(e) with(state) @@ -394,7 +394,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.OrEnd)(E* e, S* state) + static bool op(IR code:IR.OrEnd)(E e, S* state) { with(e) with(state) { @@ -415,7 +415,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.OrStart)(E* e, S* state) + static bool op(IR code:IR.OrStart)(E e, S* state) { with(e) with(state) { @@ -424,7 +424,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Option)(E* e, S* state) + static bool op(IR code:IR.Option)(E e, S* state) { with(e) with(state) { @@ -439,7 +439,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.GotoEndOr)(E* e, S* state) + static bool op(IR code:IR.GotoEndOr)(E e, S* state) { with(e) with(state) { @@ -448,7 +448,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.GroupStart)(E* e, S* state) + static bool op(IR code:IR.GroupStart)(E e, S* state) { with(e) with(state) { @@ -458,7 +458,7 @@ template ThompsonOps(E, S, bool withInput:true) return true; } } - static bool op(IR code:IR.GroupEnd)(E* e, S* state) + static bool op(IR code:IR.GroupEnd)(E e, S* state) { with(e) with(state) { @@ -469,7 +469,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Backref)(E* e, S* state) + static bool op(IR code:IR.Backref)(E e, S* state) { with(e) with(state) { @@ -506,7 +506,7 @@ template ThompsonOps(E, S, bool withInput:true) } - static bool op(IR code)(E* e, S* state) + static bool op(IR code)(E e, S* state) if (code == IR.LookbehindStart || code == IR.NeglookbehindStart) { with(e) with(state) @@ -516,10 +516,9 @@ template ThompsonOps(E, S, bool withInput:true) 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)); + auto matcher = fwdMatcher(t.pc, end, me - ms, subCounters.get(t.pc, 0)); else - auto matcher = bwdMatcher(t.pc, end, subCounters.get(t.pc, 0)); - matcher.re.ngroup = me - ms; + auto matcher = bwdMatcher(t.pc, end, me - ms, subCounters.get(t.pc, 0)); matcher.backrefed = backrefed.empty ? t.matches : backrefed; //backMatch auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookbehindStart)); @@ -534,7 +533,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code)(E* e, S* state) + static bool op(IR code)(E e, S* state) if (code == IR.LookaheadStart || code == IR.NeglookaheadStart) { with(e) with(state) @@ -545,10 +544,9 @@ template ThompsonOps(E, S, bool withInput:true) 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)); + auto matcher = bwdMatcher(t.pc, end, me - ms, subCounters.get(t.pc, 0)); else - auto matcher = fwdMatcher(t.pc, end, subCounters.get(t.pc, 0)); - matcher.re.ngroup = me - ms; + auto matcher = fwdMatcher(t.pc, end, me - ms, subCounters.get(t.pc, 0)); matcher.backrefed = backrefed.empty ? t.matches : backrefed; auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookaheadStart)); freelist = matcher.freelist; @@ -564,7 +562,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code)(E* e, S* state) + static bool op(IR code)(E e, S* state) if (code == IR.LookaheadEnd || code == IR.NeglookaheadEnd || code == IR.LookbehindEnd || code == IR.NeglookbehindEnd) { @@ -579,13 +577,13 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Nop)(E* e, S* 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) + static bool op(IR code:IR.OrChar)(E e, S* state) { with(e) with(state) { @@ -607,7 +605,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Char)(E* e, S* state) + static bool op(IR code:IR.Char)(E e, S* state) { with(e) with(state) { @@ -623,7 +621,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Any)(E* e, S* state) + static bool op(IR code:IR.Any)(E e, S* state) { with(e) with(state) { @@ -634,7 +632,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.CodepointSet)(E* e, S* state) + static bool op(IR code:IR.CodepointSet)(E e, S* state) { with(e) with(state) { @@ -652,7 +650,7 @@ template ThompsonOps(E, S, bool withInput:true) } } - static bool op(IR code:IR.Trie)(E* e, S* state) + static bool op(IR code:IR.Trie)(E e, S* state) { with(e) with(state) { @@ -676,7 +674,7 @@ template ThompsonOps(E,S, bool withInput:false) { @trusted: // can't match these without input - static bool op(IR code)(E* e, S* state) + 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) { @@ -684,7 +682,7 @@ template ThompsonOps(E,S, bool withInput:false) } // special case of zero-width backref - static bool op(IR code:IR.Backref)(E* e, S* state) + static bool op(IR code:IR.Backref)(E e, S* state) { with(e) with(state) { @@ -702,7 +700,7 @@ template ThompsonOps(E,S, bool withInput:false) } // forward all control flow to normal versions - static bool op(IR code)(E* e, S* state) + 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) { @@ -714,19 +712,19 @@ template ThompsonOps(E,S, bool withInput:false) Thomspon matcher does all matching in lockstep, never looking at the same char twice +/ -@trusted struct ThompsonMatcher(Char, StreamType = Input!Char) +@trusted class ThompsonMatcher(Char, StreamType = Input!Char): Matcher!Char if (is(Char : dchar)) { alias DataIndex = Stream.DataIndex; alias Stream = StreamType; - alias OpFunc = bool function(ThompsonMatcher*, State*); + alias OpFunc = bool function(ThompsonMatcher, State*) pure; alias BackMatcher = ThompsonMatcher!(Char, BackLooper!(Stream)); - alias OpBackFunc = bool function(BackMatcher*, BackMatcher.State*); + alias OpBackFunc = bool function(BackMatcher, BackMatcher.State*) pure; Thread!DataIndex* freelist; ThreadList!DataIndex clist, nlist; DataIndex[] merge; Group!DataIndex[] backrefed; - Regex!Char re; //regex program + const Regex!Char re; //regex program Stream s; dchar front; DataIndex index; @@ -737,16 +735,19 @@ if (is(Char : dchar)) OpBackFunc[] opCacheBackTrue; // ditto OpBackFunc[] opCacheBackFalse; // ditto size_t threadSize; + size_t _refCount; int matched; bool exhausted; +final: + pure static struct State { Thread!DataIndex* t; ThreadList!DataIndex worklist; Group!DataIndex[] matches; - bool popState(E)(E* e) + bool popState(E)(E e) { with(e) { @@ -784,6 +785,10 @@ if (is(Char : dchar)) //true if it's end of input @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } + override @property ref size_t refCount() @safe { return _refCount; } + + override @property ref const(Regex!Char) pattern() @safe { return re; } + bool next() { if (!s.nextChar(front, index)) @@ -843,19 +848,36 @@ if (is(Char : dchar)) } } - this()(Regex!Char program, Stream stream, void[] memory) + override Matcher!Char rearm(in Char[] data) + { + exhausted = false; + matched = 0; + s = Stream(data); + return this; + } + + this()(ref const Regex!Char program, Stream stream, void[] memory) { + // We are emplace'd to malloced memory w/o blitting T.init over it\ + // make sure we initialize all fields explicitly + _refCount = 1; + subCounters = null; + backrefed = null; + exhausted = false; + matched = 0; re = program; s = stream; initExternalMemory(memory); genCounter = 0; } - this(ref ThompsonMatcher matcher, size_t lo, size_t hi, Stream stream) + this(ThompsonMatcher matcher, size_t lo, size_t hi, uint nGroup, Stream stream) { + _refCount = 1; + subCounters = matcher.subCounters; s = stream; - re = matcher.re; - re.ir = re.ir[lo .. hi]; + auto code = matcher.re.ir[lo .. hi]; + re = matcher.re.withCode(code).withNGroup(nGroup); threadSize = matcher.threadSize; merge = matcher.merge; freelist = matcher.freelist; @@ -867,11 +889,13 @@ if (is(Char : dchar)) index = matcher.index; } - this(ref BackMatcher matcher, size_t lo, size_t hi, Stream stream) + this(BackMatcher matcher, size_t lo, size_t hi, uint nGroup, Stream stream) { + _refCount = 1; + subCounters = matcher.subCounters; s = stream; - re = matcher.re; - re.ir = re.ir[lo .. hi]; + auto code = matcher.re.ir[lo .. hi]; + re = matcher.re.withCode(code).withNGroup(nGroup); threadSize = matcher.threadSize; merge = matcher.merge; freelist = matcher.freelist; @@ -883,31 +907,35 @@ if (is(Char : dchar)) index = matcher.index; } - auto fwdMatcher()(size_t lo, size_t hi, size_t counter) + auto fwdMatcher()(size_t lo, size_t hi, uint nGroup, size_t counter) { - auto m = ThompsonMatcher!(Char, Stream)(this, lo, hi, s); + auto m = new ThompsonMatcher!(Char, Stream)(this, lo, hi, nGroup, s); m.genCounter = counter; return m; } - auto bwdMatcher()(size_t lo, size_t hi, size_t counter) + auto bwdMatcher()(size_t lo, size_t hi, uint nGroup, size_t counter) { alias BackLooper = typeof(s.loopBack(index)); - auto m = ThompsonMatcher!(Char, BackLooper)(this, lo, hi, s.loopBack(index)); + auto m = new ThompsonMatcher!(Char, BackLooper)(this, lo, hi, nGroup, s.loopBack(index)); m.genCounter = counter; m.next(); return m; } - auto dupTo(void[] memory) + override void dupTo(Matcher!Char engine, void[] memory) { - typeof(this) tmp = this;//bitblit - tmp.initExternalMemory(memory); - tmp.genCounter = 0; - return tmp; + auto thompson = cast(ThompsonMatcher) engine; + thompson.s = s; + thompson.subCounters = null; + thompson.front = front; + thompson.index = index; + thompson.matched = matched; + thompson.exhausted = exhausted; + thompson.initExternalMemory(memory); } - int match(Group!DataIndex[] matches) + override int match(Group!DataIndex[] matches) { debug(std_regex_matcher) writeln("------------------------------------------"); @@ -1052,9 +1080,9 @@ if (is(Char : dchar)) { debug(std_regex_matcher) writeln("---- Evaluating thread"); static if (withInput) - while (opCacheTrue.ptr[state.t.pc](&this, state)){} + while (opCacheTrue.ptr[state.t.pc](this, state)){} else - while (opCacheFalse.ptr[state.t.pc](&this, state)){} + while (opCacheFalse.ptr[state.t.pc](this, state)){} } enum uint RestartPc = uint.max; //match the input, evaluating IR without searching diff --git a/libphobos/src/std/regex/package.d b/libphobos/src/std/regex/package.d index bfc7d7f..82207b6 100644 --- a/libphobos/src/std/regex/package.d +++ b/libphobos/src/std/regex/package.d @@ -7,6 +7,7 @@ in text processing utilities. $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD Matching) $(TD @@ -18,7 +19,7 @@ $(TR $(TD Matching) $(TD $(TR $(TD Building) $(TD $(LREF ctRegex) $(LREF escaper) - $(LREF _regex) + $(LREF regex) )) $(TR $(TD Replace) $(TD $(LREF replace) @@ -39,7 +40,7 @@ $(TR $(TD Objects) $(TD $(LREF Splitter) $(LREF StaticRegex) )) -) +)) $(SECTION Synopsis) --- @@ -92,7 +93,7 @@ $(TR $(TD Objects) $(TD 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 + For an introduction to `std.regex` see a $(HTTP dlang.org/regular-expression.html, short tour) of the module API and its abilities. @@ -136,7 +137,7 @@ $(TR $(TD Objects) $(TD $(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 \\\\, 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. @@ -208,7 +209,8 @@ $(TR $(TD Objects) $(TD $(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 Any atom, Has the same meaning as outside of a character class, + except for ] which must be written as \\]) $(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, @@ -250,25 +252,25 @@ $(TR $(TD Objects) $(TD 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 + applies to the `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)$(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 \\\\, '\\' 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) + are slices of the original input. The notable exception is the `replace` family of functions that generate a new string from the input. In cases where producing the replacement is the ultimate goal @@ -281,10 +283,10 @@ $(TR $(TD Objects) $(TD Authors: Dmitry Olshansky, - API and utility constructs are modeled after the original $(D std.regex) + API and utility constructs are modeled after the original `std.regex` by Walter Bright and Andrei Alexandrescu. - Source: $(PHOBOSSRC std/_regex/_package.d) + Source: $(PHOBOSSRC std/regex/package.d) Macros: REG_ROW = $(TR $(TD $(I $1 )) $(TD $+) ) @@ -298,13 +300,12 @@ 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; +import std.typecons : Flag, Yes, No; /++ - $(D Regex) object holds regular expression pattern in compiled form. + `Regex` object holds regular expression pattern in compiled form. - Instances of this object are constructed via calls to $(D regex). + Instances of this object are constructed via calls to `regex`. This is an intended form for caching and storage of frequently used regular expressions. @@ -336,35 +337,34 @@ import std.typecons; // : Flag, Yes, No; public alias Regex(Char) = std.regex.internal.ir.Regex!(Char); /++ - A $(D StaticRegex) is $(D Regex) object that contains D code specially + A `StaticRegex` is `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. + No longer used, kept as alias to Regex for backwards compatibility. +/ -public alias StaticRegex(Char) = std.regex.internal.ir.StaticRegex!(Char); +public alias StaticRegex = Regex; /++ Compile regular expression pattern for the later execution. - Returns: $(D Regex) object that works on inputs having - the same character width as $(D pattern). + Returns: `Regex` object that works on inputs having + the same character width as `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) + flags = The _attributes (g, i, m, s and x accepted) - Throws: $(D RegexException) if there were any errors during compilation. + Throws: `RegexException` if there were any errors during compilation. +/ -@trusted public auto regex(S)(S[] patterns, const(char)[] flags="") +@trusted public auto regex(S : C[], C)(const 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; + const(C)[] pat; if (patterns.length > 1) { auto app = appender!S(); @@ -404,19 +404,42 @@ if (isSomeString!(S)) /// @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"); + void test(S)() + { + // multi-pattern regex example + S[] arr = [`([a-z]+):(\d+)`, `(\d+),\d+`]; + auto multi = regex(arr); // multi regex + S str = "abc:43 12,34"; + auto m = str.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"); + } + + import std.meta : AliasSeq; + static foreach (C; AliasSeq!(string, wstring, dstring)) + // Test with const array of patterns - see https://issues.dlang.org/show_bug.cgi?id=20301 + static foreach (S; AliasSeq!(C, const C, immutable C)) + test!S(); } -public auto regexImpl(S)(S pattern, const(char)[] flags="") -if (isSomeString!(S)) +@system unittest +{ + import std.conv : to; + import std.string : indexOf; + + immutable pattern = "s+"; + auto regexString = to!string(regex(pattern, "U")); + assert(regexString.length <= pattern.length + 100, "String representation shouldn't be unreasonably bloated."); + assert(indexOf(regexString, "s+") >= 0, "String representation should include pattern."); + assert(indexOf(regexString, 'U') >= 0, "String representation should include flags."); +} + +public auto regexImpl(S)(const S pattern, const(char)[] flags="") +if (isSomeString!(typeof(pattern))) { import std.regex.internal.parser : Parser, CodeGen; auto parser = Parser!(Unqual!(typeof(pattern)), CodeGen)(pattern, flags); @@ -425,19 +448,52 @@ if (isSomeString!(S)) } +private struct CTRegexWrapper(Char) +{ + private immutable(Regex!Char)* re; + + // allow code that expects mutable Regex to still work + // we stay "logically const" + @property @trusted ref getRe() const { return *cast(Regex!Char*) re; } + alias getRe this; +} + template ctRegexImpl(alias pattern, string flags=[]) { import std.regex.internal.backtracking, std.regex.internal.parser; - enum r = regex(pattern, flags); + static immutable r = cast(immutable) regex(pattern, flags); alias Char = BasicElementOf!(typeof(pattern)); enum source = ctGenRegExCode(r); - alias Matcher = BacktrackingMatcher!(true); - @trusted bool func(ref Matcher!Char matcher) + @trusted pure bool func(BacktrackingMatcher!Char matcher) { debug(std_regex_ctr) pragma(msg, source); + cast(void) matcher; mixin(source); } - enum nr = StaticRegex!Char(r, &func); + static immutable staticRe = + cast(immutable) r.withFactory(new CtfeFactory!(BacktrackingMatcher, Char, func)); + enum wrapper = CTRegexWrapper!Char(&staticRe); +} + +@safe pure unittest +{ + // test compat for logical const workaround + static void test(StaticRegex!char) + { + } + enum re = ctRegex!``; + test(re); +} + +@safe pure unittest +{ + auto re = ctRegex!`foo`; + assert(matchFirst("foo", re)); + + // test reassignment + re = ctRegex!`bar`; + assert(matchFirst("bar", re)); + assert(!matchFirst("bar", ctRegex!`foo`)); } /++ @@ -448,108 +504,61 @@ template ctRegexImpl(alias pattern, string flags=[]) Params: pattern = Regular expression - flags = The _attributes (g, i, m and x accepted) + flags = The _attributes (g, i, m, s and x accepted) +/ -public enum ctRegex(alias pattern, alias flags=[]) = ctRegexImpl!(pattern, flags).nr; +public enum ctRegex(alias pattern, alias flags=[]) = ctRegexImpl!(pattern, flags).wrapper; -enum isRegexFor(RegEx, R) = is(RegEx == Regex!(BasicElementOf!R)) - || is(RegEx == StaticRegex!(BasicElementOf!R)); +enum isRegexFor(RegEx, R) = is(immutable RegEx == immutable Regex!(BasicElementOf!R)) + || is(RegEx : const(Regex!(BasicElementOf!R))) + || is(immutable RegEx == immutable StaticRegex!(BasicElementOf!R)); /++ - $(D Captures) object contains submatches captured during a call - to $(D match) or iteration over $(D RegexMatch) range. + `Captures` object contains submatches captured during a call + to `match` or iteration over `RegexMatch` range. First element of range is the whole match. +/ -@trusted public struct Captures(R, DIndex = size_t) +@trusted public struct Captures(R) if (isSomeString!R) {//@trusted because of union inside - alias DataIndex = DIndex; + alias DataIndex = size_t; alias String = R; + alias Store = SmallFixedArray!(Group!DataIndex, 3); private: import std.conv : text; + Store matches; + const(NamedGroup)[] _names; 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) + this(R input, uint n, const(NamedGroup)[] named) { _input = input; _names = named; - newMatches(n); + matches = Store(n); _b = n; _f = 0; } - this(alias Engine)(ref RegexMatch!(R,Engine) rmatch) + this(ref RegexMatch!R rmatch) { _input = rmatch._input; - _names = rmatch._engine.re.dict; - immutable n = rmatch._engine.re.ngroup; - newMatches(n); + _names = rmatch._engine.pattern.dict; + immutable n = rmatch._engine.pattern.ngroup; + matches = Store(n); _b = n; _f = 0; } - @property inout(Group!DataIndex[]) matches() inout + inout(R) getMatch(size_t index) 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; + auto m = &matches[index]; + return *m ? _input[m.begin .. m.end] : null; } 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() { @@ -573,14 +582,14 @@ public: @property R front() { assert(_nMatch, "attempted to get front of an empty match"); - return _input[matches[_f].begin .. matches[_f].end]; + return getMatch(_f); } ///ditto @property R back() { assert(_nMatch, "attempted to get back of an empty match"); - return _input[matches[_b - 1].begin .. matches[_b - 1].end]; + return getMatch(_b - 1); } ///ditto @@ -604,9 +613,7 @@ public: 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]; + return getMatch(_f + i); } /++ @@ -656,7 +663,7 @@ public: if (isSomeString!String) { size_t index = lookupNamedGroup(_names, i); - return _input[matches[index].begin .. matches[index].end]; + return getMatch(index); } ///Number of matches in this object. @@ -686,65 +693,69 @@ public: assert(c.empty); assert(!matchFirst("nothing", "something")); + + // Captures that are not matched will be null. + c = matchFirst("ac", regex(`a(b)?c`)); + assert(c); + assert(!c[1]); +} + +@system unittest +{ + Captures!string c; + string s = "abc"; + assert(cast(bool)(c = matchFirst(s, regex("d"))) + || cast(bool)(c = matchFirst(s, regex("a")))); +} + +// https://issues.dlang.org/show_bug.cgi?id=19979 +@system unittest +{ + auto c = matchFirst("bad", regex(`(^)(not )?bad($)`)); + assert(c[0] && c[0].length == "bad".length); + assert(c[1] && !c[1].length); + assert(!c[2]); + assert(c[3] && !c[3].length); } /++ - A regex engine state, as returned by $(D match) family of functions. + A regex engine state, as returned by `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) +@trusted public struct RegexMatch(R) if (isSomeString!R) { + import std.typecons : Rebindable; private: - import core.stdc.stdlib : malloc, free; alias Char = BasicElementOf!R; - alias EngineType = Engine!Char; - EngineType _engine; + Matcher!Char _engine; + Rebindable!(const MatcherFactory!Char) _factory; R _input; - Captures!(R,EngineType.DataIndex) _captures; - void[] _memory;//is ref-counted + Captures!R _captures; 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); + if (prog.factory is null) _factory = defaultFactory!Char(prog); + else _factory = prog.factory; + _engine = _factory.create(prog, input); + assert(_engine.refCount == 1); + _captures = Captures!R(this); + _captures.matches.mutate((slice) pure { _captures._nMatch = _engine.match(slice); }); } - @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); - } + if (_engine) _factory.incRef(_engine); } ~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); - } + if (_engine) _factory.decRef(_engine); } ///Shorthands for front.pre, front.post, front.hit. @@ -777,7 +788,7 @@ public: assert(m.empty); --- +/ - @property auto front() + @property inout(Captures!R) front() inout { return _captures; } @@ -786,21 +797,15 @@ public: 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) + // CoW - if refCount is not 1, we are aliased by somebody else + if (_engine.refCount != 1) { - // has external references - allocate new space - _captures.newMatches(_engine.re.ngroup); + // we create a new engine & abandon this reference + auto old = _engine; + _engine = _factory.dup(old, _input); + _factory.decRef(old); } - _captures._nMatch = _engine.match(_captures.matches); + _captures.matches.mutate((slice) { _captures._nMatch = _engine.match(slice); }); } ///ditto @@ -813,42 +818,79 @@ public: T opCast(T:bool)(){ return !empty; } /// Same as .front, provided for compatibility with original std.regex. - @property auto captures() inout { return _captures; } - + @property inout(Captures!R) captures() inout { return _captures; } } -private @trusted auto matchOnce(alias Engine, RegEx, R)(R input, RegEx re) +private auto matchOnceImpl(RegEx, R)(R input, const auto ref RegEx prog) @trusted { - 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); + static struct Key + { + immutable(Char)[] pattern; + uint flags; + } + static Key cacheKey = Key("", -1); + static Matcher!Char cache; + auto factory = prog.factory is null ? defaultFactory!Char(prog) : prog.factory; + auto key = Key(prog.pattern, prog.flags); + Matcher!Char engine; + if (cacheKey == key) + { + engine = cache; + engine.rearm(input); + } + else + { + engine = factory.create(prog, input); + if (cache) factory.decRef(cache); // destroy cached engine *after* building a new one + cache = engine; + cacheKey = key; + } + auto captures = Captures!R(input, prog.ngroup, prog.dict); + captures.matches.mutate((slice) pure { captures._nMatch = engine.match(slice); }); return captures; } -private auto matchMany(alias Engine, RegEx, R)(R input, RegEx re) +// matchOnce is constructed as a safe, pure wrapper over matchOnceImpl. It can be +// faked as pure because the static mutable variables are used to cache the key and +// character matcher. The technique used avoids delegates and GC. +private @safe auto matchOnce(RegEx, R)(R input, const auto ref RegEx prog) pure +{ + static auto impl(R input, const ref RegEx prog) + { + return matchOnceImpl(input, prog); + } + + static @trusted auto pureImpl(R input, const ref RegEx prog) + { + auto p = assumePureFunction(&impl); + return p(input, prog); + } + + return pureImpl(input, prog); +} + +private auto matchMany(RegEx, R)(R input, auto ref RegEx re) @safe { - re.flags |= RegexOption.global; - return RegexMatch!(R, Engine)(input, re); + return RegexMatch!R(input, re.withFlags(re.flags | RegexOption.global)); } @system unittest { //sanity checks for new API auto re = regex("abc"); - assert(!"abc".matchOnce!(ThompsonMatcher)(re).empty); - assert("abc".matchOnce!(ThompsonMatcher)(re)[0] == "abc"); + assert(!"abc".matchOnce(re).empty); + assert("abc".matchOnce(re)[0] == "abc"); } +// https://issues.dlang.org/show_bug.cgi?id=18135 +@system unittest +{ + static struct MapResult { RegexMatch!string m; } + MapResult m; + m = MapResult(); + assert(m == m); +} private enum isReplaceFunctor(alias fun, R) = __traits(compiles, (Captures!R c) { fun(c); }); @@ -922,7 +964,7 @@ if (isSomeString!R && isRegexFor!(RegEx, R)) /++ - Start matching $(D input) to regex pattern $(D re), + Start matching `input` to regex pattern `re`, using Thompson NFA matching scheme. The use of this function is $(RED discouraged) - use either of @@ -934,37 +976,28 @@ if (isSomeString!R && isRegexFor!(RegEx, R)) 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. + Returns: a `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))) +if (isSomeString!R && isRegexFor!(RegEx,R)) { - import std.regex.internal.thompson : ThompsonMatcher; - return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, re); + return RegexMatch!(Unqual!(typeof(input)))(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); + return RegexMatch!(Unqual!(typeof(input)))(input, regex(re)); } /++ - Find the first (leftmost) slice of the $(D input) that - matches the pattern $(D re). This function picks the most suitable + Find the first (leftmost) slice of the `input` that + matches the pattern `re`. This function picks the most suitable regular expression engine depending on the pattern properties. - $(D re) parameter can be one of three types: + `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 @@ -978,44 +1011,34 @@ if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) 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))) +if (isSomeString!R && isRegexFor!(RegEx, R)) { - import std.regex.internal.thompson : ThompsonMatcher; - return matchOnce!ThompsonMatcher(input, re); + return matchOnce(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)); + return matchOnce(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); + return matchOnce(input, regex(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 + Initiate a search for all non-overlapping matches to the pattern `re` + in the given `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: + `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 @@ -1029,33 +1052,23 @@ if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) 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))) +if (isSomeString!R && isRegexFor!(RegEx, R)) { - import std.regex.internal.thompson : ThompsonMatcher; - return matchMany!ThompsonMatcher(input, re); + return matchMany(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)); + return matchMany(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); + return matchMany(input, regex(re)); } // another set of tests just to cover the new API @@ -1065,8 +1078,8 @@ if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) import std.algorithm.iteration : map; import std.conv : to; - foreach (String; AliasSeq!(string, wstring, const(dchar)[])) - { + static 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); @@ -1097,11 +1110,11 @@ if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) 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), + Start matching of `input` to regex pattern `re`, using traditional $(LINK2 https://en.wikipedia.org/wiki/Backtracking, backtracking) matching scheme. @@ -1114,30 +1127,21 @@ if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) 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 + Returns: a `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))) +if (isSomeString!R && isRegexFor!(RegEx, R)) { - import std.regex.internal.backtracking : BacktrackingMatcher; - return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, re); + return RegexMatch!(Unqual!(typeof(input)))(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); + return RegexMatch!(Unqual!(typeof(input)))(input, regex(re)); } // produces replacement string from format using captures for substitution @@ -1215,8 +1219,8 @@ L_Replace_Loop: } /++ - Construct a new string from $(D input) by replacing the first match with - a string generated from it according to the $(D format) specifier. + Construct a new string from `input` by replacing the first match with + a string generated from it according to the `format` specifier. To replace all matches use $(LREF replaceAll). @@ -1244,18 +1248,18 @@ if (isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) /++ 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 + matches of pattern `re` in the `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 a user-defined functor `fun` that returns a new string to use as replacement. - This version replaces the first match in $(D input), + This version replaces the first match in `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. + A new string of the same type as `input` with all matches + replaced by return values of `fun`. If no matches found + returns the `input` itself. +/ public R replaceFirst(alias fun, R, RegEx)(R input, RegEx re) if (isSomeString!R && isRegexFor!(RegEx, R)) @@ -1275,11 +1279,11 @@ if (isSomeString!R && isRegexFor!(RegEx, R)) /++ 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 + on each call outputs the result piece-wise to the `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 + for the substitution guided by the `format` string and the one with the user defined callback. +/ public @trusted void replaceFirstInto(Sink, R, C, RegEx) @@ -1331,9 +1335,9 @@ if (isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) } /++ - 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. + Construct a new string from `input` by replacing all of the + fragments that match a pattern `re` with a string generated + from the match according to the `format` specifier. To replace only the first match use $(LREF replaceFirst). @@ -1344,7 +1348,7 @@ if (isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) see $(S_LINK Replace _format string, the _format string). Returns: - A string of the same type as $(D input) with the all + A string of the same type as `input` with the all of the matches (if any) replaced. If no match is found returns the input string itself. +/ @@ -1364,18 +1368,18 @@ if (isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) /++ 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 + matches of pattern `re` in the `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 a user-defined functor `fun` that returns a new string to use as replacement. - This version replaces all of the matches found in $(D input), + This version replaces all of the matches found in `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. + A new string of the same type as `input` with all matches + replaced by return values of `fun`. If no matches found + returns the `input` itself. Params: input = string to search @@ -1404,7 +1408,7 @@ if (isSomeString!R && isRegexFor!(RegEx, R)) /++ 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 + on each call outputs the result piece-wise to the `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, @@ -1450,8 +1454,8 @@ if (isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) import std.array : appender; import std.conv; // try and check first/all simple substitution - foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) - { + static 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(); @@ -1482,11 +1486,11 @@ if (isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) 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). + Old API for replacement, operation depends on flags of pattern `re`. With "g" flag it performs the equivalent of $(LREF replaceAll) otherwise it works the same as $(LREF replaceFirst). @@ -1530,7 +1534,7 @@ private: @trusted this(Range input, RegEx separator) {//@@@BUG@@@ generated opAssign of RegexMatch is not @trusted _input = input; - separator.flags |= RegexOption.global; + const re = separator.withFlags(separator.flags | RegexOption.global); if (_input.empty) { //there is nothing to match at all, make _offset > 0 @@ -1538,7 +1542,7 @@ private: } else { - _match = Rx(_input, separator); + _match = Rx(_input, re); static if (keepSeparators) if (_match.pre.empty) @@ -1659,7 +1663,7 @@ if ( .equal([",", "1", ",", "2", ",", "3"])); } -///An eager version of $(D splitter) that creates an array with splitted slices of $(D input). +///An eager version of `splitter` that creates an array with splitted slices of `input`. public @trusted String[] split(String, RegEx)(String input, RegEx rx) if (isSomeString!String && isRegexFor!(RegEx, String)) { @@ -1725,11 +1729,11 @@ auto escaper(Range)(Range r) { import std.algorithm.comparison; import std.conv; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static 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 index 071adca..e5dc67e 100644 --- a/libphobos/src/std/signals.d +++ b/libphobos/src/std/signals.d @@ -37,25 +37,25 @@ * $(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 + * $(RED Slots can only be delegates referring directly to + * class or interface member functions. 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. + * a nested function, a COM interface, a closure, 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. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/signals.d) * * $(SCRIPT inhibitQuickIndex = 1;) */ -/* Copyright Digital Mars 2000 - 2009. +/* Copyright The D Language Foundation 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) @@ -93,7 +93,8 @@ mixin template Signal(T1...) * 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. + * used as slots. This applies even if the nested function does not access + * it's parent function variables. */ alias slot_t = void delegate(T1); @@ -221,6 +222,17 @@ mixin template Signal(T1...) } } + /*** + * Disconnect all the slots. + */ + final void disconnectAll() + { + debug (signal) writefln("Signal.disconnectAll"); + __dtor(); + slots_idx = 0; + status = ST.idle; + } + /* ** * Special function called when o is destroyed. * It causes any slots dependent on o to be removed from the list @@ -228,7 +240,8 @@ mixin template Signal(T1...) */ final void unhook(Object o) in { assert( status == ST.idle ); } - body { + do + { debug (signal) writefln("Signal.unhook(o = %s)", cast(void*) o); for (size_t i = 0; i < slots_idx; ) { @@ -639,7 +652,7 @@ void linkin() { } a.emit(); // should not raise segfault since &o.watch2 is no longer connected } -version (none) // Disabled because of dmd @@@BUG5028@@@ +version (none) // Disabled because of https://issues.dlang.org/show_bug.cgi?id=5028 @system unittest { class A @@ -706,3 +719,58 @@ version (none) // Disabled because of dmd @@@BUG5028@@@ assert( dot2.value == -22 ); } +@system unittest +{ + import std.signals; + + class Observer + { // our slot + void watch(string msg, int value) + { + if (value != 0) + { + assert(msg == "setting new value"); + assert(value == 1); + } + } + } + + class Foo + { + int value() { return _value; } + + int value(int v) + { + if (v != _value) + { + _value = v; + // call all the connected slots with the 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 Observer; + + a.value = 3; // should not call o.watch() + a.connect(&o.watch); // o.watch is the slot + a.connect(&o2.watch); + a.value = 1; // should call o.watch() + a.disconnectAll(); + a.value = 5; // so should not call o.watch() + a.connect(&o.watch); // connect again + a.connect(&o2.watch); + a.value = 1; // should call o.watch() + destroy(o); // destroying o should automatically disconnect it + destroy(o2); + a.value = 7; // should not call o.watch() +} diff --git a/libphobos/src/std/socket.d b/libphobos/src/std/socket.d index d7de153..be0aeba 100644 --- a/libphobos/src/std/socket.d +++ b/libphobos/src/std/socket.d @@ -1,32 +1,12 @@ // Written in the D programming language +// NOTE: When working on this module, be sure to run tests with -debug=std_socket +// E.g.: dmd -version=StdUnittest -debug=std_socket -unittest -main -run socket +// This will enable some tests which are too slow or flaky to run as part of CI. + /* 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 @@ -39,7 +19,7 @@ * 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) + * Source: $(PHOBOSSRC std/socket.d) */ module std.socket; @@ -52,6 +32,12 @@ import std.exception; import std.internal.cstring; +version (iOS) + version = iOSDerived; +else version (TVOS) + version = iOSDerived; +else version (WatchOS) + version = iOSDerived; @safe: @@ -60,7 +46,7 @@ version (Windows) pragma (lib, "ws2_32.lib"); pragma (lib, "wsock32.lib"); - import core.sys.windows.windows, std.windows.syserror; + import core.sys.windows.winbase, 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; @@ -100,7 +86,7 @@ else version (Posix) import core.stdc.errno; - enum socket_t : int32_t { init = -1 } + enum socket_t : int32_t { _init = -1 } private const int _SOCKET_ERROR = -1; private enum : int @@ -117,30 +103,28 @@ else version (Posix) } else { - static assert(0); // No socket support yet. + static assert(0, "No socket support for this platform yet."); } -version (unittest) +version (StdUnittest) { - 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 + debug (std_socket) test(); - catch (Throwable e) + else { - writefln(" --- std.socket(%d) test fails depending on environment ---", line); - writefln(" (%s)", e); + import std.stdio : writefln; + try + test(); + catch (Throwable e) + writefln("Ignoring std.socket(%d) test failure (likely caused by flaky environment): %s", line, e.msg); } } } -/// Base exception thrown by $(D std.socket). +/// Base exception thrown by `std.socket`. class SocketException: Exception { mixin basicExceptionCtors; @@ -262,17 +246,29 @@ class SocketFeatureException: SocketException /** * Returns: - * $(D true) if the last socket operation failed because the socket - * was in non-blocking mode and the operation would have blocked. + * `true` if the last socket operation failed because the socket + * was in non-blocking mode and the operation would have blocked, + * or if the socket is in blocking mode and set a SNDTIMEO or RCVTIMEO, + * and the operation timed out. */ bool wouldHaveBlocked() nothrow @nogc { version (Windows) - return _lasterr() == WSAEWOULDBLOCK; + return _lasterr() == WSAEWOULDBLOCK || _lasterr() == WSAETIMEDOUT; else version (Posix) return _lasterr() == EAGAIN; else - static assert(0); + static assert(0, "No socket support for this platform yet."); +} + +@safe unittest +{ + auto sockets = socketPair(); + auto s = sockets[0]; + s.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"msecs"(10)); + ubyte[] buffer = new ubyte[](16); + auto rec = s.receive(buffer); + assert(rec == -1 && wouldHaveBlocked()); } @@ -329,7 +325,7 @@ shared static ~this() @system nothrow @nogc /** * The communication domain used to resolve an address. */ -enum AddressFamily: int +enum AddressFamily: ushort { UNSPEC = AF_UNSPEC, /// Unspecified address family UNIX = AF_UNIX, /// Local communication @@ -372,7 +368,7 @@ enum ProtocolType: int /** - * $(D Protocol) is a class for retrieving protocol information. + * `Protocol` is a class for retrieving protocol information. * * Example: * --- @@ -424,7 +420,7 @@ class Protocol } /** Returns: false on failure */ - bool getProtocolByName(in char[] name) @trusted nothrow + bool getProtocolByName(scope const(char)[] name) @trusted nothrow { protoent* proto; proto = getprotobyname(name.tempCString()); @@ -454,6 +450,7 @@ class Protocol version (CRuntime_Bionic) {} else @safe unittest { + // import std.stdio : writefln; softUnittest({ Protocol proto = new Protocol; assert(proto.getProtocolByType(ProtocolType.TCP)); @@ -470,7 +467,7 @@ version (CRuntime_Bionic) {} else /** - * $(D Service) is a class for retrieving service information. + * `Service` is a class for retrieving service information. * * Example: * --- @@ -529,7 +526,7 @@ class Service * 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 + bool getServiceByName(scope const(char)[] name, scope const(char)[] protocolName = null) @trusted nothrow { servent* serv; serv = getservbyname(name.tempCString(), protocolName.tempCString()); @@ -541,7 +538,7 @@ class Service /// ditto - bool getServiceByPort(ushort port, in char[] protocolName = null) @trusted nothrow + bool getServiceByPort(ushort port, scope const(char)[] protocolName = null) @trusted nothrow { servent* serv; serv = getservbyport(port, protocolName.tempCString()); @@ -555,6 +552,7 @@ class Service @safe unittest { + import std.stdio : writefln; softUnittest({ Service serv = new Service; if (serv.getServiceByName("epmap", "tcp")) @@ -710,7 +708,7 @@ class InternetHost * Resolve host name. * Returns: false if unable to resolve. */ - bool getHostByName(in char[] name) @trusted + bool getHostByName(scope const(char)[] name) @trusted { static if (is(typeof(gethostbyname_r))) { @@ -760,7 +758,7 @@ class InternetHost * dotted-decimal form $(I a.b.c.d). * Returns: false if unable to resolve. */ - bool getHostByAddr(in char[] addr) @trusted + bool getHostByAddr(scope const(char)[] addr) @trusted { return getHost!q{ auto x = inet_addr(param.tempCString()); @@ -799,30 +797,30 @@ class InternetHost } -/// Holds information about a socket _address retrieved by $(D getAddressInfo). +/// Holds information about a socket _address retrieved by `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. + string canonicalName; /// Canonical name, when `AddressInfoFlags.CANONNAME` is used. } /** * A subset of flags supported on all platforms with getaddrinfo. - * Specifies option flags for $(D getAddressInfo). + * Specifies option flags for `getAddressInfo`. */ enum AddressInfoFlags: int { - /// The resulting addresses will be used in a call to $(D Socket.bind). + /// The resulting addresses will be used in a call to `Socket.bind`. PASSIVE = AI_PASSIVE, - /// The canonical name is returned in $(D canonicalName) member in the first $(D AddressInfo). + /// The canonical name is returned in `canonicalName` member in the first `AddressInfo`. CANONNAME = AI_CANONNAME, /** - * The $(D node) parameter passed to $(D getAddressInfo) must be a numeric string. + * The `node` parameter passed to `getAddressInfo` must be a numeric string. * This will suppress any potentially lengthy network host address lookups. */ NUMERICHOST = AI_NUMERICHOST, @@ -849,21 +847,21 @@ private string formatGaiError(int err) @trusted /** * 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. + * `getAddress` for compatibility with older systems. * - * Returns: Array with one $(D AddressInfo) per socket address. + * Returns: Array with one `AddressInfo` per socket address. * - * Throws: $(D SocketOSException) on failure, or $(D SocketFeatureException) + * Throws: `SocketOSException` on failure, or `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)) + * $(UL $(LI `string` - service name or port number) + * $(LI `AddressInfoFlags` - option flags) + * $(LI `AddressFamily` - address family to filter by) + * $(LI `SocketType` - socket type to filter by) + * $(LI `ProtocolType` - protocol to filter by)) * * Example: * --- @@ -898,16 +896,16 @@ private string formatGaiError(int err) @trusted * AddressFamily.INET6); * --- */ -AddressInfo[] getAddressInfo(T...)(in char[] node, T options) +AddressInfo[] getAddressInfo(T...)(scope const(char)[] node, scope T options) { const(char)[] service = null; addrinfo hints; hints.ai_family = AF_UNSPEC; - foreach (option; options) + foreach (i, option; options) { static if (is(typeof(option) : const(char)[])) - service = option; + service = options[i]; else static if (is(typeof(option) == AddressInfoFlags)) hints.ai_flags |= option; @@ -943,7 +941,7 @@ AddressInfo[] getAddressInfo(T...)(in char[] node, T options) }), "getAddressInfo breaks @safe"); } -private AddressInfo[] getAddressInfoImpl(in char[] node, in char[] service, addrinfo* hints) @system +private AddressInfo[] getAddressInfoImpl(scope const(char)[] node, scope const(char)[] service, addrinfo* hints) @system { import std.array : appender; @@ -1019,7 +1017,7 @@ private AddressInfo[] getAddressInfoImpl(in char[] node, in char[] service, addr } -private ushort serviceToPort(in char[] service) +private ushort serviceToPort(scope const(char)[] service) { if (service == "") return InternetAddress.PORT_ANY; @@ -1036,12 +1034,12 @@ private ushort serviceToPort(in char[] service) /** * Provides _protocol-independent translation from host names to socket - * addresses. Uses $(D getAddressInfo) if the current system supports it, - * and $(D InternetHost) otherwise. + * addresses. Uses `getAddressInfo` if the current system supports it, + * and `InternetHost` otherwise. * - * Returns: Array with one $(D Address) instance per socket address. + * Returns: Array with one `Address` instance per socket address. * - * Throws: $(D SocketOSException) on failure. + * Throws: `SocketOSException` on failure. * * Example: * --- @@ -1056,7 +1054,7 @@ private ushort serviceToPort(in char[] service) * writefln(" Lookup failed: %s", e.msg); * --- */ -Address[] getAddress(in char[] hostname, in char[] service = null) +Address[] getAddress(scope const(char)[] hostname, scope const(char)[] service = null) { if (getaddrinfoPointer && freeaddrinfoPointer) { @@ -1073,7 +1071,7 @@ Address[] getAddress(in char[] hostname, in char[] service = null) } /// ditto -Address[] getAddress(in char[] hostname, ushort port) +Address[] getAddress(scope const(char)[] hostname, ushort port) { if (getaddrinfoPointer && freeaddrinfoPointer) return getAddress(hostname, to!string(port)); @@ -1115,13 +1113,13 @@ Address[] getAddress(in char[] hostname, ushort port) /** * 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. + * attempt name resolution. Uses `getAddressInfo` with + * `AddressInfoFlags.NUMERICHOST` if the current system supports it, and + * `InternetAddress` otherwise. * - * Returns: An $(D Address) instance representing specified address. + * Returns: An `Address` instance representing specified address. * - * Throws: $(D SocketException) on failure. + * Throws: `SocketException` on failure. * * Example: * --- @@ -1150,7 +1148,7 @@ Address[] getAddress(in char[] hostname, ushort port) * } * --- */ -Address parseAddress(in char[] hostaddr, in char[] service = null) +Address parseAddress(scope const(char)[] hostaddr, scope const(char)[] service = null) { if (getaddrinfoPointer && freeaddrinfoPointer) return getAddressInfo(hostaddr, service, AddressInfoFlags.NUMERICHOST)[0].address; @@ -1159,7 +1157,7 @@ Address parseAddress(in char[] hostaddr, in char[] service = null) } /// ditto -Address parseAddress(in char[] hostaddr, ushort port) +Address parseAddress(scope const(char)[] hostaddr, ushort port) { if (getaddrinfoPointer && freeaddrinfoPointer) return parseAddress(hostaddr, to!string(port)); @@ -1196,7 +1194,7 @@ Address parseAddress(in char[] hostaddr, ushort port) /** - * Class for exceptions thrown from an $(D Address). + * Class for exceptions thrown from an `Address`. */ class AddressException: SocketOSException { @@ -1205,7 +1203,7 @@ class AddressException: SocketOSException /** - * $(D Address) is an abstract class for representing a socket addresses. + * `Address` is an abstract class for representing a socket addresses. * * Example: * --- @@ -1230,11 +1228,11 @@ class AddressException: SocketOSException */ abstract class Address { - /// Returns pointer to underlying $(D sockaddr) structure. + /// Returns pointer to underlying `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. + /// Returns actual size of underlying `sockaddr` structure. abstract @property socklen_t nameLen() const pure nothrow @nogc; // Socket.remoteAddress, Socket.localAddress, and Socket.receiveFrom @@ -1321,7 +1319,7 @@ abstract class Address /** * Attempts to retrieve the host address as a human-readable string. * - * Throws: $(D AddressException) on failure, or $(D SocketFeatureException) + * Throws: `AddressException` on failure, or `SocketFeatureException` * if address retrieval for this address family is not available on the * current system. */ @@ -1333,10 +1331,10 @@ abstract class Address /** * Attempts to retrieve the host name as a fully qualified domain name. * - * Returns: The FQDN corresponding to this $(D Address), or $(D null) if + * Returns: The FQDN corresponding to this `Address`, or `null` if * the host name did not resolve. * - * Throws: $(D AddressException) on error, or $(D SocketFeatureException) + * Throws: `AddressException` on error, or `SocketFeatureException` * if host name lookup for this address family is not available on the * current system. */ @@ -1348,7 +1346,7 @@ abstract class Address /** * Attempts to retrieve the numeric port number as a string. * - * Throws: $(D AddressException) on failure, or $(D SocketFeatureException) + * Throws: `AddressException` on failure, or `SocketFeatureException` * if port number retrieval for this address family is not available on the * current system. */ @@ -1360,7 +1358,7 @@ abstract class Address /** * Attempts to retrieve the service name as a string. * - * Throws: $(D AddressException) on failure, or $(D SocketFeatureException) + * Throws: `AddressException` on failure, or `SocketFeatureException` * if service name lookup for this address family is not available on the * current system. */ @@ -1387,7 +1385,7 @@ abstract class Address } /** - * $(D UnknownAddress) encapsulates an unknown socket address. + * `UnknownAddress` encapsulates an unknown socket address. */ class UnknownAddress: Address { @@ -1396,12 +1394,12 @@ protected: public: - override @property sockaddr* name() + override @property sockaddr* name() return { return &sa; } - override @property const(sockaddr)* name() const + override @property const(sockaddr)* name() const return { return &sa; } @@ -1416,7 +1414,7 @@ public: /** - * $(D UnknownAddressReference) encapsulates a reference to an arbitrary + * `UnknownAddressReference` encapsulates a reference to an arbitrary * socket address. */ class UnknownAddressReference: Address @@ -1426,14 +1424,14 @@ protected: socklen_t len; public: - /// Constructs an $(D Address) with a reference to the specified $(D sockaddr). + /// Constructs an `Address` with a reference to the specified `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). + /// Constructs an `Address` with a copy of the specified `sockaddr`. this(const(sockaddr)* sa, socklen_t len) @system pure nothrow { this.sa = cast(sockaddr*) (cast(ubyte*) sa)[0 .. len].dup.ptr; @@ -1459,10 +1457,10 @@ public: /** - * $(D InternetAddress) encapsulates an IPv4 (Internet Protocol version 4) + * `InternetAddress` encapsulates an IPv4 (Internet Protocol version 4) * socket address. * - * Consider using $(D getAddress), $(D parseAddress) and $(D Address) methods + * Consider using `getAddress`, `parseAddress` and `Address` methods * instead of using this class directly. */ class InternetAddress: Address @@ -1477,12 +1475,12 @@ protected: public: - override @property sockaddr* name() + override @property sockaddr* name() return { return cast(sockaddr*)&sin; } - override @property const(sockaddr)* name() const + override @property const(sockaddr)* name() const return { return cast(const(sockaddr)*)&sin; } @@ -1511,14 +1509,14 @@ public: } /** - * Construct a new $(D InternetAddress). + * Construct a new `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) + * or a host name which will be resolved using an `InternetHost` * object. - * port = port number, may be $(D PORT_ANY). + * port = port number, may be `PORT_ANY`. */ - this(in char[] addr, ushort port) + this(scope const(char)[] addr, ushort port) { uint uiaddr = parse(addr); if (ADDR_NONE == uiaddr) @@ -1536,10 +1534,10 @@ public: } /** - * Construct a new $(D InternetAddress). + * Construct a new `InternetAddress`. * Params: - * addr = (optional) an IPv4 address in host byte order, may be $(D ADDR_ANY). - * port = port number, may be $(D PORT_ANY). + * addr = (optional) an IPv4 address in host byte order, may be `ADDR_ANY`. + * port = port number, may be `PORT_ANY`. */ this(uint addr, ushort port) pure nothrow @nogc { @@ -1557,13 +1555,13 @@ public: } /** - * Construct a new $(D InternetAddress). + * Construct a new `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); + assert(addr.sin_family == AddressFamily.INET, "Socket address is not of INET family."); sin = addr; } @@ -1582,10 +1580,10 @@ public: /** * 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. + * Returns: The FQDN corresponding to this `InternetAddress`, or + * `null` if the host name did not resolve. * - * Throws: $(D AddressException) on error. + * Throws: `AddressException` on error. */ override string toHostNameString() const { @@ -1634,9 +1632,9 @@ public: * 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. + * `ADDR_NONE` is returned. */ - static uint parse(in char[] addr) @trusted nothrow + static uint parse(scope const(char)[] addr) @trusted nothrow { return ntohl(inet_addr(addr.tempCString())); } @@ -1693,18 +1691,18 @@ public: } }); - version (SlowTests) + debug (std_socket) softUnittest({ // test failing reverse lookup - const InternetAddress ia = new InternetAddress("127.114.111.120", 80); + const InternetAddress ia = new InternetAddress("255.255.255.255", 80); assert(ia.toHostNameString() is null); if (getnameinfoPointer) { // test failing reverse lookup, via gethostbyaddr auto getnameinfoPointerBackup = getnameinfoPointer; - getnameinfoPointer = null; - scope(exit) getnameinfoPointer = getnameinfoPointerBackup; + cast() getnameinfoPointer = null; + scope(exit) cast() getnameinfoPointer = getnameinfoPointerBackup; assert(ia.toHostNameString() is null); } @@ -1713,10 +1711,10 @@ public: /** - * $(D Internet6Address) encapsulates an IPv6 (Internet Protocol version 6) + * `Internet6Address` encapsulates an IPv6 (Internet Protocol version 6) * socket address. * - * Consider using $(D getAddress), $(D parseAddress) and $(D Address) methods + * Consider using `getAddress`, `parseAddress` and `Address` methods * instead of using this class directly. */ class Internet6Address: Address @@ -1731,12 +1729,12 @@ protected: public: - override @property sockaddr* name() + override @property sockaddr* name() return { return cast(sockaddr*)&sin6; } - override @property const(sockaddr)* name() const + override @property const(sockaddr)* name() const return { return cast(const(sockaddr)*)&sin6; } @@ -1751,16 +1749,19 @@ public: /// 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; + version (Windows) + { + static immutable addr = IN6ADDR_ANY.s6_addr; + return addr; + } + else + return IN6ADDR_ANY.s6_addr; } else static if (is(typeof(in6addr_any))) { - addr = &in6addr_any.s6_addr; - return *addr; + return in6addr_any.s6_addr; } else static assert(0); @@ -1782,13 +1783,13 @@ public: } /** - * Construct a new $(D Internet6Address). + * Construct a new `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). + * or a host name which will be resolved using `getAddressInfo`. * service = (optional) service name. */ - this(in char[] addr, in char[] service = null) @trusted + this(scope const(char)[] addr, scope const(char)[] service = null) @trusted { auto results = getAddressInfo(addr, service, AddressFamily.INET6); assert(results.length && results[0].family == AddressFamily.INET6); @@ -1796,13 +1797,13 @@ public: } /** - * Construct a new $(D Internet6Address). + * Construct a new `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). + * or a host name which will be resolved using `getAddressInfo`. + * port = port number, may be `PORT_ANY`. */ - this(in char[] addr, ushort port) + this(scope const(char)[] addr, ushort port) { if (port == PORT_ANY) this(addr); @@ -1811,11 +1812,11 @@ public: } /** - * Construct a new $(D Internet6Address). + * Construct a new `Internet6Address`. * Params: * addr = (optional) an IPv6 host address in host byte order, or - * $(D ADDR_ANY). - * port = port number, may be $(D PORT_ANY). + * `ADDR_ANY`. + * port = port number, may be `PORT_ANY`. */ this(ubyte[16] addr, ushort port) pure nothrow @nogc { @@ -1833,7 +1834,7 @@ public: } /** - * Construct a new $(D Internet6Address). + * Construct a new `Internet6Address`. * Params: * addr = A sockaddr_in6 as obtained from lower-level API calls such as getifaddrs. */ @@ -1846,9 +1847,9 @@ public: /** * Parse an IPv6 host address string as described in RFC 2373, and return the * address. - * Throws: $(D SocketException) on error. + * Throws: `SocketException` on error. */ - static ubyte[16] parse(in char[] addr) @trusted + static ubyte[16] parse(scope const(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 @@ -1895,8 +1896,8 @@ version (StdDdoc) } /** - * $(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. + * `UnixAddress` encapsulates an address for a Unix domain socket + * (`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 @@ -1917,11 +1918,11 @@ version (StdDdoc) { private this() pure nothrow @nogc {} - /// Construct a new $(D UnixAddress) from the specified path. - this(in char[] path) { } + /// Construct a new `UnixAddress` from the specified path. + this(scope const(char)[] path) { } /** - * Construct a new $(D UnixAddress). + * Construct a new `UnixAddress`. * Params: * addr = A sockaddr_un as obtained from lower-level API calls. */ @@ -1968,12 +1969,12 @@ static if (is(sockaddr_un)) } public: - override @property sockaddr* name() + override @property sockaddr* name() return { return cast(sockaddr*)&sun; } - override @property const(sockaddr)* name() const + override @property const(sockaddr)* name() const return { return cast(const(sockaddr)*)&sun; } @@ -1983,7 +1984,7 @@ static if (is(sockaddr_un)) return _nameLen; } - this(in char[] path) @trusted pure + this(scope const(char)[] path) @trusted pure { enforce(path.length <= sun.sun_path.sizeof, new SocketParameterException("Path too long")); sun.sun_family = AddressFamily.UNIX; @@ -2011,6 +2012,8 @@ static if (is(sockaddr_un)) @property string path() @trusted const pure { auto len = _nameLen - sockaddr_un.init.sun_path.offsetof; + if (len == 0) + return null; // An empty path may be returned from getpeername // For pathname socket address we need to strip off the terminating '\0' if (sun.sun_path.ptr[0]) --len; @@ -2026,14 +2029,33 @@ static if (is(sockaddr_un)) @safe unittest { import core.stdc.stdio : remove; - import std.file : deleteme; + + version (iOSDerived) + { + // Slightly different version of `std.file.deleteme` to reduce the path + // length on iOS derived platforms. Due to the sandbox, the length + // of paths can quickly become too long. + static string deleteme() + { + import std.conv : text; + import std.process : thisProcessID; + import std.file : tempDir; + + return text(tempDir, thisProcessID); + } + } + + else + import std.file : deleteme; immutable ubyte[] data = [1, 2, 3, 4]; Socket[2] pair; - auto names = [ deleteme ~ "-unix-socket" ]; + const basePath = deleteme; + auto names = [ basePath ~ "-socket" ]; version (linux) - names ~= "\0" ~ deleteme ~ "-abstract\0unix\0socket"; + names ~= "\0" ~ basePath ~ "-abstract\0unix\0socket"; + foreach (name; names) { auto address = new UnixAddress(name); @@ -2060,13 +2082,19 @@ static if (is(sockaddr_un)) auto buf = new ubyte[data.length]; pair[1].receive(buf); assert(buf == data); + + // getpeername is free to return an empty name for a unix + // domain socket pair or unbound socket. Let's confirm it + // returns successfully and doesn't throw anything. + // See https://issues.dlang.org/show_bug.cgi?id=20544 + assertNotThrown(pair[1].remoteAddress().toString()); } } } /** - * Class for exceptions thrown by $(D Socket.accept). + * Class for exceptions thrown by `Socket.accept`. */ class SocketAcceptException: SocketOSException { @@ -2132,10 +2160,10 @@ struct TimeVal /** - * A collection of sockets for use with $(D Socket.select). + * A collection of sockets for use with `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) + * `SocketSet` wraps the platform `fd_set` type. However, unlike + * `fd_set`, `SocketSet` is not statically limited to `FD_SETSIZE` * or any other limit, and grows as needed. */ class SocketSet @@ -2243,7 +2271,7 @@ public: /** * Create a SocketSet with a specific initial capacity (defaults to - * $(D FD_SETSIZE), the system's default capacity). + * `FD_SETSIZE`, the system's default capacity). */ this(size_t size = FD_SETSIZE) pure nothrow { @@ -2251,7 +2279,7 @@ public: reset(); } - /// Reset the $(D SocketSet) so that there are 0 $(D Socket)s in the collection. + /// Reset the `SocketSet` so that there are 0 `Socket`s in the collection. void reset() pure nothrow @nogc { version (Windows) @@ -2294,7 +2322,7 @@ public: } /** - * Add a $(D Socket) to the collection. + * Add a `Socket` to the collection. * The socket must not already be in the collection. */ void add(Socket s) pure nothrow @@ -2324,7 +2352,7 @@ public: /** - * Remove this $(D Socket) from the collection. + * Remove this `Socket` from the collection. * Does nothing if the socket is not in the collection already. */ void remove(Socket s) pure nothrow @@ -2349,7 +2377,7 @@ public: } - /// Return nonzero if this $(D Socket) is in the collection. + /// Return nonzero if this `Socket` is in the collection. int isSet(Socket s) const pure nothrow @nogc { return isSet(s.sock); @@ -2358,12 +2386,12 @@ public: /** * Returns: - * The current capacity of this $(D SocketSet). The exact + * The current capacity of this `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 + * restriction, and `SocketSet` will grow its capacity as * needed automatically. */ @property uint max() const pure nothrow @nogc @@ -2415,12 +2443,21 @@ public: @safe unittest { - softUnittest({ + version (iOSDerived) + { + enum PAIRS = 256; + enum LIMIT = 1024; + } + else + { enum PAIRS = 768; + enum LIMIT = 2048; + } + + softUnittest({ version (Posix) () @trusted { - enum LIMIT = 2048; static assert(LIMIT > PAIRS*2); import core.sys.posix.sys.resource; rlimit fileLimit; @@ -2477,18 +2514,28 @@ public: assert(!errorSet.isSet(testPair[1])); ubyte[1] b; - testPair[0].send(b[]); + // Socket.send can't be marked with `scope` + // -> @safe DIP1000 code can't use it - see https://github.com/dlang/phobos/pull/6204 + () @trusted { + 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[]); + // Socket.receive can't be marked with `scope` + // -> @safe DIP1000 code can't use it - see https://github.com/dlang/phobos/pull/6204 + () @trusted { + testPair[1].receive(b[]); + }(); } }); } -@safe unittest // Issue 14012, 14013 +// https://issues.dlang.org/show_bug.cgi?id=14012 +// https://issues.dlang.org/show_bug.cgi?id=14013 +@safe unittest { auto set = new SocketSet(1); assert(set.max >= 0); @@ -2570,7 +2617,7 @@ enum SocketOption: int /** - * $(D Socket) is a class that creates a network communication endpoint using + * `Socket` is a class that creates a network communication endpoint using * the Berkeley sockets interface. */ class Socket @@ -2591,9 +2638,9 @@ private: @safe unittest { - version (SlowTests) + debug (std_socket) softUnittest({ - import std.datetime; + import std.datetime.stopwatch; import std.typecons; enum msecs = 1000; @@ -2611,7 +2658,7 @@ private: sock.getOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, readBack); assert(readBack.total!"msecs" == msecs); - assert(sw.peek().msecs > msecs-100 && sw.peek().msecs < msecs+100); + assert(sw.peek().total!"msecs" > msecs - 100 && sw.peek().total!"msecs" < msecs + 100); }); } @@ -2639,7 +2686,7 @@ 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 + * this socket type within the address family, the `ProtocolType` may be * omitted. */ this(AddressFamily af, SocketType type, ProtocolType protocol) @trusted @@ -2662,7 +2709,7 @@ public: /// ditto - this(AddressFamily af, SocketType type, in char[] protocolName) @trusted + this(AddressFamily af, SocketType type, scope const(char)[] protocolName) @trusted { protoent* proto; proto = getprotobyname(protocolName.tempCString()); @@ -2674,9 +2721,9 @@ public: /** * Create a blocking socket using the parameters from the specified - * $(D AddressInfo) structure. + * `AddressInfo` structure. */ - this(in AddressInfo info) + this(const scope AddressInfo info) { this(info.family, info.type, info.protocol); } @@ -2764,7 +2811,14 @@ public: return !getsockopt(sock, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize); } - /// Associate a local address with this socket. + /** + * Associate a local address with this socket. + * + * Params: + * addr = The $(LREF Address) to associate this socket with. + * + * Throws: $(LREF SocketOSException) when unable to bind the socket. + */ void bind(Address addr) @trusted { if (_SOCKET_ERROR == .bind(sock, addr.name, addr.nameLen)) @@ -2805,9 +2859,9 @@ public: } /** - * 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. + * Listen for an incoming connection. `bind` must be called before you + * can `listen`. The `backlog` is a request of how many pending + * incoming connections are queued until `accept`ed. */ void listen(int backlog) @trusted { @@ -2816,10 +2870,10 @@ public: } /** - * Called by $(D accept) when a new $(D Socket) must be created for a new + * Called by `accept` when a new `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 + * instance of your class. The returned `Socket`'s handle must not be + * set; `Socket` has a protected constructor `this()` to use in this * situation. * * Override to use a derived class. @@ -2831,9 +2885,9 @@ public: } /** - * 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. + * Accept an incoming connection. If the socket is blocking, `accept` + * waits for a connection request. Throws `SocketAcceptException` if + * unable to _accept. See `accepting` for use with derived classes. */ Socket accept() @trusted { @@ -2883,10 +2937,8 @@ public: /** * 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 + * The `Socket` object is no longer usable after `close`. + * Calling `shutdown` before `close` is recommended * for connection-oriented sockets. */ void close() @trusted nothrow @nogc @@ -2907,7 +2959,7 @@ public: return to!string(result.ptr); } - /// Remote endpoint $(D Address). + /// Remote endpoint `Address`. @property Address remoteAddress() @trusted { Address addr = createAddress(); @@ -2919,7 +2971,7 @@ public: return addr; } - /// Local endpoint $(D Address). + /// Local endpoint `Address`. @property Address localAddress() @trusted { Address addr = createAddress(); @@ -2932,8 +2984,8 @@ public: } /** - * Send or receive error code. See $(D wouldHaveBlocked), - * $(D lastSocketError) and $(D Socket.getErrorText) for obtaining more + * Send or receive error code. See `wouldHaveBlocked`, + * `lastSocketError` and `Socket.getErrorText` for obtaining more * information about the error. */ enum int ERROR = _SOCKET_ERROR; @@ -2949,8 +3001,8 @@ public: /** * 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 + * buffer space left, `send` waits. + * Returns: The number of bytes actually sent, or `Socket.ERROR` on * failure. */ ptrdiff_t send(const(void)[] buf, SocketFlags flags) @trusted @@ -2975,8 +3027,8 @@ public: /** * 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 + * If the socket is blocking and there is no buffer space left, `sendTo` waits. + * Returns: The number of bytes actually sent, or `Socket.ERROR` on * failure. */ ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags, Address to) @trusted @@ -3025,10 +3077,10 @@ public: /** - * Receive data on the connection. If the socket is blocking, $(D receive) + * Receive data on the connection. If the socket is blocking, `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. + * Returns: The number of bytes actually received, `0` if the remote side + * has closed the connection, or `Socket.ERROR` on failure. */ ptrdiff_t receive(void[] buf, SocketFlags flags) @trusted { @@ -3053,11 +3105,11 @@ public: } /** - * Receive data and get the remote endpoint $(D Address). - * If the socket is blocking, $(D receiveFrom) waits until there is data to + * Receive data and get the remote endpoint `Address`. + * If the socket is blocking, `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. + * Returns: The number of bytes actually received, `0` if the remote side + * has closed the connection, or `Socket.ERROR` on failure. */ ptrdiff_t receiveFrom(void[] buf, SocketFlags flags, ref Address from) @trusted { @@ -3123,7 +3175,7 @@ public: /** * Get a socket option. - * Returns: The number of bytes written to $(D result). + * Returns: The number of bytes written to `result`. * The length, in bytes, of the actual result - very different from getsockopt() */ int getOption(SocketOptionLevel level, SocketOption option, void[] result) @trusted @@ -3197,8 +3249,8 @@ public: } /** - * Sets a timeout (duration) option, i.e. $(D SocketOption.SNDTIMEO) or - * $(D RCVTIMEO). Zero indicates no timeout. + * Sets a timeout (duration) option, i.e. `SocketOption.SNDTIMEO` or + * `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. @@ -3207,17 +3259,17 @@ public: * 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, + * a second. `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). + * option = Either `SocketOption.SNDTIMEO` or `SocketOption.RCVTIMEO`. * value = The timeout duration to set. Must not be negative. * - * Throws: $(D SocketException) if setting the options fails. + * Throws: `SocketException` if setting the options fails. * * Example: * --- @@ -3284,8 +3336,8 @@ public: * 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 + * Throws: `SocketOSException` if setting the options fails, or + * `SocketFeatureException` if setting keep-alive parameters is * unsupported on the current platform. */ void setKeepAlive(int time, int interval) @trusted @@ -3317,12 +3369,12 @@ public: /** * 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 + * `TimeVal`, may be specified; if a timeout is not specified or the + * `TimeVal` is `null`, the maximum timeout is used. The `TimeVal` + * timeout has an unspecified value when `select` returns. + * Returns: The number of sockets with status changes, `0` on timeout, + * or `-1` on interruption. If the return value is greater than `0`, + * the `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 @@ -3367,7 +3419,7 @@ public: assert(checkWrite !is checkError); } } - body + do { fd_set* fr, fw, fe; int n = 0; @@ -3481,7 +3533,7 @@ public: } -/// $(D TcpSocket) is a shortcut class for a TCP Socket. +/// `TcpSocket` is a shortcut class for a TCP Socket. class TcpSocket: Socket { /// Constructs a blocking TCP Socket. @@ -3498,7 +3550,7 @@ class TcpSocket: Socket //shortcut - /// Constructs a blocking TCP Socket and connects to an $(D Address). + /// Constructs a blocking TCP Socket and connects to an `Address`. this(Address connectTo) { this(connectTo.addressFamily); @@ -3507,7 +3559,7 @@ class TcpSocket: Socket } -/// $(D UdpSocket) is a shortcut class for a UDP Socket. +/// `UdpSocket` is a shortcut class for a UDP Socket. class UdpSocket: Socket { /// Constructs a blocking UDP Socket. @@ -3524,50 +3576,167 @@ class UdpSocket: Socket } } -// Issue 16514 +// https://issues.dlang.org/show_bug.cgi?id=16514 @safe unittest { + void checkAttributes(string attributes)() + { + mixin(attributes ~ q{ void function() fun = {};}); + fun(); + } + 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); } + @property pure nothrow @nogc @safe socket_t handle() const + { + checkAttributes!q{pure nothrow @nogc @safe}; assert(0); + } + @property nothrow @nogc @trusted bool blocking() const + { + checkAttributes!q{nothrow @nogc @trusted}; assert(0); + } + @property @trusted void blocking(bool byes) + { + checkAttributes!q{@trusted}; + } + @property @safe AddressFamily addressFamily() + { + checkAttributes!q{@safe}; assert(0); + } + @property @trusted bool isAlive() const + { + checkAttributes!q{@trusted}; assert(0); + } + @trusted void bind(Address addr) + { + checkAttributes!q{@trusted}; + } + @trusted void connect(Address to) + { + checkAttributes!q{@trusted}; + } + @trusted void listen(int backlog) + { + checkAttributes!q{@trusted}; + } + protected pure nothrow @safe Socket accepting() + { + checkAttributes!q{pure nothrow @safe}; assert(0); + } + @trusted Socket accept() + { + checkAttributes!q{@trusted}; assert(0); + } + nothrow @nogc @trusted void shutdown(SocketShutdown how) + { + checkAttributes!q{nothrow @nogc @trusted}; + } + nothrow @nogc @trusted void close() + { + checkAttributes!q{nothrow @nogc @trusted}; + } + @property @trusted Address remoteAddress() + { + checkAttributes!q{@trusted}; assert(0); + } + @property @trusted Address localAddress() + { + checkAttributes!q{@trusted}; assert(0); + } + @trusted ptrdiff_t send(const(void)[] buf, SocketFlags flags) + { + checkAttributes!q{@trusted}; assert(0); + } + @safe ptrdiff_t send(const(void)[] buf) + { + checkAttributes!q{@safe}; assert(0); + } + @trusted ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags, Address to) + { + checkAttributes!q{@trusted}; assert(0); + } + @safe ptrdiff_t sendTo(const(void)[] buf, Address to) + { + checkAttributes!q{@safe}; assert(0); + } + @trusted ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags) + { + checkAttributes!q{@trusted}; assert(0); + } + @safe ptrdiff_t sendTo(const(void)[] buf) + { + checkAttributes!q{@safe}; assert(0); + } + @trusted ptrdiff_t receive(void[] buf, SocketFlags flags) + { + checkAttributes!q{@trusted}; assert(0); + } + @safe ptrdiff_t receive(void[] buf) + { + checkAttributes!q{@safe}; assert(0); + } + @trusted ptrdiff_t receiveFrom(void[] buf, SocketFlags flags, ref Address from) + { + checkAttributes!q{@trusted}; assert(0); + } + @safe ptrdiff_t receiveFrom(void[] buf, ref Address from) + { + checkAttributes!q{@safe}; assert(0); + } + @trusted ptrdiff_t receiveFrom(void[] buf, SocketFlags flags) + { + checkAttributes!q{@trusted}; assert(0); + } + @safe ptrdiff_t receiveFrom(void[] buf) + { + checkAttributes!q{@safe}; assert(0); + } + @trusted int getOption(SocketOptionLevel level, SocketOption option, void[] result) + { + checkAttributes!q{@trusted}; assert(0); + } + @trusted int getOption(SocketOptionLevel level, SocketOption option, out int32_t result) + { + checkAttributes!q{@trusted}; assert(0); + } + @trusted int getOption(SocketOptionLevel level, SocketOption option, out Linger result) + { + checkAttributes!q{@trusted}; assert(0); + } + @trusted void getOption(SocketOptionLevel level, SocketOption option, out Duration result) + { + checkAttributes!q{@trusted}; + } + @trusted void setOption(SocketOptionLevel level, SocketOption option, void[] value) + { + checkAttributes!q{@trusted}; + } + @trusted void setOption(SocketOptionLevel level, SocketOption option, int32_t value) + { + checkAttributes!q{@trusted}; + } + @trusted void setOption(SocketOptionLevel level, SocketOption option, Linger value) + { + checkAttributes!q{@trusted}; + } + @trusted void setOption(SocketOptionLevel level, SocketOption option, Duration value) + { + checkAttributes!q{@trusted}; + } + @safe string getErrorText() + { + checkAttributes!q{@safe}; assert(0); + } + @trusted void setKeepAlive(int time, int interval) + { + checkAttributes!q{@trusted}; + } + protected pure nothrow @safe Address createAddress() + { + checkAttributes!q{pure nothrow @safe}; assert(0); + } } } } @@ -3577,7 +3746,7 @@ class UdpSocket: Socket * * The two sockets are indistinguishable. * - * Throws: $(D SocketException) if creation of the sockets fails. + * Throws: `SocketException` if creation of the sockets fails. */ Socket[2] socketPair() @trusted { diff --git a/libphobos/src/std/stdint.d b/libphobos/src/std/stdint.d index b4a5ff9..88f4a22 100644 --- a/libphobos/src/std/stdint.d +++ b/libphobos/src/std/stdint.d @@ -116,12 +116,12 @@ * Macros: * ATABLE=$0
* - * Copyright: Copyright Digital Mars 2000 - 2009. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/stdint.d) */ -/* Copyright Digital Mars 2000 - 2009. +/* Copyright The D Language Foundation 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) diff --git a/libphobos/src/std/stdio.d b/libphobos/src/std/stdio.d index bbf7857..a88beb8 100644 --- a/libphobos/src/std/stdio.d +++ b/libphobos/src/std/stdio.d @@ -1,11 +1,44 @@ // Written in the D programming language. /** +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Symbols)) +$(TR $(TD File handles) $(TD + $(MYREF __popen) + $(MYREF File) + $(MYREF isFileHandle) + $(MYREF openNetwork) + $(MYREF stderr) + $(MYREF stdin) + $(MYREF stdout) +)) +$(TR $(TD Reading) $(TD + $(MYREF chunks) + $(MYREF lines) + $(MYREF readf) + $(MYREF readln) +)) +$(TR $(TD Writing) $(TD + $(MYREF toFile) + $(MYREF write) + $(MYREF writef) + $(MYREF writefln) + $(MYREF writeln) +)) +$(TR $(TD Misc) $(TD + $(MYREF KeepTerminator) + $(MYREF LockType) + $(MYREF StdioException) +)) +)) + 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-. +Source: $(PHOBOSSRC std/stdio.d) +Copyright: Copyright The D Language Foundation 2007-. License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP digitalmars.com, Walter Bright), $(HTTP erdani.org, Andrei Alexandrescu), @@ -13,17 +46,17 @@ Authors: $(HTTP digitalmars.com, Walter Bright), */ module std.stdio; -import core.stdc.stddef; // wchar_t +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 +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, No, Yes; /++ -If flag $(D KeepTerminator) is set to $(D KeepTerminator.yes), then the delimiter +If flag `KeepTerminator` is set to `KeepTerminator.yes`, then the delimiter is included in the strings returned. +/ alias KeepTerminator = Flag!"keepTerminator"; @@ -58,18 +91,22 @@ else version (CRuntime_UClibc) else version (OSX) { version = GENERIC_IO; + version = Darwin; } else version (iOS) { version = GENERIC_IO; + version = Darwin; } else version (TVOS) { version = GENERIC_IO; + version = Darwin; } else version (WatchOS) { version = GENERIC_IO; + version = Darwin; } else version (FreeBSD) { @@ -112,147 +149,214 @@ version (Windows) 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; + import core.sys.windows.basetsd : HANDLE; } version (Posix) { - static import core.sys.posix.stdio; // getdelim + static import core.sys.posix.stdio; // getdelim, flockfile } 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); - } + private alias _FPUTC = _fputc_nlock; + private alias _FPUTWC = _fputwc_nlock; + private alias _FGETC = _fgetc_nlock; + private alias _FGETWC = _fgetwc_nlock; + private alias _FLOCK = __fp_lock; + private alias _FUNLOCK = __fp_unlock; + + // Alias for MICROSOFT_STDIO compatibility. + // @@@DEPRECATED_2.107@@@ + // Rename this back to _setmode once the deprecation phase has ended. + private alias __setmode = setmode; + + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTC = _fputc_nlock; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTWC = _fputwc_nlock; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETC = _fgetc_nlock; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETWC = _fgetwc_nlock; - + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FLOCK = __fp_lock; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FUNLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FUNLOCK = __fp_unlock; - + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias _setmode was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias _setmode = setmode; - enum _O_BINARY = 0x8000; + // @@@DEPRECATED_2.107@@@ + deprecated("internal function _fileno was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") 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*); - } + private alias _FPUTC = _fputc_nolock; + private alias _FPUTWC = _fputwc_nolock; + private alias _FGETC = _fgetc_nolock; + private alias _FGETWC = _fgetwc_nolock; + private alias _FLOCK = _lock_file; + private alias _FUNLOCK = _unlock_file; + + // @@@DEPRECATED_2.107@@@ + // Remove this once the deprecation phase for DIGITAL_MARS_STDIO has ended. + private alias __setmode = _setmode; + + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTC = _fputc_nolock; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTWC = _fputwc_nolock; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETC = _fgetc_nolock; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETWC = _fgetwc_nolock; - + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FLOCK = _lock_file; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FUNLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") 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); - } - + private alias _FPUTC = fputc_unlocked; + private alias _FPUTWC = fputwc_unlocked; + private alias _FGETC = fgetc_unlocked; + private alias _FGETWC = fgetwc_unlocked; + private alias _FLOCK = core.sys.posix.stdio.flockfile; + private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; + + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTC = fputc_unlocked; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTWC = fputwc_unlocked; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETC = fgetc_unlocked; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETWC = fgetwc_unlocked; - - alias FLOCK = flockfile; - alias FUNLOCK = funlockfile; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") + alias FLOCK = core.sys.posix.stdio.flockfile; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FUNLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") + alias FUNLOCK = core.sys.posix.stdio.funlockfile; } else version (GENERIC_IO) { nothrow: @nogc: - extern (C) + private int _FPUTC(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); } + private int _FPUTWC(wchar_t c, _iobuf* fp) { - void flockfile(FILE*); - void funlockfile(FILE*); + import core.stdc.wchar_ : fputwc; + return fputwc(c, cast(shared) fp); + } + private int _FGETC(_iobuf* fp) { return fgetc(cast(shared) fp); } + private int _FGETWC(_iobuf* fp) + { + import core.stdc.wchar_ : fgetwc; + return fgetwc(cast(shared) fp); } + version (Posix) + { + private alias _FLOCK = core.sys.posix.stdio.flockfile; + private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; + } + else + { + static assert(0, "don't know how to lock files on GENERIC_IO"); + } + + // @@@DEPRECATED_2.107@@@ + deprecated("internal function fputc_unlocked was unintentionally available " + ~ "from std.stdio and will be removed afer 2.107") int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); } + // @@@DEPRECATED_2.107@@@ + deprecated("internal function fputwc_unlocked was unintentionally available " + ~ "from std.stdio and will be removed afer 2.107") int fputwc_unlocked(wchar_t c, _iobuf* fp) { import core.stdc.wchar_ : fputwc; return fputwc(c, cast(shared) fp); } + // @@@DEPRECATED_2.107@@@ + deprecated("internal function fgetc_unlocked was unintentionally available " + ~ "from std.stdio and will be removed afer 2.107") int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); } + // @@@DEPRECATED_2.107@@@ + deprecated("internal function fgetwc_unlocked was unintentionally available " + ~ "from std.stdio and will be removed afer 2.107") int fgetwc_unlocked(_iobuf* fp) { import core.stdc.wchar_ : fgetwc; return fgetwc(cast(shared) fp); } + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTC = fputc_unlocked; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FPUTWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FPUTWC = fputwc_unlocked; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETC = fgetc_unlocked; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FGETWC was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") alias FGETWC = fgetwc_unlocked; - alias FLOCK = flockfile; - alias FUNLOCK = funlockfile; + version (Posix) + { + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") + alias FLOCK = core.sys.posix.stdio.flockfile; + // @@@DEPRECATED_2.107@@@ + deprecated("internal alias FUNLOCK was unintentionally available from " + ~ "std.stdio and will be removed afer 2.107") + alias FUNLOCK = core.sys.posix.stdio.funlockfile; + } } else { @@ -275,7 +379,7 @@ static if (__traits(compiles, core.sys.posix.stdio.getdelim)) } //------------------------------------------------------------------------------ -struct ByRecord(Fields...) +private struct ByRecordImpl(Fields...) { private: import std.typecons : Tuple; @@ -311,7 +415,7 @@ public: { import std.conv : text; import std.exception : enforce; - import std.format : formattedRead; + import std.format.read : formattedRead; import std.string : chomp; enforce(file.isOpen, "ByRecord: File must be open"); @@ -332,27 +436,29 @@ public: template byRecord(Fields...) { - ByRecord!(Fields) byRecord(File f, string format) + auto byRecord(File f, string format) { return typeof(return)(f, format); } } /** -Encapsulates a $(D FILE*). Generally D does not attempt to provide +Encapsulates a `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 +manipulating `FILE*` values directly is unsafe and error-prone in +many ways. The `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 +The underlying `FILE*` handle is maintained in a reference-counted +manner, such that as soon as the last `File` variable bound to a +given `FILE*` goes out of scope, the underlying `FILE*` is automatically closed. Example: ---- // test.d +import std.stdio; + void main(string[] args) { auto f = File("test.txt", "w"); // open for writing @@ -378,6 +484,7 @@ Hello, Jimmy! */ struct File { + import core.atomic : atomicOp, atomicStore, atomicLoad; import std.range.primitives : ElementEncodingType; import std.traits : isScalarType, isArray; enum Orientation { unknown, narrow, wide } @@ -385,7 +492,7 @@ struct File private struct Impl { FILE * handle = null; // Is null iff this Impl is closed by another File - uint refs = uint.max / 2; + shared uint refs = uint.max / 2; bool isPopened; // true iff the stream has been created by popen() Orientation orientation; } @@ -399,8 +506,14 @@ struct File assert(!_p); _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); + initImpl(handle, name, refs, isPopened); + } + + private void initImpl(FILE* handle, string name, uint refs = 1, bool isPopened = false) + { + assert(_p); _p.handle = handle; - _p.refs = refs; + atomicStore(_p.refs, refs); _p.isPopened = isPopened; _p.orientation = Orientation.unknown; _name = name; @@ -409,10 +522,10 @@ struct File /** 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) +Copying one `File` object to another results in the two `File` objects referring to the same underlying file. -The destructor automatically closes the file as soon as no $(D File) +The destructor automatically closes the file as soon as no `File` object refers to it anymore. Params: @@ -422,14 +535,14 @@ Params: $(HTTP cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function) -Throws: $(D ErrnoException) if the file could not be opened. +Throws: `ErrnoException` if the file could not be opened. */ - this(string name, in char[] stdioOpenmode = "rb") @safe + this(string name, scope const(char)[] stdioOpenmode = "rb") @safe { import std.conv : text; import std.exception : errnoEnforce; - this(errnoEnforce(.fopen(name, stdioOpenmode), + this(errnoEnforce(_fopen(name, stdioOpenmode), text("Cannot open file `", name, "' in mode `", stdioOpenmode, "'")), name); @@ -437,15 +550,7 @@ Throws: $(D ErrnoException) if the file could not be opened. // 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); + setAppendWin(stdioOpenmode); } } @@ -484,8 +589,8 @@ Throws: $(D ErrnoException) if the file could not be opened. this(this) @safe nothrow { if (!_p) return; - assert(_p.refs); - ++_p.refs; + assert(atomicLoad(_p.refs)); + atomicOp!"+="(_p.refs, 1); } /** @@ -493,25 +598,134 @@ 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 + ref File opAssign(File rhs) @safe return { import std.algorithm.mutation : swap; swap(this, rhs); + return this; + } + + // https://issues.dlang.org/show_bug.cgi?id=20129 + @safe unittest + { + File[int] aa; + aa.require(0, File.init); } /** -First calls $(D detach) (throwing on failure), and then attempts to -_open file $(D name) with mode $(D stdioOpenmode). The mode has the +Detaches from the current file (throwing on failure), and then attempts to +_open file `name` with mode `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. +Throws: `ErrnoException` in case of error. */ - void open(string name, in char[] stdioOpenmode = "rb") @safe + void open(string name, scope const(char)[] stdioOpenmode = "rb") @trusted { - detach(); - this = File(name, stdioOpenmode); + resetFile(name, stdioOpenmode, false); + } + + // https://issues.dlang.org/show_bug.cgi?id=20585 + @system unittest + { + File f; + try + f.open("doesn't exist"); + catch (Exception _e) + { + } + + assert(!f.isOpen); + + f.close(); // to check not crash here + } + + private void resetFile(string name, scope const(char)[] stdioOpenmode, bool isPopened) @trusted + { + import core.stdc.stdlib : malloc; + import std.exception : enforce; + import std.conv : text; + import std.exception : errnoEnforce; + + if (_p !is null) + { + detach(); + } + + FILE* handle; + version (Posix) + { + if (isPopened) + { + errnoEnforce(handle = _popen(name, stdioOpenmode), + "Cannot run command `"~name~"'"); + } + else + { + errnoEnforce(handle = _fopen(name, stdioOpenmode), + text("Cannot open file `", name, "' in mode `", + stdioOpenmode, "'")); + } + } + else + { + assert(isPopened == false); + errnoEnforce(handle = _fopen(name, stdioOpenmode), + text("Cannot open file `", name, "' in mode `", + stdioOpenmode, "'")); + } + _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); + initImpl(handle, name, 1, isPopened); + version (MICROSOFT_STDIO) + { + setAppendWin(stdioOpenmode); + } + } + + private void closeHandles() @trusted + { + assert(_p); + import std.exception : errnoEnforce; + + 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~"'"); + _p.handle = null; + return; + } + } + if (_p.handle) + { + auto handle = _p.handle; + _p.handle = null; + // fclose disassociates the FILE* even in case of error (issue 19751) + errnoEnforce(.fclose(handle) == 0, + "Could not close file `"~_name~"'"); + } + } + + version (MICROSOFT_STDIO) + { + private void setAppendWin(scope const(char)[] stdioOpenmode) @safe + { + bool append, update; + foreach (c; stdioOpenmode) + if (c == 'a') + append = true; + else + if (c == '+') + update = true; + if (append && !update) + seek(size); + } } /** @@ -525,9 +739,9 @@ function. Note: Calling `reopen` with a `null` `name` is not implemented in all C runtimes. -Throws: $(D ErrnoException) in case of error. +Throws: `ErrnoException` in case of error. */ - void reopen(string name, in char[] stdioOpenmode = "rb") @trusted + void reopen(string name, scope const(char)[] stdioOpenmode = "rb") @trusted { import std.conv : text; import std.exception : enforce, errnoEnforce; @@ -595,35 +809,36 @@ Throws: $(D ErrnoException) in case of error. } /** -First calls $(D detach) (throwing on failure), and then runs a command +Detaches from the current file (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. +Throws: `ErrnoException` in case of error. */ - version (Posix) void popen(string command, in char[] stdioOpenmode = "r") @safe + version (Posix) void popen(string command, scope const(char)[] stdioOpenmode = "r") @safe { - import std.exception : errnoEnforce; - - detach(); - this = File(errnoEnforce(.popen(command, stdioOpenmode), - "Cannot run command `"~command~"'"), - command, 1, true); + resetFile(command, stdioOpenmode ,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. +First calls `detach` (throwing on failure), then attempts to +associate the given file descriptor with the `File`, and sets the file's name to `null`. + +The mode must be compatible with the mode of the file descriptor. -Throws: $(D ErrnoException) in case of error. +Throws: `ErrnoException` in case of error. +Params: + fd = File descriptor to associate with this `File`. + stdioOpenmode = Mode to associate with this File. The mode has the same semantics + semantics as in the C standard library + $(HTTP cplusplus.com/reference/cstdio/fopen/, fdopen) function, and must be compatible with `fd`. */ - void fdopen(int fd, in char[] stdioOpenmode = "rb") @safe + void fdopen(int fd, scope const(char)[] stdioOpenmode = "rb") @safe { fdopen(fd, stdioOpenmode, null); } - package void fdopen(int fd, in char[] stdioOpenmode, string name) @trusted + package void fdopen(int fd, scope const(char)[] stdioOpenmode, string name) @trusted { import std.exception : errnoEnforce; import std.internal.cstring : tempCString; @@ -637,15 +852,14 @@ Throws: $(D ErrnoException) in case of error. // 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); + _FLOCK(fp); auto iob = cast(_iobuf*) fp; .close(iob._file); iob._file = fd; iob._flag &= ~_IOTRAN; - FUNLOCK(fp); + _FUNLOCK(fp); } else { @@ -666,17 +880,17 @@ Throws: $(D ErrnoException) in case of error. 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 +First calls `detach` (throwing on failure), and then attempts to +associate the given Windows `HANDLE` with the `File`. The mode must be compatible with the access attributes of the handle. Windows only. -Throws: $(D ErrnoException) in case of error. +Throws: `ErrnoException` in case of error. */ version (StdDdoc) - void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode); + void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode); version (Windows) - void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode) + void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode) { import core.stdc.stdint : intptr_t; import std.exception : errnoEnforce; @@ -709,17 +923,17 @@ Throws: $(D ErrnoException) in case of error. } -/** Returns $(D true) if the file is opened. */ +/** Returns `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 +Returns `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. +Throws: `Exception` if the file is not opened. */ @property bool eof() const @trusted pure { @@ -729,16 +943,22 @@ Throws: $(D Exception) if the file is not opened. 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 +/** + Returns the name last used to initialize this `File`, if any. + + Some functions that create or initialize the `File` set the name field to `null`. + Examples include $(LREF tmpfile), $(LREF wrapFile), and $(LREF fdopen). See the + documentation of those functions for details. + + Returns: The name last used to initialize this this file, or `null` otherwise. + */ + @property string name() const @safe pure nothrow return { return _name; } /** -If the file is not opened, returns $(D true). Otherwise, returns +If the file is not opened, returns `true`. Otherwise, returns $(HTTP cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for the file handle. */ @@ -749,7 +969,7 @@ the file handle. @safe unittest { - // Issue 12349 + // https://issues.dlang.org/show_bug.cgi?id=12349 static import std.file; auto deleteme = testFilename(); auto f = File(deleteme, "w"); @@ -760,20 +980,21 @@ the file handle. } /** -Detaches from the underlying file. If the sole owner, calls $(D close). +Detaches from the underlying file. If the sole owner, calls `close`. -Throws: $(D ErrnoException) on failure if closing the file. +Throws: `ErrnoException` on failure if closing the file. */ - void detach() @safe + void detach() @trusted { + import core.stdc.stdlib : free; + if (!_p) return; - if (_p.refs == 1) - close(); - else + scope(exit) _p = null; + + if (atomicOp!"-="(_p.refs, 1) == 0) { - assert(_p.refs); - --_p.refs; - _p = null; + scope(exit) free(_p); + closeHandles(); } } @@ -797,11 +1018,11 @@ 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 +File) object is empty. This is different from `detach` in that it +always closes the file; consequently, all other `File` objects referring to the same handle will see a closed file henceforth. -Throws: $(D ErrnoException) on error. +Throws: `ErrnoException` on error. */ void close() @trusted { @@ -811,30 +1032,14 @@ Throws: $(D ErrnoException) on error. if (!_p) return; // succeed vacuously scope(exit) { - assert(_p.refs); - if (!--_p.refs) + if (atomicOp!"-="(_p.refs, 1) == 0) 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~"'"); + closeHandles(); } /** @@ -849,12 +1054,12 @@ _clearerr) for the file handle. } /** -Flushes the C $(D FILE) buffers. +Flushes the C `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. +Throws: `Exception` if the file is not opened or if the call to `fflush` fails. */ void flush() @trusted { @@ -866,7 +1071,7 @@ Throws: $(D Exception) if the file is not opened or if the call to $(D fflush) f @safe unittest { - // Issue 12349 + // https://issues.dlang.org/show_bug.cgi?id=12349 import std.exception : assertThrown; static import std.file; @@ -880,15 +1085,17 @@ Throws: $(D Exception) if the file is not opened or if the call to $(D fflush) f /** 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. +Call $(LREF flush) before calling this function to flush the C `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 +`FlushFileBuffers`) on Windows, +$(HTTP developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html, +`F_FULLFSYNC fcntl`) on Darwin and $(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, -$(D fsync)) on POSIX for the file handle. +`fsync`) on POSIX for the file handle. -Throws: $(D Exception) if the file is not opened or if the OS call fails. +Throws: `Exception` if the file is not opened or if the OS call fails. */ void sync() @trusted { @@ -898,9 +1105,15 @@ Throws: $(D Exception) if the file is not opened or if the OS call fails. version (Windows) { - import core.sys.windows.windows : FlushFileBuffers; + import core.sys.windows.winbase : FlushFileBuffers; wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); } + else version (Darwin) + { + import core.sys.darwin.fcntl : fcntl, F_FULLFSYNC; + import std.exception : errnoEnforce; + errnoEnforce(fcntl(fileno, F_FULLFSYNC, 0) != -1, "fcntl failed"); + } else { import core.sys.posix.unistd : fsync; @@ -914,31 +1127,32 @@ 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 +Returns: The slice of `buffer` containing the data that was actually read. +This will be shorter than `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. +Throws: `Exception` if `buffer` is empty. + `ErrnoException` if the file is not opened or the call to `fread` fails. -$(D rawRead) always reads in binary mode on Windows. +`rawRead` always reads in binary mode on Windows. */ T[] rawRead(T)(T[] buffer) { - import std.exception : errnoEnforce; + import std.exception : enforce, errnoEnforce; if (!buffer.length) throw new Exception("rawRead must take a non-empty buffer"); + enforce(isOpen, "Attempting to read from an unopened file"); version (Windows) { - immutable fd = ._fileno(_p.handle); - immutable mode = ._setmode(fd, _O_BINARY); - scope(exit) ._setmode(fd, 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 + // https://issues.dlang.org/show_bug.cgi?id=4243 immutable info = __fhnd_info[fd]; atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); scope(exit) __fhnd_info[fd] = info; @@ -959,7 +1173,7 @@ $(D rawRead) always reads in binary mode on Windows. { static import std.file; - auto testFile = testFilename(); + auto testFile = std.file.deleteme(); std.file.write(testFile, "\r\n\n\r\n"); scope(exit) std.file.remove(testFile); @@ -969,15 +1183,40 @@ $(D rawRead) always reads in binary mode on Windows. assert(buf == "\r\n\n\r\n"); } + // https://issues.dlang.org/show_bug.cgi?id=21729 + @system unittest + { + import std.exception : assertThrown; + + File f; + ubyte[1] u; + assertThrown(f.rawRead(u)); + } + + // https://issues.dlang.org/show_bug.cgi?id=21728 + @system unittest + { + static if (__traits(compiles, { import std.process : pipe; })) // not available for iOS + { + import std.process : pipe; + import std.exception : assertThrown; + + auto p = pipe(); + p.readEnd.close; + ubyte[1] u; + assertThrown(p.readEnd.rawRead(u)); + } + } + /** 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. +`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. +Throws: `ErrnoException` if the file is not opened or if the call to `fwrite` fails. */ void rawWrite(T)(in T[] buffer) { @@ -986,21 +1225,37 @@ Throws: $(D ErrnoException) if the file is not opened or if the call to $(D fwri version (Windows) { - flush(); // before changing translation mode - immutable fd = ._fileno(_p.handle); - immutable mode = ._setmode(fd, _O_BINARY); - scope(exit) ._setmode(fd, mode); + immutable fd = .fileno(_p.handle); + immutable oldMode = .__setmode(fd, _O_BINARY); + + if (oldMode != _O_BINARY) + { + // need to flush the data that was written with the original mode + .__setmode(fd, oldMode); + flush(); // before changing translation mode .__setmode(fd, _O_BINARY); + .__setmode(fd, _O_BINARY); + } + version (DIGITAL_MARS_STDIO) { import core.atomic : atomicOp; - // @@@BUG@@@ 4243 + // https://issues.dlang.org/show_bug.cgi?id=4243 immutable info = __fhnd_info[fd]; atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); - scope(exit) __fhnd_info[fd] = info; + scope (exit) __fhnd_info[fd] = info; + } + + scope (exit) + { + if (oldMode != _O_BINARY) + { + flush(); + .__setmode(fd, oldMode); + } } - scope(exit) flush(); // before restoring translation mode } + auto result = trustedFwrite(_p.handle, buffer); if (result == result.max) result = 0; errnoEnforce(result == buffer.length, @@ -1014,7 +1269,7 @@ Throws: $(D ErrnoException) if the file is not opened or if the call to $(D fwri { static import std.file; - auto testFile = testFilename(); + auto testFile = std.file.deleteme(); auto f = File(testFile, "w"); scope(exit) std.file.remove(testFile); @@ -1025,16 +1280,34 @@ Throws: $(D ErrnoException) if the file is not opened or if the call to $(D fwri /** Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek) -for the file handle. +for the file handle to move its position indicator. -Throws: $(D Exception) if the file is not opened. - $(D ErrnoException) if the call to $(D fseek) fails. +Params: + offset = Binary files: Number of bytes to offset from origin.$(BR) + Text files: Either zero, or a value returned by $(LREF tell). + origin = Binary files: Position used as reference for the offset, must be + one of $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio), + $(REF_ALTTEXT SEEK_CUR, SEEK_CUR, core,stdc,stdio) or + $(REF_ALTTEXT SEEK_END, SEEK_END, core,stdc,stdio).$(BR) + Text files: Shall necessarily be + $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio). + +Throws: `Exception` if the file is not opened. + `ErrnoException` if the call to `fseek` fails. */ void seek(long offset, int origin = SEEK_SET) @trusted { import std.conv : to, text; import std.exception : enforce, errnoEnforce; + // Some libc sanitize the whence input (e.g. glibc), but some don't, + // e.g. Microsoft runtime crashes on an invalid origin, + // and Musl additionally accept SEEK_DATA & SEEK_HOLE (Linux extension). + // To provide a consistent behavior cross platform, we use the glibc check + // See also https://issues.dlang.org/show_bug.cgi?id=19797 + enforce(origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END, + "Invalid `origin` argument passed to `seek`, must be one of: SEEK_SET, SEEK_CUR, SEEK_END"); + enforce(isOpen, "Attempting to seek() in an unopened file"); version (Windows) { @@ -1062,6 +1335,7 @@ Throws: $(D Exception) if the file is not opened. { import std.conv : text; static import std.file; + import std.exception; auto deleteme = testFilename(); auto f = File(deleteme, "w+"); @@ -1084,14 +1358,16 @@ Throws: $(D Exception) if the file is not opened. // f.rawWrite("abcdefghijklmnopqrstuvwxyz"); // f.seek(-3, SEEK_END); // assert(f.readln() == "xyz"); + + assertThrown(f.seek(0, ushort.max)); } /** 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. +Throws: `Exception` if the file is not opened. + `ErrnoException` if the call to `ftell` fails. */ @property ulong tell() const @trusted { @@ -1121,7 +1397,7 @@ Throws: $(D Exception) if the file is not opened. import std.conv : text; static import std.file; - auto testFile = testFilename(); + auto testFile = std.file.deleteme(); std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz"); scope(exit) { std.file.remove(testFile); } @@ -1135,7 +1411,7 @@ Throws: $(D Exception) if the file is not opened. Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind) for the file handle. -Throws: $(D Exception) if the file is not opened. +Throws: `Exception` if the file is not opened. */ void rewind() @safe { @@ -1149,8 +1425,8 @@ Throws: $(D Exception) if the file is not opened. 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. +Throws: `Exception` if the file is not opened. + `ErrnoException` if the call to `setvbuf` fails. */ void setvbuf(size_t size, int mode = _IOFBF) @trusted { @@ -1165,8 +1441,8 @@ Throws: $(D Exception) if the file is not opened. 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. +Throws: `Exception` if the file is not opened. + `ErrnoException` if the call to `setvbuf` fails. */ void setvbuf(void[] buf, int mode = _IOFBF) @trusted { @@ -1181,7 +1457,8 @@ Throws: $(D Exception) if the file is not opened. version (Windows) { - import core.sys.windows.windows : ULARGE_INTEGER, OVERLAPPED, BOOL; + import core.sys.windows.winbase : OVERLAPPED; + import core.sys.windows.winnt : BOOL, ULARGE_INTEGER; private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length, Flags flags) @@ -1199,9 +1476,9 @@ Throws: $(D Exception) if the file is not opened. liLength.HighPart, &overlapped); } - private static T wenforce(T)(T cond, string str) + private static T wenforce(T)(T cond, lazy string str) { - import core.sys.windows.windows : GetLastError; + import core.sys.windows.winbase : GetLastError; import std.windows.syserror : sysErrorString; if (cond) return cond; @@ -1230,14 +1507,14 @@ Throws: $(D Exception) if the file is not opened. /** 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. +If both `start` and `length` are zero, the entire file is locked. -Locks created using $(D lock) and $(D tryLock) have the following properties: +Locks created using `lock` and `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.) + even locks acquired via a different `File` will be released as well.) $(LI Not all NFS implementations correctly implement file locking.) ) */ @@ -1259,7 +1536,7 @@ $(UL else version (Windows) { - import core.sys.windows.windows : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; + import core.sys.windows.winbase : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; immutable type = lockType == LockType.readWrite ? LOCKFILE_EXCLUSIVE_LOCK : 0; wenforce(lockImpl!LockFileEx(start, length, type), @@ -1271,8 +1548,8 @@ $(UL /** 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 +If both `start` and `length` are zero, the entire file is locked. +Returns: `true` if the lock was successful, and `false` if the specified file segment was already locked. */ bool tryLock(LockType lockType = LockType.readWrite, @@ -1297,8 +1574,9 @@ specified file segment was already locked. else version (Windows) { - import core.sys.windows.windows : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, - ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, LOCKFILE_FAIL_IMMEDIATELY; + import core.sys.windows.winbase : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, + LOCKFILE_FAIL_IMMEDIATELY; + import core.sys.windows.winerror : ERROR_IO_PENDING, ERROR_LOCK_VIOLATION; immutable type = lockType == LockType.readWrite ? LOCKFILE_EXCLUSIVE_LOCK : 0; immutable res = lockImpl!LockFileEx(start, length, @@ -1331,7 +1609,7 @@ Removes the lock over the specified file segment. else version (Windows) { - import core.sys.windows.windows : UnlockFileEx; + import core.sys.windows.winbase : UnlockFileEx; wenforce(lockImpl!UnlockFileEx(start, length), "Could not remove lock for file `"~_name~"'"); } @@ -1361,6 +1639,8 @@ Removes the lock over the specified file segment. version (Posix) @system unittest { + static if (__traits(compiles, { import std.process : spawnProcess; })) + { static import std.file; auto deleteme = testFilename(); scope(exit) std.file.remove(deleteme); @@ -1369,18 +1649,17 @@ Removes the lock over the specified file segment. // 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; + import core.sys.posix.sys.wait : waitpid; + import core.sys.posix.unistd : fork, _exit; int child, status; if ((child = fork()) == 0) { code(); - exit(0); + _exit(0); } else { - assert(wait(&status) != -1); + assert(waitpid(child, &status, 0) != -1); assert(status == 0, "Fork crashed"); } } @@ -1413,52 +1692,64 @@ Removes the lock over the specified file segment. g.unlock(); }); f.unlock(); - } + } // static if + } // unittest /** 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. +Throws: `Exception` if the file is not opened. + `ErrnoException` on an error writing to the file. */ void write(S...)(S args) { import std.traits : isBoolean, isIntegral, isAggregateType; + import std.utf : UTFException; auto w = lockingTextWriter(); foreach (arg; args) { - alias A = typeof(arg); - static if (isAggregateType!A || is(A == enum)) + try { - import std.format : formattedWrite; + alias A = typeof(arg); + static if (isAggregateType!A || is(A == enum)) + { + import std.format.write : formattedWrite; - formattedWrite(w, "%s", arg); - } - else static if (isSomeString!A) - { - put(w, arg); - } - else static if (isIntegral!A) - { - import std.conv : toTextRange; + 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); + 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.write : formattedWrite; + + // Most general case + formattedWrite(w, "%s", arg); + } } - else + catch (UTFException e) { - import std.format : formattedWrite; - - // Most general case - formattedWrite(w, "%s", arg); + /* Reset the writer so that it doesn't throw another + UTFException on destruction. */ + w.highSurrogate = '\0'; + throw e; } } } @@ -1466,8 +1757,8 @@ Throws: $(D Exception) if the file is not opened. /** 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. +Throws: `Exception` if the file is not opened. + `ErrnoException` on an error writing to the file. */ void writeln(S...)(S args) { @@ -1479,13 +1770,13 @@ 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). +fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 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. +Throws: `Exception` if the file is not opened. + `ErrnoException` on an error writing to the file. */ void writef(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt))) @@ -1500,7 +1791,7 @@ Throws: $(D Exception) if the file is not opened. /// ditto void writef(Char, A...)(in Char[] fmt, A args) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; formattedWrite(lockingTextWriter(), fmt, args); } @@ -1519,7 +1810,7 @@ Throws: $(D Exception) if the file is not opened. /// ditto void writefln(Char, A...)(in Char[] fmt, A args) { - import std.format : formattedWrite; + import std.format.write : formattedWrite; auto w = lockingTextWriter(); formattedWrite(w, fmt, args); @@ -1530,12 +1821,12 @@ Throws: $(D Exception) if the file is not opened. 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 +retaining a reference to the read data, consider the `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')). + S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. + terminator = Line terminator (by default, `'\n'`). Note: String terminators are not supported due to ambiguity with readln(buf) below. @@ -1544,7 +1835,7 @@ 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. + `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. Example: --- @@ -1576,8 +1867,8 @@ void main() 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[])) - { + static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) + {{ auto witness = [ "hello\n", "world\n" ]; auto f = File(deleteme); uint i = 0; @@ -1588,7 +1879,7 @@ void main() assert(equal(buf, witness[i++])); } assert(i == witness.length); - } + }} } @system unittest @@ -1600,17 +1891,17 @@ void main() 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) - { + static 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 +Read line from the file handle and write it to `buf[]`, including terminating character. This can be faster than $(D line = File.readln()) because you can reuse @@ -1619,15 +1910,16 @@ 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 +enlarged if necessary, then set to the slice exactly containing the line. +terminator = Line terminator (by default, `'\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 +0 for end of file, otherwise number of characters read. +The return value will always be equal to `buf.length`. -Throws: $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode +Throws: `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. Example: @@ -1635,6 +1927,7 @@ Example: // Read lines from `stdin` into a string // Ignore lines starting with '#' // Write the string to `stdout` +import std.stdio; void main() { @@ -1654,17 +1947,18 @@ void main() --- 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 +because `stdin.readln(buf)` reuses (if possible) memory allocated +for `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 +For even better performance you can help `readln` by passing in a large buffer to avoid memory reallocations. This can be done by reusing the -largest buffer returned by $(D readln): +largest buffer returned by `readln`: Example: --- // Read lines from `stdin` and count words +import std.array, std.stdio; void main() { @@ -1706,14 +2000,19 @@ is recommended if you want to process a complete file. } else { - // TODO: optimize this string s = readln(terminator); - buf.length = 0; - if (!s.length) return 0; - foreach (C c; s) + if (!s.length) { - buf ~= c; + buf = buf[0 .. 0]; + return 0; } + + import std.utf : codeLength; + buf.length = codeLength!C(s); + size_t idx; + foreach (C c; s) + buf[idx++] = c; + return buf.length; } } @@ -1736,7 +2035,8 @@ is recommended if you want to process a complete file. assert(buffer[beyond] == 'a'); } - @system unittest // bugzilla 15293 + // https://issues.dlang.org/show_bug.cgi?id=15293 + @system unittest { // @system due to readln static import std.file; @@ -1816,10 +2116,13 @@ is recommended if you want to process a complete file. /** * Reads formatted _data from the file using $(REF formattedRead, std,_format). * Params: - * format = The $(LINK2 std_format.html#_format-string, _format string). + * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). * When passed as a compile-time argument, the string will be statically checked * against the argument types passed. * data = Items to be read. + * Returns: + * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, + * this number will be less than the number of variables provided. * Example: ---- // test.d @@ -1854,9 +2157,9 @@ $(CONSOLE } /// ditto - uint readf(Data...)(in char[] format, auto ref Data data) + uint readf(Data...)(scope const(char)[] format, auto ref Data data) { - import std.format : formattedRead; + import std.format.read : formattedRead; assert(isOpen); auto input = LockingTextReader(this); @@ -1868,7 +2171,7 @@ $(CONSOLE { static import std.file; - auto deleteme = testFilename(); + auto deleteme = std.file.deleteme(); std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); scope(exit) std.file.remove(deleteme); string s; @@ -1899,7 +2202,7 @@ $(CONSOLE f.readf("%s\n", &s); assert(s == "world", "["~s~"]"); - // Issue 11698 + // https://issues.dlang.org/show_bug.cgi?id=11698 bool b1, b2; f.readf("%s\n%s\n", &b1, &b2); assert(b1 == true && b2 == false); @@ -1920,13 +2223,14 @@ $(CONSOLE assert(s1 == "hello"); assert(s2 == "world"); - // Issue 11698 + // https://issues.dlang.org/show_bug.cgi?id=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 + // Nice error of std.stdio.readf with newlines + // https://issues.dlang.org/show_bug.cgi?id=12260 @system unittest { static import std.file; @@ -1958,7 +2262,7 @@ $(CONSOLE } /** -Unsafe function that wraps an existing $(D FILE*). The resulting $(D +Unsafe function that wraps an existing `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 @@ -1970,7 +2274,7 @@ Note that the created file has no $(LREF name)*/ } /** -Returns the $(D FILE*) corresponding to this object. +Returns the `FILE*` corresponding to this object. */ FILE* getFP() @safe pure { @@ -1999,7 +2303,7 @@ Returns the file number corresponding to this object. } /** -Returns the underlying operating system $(D HANDLE) (Windows only). +Returns the underlying operating system `HANDLE` (Windows only). */ version (StdDdoc) @property HANDLE windowsHandle(); @@ -2020,7 +2324,7 @@ 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 struct ByLineImpl(Char, Terminator) { private: import std.typecons : RefCounted, RefCountedAutoInitialize; @@ -2068,6 +2372,7 @@ Allows to directly use range operations on lines of a file. Char[] buffer; Terminator terminator; KeepTerminator keepTerminator; + bool haveLine; public: this(File f, KeepTerminator kt, Terminator terminator) @@ -2075,22 +2380,32 @@ Allows to directly use range operations on lines of a file. file = f; this.terminator = terminator; keepTerminator = kt; - popFront(); } // Range primitive implementations. @property bool empty() { + needLine(); return line is null; } @property Char[] front() { + needLine(); return line; } void popFront() { + needLine(); + haveLine = false; + } + + private: + void needLine() + { + if (haveLine) + return; import std.algorithm.searching : endsWith; assert(file.isOpen); line = buffer; @@ -2112,36 +2427,37 @@ Allows to directly use range operations on lines of a file. else static if (isArray!Terminator) { static assert( - is(Unqual!(ElementEncodingType!Terminator) == Char)); + is(immutable ElementEncodingType!Terminator == immutable Char)); const tlen = terminator.length; } else static assert(false); line = line[0 .. line.length - tlen]; } + haveLine = true; } } } /** -Returns an input range set up to read from the file handle one line -at a time. +Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) +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. +The element type for the range will be `Char[]`. Range primitives +may throw `StdioException` on I/O error. Note: -Each $(D front) will not persist after $(D +Each `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 +calling `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 +Char = Character type for each line, defaulting to `char`. +keepTerminator = Use `Yes.keepTerminator` to include the terminator at the end of each line. -terminator = Line separator ($(D '\n') by default). Use +terminator = Line separator (`'\n'` by default). Use $(REF newline, std,ascii) for portability (unless the file was opened in text mode). @@ -2180,7 +2496,7 @@ void main() } ---- Notice that neither example accesses the line data returned by -$(D front) after the corresponding $(D popFront) call is made (because +`front` after the corresponding `popFront` call is made (because the contents may well have changed). */ auto byLine(Terminator = char, Char = char) @@ -2188,15 +2504,15 @@ the contents may well have changed). Terminator terminator = '\n') if (isScalarType!Terminator) { - return ByLine!(Char, Terminator)(this, keepTerminator, terminator); + return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); } /// ditto auto byLine(Terminator, Char = char) (KeepTerminator keepTerminator, Terminator terminator) - if (is(Unqual!(ElementEncodingType!Terminator) == Char)) + if (is(immutable ElementEncodingType!Terminator == immutable Char)) { - return ByLine!(Char, Terminator)(this, keepTerminator, terminator); + return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); } @system unittest @@ -2207,13 +2523,27 @@ the contents may well have changed). scope(success) std.file.remove(deleteme); import std.meta : AliasSeq; - foreach (T; AliasSeq!(char, wchar, dchar)) - { + static 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); - } + }} + } + + // https://issues.dlang.org/show_bug.cgi?id=19980 + @system unittest + { + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "Line 1\nLine 2\nLine 3\n"); + scope(success) std.file.remove(deleteme); + + auto f = File(deleteme); + f.byLine(); + f.byLine(); + assert(f.byLine().front == "Line 1"); } private struct ByLineCopy(Char, Terminator) @@ -2253,14 +2583,14 @@ the contents may well have changed). private struct ByLineCopyImpl(Char, Terminator) { - ByLine!(Unqual!Char, Terminator).Impl impl; + ByLineImpl!(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); + impl = ByLineImpl!(Unqual!Char, Terminator).Impl(f, kt, terminator); } @property bool empty() @@ -2286,21 +2616,22 @@ the contents may well have changed). } /** -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 +Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) +set up to read from the file handle one line +at a time. Each line will be newly allocated. `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). +`File.byLine.map!idup`. -The element type for the range will be $(D Char[]). Range -primitives may throw $(D StdioException) on I/O error. +The element type for the range will be `Char[]`. Range +primitives may throw `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 +keepTerminator = Use `Yes.keepTerminator` to include the terminator at the end of each line. -terminator = Line separator ($(D '\n') by default). Use +terminator = Line separator (`'\n'` by default). Use $(REF newline, std,ascii) for portability (unless the file was opened in text mode). @@ -2332,7 +2663,7 @@ $(REF readText, std,file) /// ditto auto byLineCopy(Terminator, Char = immutable char) (KeepTerminator keepTerminator, Terminator terminator) - if (is(Unqual!(ElementEncodingType!Terminator) == Unqual!Char)) + if (is(immutable ElementEncodingType!Terminator == immutable Char)) { return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); } @@ -2393,7 +2724,7 @@ $(REF readText, std,file) } assert(i == witness.length, text(i, " != ", witness.length)); - // Issue 11830 + // https://issues.dlang.org/show_bug.cgi?id=11830 auto walkedLength = File(deleteme).byLine(kt, term).walkLength; assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); @@ -2453,9 +2784,9 @@ $(REF readText, std,file) auto file = File.tmpfile(); file.write("1\n2\n3\n"); - // bug 9599 + // https://issues.dlang.org/show_bug.cgi?id=9599 file.rewind(); - File.ByLine!(char, char) fbl = file.byLine(); + File.ByLineImpl!(char, char) fbl = file.byLine(); auto fbl2 = fbl; assert(fbl.front == "1"); assert(fbl.front is fbl2.front); @@ -2489,10 +2820,10 @@ $(REF readText, std,file) } /** - Creates an input range set up to parse one line at a time from the file - into a tuple. + Creates an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + 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. + Range primitives may throw `StdioException` on I/O error. Params: format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) @@ -2507,9 +2838,9 @@ $(REF readText, std,file) */ template byRecord(Fields...) { - ByRecord!(Fields) byRecord(string format) + auto byRecord(string format) { - return typeof(return)(this, format); + return ByRecordImpl!(Fields)(this, format); } } @@ -2520,7 +2851,7 @@ $(REF readText, std,file) import std.typecons : tuple; // prepare test file - auto testFile = testFilename(); + auto testFile = std.file.deleteme(); 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); @@ -2540,7 +2871,7 @@ $(REF readText, std,file) /* * Range that reads a chunk at a time. */ - struct ByChunk + private struct ByChunkImpl { private: File file_; @@ -2568,7 +2899,7 @@ $(REF readText, std,file) prime(); } - // $(D ByChunk)'s input range primitive operations. + // `ByChunk`'s input range primitive operations. @property nothrow bool empty() const { @@ -2602,11 +2933,11 @@ $(REF readText, std,file) } /** -Returns an input range set up to read from the file handle a chunk at a -time. +Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) +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. +The element type for the range will be `ubyte[]`. Range primitives +may throw `StdioException` on I/O error. Example: --------- @@ -2621,7 +2952,7 @@ void main() --------- The parameter may be a number (as shown in the example above) dictating the -size of each chunk. Alternatively, $(D byChunk) accepts a +size of each chunk. Alternatively, `byChunk` accepts a user-provided buffer that it uses directly. Example: @@ -2637,14 +2968,14 @@ void main() --------- 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)). +`front` will not persist after `popFront` is called, so if retention is +needed, the caller must copy its contents (e.g. by calling `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 +In the example above, `buffer.length` is 4096 for all iterations, except +for the last one, in which case `buffer.length` may be less than 4096 (but always greater than zero). -With the mentioned limitations, $(D byChunk) works with any algorithm +With the mentioned limitations, `byChunk` works with any algorithm compatible with input ranges. Example: @@ -2671,21 +3002,21 @@ void main() } --- -Returns: A call to $(D byChunk) returns a range initialized with the $(D File) +Returns: A call to `byChunk` returns a range initialized with the `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). +is empty, throws an `Exception`. In case of an I/O error throws +`StdioException`. */ auto byChunk(size_t chunkSize) { - return ByChunk(this, chunkSize); + return ByChunkImpl(this, chunkSize); } /// Ditto - ByChunk byChunk(ubyte[] buffer) + auto byChunk(ubyte[] buffer) { - return ByChunk(this, buffer); + return ByChunkImpl(this, buffer); } @system unittest @@ -2740,53 +3071,88 @@ $(D StdioException). // Note: This was documented until 2013/08 /* -$(D Range) that locks the file and allows fast writing to it. +`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_; + // Access the FILE* handle through the 'file_' member + // to keep the object alive through refcounting + File file_; - // the unshared version of fps - @property _iobuf* handle_() @trusted { return cast(_iobuf*) fps_; } + // the unshared version of FILE* handle, extracted from the File object + @property _iobuf* handle_() @trusted { return cast(_iobuf*) file_._p.handle; } // the file's orientation (byte- or wide-oriented) int orientation_; + + // Buffers for when we need to transcode. + wchar highSurrogate = '\0'; // '\0' indicates empty + void highSurrogateShouldBeEmpty() @safe + { + import std.utf : UTFException; + if (highSurrogate != '\0') + throw new UTFException("unpaired surrogate UTF-16 value"); + } + char[4] rbuf8; + size_t rbuf8Filled = 0; 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_); + file_ = f; + FILE* fps = f._p.handle; + + version (MICROSOFT_STDIO) + { + // Microsoft doesn't implement fwide. Instead, there's the + // concept of ANSI/UNICODE mode. fputc doesn't work in UNICODE + // mode; fputwc has to be used. So that essentially means + // "wide-oriented" for us. + immutable int mode = __setmode(f.fileno, _O_TEXT); + // Set some arbitrary mode to obtain the previous one. + __setmode(f.fileno, mode); // Restore previous mode. + if (mode & (_O_WTEXT | _O_U16TEXT | _O_U8TEXT)) + { + orientation_ = 1; // wide + } + } + else + { + import core.stdc.wchar_ : fwide; + orientation_ = fwide(fps, 0); + } + + _FLOCK(fps); } ~this() @trusted { - if (fps_) + if (auto p = file_._p) { - FUNLOCK(fps_); - fps_ = null; + if (p.handle) _FUNLOCK(p.handle); } + file_ = File.init; + /* Destroy file_ before possibly throwing. Else it wouldn't be + destroyed, and its reference count would be wrong. */ + highSurrogateShouldBeEmpty(); } this(this) @trusted { - if (fps_) + if (auto p = file_._p) { - FLOCK(fps_); + if (p.handle) _FLOCK(p.handle); } } /// Range primitive implementations. - void put(A)(A writeme) - if ((isSomeChar!(Unqual!(ElementType!A)) || + void put(A)(scope A writeme) + if ((isSomeChar!(ElementType!A) || is(ElementType!A : const(ubyte))) && isInputRange!A && !isInfinite!A) @@ -2801,66 +3167,106 @@ $(D Range) that locks the file and allows fast writing to it. { //file.write(writeme); causes infinite recursion!!! //file.rawWrite(writeme); - auto result = trustedFwrite(fps_, writeme); + auto result = trustedFwrite(file_._p.handle, writeme); if (result != writeme.length) errnoEnforce(0); return; } } // put each element in turn. - alias Elem = Unqual!(ElementType!A); - foreach (Elem c; writeme) + foreach (c; writeme) { put(c); } } /// ditto - void put(C)(C c) @safe if (isSomeChar!C || is(C : const(ubyte))) + void put(C)(scope C c) @safe if (isSomeChar!C || is(C : const(ubyte))) { import std.traits : Parameters; + import std.utf : decodeFront, encode, stride; static auto trustedFPUTC(int ch, _iobuf* h) @trusted { - return FPUTC(ch, h); + return _FPUTC(ch, h); } - static auto trustedFPUTWC(Parameters!FPUTWC[0] ch, _iobuf* h) @trusted + static auto trustedFPUTWC(Parameters!_FPUTWC[0] ch, _iobuf* h) @trusted { - return FPUTWC(ch, h); + return _FPUTWC(ch, h); } static if (c.sizeof == 1) { - // simple char + highSurrogateShouldBeEmpty(); if (orientation_ <= 0) trustedFPUTC(c, handle_); - else trustedFPUTWC(c, handle_); + else if (c <= 0x7F) trustedFPUTWC(c, handle_); + else if (c >= 0b1100_0000) // start byte of multibyte sequence + { + rbuf8[0] = c; + rbuf8Filled = 1; + } + else // continuation byte of multibyte sequence + { + rbuf8[rbuf8Filled] = c; + ++rbuf8Filled; + if (stride(rbuf8[]) == rbuf8Filled) // sequence is complete + { + char[] str = rbuf8[0 .. rbuf8Filled]; + immutable dchar d = decodeFront(str); + wchar_t[4 / wchar_t.sizeof] wbuf; + immutable size = encode(wbuf, d); + foreach (i; 0 .. size) + trustedFPUTWC(wbuf[i], handle_); + rbuf8Filled = 0; + } + } } else static if (c.sizeof == 2) { - import std.utf : encode, UseReplacementDchar; + import std.utf : decode; - if (orientation_ <= 0) + if (c <= 0x7F) { - if (c <= 0x7F) + highSurrogateShouldBeEmpty(); + if (orientation_ <= 0) trustedFPUTC(c, handle_); + else trustedFPUTWC(c, handle_); + } + else if (0xD800 <= c && c <= 0xDBFF) // high surrogate + { + highSurrogateShouldBeEmpty(); + highSurrogate = c; + } + else // standalone or low surrogate + { + dchar d = c; + if (highSurrogate != '\0') { - trustedFPUTC(c, handle_); + immutable wchar[2] rbuf = [highSurrogate, c]; + size_t index = 0; + d = decode(rbuf[], index); + highSurrogate = 0; + } + if (orientation_ <= 0) + { + char[4] wbuf; + immutable size = encode(wbuf, d); + foreach (i; 0 .. size) + trustedFPUTC(wbuf[i], handle_); } else { - char[4] buf; - immutable size = encode!(UseReplacementDchar.yes)(buf, c); - foreach (i ; 0 .. size) - trustedFPUTC(buf[i], handle_); + wchar_t[4 / wchar_t.sizeof] wbuf; + immutable size = encode(wbuf, d); + foreach (i; 0 .. size) + trustedFPUTWC(wbuf[i], handle_); } - } - else - { - trustedFPUTWC(c, handle_); + rbuf8Filled = 0; } } else // 32-bit characters { import std.utf : encode; + highSurrogateShouldBeEmpty(); if (orientation_ <= 0) { if (c <= 0x7F) @@ -2884,21 +3290,21 @@ $(D Range) that locks the file and allows fast writing to it. assert(isValidDchar(c)); if (c <= 0xFFFF) { - trustedFPUTWC(c, handle_); + trustedFPUTWC(cast(wchar_t) c, handle_); } else { - trustedFPUTWC(cast(wchar) + trustedFPUTWC(cast(wchar_t) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800), handle_); - trustedFPUTWC(cast(wchar) + trustedFPUTWC(cast(wchar_t) (((c - 0x10000) & 0x3FF) + 0xDC00), handle_); } } else version (Posix) { - trustedFPUTWC(c, handle_); + trustedFPUTWC(cast(wchar_t) c, handle_); } else { @@ -2909,10 +3315,24 @@ $(D Range) that locks the file and allows fast writing to it. } } -/** Returns an output range that locks the file and allows fast writing to it. - -See $(LREF byChunk) for an example. -*/ + /** + * Output range which locks the file when created, and unlocks the file when it goes + * out of scope. + * + * Returns: An $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + * which accepts string types, `ubyte[]`, individual character types, and + * individual `ubyte`s. + * + * Note: Writing either arrays of `char`s or `ubyte`s is faster than + * writing each character individually from a range. For large amounts of data, + * writing the contents in chunks using an intermediary array can result + * in a speed increase. + * + * Throws: $(REF UTFException, std, utf) if the data given is a `char` range + * and it contains malformed UTF data. + * + * See_Also: $(LREF byChunk) for an example. + */ auto lockingTextWriter() @safe { return LockingTextWriter(this); @@ -2925,7 +3345,9 @@ See $(LREF byChunk) for an example. { import std.traits : hasIndirections; private: - FILE* fps; + // Access the FILE* handle through the 'file_' member + // to keep the object alive through refcounting + File file_; string name; version (Windows) @@ -2935,52 +3357,54 @@ See $(LREF byChunk) for an example. ubyte oldInfo; } - package: - this(ref File f) + public: + // Don't use this, but `File.lockingBinaryWriter()` instead. + // Must be public for RefCounted and emplace() in druntime. + this(scope ref File f) { import std.exception : enforce; - + file_ = f; enforce(f._p && f._p.handle); name = f._name; - fps = f._p.handle; + FILE* fps = f._p.handle; static if (locking) - FLOCK(fps); + _FLOCK(fps); version (Windows) { .fflush(fps); // before changing translation mode - fd = ._fileno(fps); - oldMode = ._setmode(fd, _O_BINARY); + fd = .fileno(fps); + oldMode = .__setmode(fd, _O_BINARY); version (DIGITAL_MARS_STDIO) { import core.atomic : atomicOp; - // @@@BUG@@@ 4243 + // https://issues.dlang.org/show_bug.cgi?id=4243 oldInfo = __fhnd_info[fd]; atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); } } } - public: ~this() { - if (!fps) + if (!file_._p || !file_._p.handle) return; + FILE* fps = file_._p.handle; + version (Windows) { .fflush(fps); // before restoring translation mode version (DIGITAL_MARS_STDIO) { - // @@@BUG@@@ 4243 + // https://issues.dlang.org/show_bug.cgi?id=4243 __fhnd_info[fd] = oldInfo; } - ._setmode(fd, oldMode); + .__setmode(fd, oldMode); } - FUNLOCK(fps); - fps = null; + _FUNLOCK(fps); } void rawWrite(T)(in T[] buffer) @@ -2988,7 +3412,7 @@ See $(LREF byChunk) for an example. import std.conv : text; import std.exception : errnoEnforce; - auto result = trustedFwrite(fps, buffer); + auto result = trustedFwrite(file_._p.handle, buffer); if (result == result.max) result = 0; errnoEnforce(result == buffer.length, text("Wrote ", result, " instead of ", buffer.length, @@ -3004,21 +3428,21 @@ See $(LREF byChunk) for an example. { this(this) { - if (fps) + if (auto p = file_._p) { - FLOCK(fps); + if (p.handle) _FLOCK(p.handle); } } } - void put(T)(auto ref in T value) + void put(T)(auto ref scope const T value) if (!hasIndirections!T && !isInputRange!T) { rawWrite((&value)[0 .. 1]); } - void put(T)(in T[] array) + void put(T)(scope const(T)[] array) if (!hasIndirections!T && !isInputRange!T) { @@ -3032,7 +3456,7 @@ 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; +import std.algorithm, std.complex, std.range, std.stdio; void main() { @@ -3042,7 +3466,7 @@ void main() 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) + recurrence!((a, n) => x + y * complex(0, 1) + a[n-1]^^2)(complex(0)) .take(ubyte.max) .countUntil!(z => z.re^^2 + z.im^^2 > 4)) ) @@ -3076,6 +3500,19 @@ void main() auto deleteme = testFilename(); scope(exit) collectException(std.file.remove(deleteme)); + + { + auto writer = File(deleteme, "wb").lockingBinaryWriter(); + auto input = File(deleteme, "rb"); + + ubyte[1] byteIn = [42]; + writer.rawWrite(byteIn); + destroy(writer); + + ubyte[1] byteOut = input.rawRead(new ubyte[1]); + assert(byteIn[0] == byteOut[0]); + } + auto output = File(deleteme, "wb"); auto writer = output.lockingBinaryWriter(); auto input = File(deleteme, "rb"); @@ -3125,7 +3562,22 @@ void main() assert(dcharsOut == "foo"); } -/// Get the size of the file, ulong.max if file is not searchable, but still throws if an actual error occurs. +/** Returns the size of the file in bytes, ulong.max if file is not searchable or throws if the operation fails. +Example: +--- +import std.stdio, std.file; + +void main() +{ + string deleteme = "delete.me"; + auto file_handle = File(deleteme, "w"); + file_handle.write("abc"); //create temporary file + scope(exit) deleteme.remove; //remove temporary file at scope exit + + assert(file_handle.size() == 3); //check if file size is 3 bytes +} +--- +*/ @property ulong size() @safe { import std.exception : collectException; @@ -3300,20 +3752,155 @@ void main() scope(exit) std.file.remove(deleteme); { - File f = File(deleteme, "w"); - auto writer = f.lockingTextWriter(); + auto writer = File(deleteme, "w").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 + // https://issues.dlang.org/show_bug.cgi?id=11945 + writer.put(repeat('#', 12)); + // https://issues.dlang.org/show_bug.cgi?id=17229 + writer.put(cast(immutable(ubyte)[])"日本語"); } assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); } +@safe unittest // wchar -> char +{ + static import std.file; + import std.exception : assertThrown; + import std.utf : UTFException; + + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + + { + auto writer = File(deleteme, "w").lockingTextWriter(); + writer.put("\U0001F608"w); + } + assert(std.file.readText!string(deleteme) == "\U0001F608"); + + // Test invalid input: unpaired high surrogate + { + immutable wchar surr = "\U0001F608"w[0]; + auto f = File(deleteme, "w"); + assertThrown!UTFException(() { + auto writer = f.lockingTextWriter(); + writer.put('x'); + writer.put(surr); + assertThrown!UTFException(writer.put(char('y'))); + assertThrown!UTFException(writer.put(wchar('y'))); + assertThrown!UTFException(writer.put(dchar('y'))); + assertThrown!UTFException(writer.put(surr)); + // First `surr` is still unpaired at this point. `writer` gets + // destroyed now, and the destructor throws a UTFException for + // the unpaired surrogate. + } ()); + } + assert(std.file.readText!string(deleteme) == "x"); + + // Test invalid input: unpaired low surrogate + { + immutable wchar surr = "\U0001F608"w[1]; + auto writer = File(deleteme, "w").lockingTextWriter(); + assertThrown!UTFException(writer.put(surr)); + writer.put('y'); + assertThrown!UTFException(writer.put(surr)); + } + assert(std.file.readText!string(deleteme) == "y"); +} + +@safe unittest // issue 18801 +{ + static import std.file; + import std.string : stripLeft; + + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + + { + auto writer = File(deleteme, "w,ccs=UTF-8").lockingTextWriter(); + writer.put("foo"); + } + assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foo"); + + { + auto writer = File(deleteme, "a,ccs=UTF-8").lockingTextWriter(); + writer.put("bar"); + } + assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foobar"); +} +@safe unittest // char/wchar -> wchar_t +{ + import core.stdc.locale : LC_CTYPE, setlocale; + import core.stdc.wchar_ : fwide; + import core.stdc.string : strlen; + import std.algorithm.searching : any, endsWith; + import std.conv : text; + import std.meta : AliasSeq; + import std.string : fromStringz, stripLeft; + static import std.file; + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + const char* oldCt = () @trusted { + const(char)* p = setlocale(LC_CTYPE, null); + // Subsequent calls to `setlocale` might invalidate this return value, + // so duplicate it. + // See: https://github.com/dlang/phobos/pull/7660 + return p ? p[0 .. strlen(p) + 1].idup.ptr : null; + }(); + const utf8 = ["en_US.UTF-8", "C.UTF-8", ".65001"].any!((loc) @trusted { + return setlocale(LC_CTYPE, loc.ptr).fromStringz.endsWith(loc); + }); + scope(exit) () @trusted { setlocale(LC_CTYPE, oldCt); } (); + version (DIGITAL_MARS_STDIO) // DM can't handle Unicode above U+07FF. + { + alias strs = AliasSeq!("xä\u07FE", "yö\u07FF"w); + } + else + { + alias strs = AliasSeq!("xä\U0001F607", "yö\U0001F608"w); + } + { + auto f = File(deleteme, "w"); + version (MICROSOFT_STDIO) + { + () @trusted { __setmode(fileno(f.getFP()), _O_U8TEXT); } (); + } + else + { + assert(fwide(f.getFP(), 1) == 1); + } + auto writer = f.lockingTextWriter(); + assert(writer.orientation_ == 1); + static foreach (s; strs) writer.put(s); + } + assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == + text(strs)); +} +@safe unittest // https://issues.dlang.org/show_bug.cgi?id=18789 +{ + static import std.file; + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + // converting to char + { + auto f = File(deleteme, "w"); + f.writeln("\U0001F608"w); // UTFException + } + // converting to wchar_t + { + auto f = File(deleteme, "w,ccs=UTF-16LE"); + // from char + f.writeln("ö"); // writes garbage + f.writeln("\U0001F608"); // ditto + // from wchar + f.writeln("\U0001F608"w); // leads to ErrnoException + } +} + @safe unittest { import std.exception : collectException; @@ -3321,7 +3908,37 @@ void main() assert(e && e.msg == "Attempting to write to closed File"); } -/// Used to specify the lock type for $(D File.lock) and $(D File.tryLock). +@safe unittest // https://issues.dlang.org/show_bug.cgi?id=21592 +{ + import std.exception : collectException; + import std.utf : UTFException; + static import std.file; + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + auto f = File(deleteme, "w"); + auto e = collectException!UTFException(f.writeln(wchar(0xD801))); + assert(e.next is null); +} + +version (StdStressTest) +{ + // https://issues.dlang.org/show_bug.cgi?id=15768 + @system unittest + { + import std.parallelism : parallel; + import std.range : iota; + + auto deleteme = testFilename(); + stderr = File(deleteme, "w"); + + foreach (t; 1_000_000.iota.parallel) + { + stderr.write("aaa"); + } + } +} + +/// Used to specify the lock type for `File.lock` and `File.tryLock`. enum LockType { /** @@ -3353,12 +3970,12 @@ struct LockingTextReader import std.exception : enforce; enforce(f.isOpen, "LockingTextReader: File must be open"); _f = f; - FLOCK(_f._p.handle); + _FLOCK(_f._p.handle); } this(this) { - FLOCK(_f._p.handle); + _FLOCK(_f._p.handle); } ~this() @@ -3367,7 +3984,7 @@ struct LockingTextReader ungetc(_front, cast(FILE*)_f._p.handle); // File locking has its own reference count - if (_f.isOpen) FUNLOCK(_f._p.handle); + if (_f.isOpen) _FUNLOCK(_f._p.handle); } void opAssign(LockingTextReader r) @@ -3382,7 +3999,7 @@ struct LockingTextReader { if (!_f.isOpen || _f.eof) return true; - immutable int c = FGETC(cast(_iobuf*) _f._p.handle); + immutable int c = _FGETC(cast(_iobuf*) _f._p.handle); if (c == EOF) { .destroy(_f); @@ -3430,7 +4047,7 @@ struct LockingTextReader auto deleteme = testFilename(); std.file.write(deleteme, "1 2 3"); scope(exit) std.file.remove(deleteme); - int x, y; + int x; auto f = File(deleteme); f.readf("%s ", &x); assert(x == 1); @@ -3440,7 +4057,8 @@ struct LockingTextReader assert(x == 3); } -@system unittest // bugzilla 13686 +// https://issues.dlang.org/show_bug.cgi?id=13686 +@system unittest { import std.algorithm.comparison : equal; static import std.file; @@ -3458,7 +4076,8 @@ struct LockingTextReader assert(equal(ltr, "Тест".byDchar)); } -@system unittest // bugzilla 12320 +// https://issues.dlang.org/show_bug.cgi?id=12320 +@system unittest { static import std.file; auto deleteme = testFilename(); @@ -3472,7 +4091,8 @@ struct LockingTextReader assert(ltr.empty); } -@system unittest // bugzilla 14861 +// https://issues.dlang.org/show_bug.cgi?id=14861 +@system unittest { // @system due to readf static import std.file; @@ -3492,7 +4112,7 @@ struct LockingTextReader } /** - * Indicates whether $(D T) is a file handle, i.e. the type + * Indicates whether `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). * @@ -3521,15 +4141,12 @@ private @property File trustedStdout() @trusted } /*********************************** -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. +Writes its arguments in text format to standard output (without a trailing newline). Params: args = the items to write to `stdout` -Throws: In case of an I/O error, throws an $(D StdioException). +Throws: In case of an I/O error, throws an `StdioException`. Example: Reads `stdin` and writes it to `stdout` with an argument @@ -3581,7 +4198,7 @@ if (!is(T[0] : File)) * 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 + * Reads `stdin` and writes it to `stdout` with an argument * counter. --- import std.stdio; @@ -3599,7 +4216,6 @@ void main() */ void writeln(T...)(T args) { - import std.traits : isAggregateType; static if (T.length == 0) { import std.exception : enforce; @@ -3607,17 +4223,13 @@ void writeln(T...)(T args) 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]))) + is(T[0] : const(char)[]) && + (is(T[0] == U[], U) || __traits(isStaticArray, T[0]))) { - import std.traits : isStaticArray; - // Specialization for strings - a very frequent case auto w = .trustedStdout.lockingTextWriter(); - static if (isStaticArray!(typeof(args[0]))) + static if (__traits(isStaticArray, T[0])) { w.put(args[0][]); } @@ -3641,15 +4253,19 @@ void writeln(T...)(T args) if (false) writeln("wyda"); - // bug 8040 + // https://issues.dlang.org/show_bug.cgi?id=8040 if (false) writeln(null); if (false) writeln(">", null, "<"); - // Bugzilla 14041 + // https://issues.dlang.org/show_bug.cgi?id=14041 if (false) { char[8] a; writeln(a); + immutable b = a; + b.writeln; + const c = a[]; + c.writeln; } } @@ -3687,9 +4303,9 @@ void writeln(T...)(T args) stdout.open(deleteme, "w"); writeln("Hello!"c); - writeln("Hello!"w); // bug 8386 - writeln("Hello!"d); // bug 8386 - writeln("embedded\0null"c); // bug 8730 + writeln("Hello!"w); // https://issues.dlang.org/show_bug.cgi?id=8386 + writeln("Hello!"d); // https://issues.dlang.org/show_bug.cgi?id=8386 + writeln("embedded\0null"c); // https://issues.dlang.org/show_bug.cgi?id=8730 stdout.close(); version (Windows) assert(cast(char[]) std.file.read(deleteme) == @@ -3708,8 +4324,8 @@ void writeln(T...)(T args) scope(exit) { std.file.remove(deleteme); } enum EI : int { A, B } - enum ED : double { A, B } - enum EC : char { A, B } + enum ED : double { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle + enum EC : char { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle enum ES : string { A = "aaa", B = "bbb" } f.writeln(EI.A); // false, but A on 2.058 @@ -3745,12 +4361,19 @@ void writeln(T...)(T args) useInit(stdout.lockingTextWriter()); } +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=21920 + void function(string) printer = &writeln!string; + if (false) printer("Hello"); +} + /*********************************** Writes formatted data to standard output (without a trailing newline). Params: -fmt = The $(LINK2 std_format.html#format-string, format string). +fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). When passed as a compile-time argument, the string will be statically checked against the argument types passed. args = Items to write. @@ -3761,7 +4384,7 @@ 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 +to print a message to `stderr`. This syntax is no longer supported, and has been superceded by: ------ @@ -3861,12 +4484,15 @@ void writefln(Char, A...)(in Char[] fmt, A args) } /** - * Reads formatted data from $(D stdin) using $(REF formattedRead, std,_format). + * Reads formatted data from `stdin` using $(REF formattedRead, std,_format). * Params: - * format = The $(LINK2 std_format.html#_format-string, _format string). + * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). * When passed as a compile-time argument, the string will be statically checked * against the argument types passed. * args = Items to be read. + * Returns: + * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, + * this number will be less than the number of variables provided. * Example: ---- // test.d @@ -3899,7 +4525,7 @@ if (isSomeString!(typeof(format))) } /// ditto -uint readf(A...)(in char[] format, auto ref A args) +uint readf(A...)(scope const(char)[] format, auto ref A args) { return stdin.readf(format, args); } @@ -3907,7 +4533,7 @@ uint readf(A...)(in char[] format, auto ref A args) @system unittest { float f; - if (false) uint x = readf("%s", &f); + if (false) readf("%s", &f); char a; wchar b; @@ -3919,23 +4545,23 @@ uint readf(A...)(in char[] format, auto ref A args) } /********************************** - * Read line from $(D stdin). + * Read line from `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 + * retaining a reference to the read data, consider the `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')). + * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. + * terminator = Line terminator (by default, `'\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. + * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. * Example: - * Reads $(D stdin) and writes it to $(D stdout). + * Reads `stdin` and writes it to `stdout`. --- import std.stdio; @@ -3954,22 +4580,22 @@ if (isSomeString!S) } /********************************** - * Read line from $(D stdin) and write it to buf[], including terminating character. + * Read line from `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 + * `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) + * terminator = Line terminator (by default, `'\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. + * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. * Example: - * Reads $(D stdin) and writes it to $(D stdout). + * Reads `stdin` and writes it to `stdout`. --- import std.stdio; @@ -4005,27 +4631,27 @@ if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && { readln(); readln('\t'); - foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) + static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) { readln!String(); readln!String('\t'); } - foreach (String; AliasSeq!(char[], wchar[], dchar[])) - { + static foreach (String; AliasSeq!(char[], wchar[], dchar[])) + {{ String buf; readln(buf); readln(buf, '\t'); readln(buf, "
"); - } + }} } } /* - * Convenience function that forwards to $(D core.sys.posix.stdio.fopen) - * (to $(D _wfopen) on Windows) + * Convenience function that forwards to `core.sys.posix.stdio.fopen` + * (to `_wfopen` on Windows) * with appropriately-constructed C-style strings. */ -private FILE* fopen(R1, R2)(R1 name, R2 mode = "r") +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)) { @@ -4034,7 +4660,7 @@ if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) auto namez = name.tempCString!FSChar(); auto modez = mode.tempCString!FSChar(); - static fopenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc + static _fopenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc { version (Windows) { @@ -4055,19 +4681,19 @@ if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) } else { - return .fopen(namez, modez); + return fopen(namez, modez); } } - return fopenImpl(namez, modez); + return _fopenImpl(namez, modez); } version (Posix) { /*********************************** - * Convenience function that forwards to $(D core.sys.posix.stdio.popen) + * Convenience function that forwards to `core.sys.posix.stdio.popen` * with appropriately-constructed C-style strings. */ - FILE* popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc + 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)) { @@ -4086,7 +4712,7 @@ version (Posix) } /* - * Convenience function that forwards to $(D core.stdc.stdio.fwrite) + * Convenience function that forwards to `core.stdc.stdio.fwrite` */ private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted { @@ -4094,7 +4720,7 @@ private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted } /* - * Convenience function that forwards to $(D core.stdc.stdio.fread) + * Convenience function that forwards to `core.stdc.stdio.fread` */ private auto trustedFread(T)(FILE* f, T[] obj) @trusted { @@ -4102,7 +4728,7 @@ private auto trustedFread(T)(FILE* f, T[] obj) @trusted } /** - * Iterates through the lines of a file by using $(D foreach). + * Iterates through the lines of a file by using `foreach`. * * Example: * @@ -4115,24 +4741,24 @@ void main() } } --------- -The line terminator ($(D '\n') by default) is part of the string read (it +The line terminator (`'\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) +supported for `line`, and the behavior of `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 +$(OL $(LI If `line` has type `string`, $(D +wstring), or `dstring`, a new string of the respective type +is allocated every read.) $(LI If `line` has type $(D +char[]), `wchar[]`, `dchar[]`, the line's content +will be reused (overwritten) across reads.) $(LI If `line` +has type `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 +If `line` has type `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 +the first symbol (of integral type, e.g. `ulong` or $(D uint)) tracks the zero-based number of the current line. Example: @@ -4143,7 +4769,7 @@ Example: } ---- - In case of an I/O error, an $(D StdioException) is thrown. + In case of an I/O error, an `StdioException` is thrown. See_Also: $(LREF byLine) @@ -4158,7 +4784,7 @@ struct lines Constructor. Params: f = File to read lines from. - terminator = Line separator ($(D '\n') by default). + terminator = Line separator (`'\n'` by default). */ this(File f, dchar terminator = '\n') { @@ -4172,8 +4798,6 @@ struct lines 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; @@ -4220,12 +4844,12 @@ struct lines enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); int result = 1; int c = void; - FLOCK(f._p.handle); - scope(exit) FUNLOCK(f._p.handle); + _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) + while ((c = _FGETC(cast(_iobuf*) f._p.handle)) != -1) { buffer ~= to!(ubyte)(c); if (c == terminator) @@ -4235,8 +4859,8 @@ struct lines else alias arg = buffer; // unlock the file while calling the delegate - FUNLOCK(f._p.handle); - scope(exit) FLOCK(f._p.handle); + _FUNLOCK(f._p.handle); + scope(exit) _FLOCK(f._p.handle); static if (Parms.length == 1) { result = dg(arg); @@ -4251,7 +4875,7 @@ struct lines buffer.length = 0; } } - // can only reach when FGETC returned -1 + // can only reach when _FGETC returned -1 if (!f.eof) throw new StdioException("Error in reading file"); // error occured return result; } @@ -4354,7 +4978,7 @@ struct lines } - foreach (T; AliasSeq!(ubyte[])) + static foreach (T; AliasSeq!(ubyte[])) { // test looping with a file with three lines, last without a newline // using a counter too this time @@ -4374,7 +4998,7 @@ struct lines } /** -Iterates through a file a chunk at a time by using $(D foreach). +Iterates through a file a chunk at a time by using `foreach`. Example: @@ -4388,12 +5012,12 @@ void main() } --------- -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 +The content of `buffer` is reused across calls. In the + example above, `buffer.length` is 4096 for all iterations, + except for the last one, in which case `buffer.length` may be less than 4096 (but always greater than zero). - In case of an I/O error, an $(D StdioException) is thrown. + In case of an I/O error, an `StdioException` is thrown. */ auto chunks(File f, size_t size) { @@ -4410,7 +5034,7 @@ private struct ChunksImpl { assert(size, "size must be larger than 0"); } - body + do { this.f = f; this.size = size; @@ -4419,6 +5043,9 @@ private struct ChunksImpl int opApply(D)(scope D dg) { import core.stdc.stdlib : alloca; + import std.exception : enforce; + + enforce(f.isOpen, "Attempting to read from an unopened file"); enum maxStackSize = 1024 * 16; ubyte[] buffer = void; if (size < maxStackSize) @@ -4484,12 +5111,26 @@ private struct ChunksImpl f.close(); } +// Issue 21730 - null ptr dereferenced in ChunksImpl.opApply (SIGSEGV) +@system unittest +{ + import std.exception : assertThrown; + static import std.file; + + auto deleteme = testFilename(); + scope(exit) { if (std.file.exists(deleteme)) std.file.remove(deleteme); } + + auto err1 = File(deleteme, "w+x"); + err1.close; + std.file.remove(deleteme); + assertThrown(() {foreach (ubyte[] buf; chunks(err1, 4096)) {}}()); +} /** 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 +rather than encoded according to the `File`'s $(HTTP en.cppreference.com/w/c/io#Narrow_and_wide_orientation, orientation). */ @@ -4533,7 +5174,7 @@ Initialize with a message and an error code. : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); } -/** Convenience functions that throw an $(D StdioException). */ +/** Convenience functions that throw an `StdioException`. */ static void opCall(string msg) { throw new StdioException(msg); @@ -4587,10 +5228,19 @@ enum StdFileHandle: string } /** 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. + + Returns: + stdin as a $(LREF File). + + Note: + The returned $(LREF File) wraps $(REF stdin,core,stdc,stdio), and + is therefore thread global. Reassigning `stdin` to a different + `File` must be done in a single-threaded or locked context in + order to avoid race conditions. + + All reading from `stdin` automatically locks the file globally, + and will cause all other threads calling `read` to wait until + the lock is released. */ alias stdin = makeGlobal!(StdFileHandle.stdin); @@ -4603,7 +5253,8 @@ alias stdin = makeGlobal!(StdFileHandle.stdin); import std.array : array; import std.typecons : Yes; - void main() { + void main() + { stdin // read from stdin .byLineCopy(Yes.keepTerminator) // copying each line .array() // convert to array of lines @@ -4615,22 +5266,94 @@ alias stdin = makeGlobal!(StdFileHandle.stdin); /** 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. + + Returns: + stdout as a $(LREF File). + + Note: + The returned $(LREF File) wraps $(REF stdout,core,stdc,stdio), and + is therefore thread global. Reassigning `stdout` to a different + `File` must be done in a single-threaded or locked context in + order to avoid race conditions. + + All writing to `stdout` automatically locks the file globally, + and will cause all other threads calling `write` to wait until + the lock is released. */ alias stdout = makeGlobal!(StdFileHandle.stdout); +/// +@safe unittest +{ + void main() + { + stdout.writeln("Write a message to stdout."); + } +} + +/// +@safe unittest +{ + void main() + { + import std.algorithm.iteration : filter, map, sum; + import std.format : format; + import std.range : iota, tee; + + int len; + const r = 6.iota + .filter!(a => a % 2) // 1 3 5 + .map!(a => a * 2) // 2 6 10 + .tee!(_ => stdout.writefln("len: %d", len++)) + .sum; + + assert(r == 18); + } +} + +/// +@safe unittest +{ + void main() + { + import std.algorithm.mutation : copy; + import std.algorithm.iteration : map; + import std.format : format; + import std.range : iota; + + 10.iota + .map!(e => "N: %d".format(e)) + .copy(stdout.lockingTextWriter()); // the OutputRange + } +} + /** 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. + + Returns: + stderr as a $(LREF File). + + Note: + The returned $(LREF File) wraps $(REF stderr,core,stdc,stdio), and + is therefore thread global. Reassigning `stderr` to a different + `File` must be done in a single-threaded or locked context in + order to avoid race conditions. + + All writing to `stderr` automatically locks the file globally, + and will cause all other threads calling `write` to wait until + the lock is released. */ alias stderr = makeGlobal!(StdFileHandle.stderr); +/// +@safe unittest +{ + void main() + { + stderr.writeln("Write a message to stderr."); + } +} + @system unittest { static import std.file; @@ -4745,8 +5468,8 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie { version (DIGITAL_MARS_STDIO) { - FLOCK(fps); - scope(exit) FUNLOCK(fps); + _FLOCK(fps); + scope(exit) _FUNLOCK(fps); /* Since fps is now locked, we can create an "unshared" version * of fp. @@ -4761,7 +5484,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie * Read them and convert to chars. */ static assert(wchar_t.sizeof == 2); - for (int c = void; (c = FGETWC(fp)) != -1; ) + for (int c = void; (c = _FGETWC(fp)) != -1; ) { if ((c & ~0x7F) == 0) { @@ -4774,7 +5497,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie if (c >= 0xD800 && c <= 0xDBFF) { int c2 = void; - if ((c2 = FGETWC(fp)) != -1 || + if ((c2 = _FGETWC(fp)) != -1 || c2 < 0xDC00 && c2 > 0xDFFF) { StdioException("unpaired UTF-16 surrogate"); @@ -4796,7 +5519,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie */ L1: int c; - while ((c = FGETC(fp)) != -1) + while ((c = _FGETC(fp)) != -1) { app.putchar(cast(char) c); if (c == terminator) @@ -4866,8 +5589,8 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie } else version (MICROSOFT_STDIO) { - FLOCK(fps); - scope(exit) FUNLOCK(fps); + _FLOCK(fps); + scope(exit) _FUNLOCK(fps); /* Since fps is now locked, we can create an "unshared" version * of fp. @@ -4878,7 +5601,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie app.initialize(buf); int c; - while ((c = FGETC(fp)) != -1) + while ((c = _FGETC(fp)) != -1) { app.putchar(cast(char) c); if (c == terminator) @@ -4904,13 +5627,13 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie /* Stream is in wide characters. * Read them and convert to chars. */ - FLOCK(fps); - scope(exit) FUNLOCK(fps); + _FLOCK(fps); + scope(exit) _FUNLOCK(fps); auto fp = cast(_iobuf*) fps; version (Windows) { buf.length = 0; - for (int c = void; (c = FGETWC(fp)) != -1; ) + for (int c = void; (c = _FGETWC(fp)) != -1; ) { if ((c & ~0x7F) == 0) { buf ~= c; @@ -4922,7 +5645,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie if (c >= 0xD800 && c <= 0xDBFF) { int c2 = void; - if ((c2 = FGETWC(fp)) != -1 || + if ((c2 = _FGETWC(fp)) != -1 || c2 < 0xDC00 && c2 > 0xDFFF) { StdioException("unpaired UTF-16 surrogate"); @@ -4940,7 +5663,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie else version (Posix) { buf.length = 0; - for (int c; (c = FGETWC(fp)) != -1; ) + for (int c; (c = _FGETWC(fp)) != -1; ) { import std.utf : encode; @@ -4998,8 +5721,8 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie { import core.stdc.wchar_ : fwide; - FLOCK(fps); - scope(exit) FUNLOCK(fps); + _FLOCK(fps); + scope(exit) _FUNLOCK(fps); auto fp = cast(_iobuf*) fps; if (orientation == File.Orientation.wide) { @@ -5009,7 +5732,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie version (Windows) { buf.length = 0; - for (int c; (c = FGETWC(fp)) != -1; ) + for (int c; (c = _FGETWC(fp)) != -1; ) { if ((c & ~0x7F) == 0) { buf ~= c; @@ -5021,7 +5744,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie if (c >= 0xD800 && c <= 0xDBFF) { int c2 = void; - if ((c2 = FGETWC(fp)) != -1 || + if ((c2 = _FGETWC(fp)) != -1 || c2 < 0xDC00 && c2 > 0xDFFF) { StdioException("unpaired UTF-16 surrogate"); @@ -5040,7 +5763,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie { import std.utf : encode; buf.length = 0; - for (int c; (c = FGETWC(fp)) != -1; ) + for (int c; (c = _FGETWC(fp)) != -1; ) { if ((c & ~0x7F) == 0) buf ~= cast(char) c; @@ -5063,7 +5786,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie // First, fill the existing buffer for (size_t bufPos = 0; bufPos < buf.length; ) { - immutable c = FGETC(fp); + immutable c = _FGETC(fp); if (c == -1) { buf.length = bufPos; @@ -5078,7 +5801,7 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie } } // Then, append to it - for (int c; (c = FGETC(fp)) != -1; ) + for (int c; (c = _FGETC(fp)) != -1; ) { buf ~= cast(char) c; if (c == terminator) @@ -5105,18 +5828,17 @@ private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orie 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" + // https://issues.dlang.org/show_bug.cgi?id=13856: ln stomped to "abtd" + assert(ln == "abcd\n"); // it can also stomp the array length ln = new char[4]; - lnptr = ln.ptr; f.readln(ln); assert(ln == "0123456789abcde\n"); @@ -5182,12 +5904,13 @@ version (linux) } } -version (unittest) string testFilename(string file = __FILE__, size_t line = __LINE__) @safe +version (StdUnittest) private 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 + // filename intentionally contains non-ASCII (Russian) characters for + // https://issues.dlang.org/show_bug.cgi?id=7648 return text(deleteme, "-детка.", baseName(file), ".", line); } diff --git a/libphobos/src/std/string.d b/libphobos/src/std/string.d index 1128a090..420b68a 100644 --- a/libphobos/src/std/string.d +++ b/libphobos/src/std/string.d @@ -69,9 +69,9 @@ $(TR $(TDNW Miscellaneous) ) ))) -Objects of types $(D _string), $(D wstring), and $(D dstring) are value types +Objects of types `string`, `wstring`, and `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) +strings, use `char[]`, `wchar[]`, or `dchar[]`. The `xxxstring` types are preferable because they don't exhibit undesired aliasing, thus making code more robust. @@ -110,7 +110,7 @@ $(LEADINGROW Publicly imported functions) )) ) -There is a rich set of functions for _string handling defined in other modules. +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) @@ -129,26 +129,26 @@ See_Also: for functions that work with unicode strings ) -Copyright: Copyright Digital Mars 2007-. +Copyright: Copyright The D Language Foundation 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 + $(HTTP jmdavisprog.com, Jonathan M Davis), + and David L. 'SpottedTiger' Davis -Source: $(PHOBOSSRC std/_string.d) +Source: $(PHOBOSSRC std/string.d) */ module std.string; -version (unittest) +version (StdUnittest) { private: struct TestAliasedString { - string get() @safe @nogc pure nothrow { return _s; } + string get() @safe @nogc pure nothrow return scope { return _s; } alias get this; @disable this(this); string _s; @@ -175,13 +175,13 @@ 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 +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; @@ -201,46 +201,163 @@ class StringException : Exception mixin basicExceptionCtors; } +/// +@safe pure unittest +{ + import std.exception : assertThrown; + auto bad = " a\n\tb\n c"; + assertThrown!StringException(bad.outdent); +} /++ 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. + Returns: A D-style array of `char`, `wchar` or `dchar` 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(Char)(return scope inout(Char)* cString) @nogc @system pure nothrow +if (isSomeChar!Char) +{ + import core.stdc.stddef : wchar_t; -inout(char)[] fromStringz(inout(char)* cString) @nogc @system pure nothrow { - import core.stdc.string : strlen; - return cString ? cString[0 .. strlen(cString)] : null; + static if (is(immutable Char == immutable char)) + import core.stdc.string : cstrlen = strlen; + else static if (is(immutable Char == immutable wchar_t)) + import core.stdc.wchar_ : cstrlen = wcslen; + else + static size_t cstrlen(scope const Char* s) + { + const(Char)* p = s; + while (*p) + ++p; + return p - s; + } + + return cString ? cString[0 .. cstrlen(cString)] : null; +} + +/// ditto +inout(Char)[] fromStringz(Char)(return scope inout(Char)[] cString) @nogc @safe pure nothrow +if (isSomeChar!Char) +{ + foreach (i; 0 .. cString.length) + if (cString[i] == '\0') + return cString[0 .. i]; + + return cString; } /// @system pure unittest { - assert(fromStringz(null) == null); - assert(fromStringz("foo") == "foo"); + assert(fromStringz("foo\0"c.ptr) == "foo"c); + assert(fromStringz("foo\0"w.ptr) == "foo"w); + assert(fromStringz("foo\0"d.ptr) == "foo"d); + + assert(fromStringz("福\0"c.ptr) == "福"c); + assert(fromStringz("福\0"w.ptr) == "福"w); + assert(fromStringz("福\0"d.ptr) == "福"d); +} + +/// +@nogc @safe pure nothrow unittest +{ + struct C + { + char[32] name; + } + assert(C("foo\0"c).name.fromStringz() == "foo"c); + + struct W + { + wchar[32] name; + } + assert(W("foo\0"w).name.fromStringz() == "foo"w); + + struct D + { + dchar[32] name; + } + assert(D("foo\0"d).name.fromStringz() == "foo"d); +} + +@nogc @safe pure nothrow unittest +{ + assert( string.init.fromStringz() == ""c); + assert(wstring.init.fromStringz() == ""w); + assert(dstring.init.fromStringz() == ""d); + + immutable char[3] a = "foo"c; + assert(a.fromStringz() == "foo"c); + + immutable wchar[3] b = "foo"w; + assert(b.fromStringz() == "foo"w); + + immutable dchar[3] c = "foo"d; + assert(c.fromStringz() == "foo"d); +} + +@system pure unittest +{ + char* a = null; + assert(fromStringz(a) == null); + wchar* b = null; + assert(fromStringz(b) == null); + dchar* c = null; + assert(fromStringz(c) == null); + + const char* d = "foo\0"; + assert(fromStringz(d) == "foo"); + + immutable char* e = "foo\0"; + assert(fromStringz(e) == "foo"); + + const wchar* f = "foo\0"; + assert(fromStringz(f) == "foo"); + + immutable wchar* g = "foo\0"; + assert(fromStringz(g) == "foo"); + + const dchar* h = "foo\0"; + assert(fromStringz(h) == "foo"); + + immutable dchar* i = "foo\0"; + assert(fromStringz(i) == "foo"); + + immutable wchar z = 0x0000; + // Test some surrogate pairs + // high surrogates are in the range 0xD800 .. 0xDC00 + // low surrogates are in the range 0xDC00 .. 0xE000 + // since UTF16 doesn't specify endianness we test both. + foreach (wchar[] t; [[0xD800, 0xDC00], [0xD800, 0xE000], [0xDC00, 0xDC00], + [0xDC00, 0xE000], [0xDA00, 0xDE00]]) + { + immutable hi = t[0], lo = t[1]; + assert(fromStringz([hi, lo, z].ptr) == [hi, lo]); + assert(fromStringz([lo, hi, z].ptr) == [lo, hi]); + } } /++ 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. + Returns: A C-style null-terminated string equivalent to `s`. `s` + must not contain embedded `'\0'`'s as any C function will treat the + first `'\0'` that it sees as the end of the string. If `s.empty` is + `true`, then a string containing only `'\0'` is returned. - $(RED Important Note:) When passing a $(D char*) to a C function, and the C + $(RED Important Note:) When passing a `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 +immutable(char)* toStringz(scope const(char)[] s) @trusted pure nothrow out (result) { import core.stdc.string : strlen, memcmp; @@ -248,13 +365,18 @@ out (result) { auto slen = s.length; while (slen > 0 && s[slen-1] == 0) --slen; - assert(strlen(result) == slen); - assert(result[0 .. slen] == s[0 .. slen]); + assert(strlen(result) == slen, + "The result c string is shorter than the in input string"); + assert(result[0 .. slen] == s[0 .. slen], + "The input and result string are not equal"); } } -body +do { import std.exception : assumeUnique; + + if (s.empty) return "".ptr; + /+ 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 @@ -278,26 +400,6 @@ body 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 { @@ -325,6 +427,27 @@ pure nothrow @system unittest const string test2 = ""; p = toStringz(test2); assert(*p == 0); + + assert(toStringz([]) is toStringz("")); +} + +pure nothrow @system unittest // https://issues.dlang.org/show_bug.cgi?id=15136 +{ + static struct S + { + immutable char[5] str; + ubyte foo; + this(char[5] str) pure nothrow + { + this.str = str; + } + } + auto s = S("01234"); + const str = s.str.toStringz; + assert(str !is s.str.ptr); + assert(*(str + 5) == 0); // Null terminated. + s.foo = 42; + assert(*(str + 5) == 0); // Still null terminated. } @@ -340,27 +463,199 @@ alias CaseSensitive = Flag!"caseSensitive"; 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) + cs = `Yes.caseSensitive` or `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). + the index of the first occurrence of `c` in `s` with + respect to the start index `startIdx`. If `c` + is not found, then `-1` is returned. + If `c` is found the value of the returned index is at least + `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 + If the sequence starting at `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) +ptrdiff_t indexOf(Range)(Range s, dchar c, CaseSensitive cs = Yes.caseSensitive) +if (isInputRange!Range && isSomeChar!(ElementType!Range) && !isSomeString!Range) +{ + return _indexOf(s, c, cs); +} + +/// Ditto +ptrdiff_t indexOf(C)(scope const(C)[] s, dchar c, CaseSensitive cs = Yes.caseSensitive) +if (isSomeChar!C) +{ + return _indexOf(s, c, cs); +} + +/// Ditto +ptrdiff_t indexOf(Range)(Range s, dchar c, size_t startIdx, CaseSensitive cs = Yes.caseSensitive) +if (isInputRange!Range && isSomeChar!(ElementType!Range) && !isSomeString!Range) +{ + return _indexOf(s, c, startIdx, cs); +} + +/// Ditto +ptrdiff_t indexOf(C)(scope const(C)[] s, dchar c, size_t startIdx, CaseSensitive cs = Yes.caseSensitive) +if (isSomeChar!C) +{ + return _indexOf(s, c, startIdx, cs); +} + +/// +@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); +} + +@safe pure unittest +{ + assert(testAliasedString!indexOf("std/string.d", '/')); + + enum S : string { a = "std/string.d" } + assert(S.a.indexOf('/') == 3); + + char[S.a.length] sa = S.a[]; + assert(sa.indexOf('/') == 3); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + import std.traits : EnumMembers; + import std.utf : byChar, byWchar, byDchar; + + assertCTFEable!( + { + static 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", '/', 0)); + assert(testAliasedString!indexOf("std/string.d", '/', 1)); + assert(testAliasedString!indexOf("std/string.d", '/', 4)); + + enum S : string { a = "std/string.d" } + assert(S.a.indexOf('/', 0) == 3); + assert(S.a.indexOf('/', 1) == 3); + assert(S.a.indexOf('/', 4) == -1); + + char[S.a.length] sa = S.a[]; + assert(sa.indexOf('/', 0) == 3); + assert(sa.indexOf('/', 1) == 3); + assert(sa.indexOf('/', 4) == -1); +} + +@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); + + static 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); + } +} + +private ptrdiff_t _indexOf(Range)(Range s, dchar c, CaseSensitive cs = Yes.caseSensitive) +if (isInputRange!Range && isSomeChar!(ElementType!Range)) { static import std.ascii; static import std.uni; @@ -484,11 +779,8 @@ if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && 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) +private ptrdiff_t _indexOf(Range)(Range s, dchar c, size_t startIdx, CaseSensitive cs = Yes.caseSensitive) +if (isInputRange!Range && isSomeChar!(ElementType!Range)) { static if (isSomeString!(typeof(s)) || (hasSlicing!(typeof(s)) && hasLength!(typeof(s)))) @@ -519,263 +811,165 @@ if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && 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) +private template _indexOfStr(CaseSensitive cs) { - 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)) + private ptrdiff_t _indexOfStr(Range, Char)(Range s, const(Char)[] sub) + if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + isSomeChar!Char) { - 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); + alias Char1 = Unqual!(ElementEncodingType!Range); - 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; + static if (isSomeString!Range) + { + static if (is(Char1 == Char) && cs == Yes.caseSensitive) + { + import std.algorithm.searching : countUntil; + return s.representation.countUntil(sub.representation); + } + else + { + import std.algorithm.searching : find; - 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); + const(Char1)[] balance; + static 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 - 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); + 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(); - 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); + // Special case for single character search + if (subr.empty) + return indexOf(s, sub0, cs); - 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); - } + static if (cs == No.caseSensitive) + sub0 = toLower(sub0); - 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); + /* 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; + static 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; + static if (cs == Yes.caseSensitive) + { + if (c != s2.front) + goto Lnext; + } + else + { + if (toLower(c) != toLower(s2.front)) + goto Lnext; + } + } + return index; + } + Lnext: + index += codeLength!Char1(c2); + } + return -1; + } } } /++ - Searches for substring in $(D s). + Searches for substring in `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) + cs = `Yes.caseSensitive` (default) or `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. + the index of the first occurrence of `sub` in `s` with + respect to the start index `startIdx`. If `sub` is not found, + then `-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). + If `sub` is found the value of the returned index is at least + `startIdx`. Throws: - If the sequence starting at $(D startIdx) does not represent a well + If the sequence starting at `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) +ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub) if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && isSomeChar!Char) { - alias Char1 = Unqual!(ElementEncodingType!Range); - - static if (isSomeString!Range) - { - import std.algorithm.searching : find; + return _indexOfStr!(Yes.caseSensitive)(s, sub); +} - 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; } (); - } +/// Ditto +ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub, in CaseSensitive cs) +if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + isSomeChar!Char) +{ + if (cs == Yes.caseSensitive) + return indexOf(s, sub); 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); + return _indexOfStr!(No.caseSensitive)(s, sub); +} - /* 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); - } +/// Ditto +ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, + in size_t startIdx) +@safe +if (isSomeChar!Char1 && isSomeChar!Char2) +{ + if (startIdx >= s.length) return -1; - } + ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub); + if (foundIdx == -1) + return -1; + return foundIdx + cast(ptrdiff_t) startIdx; } /// Ditto ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, - in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) + in size_t startIdx, in CaseSensitive cs) @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; + if (startIdx >= s.length) + return -1; + ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub, cs); + if (foundIdx == -1) + return -1; + return foundIdx + cast(ptrdiff_t) startIdx; } /// @@ -800,8 +994,25 @@ if (isSomeChar!Char1 && isSomeChar!Char2) assert(indexOf(s, "wO", No.caseSensitive) == 6); } +@safe pure nothrow @nogc unittest +{ + string s = "Hello World"; + assert(indexOf(s, "Wo", 4) == 6); + assert(indexOf(s, "Zo", 100) == -1); + assert(indexOf(s, "Wo") == 6); + assert(indexOf(s, "Zo") == -1); +} + +ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub) +if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + isSomeChar!Char) && + is(StringTypeOf!Range)) +{ + return indexOf!(StringTypeOf!Range)(s, sub); +} + ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub, - in CaseSensitive cs = Yes.caseSensitive) + in CaseSensitive cs) if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && isSomeChar!Char) && is(StringTypeOf!Range)) @@ -809,7 +1020,7 @@ if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && return indexOf!(StringTypeOf!Range)(s, sub, cs); } -@safe pure unittest +@safe pure nothrow @nogc unittest { assert(testAliasedString!indexOf("std/string.d", "string")); } @@ -822,10 +1033,10 @@ if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ 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); @@ -855,7 +1066,7 @@ if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && // 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) { @@ -890,10 +1101,10 @@ unittest import std.conv : to; import std.traits : EnumMembers; - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ 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); @@ -930,7 +1141,7 @@ unittest // 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) { @@ -949,19 +1160,19 @@ unittest 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) + cs = `Yes.caseSensitive` or `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). + The index of the last occurrence of `c` in `s`. If `c` is not + found, then `-1` is returned. The `startIdx` slices `s` in + the following way $(D s[0 .. startIdx]). `startIdx` represents a + codeunit index in `s`. Throws: - If the sequence ending at $(D startIdx) does not represent a well + If the sequence ending at `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. + `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 @@ -1068,8 +1279,8 @@ if (isSomeChar!Char) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static 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); @@ -1089,7 +1300,7 @@ if (isSomeChar!Char) 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) { @@ -1105,8 +1316,8 @@ if (isSomeChar!Char) import std.conv : to; import std.traits : EnumMembers; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static 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); @@ -1123,7 +1334,7 @@ if (isSomeChar!Char) 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) { @@ -1138,19 +1349,19 @@ if (isSomeChar!Char) 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) + cs = `Yes.caseSensitive` or `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). + the index of the last occurrence of `sub` in `s`. If `sub` is + not found, then `-1` is returned. The `startIdx` slices `s` + in the following way $(D s[0 .. startIdx]). `startIdx` represents a + codeunit index in `s`. Throws: - If the sequence ending at $(D startIdx) does not represent a well + If the sequence ending at `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. + `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 @@ -1169,7 +1380,7 @@ if (isSomeChar!Char1 && isSomeChar!Char2) if (cs == Yes.caseSensitive) { - static if (is(Unqual!Char1 == Unqual!Char2)) + static if (is(immutable Char1 == immutable Char2)) { import core.stdc.string : memcmp; @@ -1181,12 +1392,8 @@ if (isSomeChar!Char1 && isSomeChar!Char2) { if (__ctfe) { - foreach (j; 1 .. sub.length) - { - if (s[i + j] != sub[j]) - continue; - } - return i; + if (s[i + 1 .. i + sub.length] == sub[1 .. $]) + return i; } else { @@ -1270,8 +1477,8 @@ if (isSomeChar!Char1 && isSomeChar!Char2) { import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ auto r = to!S("").lastIndexOf("hello"); assert(r == -1, to!string(r)); @@ -1280,7 +1487,7 @@ if (isSomeChar!Char1 && isSomeChar!Char2) r = to!S("").lastIndexOf(""); assert(r == -1, to!string(r)); - } + }} } @safe pure unittest @@ -1291,10 +1498,10 @@ if (isSomeChar!Char1 && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ enum typeStr = S.stringof ~ " " ~ T.stringof; assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr); @@ -1329,7 +1536,7 @@ if (isSomeChar!Char1 && isSomeChar!Char2) 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) { @@ -1343,20 +1550,21 @@ if (isSomeChar!Char1 && isSomeChar!Char2) }); } -@safe pure unittest // issue13529 +// https://issues.dlang.org/show_bug.cgi?id=13529 +@safe pure unittest { import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - { + static 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); - } + }} } } @@ -1365,10 +1573,10 @@ if (isSomeChar!Char1 && isSomeChar!Char2) import std.conv : to; import std.traits : EnumMembers; - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ enum typeStr = S.stringof ~ " " ~ T.stringof; assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr); @@ -1396,7 +1604,7 @@ if (isSomeChar!Char1 && isSomeChar!Char2) 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) { @@ -1409,6 +1617,19 @@ if (isSomeChar!Char1 && isSomeChar!Char2) } } +// https://issues.dlang.org/show_bug.cgi?id=20783 +@safe pure @nogc unittest +{ + enum lastIndex = "aa".lastIndexOf("ab"); + assert(lastIndex == -1); +} + +@safe pure @nogc unittest +{ + enum lastIndex = "hello hello hell h".lastIndexOf("hello"); + assert(lastIndex == 6); +} + private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)( const(Char)[] haystack, const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive) @safe pure @@ -1527,10 +1748,10 @@ if (isSomeChar!Char && isSomeChar!Char2) /** 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) + needles) in `haystack`. If no element of `needles` is found, + then `-1` is returned. The `startIdx` slices `haystack` in the + following way $(D haystack[startIdx .. $]). `startIdx` represents a + codeunit index in `haystack`. If the sequence ending at `startIdx` does not represent a well formed codepoint, then a $(REF UTFException, std,utf) may be thrown. @@ -1539,7 +1760,7 @@ if (isSomeChar!Char && isSomeChar!Char2) 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). + returns `-1`. cs = Indicates whether the comparisons are case sensitive. */ ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, @@ -1593,8 +1814,8 @@ if (isSomeChar!Char && isSomeChar!Char2) { import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ auto r = to!S("").indexOfAny("hello"); assert(r == -1, to!string(r)); @@ -1603,7 +1824,7 @@ if (isSomeChar!Char && isSomeChar!Char2) r = to!S("").indexOfAny(""); assert(r == -1, to!string(r)); - } + }} } @safe pure unittest @@ -1613,10 +1834,10 @@ if (isSomeChar!Char && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + { 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); @@ -1639,7 +1860,7 @@ if (isSomeChar!Char && isSomeChar!Char2) No.caseSensitive) == 0); assert(indexOfAny("\u0100", to!T("\u0100"), No.caseSensitive) == 0); - }(); + } } } ); @@ -1650,10 +1871,10 @@ if (isSomeChar!Char && isSomeChar!Char2) import std.conv : to; import std.traits : EnumMembers; - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + { 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); @@ -1678,7 +1899,7 @@ if (isSomeChar!Char && isSomeChar!Char2) assert(indexOfAny("\u0100", to!T("\u0100"), 0, No.caseSensitive) == 0); - }(); + } foreach (cs; EnumMembers!CaseSensitive) { @@ -1694,10 +1915,10 @@ if (isSomeChar!Char && isSomeChar!Char2) /** 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 + needles) in `haystack`. If no element of `needles` is found, + then `-1` is returned. The `stopIdx` slices `haystack` in the + following way $(D s[0 .. stopIdx]). `stopIdx` represents a codeunit + index in `haystack`. If the sequence ending at `startIdx` does not represent a well formed codepoint, then a $(REF UTFException, std,utf) may be thrown. @@ -1706,7 +1927,7 @@ if (isSomeChar!Char && isSomeChar!Char2) 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). + returns `-1`. cs = Indicates whether the comparisons are case sensitive. */ ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, @@ -1757,8 +1978,8 @@ if (isSomeChar!Char && isSomeChar!Char2) { import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ auto r = to!S("").lastIndexOfAny("hello"); assert(r == -1, to!string(r)); @@ -1767,7 +1988,7 @@ if (isSomeChar!Char && isSomeChar!Char2) r = to!S("").lastIndexOfAny(""); assert(r == -1, to!string(r)); - } + }} } @safe pure unittest @@ -1777,10 +1998,10 @@ if (isSomeChar!Char && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ 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); @@ -1817,7 +2038,7 @@ if (isSomeChar!Char && isSomeChar!Char2) assert(lastIndexOfAny("\u0100", to!T("\u0100"), No.caseSensitive) == 0); - }(); + }} } } ); @@ -1830,10 +2051,10 @@ if (isSomeChar!Char && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ enum typeStr = S.stringof ~ " " ~ T.stringof; assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337) == -1, @@ -1869,7 +2090,7 @@ if (isSomeChar!Char && isSomeChar!Char2) No.caseSensitive) == -1, typeStr); assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2, No.caseSensitive) == 0, typeStr); - }(); + }} } } ); @@ -1877,15 +2098,15 @@ if (isSomeChar!Char && isSomeChar!Char2) /** 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. + in `needles` in `haystack`. If all element of `haystack` are + element of `needles` `-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). + returns `-1`. cs = Indicates whether the comparisons are case sensitive. */ ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, @@ -1935,8 +2156,8 @@ if (isSomeChar!Char && isSomeChar!Char2) { import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ auto r = to!S("").indexOfNeither("hello"); assert(r == -1, to!string(r)); @@ -1945,7 +2166,7 @@ if (isSomeChar!Char && isSomeChar!Char2) r = to!S("").indexOfNeither(""); assert(r == -1, to!string(r)); - } + }} } @safe pure unittest @@ -1955,10 +2176,10 @@ if (isSomeChar!Char && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + { assert(indexOfNeither(cast(S) null, to!T("a")) == -1); assert(indexOfNeither("abba", "a") == 1); @@ -1986,7 +2207,7 @@ if (isSomeChar!Char && isSomeChar!Char2) to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), No.caseSensitive))); } - }(); + } } } ); @@ -1999,10 +2220,10 @@ if (isSomeChar!Char && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + { 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))); @@ -2029,7 +2250,7 @@ if (isSomeChar!Char && isSomeChar!Char2) No.caseSensitive) == 2, to!string(indexOfNeither( to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive))); } - }(); + } } } ); @@ -2037,15 +2258,15 @@ if (isSomeChar!Char && isSomeChar!Char2) /** 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. + an elements in `needles` in `haystack`. If all element of + `haystack` are element of `needles` `-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). + returns `-1`. cs = Indicates whether the comparisons are case sensitive. */ ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, @@ -2089,8 +2310,8 @@ if (isSomeChar!Char && isSomeChar!Char2) { import std.conv : to; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ auto r = to!S("").lastIndexOfNeither("hello"); assert(r == -1, to!string(r)); @@ -2099,7 +2320,7 @@ if (isSomeChar!Char && isSomeChar!Char2) r = to!S("").lastIndexOfNeither(""); assert(r == -1, to!string(r)); - } + }} } @safe pure unittest @@ -2109,10 +2330,10 @@ if (isSomeChar!Char && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ 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); @@ -2141,7 +2362,7 @@ if (isSomeChar!Char && isSomeChar!Char2) assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), No.caseSensitive) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), No.caseSensitive))); - }(); + }} } } ); @@ -2154,10 +2375,10 @@ if (isSomeChar!Char && isSomeChar!Char2) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) + static foreach (S; AliasSeq!(string, wstring, dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(string, wstring, dstring)) + {{ 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); @@ -2185,7 +2406,7 @@ if (isSomeChar!Char && isSomeChar!Char2) assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2, No.caseSensitive) == 1, to!string(lastIndexOfNeither( to!S("dfefffg"), to!T("NSA"), 2, No.caseSensitive))); - }(); + }} } } ); @@ -2193,8 +2414,8 @@ if (isSomeChar!Char && isSomeChar!Char2) /** * 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. + * as the string except the character type is replaced by `ubyte`, + * `ushort`, or `uint` depending on the character width. * * Params: * s = The string to return the _representation of. @@ -2233,10 +2454,10 @@ if (isSomeChar!Char) assert(representation(str) is cast(T[]) str); } - foreach (Type; AliasSeq!(Tuple!(char , ubyte ), + static 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"; @@ -2246,13 +2467,13 @@ if (isSomeChar!Char) 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 + * Capitalize the first character of `s` and convert the rest of `s` to * lowercase. * * Params: @@ -2300,8 +2521,8 @@ if (!isSomeString!S && is(StringTypeOf!S)) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) - { + static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + {{ S s1 = to!S("FoL"); S s2; @@ -2325,15 +2546,15 @@ if (!isSomeString!S && is(StringTypeOf!S)) 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 + Split `s` into an array of lines according to the unicode standard using + `'\r'`, `'\n'`, `"\r\n"`, $(REF lineSep, std,uni), + $(REF paraSep, std,uni), `U+0085` (NEL), `'\v'` and `'\f'` + as delimiters. If `keepTerm` is set to `KeepTerminator.yes`, then the delimiter is included in the strings returned. Does not throw on invalid UTF; such is simply passed unchanged @@ -2345,11 +2566,11 @@ if (!isSomeString!S && is(StringTypeOf!S)) 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 + s = a string of `chars`, `wchars`, or `dchars`, or any custom + type that casts to a `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) + array of strings, each element is a line that is a slice of `s` See_Also: $(LREF lineSplitter) $(REF splitter, std,algorithm) @@ -2358,14 +2579,14 @@ if (!isSomeString!S && is(StringTypeOf!S)) alias KeepTerminator = Flag!"keepTerminator"; /// ditto -S[] splitLines(S)(S s, in KeepTerminator keepTerm = No.keepTerminator) @safe pure -if (isSomeString!S) +C[][] splitLines(C)(C[] s, KeepTerminator keepTerm = No.keepTerminator) @safe pure +if (isSomeChar!C) { import std.array : appender; import std.uni : lineSep, paraSep; size_t iStart = 0; - auto retval = appender!(S[])(); + auto retval = appender!(C[][])(); for (size_t i; i < s.length; ++i) { @@ -2454,15 +2675,19 @@ if (isSomeString!S) assert(splitLines(s) == [s]); } -auto splitLines(S)(auto ref S s, in KeepTerminator keepTerm = No.keepTerminator) -if (!isSomeString!S && is(StringTypeOf!S)) +@safe pure nothrow unittest { - return splitLines!(StringTypeOf!S)(s, keepTerm); + assert(testAliasedString!splitLines("hello\nworld")); + + enum S : string { a = "hello\nworld" } + assert(S.a.splitLines() == ["hello", "world"]); } -@safe pure nothrow unittest +@system pure nothrow unittest { - assert(testAliasedString!splitLines("hello\nworld")); + // dip1000 cannot express an array of scope arrays, so this is not @safe + char[11] sa = "hello\nworld"; + assert(sa.splitLines() == ["hello", "world"]); } @safe pure unittest @@ -2472,8 +2697,8 @@ if (!isSomeString!S && is(StringTypeOf!S)) assertCTFEable!( { - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static 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" @@ -2525,7 +2750,7 @@ if (!isSomeString!S && is(StringTypeOf!S)) lines = splitLines(s, Yes.keepTerminator); assert(lines.length == 14); assert(lines[13] == "cookies"); - } + }} }); } @@ -2644,7 +2869,7 @@ public: { if (iStart == _unComputed) { - assert(!empty); + assert(!empty, "Can not popFront an empty range"); front; } iStart = _unComputed; @@ -2663,9 +2888,9 @@ public: /*********************************** * 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 + using `'\r'`, `'\n'`, `'\v'`, `'\f'`, `"\r\n"`, + $(REF lineSep, std,uni), $(REF paraSep, std,uni) and `'\u0085'` (NEL) + as delimiters. If `keepTerm` is set to `Yes.keepTerminator`, then the delimiter is included in the slices returned. Does not throw on invalid UTF; such is simply passed unchanged @@ -2676,10 +2901,10 @@ public: Does not allocate memory. Params: - r = array of $(D chars), $(D wchars), or $(D dchars) or a slicable range + r = array of `chars`, `wchars`, or `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) + range of slices of the input range `r` See_Also: $(LREF splitLines) @@ -2687,13 +2912,18 @@ public: $(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) +if (hasSlicing!Range && hasLength!Range && isSomeChar!(ElementType!Range) && !isSomeString!Range) { return LineSplitter!(keepTerm, Range)(r); } +/// Ditto +auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, C)(C[] r) +if (isSomeChar!C) +{ + return LineSplitter!(keepTerm, C[])(r); +} + /// @safe pure unittest { @@ -2707,12 +2937,6 @@ if ((hasSlicing!Range && hasLength!Range && isSomeChar!(ElementType!Range) || 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; @@ -2721,8 +2945,8 @@ if (isConvertibleToString!Range) assertCTFEable!( { - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static 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" @@ -2775,7 +2999,7 @@ if (isConvertibleToString!Range) lines = lineSplitter!(Yes.keepTerminator)(s).array; assert(lines.length == 14); assert(lines[13] == "cookies"); - } + }} }); } @@ -2796,9 +3020,17 @@ if (isConvertibleToString!Range) @nogc @safe pure unittest { import std.algorithm.comparison : equal; + import std.range : only; + auto s = "std/string.d"; auto as = TestAliasedString(s); assert(equal(s.lineSplitter(), as.lineSplitter())); + + enum S : string { a = "hello\nworld" } + assert(equal(S.a.lineSplitter(), only("hello", "world"))); + + char[S.a.length] sa = S.a[]; + assert(equal(sa.lineSplitter(), only("hello", "world"))); } @safe pure unittest @@ -2813,15 +3045,18 @@ if (isConvertibleToString!Range) } /++ - Strips leading whitespace (as defined by $(REF isWhite, std,uni)). + Strips leading whitespace (as defined by $(REF isWhite, std,uni)) or + as specified in the second argument. Params: input = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) of characters + chars = string of characters to be stripped - Returns: $(D input) stripped of leading whitespace. + Returns: `input` stripped of leading whitespace or characters + specified in the second argument. - Postconditions: $(D input) and the returned value + Postconditions: `input` and the returned value will share the same tail (see $(REF sameTail, std,array)). See_Also: @@ -2831,70 +3066,196 @@ auto stripLeft(Range)(Range input) if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && !isInfinite!Range && !isConvertibleToString!Range) { + import std.traits : isDynamicArray; static import std.ascii; static import std.uni; - import std.utf : decodeFront; - while (!input.empty) + static if (is(immutable ElementEncodingType!Range == immutable dchar) + || is(immutable ElementEncodingType!Range == immutable wchar)) { - auto c = input.front; - if (std.ascii.isASCII(c)) + // Decoding is never needed for dchar. It happens not to be needed + // here for wchar because no whitepace is outside the basic + // multilingual plane meaning every whitespace character is encoded + // with a single wchar and due to the design of UTF-16 those wchars + // will not occur as part of the encoding of multi-wchar codepoints. + static if (isDynamicArray!Range) + { + foreach (i; 0 .. input.length) + { + if (!std.uni.isWhite(input[i])) + return input[i .. $]; + } + return input[$ .. $]; + } + else + { + while (!input.empty) + { + if (!std.uni.isWhite(input.front)) + break; + input.popFront(); + } + return input; + } + } + else + { + static if (isDynamicArray!Range) + { + // ASCII optimization for dynamic arrays. + size_t i = 0; + for (const size_t end = input.length; i < end; ++i) + { + auto c = input[i]; + if (c >= 0x80) goto NonAsciiPath; + if (!std.ascii.isWhite(c)) break; + } + input = input[i .. $]; + return input; + + NonAsciiPath: + input = input[i .. $]; + // Fall through to standard case. + } + + import std.utf : decode, decodeFront, UseReplacementDchar; + + static if (isNarrowString!Range) + { + for (size_t index = 0; index < input.length;) + { + const saveIndex = index; + if (!std.uni.isWhite(decode!(UseReplacementDchar.yes)(input, index))) + return input[saveIndex .. $]; + } + return input[$ .. $]; + } + else + { + 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!(UseReplacementDchar.yes)(input); + if (!std.uni.isWhite(dc)) + return save; + } + } + return input; + } + } +} + +/// +nothrow @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(" \u2028hello world") == + "hello world"); + 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 "); + assert(stripLeft(" \u2022hello world ".byChar).array == + "\u2022hello world "); +} + +auto stripLeft(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + return stripLeft!(StringTypeOf!Range)(str); +} + +@nogc nothrow @safe pure unittest +{ + assert(testAliasedString!stripLeft(" hello")); +} + +/// Ditto +auto stripLeft(Range, Char)(Range input, const(Char)[] chars) +if (((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) || + isConvertibleToString!Range) && isSomeChar!Char) +{ + static if (isConvertibleToString!Range) + return stripLeft!(StringTypeOf!Range)(input, chars); + else + { + for (; !input.empty; input.popFront) { - if (!std.ascii.isWhite(c)) + if (chars.indexOf(input.front) == -1) break; - input.popFront(); - } - else - { - auto save = input.save; - auto dc = decodeFront(input); - if (!std.uni.isWhite(dc)) - return save; } + return input; } - return input; } /// @safe pure unittest { - import std.uni : lineSep, paraSep; - assert(stripLeft(" hello world ") == + 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 == + assert(stripLeft("xxxxxhello world ", "x") == + "hello world "); + assert(stripLeft("xxxyy hello world ", "xy ") == "hello world "); } -auto stripLeft(Range)(auto ref Range str) -if (isConvertibleToString!Range) +/// +@safe pure unittest { - return stripLeft!(StringTypeOf!Range)(str); + import std.array : array; + import std.utf : byChar, byWchar, byDchar; + + assert(stripLeft(" xxxyy hello world "w.byChar, "xy ").array == + "hello world "); + + assert(stripLeft("\u2028\u2020hello world\u2028"w.byWchar, + "\u2028").array == "\u2020hello world\u2028"); + assert(stripLeft("\U00010001hello world"w.byWchar, " ").array == + "\U00010001hello world"w); + assert(stripLeft("\U00010001 xyhello world"d.byDchar, + "\U00010001 xy").array == "hello world"d); + + assert(stripLeft("\u2020hello"w, "\u2020"w) == "hello"w); + assert(stripLeft("\U00010001hello"d, "\U00010001"d) == "hello"d); + assert(stripLeft(" hello ", "") == " hello "); } @safe pure unittest { - assert(testAliasedString!stripLeft(" hello")); + assert(testAliasedString!stripLeft(" xyz hello", "xyz ")); } /++ - Strips trailing whitespace (as defined by $(REF isWhite, std,uni)). + Strips trailing whitespace (as defined by $(REF isWhite, std,uni)) or + as specified in the second argument. Params: str = string or random access range of characters + chars = string of characters to be stripped Returns: - slice of $(D str) stripped of trailing whitespace. + slice of `str` stripped of trailing whitespace or characters + specified in the second argument. See_Also: Generic stripping on ranges: $(REF _stripRight, std, algorithm, mutation) @@ -2905,63 +3266,60 @@ if (isSomeString!Range || !isConvertibleToString!Range && isSomeChar!(ElementEncodingType!Range)) { + import std.traits : isDynamicArray; import std.uni : isWhite; alias C = Unqual!(ElementEncodingType!(typeof(str))); - static if (isSomeString!(typeof(str))) + static if (isSomeString!(typeof(str)) && C.sizeof >= 2) { - import std.utf : codeLength; - - foreach_reverse (i, dchar c; str) + // No whitespace takes multiple wchars to encode and due to + // the design of UTF-16 those wchars will not occur as part + // of the encoding of multi-wchar codepoints. + foreach_reverse (i, C c; str) { if (!isWhite(c)) - return str[0 .. i + codeLength!C(c)]; + return str[0 .. i + 1]; } - return str[0 .. 0]; } else { - size_t i = str.length; - while (i--) + // ASCII optimization for dynamic arrays. + static if (isDynamicArray!(typeof(str))) { - static if (C.sizeof == 4) - { - if (isWhite(str[i])) - continue; - break; - } - else static if (C.sizeof == 2) + static import std.ascii; + foreach_reverse (i, C c; str) { - auto c2 = str[i]; - if (c2 < 0xD800 || c2 >= 0xE000) + if (c >= 0x80) { - if (isWhite(c2)) - continue; + str = str[0 .. i + 1]; + goto NonAsciiPath; } - else if (c2 >= 0xDC00) + if (!std.ascii.isWhite(c)) { - 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; - } - } - } + return str[0 .. i + 1]; } + } + return str[0 .. 0]; + } + + NonAsciiPath: + + size_t i = str.length; + while (i--) + { + static if (C.sizeof >= 2) + { + // No whitespace takes multiple wchars to encode and due to + // the design of UTF-16 those wchars will not occur as part + // of the encoding of multi-wchar codepoints. + if (isWhite(str[i])) + continue; break; } else static if (C.sizeof == 1) { - import std.utf : byDchar; - - char cx = str[i]; + const cx = str[i]; if (cx <= 0x7F) { if (isWhite(cx)) @@ -2970,21 +3328,30 @@ if (isSomeString!Range || } else { - size_t stride = 0; - - while (1) + if (i == 0 || (0b1100_0000 & cx) != 0b1000_0000) + break; + const uint d = 0b0011_1111 & cx; + const c2 = str[i - 1]; + if ((c2 & 0b1110_0000) == 0b1100_0000) // 2 byte encoding. { - ++stride; - if (!i || (cx & 0xC0) == 0xC0 || stride == 4) - break; - cx = str[i - 1]; - if (!(cx & 0x80)) - break; - --i; + if (isWhite(d + (uint(c2 & 0b0001_1111) << 6))) + { + i--; + continue; + } + break; } - - if (!str[i .. i + stride].byDchar.front.isWhite) - return str[0 .. i + stride]; + if (i == 1 || (c2 & 0b1100_0000) != 0b1000_0000) + break; + const c3 = str[i - 2]; + // In UTF-8 all whitespace is encoded in 3 bytes or fewer. + if ((c3 & 0b1111_0000) == 0b1110_0000 && + isWhite(d + (uint(c2 & 0b0011_1111) << 6) + (uint(c3 & 0b0000_1111) << 12))) + { + i -= 2; + continue; + } + break; } } else @@ -2996,7 +3363,7 @@ if (isSomeString!Range || } /// -@safe pure +nothrow @safe pure unittest { import std.uni : lineSep, paraSep; @@ -3018,7 +3385,7 @@ if (isConvertibleToString!Range) return stripRight!(StringTypeOf!Range)(str); } -@safe pure unittest +@nogc nothrow @safe pure unittest { assert(testAliasedString!stripRight("hello ")); } @@ -3034,7 +3401,7 @@ if (isConvertibleToString!Range) 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)) + static foreach (C; AliasSeq!(char, wchar, dchar)) { foreach (s; invalidUTFstrings!C()) { @@ -3047,16 +3414,73 @@ if (isConvertibleToString!Range) cast(void) stripRight(ws.byUTF!wchar).array; } +/// Ditto +auto stripRight(Range, Char)(Range str, const(Char)[] chars) +if (((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range)) || + isConvertibleToString!Range) && isSomeChar!Char) +{ + static if (isConvertibleToString!Range) + return stripRight!(StringTypeOf!Range)(str, chars); + else + { + for (; !str.empty; str.popBack) + { + if (chars.indexOf(str.back) == -1) + break; + } + return str; + } +} + +/// +@safe pure +unittest +{ + assert(stripRight(" hello world ", "x") == + " hello world "); + assert(stripRight(" hello world ", " ") == + " hello world"); + assert(stripRight(" hello worldxy ", "xy ") == + " hello world"); +} + +@safe pure unittest +{ + assert(testAliasedString!stripRight("hello xyz ", "xyz ")); +} + +@safe pure unittest +{ + import std.array : array; + import std.utf : byChar, byDchar, byUTF, byWchar; + + assert(stripRight(" hello world xyz ".byChar, + "xyz ").array == " hello world"); + assert(stripRight("\u2028hello world\u2020\u2028"w.byWchar, + "\u2028").array == "\u2028hello world\u2020"); + assert(stripRight("hello world\U00010001"w.byWchar, + " ").array == "hello world\U00010001"w); + assert(stripRight("hello world\U00010001 xy"d.byDchar, + "\U00010001 xy").array == "hello world"d); + assert(stripRight("hello\u2020"w, "\u2020"w) == "hello"w); + assert(stripRight("hello\U00010001"d, "\U00010001"d) == "hello"d); + assert(stripRight(" hello ", "") == " hello "); +} + /++ Strips both leading and trailing whitespace (as defined by - $(REF isWhite, std,uni)). + $(REF isWhite, std,uni)) or as specified in the second argument. Params: str = string or random access range of characters + chars = string of characters to be stripped + leftChars = string of leading characters to be stripped + rightChars = string of trailing characters to be stripped Returns: - slice of $(D str) stripped of leading and trailing whitespace. + slice of `str` stripped of leading and trailing whitespace + or characters as specified in the second argument. See_Also: Generic stripping on ranges: $(REF _strip, std, algorithm, mutation) @@ -3105,7 +3529,7 @@ if (isConvertibleToString!Range) assertCTFEable!( { - foreach (S; AliasSeq!( char[], const char[], string, + static foreach (S; AliasSeq!( char[], const char[], string, wchar[], const wchar[], wstring, dchar[], const dchar[], dstring)) { @@ -3144,15 +3568,134 @@ if (isConvertibleToString!Range) }); } +/// Ditto +auto strip(Range, Char)(Range str, const(Char)[] chars) +if (((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range)) || + isConvertibleToString!Range) && isSomeChar!Char) +{ + static if (isConvertibleToString!Range) + return strip!(StringTypeOf!Range)(str, chars); + else + return stripRight(stripLeft(str, chars), chars); +} + +/// +@safe pure unittest +{ + assert(strip(" hello world ", "x") == + " hello world "); + assert(strip(" hello world ", " ") == + "hello world"); + assert(strip(" xyxyhello worldxyxy ", "xy ") == + "hello world"); + assert(strip("\u2020hello\u2020"w, "\u2020"w) == "hello"w); + assert(strip("\U00010001hello\U00010001"d, "\U00010001"d) == "hello"d); + assert(strip(" hello ", "") == " hello "); +} + +@safe pure unittest +{ + assert(testAliasedString!strip(" xyz hello world xyz ", "xyz ")); +} + +/// Ditto +auto strip(Range, Char)(Range str, const(Char)[] leftChars, const(Char)[] rightChars) +if (((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range)) || + isConvertibleToString!Range) && isSomeChar!Char) +{ + static if (isConvertibleToString!Range) + return strip!(StringTypeOf!Range)(str, leftChars, rightChars); + else + return stripRight(stripLeft(str, leftChars), rightChars); +} + +/// +@safe pure unittest +{ + assert(strip("xxhelloyy", "x", "y") == "hello"); + assert(strip(" xyxyhello worldxyxyzz ", "xy ", "xyz ") == + "hello world"); + assert(strip("\u2020hello\u2028"w, "\u2020"w, "\u2028"w) == "hello"w); + assert(strip("\U00010001hello\U00010002"d, "\U00010001"d, "\U00010002"d) == + "hello"d); + assert(strip(" hello ", "", "") == " hello "); +} + +@safe pure unittest +{ + assert(testAliasedString!strip(" xy hello world pq ", "xy ", "pq ")); +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + static foreach (S; AliasSeq!( char[], const char[], string, + wchar[], const wchar[], wstring, + dchar[], const dchar[], dstring)) + { + assert(equal(stripLeft(to!S(" \tfoo\t "), "\t "), "foo\t ")); + assert(equal(stripLeft(to!S("\u2008 foo\t \u2007"), "\u2008 "), + "foo\t \u2007")); + assert(equal(stripLeft(to!S("\u0085 μ \u0085 \u00BB \r"), "\u0085 "), + "μ \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 "), "\t "), " foo")); + assert(equal(stripRight(to!S("\u2008 foo\t \u2007"), "\u2007\t "), + "\u2008 foo")); + assert(equal(stripRight(to!S("\u0085 μ \u0085 \u00BB \r"), "\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 "), "\t "), "foo")); + assert(equal(strip(to!S("\u2008 foo\t \u2007"), "\u2008\u2007\t "), + "foo")); + assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB \r"), "\u0085\r "), + "μ \u0085 \u00BB")); + assert(equal(strip(to!S("\U0010FFFE"), " "), "\U0010FFFE")); + assert(equal(strip(to!S(""), " "), "")); + + assert(equal(strip(to!S(" \nfoo\t "), "\n ", "\t "), "foo")); + assert(equal(strip(to!S("\u2008\n foo\t \u2007"), + "\u2008\n ", "\u2007\t "), "foo")); + assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB μ \u00BB\r"), + "\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 = " xyz "; + 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 `str` ends with `delimiter`, then `str` is returned without + `delimiter` on its end. If it `str` does $(I not) end with + `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, + If no `delimiter` is given, then one trailing `'\r'`, `'\n'`, + `"\r\n"`, `'\f'`, `'\v'`, $(REF lineSep, std,uni), $(REF paraSep, std,uni), or $(REF nelSep, std,uni) + is removed from the end of `str`. If `str` does not end with any of those characters, then it is returned unchanged. Params: @@ -3229,7 +3772,7 @@ if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) || alias C1 = ElementEncodingType!Range; - static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4))) + static if (is(immutable C1 == immutable C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4))) { import std.algorithm.searching : endsWith; if (str.endsWith(delimiter)) @@ -3306,11 +3849,9 @@ if (isConvertibleToString!Range) import std.conv : to; import std.exception : assertCTFEable; - string s; - assertCTFEable!( { - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) { // @@@ BUG IN COMPILER, MUST INSERT CAST assert(chomp(cast(S) null) is null); @@ -3330,8 +3871,8 @@ if (isConvertibleToString!Range) 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 + static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { // @@@ 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"); @@ -3342,7 +3883,7 @@ if (isConvertibleToString!Range) 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"); - }(); + } } }); @@ -3361,10 +3902,10 @@ if (isConvertibleToString!Range) /++ - 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 + If `str` starts with `delimiter`, then the part of `str` following + `delimiter` is returned. If `str` does $(I not) start with - $(D delimiter), then it is returned unchanged. + `delimiter`, then it is returned unchanged. Params: str = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) @@ -3382,7 +3923,7 @@ if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) || { alias C1 = ElementEncodingType!Range; - static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4))) + static if (is(immutable C1 == immutable C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4))) { import std.algorithm.searching : startsWith; if (str.startsWith(delimiter)) @@ -3433,16 +3974,16 @@ unittest import std.exception : assertCTFEable; assertCTFEable!( { - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + static 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 + static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { 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")); - }(); + } } }); @@ -3467,9 +4008,9 @@ unittest } /++ - 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. + Returns `str` without its last character, if there is one. If `str` + ends with `"\r\n"`, then both are removed. If `str` is empty, then + it is returned unchanged. Params: str = string (must be valid UTF) @@ -3599,7 +4140,7 @@ if (isConvertibleToString!Range) assertCTFEable!( { - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) { assert(chop(cast(S) null) is null); assert(equal(chop(to!S("hello")), "hell")); @@ -3614,14 +4155,14 @@ if (isConvertibleToString!Range) /++ - Left justify $(D s) in a field $(D width) characters wide. $(D fillChar) + Left justify `s` in a field `width` characters wide. `fillChar` is the character that will be used to fill up the space in the field that - $(D s) doesn't fill. + `s` doesn't fill. Params: s = string width = minimum field width - fillChar = used to pad end up to $(D width) characters + fillChar = used to pad end up to `width` characters Returns: GC allocated string @@ -3645,14 +4186,14 @@ if (isSomeString!S) } /++ - Left justify $(D s) in a field $(D width) characters wide. $(D fillChar) + Left justify `s` in a field `width` characters wide. `fillChar` is the character that will be used to fill up the space in the field that - $(D s) doesn't fill. + `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 + fillChar = used to pad end up to `width` characters Returns: a lazy range of the left justified result @@ -3756,14 +4297,14 @@ if (isConvertibleToString!Range) } /++ - Right justify $(D s) in a field $(D width) characters wide. $(D fillChar) + Right justify `s` in a field `width` characters wide. `fillChar` is the character that will be used to fill up the space in the field that - $(D s) doesn't fill. + `s` doesn't fill. Params: s = string width = minimum field width - fillChar = used to pad end up to $(D width) characters + fillChar = used to pad end up to `width` characters Returns: GC allocated string @@ -3787,15 +4328,15 @@ if (isSomeString!S) } /++ - Right justify $(D s) in a field $(D width) characters wide. $(D fillChar) + Right justify `s` in a field `width` characters wide. `fillChar` is the character that will be used to fill up the space in the field that - $(D s) doesn't fill. + `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 + fillChar = used to pad end up to `width` characters Returns: a lazy range of the right justified result @@ -3836,7 +4377,7 @@ if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && { // Replace _width with nfill // (use alias instead of union because CTFE cannot deal with unions) - assert(_width); + assert(_width, "width of 0 not allowed"); static if (hasLength!Range) { immutable len = _input.length; @@ -3900,7 +4441,7 @@ if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && return Result(r, width, fillChar); } else - static assert(0); + static assert(0, "Invalid character type of " ~ C.stringof); } /// @@ -3947,9 +4488,9 @@ if (isConvertibleToString!Range) } /++ - Center $(D s) in a field $(D width) characters wide. $(D fillChar) + Center `s` in a field `width` characters wide. `fillChar` is the character that will be used to fill up the space in the field that - $(D s) doesn't fill. + `s` doesn't fill. Params: s = The string to center @@ -3984,8 +4525,8 @@ unittest assertCTFEable!( { - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + {{ S s = to!S("hello"); assert(leftJustify(s, 2) == "hello"); @@ -4007,20 +4548,20 @@ unittest 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) + Center justify `r` in a field `width` characters wide. `fillChar` is the character that will be used to fill up the space in the field that - $(D r) doesn't fill. + `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 + fillChar = used to pad end up to `width` characters Returns: a lazy range of the center justified result @@ -4084,12 +4625,13 @@ if (isConvertibleToString!Range) assert(testAliasedString!centerJustifier("hello", 8)); } -@system unittest +@safe unittest { static auto byFwdRange(dstring s) { static struct FRange { + @safe: dstring str; this(dstring s) { str = s; } @property bool empty() { return str.length == 0; } @@ -4124,7 +4666,7 @@ if (isConvertibleToString!Range) /++ - Replace each tab character in $(D s) with the number of spaces necessary + Replace each tab character in `s` with the number of spaces necessary to align the following character at the next tab stop. Params: @@ -4143,7 +4685,7 @@ if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) } /// -@system pure unittest +@safe pure unittest { assert(detab(" \n\tx", 9) == " \n x"); } @@ -4174,7 +4716,7 @@ if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) } /++ - Replace each tab character in $(D r) with the number of spaces + Replace each tab character in `r` with the number of spaces necessary to align the following character at the next tab stop. Params: @@ -4296,7 +4838,7 @@ if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && } /// -@system pure unittest +@safe pure unittest { import std.array : array; @@ -4314,7 +4856,7 @@ if (isConvertibleToString!Range) assert(testAliasedString!detabber( " ab\t asdf ", 8)); } -@system pure unittest +@safe pure unittest { import std.algorithm.comparison : cmp; import std.conv : to; @@ -4322,8 +4864,8 @@ if (isConvertibleToString!Range) assertCTFEable!( { - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static 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); @@ -4340,12 +4882,12 @@ if (isConvertibleToString!Range) assert(detab("\u0085\t", 9) == "\u0085 "); assert(detab("\u2028\t", 9) == "\u2028 "); assert(detab(" \u2029\t", 9) == " \u2029 "); - } + }} }); } /// -@system pure unittest +@safe pure unittest { import std.array : array; import std.utf : byChar, byWchar; @@ -4361,12 +4903,12 @@ if (isConvertibleToString!Range) } /++ - Replaces spaces in $(D s) with the optimal number of tabs. + Replaces spaces in `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. + tabSize = Tab columns are `tabSize` spaces apart. Returns: GC allocated string with spaces replaced with tabs; @@ -4401,7 +4943,7 @@ if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) && } /++ - Replaces spaces in range $(D r) with the optimal number of tabs. + Replaces spaces in range `r` with the optimal number of tabs. All spaces and tabs at the end of a line are removed. Params: @@ -4420,7 +4962,7 @@ if (isForwardRange!Range && !isConvertibleToString!Range) import std.uni : lineSep, paraSep, nelSep; import std.utf : codeUnitLimit, decodeFront; - assert(tabSize > 0); + assert(tabSize > 0, "tabSize must be greater than 0"); alias C = Unqual!(ElementEncodingType!Range); static struct Result @@ -4508,7 +5050,8 @@ if (isForwardRange!Range && !isConvertibleToString!Range) { while (1) { - assert(_input.length); + assert(_input.length, "input did not contain non " + ~ "whitespace character"); cx = _input[0]; if (cx == ' ') ++column; @@ -4523,7 +5066,8 @@ if (isForwardRange!Range && !isConvertibleToString!Range) { while (1) { - assert(!_input.empty); + assert(_input.length, "input did not contain non " + ~ "whitespace character"); cx = _input.front; if (cx == ' ') ++column; @@ -4716,16 +5260,17 @@ unittest /++ - 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. + Replaces the characters in `str` which are keys in `transTable` with + their corresponding values in `transTable`. `transTable` is an AA + where its keys are `dchar` and its values are either `dchar` or some + type of string. Also, if `toRemove` is given, the characters in it are + removed from `str` prior to translation. `str` itself is unaltered. A copy with the changes is returned. See_Also: - $(LREF tr) - $(REF replace, std,array) + $(LREF tr), + $(REF replace, std,array), + $(REF substitute, std,algorithm,iteration) Params: str = The original string. @@ -4756,7 +5301,8 @@ if (isSomeChar!C1 && isSomeChar!C2) assert(translate("hello world", transTable2) == "h5llorange worangerld"); } -@safe pure unittest // issue 13018 +// https://issues.dlang.org/show_bug.cgi?id=13018 +@safe pure unittest { immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; assert(translate("hello world", transTable1) == "h5ll7 w7rld"); @@ -4774,10 +5320,11 @@ if (isSomeChar!C1 && isSomeChar!C2) assertCTFEable!( { - foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[], + static foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[], wchar[], const(wchar)[], immutable(wchar)[], dchar[], const(dchar)[], immutable(dchar)[])) - { + {(){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 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']) == @@ -4788,13 +5335,14 @@ if (isSomeChar!C1 && isSomeChar!C2) 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)[], + static 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], + (){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 + static 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")); @@ -4802,13 +5350,14 @@ if (isSomeChar!C1 && isSomeChar!C2) == 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)))); - } + assert(translate(s, transTable) == "qe55o wor5d"); + }();} }); } @@ -4831,10 +5380,11 @@ if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2) assertCTFEable!( { - foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[], + static foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[], wchar[], const(wchar)[], immutable(wchar)[], dchar[], const(dchar)[], immutable(dchar)[])) - { + {(){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 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"]) == @@ -4849,14 +5399,14 @@ if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2) 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)[], + static 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], + (){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 + static 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")); @@ -4868,18 +5418,19 @@ if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2) 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)))); - } + assert(translate(s, transTable) == "sillyeputtyputtyo worputtyd"); + }();} }); } /++ - This is an overload of $(D translate) which takes an existing buffer to write the contents to. + This is an overload of `translate` which takes an existing buffer to write the contents to. Params: str = The original string. @@ -4888,7 +5439,7 @@ if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2) 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, +void translate(C1, C2 = immutable char, Buffer)(const(C1)[] str, in dchar[dchar] transTable, const(C2)[] toRemove, Buffer buffer) @@ -4916,7 +5467,8 @@ if (isSomeChar!C1 && isSomeChar!C2 && isOutputRange!(Buffer, C1)) assert(buffer.data == "h5llorange worangerld"); } -@safe pure unittest // issue 13018 +// https://issues.dlang.org/show_bug.cgi?id=13018 +@safe pure unittest { import std.array : appender; immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; @@ -4944,8 +5496,8 @@ 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, +private void translateImpl(C1, T, C2, Buffer)(const(C1)[] str, + scope T transTable, const(C2)[] toRemove, Buffer buffer) { @@ -4974,26 +5526,27 @@ private void translateImpl(C1, T, C2, Buffer)(C1[] str, 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). + an AA. Rather, it takes a `string` generated by $(LREF makeTransTable). - The array generated by $(D makeTransTable) is $(D 256) elements long such that + The array generated by `makeTransTable` is `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) + ASCII characters if you want to (ASCII only actually uses `128` characters), but be warned that Extended ASCII characters are not valid - Unicode and therefore will result in a $(D UTFException) being thrown from + Unicode and therefore will result in a `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. + `127` with another code unit or replacing any code unit with another code + unit greater than `127` which will cause UTF validation issues. See_Also: - $(LREF tr) - $(REF replace, std,array) + $(LREF tr), + $(REF replace, std,array), + $(REF substitute, std,algorithm,iteration) Params: str = The original string. @@ -5001,13 +5554,16 @@ private void translateImpl(C1, T, C2, Buffer)(C1[] str, 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)) +C[] translate(C = immutable char)(scope const(char)[] str, scope const(char)[] transTable, + scope const(char)[] toRemove = null) @trusted pure nothrow +if (is(immutable C == immutable char)) in { - assert(transTable.length == 256); + import std.conv : to; + assert(transTable.length == 256, "transTable had invalid length of " ~ + to!string(transTable.length)); } -body +do { bool[256] remTable = false; @@ -5033,6 +5589,14 @@ body return cast(C[])(buffer); } +/// +@safe pure nothrow unittest +{ + auto transTable1 = makeTrans("eo5", "57q"); + assert(translate("hello world", transTable1) == "h5ll7 w7rld"); + + assert(translate("hello world", transTable1, "low") == "h5 rd"); +} /** * Do same thing as $(LREF makeTransTable) but allocate the translation table @@ -5040,7 +5604,7 @@ body * * Use $(LREF makeTransTable) instead. */ -string makeTrans(in char[] from, in char[] to) @trusted pure nothrow +string makeTrans(scope const(char)[] from, scope const(char)[] to) @trusted pure nothrow { return makeTransTable(from, to)[].idup; } @@ -5064,19 +5628,20 @@ string makeTrans(in char[] from, in char[] to) @trusted pure nothrow * Returns: * translation array */ - -char[256] makeTransTable(in char[] from, in char[] to) @safe pure nothrow @nogc +char[256] makeTransTable(scope const(char)[] from, scope const(char)[] to) @safe pure nothrow @nogc in { import std.ascii : isASCII; - assert(from.length == to.length); - assert(from.length <= 256); + assert(from.length == to.length, "from.length must match to.length"); + assert(from.length <= 256, "from.length must be <= 256"); foreach (char c; from) - assert(isASCII(c)); + assert(isASCII(c), + "all characters in from must be valid ascii character"); foreach (char c; to) - assert(isASCII(c)); + assert(isASCII(c), + "all characters in to must be valid ascii character"); } -body +do { char[256] result = void; @@ -5087,6 +5652,13 @@ body return result; } +/// +@safe pure unittest +{ + assert(translate("hello world", makeTransTable("hl", "q5")) == "qe55o wor5d"); + assert(translate("hello world", makeTransTable("12345", "67890")) == "hello world"); +} + @safe pure unittest { import std.conv : to; @@ -5094,16 +5666,17 @@ body assertCTFEable!( { - foreach (C; AliasSeq!(char, const char, immutable char)) - { + static 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)))); - } + assert(translate(s, transTable) == "qe55o wor5d"); + }} - foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[])) + static 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")) == @@ -5114,8 +5687,8 @@ body 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 + static foreach (T; AliasSeq!(char[], const(char)[], immutable(char)[])) + { 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")) == @@ -5124,13 +5697,13 @@ body 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. + This is an $(I $(RED ASCII-only)) overload of `translate` which takes an existing buffer to write the contents to. Params: str = The original string. @@ -5139,14 +5712,15 @@ body 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)) +void translate(C = immutable char, Buffer)(scope const(char)[] str, scope const(char)[] transTable, + scope const(char)[] toRemove, Buffer buffer) @trusted pure +if (is(immutable C == immutable char) && isOutputRange!(Buffer, char)) in { - assert(transTable.length == 256); + assert(transTable.length == 256, format! + "transTable.length %s must equal 256"(transTable.length)); } -body +do { bool[256] remTable = false; @@ -5255,10 +5829,10 @@ if (isSomeString!S) /++ - Replaces the characters in $(D str) which are in $(D from) with the - the corresponding characters in $(D to) and returns the resulting string. + Replaces the characters in `str` which are in `from` with the + the corresponding characters in `to` and returns the resulting string. - $(D tr) is based on + `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. @@ -5271,27 +5845,32 @@ if (isSomeString!S) 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 + $(TR $(TD `'c'`) $(TD Complement the list of characters in `from`)) + $(TR $(TD `'d'`) $(TD Removes matching characters with no corresponding + replacement in `to`)) + $(TR $(TD `'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'` is present, then the number of characters in + `to` may be only `0` or `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'` is $(I not) present, and `to` is empty, then + `to` is taken to be the same as `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). + If the modifier `'d'` is $(I not) present, and `to` is shorter than + `from`, then `to` is extended by replicating the last character in + `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 + Both `from` and `to` may contain ranges using the `'-'` character + (e.g. `"a-d"` is synonymous with `"abcd"`.) Neither accept a leading + `'^'` as meaning the complement of the string (use the `'c'` modifier for that). + + See_Also: + $(LREF translate), + $(REF replace, std,array), + $(REF substitute, std,algorithm,iteration) +/ C1[] tr(C1, C2, C3, C4 = immutable char) (C1[] str, const(C2)[] from, const(C3)[] to, const(C4)[] modifiers = null) @@ -5311,7 +5890,8 @@ C1[] tr(C1, C2, C3, C4 = immutable char) 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); + default: assert(false, "modifier must be one of ['c', 'd', 's'] not " + ~ c); } } @@ -5394,7 +5974,7 @@ C1[] tr(C1, C2, C3, C4 = immutable char) if (mod_s && modified && newc == lastc) continue; result.put(newc); - assert(newc != dchar.init); + assert(newc != dchar.init, "character must not be dchar.init"); modified = true; lastc = newc; continue; @@ -5408,6 +5988,15 @@ C1[] tr(C1, C2, C3, C4 = immutable char) return result.data; } +/// +@safe pure unittest +{ + assert(tr("abcdef", "cd", "CD") == "abCDef"); + assert(tr("1st March, 2018", "March", "MAR", "s") == "1st MAR, 2018"); + assert(tr("abcdef", "ef", "", "d") == "abcd"); + assert(tr("14-Jul-87", "a-zA-Z", " ", "cs") == " Jul "); +} + @safe pure unittest { import std.algorithm.comparison : equal; @@ -5447,6 +6036,7 @@ C1[] tr(C1, C2, C3, C4 = immutable char) auto s = to!S("hello world"); static assert(is(typeof(s) == typeof(tr(s, "he", "if")))); + assert(tr(s, "he", "if") == "ifllo world"); } }); } @@ -5459,11 +6049,11 @@ C1[] tr(C1, C2, C3, C4 = immutable char) } /** - * 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 + * Takes a string `s` and determines if it represents a number. This function + * also takes an optional parameter, `bAllowSep`, which will accept the + * separator characters `','` and `'__'` 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 + * of the conversion functions like `to!int()`, `to!float()`, and etc * else an error will occur. * * Also please note, that no spaces are allowed within the string @@ -5476,7 +6066,7 @@ C1[] tr(C1, C2, C3, C4 = immutable char) * bAllowSep = accept separator characters or not * * Returns: - * $(D bool) + * `bool` */ bool isNumeric(S)(S s, bool bAllowSep = false) if (isSomeString!S || @@ -5692,7 +6282,7 @@ if (isSomeString!S || { import std.conv : to; - foreach (T; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) + static foreach (T; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) { assert("123".to!T.isNumeric()); assert("123UL".to!T.isNumeric()); @@ -5769,7 +6359,6 @@ if (isSomeString!S || { 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-"; @@ -5803,10 +6392,8 @@ if (isSomeString!S || * $(LUCKY The Soundex Indexing System) * $(LREF soundex) * - * Bugs: + * Note: * 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) && @@ -5864,12 +6451,25 @@ if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && return result; } +/// ditto char[4] soundexer(Range)(auto ref Range str) if (isConvertibleToString!Range) { return soundexer!(StringTypeOf!Range)(str); } +/// +@safe unittest +{ + assert(soundexer("Gauss") == "G200"); + assert(soundexer("Ghosh") == "G200"); + + assert(soundexer("Robert") == "R163"); + assert(soundexer("Rupert") == "R163"); + + assert(soundexer("0123^&^^**&^") == ['\0', '\0', '\0', '\0']); +} + /***************************** * Like $(LREF soundexer), but with different parameters * and return value. @@ -5885,7 +6485,7 @@ if (isConvertibleToString!Range) * See_Also: * $(LREF soundexer) */ -char[] soundex(const(char)[] str, char[] buffer = null) +char[] soundex(scope const(char)[] str, return scope char[] buffer = null) @safe pure nothrow in { @@ -5895,13 +6495,15 @@ out (result) { if (result !is null) { - assert(result.length == 4); - assert(result[0] >= 'A' && result[0] <= 'Z'); + assert(result.length == 4, "Result must have length of 4"); + assert(result[0] >= 'A' && result[0] <= 'Z', "The first character of " + ~ " the result must be an upper character not " ~ result); foreach (char c; result[1 .. 4]) - assert(c >= '0' && c <= '6'); + assert(c >= '0' && c <= '6', "the last three character of the" + ~ " result must be number between 0 and 6 not " ~ result); } } -body +do { char[4] result = soundexer(str); if (result[0] == 0) @@ -5912,6 +6514,17 @@ body return buffer; } +/// +@safe unittest +{ + assert(soundex("Gauss") == "G200"); + assert(soundex("Ghosh") == "G200"); + + assert(soundex("Robert") == "R163"); + assert(soundex("Rupert") == "R163"); + + assert(soundex("0123^&^^**&^") == null); +} @safe pure nothrow unittest { @@ -5981,7 +6594,6 @@ body * 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; @@ -6095,11 +6707,11 @@ string[string] abbrev(string[] values) @safe pure */ size_t column(Range)(Range str, in size_t tabsize = 8) -if ((isInputRange!Range && isSomeChar!(Unqual!(ElementEncodingType!Range)) || +if ((isInputRange!Range && isSomeChar!(ElementEncodingType!Range) || isNarrowString!Range) && !isConvertibleToString!Range) { - static if (is(Unqual!(ElementEncodingType!Range) == char)) + static if (is(immutable ElementEncodingType!Range == immutable char)) { // decoding needed for chars import std.utf : byDchar; @@ -6378,7 +6990,7 @@ void main() { * StringException if indentation is done with different sequences * of whitespace characters. */ -S[] outdent(S)(S[] lines) @safe pure +S[] outdent(S)(return scope S[] lines) @safe pure if (isSomeString!S) { import std.algorithm.searching : startsWith; @@ -6438,6 +7050,32 @@ if (isSomeString!S) return lines; } +/// +@safe pure unittest +{ + auto str1 = [ + " void main()\n", + " {\n", + " test();\n", + " }\n" + ]; + auto str1Expected = [ + "void main()\n", + "{\n", + " test();\n", + "}\n" + ]; + assert(str1.outdent == str1Expected); + + auto str2 = [ + "void main()\n", + " {\n", + " test();\n", + " }\n" + ]; + assert(str2.outdent == str2); +} + @safe pure unittest { import std.conv : to; @@ -6470,8 +7108,8 @@ if (isSomeString!S) assertCTFEable!( { - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ enum S blank = ""; assert(blank.outdent() == blank); static assert(blank.outdent() == blank); @@ -6527,7 +7165,7 @@ if (isSomeString!S) enum expected7 = "a \nb "; assert(testStr7.outdent() == expected7); static assert(testStr7.outdent() == expected7); - } + }} }); } @@ -6538,11 +7176,11 @@ if (isSomeString!S) assertThrown!StringException(bad.outdent); } -/** Assume the given array of integers $(D arr) is a well-formed UTF string and +/** Assume the given array of integers `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. +`ubyte` becomes `char`, `ushort` becomes `wchar` and `uint` +becomes `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. @@ -6553,16 +7191,27 @@ Params: Returns: arr retyped as an array of chars, wchars, or dchars +Throws: + In debug mode `AssertError`, when the result is not a well-formed UTF string. + See_Also: $(LREF representation) */ -auto assumeUTF(T)(T[] arr) pure -if (staticIndexOf!(Unqual!T, ubyte, ushort, uint) != -1) +auto assumeUTF(T)(T[] arr) +if (staticIndexOf!(immutable T, immutable ubyte, immutable ushort, immutable uint) != -1) { import std.traits : ModifyTypePreservingTQ; + import std.exception : collectException; import std.utf : validate; + alias ToUTFType(U) = AliasSeq!(char, wchar, dchar)[U.sizeof / 2]; - auto asUTF = cast(ModifyTypePreservingTQ!(ToUTFType, T)[])arr; - debug validate(asUTF); + auto asUTF = cast(ModifyTypePreservingTQ!(ToUTFType, T)[]) arr; + + debug + { + scope ex = collectException(validate(asUTF)); + assert(!ex, ex.msg); + } + return asUTF; } @@ -6573,14 +7222,14 @@ if (staticIndexOf!(Unqual!T, ubyte, ushort, uint) != -1) immutable(ubyte)[] b = a.representation; string c = b.assumeUTF; - assert(a == c); + assert(c == "Hölo World"); } pure @system unittest { import std.algorithm.comparison : equal; - foreach (T; AliasSeq!(char[], wchar[], dchar[])) - { + static foreach (T; AliasSeq!(char[], wchar[], dchar[])) + {{ immutable T jti = "Hello World"; T jt = jti.dup; @@ -6609,5 +7258,18 @@ pure @system unittest assert(equal(jt, ht)); assert(equal(jt, htc)); assert(equal(jt, hti)); - } + }} +} + +pure @system unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown, assertNotThrown; + + immutable(ubyte)[] a = [ 0xC0 ]; + + debug + assertThrown!AssertError( () nothrow @nogc @safe {cast(void) a.assumeUTF;} () ); + else + assertNotThrown!AssertError( () nothrow @nogc @safe {cast(void) a.assumeUTF;} () ); } diff --git a/libphobos/src/std/sumtype.d b/libphobos/src/std/sumtype.d new file mode 100644 index 0000000..8242f1e --- /dev/null +++ b/libphobos/src/std/sumtype.d @@ -0,0 +1,2500 @@ +/++ +[SumType] is a generic discriminated union implementation that uses +design-by-introspection to generate safe and efficient code. Its features +include: + +* [Pattern matching.][match] +* Support for self-referential types. +* Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are + inferred whenever possible). +* A type-safe and memory-safe API compatible with DIP 1000 (`scope`). +* No dependency on runtime type information (`TypeInfo`). +* Compatibility with BetterC. + +License: Boost License 1.0 +Authors: Paul Backus ++/ +module std.sumtype; + +/// $(DIVID basic-usage,$(H3 Basic usage)) +version (D_BetterC) {} else +@safe unittest +{ + import std.math.operations : isClose; + + struct Fahrenheit { double degrees; } + struct Celsius { double degrees; } + struct Kelvin { double degrees; } + + alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin); + + // Construct from any of the member types. + Temperature t1 = Fahrenheit(98.6); + Temperature t2 = Celsius(100); + Temperature t3 = Kelvin(273); + + // Use pattern matching to access the value. + Fahrenheit toFahrenheit(Temperature t) + { + return Fahrenheit( + t.match!( + (Fahrenheit f) => f.degrees, + (Celsius c) => c.degrees * 9.0/5 + 32, + (Kelvin k) => k.degrees * 9.0/5 - 459.4 + ) + ); + } + + assert(toFahrenheit(t1).degrees.isClose(98.6)); + assert(toFahrenheit(t2).degrees.isClose(212)); + assert(toFahrenheit(t3).degrees.isClose(32)); + + // Use ref to modify the value in place. + void freeze(ref Temperature t) + { + t.match!( + (ref Fahrenheit f) => f.degrees = 32, + (ref Celsius c) => c.degrees = 0, + (ref Kelvin k) => k.degrees = 273 + ); + } + + freeze(t1); + assert(toFahrenheit(t1).degrees.isClose(32)); + + // Use a catch-all handler to give a default result. + bool isFahrenheit(Temperature t) + { + return t.match!( + (Fahrenheit f) => true, + _ => false + ); + } + + assert(isFahrenheit(t1)); + assert(!isFahrenheit(t2)); + assert(!isFahrenheit(t3)); +} + +/** $(DIVID introspection-based-matching, $(H3 Introspection-based matching)) + * + * In the `length` and `horiz` functions below, the handlers for `match` do not + * specify the types of their arguments. Instead, matching is done based on how + * the argument is used in the body of the handler: any type with `x` and `y` + * properties will be matched by the `rect` handlers, and any type with `r` and + * `theta` properties will be matched by the `polar` handlers. + */ +version (D_BetterC) {} else +@safe unittest +{ + import std.math.operations : isClose; + import std.math.trigonometry : cos; + import std.math.constants : PI; + import std.math.algebraic : sqrt; + + struct Rectangular { double x, y; } + struct Polar { double r, theta; } + alias Vector = SumType!(Rectangular, Polar); + + double length(Vector v) + { + return v.match!( + rect => sqrt(rect.x^^2 + rect.y^^2), + polar => polar.r + ); + } + + double horiz(Vector v) + { + return v.match!( + rect => rect.x, + polar => polar.r * cos(polar.theta) + ); + } + + Vector u = Rectangular(1, 1); + Vector v = Polar(1, PI/4); + + assert(length(u).isClose(sqrt(2.0))); + assert(length(v).isClose(1)); + assert(horiz(u).isClose(1)); + assert(horiz(v).isClose(sqrt(0.5))); +} + +/** $(DIVID arithmetic-expression-evaluator, $(H3 Arithmetic expression evaluator)) + * + * This example makes use of the special placeholder type `This` to define a + * [recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an + * [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for + * representing simple arithmetic expressions. + */ +version (D_BetterC) {} else +@system unittest +{ + import std.functional : partial; + import std.traits : EnumMembers; + import std.typecons : Tuple; + + enum Op : string + { + Plus = "+", + Minus = "-", + Times = "*", + Div = "/" + } + + // An expression is either + // - a number, + // - a variable, or + // - a binary operation combining two sub-expressions. + alias Expr = SumType!( + double, + string, + Tuple!(Op, "op", This*, "lhs", This*, "rhs") + ); + + // Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"), + // the Tuple type above with Expr substituted for This. + alias BinOp = Expr.Types[2]; + + // Factory function for number expressions + Expr* num(double value) + { + return new Expr(value); + } + + // Factory function for variable expressions + Expr* var(string name) + { + return new Expr(name); + } + + // Factory function for binary operation expressions + Expr* binOp(Op op, Expr* lhs, Expr* rhs) + { + return new Expr(BinOp(op, lhs, rhs)); + } + + // Convenience wrappers for creating BinOp expressions + alias sum = partial!(binOp, Op.Plus); + alias diff = partial!(binOp, Op.Minus); + alias prod = partial!(binOp, Op.Times); + alias quot = partial!(binOp, Op.Div); + + // Evaluate expr, looking up variables in env + double eval(Expr expr, double[string] env) + { + return expr.match!( + (double num) => num, + (string var) => env[var], + (BinOp bop) + { + double lhs = eval(*bop.lhs, env); + double rhs = eval(*bop.rhs, env); + final switch (bop.op) + { + static foreach (op; EnumMembers!Op) + { + case op: + return mixin("lhs" ~ op ~ "rhs"); + } + } + } + ); + } + + // Return a "pretty-printed" representation of expr + string pprint(Expr expr) + { + import std.format : format; + + return expr.match!( + (double num) => "%g".format(num), + (string var) => var, + (BinOp bop) => "(%s %s %s)".format( + pprint(*bop.lhs), + cast(string) bop.op, + pprint(*bop.rhs) + ) + ); + } + + Expr* myExpr = sum(var("a"), prod(num(2), var("b"))); + double[string] myEnv = ["a":3, "b":4, "c":7]; + + assert(eval(*myExpr, myEnv) == 11); + assert(pprint(*myExpr) == "(a + (2 * b))"); +} + +import std.format.spec : FormatSpec, singleSpec; +import std.meta : AliasSeq, Filter, IndexOf = staticIndexOf, Map = staticMap; +import std.meta : NoDuplicates; +import std.meta : anySatisfy, allSatisfy; +import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; +import std.traits : isAssignable, isCopyable, isStaticArray, isRvalueAssignable; +import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; +import std.traits : CommonType; +import std.typecons : ReplaceTypeUnless; +import std.typecons : Flag; + +/// Placeholder used to refer to the enclosing [SumType]. +struct This {} + +// Converts an unsigned integer to a compile-time string constant. +private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; + +// Check that .stringof does what we expect, since it's not guaranteed by the +// language spec. +@safe unittest +{ + assert(toCtString!0 == "0"); + assert(toCtString!123456 == "123456"); +} + +// True if a variable of type T can appear on the lhs of an assignment +private enum isAssignableTo(T) = + isAssignable!T || (!isCopyable!T && isRvalueAssignable!T); + +// toHash is required by the language spec to be nothrow and @safe +private enum isHashable(T) = __traits(compiles, + () nothrow @safe { hashOf(T.init); } +); + +private enum hasPostblit(T) = __traits(hasPostblit, T); + +/** + * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a + * single value from any of a specified set of types. + * + * The value in a `SumType` can be operated on using [pattern matching][match]. + * + * To avoid ambiguity, duplicate types are not allowed (but see the + * ["basic usage" example](#basic-usage) for a workaround). + * + * The special type `This` can be used as a placeholder to create + * self-referential types, just like with `Algebraic`. See the + * ["Arithmetic expression evaluator" example](#arithmetic-expression-evaluator) for + * usage. + * + * A `SumType` is initialized by default to hold the `.init` value of its + * first member type, just like a regular union. The version identifier + * `SumTypeNoDefaultCtor` can be used to disable this behavior. + * + * See_Also: $(REF Algebraic, std,variant) + */ +struct SumType(Types...) +if (is(NoDuplicates!Types == Types) && Types.length > 0) +{ + /// The types a `SumType` can hold. + alias Types = AliasSeq!( + ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType) + ); + +private: + + enum bool canHoldTag(T) = Types.length <= T.max; + alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong); + + alias Tag = Filter!(canHoldTag, unsignedInts)[0]; + + union Storage + { + // Workaround for https://issues.dlang.org/show_bug.cgi?id=20068 + template memberName(T) + if (IndexOf!(T, Types) >= 0) + { + enum tid = IndexOf!(T, Types); + mixin("enum memberName = `values_", toCtString!tid, "`;"); + } + + static foreach (T; Types) + { + mixin("T ", memberName!T, ";"); + } + } + + Storage storage; + Tag tag; + + /* Accesses the value stored in a SumType. + * + * This method is memory-safe, provided that: + * + * 1. A SumType's tag is always accurate. + * 2. A SumType cannot be assigned to in @safe code if that assignment + * could cause unsafe aliasing. + * + * All code that accesses a SumType's tag or storage directly, including + * @safe code in this module, must be manually checked to ensure that it + * does not violate either of the above requirements. + */ + @trusted + ref inout(T) get(T)() inout + if (IndexOf!(T, Types) >= 0) + { + enum tid = IndexOf!(T, Types); + assert(tag == tid, + "This `" ~ SumType.stringof ~ + "` does not contain a(n) `" ~ T.stringof ~ "`" + ); + return __traits(getMember, storage, Storage.memberName!T); + } + +public: + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 + version (StdDdoc) + { + // Dummy type to stand in for loop variable + private struct T; + + /// Constructs a `SumType` holding a specific value. + this(T value); + + /// ditto + this(const(T) value) const; + + /// ditto + this(immutable(T) value) immutable; + } + + static foreach (tid, T; Types) + { + /// Constructs a `SumType` holding a specific value. + this(T value) + { + import core.lifetime : forward; + + static if (isCopyable!T) + { + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 + __traits(getMember, storage, Storage.memberName!T) = __ctfe ? value : forward!value; + } + else + { + __traits(getMember, storage, Storage.memberName!T) = forward!value; + } + + tag = tid; + } + + static if (isCopyable!(const(T))) + { + static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid) + { + /// ditto + this(const(T) value) const + { + __traits(getMember, storage, Storage.memberName!T) = value; + tag = tid; + } + } + } + else + { + @disable this(const(T) value) const; + } + + static if (isCopyable!(immutable(T))) + { + static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid) + { + /// ditto + this(immutable(T) value) immutable + { + __traits(getMember, storage, Storage.memberName!T) = value; + tag = tid; + } + } + } + else + { + @disable this(immutable(T) value) immutable; + } + } + + static if (anySatisfy!(hasElaborateCopyConstructor, Types)) + { + static if + ( + allSatisfy!(isCopyable, Map!(InoutOf, Types)) + && !anySatisfy!(hasPostblit, Map!(InoutOf, Types)) + ) + { + /// Constructs a `SumType` that's a copy of another `SumType`. + this(ref inout(SumType) other) inout + { + storage = other.match!((ref value) { + alias OtherTypes = Map!(InoutOf, Types); + enum tid = IndexOf!(typeof(value), OtherTypes); + alias T = Types[tid]; + + mixin("inout(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }); + + tag = other.tag; + } + } + else + { + static if (allSatisfy!(isCopyable, Types)) + { + /// ditto + this(ref SumType other) + { + storage = other.match!((ref value) { + alias T = typeof(value); + + mixin("Storage newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }); + + tag = other.tag; + } + } + else + { + @disable this(ref SumType other); + } + + static if (allSatisfy!(isCopyable, Map!(ConstOf, Types))) + { + /// ditto + this(ref const(SumType) other) const + { + storage = other.match!((ref value) { + alias OtherTypes = Map!(ConstOf, Types); + enum tid = IndexOf!(typeof(value), OtherTypes); + alias T = Types[tid]; + + mixin("const(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }); + + tag = other.tag; + } + } + else + { + @disable this(ref const(SumType) other) const; + } + + static if (allSatisfy!(isCopyable, Map!(ImmutableOf, Types))) + { + /// ditto + this(ref immutable(SumType) other) immutable + { + storage = other.match!((ref value) { + alias OtherTypes = Map!(ImmutableOf, Types); + enum tid = IndexOf!(typeof(value), OtherTypes); + alias T = Types[tid]; + + mixin("immutable(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }); + + tag = other.tag; + } + } + else + { + @disable this(ref immutable(SumType) other) immutable; + } + } + } + + version (SumTypeNoDefaultCtor) + { + @disable this(); + } + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 + version (StdDdoc) + { + // Dummy type to stand in for loop variable + private struct T; + + /** + * Assigns a value to a `SumType`. + * + * Assigning to a `SumType` is `@system` if any of the + * `SumType`'s members contain pointers or references, since + * those members may be reachable through external references, + * and overwriting them could therefore lead to memory + * corruption. + * + * An individual assignment can be `@trusted` if the caller can + * guarantee that there are no outstanding references to $(I any) + * of the `SumType`'s members when the assignment occurs. + */ + ref SumType opAssign(T rhs); + } + + static foreach (tid, T; Types) + { + static if (isAssignableTo!T) + { + /** + * Assigns a value to a `SumType`. + * + * Assigning to a `SumType` is `@system` if any of the `SumType`'s + * $(I other) members contain pointers or references, since those + * members may be reachable through external references, and + * overwriting them could therefore lead to memory corruption. + * + * An individual assignment can be `@trusted` if the caller can + * guarantee that, when the assignment occurs, there are no + * outstanding references to any such members. + */ + ref SumType opAssign(T rhs) + { + import core.lifetime : forward; + import std.traits : hasIndirections, hasNested; + import std.meta : AliasSeq, Or = templateOr; + + alias OtherTypes = + AliasSeq!(Types[0 .. tid], Types[tid + 1 .. $]); + enum unsafeToOverwrite = + anySatisfy!(Or!(hasIndirections, hasNested), OtherTypes); + + static if (unsafeToOverwrite) + { + cast(void) () @system {}(); + } + + this.match!destroyIfOwner; + + mixin("Storage newStorage = { ", + Storage.memberName!T, ": forward!rhs", + " };"); + + storage = newStorage; + tag = tid; + + return this; + } + } + } + + static if (allSatisfy!(isAssignableTo, Types)) + { + static if (allSatisfy!(isCopyable, Types)) + { + /** + * Copies the value from another `SumType` into this one. + * + * See the value-assignment overload for details on `@safe`ty. + * + * Copy assignment is `@disable`d if any of `Types` is non-copyable. + */ + ref SumType opAssign(ref SumType rhs) + { + rhs.match!((ref value) { this = value; }); + return this; + } + } + else + { + @disable ref SumType opAssign(ref SumType rhs); + } + + /** + * Moves the value from another `SumType` into this one. + * + * See the value-assignment overload for details on `@safe`ty. + */ + ref SumType opAssign(SumType rhs) + { + import core.lifetime : move; + + rhs.match!((ref value) { this = move(value); }); + return this; + } + } + + /** + * Compares two `SumType`s for equality. + * + * Two `SumType`s are equal if they are the same kind of `SumType`, they + * contain values of the same type, and those values are equal. + */ + bool opEquals(this This, Rhs)(auto ref Rhs rhs) + if (!is(CommonType!(This, Rhs) == void)) + { + static if (is(This == Rhs)) + { + return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) { + static if (is(typeof(value) == typeof(rhsValue))) + { + return value == rhsValue; + } + else + { + return false; + } + }); + } + else + { + alias CommonSumType = CommonType!(This, Rhs); + return cast(CommonSumType) this == cast(CommonSumType) rhs; + } + } + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=19407 + static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types))) + { + // If possible, include the destructor only when it's needed + private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types); + } + else + { + // If we can't tell, always include it, even when it does nothing + private enum includeDtor = true; + } + + static if (includeDtor) + { + /// Calls the destructor of the `SumType`'s current value. + ~this() + { + this.match!destroyIfOwner; + } + } + + invariant + { + this.match!((ref value) { + static if (is(typeof(value) == class)) + { + if (value !is null) + { + assert(value); + } + } + else static if (is(typeof(value) == struct)) + { + assert(&value); + } + }); + } + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 + version (StdDdoc) + { + /** + * Returns a string representation of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + string toString(this This)(); + + /** + * Handles formatted writing of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + * + * Params: + * sink = Output range to write to. + * fmt = Format specifier to use. + * + * See_Also: $(REF formatValue, std,format) + */ + void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt); + } + + version (D_BetterC) {} else + /** + * Returns a string representation of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + string toString(this This)() + { + import std.conv : to; + + return this.match!(to!string); + } + + version (D_BetterC) {} else + /** + * Handles formatted writing of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + * + * Params: + * sink = Output range to write to. + * fmt = Format specifier to use. + * + * See_Also: $(REF formatValue, std,format) + */ + void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) + { + import std.format.write : formatValue; + + this.match!((ref value) { + formatValue(sink, value, fmt); + }); + } + + static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) + { + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 + version (StdDdoc) + { + /** + * Returns the hash of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + size_t toHash() const; + } + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=20095 + version (D_BetterC) {} else + /** + * Returns the hash of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + size_t toHash() const + { + return this.match!hashOf; + } + } +} + +// Construction +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); +} + +// Assignment +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + x = 3.14; +} + +// Self assignment +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); + y = x; +} + +// Equality +@safe unittest +{ + alias MySum = SumType!(int, float); + + assert(MySum(123) == MySum(123)); + assert(MySum(123) != MySum(456)); + assert(MySum(123) != MySum(123.0)); + assert(MySum(123) != MySum(456.0)); + +} + +// Equality of differently-qualified SumTypes +// Disabled in BetterC due to use of dynamic arrays +version (D_BetterC) {} else +@safe unittest +{ + alias SumA = SumType!(int, float); + alias SumB = SumType!(const(int[]), int[]); + alias SumC = SumType!(int[], const(int[])); + + int[] ma = [1, 2, 3]; + const(int[]) ca = [1, 2, 3]; + + assert(const(SumA)(123) == SumA(123)); + assert(const(SumB)(ma[]) == SumB(ca[])); + assert(const(SumC)(ma[]) == SumC(ca[])); +} + +// Imported types +@safe unittest +{ + import std.typecons : Tuple; + + alias MySum = SumType!(Tuple!(int, int)); +} + +// const and immutable types +@safe unittest +{ + alias MySum = SumType!(const(int[]), immutable(float[])); +} + +// Recursive types +@safe unittest +{ + alias MySum = SumType!(This*); + assert(is(MySum.Types[0] == MySum*)); +} + +// Allowed types +@safe unittest +{ + import std.meta : AliasSeq; + + alias MySum = SumType!(int, float, This*); + + assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); +} + +// Types with destructors and postblits +@system unittest +{ + int copies; + + static struct Test + { + bool initialized = false; + int* copiesPtr; + + this(this) { (*copiesPtr)++; } + ~this() { if (initialized) (*copiesPtr)--; } + } + + alias MySum = SumType!(int, Test); + + Test t = Test(true, &copies); + + { + MySum x = t; + assert(copies == 1); + } + assert(copies == 0); + + { + MySum x = 456; + assert(copies == 0); + } + assert(copies == 0); + + { + MySum x = t; + assert(copies == 1); + x = 456; + assert(copies == 0); + } + + { + MySum x = 456; + assert(copies == 0); + x = t; + assert(copies == 1); + } + + { + MySum x = t; + MySum y = x; + assert(copies == 2); + } + + { + MySum x = t; + MySum y; + y = x; + assert(copies == 2); + } +} + +// Doesn't destroy reference types +// Disabled in BetterC due to use of classes +version (D_BetterC) {} else +@system unittest +{ + bool destroyed; + + class C + { + ~this() + { + destroyed = true; + } + } + + struct S + { + ~this() {} + } + + alias MySum = SumType!(S, C); + + C c = new C(); + { + MySum x = c; + destroyed = false; + } + assert(!destroyed); + + { + MySum x = c; + destroyed = false; + x = S(); + assert(!destroyed); + } +} + +// Types with @disable this() +@safe unittest +{ + static struct NoInit + { + @disable this(); + } + + alias MySum = SumType!(NoInit, int); + + assert(!__traits(compiles, MySum())); + auto _ = MySum(42); +} + +// const SumTypes +version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117 +@safe unittest +{ + auto _ = const(SumType!(int[]))([1, 2, 3]); +} + +// Equality of const SumTypes +@safe unittest +{ + alias MySum = SumType!int; + + auto _ = const(MySum)(123) == const(MySum)(456); +} + +// Compares reference types using value equality +@safe unittest +{ + import std.array : staticArray; + + static struct Field {} + static struct Struct { Field[] fields; } + alias MySum = SumType!Struct; + + static arr1 = staticArray([Field()]); + static arr2 = staticArray([Field()]); + + auto a = MySum(Struct(arr1[])); + auto b = MySum(Struct(arr2[])); + + assert(a == b); +} + +// toString +// Disabled in BetterC due to use of std.conv.text +version (D_BetterC) {} else +@safe unittest +{ + import std.conv : text; + + static struct Int { int i; } + static struct Double { double d; } + alias Sum = SumType!(Int, Double); + + assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); + assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); + assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); +} + +// string formatting +// Disabled in BetterC due to use of std.format.format +version (D_BetterC) {} else +@safe unittest +{ + import std.format : format; + + SumType!int x = 123; + + assert(format!"%s"(x) == format!"%s"(123)); + assert(format!"%x"(x) == format!"%x"(123)); +} + +// string formatting of qualified SumTypes +// Disabled in BetterC due to use of std.format.format and dynamic arrays +version (D_BetterC) {} else +@safe unittest +{ + import std.format : format; + + int[] a = [1, 2, 3]; + const(SumType!(int[])) x = a; + + assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); +} + +// Github issue #16 +// Disabled in BetterC due to use of dynamic arrays +version (D_BetterC) {} else +@safe unittest +{ + alias Node = SumType!(This[], string); + + // override inference of @system attribute for cyclic functions + assert((() @trusted => + Node([Node([Node("x")])]) + == + Node([Node([Node("x")])]) + )()); +} + +// Github issue #16 with const +// Disabled in BetterC due to use of dynamic arrays +version (D_BetterC) {} else +@safe unittest +{ + alias Node = SumType!(const(This)[], string); + + // override inference of @system attribute for cyclic functions + assert((() @trusted => + Node([Node([Node("x")])]) + == + Node([Node([Node("x")])]) + )()); +} + +// Stale pointers +// Disabled in BetterC due to use of dynamic arrays +version (D_BetterC) {} else +@system unittest +{ + alias MySum = SumType!(ubyte, void*[2]); + + MySum x = [null, cast(void*) 0x12345678]; + void** p = &x.get!(void*[2])[1]; + x = ubyte(123); + + assert(*p != cast(void*) 0x12345678); +} + +// Exception-safe assignment +// Disabled in BetterC due to use of exceptions +version (D_BetterC) {} else +@safe unittest +{ + static struct A + { + int value = 123; + } + + static struct B + { + int value = 456; + this(this) { throw new Exception("oops"); } + } + + alias MySum = SumType!(A, B); + + MySum x; + try + { + x = B(); + } + catch (Exception e) {} + + assert( + (x.tag == 0 && x.get!A.value == 123) || + (x.tag == 1 && x.get!B.value == 456) + ); +} + +// Types with @disable this(this) +@safe unittest +{ + import core.lifetime : move; + + static struct NoCopy + { + @disable this(this); + } + + alias MySum = SumType!NoCopy; + + NoCopy lval = NoCopy(); + + MySum x = NoCopy(); + MySum y = NoCopy(); + + + assert(!__traits(compiles, SumType!NoCopy(lval))); + + y = NoCopy(); + y = move(x); + assert(!__traits(compiles, y = lval)); + assert(!__traits(compiles, y = x)); + + bool b = x == y; +} + +// Github issue #22 +// Disabled in BetterC due to use of std.typecons.Nullable +version (D_BetterC) {} else +@safe unittest +{ + import std.typecons; + + static struct A + { + SumType!(Nullable!int) a = Nullable!int.init; + } +} + +// Static arrays of structs with postblits +// Disabled in BetterC due to use of dynamic arrays +version (D_BetterC) {} else +@safe unittest +{ + static struct S + { + int n; + this(this) { n++; } + } + + SumType!(S[1]) x = [S(0)]; + SumType!(S[1]) y = x; + + auto xval = x.get!(S[1])[0].n; + auto yval = y.get!(S[1])[0].n; + + assert(xval != yval); +} + +// Replacement does not happen inside SumType +// Disabled in BetterC due to use of associative arrays +version (D_BetterC) {} else +@safe unittest +{ + import std.typecons : Tuple, ReplaceTypeUnless; + alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; + alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A); + static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); +} + +// Supports nested self-referential SumTypes +@safe unittest +{ + import std.typecons : Tuple, Flag; + alias Nat = SumType!(Flag!"0", Tuple!(This*)); + alias Inner = SumType!Nat; + alias Outer = SumType!(Nat*, Tuple!(This*, This*)); +} + +// Self-referential SumTypes inside Algebraic +// Disabled in BetterC due to use of std.variant.Algebraic +version (D_BetterC) {} else +@safe unittest +{ + import std.variant : Algebraic; + + alias T = Algebraic!(SumType!(This*)); + + assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); +} + +// Doesn't call @system postblits in @safe code +@safe unittest +{ + static struct SystemCopy { @system this(this) {} } + SystemCopy original; + + assert(!__traits(compiles, () @safe + { + SumType!SystemCopy copy = original; + })); + + assert(!__traits(compiles, () @safe + { + SumType!SystemCopy copy; copy = original; + })); +} + +// Doesn't overwrite pointers in @safe code +@safe unittest +{ + alias MySum = SumType!(int*, int); + + MySum x; + + assert(!__traits(compiles, () @safe + { + x = 123; + })); + + assert(!__traits(compiles, () @safe + { + x = MySum(123); + })); +} + +// Types with invariants +// Disabled in BetterC due to use of exceptions +version (D_BetterC) {} else +@system unittest +{ + import std.exception : assertThrown; + import core.exception : AssertError; + + struct S + { + int i; + invariant { assert(i >= 0); } + } + + class C + { + int i; + invariant { assert(i >= 0); } + } + + // Only run test if contract checking is enabled + try + { + S probe = S(-1); + assert(&probe); + } + catch (AssertError _) + { + SumType!S x; + x.match!((ref v) { v.i = -1; }); + assertThrown!AssertError(assert(&x)); + + SumType!C y = new C(); + y.match!((ref v) { v.i = -1; }); + assertThrown!AssertError(assert(&y)); + } +} + +// Calls value postblit on self-assignment +@safe unittest +{ + static struct S + { + int n; + this(this) { n++; } + } + + SumType!S x = S(); + SumType!S y; + y = x; + + auto xval = x.get!S.n; + auto yval = y.get!S.n; + + assert(xval != yval); +} + +// Github issue #29 +@safe unittest +{ + alias A = SumType!string; + + @safe A createA(string arg) + { + return A(arg); + } + + @safe void test() + { + A a = createA(""); + } +} + +// SumTypes as associative array keys +// Disabled in BetterC due to use of associative arrays +version (D_BetterC) {} else +@safe unittest +{ + int[SumType!(int, string)] aa; +} + +// toString with non-copyable types +// Disabled in BetterC due to use of std.conv.to (in toString) +version (D_BetterC) {} else +@safe unittest +{ + struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy x; + + auto _ = x.toString(); +} + +// Can use the result of assignment +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum a = MySum(123); + MySum b = MySum(3.14); + + assert((a = b) == b); + assert((a = MySum(123)) == MySum(123)); + assert((a = 3.14) == MySum(3.14)); + assert(((a = b) = MySum(123)) == MySum(123)); +} + +// Types with copy constructors +@safe unittest +{ + static struct S + { + int n; + + this(ref return scope inout S other) inout + { + n = other.n + 1; + } + } + + SumType!S x = S(); + SumType!S y = x; + + auto xval = x.get!S.n; + auto yval = y.get!S.n; + + assert(xval != yval); +} + +// Copyable by generated copy constructors +@safe unittest +{ + static struct Inner + { + ref this(ref inout Inner other) {} + } + + static struct Outer + { + SumType!Inner inner; + } + + Outer x; + Outer y = x; +} + +// Types with qualified copy constructors +@safe unittest +{ + static struct ConstCopy + { + int n; + this(inout int n) inout { this.n = n; } + this(ref const typeof(this) other) const { this.n = other.n; } + } + + static struct ImmutableCopy + { + int n; + this(inout int n) inout { this.n = n; } + this(ref immutable typeof(this) other) immutable { this.n = other.n; } + } + + const SumType!ConstCopy x = const(ConstCopy)(1); + immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1); +} + +// Types with disabled opEquals +@safe unittest +{ + static struct S + { + @disable bool opEquals(const S rhs) const; + } + + auto _ = SumType!S(S()); +} + +// Types with non-const opEquals +@safe unittest +{ + static struct S + { + int i; + bool opEquals(S rhs) { return i == rhs.i; } + } + + auto _ = SumType!S(S(123)); +} + +// Incomparability of different SumTypes +@safe unittest +{ + SumType!(int, string) x = 123; + SumType!(string, int) y = 123; + + assert(!__traits(compiles, x != y)); +} + +// Self-reference in return/parameter type of function pointer member +// Disabled in BetterC due to use of delegates +version (D_BetterC) {} else +@safe unittest +{ + alias T = SumType!(int, This delegate(This)); +} + +// Construction and assignment from implicitly-convertible lvalue +@safe unittest +{ + alias MySum = SumType!bool; + + const(bool) b = true; + + MySum x = b; + MySum y; y = b; +} + +// @safe assignment to the only pointer type in a SumType +@safe unittest +{ + SumType!(string, int) sm = 123; + sm = "this should be @safe"; +} + +// Pointers to local variables +// https://issues.dlang.org/show_bug.cgi?id=22117 +@safe unittest +{ + int n = 123; + immutable int ni = 456; + + SumType!(int*) s = &n; + const SumType!(int*) sc = &n; + immutable SumType!(int*) si = ∋ +} + +/// True if `T` is an instance of the `SumType` template, otherwise false. +private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); + +@safe unittest +{ + static struct Wrapper + { + SumType!int s; + alias s this; + } + + assert(isSumTypeInstance!(SumType!int)); + assert(!isSumTypeInstance!Wrapper); +} + +/// True if `T` is a [SumType] or implicitly converts to one, otherwise false. +enum bool isSumType(T) = is(T : SumType!Args, Args...); + +/// +@safe unittest +{ + static struct ConvertsToSumType + { + SumType!int payload; + alias payload this; + } + + static struct ContainsSumType + { + SumType!int payload; + } + + assert(isSumType!(SumType!int)); + assert(isSumType!ConvertsToSumType); + assert(!isSumType!ContainsSumType); +} + +/** + * Calls a type-appropriate function with the value held in a [SumType]. + * + * For each possible type the [SumType] can hold, the given handlers are + * checked, in order, to see whether they accept a single argument of that type. + * The first one that does is chosen as the match for that type. (Note that the + * first match may not always be the most exact match. + * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for + * one common pitfall.) + * + * Every type must have a matching handler, and every handler must match at + * least one type. This is enforced at compile time. + * + * Handlers may be functions, delegates, or objects with `opCall` overloads. If + * a function with more than one overload is given as a handler, all of the + * overloads are considered as potential matches. + * + * Templated handlers are also accepted, and will match any type for which they + * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See + * ["Introspection-based matching"](#introspection-based-matching) for an + * example of templated handler usage. + * + * If multiple [SumType]s are passed to match, their values are passed to the + * handlers as separate arguments, and matching is done for each possible + * combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for + * an example. + * + * Returns: + * The value returned from the handler that matches the currently-held type. + * + * See_Also: $(REF visit, std,variant) + */ +template match(handlers...) +{ + import std.typecons : Yes; + + /** + * The actual `match` function. + * + * Params: + * args = One or more [SumType] objects. + */ + auto ref match(SumTypes...)(auto ref SumTypes args) + if (allSatisfy!(isSumType, SumTypes) && args.length > 0) + { + return matchImpl!(Yes.exhaustive, handlers)(args); + } +} + +/** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches)) + * + * Sometimes, implicit conversions may cause a handler to match more types than + * intended. The example below shows two solutions to this problem. + */ +@safe unittest +{ + alias Number = SumType!(double, int); + + Number x; + + // Problem: because int implicitly converts to double, the double + // handler is used for both types, and the int handler never matches. + assert(!__traits(compiles, + x.match!( + (double d) => "got double", + (int n) => "got int" + ) + )); + + // Solution 1: put the handler for the "more specialized" type (in this + // case, int) before the handler for the type it converts to. + assert(__traits(compiles, + x.match!( + (int n) => "got int", + (double d) => "got double" + ) + )); + + // Solution 2: use a template that only accepts the exact type it's + // supposed to match, instead of any type that implicitly converts to it. + alias exactly(T, alias fun) = function (arg) + { + static assert(is(typeof(arg) == T)); + return fun(arg); + }; + + // Now, even if we put the double handler first, it will only be used for + // doubles, not ints. + assert(__traits(compiles, + x.match!( + exactly!(double, d => "got double"), + exactly!(int, n => "got int") + ) + )); +} + +/** $(DIVID multiple-dispatch, $(H3 Multiple dispatch)) + * + * Pattern matching can be performed on multiple `SumType`s at once by passing + * handlers with multiple arguments. This usually leads to more concise code + * than using nested calls to `match`, as show below. + */ +@safe unittest +{ + struct Point2D { double x, y; } + struct Point3D { double x, y, z; } + + alias Point = SumType!(Point2D, Point3D); + + version (none) + { + // This function works, but the code is ugly and repetitive. + // It uses three separate calls to match! + @safe pure nothrow @nogc + bool sameDimensions(Point p1, Point p2) + { + return p1.match!( + (Point2D _) => p2.match!( + (Point2D _) => true, + _ => false + ), + (Point3D _) => p2.match!( + (Point3D _) => true, + _ => false + ) + ); + } + } + + // This version is much nicer. + @safe pure nothrow @nogc + bool sameDimensions(Point p1, Point p2) + { + alias doMatch = match!( + (Point2D _1, Point2D _2) => true, + (Point3D _1, Point3D _2) => true, + (_1, _2) => false + ); + + return doMatch(p1, p2); + } + + Point a = Point2D(1, 2); + Point b = Point2D(3, 4); + Point c = Point3D(5, 6, 7); + Point d = Point3D(8, 9, 0); + + assert( sameDimensions(a, b)); + assert( sameDimensions(c, d)); + assert(!sameDimensions(a, c)); + assert(!sameDimensions(d, b)); +} + +/** + * Attempts to call a type-appropriate function with the value held in a + * [SumType], and throws on failure. + * + * Matches are chosen using the same rules as [match], but are not required to + * be exhaustive—in other words, a type (or combination of types) is allowed to + * have no matching handler. If a type without a handler is encountered at + * runtime, a [MatchException] is thrown. + * + * Not available when compiled with `-betterC`. + * + * Returns: + * The value returned from the handler that matches the currently-held type, + * if a handler was given for that type. + * + * Throws: + * [MatchException], if the currently-held type has no matching handler. + * + * See_Also: `std.variant.tryVisit` + * See_Also: $(REF tryVisit, std,variant) + */ +version (D_Exceptions) +template tryMatch(handlers...) +{ + import std.typecons : No; + + /** + * The actual `tryMatch` function. + * + * Params: + * args = One or more [SumType] objects. + */ + auto ref tryMatch(SumTypes...)(auto ref SumTypes args) + if (allSatisfy!(isSumType, SumTypes) && args.length > 0) + { + return matchImpl!(No.exhaustive, handlers)(args); + } +} + +/** + * Thrown by [tryMatch] when an unhandled type is encountered. + * + * Not available when compiled with `-betterC`. + */ +version (D_Exceptions) +class MatchException : Exception +{ + /// + pure @safe @nogc nothrow + this(string msg, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line); + } +} + +/** + * True if `handler` is a potential match for `Ts`, otherwise false. + * + * See the documentation for [match] for a full explanation of how matches are + * chosen. + */ +template canMatch(alias handler, Ts...) +if (Ts.length > 0) +{ + enum canMatch = is(typeof((Ts args) => handler(args))); +} + +/// +@safe unittest +{ + alias handleInt = (int i) => "got an int"; + + assert( canMatch!(handleInt, int)); + assert(!canMatch!(handleInt, string)); +} + +// Includes all overloads of the given handler +@safe unittest +{ + static struct OverloadSet + { + static void fun(int n) {} + static void fun(double d) {} + } + + assert(canMatch!(OverloadSet.fun, int)); + assert(canMatch!(OverloadSet.fun, double)); +} + +// Like aliasSeqOf!(iota(n)), but works in BetterC +private template Iota(size_t n) +{ + static if (n == 0) + { + alias Iota = AliasSeq!(); + } + else + { + alias Iota = AliasSeq!(Iota!(n - 1), n - 1); + } +} + +@safe unittest +{ + assert(is(Iota!0 == AliasSeq!())); + assert(Iota!1 == AliasSeq!(0)); + assert(Iota!3 == AliasSeq!(0, 1, 2)); +} + +/* The number that the dim-th argument's tag is multiplied by when + * converting TagTuples to and from case indices ("caseIds"). + * + * Named by analogy to the stride that the dim-th index into a + * multidimensional static array is multiplied by to calculate the + * offset of a specific element. + */ +private size_t stride(size_t dim, lengths...)() +{ + import core.checkedint : mulu; + + size_t result = 1; + bool overflow = false; + + static foreach (i; 0 .. dim) + { + result = mulu(result, lengths[i], overflow); + } + + /* The largest number matchImpl uses, numCases, is calculated with + * stride!(SumTypes.length), so as long as this overflow check + * passes, we don't need to check for overflow anywhere else. + */ + assert(!overflow, "Integer overflow"); + return result; +} + +private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) +{ + auto ref matchImpl(SumTypes...)(auto ref SumTypes args) + if (allSatisfy!(isSumType, SumTypes) && args.length > 0) + { + enum typeCount(SumType) = SumType.Types.length; + alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes)); + + /* A TagTuple represents a single possible set of tags that `args` + * could have at runtime. + * + * Because D does not allow a struct to be the controlling expression + * of a switch statement, we cannot dispatch on the TagTuple directly. + * Instead, we must map each TagTuple to a unique integer and generate + * a case label for each of those integers. + * + * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses + * the same technique that's used to map index tuples to memory offsets + * in a multidimensional static array. + * + * For example, when `args` consists of two SumTypes with two member + * types each, the TagTuples corresponding to each case label are: + * + * case 0: TagTuple([0, 0]) + * case 1: TagTuple([1, 0]) + * case 2: TagTuple([0, 1]) + * case 3: TagTuple([1, 1]) + * + * When there is only one argument, the caseId is equal to that + * argument's tag. + */ + static struct TagTuple + { + size_t[SumTypes.length] tags; + alias tags this; + + invariant + { + static foreach (i; 0 .. tags.length) + { + assert(tags[i] < SumTypes[i].Types.length, "Invalid tag"); + } + } + + this(ref const(SumTypes) args) + { + static foreach (i; 0 .. tags.length) + { + tags[i] = args[i].tag; + } + } + + static TagTuple fromCaseId(size_t caseId) + { + TagTuple result; + + // Most-significant to least-significant + static foreach_reverse (i; 0 .. result.length) + { + result[i] = caseId / stride!i; + caseId %= stride!i; + } + + return result; + } + + size_t toCaseId() + { + size_t result; + + static foreach (i; 0 .. tags.length) + { + result += tags[i] * stride!i; + } + + return result; + } + } + + /* + * A list of arguments to be passed to a handler needed for the case + * labeled with `caseId`. + */ + template handlerArgs(size_t caseId) + { + enum tags = TagTuple.fromCaseId(caseId); + enum argsFrom(size_t i : tags.length) = ""; + enum argsFrom(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~ + ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ argsFrom!(i + 1); + enum handlerArgs = argsFrom!0; + } + + /* An AliasSeq of the types of the member values in the argument list + * returned by `handlerArgs!caseId`. + * + * Note that these are the actual (that is, qualified) types of the + * member values, which may not be the same as the types listed in + * the arguments' `.Types` properties. + */ + template valueTypes(size_t caseId) + { + enum tags = TagTuple.fromCaseId(caseId); + + template getType(size_t i) + { + enum tid = tags[i]; + alias T = SumTypes[i].Types[tid]; + alias getType = typeof(args[i].get!T()); + } + + alias valueTypes = Map!(getType, Iota!(tags.length)); + } + + /* The total number of cases is + * + * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length + * + * Or, equivalently, + * + * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length].sizeof + * + * Conveniently, this is equal to stride!(SumTypes.length), so we can + * use that function to compute it. + */ + enum numCases = stride!(SumTypes.length); + + /* Guaranteed to never be a valid handler index, since + * handlers.length <= size_t.max. + */ + enum noMatch = size_t.max; + + // An array that maps caseIds to handler indices ("hids"). + enum matches = () + { + size_t[numCases] matches; + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561 + foreach (ref match; matches) + { + match = noMatch; + } + + static foreach (caseId; 0 .. numCases) + { + static foreach (hid, handler; handlers) + { + static if (canMatch!(handler, valueTypes!caseId)) + { + if (matches[caseId] == noMatch) + { + matches[caseId] = hid; + } + } + } + } + + return matches; + }(); + + import std.algorithm.searching : canFind; + + // Check for unreachable handlers + static foreach (hid, handler; handlers) + { + static assert(matches[].canFind(hid), + "`handlers[" ~ toCtString!hid ~ "]` " ~ + "of type `" ~ ( __traits(isTemplate, handler) + ? "template" + : typeof(handler).stringof + ) ~ "` " ~ + "never matches" + ); + } + + // Workaround for https://issues.dlang.org/show_bug.cgi?id=19993 + enum handlerName(size_t hid) = "handler" ~ toCtString!hid; + + static foreach (size_t hid, handler; handlers) + { + mixin("alias ", handlerName!hid, " = handler;"); + } + + immutable argsId = TagTuple(args).toCaseId; + + final switch (argsId) + { + static foreach (caseId; 0 .. numCases) + { + case caseId: + static if (matches[caseId] != noMatch) + { + return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")"); + } + else + { + static if (exhaustive) + { + static assert(false, + "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); + } + else + { + throw new MatchException( + "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); + } + } + } + } + + assert(false, "unreachable"); + } +} + +// Matching +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); + + assert(x.match!((int v) => true, (float v) => false)); + assert(y.match!((int v) => false, (float v) => true)); +} + +// Missing handlers +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + + assert(!__traits(compiles, x.match!((int x) => true))); + assert(!__traits(compiles, x.match!())); +} + +// Handlers with qualified parameters +// Disabled in BetterC due to use of dynamic arrays +version (D_BetterC) {} else +@safe unittest +{ + alias MySum = SumType!(int[], float[]); + + MySum x = MySum([1, 2, 3]); + MySum y = MySum([1.0, 2.0, 3.0]); + + assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); + assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); +} + +// Handlers for qualified types +// Disabled in BetterC due to use of dynamic arrays +version (D_BetterC) {} else +@safe unittest +{ + alias MySum = SumType!(immutable(int[]), immutable(float[])); + + MySum x = MySum([1, 2, 3]); + + assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); + assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); + // Tail-qualified parameters + assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); + assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); + // Generic parameters + assert(x.match!((immutable v) => true)); + assert(x.match!((const v) => true)); + // Unqualified parameters + assert(!__traits(compiles, + x.match!((int[] v) => true, (float[] v) => false) + )); +} + +// Delegate handlers +// Disabled in BetterC due to use of closures +version (D_BetterC) {} else +@safe unittest +{ + alias MySum = SumType!(int, float); + + int answer = 42; + MySum x = MySum(42); + MySum y = MySum(3.14); + + assert(x.match!((int v) => v == answer, (float v) => v == answer)); + assert(!y.match!((int v) => v == answer, (float v) => v == answer)); +} + +version (unittest) +{ + version (D_BetterC) + { + // std.math.isClose depends on core.runtime.math, so use a + // libc-based version for testing with -betterC + @safe pure @nogc nothrow + private bool isClose(double lhs, double rhs) + { + import core.stdc.math : fabs; + + return fabs(lhs - rhs) < 1e-5; + } + } + else + { + import std.math.operations : isClose; + } +} + +// Generic handler +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); + + assert(x.match!(v => v*2) == 84); + assert(y.match!(v => v*2).isClose(6.28)); +} + +// Fallback to generic handler +// Disabled in BetterC due to use of std.conv.to +version (D_BetterC) {} else +@safe unittest +{ + import std.conv : to; + + alias MySum = SumType!(int, float, string); + + MySum x = MySum(42); + MySum y = MySum("42"); + + assert(x.match!((string v) => v.to!int, v => v*2) == 84); + assert(y.match!((string v) => v.to!int, v => v*2) == 42); +} + +// Multiple non-overlapping generic handlers +@safe unittest +{ + import std.array : staticArray; + + alias MySum = SumType!(int, float, int[], char[]); + + static ints = staticArray([1, 2, 3]); + static chars = staticArray(['a', 'b', 'c']); + + MySum x = MySum(42); + MySum y = MySum(3.14); + MySum z = MySum(ints[]); + MySum w = MySum(chars[]); + + assert(x.match!(v => v*2, v => v.length) == 84); + assert(y.match!(v => v*2, v => v.length).isClose(6.28)); + assert(w.match!(v => v*2, v => v.length) == 3); + assert(z.match!(v => v*2, v => v.length) == 3); +} + +// Structural matching +@safe unittest +{ + static struct S1 { int x; } + static struct S2 { int y; } + alias MySum = SumType!(S1, S2); + + MySum a = MySum(S1(0)); + MySum b = MySum(S2(0)); + + assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); + assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); +} + +// Separate opCall handlers +@safe unittest +{ + static struct IntHandler + { + bool opCall(int arg) + { + return true; + } + } + + static struct FloatHandler + { + bool opCall(float arg) + { + return false; + } + } + + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); + + assert(x.match!(IntHandler.init, FloatHandler.init)); + assert(!y.match!(IntHandler.init, FloatHandler.init)); +} + +// Compound opCall handler +@safe unittest +{ + static struct CompoundHandler + { + bool opCall(int arg) + { + return true; + } + + bool opCall(float arg) + { + return false; + } + } + + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); + + assert(x.match!(CompoundHandler.init)); + assert(!y.match!(CompoundHandler.init)); +} + +// Ordered matching +@safe unittest +{ + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + + assert(x.match!((int v) => true, v => false)); +} + +// Non-exhaustive matching +version (D_Exceptions) +@system unittest +{ + import std.exception : assertThrown, assertNotThrown; + + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); + + assertNotThrown!MatchException(x.tryMatch!((int n) => true)); + assertThrown!MatchException(y.tryMatch!((int n) => true)); +} + +// Non-exhaustive matching in @safe code +version (D_Exceptions) +@safe unittest +{ + SumType!(int, float) x; + + auto _ = x.tryMatch!( + (int n) => n + 1, + ); +} + +// Handlers with ref parameters +@safe unittest +{ + alias Value = SumType!(long, double); + + auto value = Value(3.14); + + value.match!( + (long) {}, + (ref double d) { d *= 2; } + ); + + assert(value.get!double.isClose(6.28)); +} + +// Unreachable handlers +@safe unittest +{ + alias MySum = SumType!(int, string); + + MySum s; + + assert(!__traits(compiles, + s.match!( + (int _) => 0, + (string _) => 1, + (double _) => 2 + ) + )); + + assert(!__traits(compiles, + s.match!( + _ => 0, + (int _) => 1 + ) + )); +} + +// Unsafe handlers +@system unittest +{ + SumType!int x; + alias unsafeHandler = (int x) @system { return; }; + + assert(!__traits(compiles, () @safe + { + x.match!unsafeHandler; + })); + + auto test() @system + { + return x.match!unsafeHandler; + } +} + +// Overloaded handlers +@safe unittest +{ + static struct OverloadSet + { + static string fun(int i) { return "int"; } + static string fun(double d) { return "double"; } + } + + alias MySum = SumType!(int, double); + + MySum a = 42; + MySum b = 3.14; + + assert(a.match!(OverloadSet.fun) == "int"); + assert(b.match!(OverloadSet.fun) == "double"); +} + +// Overload sets that include SumType arguments +@safe unittest +{ + alias Inner = SumType!(int, double); + alias Outer = SumType!(Inner, string); + + static struct OverloadSet + { + @safe: + static string fun(int i) { return "int"; } + static string fun(double d) { return "double"; } + static string fun(string s) { return "string"; } + static string fun(Inner i) { return i.match!fun; } + static string fun(Outer o) { return o.match!fun; } + } + + Outer a = Inner(42); + Outer b = Inner(3.14); + Outer c = "foo"; + + assert(OverloadSet.fun(a) == "int"); + assert(OverloadSet.fun(b) == "double"); + assert(OverloadSet.fun(c) == "string"); +} + +// Overload sets with ref arguments +@safe unittest +{ + static struct OverloadSet + { + static void fun(ref int i) { i = 42; } + static void fun(ref double d) { d = 3.14; } + } + + alias MySum = SumType!(int, double); + + MySum x = 0; + MySum y = 0.0; + + x.match!(OverloadSet.fun); + y.match!(OverloadSet.fun); + + assert(x.match!((value) => is(typeof(value) == int) && value == 42)); + assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); +} + +// Overload sets with templates +@safe unittest +{ + import std.traits : isNumeric; + + static struct OverloadSet + { + static string fun(string arg) + { + return "string"; + } + + static string fun(T)(T arg) + if (isNumeric!T) + { + return "numeric"; + } + } + + alias MySum = SumType!(int, string); + + MySum x = 123; + MySum y = "hello"; + + assert(x.match!(OverloadSet.fun) == "numeric"); + assert(y.match!(OverloadSet.fun) == "string"); +} + +// Github issue #24 +@safe unittest +{ + void test() @nogc + { + int acc = 0; + SumType!int(1).match!((int x) => acc += x); + } +} + +// Github issue #31 +@safe unittest +{ + void test() @nogc + { + int acc = 0; + + SumType!(int, string)(1).match!( + (int x) => acc += x, + (string _) => 0, + ); + } +} + +// Types that `alias this` a SumType +@safe unittest +{ + static struct A {} + static struct B {} + static struct D { SumType!(A, B) value; alias value this; } + + auto _ = D().match!(_ => true); +} + +// Multiple dispatch +@safe unittest +{ + alias MySum = SumType!(int, string); + + static int fun(MySum x, MySum y) + { + import std.meta : Args = AliasSeq; + + return Args!(x, y).match!( + (int xv, int yv) => 0, + (string xv, int yv) => 1, + (int xv, string yv) => 2, + (string xv, string yv) => 3 + ); + } + + assert(fun(MySum(0), MySum(0)) == 0); + assert(fun(MySum(""), MySum(0)) == 1); + assert(fun(MySum(0), MySum("")) == 2); + assert(fun(MySum(""), MySum("")) == 3); +} + +// inout SumTypes +@safe unittest +{ + inout(int[]) fun(inout(SumType!(int[])) x) + { + return x.match!((inout(int[]) a) => a); + } +} + +private void destroyIfOwner(T)(ref T value) +{ + static if (hasElaborateDestructor!T) + { + destroy(value); + } +} diff --git a/libphobos/src/std/system.d b/libphobos/src/std/system.d index 353d692..7a115da 100644 --- a/libphobos/src/std/system.d +++ b/libphobos/src/std/system.d @@ -3,10 +3,11 @@ /** * Information about the target operating system, environment, and CPU. * - * Copyright: Copyright Digital Mars 2000 - 2011 + * Copyright: Copyright The D Language Foundation 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) + * Authors: $(HTTP digitalmars.com, Walter Bright) and + $(HTTP jmdavisprog.com, Jonathan M Davis) + * Source: $(PHOBOSSRC std/system.d) */ module std.system; @@ -18,8 +19,8 @@ immutable 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. + on different OSes, then please use `version (Windows)`, + `version (linux)`, etc. See_Also: $(DDSUBLINK spec/version,PredefinedVersions, Predefined Versions) @@ -38,7 +39,8 @@ immutable dragonFlyBSD, /// DragonFlyBSD solaris, /// Solaris android, /// Android - otherPosix /// Other Posix Systems + otherPosix, /// Other Posix Systems + unknown, /// Unknown } /// The OS that the program was compiled for. @@ -54,7 +56,7 @@ immutable else version (NetBSD) OS os = OS.netBSD; else version (DragonFlyBSD) OS os = OS.dragonFlyBSD; else version (Posix) OS os = OS.otherPosix; - else static assert(0, "Unknown OS."); + else OS os = OS.unknown; /++ Byte order endianness. @@ -63,8 +65,8 @@ immutable 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)). + endian machine, then please use `version (BigEndian)` and + `version (LittleEndian)`. See_Also: $(DDSUBLINK spec/version,PredefinedVersions, Predefined Versions) diff --git a/libphobos/src/std/traits.d b/libphobos/src/std/traits.d index 7badab42..230a7c6 100644 --- a/libphobos/src/std/traits.d +++ b/libphobos/src/std/traits.d @@ -8,12 +8,12 @@ * $(DIVC quickindex, * $(BOOKTABLE , * $(TR $(TH Category) $(TH Templates)) - * $(TR $(TD Symbol Name _traits) $(TD + * $(TR $(TD Symbol Name traits) $(TD * $(LREF fullyQualifiedName) * $(LREF moduleName) * $(LREF packageName) * )) - * $(TR $(TD Function _traits) $(TD + * $(TR $(TD Function traits) $(TD * $(LREF isFunction) * $(LREF arity) * $(LREF functionAttributes) @@ -31,7 +31,7 @@ * $(LREF SetFunctionAttributes) * $(LREF variadicFunctionStyle) * )) - * $(TR $(TD Aggregate Type _traits) $(TD + * $(TR $(TD Aggregate Type traits) $(TD * $(LREF BaseClassesTuple) * $(LREF BaseTypeTuple) * $(LREF classInstanceAlignment) @@ -42,6 +42,7 @@ * $(LREF hasElaborateAssign) * $(LREF hasElaborateCopyConstructor) * $(LREF hasElaborateDestructor) + * $(LREF hasElaborateMove) * $(LREF hasIndirections) * $(LREF hasMember) * $(LREF hasStaticMember) @@ -58,6 +59,7 @@ * )) * $(TR $(TD Type Conversion) $(TD * $(LREF CommonType) + * $(LREF AllImplicitConversionTargets) * $(LREF ImplicitConversionTargets) * $(LREF CopyTypeQualifiers) * $(LREF CopyConstness) @@ -73,6 +75,7 @@ * $(LREF SharedOf) * $(LREF SharedInoutOf) * $(LREF SharedConstOf) + * $(LREF SharedConstInoutOf) * $(LREF ImmutableOf) * $(LREF QualifierOf) * )) @@ -128,6 +131,7 @@ * $(LREF OriginalType) * $(LREF PointerTarget) * $(LREF Signed) + * $(LREF Unconst) * $(LREF Unqual) * $(LREF Unsigned) * $(LREF ValueType) @@ -146,27 +150,26 @@ * ) * ) * - * Copyright: Copyright Digital Mars 2005 - 2009. + * Copyright: Copyright The D Language Foundation 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)), + * Tomasz Stachowiak (`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) + * Source: $(PHOBOSSRC std/traits.d) */ -/* Copyright Digital Mars 2005 - 2009. +/* Copyright The D Language Foundation 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; +import std.meta : AliasSeq, allSatisfy, anySatisfy, ApplyLeft; // Legacy inheritance from std.typetuple // See also: https://github.com/dlang/phobos/pull/5484#discussion_r122602797 @@ -176,88 +179,11 @@ import std.meta : staticMapMeta = staticMap; alias staticMap = staticMapMeta; /////////////////////////////////////////////////////////////////////////////// -// Functions +// Type lists /////////////////////////////////////////////////////////////////////////////// -// 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); @@ -281,28 +207,131 @@ private alias CharTypeList = AliasSeq!(char, wchar, dchar); } -package +/** + * Params: + * T = The type to qualify + * Returns: + * `T` with the `inout` qualifier added. + */ +alias InoutOf(T) = inout(T); + +/// +@safe unittest +{ + static assert(is(InoutOf!(int) == inout int)); + static assert(is(InoutOf!(inout int) == inout int)); + static assert(is(InoutOf!(const int) == inout const int)); + static assert(is(InoutOf!(shared int) == inout shared int)); +} + +/** + * Params: + * T = The type to qualify + * Returns: + * `T` with the `const` qualifier added. + */ +alias ConstOf(T) = const(T); + +/// +@safe unittest +{ + static assert(is(ConstOf!(int) == const int)); + static assert(is(ConstOf!(const int) == const int)); + static assert(is(ConstOf!(inout int) == const inout int)); + static assert(is(ConstOf!(shared int) == const shared int)); +} + +/** + * Params: + * T = The type to qualify + * Returns: + * `T` with the `shared` qualifier added. + */ +alias SharedOf(T) = shared(T); + +/// +@safe unittest +{ + static assert(is(SharedOf!(int) == shared int)); + static assert(is(SharedOf!(shared int) == shared int)); + static assert(is(SharedOf!(inout int) == shared inout int)); + static assert(is(SharedOf!(immutable int) == shared immutable int)); +} + +/** + * Params: + * T = The type to qualify + * Returns: + * `T` with the `inout` and `shared` qualifiers added. + */ +alias SharedInoutOf(T) = shared(inout(T)); + +/// +@safe unittest +{ + static assert(is(SharedInoutOf!(int) == shared inout int)); + static assert(is(SharedInoutOf!(int) == inout shared int)); + + static assert(is(SharedInoutOf!(const int) == shared inout const int)); + static assert(is(SharedInoutOf!(immutable int) == shared inout immutable int)); +} + +/** + * Params: + * T = The type to qualify + * Returns: + * `T` with the `const` and `shared` qualifiers added. + */ +alias SharedConstOf(T) = shared(const(T)); + +/// +@safe unittest +{ + static assert(is(SharedConstOf!(int) == shared const int)); + static assert(is(SharedConstOf!(int) == const shared int)); + + static assert(is(SharedConstOf!(inout int) == shared inout const int)); + // immutable variables are implicitly shared and const + static assert(is(SharedConstOf!(immutable int) == immutable int)); +} + +/** + * Params: + * T = The type to qualify + * Returns: + * `T` with the `const`, `shared`, and `inout` qualifiers added. + */ +alias SharedConstInoutOf(T) = shared(const(inout(T))); + +/// +@safe unittest { - // Add the mutable qualifier to the given type T. - template MutableOf(T) { alias MutableOf = T ; } + static assert(is(SharedConstInoutOf!(int) == shared const inout int)); + static assert(is(SharedConstInoutOf!(int) == const shared inout int)); + static assert(is(SharedConstInoutOf!(inout int) == shared inout const int)); + // immutable variables are implicitly shared and const + static assert(is(SharedConstInoutOf!(immutable int) == immutable int)); } -/// 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) ; } +/** + * Params: + * T = The type to qualify + * Returns: + * `T` with the `immutable` qualifier added. + */ +alias ImmutableOf(T) = immutable(T); + +/// +@safe unittest +{ + static assert(is(ImmutableOf!(int) == immutable int)); + static assert(is(ImmutableOf!(const int) == immutable int)); + static assert(is(ImmutableOf!(inout int) == immutable int)); + static assert(is(ImmutableOf!(shared int) == immutable int)); +} @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)); @@ -311,16 +340,45 @@ template ImmutableOf(T) { alias ImmutableOf = immutable(T) ; } static assert(is( ImmutableOf!int == immutable int)); } -/// Get qualifier template from the given type T +/** + * Gives a template that can be used to apply the same + * attributes that are on the given type `T`. E.g. passing + * `inout shared int` will return `SharedInoutOf`. + * + * Params: + * T = the type to check qualifiers from + * Returns: + * The 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; + static if (is(immutable T == T)) + { + alias QualifierOf = ImmutableOf; + } + else + { + private enum quals = is(const T == T) | (is(inout T == T) << 1) | (is(shared T == T) << 2); + static if (quals == 0) { import std.meta : Alias; alias QualifierOf = Alias; } + else static if (quals == 1) alias QualifierOf = ConstOf; + else static if (quals == 2) alias QualifierOf = InoutOf; + else static if (quals == 3) alias QualifierOf = ConstInoutOf; + else static if (quals == 4) alias QualifierOf = SharedOf; + else static if (quals == 5) alias QualifierOf = SharedConstOf; + else static if (quals == 6) alias QualifierOf = SharedInoutOf; + else alias QualifierOf = SharedConstInoutOf; + } +} + +/// +@safe unittest +{ + static assert(__traits(isSame, QualifierOf!(shared const inout int), SharedConstInoutOf)); + static assert(__traits(isSame, QualifierOf!(immutable int), ImmutableOf)); + static assert(__traits(isSame, QualifierOf!(shared int), SharedOf)); + static assert(__traits(isSame, QualifierOf!(shared inout int), SharedInoutOf)); + import std.meta : Alias; + static assert(__traits(isSame, QualifierOf!(int), Alias)); } @safe unittest @@ -334,9 +392,10 @@ template QualifierOf(T) alias Qual7 = QualifierOf!( immutable int); static assert(is(Qual7!long == immutable long)); } -version (unittest) +version (StdUnittest) { - alias TypeQualifierList = AliasSeq!(MutableOf, ConstOf, SharedOf, SharedConstOf, ImmutableOf); + import std.meta : Alias; + alias TypeQualifierList = AliasSeq!(Alias, ConstOf, SharedOf, SharedConstOf, ImmutableOf); struct SubTypeOf(T) { @@ -355,12 +414,14 @@ template packageName(alias T) { import std.algorithm.searching : startsWith; + enum bool isNotFunc = !isSomeFunction!(T); + static if (__traits(compiles, parentOf!T)) enum parent = packageName!(parentOf!T); else enum string parent = null; - static if (T.stringof.startsWith("package ")) + static if (isNotFunc && T.stringof.startsWith("package ")) enum packageName = (parent.length ? parent ~ '.' : "") ~ T.stringof[8 .. $]; else static if (parent) enum packageName = parent; @@ -371,7 +432,6 @@ template packageName(alias T) /// @safe unittest { - import std.traits; static assert(packageName!packageName == "std"); } @@ -379,8 +439,7 @@ template packageName(alias T) { import std.array; - // Commented out because of dmd @@@BUG8922@@@ - // static assert(packageName!std == "std"); // this package (currently: "std.std") + static assert(packageName!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 @@ -394,7 +453,7 @@ template packageName(alias T) static assert(packageName!(X12287!int.i) == "std"); } -version (none) version (unittest) //Please uncomment me when changing packageName to test global imports +version (none) @safe unittest //Please uncomment me when changing packageName to test global imports { import core.sync.barrier; // global import static assert(packageName!core == "core"); @@ -402,6 +461,25 @@ version (none) version (unittest) //Please uncomment me when changing packageNam static assert(packageName!Barrier == "core.sync"); } +/// +@safe unittest +{ + static assert(packageName!moduleName == "std"); +} + +// https://issues.dlang.org/show_bug.cgi?id=13741 +@safe unittest +{ + import std.ascii : isWhite; + static assert(packageName!(isWhite) == "std"); + + struct Foo{void opCall(int){}} + static assert(packageName!(Foo.opCall) == "std"); + + @property void function(int) vf; + static assert(packageName!(vf) == "std"); +} + /** * Get the module name (including package) for the given symbol. */ @@ -409,9 +487,13 @@ template moduleName(alias T) { import std.algorithm.searching : startsWith; - static assert(!T.stringof.startsWith("package "), "cannot get the module name for a package"); + enum bool isNotFunc = !isSomeFunction!(T); + + static if (isNotFunc) + static assert(!T.stringof.startsWith("package "), + "cannot get the module name for a package"); - static if (T.stringof.startsWith("module ")) + static if (isNotFunc && T.stringof.startsWith("module ")) { static if (__traits(compiles, packageName!T)) enum packagePrefix = packageName!T ~ '.'; @@ -427,7 +509,6 @@ template moduleName(alias T) /// @safe unittest { - import std.traits; static assert(moduleName!moduleName == "std.traits"); } @@ -450,7 +531,20 @@ template moduleName(alias T) static assert(moduleName!(X12287!int.i) == "std.traits"); } -version (none) version (unittest) //Please uncomment me when changing moduleName to test global imports +// https://issues.dlang.org/show_bug.cgi?id=13741 +@safe unittest +{ + import std.ascii : isWhite; + static assert(moduleName!(isWhite) == "std.ascii"); + + struct Foo{void opCall(int){}} + static assert(moduleName!(Foo.opCall) == "std.traits"); + + @property void function(int) vf; + static assert(moduleName!(vf) == "std.traits"); +} + +version (none) @safe unittest //Please uncomment me when changing moduleName to test global imports { import core.sync.barrier; // global import static assert(!__traits(compiles, moduleName!(core.sync))); @@ -469,7 +563,7 @@ static assert(fullyQualifiedName!(const MyStruct[]) == "const(myModule.MyStruct[ ----------------- */ template fullyQualifiedName(T...) - if (T.length == 1) +if (T.length == 1) { static if (is(T)) @@ -484,13 +578,14 @@ template fullyQualifiedName(T...) static assert(fullyQualifiedName!fullyQualifiedName == "std.traits.fullyQualifiedName"); } -version (unittest) +version (StdUnittest) { // Used for both fqnType and fqnSym unittests private struct QualifiedNameTests { struct Inner { + bool value; } ref const(Inner[string]) func( ref Inner var1, lazy scope string var2 ); @@ -607,8 +702,6 @@ private template fqnSym(alias T) private template fqnType(T, bool alreadyConst, bool alreadyImmutable, bool alreadyShared, bool alreadyInout) { - import std.format : format; - // Convenience tags enum { _const = 0, @@ -622,14 +715,17 @@ private template fqnType(T, string storageClassesString(uint psc)() @property { + import std.conv : text; + alias PSC = ParameterStorageClass; - return format("%s%s%s%s%s", + return text( psc & PSC.scope_ ? "scope " : "", psc & PSC.return_ ? "return " : "", + psc & PSC.in_ ? "in " : "", psc & PSC.out_ ? "out " : "", psc & PSC.ref_ ? "ref " : "", - psc & PSC.lazy_ ? "lazy " : "" + psc & PSC.lazy_ ? "lazy " : "", ); } @@ -658,7 +754,7 @@ private template fqnType(T, import std.range : zip; string result = join( - map!(a => format("%s%s", a[0], a[1]))( + map!(a => (a[0] ~ a[1]))( zip([staticMap!(storageClassesString, parameterStC)], [staticMap!(fullyQualifiedName, parameters)]) ), @@ -676,7 +772,7 @@ private template fqnType(T, enum linkage = functionLinkage!T; if (linkage != "D") - return format("extern(%s) ", linkage); + return "extern(" ~ linkage ~ ") "; else return ""; } @@ -689,16 +785,15 @@ private template fqnType(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" : "" - ); + return + (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, @@ -707,15 +802,12 @@ private template fqnType(T, auto result = typeString; if (addShared) { - result = format("shared(%s)", result); + result = "shared(" ~ result ~")"; } if (addConst || addImmutable || addInout) { - result = format("%s(%s)", - addConst ? "const" : - addImmutable ? "immutable" : "inout", - result - ); + result = (addConst ? "const" : addImmutable ? "immutable" : "inout") + ~ "(" ~ result ~ ")"; } return result; } @@ -752,61 +844,62 @@ private template fqnType(T, } else static if (isStaticArray!T) { + import std.conv : to; enum fqnType = chain!( - format("%s[%s]", fqnType!(typeof(T.init[0]), qualifiers), T.length) + fqnType!(typeof(T.init[0]), qualifiers) ~ "[" ~ to!string(T.length) ~ "]" ); } else static if (isArray!T) { enum fqnType = chain!( - format("%s[]", fqnType!(typeof(T.init[0]), qualifiers)) + 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)) + 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 qualifierString = + (is(F == shared) ? " shared" : "") + ~ (is(F == inout) ? " inout" : + is(F == immutable) ? " immutable" : + is(F == const) ? " const" : ""); enum fqnType = chain!( - format(formatStr, linkageString!T, fqnType!(ReturnType!T, noQualifiers), - parametersTypeString!(T), functionAttributeString!T, qualifierString) + linkageString!T + ~ fqnType!(ReturnType!T, noQualifiers) + ~ " delegate(" ~ 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) + linkageString!T + ~ fqnType!(ReturnType!T, noQualifiers) + ~ (isFunctionPointer!T ? " function(" : "(") + ~ parametersTypeString!(T) ~ ")" + ~ functionAttributeString!T ); } } else static if (isPointer!T) { enum fqnType = chain!( - format("%s*", fqnType!(PointerTarget!T, qualifiers)) + fqnType!(PointerTarget!T, qualifiers) ~ "*" ); } else static if (is(T : __vector(V[N]), V, size_t N)) { + import std.conv : to; enum fqnType = chain!( - format("__vector(%s[%s])", fqnType!(V, qualifiers), N) + "__vector(" ~ fqnType!(V, qualifiers) ~ "[" ~ N.to!string ~ "])" ); } else @@ -889,12 +982,12 @@ private template fqnType(T, * 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) + * or a class with an `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) +if (func.length == 1 && isCallable!func) { static if (is(FunctionTypeOf!func R == return)) alias ReturnType = R; @@ -949,11 +1042,11 @@ template ReturnType(func...) /*** 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). +to function, a delegate, a struct with an `opCall`, a pointer to a +struct with an `opCall`, or a class with an `opCall`. */ template Parameters(func...) - if (func.length == 1 && isCallable!func) +if (func.length == 1 && isCallable!func) { static if (is(FunctionTypeOf!func P == function)) alias Parameters = P; @@ -999,11 +1092,12 @@ alias ParameterTypeTuple = Parameters; } /** -Returns the number of arguments of function $(D func). +Returns the number of arguments of function `func`. arity is undefined for variadic functions. */ -template arity(alias func) - if ( isCallable!func && variadicFunctionStyle!func == Variadic.no ) +template arity(func...) +if (func.length == 1 && isCallable!func && + variadicFunctionStyle!func == Variadic.no) { enum size_t arity = Parameters!func.length; } @@ -1019,6 +1113,13 @@ template arity(alias func) static assert(!__traits(compiles, arity!variadicFoo)); } +// https://issues.dlang.org/show_bug.cgi?id=11389 +@safe unittest +{ + alias TheType = size_t function( string[] ); + static assert(arity!TheType == 1); +} + /** Get tuple, one per function parameter, of the storage classes of the parameters. Params: @@ -1032,17 +1133,18 @@ 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 + none = 0x00, + in_ = 0x01, /// ditto + ref_ = 0x02, /// ditto + out_ = 0x04, /// ditto + lazy_ = 0x08, /// ditto + scope_ = 0x10, /// ditto + return_ = 0x20, /// ditto } /// ditto template ParameterStorageClassTuple(func...) - if (func.length == 1 && isCallable!func) +if (func.length == 1 && isCallable!func) { alias Func = FunctionTypeOf!func; @@ -1073,22 +1175,32 @@ template ParameterStorageClassTuple(func...) { alias STC = ParameterStorageClass; // shorten the enum name - void func(ref int ctx, out real result, real param) + void func(ref int ctx, out real result, in real param, void* ptr) { } alias pstc = ParameterStorageClassTuple!func; - static assert(pstc.length == 3); // three parameters + static assert(pstc.length == 4); // number of parameters static assert(pstc[0] == STC.ref_); static assert(pstc[1] == STC.out_); - static assert(pstc[2] == STC.none); + version (none) + { + // TODO: When the DMD PR (dlang/dmd#11474) gets merged, + // remove the versioning and the second test + static assert(pstc[2] == STC.in_); + // This is the current behavior, before `in` is fixed to not be an alias + static assert(pstc[2] == STC.scope_); + } + static assert(pstc[3] == STC.none); } -/***************** - * Convert string tuple Attribs to ParameterStorageClass bits - * Params: - * Attribs = string tuple - * Returns: - * ParameterStorageClass bits +/** +Convert the result of `__traits(getParameterStorageClasses)` +to $(LREF ParameterStorageClass) `enum`s. + +Params: + Attribs = The return value of `__traits(getParameterStorageClasses)` +Returns: + The bitwise OR of the equivalent $(LREF ParameterStorageClass) `enum`s. */ template extractParameterStorageClassFlags(Attribs...) { @@ -1097,11 +1209,12 @@ template extractParameterStorageClassFlags(Attribs...) auto result = ParameterStorageClass.none; static if (Attribs.length > 0) { - foreach (attrib; [Attribs]) + static foreach (attrib; Attribs) { final switch (attrib) with (ParameterStorageClass) { case "scope": result |= scope_; break; + case "in": result |= in_; break; case "out": result |= out_; break; case "ref": result |= ref_; break; case "lazy": result |= lazy_; break; @@ -1118,6 +1231,28 @@ template extractParameterStorageClassFlags(Attribs...) }(); } +/// +@safe unittest +{ + static void func(ref int ctx, out real result); + + enum param1 = extractParameterStorageClassFlags!( + __traits(getParameterStorageClasses, func, 0) + ); + static assert(param1 == ParameterStorageClass.ref_); + + enum param2 = extractParameterStorageClassFlags!( + __traits(getParameterStorageClasses, func, 1) + ); + static assert(param2 == ParameterStorageClass.out_); + + enum param3 = extractParameterStorageClassFlags!( + __traits(getParameterStorageClasses, func, 0), + __traits(getParameterStorageClasses, func, 1) + ); + static assert(param3 == (ParameterStorageClass.ref_ | ParameterStorageClass.out_)); +} + @safe unittest { alias STC = ParameterStorageClass; @@ -1154,14 +1289,14 @@ template extractParameterStorageClassFlags(Attribs...) static assert(dglit_pstc.length == 1); static assert(dglit_pstc[0] == STC.ref_); - // Bugzilla 9317 + // https://issues.dlang.org/show_bug.cgi?id=9317 static inout(int) func(inout int param) { return param; } static assert(ParameterStorageClassTuple!(typeof(func))[0] == STC.none); } @safe unittest { - // Bugzilla 14253 + // https://issues.dlang.org/show_bug.cgi?id=14253 static struct Foo { ref Foo opAssign(ref Foo rhs) return { return this; } } @@ -1174,7 +1309,7 @@ template extractParameterStorageClassFlags(Attribs...) Get, as a tuple, the identifiers of the parameters to a function symbol. */ template ParameterIdentifierTuple(func...) - if (func.length == 1 && isCallable!func) +if (func.length == 1 && isCallable!func) { static if (is(FunctionTypeOf!func PT == __parameters)) { @@ -1182,7 +1317,9 @@ template ParameterIdentifierTuple(func...) { static if (!isFunctionPointer!func && !isDelegate!func // Unnamed parameters yield CT error. - && is(typeof(__traits(identifier, PT[i .. i+1])))) + && is(typeof(__traits(identifier, PT[i .. i+1]))) + // Filter out unnamed args, which look like (Type) instead of (Type name). + && PT[i].stringof != PT[i .. i+1].stringof[1..$-1]) { enum Get = __traits(identifier, PT[i .. i+1]); } @@ -1194,7 +1331,7 @@ template ParameterIdentifierTuple(func...) } else { - static assert(0, func[0].stringof ~ "is not a function"); + static assert(0, func[0].stringof ~ " is not a function"); // Define dummy entities to avoid pointless errors template Get(size_t i) { enum Get = ""; } @@ -1219,6 +1356,16 @@ template ParameterIdentifierTuple(func...) static assert([ParameterIdentifierTuple!foo] == ["num", "name", ""]); } +// https://issues.dlang.org/show_bug.cgi?id=19456 +@safe unittest +{ + struct SomeType {} + void foo(SomeType); + void bar(int); + static assert([ParameterIdentifierTuple!foo] == [""]); + static assert([ParameterIdentifierTuple!bar] == [""]); +} + @safe unittest { alias PIT = ParameterIdentifierTuple; @@ -1258,10 +1405,10 @@ template ParameterIdentifierTuple(func...) /** 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. +If a parameter doesn't have the default value, `void` is returned instead. */ template ParameterDefaults(func...) - if (func.length == 1 && isCallable!func) +if (func.length == 1 && isCallable!func) { alias param_names = ParameterIdentifierTuple!func; static if (is(FunctionTypeOf!(func[0]) PT == __parameters)) @@ -1285,7 +1432,6 @@ template ParameterDefaults(func...) // like this. auto " ~ val ~ " = " ~ args ~ "[0]; auto " ~ ptr ~ " = &" ~ val ~ "; - // workaround Bugzilla 16582 return *" ~ ptr ~ "; }; "); @@ -1298,7 +1444,7 @@ template ParameterDefaults(func...) } else { - static assert(0, func[0].stringof ~ "is not a function"); + static assert(0, func[0].stringof ~ " is not a function"); // Define dummy entities to avoid pointless errors template Get(size_t i) { enum Get = ""; } @@ -1326,7 +1472,8 @@ template ParameterDefaults(func...) static assert( ParameterDefaults!foo[3] == 0); } -@safe unittest // issue 17192 +// https://issues.dlang.org/show_bug.cgi?id=17192 +@safe unittest { 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_) @@ -1334,7 +1481,7 @@ template ParameterDefaults(func...) } alias Voids = ParameterDefaults!func; static assert(Voids.length == 12); - foreach (V; Voids) static assert(is(V == void)); + static foreach (V; Voids) static assert(is(V == void)); } /** @@ -1359,7 +1506,8 @@ alias ParameterDefaultValueTuple = ParameterDefaults; 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 functions return empty string + // https://issues.dlang.org/show_bug.cgi?id=10800 @property void foo(int x = 3) { } static assert(PDVT!foo.length == 1); static assert(PDVT!foo[0] == 3); @@ -1371,16 +1519,18 @@ alias ParameterDefaultValueTuple = ParameterDefaults; static immutable Colour white = Colour(255,255,255,255); } + // https://issues.dlang.org/show_bug.cgi?id=8106 void bug8106(Colour c = Colour.white) {} //pragma(msg, PDVT!bug8106); static assert(PDVT!bug8106[0] == Colour.white); + // https://issues.dlang.org/show_bug.cgi?id=16582 void bug16582(scope int* val = null) {} static assert(PDVT!bug16582[0] is null); } /** -Returns the FunctionAttribute mask for function $(D func). +Returns the FunctionAttribute mask for function `func`. See_Also: $(LREF hasFunctionAttributes) @@ -1405,11 +1555,12 @@ enum FunctionAttribute : uint shared_ = 1 << 11, /// ditto return_ = 1 << 12, /// ditto scope_ = 1 << 13, /// ditto + live = 1 << 14, /// ditto } /// ditto template functionAttributes(func...) - if (func.length == 1 && isCallable!func) +if (func.length == 1 && isCallable!func) { // @bug: workaround for opCall alias FuncSym = Select!(is(typeof(__traits(getFunctionAttributes, func))), @@ -1422,8 +1573,6 @@ template functionAttributes(func...) /// @safe unittest { - import std.traits : functionAttributes, FunctionAttribute; - alias FA = FunctionAttribute; // shorten the enum name real func(real x) pure nothrow @safe @@ -1458,6 +1607,8 @@ template functionAttributes(func...) int safeF() @safe { return 0; } int pureF() pure { return 0; } + + int liveF() @live { return 0; } } static assert(functionAttributes!(S.noF) == FA.system); @@ -1499,6 +1650,9 @@ template functionAttributes(func...) static assert(functionAttributes!(S.pureF) == (FA.pure_ | FA.system)); static assert(functionAttributes!(typeof(S.pureF)) == (FA.pure_ | FA.system)); + static assert(functionAttributes!(S.liveF) == (FA.live | FA.system)); + static assert(functionAttributes!(typeof(S.liveF)) == (FA.live | FA.system)); + int pure_nothrow() nothrow pure; void safe_nothrow() @safe nothrow; static ref int static_ref_property() @property; @@ -1548,7 +1702,7 @@ private FunctionAttribute extractAttribFlags(Attribs...)() { auto res = FunctionAttribute.none; - foreach (attrib; Attribs) + static foreach (attrib; Attribs) { switch (attrib) with (FunctionAttribute) { @@ -1566,6 +1720,7 @@ private FunctionAttribute extractAttribFlags(Attribs...)() case "shared": res |= shared_; break; case "return": res |= return_; break; case "scope": res |= scope_; break; + case "@live": res |= live; break; default: assert(0, attrib); } } @@ -1587,14 +1742,14 @@ See_Also: $(LREF functionAttributes) */ template hasFunctionAttributes(args...) - if (args.length > 0 && isCallable!(args[0]) - && allSatisfy!(isSomeString, typeof(args[1 .. $]))) +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 .. $]) + static foreach (attribute; args[1 .. $]) { if (!funcAttribs.canFind(attribute)) return false; @@ -1639,6 +1794,8 @@ template hasFunctionAttributes(args...) int safeF() @safe; int pureF() pure; + + int liveF() @live; } // true if no args passed @@ -1696,6 +1853,10 @@ template hasFunctionAttributes(args...) static assert(hasFunctionAttributes!(typeof(S.pureF), "pure", "@system")); static assert(!hasFunctionAttributes!(S.pureF, "pure", "@system", "ref")); + static assert(hasFunctionAttributes!(S.liveF, "@live", "@system")); + static assert(hasFunctionAttributes!(typeof(S.liveF), "@live", "@system")); + static assert(!hasFunctionAttributes!(S.liveF, "@live", "@system", "ref")); + int pure_nothrow() nothrow pure { return 0; } void safe_nothrow() @safe nothrow { } static ref int static_ref_property() @property { return *(new int); } @@ -1768,10 +1929,10 @@ template hasFunctionAttributes(args...) } /** -$(D true) if $(D func) is $(D @safe) or $(D @trusted). +`true` if `func` is `@safe` or `@trusted`. */ template isSafe(alias func) - if (isCallable!func) +if (isCallable!func) { enum isSafe = (functionAttributes!func & FunctionAttribute.safe) != 0 || (functionAttributes!func & FunctionAttribute.trusted) != 0; @@ -1804,9 +1965,9 @@ template isSafe(alias func) static assert(!isSafe!(Set.systemF)); //Functions - @safe static safeFunc() {} - @trusted static trustedFunc() {} - @system static systemFunc() {} + @safe static void safeFunc() {} + @trusted static void trustedFunc() {} + @system static void systemFunc() {} static assert( isSafe!safeFunc); static assert( isSafe!trustedFunc); @@ -1847,7 +2008,7 @@ template isSafe(alias func) /** -$(D true) if $(D func) is $(D @system). +`true` if `func` is `@system`. */ template isUnsafe(alias func) { @@ -1880,9 +2041,9 @@ template isUnsafe(alias func) static assert( isUnsafe!(Set.systemF)); //Functions - @safe static safeFunc() {} - @trusted static trustedFunc() {} - @system static systemFunc() {} + @safe static void safeFunc() {} + @trusted static void trustedFunc() {} + @system static void systemFunc() {} static assert(!isUnsafe!safeFunc); static assert(!isUnsafe!trustedFunc); @@ -1927,10 +2088,10 @@ 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", or "Objective-C" + one of the strings "D", "C", "C++", "Windows", "Objective-C", or "System". */ template functionLinkage(func...) - if (func.length == 1 && isCallable!func) +if (func.length == 1 && isCallable!func) { enum string functionLinkage = __traits(getLinkage, FunctionTypeOf!func); } @@ -1974,17 +2135,21 @@ Returns: */ 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. + /// Function is not variadic. + no, + /// Function is a _C-style variadic function, which uses + /// `core.stdc.stdarg` + c, + /// Function is a _D-style variadic function, which uses + /// `__argptr` and `__arguments`. + d, + /// Function is a typesafe variadic function. + typesafe, } /// ditto template variadicFunctionStyle(func...) - if (func.length == 1 && isCallable!func) +if (func.length == 1 && isCallable!func) { enum string varargs = __traits(getFunctionVariadicStyle, FunctionTypeOf!func); enum Variadic variadicFunctionStyle = @@ -2023,28 +2188,31 @@ template variadicFunctionStyle(func...) /** -Get the function type from a callable object $(D func). +Get the function type from a callable object `func`. -Using builtin $(D typeof) on a property function yields the types of the +Using builtin `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. +`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) +if (func.length == 1 && isCallable!func) { - static if (is(typeof(& func[0]) Fsym : Fsym*) && is(Fsym == function) || is(typeof(& func[0]) Fsym == delegate)) + 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)) + else static if (is(typeof(& func[0].opCall) Fobj == delegate) || 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)) + else static if ( + (is(typeof(& func[0].opCall) Ftyp : Ftyp*) && is(Ftyp == function)) || + (is(typeof(& func[0].opCall!()) Ftyp : Ftyp*) && is(Ftyp == function)) + ) { alias FunctionTypeOf = Ftyp; // HIT: callable type } @@ -2105,6 +2273,13 @@ template FunctionTypeOf(func...) static assert(is( FunctionTypeOf!stcall_val == typeof(test) )); static assert(is( FunctionTypeOf!stcall_ptr == typeof(test) )); + struct TemplatedOpCallF { int opCall()(int) { return 0; } } + static assert(is( FunctionTypeOf!TemplatedOpCallF == typeof(TemplatedOpCallF.opCall!()) )); + + int foovar; + struct TemplatedOpCallDg { int opCall()() { return foovar; } } + static assert(is( FunctionTypeOf!TemplatedOpCallDg == typeof(TemplatedOpCallDg.opCall!()) )); + interface Overloads { void test(string); @@ -2112,7 +2287,7 @@ template FunctionTypeOf(func...) int test(int); int test() @property; } - alias ov = AliasSeq!(__traits(getVirtualFunctions, Overloads, "test")); + alias ov = __traits(getVirtualFunctions, Overloads, "test"); alias F_ov0 = FunctionTypeOf!(ov[0]); alias F_ov1 = FunctionTypeOf!(ov[1]); alias F_ov2 = FunctionTypeOf!(ov[2]); @@ -2139,7 +2314,7 @@ template FunctionTypeOf(func...) * attrs = The desired $(LREF FunctionAttribute)s of the result type. */ template SetFunctionAttributes(T, string linkage, uint attrs) - if (isFunctionPointer!T || isDelegate!T) +if (isFunctionPointer!T || isDelegate!T) { mixin({ import std.algorithm.searching : canFind; @@ -2206,6 +2381,8 @@ template SetFunctionAttributes(T, string linkage, uint attrs) result ~= " shared"; static if (attrs & FunctionAttribute.return_) result ~= " return"; + static if (attrs & FunctionAttribute.live) + result ~= " @live"; result ~= " SetFunctionAttributes;"; return result; @@ -2214,7 +2391,7 @@ template SetFunctionAttributes(T, string linkage, uint attrs) /// Ditto template SetFunctionAttributes(T, string linkage, uint attrs) - if (is(T == function)) +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 @@ -2228,15 +2405,29 @@ template SetFunctionAttributes(T, string linkage, uint attrs) alias ExternC(T) = SetFunctionAttributes!(T, "C", functionAttributes!T); auto assumePure(T)(T t) - if (isFunctionPointer!T || isDelegate!T) + if (isFunctionPointer!T || isDelegate!T) { enum attrs = functionAttributes!T | FunctionAttribute.pure_; return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; } + + int f() + { + import core.thread : getpid; + return getpid(); + } + + int g() pure @trusted + { + auto pureF = assumePure(&f); + return pureF(); + } + assert(g() > 0); } -version (unittest) +version (StdUnittest) { +private: // Some function types to test. int sc(scope int, ref int, out int, lazy int, int); extern(System) int novar(); @@ -2249,11 +2440,11 @@ version (unittest) import std.algorithm.iteration : reduce; alias FA = FunctionAttribute; - foreach (BaseT; AliasSeq!(typeof(&sc), typeof(&novar), typeof(&cstyle), + static 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 + static foreach (T; AliasSeq!(BaseT, FunctionTypeOf!BaseT)) + {{ enum linkage = functionLinkage!T; enum attrs = functionAttributes!T; @@ -2263,13 +2454,13 @@ version (unittest) // Check that all linkage types work (D-style variadics require D linkage). static if (variadicFunctionStyle!T != Variadic.d) { - foreach (newLinkage; AliasSeq!("D", "C", "Windows", "C++")) - { + static foreach (newLinkage; AliasSeq!("D", "C", "Windows", "C++")) + {{ alias New = SetFunctionAttributes!(T, newLinkage, attrs); static assert(functionLinkage!New == newLinkage, "Linkage test failed for: " ~ T.stringof ~ ", " ~ newLinkage ~ " (got " ~ New.stringof ~ ")"); - } + }} } // Add @safe. @@ -2287,7 +2478,7 @@ version (unittest) // Strip all attributes again. alias T3 = SetFunctionAttributes!(T2, functionLinkage!T, FA.none); static assert(is(T3 == T)); - }(); + }} } } @@ -2309,13 +2500,22 @@ Returns: `false` otherwise */ template isInnerClass(T) - if (is(T == class)) +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); + { + bool hasOuterMember(string[] members...) + { + foreach (m; members) + { + if (m == "outer") + return true; + } + return false; + } + enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) && + !hasOuterMember(__traits(allMembers, T)); + } else enum isInnerClass = false; } @@ -2351,11 +2551,11 @@ template isInnerClass(T) } /** -Determines whether $(D T) has its own context pointer. -$(D T) must be either $(D class), $(D struct), or $(D union). +Determines whether `T` has its own context pointer. +`T` must be either `class`, `struct`, or `union`. */ template isNested(T) - if (is(T == class) || is(T == struct) || is(T == union)) +if (is(T == class) || is(T == struct) || is(T == union)) { enum isNested = __traits(isNested, T); } @@ -2372,19 +2572,19 @@ template isNested(T) } /** -Determines whether $(D T) or any of its representation types +Determines whether `T` or any of its representation types have a context pointer. */ template hasNested(T) { - import std.meta : anySatisfy, Filter; + import std.meta : 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 notSame(U) = !is(immutable T == immutable U); enum hasNested = isNested!T || anySatisfy!(.hasNested, Filter!(notSame, Fields!T)); } @@ -2467,14 +2667,17 @@ template hasNested(T) * 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). + * If `T` isn't a struct, class, interface or union returns a tuple + * with one element `T`. + * + * History: + * - Returned `AliasSeq!(Interface)` for interfaces prior to 2.097 */ 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)) + else static if (is(T == class) || is(T == interface)) alias Fields = typeof(T.tupleof); else alias Fields = AliasSeq!T; @@ -2483,6 +2686,7 @@ template Fields(T) /// @safe unittest { + import std.meta : AliasSeq; struct S { int x; float y; } static assert(is(Fields!S == AliasSeq!(int, float))); } @@ -2512,8 +2716,10 @@ alias FieldTypeTuple = Fields; class NestedClass { int a; void f() { ++i; } } static assert(is(FieldTypeTuple!NestedClass == AliasSeq!int)); -} + static interface I {} + static assert(is(Fields!I == AliasSeq!())); +} //Required for FieldNameTuple private enum NameOf(alias T) = T.stringof; @@ -2522,15 +2728,20 @@ 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. + * for nested types. + * Inherited fields (for classes) are not included. + * If `T` isn't a struct, class, interface or union, an + * expression tuple with an empty string is returned. + * + * History: + * - Returned `AliasSeq!""` for interfaces prior to 2.097 */ 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)) + else static if (is(T == class) || is(T == interface)) alias FieldNameTuple = staticMap!(NameOf, T.tupleof); else alias FieldNameTuple = AliasSeq!""; @@ -2539,6 +2750,7 @@ template FieldNameTuple(T) /// @safe unittest { + import std.meta : AliasSeq; struct S { int x; float y; } static assert(FieldNameTuple!S == AliasSeq!("x", "y")); static assert(FieldNameTuple!int == AliasSeq!""); @@ -2554,6 +2766,15 @@ template FieldNameTuple(T) static struct StaticStruct2 { int a, b; } static assert(FieldNameTuple!StaticStruct2 == AliasSeq!("a", "b")); + static class StaticClass1 { } + static assert(is(FieldNameTuple!StaticClass1 == AliasSeq!())); + + static class StaticClass2 : StaticClass1 { int a, b; } + static assert(FieldNameTuple!StaticClass2 == AliasSeq!("a", "b")); + + static class StaticClass3 : StaticClass2 { int c; } + static assert(FieldNameTuple!StaticClass3 == AliasSeq!("c")); + int i; struct NestedStruct1 { void f() { ++i; } } @@ -2564,6 +2785,9 @@ template FieldNameTuple(T) class NestedClass { int a; void f() { ++i; } } static assert(FieldNameTuple!NestedClass == AliasSeq!"a"); + + interface I {} + static assert(FieldNameTuple!I == AliasSeq!()); } @@ -2573,41 +2797,13 @@ 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); + alias RepresentationTypeTuple = staticMapMeta!(RepresentationTypeTupleImpl, FieldTypeTuple!T); } else { - alias RepresentationTypeTuple = Impl!T; + alias RepresentationTypeTuple = RepresentationTypeTupleImpl!T; } } @@ -2645,7 +2841,7 @@ template RepresentationTypeTuple(T) alias R1 = RepresentationTypeTuple!C; static assert(R1.length == 2 && is(R1[0] == int) && is(R1[1] == float)); - /* Issue 6642 */ + /* https://issues.dlang.org/show_bug.cgi?id=6642 */ import std.typecons : Rebindable; struct S5 { int a; Rebindable!(immutable Object) b; } @@ -2653,39 +2849,58 @@ template RepresentationTypeTuple(T) 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...) +@safe unittest { - template Impl(T...) + struct VeryLargeType { - static if (T.length == 0) + import std.format : format; + import std.range : iota; + + static foreach (i; 500.iota) { - enum Impl = false; + mixin(format!"int v%s;"(i)); } - 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 .. $]); - } + alias BigList = RepresentationTypeTuple!VeryLargeType; +} + +private template RepresentationTypeTupleImpl(T) +{ + import std.typecons : Rebindable; + + static if (is(T R: Rebindable!R)) + { + alias RepresentationTypeTupleImpl + = staticMapMeta!(.RepresentationTypeTupleImpl, RepresentationTypeTupleImpl!R); + } + else static if (is(T == struct) || is(T == union)) + { + // @@@BUG@@@ this should work + //alias .RepresentationTypes!(T[0].tupleof) + // RepresentationTypes; + alias RepresentationTypeTupleImpl + = staticMapMeta!(.RepresentationTypeTupleImpl, FieldTypeTuple!(T)); } + else + { + alias RepresentationTypeTupleImpl + = AliasSeq!T; + } +} - enum hasRawAliasing = Impl!(RepresentationTypeTuple!T); +/* +Statically evaluates to `true` if and only if `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) +{ + enum hasRawAliasing = anySatisfy!(hasRawAliasingImpl, RepresentationTypeTuple!T); } -/// +// @safe unittest { // simple types @@ -2703,13 +2918,23 @@ private template hasRawAliasing(T...) static assert(!hasRawAliasing!S2); } +// https://issues.dlang.org/show_bug.cgi?id=19228 @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 struct C + { + int*[1] a; + } + static assert(hasRawAliasing!C); +} + +@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); @@ -2749,39 +2974,36 @@ private template hasRawAliasing(T...) static assert(!hasRawAliasing!(immutable(int[string]))); } +private template hasRawAliasingImpl(T) +{ + static if (is(T foo : U*, U) && !isFunctionPointer!T) + enum hasRawAliasingImpl = !is(U == immutable); + else static if (is(T foo : U[N], U, size_t N)) + // separate static ifs to avoid forward reference + static if (is(U == class) || is(U == interface)) + enum hasRawAliasingImpl = false; + else + enum hasRawAliasingImpl = hasRawAliasingImpl!U; + else static if (is(T foo : U[], U) && !isStaticArray!(T)) + enum hasRawAliasingImpl = !is(U == immutable); + else static if (isAssociativeArray!(T)) + enum hasRawAliasingImpl = !is(T == immutable); + else + enum hasRawAliasingImpl = false; +} + /* -Statically evaluates to $(D true) if and only if $(D T)'s +Statically evaluates to `true` if and only if `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...) +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); + enum hasRawUnsharedAliasing = anySatisfy!(hasRawUnsharedAliasingImpl, RepresentationTypeTuple!T); } -/// +// @safe unittest { // simple types @@ -2931,81 +3153,64 @@ private template hasRawUnsharedAliasing(T...) static assert(!hasRawUnsharedAliasing!S28); } +private template hasRawUnsharedAliasingImpl(T) +{ + static if (is(T foo : U*, U) && !isFunctionPointer!T) + enum hasRawUnsharedAliasingImpl = !is(U == immutable) && !is(U == shared); + else static if (is(T foo : U[], U) && !isStaticArray!T) + enum hasRawUnsharedAliasingImpl = !is(U == immutable) && !is(U == shared); + else static if (isAssociativeArray!T) + enum hasRawUnsharedAliasingImpl = !is(T == immutable) && !is(T == shared); + else + enum hasRawUnsharedAliasingImpl = false; +} + /* -Statically evaluates to $(D true) if and only if $(D T)'s +Statically evaluates to `true` if and only if `T`'s representation includes at least one non-immutable object reference. */ -private template hasObjects(T...) +private template hasObjects(T) { - static if (T.length == 0) - { - enum hasObjects = false; - } - else static if (is(T[0] == struct)) + static if (is(T == struct)) { - enum hasObjects = hasObjects!( - RepresentationTypeTuple!(T[0]), T[1 .. $]); + enum hasObjects = anySatisfy!(.hasObjects, RepresentationTypeTuple!T); } else { - enum hasObjects = ((is(T[0] == class) || is(T[0] == interface)) - && !is(T[0] == immutable)) || hasObjects!(T[1 .. $]); + enum hasObjects = (is(T == class) || is(T == interface)) && !is(T == immutable); } } /* -Statically evaluates to $(D true) if and only if $(D T)'s +Statically evaluates to `true` if and only if `T`'s representation includes at least one non-immutable non-shared object reference. */ -private template hasUnsharedObjects(T...) +private template hasUnsharedObjects(T) { - static if (T.length == 0) + static if (is(T == struct)) { - enum hasUnsharedObjects = false; - } - else static if (is(T[0] == struct)) - { - enum hasUnsharedObjects = hasUnsharedObjects!( - RepresentationTypeTuple!(T[0]), T[1 .. $]); + enum hasUnsharedObjects = anySatisfy!(.hasUnsharedObjects, RepresentationTypeTuple!T); } else { - enum hasUnsharedObjects = ((is(T[0] == class) || is(T[0] == interface)) && - !is(T[0] == immutable) && !is(T[0] == shared)) || - hasUnsharedObjects!(T[1 .. $]); + enum hasUnsharedObjects = (is(T == class) || is(T == interface)) && + !is(T == immutable) && !is(T == shared); } } /** -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 +Returns `true` if and only if `T`'s representation includes at +least one of the following: $(OL $(LI a raw pointer `U*` and `U` +is not immutable;) $(LI an array `U[]` and `U` is not +immutable;) $(LI a reference to a class or interface type `C` and `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); - } + enum hasAliasing = anySatisfy!(hasAliasingImpl, T); } /// @@ -3083,18 +3288,44 @@ template hasAliasing(T...) static assert( hasAliasing!S12); static assert( hasAliasing!S13); static assert(!hasAliasing!S14); + + class S15 { S15[1] a; } + static assert( hasAliasing!S15); + static assert(!hasAliasing!(immutable(S15))); +} + +private template hasAliasingImpl(T) +{ + import std.typecons : Rebindable; + + static if (is(T : Rebindable!R, R)) + { + enum hasAliasingImpl = hasAliasingImpl!R; + } + else + { + template isAliasingDelegate(T) + { + enum isAliasingDelegate = isDelegate!T + && !is(T == immutable) + && !is(FunctionTypeOf!T == immutable); + } + enum hasAliasingImpl = hasRawAliasing!T || hasObjects!T || + anySatisfy!(isAliasingDelegate, T, RepresentationTypeTuple!T); + } } + /** -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.)) +Returns `true` if and only if `T`'s representation includes at +least one of the following: $(OL $(LI a raw pointer `U*`;) $(LI an +array `U[]`;) $(LI a reference to a class type `C`;) +$(LI an associative array;) $(LI a delegate;) +$(LI a [context pointer][isNested].)) */ template hasIndirections(T) { - import std.meta : anySatisfy; static if (is(T == struct) || is(T == union)) - enum hasIndirections = anySatisfy!(.hasIndirections, FieldTypeTuple!T); + enum hasIndirections = anySatisfy!(.hasIndirections, typeof(T.tupleof)); 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) @@ -3175,9 +3406,13 @@ template hasIndirections(T) static assert( hasIndirections!S24); static assert( hasIndirections!S25); static assert( hasIndirections!S26); + int local; + struct HasContextPointer { int opCall() { return ++local; } } + static assert(hasIndirections!HasContextPointer); } -@safe unittest //12000 +// https://issues.dlang.org/show_bug.cgi?id=12000 +@safe unittest { static struct S(T) { @@ -3193,45 +3428,17 @@ template hasIndirections(T) } /** -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 +Returns `true` if and only if `T`'s representation includes at +least one of the following: $(OL $(LI a raw pointer `U*` and `U` +is not immutable or shared;) $(LI an array `U[]` and `U` is not +immutable or shared;) $(LI a reference to a class type `C` and +`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..$]); - } + enum hasUnsharedAliasing = anySatisfy!(hasUnsharedAliasingImpl, T); } /// @@ -3256,7 +3463,7 @@ template hasUnsharedAliasing(T...) @safe unittest { - /* Issue 6642 */ + /* https://issues.dlang.org/show_bug.cgi?id=6642 */ import std.typecons : Rebindable; struct S8 { int a; Rebindable!(immutable Object) b; } static assert(!hasUnsharedAliasing!S8); @@ -3303,7 +3510,7 @@ template hasUnsharedAliasing(T...) static assert(!hasUnsharedAliasing!(Rebindable!(shared Object))); static assert( hasUnsharedAliasing!(Rebindable!Object)); - /* Issue 6979 */ + /* https://issues.dlang.org/show_bug.cgi?id=6979 */ static assert(!hasUnsharedAliasing!(int, shared(int)*)); static assert( hasUnsharedAliasing!(int, int*)); static assert( hasUnsharedAliasing!(int, const(int)[])); @@ -3370,29 +3577,43 @@ template hasUnsharedAliasing(T...) static assert(!hasUnsharedAliasing!S20); } +private template hasUnsharedAliasingImpl(T) +{ + import std.typecons : Rebindable; + + static if (is(T R: Rebindable!R)) + { + enum hasUnsharedAliasingImpl = hasUnsharedAliasingImpl!R; + } + else + { + template unsharedDelegate(T) + { + enum bool unsharedDelegate = isDelegate!T + && !is(T == shared) + && !is(T == immutable) + && !is(FunctionTypeOf!T == shared) + && !is(FunctionTypeOf!T == immutable); + } + + enum hasUnsharedAliasingImpl = + hasRawUnsharedAliasing!T || + anySatisfy!(unsharedDelegate, RepresentationTypeTuple!T) || + hasUnsharedObjects!T; + } +} + /** - True if $(D S) or any type embedded directly in the representation of $(D S) + True if `S` or any type embedded directly in the representation of `S` defines an elaborate copy constructor. Elaborate copy constructors are - introduced by defining $(D this(this)) for a $(D struct). + introduced by defining `this(this)` for a `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; - } + import core.internal.traits : hasElabCCtor = hasElaborateCopyConstructor; + alias hasElaborateCopyConstructor = hasElabCCtor!(S); } /// @@ -3420,13 +3641,13 @@ template hasElaborateCopyConstructor(S) } /** - True if $(D S) or any type directly embedded in the representation of $(D S) + True if `S` or any type directly embedded in the representation of `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). + defining `opAssign(typeof(this))` or $(D opAssign(ref typeof(this))) + for a `struct` or when there is a compiler-generated `opAssign`. - A type $(D S) gets compiler-generated $(D opAssign) in case it has - an elaborate copy constructor or elaborate destructor. + A type `S` gets compiler-generated `opAssign` if it has + an elaborate destructor. Classes and unions never have elaborate assignments. @@ -3436,7 +3657,6 @@ template hasElaborateCopyConstructor(S) */ template hasElaborateAssign(S) { - import std.meta : anySatisfy; static if (isStaticArray!S && S.length) { enum bool hasElaborateAssign = hasElaborateAssign!(typeof(S.init[0])); @@ -3513,30 +3733,18 @@ template hasElaborateAssign(S) } /** - 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 + True if `S` or any type directly embedded in the representation + of `S` defines an elaborate destructor. Elaborate destructors + are introduced by defining `~this()` for a $(D struct). Classes and unions never have elaborate destructors, even - though classes may define $(D ~this()). + though classes may define `~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; - } + import core.internal.traits : hasElabDest = hasElaborateDestructor; + alias hasElaborateDestructor = hasElabDest!(S); } /// @@ -3563,11 +3771,52 @@ template hasElaborateDestructor(S) static assert( hasElaborateDestructor!S7); } +/** + True if `S` or any type embedded directly in the representation of `S` + defines elaborate move semantics. Elaborate move semantics are + introduced by defining `opPostMove(ref typeof(this))` for a `struct`. + + Classes and unions never have elaborate move semantics. + */ +template hasElaborateMove(S) +{ + import core.internal.traits : hasElabMove = hasElaborateMove; + alias hasElaborateMove = hasElabMove!(S); +} + +/// +@safe unittest +{ + static assert(!hasElaborateMove!int); + + static struct S1 { } + static struct S2 { void opPostMove(ref S2) {} } + static struct S3 { void opPostMove(inout ref S3) inout {} } + static struct S4 { void opPostMove(const ref S4) {} } + static struct S5 { void opPostMove(S5) {} } + static struct S6 { void opPostMove(int) {} } + static struct S7 { S3[1] field; } + static struct S8 { S3[] field; } + static struct S9 { S3[0] field; } + static struct S10 { @disable this(); S3 field; } + static assert(!hasElaborateMove!S1); + static assert( hasElaborateMove!S2); + static assert( hasElaborateMove!S3); + static assert( hasElaborateMove!(immutable S3)); + static assert( hasElaborateMove!S4); + static assert(!hasElaborateMove!S5); + static assert(!hasElaborateMove!S6); + static assert( hasElaborateMove!S7); + static assert(!hasElaborateMove!S8); + static assert(!hasElaborateMove!S9); + static assert( hasElaborateMove!S10); +} + 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). + Yields `true` if and only if `T` is an aggregate that defines + a symbol called `name`. */ enum hasMember(T, string name) = __traits(hasMember, T, name); @@ -3587,7 +3836,7 @@ enum hasMember(T, string name) = __traits(hasMember, T, name); @safe unittest { - // 8321 + // https://issues.dlang.org/show_bug.cgi?id=8321 struct S { int x; void f(){} @@ -3630,20 +3879,25 @@ enum hasMember(T, string name) = __traits(hasMember, T, name); * 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). + * T = Type containing symbol `member`. + * member = Name of symbol to test that resides in `T`. * * Returns: - * $(D true) iff $(D member) exists and is static. + * `true` iff `member` exists and is static. */ template hasStaticMember(T, string member) { static if (__traits(hasMember, T, member)) { + static if (isPointer!T) + alias U = PointerTarget!T; + else + alias U = T; + import std.meta : Alias; - alias sym = Alias!(__traits(getMember, T, member)); + alias sym = Alias!(__traits(getMember, U, member)); - static if (__traits(getOverloads, T, member).length == 0) + static if (__traits(getOverloads, U, member).length == 0) enum bool hasStaticMember = __traits(compiles, &sym); else enum bool hasStaticMember = __traits(isStaticFunction, sym); @@ -3696,7 +3950,7 @@ template hasStaticMember(T, string member) static void f(); static void f2() pure nothrow @nogc @safe; - shared void g(); + void g() shared; static void function() fp; __gshared void function() gfp; @@ -3734,7 +3988,7 @@ template hasStaticMember(T, string member) static void f(); static void f2() pure nothrow @nogc @safe; - shared void g() { } + void g() shared { } static void function() fp; __gshared void function() gfp; @@ -3753,6 +4007,7 @@ template hasStaticMember(T, string member) static @property int sp(); } + static assert(!hasStaticMember!(S, "na")); static assert(!hasStaticMember!(S, "X")); static assert(!hasStaticMember!(S, "Y")); static assert(!hasStaticMember!(S, "Y.i")); @@ -3777,6 +4032,7 @@ template hasStaticMember(T, string member) static assert(!hasStaticMember!(S, "p")); static assert( hasStaticMember!(S, "sp")); + static assert(!hasStaticMember!(C, "na")); static assert(!hasStaticMember!(C, "X")); static assert(!hasStaticMember!(C, "Y")); static assert(!hasStaticMember!(C, "Y.i")); @@ -3788,8 +4044,8 @@ template hasStaticMember(T, string member) 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, "dm")); + static assert( hasStaticMember!(C, "sd")); static assert(!hasStaticMember!(C, "g")); static assert( hasStaticMember!(C, "fp")); static assert( hasStaticMember!(C, "gfp")); @@ -3800,75 +4056,65 @@ template hasStaticMember(T, string member) static assert( hasStaticMember!(C, "iosf")); static assert(!hasStaticMember!(C, "p")); static assert( hasStaticMember!(C, "sp")); + + alias P = S*; + static assert(!hasStaticMember!(P, "na")); + static assert(!hasStaticMember!(P, "X")); + static assert(!hasStaticMember!(P, "Y")); + static assert(!hasStaticMember!(P, "Y.i")); + static assert(!hasStaticMember!(P, "S")); + static assert(!hasStaticMember!(P, "C")); + static assert( hasStaticMember!(P, "sx")); + static assert( hasStaticMember!(P, "gx")); + static assert(!hasStaticMember!(P, "y")); + static assert( hasStaticMember!(P, "sy")); + static assert( hasStaticMember!(P, "f")); + static assert( hasStaticMember!(P, "f2")); + static assert(!hasStaticMember!(P, "dm")); + static assert( hasStaticMember!(P, "sd")); + static assert(!hasStaticMember!(P, "g")); + static assert( hasStaticMember!(P, "fp")); + static assert( hasStaticMember!(P, "gfp")); + static assert(!hasStaticMember!(P, "fpm")); + static assert(!hasStaticMember!(P, "m")); + static assert(!hasStaticMember!(P, "m2")); + static assert(!hasStaticMember!(P, "iom")); + static assert( hasStaticMember!(P, "iosf")); + static assert(!hasStaticMember!(P, "p")); + static assert( hasStaticMember!(P, "sp")); } /** -Retrieves the members of an enumerated type $(D enum E). +Retrieves the members of an enumerated type `enum E`. Params: - E = An enumerated type. $(D E) may have duplicated values. + E = An enumerated type. `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). + Static tuple composed of the members of the enumerated type `E`. + The members are arranged in the same order as declared in `E`. + The name of the enum can be found by querying the compiler for the + name of the identifier, i.e. `__traits(identifier, EnumMembers!MyEnum[i])`. + For enumerations with unique values, $(REF to, std,conv) can also be used. 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. + 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: + Returned values are strictly typed with `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); --------------------- + Cast is not necessary if the type of the variable is inferred. See the + example below. */ template EnumMembers(E) - if (is(E == enum)) +if (is(E == enum)) { import std.meta : AliasSeq; // Supply the specified identifier to an constant value. @@ -3916,6 +4162,81 @@ template EnumMembers(E) alias EnumMembers = EnumSpecificMembers!(__traits(allMembers, E)); } +/// Create an array of enumerated values +@safe unittest +{ + 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 `rank(v)` in the following example uses this +template for finding a member `e` in an enumerated type `E`. + */ +@safe unittest +{ + // Returns i if e is the i-th enumerator of E. + static size_t rank(E)(E e) + if (is(E == enum)) + { + static 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); +} + +/** +Use EnumMembers to generate a switch statement using static foreach. +*/ + +@safe unittest +{ + import std.conv : to; + class FooClass + { + string calledMethod; + void foo() @safe { calledMethod = "foo"; } + void bar() @safe { calledMethod = "bar"; } + void baz() @safe { calledMethod = "baz"; } + } + + enum FooEnum { foo, bar, baz } + + auto var = FooEnum.bar; + auto fooObj = new FooClass(); + s: final switch (var) + { + static foreach (member; EnumMembers!FooEnum) + { + case member: // Generate a case for each enum value. + // Call fooObj.{name of enum value}(). + __traits(getMember, fooObj, to!string(member))(); + break s; + } + } + // As we pass in FooEnum.bar, the bar() method gets called. + assert(fooObj.calledMethod == "bar"); +} + @safe unittest { enum A { a } @@ -3948,7 +4269,8 @@ template EnumMembers(E) static assert([ EnumMembers!A ] == [ A.a, A.b, A.c, A.d, A.e ]); } -@safe unittest // Bugzilla 14561: huge enums +// https://issues.dlang.org/show_bug.cgi?id=14561: huge enums +@safe unittest { string genEnum() { @@ -4002,6 +4324,8 @@ template BaseTypeTuple(A) /// @safe unittest { + import std.meta : AliasSeq; + interface I1 { } interface I2 { } interface I12 : I1, I2 { } @@ -4034,7 +4358,7 @@ template BaseTypeTuple(A) * BaseClassesTuple!Object) yields the empty type tuple. */ template BaseClassesTuple(T) - if (is(T == class)) +if (is(T == class)) { static if (is(T == Object)) { @@ -4044,6 +4368,10 @@ template BaseClassesTuple(T) { alias BaseClassesTuple = AliasSeq!Object; } + else static if (!is(BaseTypeTuple!T[0] == Object) && !is(BaseTypeTuple!T[0] == class)) + { + alias BaseClassesTuple = AliasSeq!(); + } else { alias BaseClassesTuple = @@ -4055,6 +4383,8 @@ template BaseClassesTuple(T) /// @safe unittest { + import std.meta : AliasSeq; + class C1 { } class C2 : C1 { } class C3 : C2 { } @@ -4064,6 +4394,22 @@ template BaseClassesTuple(T) static assert(is(BaseClassesTuple!C3 == AliasSeq!(C2, C1, Object))); } +// https://issues.dlang.org/show_bug.cgi?id=17276 +@safe unittest +{ + extern (C++) static interface Ext + { + void someext(); + } + + extern (C++) static class E : Ext + { + void someext() {} + } + + alias BaseClassesWithNoObject = BaseClassesTuple!E; +} + @safe unittest { struct S { } @@ -4076,10 +4422,15 @@ template BaseClassesTuple(T) } /** - * 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. +Params: + T = The `class` or `interface` to search. + +Returns: + $(REF AliasSeq,std,meta) of all interfaces directly or + indirectly inherited by this class or interface. Interfaces + do not repeat if multiply implemented. + + `InterfacesTuple!Object` yields an empty `AliasSeq`. */ template InterfacesTuple(T) { @@ -4105,14 +4456,15 @@ template InterfacesTuple(T) alias InterfacesTuple = AliasSeq!(); } +/// @safe unittest { - // doc example interface I1 {} interface I2 {} - class A : I1, I2 { } - class B : A, I1 { } - class C : B { } + 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)); } @@ -4172,12 +4524,12 @@ template TransitiveBaseTypeTuple(T) /** -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 +Returns a tuple of non-static functions with the name `name` declared in the +class or interface `C`. Covariant duplicates are shrunk into the most derived one. */ template MemberFunctionsTuple(C, string name) - if (is(C == class) || is(C == interface)) +if (is(C == class) || is(C == interface)) { static if (__traits(hasMember, C, name)) { @@ -4189,7 +4541,7 @@ template MemberFunctionsTuple(C, string name) 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)); + alias inSight = __traits(getVirtualFunctions, Node, name); // And collect all overloads in ancestor classes to reveal hidden // methods. The result may contain duplicates. @@ -4213,8 +4565,11 @@ template MemberFunctionsTuple(C, string name) alias CollectOverloads = AliasSeq!(); // no overloads in this hierarchy } - // duplicates in this tuple will be removed by shrink() - alias overloads = CollectOverloads!C; + static if (name == "__ctor" || name == "__dtor") + alias overloads = AliasSeq!(__traits(getOverloads, C, name)); + else + // 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 @@ -4232,7 +4587,7 @@ template MemberFunctionsTuple(C, string name) 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])) + static if (is(__traits(parent, target) : __traits(parent, rest[0]))) alias shrinkOne = shrinkOne!(target, rest[1 .. $]); else alias shrinkOne = shrinkOne!(rest[0], rest[1 .. $]); @@ -4293,7 +4648,8 @@ template MemberFunctionsTuple(C, string name) static assert(__traits(isSame, foos[1], B.foo)); } -@safe unittest // Issue 15920 +// https://issues.dlang.org/show_bug.cgi?id=15920 +@safe unittest { import std.meta : AliasSeq; class A @@ -4307,11 +4663,46 @@ template MemberFunctionsTuple(C, string name) override void f(int){} } alias fs = MemberFunctionsTuple!(B, "f"); - alias bfs = AliasSeq!(__traits(getOverloads, B, "f")); + alias bfs = __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])); } +// https://issues.dlang.org/show_bug.cgi?id=8388 +@safe unittest +{ + class C + { + this() {} + this(int i) {} + this(int i, float j) {} + this(string s) {} + + /* + Commented out, because this causes a cyclic dependency + between module constructors/destructors error. Might + be caused by https://issues.dlang.org/show_bug.cgi?id=20529. */ + // static this() {} + + ~this() {} + } + + class D : C + { + this() {} + ~this() {} + } + + alias test_ctor = MemberFunctionsTuple!(C, "__ctor"); + assert(test_ctor.length == 4); + alias test_dtor = MemberFunctionsTuple!(C, "__dtor"); + assert(test_dtor.length == 1); + alias test2_ctor = MemberFunctionsTuple!(D, "__ctor"); + assert(test2_ctor.length == 1); + alias test2_dtor = MemberFunctionsTuple!(D, "__dtor"); + assert(test2_dtor.length == 1); +} + @safe unittest { interface I { I test(); } @@ -4354,7 +4745,8 @@ template MemberFunctionsTuple(C, string name) /** -Returns an alias to the template that $(D T) is an instance of. +Returns an alias to the template that `T` is an instance of. +It will return `void` if a symbol without a template is given. */ template TemplateOf(alias T : Base!Args, alias Base, Args...) { @@ -4367,6 +4759,12 @@ template TemplateOf(T : Base!Args, alias Base, Args...) alias TemplateOf = Base; } +/// ditto +template TemplateOf(T) +{ + alias TemplateOf = void; +} + /// @safe unittest { @@ -4397,9 +4795,15 @@ template TemplateOf(T : Base!Args, alias Base, Args...) static assert(__traits(isSame, TemplateOf!(Foo10!()), Foo10)); } +// https://issues.dlang.org/show_bug.cgi?id=18214 +@safe unittest +{ + static assert(is(TemplateOf!(int[]) == void)); + static assert(is(TemplateOf!bool == void)); +} /** -Returns a $(D AliasSeq) of the template arguments used to instantiate $(D T). +Returns a `AliasSeq` of the template arguments used to instantiate `T`. */ template TemplateArgsOf(alias T : Base!Args, alias Base, Args...) { @@ -4415,6 +4819,8 @@ template TemplateArgsOf(T : Base!Args, alias Base, Args...) /// @safe unittest { + import std.meta : AliasSeq; + struct Foo(T, U) {} static assert(is(TemplateArgsOf!(Foo!(int, real)) == AliasSeq!(int, real))); } @@ -4445,17 +4851,20 @@ template TemplateArgsOf(T : Base!Args, alias Base, Args...) } -private template maxAlignment(U...) if (isTypeTuple!U) +package 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 static if (U.length == 2) + enum maxAlignment = U[0].alignof > U[1].alignof ? U[0].alignof : U[1].alignof; else { - import std.algorithm.comparison : max; - enum maxAlignment = max(staticMap!(.maxAlignment, U)); + enum a = maxAlignment!(U[0 .. ($+1)/2]); + enum b = maxAlignment!(U[($+1)/2 .. $]); + enum maxAlignment = a > b ? a : b; } } @@ -4463,7 +4872,8 @@ private template maxAlignment(U...) if (isTypeTuple!U) /** Returns class instance alignment. */ -template classInstanceAlignment(T) if (is(T == class)) +template classInstanceAlignment(T) +if (is(T == class)) { alias classInstanceAlignment = maxAlignment!(void*, typeof(T.tupleof)); } @@ -4523,27 +4933,187 @@ template CommonType(T...) 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)); + static assert(is(CommonType!(double, int) == double)); } /** - * 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) +Params: + T = The type to check + +Returns: + An $(REF AliasSeq,std,meta) with all possible target types of an implicit + conversion `T`. + + If `T` is a class derived from `Object`, the result of + $(LREF TransitiveBaseTypeTuple) is returned. + + If the type is not a built-in value type or a class derived from + `Object`, an empty $(REF AliasSeq,std,meta) is returned. + +See_Also: + $(LREF isImplicitlyConvertible) + */ +template AllImplicitConversionTargets(T) +{ + static if (is(T == bool)) + alias AllImplicitConversionTargets = + AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real, char, wchar, dchar); + else static if (is(T == byte)) + alias AllImplicitConversionTargets = + AliasSeq!(ubyte, short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real, char, wchar, dchar); + else static if (is(T == ubyte)) + alias AllImplicitConversionTargets = + AliasSeq!(byte, short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real, char, wchar, dchar); + else static if (is(T == short)) + alias AllImplicitConversionTargets = + AliasSeq!(ushort, int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T == ushort)) + alias AllImplicitConversionTargets = + AliasSeq!(short, int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T == int)) + alias AllImplicitConversionTargets = + AliasSeq!(uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T == uint)) + alias AllImplicitConversionTargets = + AliasSeq!(int, long, ulong, CentTypeList, float, double, real); + else static if (is(T == long)) + alias AllImplicitConversionTargets = AliasSeq!(ulong, float, double, real); + else static if (is(T == ulong)) + alias AllImplicitConversionTargets = AliasSeq!(long, float, double, real); + else static if (is(cent) && is(T == cent)) + alias AllImplicitConversionTargets = AliasSeq!(UnsignedCentTypeList, float, double, real); + else static if (is(ucent) && is(T == ucent)) + alias AllImplicitConversionTargets = AliasSeq!(SignedCentTypeList, float, double, real); + else static if (is(T == float)) + alias AllImplicitConversionTargets = AliasSeq!(double, real); + else static if (is(T == double)) + alias AllImplicitConversionTargets = AliasSeq!(float, real); + else static if (is(T == real)) + alias AllImplicitConversionTargets = AliasSeq!(float, double); + else static if (is(T == char)) + alias AllImplicitConversionTargets = + AliasSeq!(wchar, dchar, byte, ubyte, short, ushort, + int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T == wchar)) + alias AllImplicitConversionTargets = + AliasSeq!(dchar, short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real); + else static if (is(T == dchar)) + alias AllImplicitConversionTargets = + AliasSeq!(int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T : typeof(null))) + alias AllImplicitConversionTargets = AliasSeq!(typeof(null)); + else static if (is(T == class)) + alias AllImplicitConversionTargets = staticMap!(ApplyLeft!(CopyConstness, T), TransitiveBaseTypeTuple!(T)); + else static if (is(T == interface)) + alias AllImplicitConversionTargets = staticMap!(ApplyLeft!(CopyConstness, T), InterfacesTuple!(T)); + else static if (isDynamicArray!T && !is(typeof(T.init[0]) == const)) + { + static if (is(typeof(T.init[0]) == shared)) + alias AllImplicitConversionTargets = + AliasSeq!(const(shared(Unqual!(typeof(T.init[0]))))[]); + else + alias AllImplicitConversionTargets = + AliasSeq!(const(Unqual!(typeof(T.init[0])))[]); + } + else static if (is(T : void*)) + alias AllImplicitConversionTargets = AliasSeq!(void*); + else + alias AllImplicitConversionTargets = AliasSeq!(); +} + +/// +@safe unittest +{ + import std.meta : AliasSeq; + + static assert(is(AllImplicitConversionTargets!(ulong) == AliasSeq!(long, float, double, real))); + static assert(is(AllImplicitConversionTargets!(int) == AliasSeq!(uint, long, ulong, float, double, real))); + static assert(is(AllImplicitConversionTargets!(float) == AliasSeq!(double, real))); + static assert(is(AllImplicitConversionTargets!(double) == AliasSeq!(float, real))); + + static assert(is(AllImplicitConversionTargets!(char) == AliasSeq!( + wchar, dchar, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real + ))); + static assert(is(AllImplicitConversionTargets!(wchar) == AliasSeq!( + dchar, short, ushort, int, uint, long, ulong, float, double, real + ))); + static assert(is(AllImplicitConversionTargets!(dchar) == AliasSeq!( + int, uint, long, ulong, float, double, real + ))); + + static assert(is(AllImplicitConversionTargets!(string) == AliasSeq!(const(char)[]))); + static assert(is(AllImplicitConversionTargets!(void*) == AliasSeq!(void*))); + + interface A {} + interface B {} + class C : A, B {} + + static assert(is(AllImplicitConversionTargets!(C) == AliasSeq!(Object, A, B))); + static assert(is(AllImplicitConversionTargets!(const C) == AliasSeq!(const Object, const A, const B))); + static assert(is(AllImplicitConversionTargets!(immutable C) == AliasSeq!( + immutable Object, immutable A, immutable B + ))); + + interface I : A, B {} + + static assert(is(AllImplicitConversionTargets!(I) == AliasSeq!(A, B))); + static assert(is(AllImplicitConversionTargets!(const I) == AliasSeq!(const A, const B))); + static assert(is(AllImplicitConversionTargets!(immutable I) == AliasSeq!( + immutable A, immutable B + ))); +} + +@safe unittest +{ + static assert(is(AllImplicitConversionTargets!(double)[0] == float)); + static assert(is(AllImplicitConversionTargets!(double)[1] == real)); + static assert(is(AllImplicitConversionTargets!(string)[0] == const(char)[])); +} + + +/** +Params: + T = The type to check + +Warning: + This template is considered out-dated. It will be removed from + Phobos in 2.107.0. Please use $(LREF AllImplicitConversionTargets) instead. + +Returns: + An $(REF AliasSeq,std,meta) with all possible target types of an implicit + conversion `T`. + + If `T` is a class derived from `Object`, the result of + $(LREF TransitiveBaseTypeTuple) is returned. + + If the type is not a built-in value type or a class derived from + `Object`, an empty $(REF AliasSeq,std,meta) is returned. + +Note: + The possible targets are computed more conservatively than the + language allows, eliminating all dangerous conversions. For example, + `ImplicitConversionTargets!double` does not include `float`. + +See_Also: + $(LREF isImplicitlyConvertible) + */ +// @@@DEPRECATED_[2.107.0]@@@ +deprecated("ImplicitConversionTargets has been deprecated in favour of AllImplicitConversionTargets " + ~ "and will be removed in 2.107.0") +template ImplicitConversionTargets(T) { static if (is(T == bool)) alias ImplicitConversionTargets = @@ -4594,36 +5164,66 @@ template ImplicitConversionTargets(T) 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 (is(T == class)) + alias ImplicitConversionTargets = staticMap!(ApplyLeft!(CopyConstness, T), TransitiveBaseTypeTuple!(T)); else static if (isDynamicArray!T && !is(typeof(T.init[0]) == const)) - alias ImplicitConversionTargets = - AliasSeq!(const(Unqual!(typeof(T.init[0])))[]); + { + static if (is(typeof(T.init[0]) == shared)) + alias ImplicitConversionTargets = + AliasSeq!(const(shared(Unqual!(typeof(T.init[0]))))[]); + else + 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 +deprecated @safe unittest +{ + import std.meta : AliasSeq; + + static assert(is(ImplicitConversionTargets!(ulong) == AliasSeq!(float, double, real))); + static assert(is(ImplicitConversionTargets!(int) == AliasSeq!(long, ulong, float, double, real))); + static assert(is(ImplicitConversionTargets!(float) == AliasSeq!(double, real))); + static assert(is(ImplicitConversionTargets!(double) == AliasSeq!(real))); + + static assert(is(ImplicitConversionTargets!(char) == AliasSeq!( + wchar, dchar, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, real + ))); + static assert(is(ImplicitConversionTargets!(wchar) == AliasSeq!( + dchar, short, ushort, int, uint, long, ulong, float, double, real + ))); + static assert(is(ImplicitConversionTargets!(dchar) == AliasSeq!( + int, uint, long, ulong, float, double, real + ))); + + static assert(is(ImplicitConversionTargets!(string) == AliasSeq!(const(char)[]))); + static assert(is(ImplicitConversionTargets!(void*) == AliasSeq!(void*))); + + interface A {} + interface B {} + class C : A, B {} + + static assert(is(ImplicitConversionTargets!(C) == AliasSeq!(Object, A, B))); + static assert(is(ImplicitConversionTargets!(const C) == AliasSeq!(const Object, const A, const B))); + static assert(is(ImplicitConversionTargets!(immutable C) == AliasSeq!( + immutable Object, immutable A, immutable B + ))); +} + +deprecated @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)? +Is `From` implicitly convertible to `To`? */ -template isImplicitlyConvertible(From, To) -{ - enum bool isImplicitlyConvertible = is(typeof({ - void fun(ref From v) - { - void gun(To) {} - gun(v); - } - })); -} +enum bool isImplicitlyConvertible(From, To) = is(From : To); /// @safe unittest @@ -4642,12 +5242,12 @@ template isImplicitlyConvertible(From, To) } /** -Returns $(D true) iff a value of type $(D Rhs) can be assigned to a variable of -type $(D Lhs). +Returns `true` iff a value of type `Rhs` can be assigned to a variable of +type `Lhs`. -$(D isAssignable) returns whether both an lvalue and rvalue can be assigned. +`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). +If you omit `Rhs`, `isAssignable` will check identity assignable of `Lhs`. */ enum isAssignable(Lhs, Rhs = Lhs) = isRvalueAssignable!(Lhs, Rhs) && isLvalueAssignable!(Lhs, Rhs); @@ -4666,11 +5266,17 @@ enum isAssignable(Lhs, Rhs = Lhs) = isRvalueAssignable!(Lhs, Rhs) && isLvalueAss static assert(!isAssignable!(immutable int)); } -// ditto -private enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); +/** +Returns `true` iff an rvalue of type `Rhs` can be assigned to a variable of +type `Lhs` +*/ +enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, { lvalueOf!Lhs = rvalueOf!Rhs; }); -// ditto -private enum isLvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs); +/** +Returns `true` iff an lvalue of type `Rhs` can be assigned to a variable of +type `Lhs` +*/ +enum isLvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, { lvalueOf!Lhs = lvalueOf!Rhs; }); @safe unittest { @@ -4706,20 +5312,37 @@ private enum isLvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lh 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)); + // https://issues.dlang.org/show_bug.cgi?id=21210 + static assert(!isAssignable!S5); + + // `-preview=in` is enabled + static if (!is(typeof(mixin(q{(in ref int a) => a})))) + { + struct S6 { void opAssign(in S5); } + + static assert(isRvalueAssignable!(S6, S5)); + static assert(isLvalueAssignable!(S6, S5)); + static assert(isAssignable!(S6, S5)); + static assert(isAssignable!(S6, immutable S5)); + } + else + { + mixin(q{ struct S6 { void opAssign(in ref S5); } }); + + static assert(!isRvalueAssignable!(S6, S5)); + static assert( isLvalueAssignable!(S6, S5)); + static assert(!isAssignable!(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)) + static if (is(T == enum)) { - enum isBlitAssignable = isBlitAssignable!U; + enum isBlitAssignable = isBlitAssignable!(OriginalType!T); } else static if (isStaticArray!T && is(T == E[n], E, size_t n)) // Workaround for issue 11499 : isStaticArray!T should not be necessary. @@ -4835,7 +5458,7 @@ package template isBlitAssignable(T) /* -Works like $(D isImplicitlyConvertible), except this cares only about storage +Works like `isImplicitlyConvertible`, except this cares only about storage classes of the arguments. */ private template isStorageClassImplicitlyConvertible(From, To) @@ -4860,11 +5483,13 @@ private template isStorageClassImplicitlyConvertible(From, To) /** -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). +Determines whether the function type `F` is covariant with `G`, i.e., +functions of the type `F` can override ones of the type `G`. */ template isCovariantWith(F, G) - if (is(F == function) && is(G == function)) +if (is(F == function) && is(G == function) || + is(F == delegate) && is(G == delegate) || + isFunctionPointer!F && isFunctionPointer!G) { static if (is(F : G)) enum isCovariantWith = true; @@ -4926,7 +5551,8 @@ template isCovariantWith(F, G) } /* * Check for parameters: - * - require exact match for types (cf. bugzilla 3075) + * - require exact match for types + * (cf. https://issues.dlang.org/show_bug.cgi?id=3075) * - require exact match for in, out, ref and lazy * - overrider can add scope, but can't remove */ @@ -5010,6 +5636,15 @@ template isCovariantWith(F, G) static assert( isCovariantWith!(DerivA_1.test, DerivA_1.test)); static assert( isCovariantWith!(DerivA_2.test, DerivA_2.test)); + // function, function pointer and delegate + J function() derived_function; + I function() base_function; + J delegate() derived_delegate; + I delegate() base_delegate; + static assert(.isCovariantWith!(typeof(derived_function), typeof(base_function))); + static assert(.isCovariantWith!(typeof(*derived_function), typeof(*base_function))); + static assert(.isCovariantWith!(typeof(derived_delegate), typeof(base_delegate))); + // scope parameter interface BaseB { void test( int*, int*); } interface DerivB_1 : BaseB { override void test(scope int*, int*); } @@ -5058,31 +5693,35 @@ template isCovariantWith(F, G) 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. +Creates an lvalue or rvalue of type `T` for `typeof(...)` and +`__traits(compiles, ...)` purposes. No actual value is returned. + +Params: + T = The type to transform 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 +// Note: can't put these unittests together as function overloads // aren't allowed inside functions. +/// +@system unittest +{ + static int f(int); + static assert(is(typeof(f(rvalueOf!int)) == int)); +} + +/// +@system unittest +{ + static bool f(ref int); + static assert(is(typeof(f(lvalueOf!int)) == bool)); +} @system unittest { @@ -5090,7 +5729,7 @@ int i = rvalueOf!int; // error, no actual value is returned static struct S { } int i; struct Nested { void f() { ++i; } } - foreach (T; AliasSeq!(int, immutable int, inout int, string, S, Nested, Object)) + static 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))); @@ -5108,17 +5747,7 @@ int i = rvalueOf!int; // error, no actual value is returned // 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"); -} +private alias AliasThisTypeOf(T) = typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])); /* */ @@ -5129,7 +5758,7 @@ template BooleanTypeOf(T) else alias X = OriginalType!T; - static if (is(Unqual!X == bool)) + static if (is(immutable X == immutable bool)) { alias BooleanTypeOf = X; } @@ -5140,15 +5769,15 @@ template BooleanTypeOf(T) @safe unittest { // unexpected failure, maybe dmd type-merging bug - foreach (T; AliasSeq!bool) - foreach (Q; TypeQualifierList) + static foreach (T; AliasSeq!bool) + static 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 foreach (T; AliasSeq!(void, NumericTypeList, /*ImaginaryTypeList, ComplexTypeList,*/ CharTypeList)) + static foreach (Q; TypeQualifierList) { static assert(!is(BooleanTypeOf!( Q!T )), Q!T.stringof); static assert(!is(BooleanTypeOf!( SubTypeOf!(Q!T) ))); @@ -5175,13 +5804,13 @@ template BooleanTypeOf(T) */ 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) + static if (__traits(isIntegral, X) && __traits(isZeroInit, X) // Not char, wchar, or dchar. + && !is(immutable X == immutable bool) && !is(X == __vector)) { alias IntegralTypeOf = X; } @@ -5191,15 +5820,16 @@ template IntegralTypeOf(T) @safe unittest { - foreach (T; IntegralTypeList) - foreach (Q; TypeQualifierList) + static foreach (T; IntegralTypeList) + static 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 foreach (T; AliasSeq!(void, bool, FloatingPointTypeList, + /*ImaginaryTypeList, ComplexTypeList,*/ CharTypeList)) + static foreach (Q; TypeQualifierList) { static assert(!is(IntegralTypeOf!( Q!T ))); static assert(!is(IntegralTypeOf!( SubTypeOf!(Q!T) ))); @@ -5210,13 +5840,12 @@ template IntegralTypeOf(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) + static if (is(immutable X == immutable U, U) && is(U == float) || is(U == double) || is(U == real)) { alias FloatingPointTypeOf = X; } @@ -5226,15 +5855,15 @@ template FloatingPointTypeOf(T) @safe unittest { - foreach (T; FloatingPointTypeList) - foreach (Q; TypeQualifierList) + static foreach (T; FloatingPointTypeList) + static 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 foreach (T; AliasSeq!(void, bool, IntegralTypeList, /*ImaginaryTypeList, ComplexTypeList,*/ CharTypeList)) + static foreach (Q; TypeQualifierList) { static assert(!is(FloatingPointTypeOf!( Q!T ))); static assert(!is(FloatingPointTypeOf!( SubTypeOf!(Q!T) ))); @@ -5255,15 +5884,15 @@ template NumericTypeOf(T) @safe unittest { - foreach (T; NumericTypeList) - foreach (Q; TypeQualifierList) + static foreach (T; NumericTypeList) + static 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 foreach (T; AliasSeq!(void, bool, CharTypeList, /*ImaginaryTypeList, ComplexTypeList*/)) + static foreach (Q; TypeQualifierList) { static assert(!is(NumericTypeOf!( Q!T ))); static assert(!is(NumericTypeOf!( SubTypeOf!(Q!T) ))); @@ -5274,9 +5903,7 @@ template NumericTypeOf(T) */ template UnsignedTypeOf(T) { - import std.meta : staticIndexOf; - static if (is(IntegralTypeOf!T X) && - staticIndexOf!(Unqual!X, UnsignedIntTypeList) >= 0) + static if (is(IntegralTypeOf!T X) && __traits(isUnsigned, X)) alias UnsignedTypeOf = X; else static assert(0, T.stringof~" is not an unsigned type."); @@ -5286,9 +5913,7 @@ template UnsignedTypeOf(T) */ template SignedTypeOf(T) { - import std.meta : staticIndexOf; - static if (is(IntegralTypeOf!T X) && - staticIndexOf!(Unqual!X, SignedIntTypeList) >= 0) + static if (is(IntegralTypeOf!T X) && !__traits(isUnsigned, X)) alias SignedTypeOf = X; else static if (is(FloatingPointTypeOf!T X)) alias SignedTypeOf = X; @@ -5300,13 +5925,12 @@ template SignedTypeOf(T) */ 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) + static if (is(immutable X == immutable U, U) && is(U == char) || is(U == wchar) || is(U == dchar)) { alias CharTypeOf = X; } @@ -5316,22 +5940,22 @@ template CharTypeOf(T) @safe unittest { - foreach (T; CharTypeList) - foreach (Q; TypeQualifierList) + static foreach (T; CharTypeList) + static 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 foreach (T; AliasSeq!(void, bool, NumericTypeList, /*ImaginaryTypeList, ComplexTypeList*/)) + static 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 foreach (T; AliasSeq!(string, wstring, dstring, char[4])) + static foreach (Q; TypeQualifierList) { static assert(!is(CharTypeOf!( Q!T ))); static assert(!is(CharTypeOf!( SubTypeOf!(Q!T) ))); @@ -5347,7 +5971,7 @@ template StaticArrayTypeOf(T) else alias X = OriginalType!T; - static if (is(X : E[n], E, size_t n)) + static if (__traits(isStaticArray, X)) alias StaticArrayTypeOf = X; else static assert(0, T.stringof~" is not a static array type"); @@ -5355,19 +5979,19 @@ template StaticArrayTypeOf(T) @safe unittest { - foreach (T; AliasSeq!(bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList)) - foreach (Q; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + static foreach (T; AliasSeq!(bool, NumericTypeList, /*ImaginaryTypeList, ComplexTypeList*/)) + static foreach (Q; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) { static assert(is( Q!( T[1] ) == StaticArrayTypeOf!( Q!( T[1] ) ) )); - foreach (P; TypeQualifierList) + static 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 foreach (T; AliasSeq!void) + static foreach (Q; AliasSeq!TypeQualifierList) { static assert(is( StaticArrayTypeOf!( Q!(void[1]) ) == Q!(void[1]) )); } @@ -5382,7 +6006,7 @@ template DynamicArrayTypeOf(T) else alias X = OriginalType!T; - static if (is(Unqual!X : E[], E) && !is(typeof({ enum n = X.length; }))) + static if (is(X == E[], E)) { alias DynamicArrayTypeOf = X; } @@ -5392,13 +6016,14 @@ template DynamicArrayTypeOf(T) @safe unittest { - foreach (T; AliasSeq!(/*void, */bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList)) - foreach (Q; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + import std.meta : Alias; + static foreach (T; AliasSeq!(/*void, */bool, NumericTypeList, /*ImaginaryTypeList, ComplexTypeList*/)) + static 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 foreach (P; AliasSeq!(Alias, ConstOf, ImmutableOf)) { static assert(is( Q!(P!T[]) == DynamicArrayTypeOf!( Q!(SubTypeOf!(P!T[])) ) )); static assert(is( Q!(P!(T[])) == DynamicArrayTypeOf!( Q!(SubTypeOf!(P!(T[]))) ) )); @@ -5423,7 +6048,20 @@ template ArrayTypeOf(T) } /* -Always returns the Dynamic Array version. + * Converts strings and string-like types to the corresponding dynamic array of characters. + * Params: + * T = one of the following: + * 1. dynamic arrays of `char`, `wchar`, or `dchar` that are implicitly convertible to `const` + * (`shared` is rejected) + * 2. static arrays of `char`, `wchar`, or `dchar` that are implicitly convertible to `const` + * (`shared` is rejected) + * 3. aggregates that use `alias this` to refer to a field that is (1), (2), or (3) + * + * Other cases are rejected with a compile time error. + * `typeof(null)` is rejected. + * + * Returns: + * The result of `[]` applied to the qualified character type. */ template StringTypeOf(T) { @@ -5447,23 +6085,24 @@ template StringTypeOf(T) @safe unittest { - foreach (T; CharTypeList) - foreach (Q; AliasSeq!(MutableOf, ConstOf, ImmutableOf, InoutOf)) + import std.meta : Alias; + static foreach (T; CharTypeList) + static foreach (Q; AliasSeq!(Alias, 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 foreach (T; CharTypeList) + static foreach (Q; AliasSeq!(SharedOf, SharedConstOf, SharedInoutOf)) { static assert(!is(StringTypeOf!( Q!T[] ))); } @@ -5472,6 +6111,21 @@ template StringTypeOf(T) @safe unittest { static assert(is(StringTypeOf!(char[4]) == char[])); + + struct S + { + string s; + alias s this; + } + + struct T + { + S s; + alias s this; + } + + static assert(is(StringTypeOf!S == string)); + static assert(is(StringTypeOf!T == string)); } /* @@ -5483,7 +6137,7 @@ template AssocArrayTypeOf(T) else alias X = OriginalType!T; - static if (is(Unqual!X : V[K], K, V)) + static if (__traits(isAssociativeArray, X)) { alias AssocArrayTypeOf = X; } @@ -5493,19 +6147,19 @@ template AssocArrayTypeOf(T) @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 foreach (T; AliasSeq!(int/*bool, CharTypeList, NumericTypeList, ImaginaryTypeList, ComplexTypeList*/)) + static foreach (P; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + static foreach (Q; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + static 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 foreach (T; AliasSeq!(int/*bool, CharTypeList, NumericTypeList, ImaginaryTypeList, ComplexTypeList*/)) + static foreach (O; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + static foreach (P; AliasSeq!TypeQualifierList) + static foreach (Q; AliasSeq!TypeQualifierList) + static foreach (R; AliasSeq!TypeQualifierList) { static assert(is( O!(P!(Q!T[R!T])) == AssocArrayTypeOf!( O!(SubTypeOf!(P!(Q!T[R!T]))) ) )); } @@ -5515,16 +6169,21 @@ template AssocArrayTypeOf(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); + static if (is(T : void)) + alias BuiltinTypeOf = void; + else + { + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = BuiltinTypeOf!AT; + else + alias X = OriginalType!T; + static if (__traits(isArithmetic, X) && !is(X == __vector) || + __traits(isStaticArray, X) || is(X == E[], E) || + __traits(isAssociativeArray, X)) + alias BuiltinTypeOf = X; + else + static assert(0); + } } //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// @@ -5532,9 +6191,9 @@ template BuiltinTypeOf(T) //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// /** - * Detect whether $(D T) is a built-in boolean type. + * Detect whether `T` is a built-in boolean type. */ -enum bool isBoolean(T) = is(BooleanTypeOf!T) && !isAggregateType!T; +enum bool isBoolean(T) = __traits(isUnsigned, T) && is(T : bool); /// @safe unittest @@ -5556,10 +6215,19 @@ enum bool isBoolean(T) = is(BooleanTypeOf!T) && !isAggregateType!T; } /** - * Detect whether $(D T) is a built-in integral type. Types $(D bool), - * $(D char), $(D wchar), and $(D dchar) are not considered integral. + * Detect whether `T` is a built-in integral type. Types `bool`, + * `char`, `wchar`, and `dchar` are not considered integral. */ -enum bool isIntegral(T) = is(IntegralTypeOf!T) && !isAggregateType!T; +template isIntegral(T) +{ + static if (!__traits(isIntegral, T)) + enum isIntegral = false; + else static if (is(T U == enum)) + enum isIntegral = isIntegral!U; + else + enum isIntegral = __traits(isZeroInit, T) // Not char, wchar, or dchar. + && !is(immutable T == immutable bool) && !is(T == __vector); +} /// @safe unittest @@ -5591,9 +6259,9 @@ enum bool isIntegral(T) = is(IntegralTypeOf!T) && !isAggregateType!T; @safe unittest { - foreach (T; IntegralTypeList) + static foreach (T; IntegralTypeList) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isIntegral!(Q!T)); static assert(!isIntegral!(SubTypeOf!(Q!T))); @@ -5603,20 +6271,16 @@ enum bool isIntegral(T) = is(IntegralTypeOf!T) && !isAggregateType!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) + // base type is signed (https://issues.dlang.org/show_bug.cgi?id=7909) + enum EI : int { a = -1, b = 0, c = 1 } 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. + * Detect whether `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)); +enum bool isFloatingPoint(T) = __traits(isFloating, T) && is(T : real); /// @safe unittest @@ -5631,12 +6295,6 @@ enum bool isFloatingPoint(T) = __traits(isFloating, T) && !(is(Unqual!T == cfloa 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 { @@ -5651,43 +6309,43 @@ enum bool isFloatingPoint(T) = __traits(isFloating, T) && !(is(Unqual!T == cfloa { enum EF : real { a = 1.414, b = 1.732, c = 2.236 } - foreach (T; AliasSeq!(FloatingPointTypeList, EF)) + static foreach (T; AliasSeq!(FloatingPointTypeList, EF)) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isFloatingPoint!(Q!T)); static assert(!isFloatingPoint!(SubTypeOf!(Q!T))); } } - foreach (T; IntegralTypeList) + static foreach (T; IntegralTypeList) { - foreach (Q; TypeQualifierList) + static 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); + static if (is(__vector(float[4]))) + { + static assert(!isFloatingPoint!(__vector(float[4]))); + } } /** - * Detect whether $(D T) is a built-in numeric type (integral or floating + * Detect whether `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)); +template isNumeric(T) +{ + static if (!__traits(isArithmetic, T)) + enum isNumeric = false; + else static if (__traits(isFloating, T)) + enum isNumeric = is(T : real); // Not __vector, imaginary, or complex. + else static if (is(T U == enum)) + enum isNumeric = isNumeric!U; + else + enum isNumeric = __traits(isZeroInit, T) // Not char, wchar, or dchar. + && !is(immutable T == immutable bool) && !is(T == __vector); +} /// @safe unittest @@ -5719,14 +6377,14 @@ enum bool isNumeric(T) = __traits(isArithmetic, T) && !(is(Unqual!T == bool) || alias val this; } - static assert(!isIntegral!S); + static assert(!isNumeric!S); } @safe unittest { - foreach (T; AliasSeq!(NumericTypeList)) + static foreach (T; AliasSeq!(NumericTypeList)) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isNumeric!(Q!T)); static assert(!isNumeric!(SubTypeOf!(Q!T))); @@ -5739,13 +6397,28 @@ enum bool isNumeric(T) = __traits(isArithmetic, T) && !(is(Unqual!T == bool) || alias t this; } static assert(!isNumeric!(S!int)); + + enum EChar : char { a = 0, } + static assert(!isNumeric!EChar); + + static if (is(__vector(float[4]))) + { + static assert(!isNumeric!(__vector(float[4]))); + } + static if (is(__vector(int[4]))) + { + static assert(!isNumeric!(__vector(int[4]))); + } + + static assert(!isNumeric!ifloat); + static assert(!isNumeric!cfloat); } /** - * Detect whether $(D T) is a scalar type (a built-in numeric, character or + * Detect whether `T` is a scalar type (a built-in numeric, character or * boolean type). */ -enum bool isScalarType(T) = is(T : real) && !isAggregateType!T; +enum bool isScalarType(T) = __traits(isScalar, T) && is(T : real); /// @safe unittest @@ -5775,9 +6448,9 @@ enum bool isScalarType(T) = is(T : real) && !isAggregateType!T; } /** - * Detect whether $(D T) is a basic type (scalar type or void). + * Detect whether `T` is a basic type (scalar type or void). */ -enum bool isBasicType(T) = isScalarType!T || is(Unqual!T == void); +enum bool isBasicType(T) = isScalarType!T || is(immutable T == immutable void); /// @safe unittest @@ -5798,12 +6471,18 @@ enum bool isBasicType(T) = isScalarType!T || is(Unqual!T == void); } /** - * Detect whether $(D T) is a built-in unsigned numeric type. + * Detect whether `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)); +template isUnsigned(T) +{ + static if (!__traits(isUnsigned, T)) + enum isUnsigned = false; + else static if (is(T U == enum)) + enum isUnsigned = isUnsigned!U; + else + enum isUnsigned = __traits(isZeroInit, T) // Not char, wchar, or dchar. + && !is(immutable T == immutable bool) && !is(T == __vector); +} /// @safe unittest @@ -5825,9 +6504,9 @@ enum bool isUnsigned(T) = __traits(isUnsigned, T) && !(is(Unqual!T == char) || @safe unittest { - foreach (T; AliasSeq!(UnsignedIntTypeList)) + static foreach (T; AliasSeq!(UnsignedIntTypeList)) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isUnsigned!(Q!T)); static assert(!isUnsigned!(SubTypeOf!(Q!T))); @@ -5840,12 +6519,21 @@ enum bool isUnsigned(T) = __traits(isUnsigned, T) && !(is(Unqual!T == char) || alias t this; } static assert(!isUnsigned!(S!uint)); + + enum EChar : char { a = 0, } + static assert(!isUnsigned!EChar); + + static if (is(__vector(uint[4]))) + { + static assert(!isUnsigned!(__vector(uint[4]))); + } } /** - * Detect whether $(D T) is a built-in signed numeric type. + * Detect whether `T` is a built-in signed numeric type. */ -enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T); +enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) + && is(T : real); /// @safe unittest @@ -5869,9 +6557,9 @@ enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T); enum Eubyte : ubyte { e1 = 0 } static assert(!isSigned!Eubyte); - foreach (T; AliasSeq!(SignedIntTypeList)) + static foreach (T; AliasSeq!(SignedIntTypeList)) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isSigned!(Q!T)); static assert(!isSigned!(SubTypeOf!(Q!T))); @@ -5884,6 +6572,14 @@ enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T); alias t this; } static assert(!isSigned!(S!uint)); + + static if (is(__vector(int[4]))) + { + static assert(!isSigned!(__vector(int[4]))); + } + + static assert(!isSigned!ifloat); + static assert(!isSigned!cfloat); } // https://issues.dlang.org/show_bug.cgi?id=17196 @@ -5894,12 +6590,20 @@ enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T); } /** - * Detect whether $(D T) is one of the built-in character types. + * Detect whether `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 + * The built-in char types are any of `char`, `wchar` or `dchar`, with * or without qualifiers. */ -enum bool isSomeChar(T) = is(CharTypeOf!T) && !isAggregateType!T; +template isSomeChar(T) +{ + static if (!__traits(isUnsigned, T)) + enum isSomeChar = false; + else static if (is(T U == enum)) + enum isSomeChar = isSomeChar!U; + else + enum isSomeChar = !__traits(isZeroInit, T); +} /// @safe unittest @@ -5925,9 +6629,9 @@ enum bool isSomeChar(T) = is(CharTypeOf!T) && !isAggregateType!T; { enum EC : char { a = 'x', b = 'y' } - foreach (T; AliasSeq!(CharTypeList, EC)) + static foreach (T; AliasSeq!(CharTypeList, EC)) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isSomeChar!( Q!T )); static assert(!isSomeChar!( SubTypeOf!(Q!T) )); @@ -5944,15 +6648,15 @@ enum bool isSomeChar(T) = is(CharTypeOf!T) && !isAggregateType!T; } /** -Detect whether $(D T) is one of the built-in string types. +Detect whether `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. +The built-in string types are `Char[]`, where `Char` is any of `char`, +`wchar` or `dchar`, with or without qualifiers. -Static arrays of characters (like $(D char[80])) are not considered +Static arrays of characters (like `char[80]`) are not considered built-in string types. */ -enum bool isSomeString(T) = is(StringTypeOf!T) && !isAggregateType!T && !isStaticArray!T; +enum bool isSomeString(T) = is(immutable T == immutable C[], C) && (is(C == char) || is(C == wchar) || is(C == dchar)); /// @safe unittest @@ -5964,33 +6668,42 @@ enum bool isSomeString(T) = is(StringTypeOf!T) && !isAggregateType!T && !isStati 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])); + + enum ES : string { a = "aaa", b = "bbb" } + static assert(!isSomeString!ES); + + static struct Stringish + { + string str; + alias str this; + } + static assert(!isSomeString!Stringish); } @safe unittest { - foreach (T; AliasSeq!(char[], dchar[], string, wstring, dstring)) + static foreach (T; AliasSeq!(char[], dchar[], string, wstring, dstring)) { static assert( isSomeString!( T )); static assert(!isSomeString!(SubTypeOf!(T))); } + enum C : char { _ = 0 } + static assert(!isSomeString!(C[])); } /** - * Detect whether type $(D T) is a narrow string. + * Detect whether type `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; +enum bool isNarrowString(T) = is(immutable T == immutable C[], C) && (is(C == char) || is(C == wchar)); /// @safe unittest @@ -6002,41 +6715,56 @@ enum bool isNarrowString(T) = (is(T : const char[]) || is(T : const wchar[])) && static assert(!isNarrowString!dstring); static assert(!isNarrowString!(dchar[])); + + static assert(!isNarrowString!(typeof(null))); + static assert(!isNarrowString!(char[4])); + + enum ES : string { a = "aaa", b = "bbb" } + static assert(!isNarrowString!ES); + + static struct Stringish + { + string str; + alias str this; + } + static assert(!isNarrowString!Stringish); } @safe unittest { - foreach (T; AliasSeq!(char[], string, wstring)) + import std.meta : Alias; + static foreach (T; AliasSeq!(char[], string, wstring)) { - foreach (Q; AliasSeq!(MutableOf, ConstOf, ImmutableOf)/*TypeQualifierList*/) + static foreach (Q; AliasSeq!(Alias, ConstOf, ImmutableOf)/*TypeQualifierList*/) { static assert( isNarrowString!( Q!T )); static assert(!isNarrowString!( SubTypeOf!(Q!T) )); } } - foreach (T; AliasSeq!(int, int[], byte[], dchar[], dstring, char[4])) + static foreach (T; AliasSeq!(int, int[], byte[], dchar[], dstring, char[4])) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert(!isNarrowString!( Q!T )); static assert(!isNarrowString!( SubTypeOf!(Q!T) )); } } + enum C : char { _ = 0 } + static assert(!isNarrowString!(C[])); } /** * 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"); +enum bool isOrderingComparable(T) = is(typeof((ref T a) => a < a ? 1 : 0)); /// @safe unittest { static assert(isOrderingComparable!int); static assert(isOrderingComparable!string); - static assert(!isOrderingComparable!creal); static struct Foo {} static assert(!isOrderingComparable!Foo); @@ -6053,13 +6781,12 @@ enum bool isOrderingComparable(T) = ifTestable!(T, unaryFun!"a < a"); } /// ditto -enum bool isEqualityComparable(T) = ifTestable!(T, unaryFun!"a == a"); +enum bool isEqualityComparable(T) = is(typeof((ref T a) => a == a ? 1 : 0)); @safe unittest { static assert(isEqualityComparable!int); static assert(isEqualityComparable!string); - static assert(isEqualityComparable!creal); static assert(!isEqualityComparable!void); struct Foo {} @@ -6080,8 +6807,27 @@ enum bool isEqualityComparable(T) = ifTestable!(T, unaryFun!"a == a"); } /** - * Detect whether $(D T) is a struct, static array, or enum that is implicitly - * convertible to a string. + $(RED Warning: This trait will be deprecated as soon as it is no longer used + in Phobos. For a function parameter to safely accept a type + that implicitly converts to string as a string, the conversion + needs to happen at the callsite; otherwise, the conversion is + done inside the function, and in many cases, that means that + local memory is sliced (e.g. if a static array is passed to + the function, then it's copied, and the resulting dynamic + array will be a slice of a local variable). So, if the + resulting string escapes the function, the string refers to + invalid memory, and accessing it would mean accessing invalid + memory. As such, the only safe way for a function to accept + types that implicitly convert to string is for the implicit + conversion to be done at the callsite, and that can only occur + if the parameter is explicitly typed as an array, whereas + using isConvertibleToString in a template constraint would + result in the conversion being done inside the function. As + such, isConvertibleToString is inherently unsafe and is going + to be deprecated.) + + Detect whether `T` is a struct, static array, or enum that is implicitly + convertible to a string. */ template isConvertibleToString(T) { @@ -6108,7 +6854,8 @@ template isConvertibleToString(T) assert(!isConvertibleToString!(char[])); } -@safe unittest // Bugzilla 16573 +// https://issues.dlang.org/show_bug.cgi?id=16573 +@safe unittest { enum I : int { foo = 1 } enum S : string { foo = "foo" } @@ -6125,11 +6872,21 @@ package template convertToString(T) } /** - * Detect whether type $(D T) is a string that will be autodecoded. + * Detect whether type `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. + * Given a type `S` that is one of: + * $(OL + * $(LI `const(char)[]`) + * $(LI `const(wchar)[]`) + * ) + * Type `T` can be one of: + * $(OL + * $(LI `S`) + * $(LI implicitly convertible to `T`) + * $(LI an enum with a base type `T`) + * $(LI an aggregate with a base type `T`) + * ) + * with the proviso that `T` cannot be a static array. * * Params: * T = type to be tested @@ -6140,7 +6897,13 @@ package template convertToString(T) * See Also: * $(LREF isNarrowString) */ -enum bool isAutodecodableString(T) = (is(T : const char[]) || is(T : const wchar[])) && !isStaticArray!T; +template isAutodecodableString(T) +{ + import std.range.primitives : autodecodeStrings; + + enum isAutodecodableString = autodecodeStrings && + (is(T : const char[]) || is(T : const wchar[])) && !is(T : U[n], U, size_t n); +} /// @safe unittest @@ -6150,13 +6913,34 @@ enum bool isAutodecodableString(T) = (is(T : const char[]) || is(T : const wchar string s; alias s this; } - assert(isAutodecodableString!wstring); - assert(isAutodecodableString!Stringish); - assert(!isAutodecodableString!dstring); + static assert(isAutodecodableString!wstring); + static assert(isAutodecodableString!Stringish); + static assert(!isAutodecodableString!dstring); + + enum E : const(char)[3] { X = "abc" } + enum F : const(char)[] { X = "abc" } + enum G : F { X = F.init } + + static assert(isAutodecodableString!(char[])); + static assert(!isAutodecodableString!(E)); + static assert(isAutodecodableString!(F)); + static assert(isAutodecodableString!(G)); + + struct Stringish2 + { + Stringish s; + alias s this; + } + + enum H : Stringish { X = Stringish() } + enum I : Stringish2 { X = Stringish2() } + + static assert(isAutodecodableString!(H)); + static assert(isAutodecodableString!(I)); } /** - * Detect whether type $(D T) is a static array. + * Detect whether type `T` is a static array. */ enum bool isStaticArray(T) = __traits(isStaticArray, T); @@ -6179,11 +6963,11 @@ enum bool isStaticArray(T) = __traits(isStaticArray, T); @safe unittest { - foreach (T; AliasSeq!(int[51], int[][2], + static 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 foreach (Q; TypeQualifierList) { static assert( isStaticArray!( Q!T )); static assert(!isStaticArray!( SubTypeOf!(Q!T) )); @@ -6195,9 +6979,21 @@ enum bool isStaticArray(T) = __traits(isStaticArray, T); } /** - * Detect whether type $(D T) is a dynamic array. + * Detect whether type `T` is a dynamic array. */ -enum bool isDynamicArray(T) = is(DynamicArrayTypeOf!T) && !isAggregateType!T; +template isDynamicArray(T) +{ + static if (is(T == U[], U)) + enum bool isDynamicArray = true; + else static if (is(T U == enum)) + // BUG: isDynamicArray / isStaticArray considers enums + // with appropriate base types as dynamic/static arrays + // Retain old behaviour for now, see + // https://github.com/dlang/phobos/pull/7574 + enum bool isDynamicArray = isDynamicArray!U; + else + enum bool isDynamicArray = false; +} /// @safe unittest @@ -6213,18 +7009,36 @@ enum bool isDynamicArray(T) = is(DynamicArrayTypeOf!T) && !isAggregateType!T; @safe unittest { import std.meta : AliasSeq; - foreach (T; AliasSeq!(int[], char[], string, long[3][], double[string][])) + static foreach (T; AliasSeq!(int[], char[], string, long[3][], double[string][])) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isDynamicArray!( Q!T )); static assert(!isDynamicArray!( SubTypeOf!(Q!T) )); } } + + static assert(!isDynamicArray!(int[5])); + + static struct AliasThis + { + int[] values; + alias values this; + } + + static assert(!isDynamicArray!AliasThis); + + // https://github.com/dlang/phobos/pull/7574/files#r464115492 + enum E : string + { + a = "a", + b = "b", + } + static assert( isDynamicArray!E); } /** - * Detect whether type $(D T) is an array (static or dynamic; for associative + * Detect whether type `T` is an array (static or dynamic; for associative * arrays see $(LREF isAssociativeArray)). */ enum bool isArray(T) = isStaticArray!T || isDynamicArray!T; @@ -6244,9 +7058,9 @@ enum bool isArray(T) = isStaticArray!T || isDynamicArray!T; @safe unittest { import std.meta : AliasSeq; - foreach (T; AliasSeq!(int[], int[5], void[])) + static foreach (T; AliasSeq!(int[], int[5], void[])) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isArray!(Q!T)); static assert(!isArray!(SubTypeOf!(Q!T))); @@ -6255,7 +7069,7 @@ enum bool isArray(T) = isStaticArray!T || isDynamicArray!T; } /** - * Detect whether $(D T) is an associative array type + * Detect whether `T` is an associative array type */ enum bool isAssociativeArray(T) = __traits(isAssociativeArray, T); @@ -6267,9 +7081,9 @@ enum bool isAssociativeArray(T) = __traits(isAssociativeArray, T); @property uint[] values() { return null; } } - foreach (T; AliasSeq!(int[int], int[string], immutable(char[5])[int])) + static foreach (T; AliasSeq!(int[int], int[string], immutable(char[5])[int])) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isAssociativeArray!(Q!T)); static assert(!isAssociativeArray!(SubTypeOf!(Q!T))); @@ -6286,7 +7100,7 @@ enum bool isAssociativeArray(T) = __traits(isAssociativeArray, T); } /** - * Detect whether type $(D T) is a builtin type. + * Detect whether type `T` is a builtin type. */ enum bool isBuiltinType(T) = is(BuiltinTypeOf!T) && !isAggregateType!T; @@ -6310,7 +7124,7 @@ enum bool isBuiltinType(T) = is(BuiltinTypeOf!T) && !isAggregateType!T; } /** - * Detect whether type $(D T) is a SIMD vector type. + * Detect whether type `T` is a SIMD vector type. */ enum bool isSIMDVector(T) = is(T : __vector(V[N]), V, size_t N); @@ -6327,15 +7141,15 @@ enum bool isSIMDVector(T) = is(T : __vector(V[N]), V, size_t N); } /** - * Detect whether type $(D T) is a pointer. + * Detect whether type `T` is a pointer. */ -enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T; +enum bool isPointer(T) = is(T == U*, U) && __traits(isScalar, T); @safe unittest { - foreach (T; AliasSeq!(int*, void*, char[]*)) + static foreach (T; AliasSeq!(int*, void*, char[]*)) { - foreach (Q; TypeQualifierList) + static foreach (Q; TypeQualifierList) { static assert( isPointer!(Q!T)); static assert(!isPointer!(SubTypeOf!(Q!T))); @@ -6361,7 +7175,7 @@ alias PointerTarget(T : T*) = T; } /** - * Detect whether type $(D T) is an aggregate type. + * Detect whether type `T` is an aggregate type. */ enum bool isAggregateType(T) = is(T == struct) || is(T == union) || is(T == class) || is(T == interface); @@ -6386,10 +7200,10 @@ enum bool isAggregateType(T) = is(T == struct) || is(T == union) || } /** - * Returns $(D true) if T can be iterated over using a $(D foreach) loop with + * Returns `true` if T can be iterated over using a `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, + * the `foreach` loop is implemented. This includes ranges, structs/classes + * that define `opApply` with a single loop variable, and builtin dynamic, * static and associative arrays. */ enum bool isIterable(T) = is(typeof({ foreach (elem; T.init) {} })); @@ -6470,6 +7284,30 @@ template isInstanceOf(alias S, alias T) static assert(isInstanceOf!(templ, templ!int)); } +/** + * To use `isInstanceOf` to check the identity of a template while inside of said + * template, use $(LREF TemplateOf). + */ +@safe unittest +{ + static struct A(T = void) + { + // doesn't work as expected, only accepts A when T = void + void func(B)(B b) if (isInstanceOf!(A, B)) {} + + // correct behavior + void method(B)(B b) if (isInstanceOf!(TemplateOf!(A), B)) {} + } + + A!(void) a1; + A!(void) a2; + A!(int) a3; + + static assert(!__traits(compiles, a1.func(a3))); + static assert( __traits(compiles, a1.method(a2))); + static assert( __traits(compiles, a1.method(a3))); +} + @safe unittest { static void fun1(T)() { } @@ -6487,17 +7325,20 @@ template isInstanceOf(alias S, alias T) * * See_Also: $(LREF isTypeTuple). */ -template isExpressions(T ...) +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 + static foreach (Ti; T) + { + static if (!is(typeof(isExpressions) == bool) && // not yet defined + (is(Ti) || !__traits(compiles, { auto ex = Ti; }))) + { + enum isExpressions = false; + } + } + static if (!is(typeof(isExpressions) == bool)) // if not yet defined + { + enum isExpressions = true; + } } /// @@ -6535,7 +7376,7 @@ alias isExpressionTuple = isExpressions; /** - * Check whether the tuple $(D T) is a type tuple. + * Check whether the tuple `T` is a type tuple. * A type tuple only contains types. * * See_Also: $(LREF isExpressions). @@ -6578,24 +7419,12 @@ template isTypeTuple(T...) /** -Detect whether symbol or type $(D T) is a function pointer. +Detect whether symbol or type `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 +enum bool isFunctionPointer(alias T) = is(typeof(*T) == function); + +/// +@safe unittest { static void foo() {} void bar() {} @@ -6614,24 +7443,9 @@ template isFunctionPointer(T...) } /** -Detect whether symbol or type $(D T) is a delegate. +Detect whether symbol or type `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; -} +enum bool isDelegate(alias T) = is(typeof(T) == delegate) || is(T == delegate); /// @safe unittest @@ -6652,10 +7466,15 @@ template isDelegate(T...) } /** -Detect whether symbol or type $(D T) is a function, a function pointer or a delegate. +Detect whether symbol or type `T` is a function, a function pointer or a delegate. + +Params: + T = The type to check +Returns: + A `bool` */ template isSomeFunction(T...) - if (T.length == 1) +if (T.length == 1) { static if (is(typeof(& T[0]) U : U*) && is(U == function) || is(typeof(& T[0]) U == delegate)) { @@ -6674,12 +7493,11 @@ template isSomeFunction(T...) 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; } @@ -6692,69 +7510,126 @@ template isSomeFunction(T...) 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); } +@safe unittest +{ + void nestedFunc() { } + void nestedProp() @property { } + static assert(isSomeFunction!nestedFunc); + static assert(isSomeFunction!nestedProp); + static assert(isSomeFunction!(real function(ref int))); + static assert(isSomeFunction!(real delegate(ref int))); + static assert(isSomeFunction!((int a) { return a; })); + static assert(!isSomeFunction!isSomeFunction); +} /** -Detect whether $(D T) is a callable object, which can be called with the -function call operator $(D $(LPAREN)...$(RPAREN)). +Detect whether `T` is a callable object, which can be called with the +function call operator `$(LPAREN)...$(RPAREN)`. */ -template isCallable(T...) - if (T.length == 1) +template isCallable(alias callable) { - static if (is(typeof(& T[0].opCall) == delegate)) + static if (is(typeof(&callable.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)) + else static if (is(typeof(&callable.opCall) V : V*) && is(V == function)) // T is a type which has a static member function opCall(). enum bool isCallable = true; + else static if (is(typeof(&callable.opCall!()))) + { + alias TemplateInstanceType = typeof(&callable.opCall!()); + enum bool isCallable = isCallable!TemplateInstanceType; + } + else static if (is(typeof(&callable!()))) + { + alias TemplateInstanceType = typeof(&callable!()); + enum bool isCallable = isCallable!TemplateInstanceType; + } else - enum bool isCallable = isSomeFunction!T; + { + enum bool isCallable = isSomeFunction!callable; + } } -/// +/// Functions, lambdas, and aggregate types with (static) opCall. @safe unittest { - interface I { real value() @property; } - struct S { static int opCall(int) { return 0; } } + void f() { } + int g(int x) { return x; } + + static assert( isCallable!f); + static assert( isCallable!g); + class C { int opCall(int) { return 0; } } auto c = new C; + struct S { static int opCall(int) { return 0; } } + interface I { real value() @property; } static assert( isCallable!c); - static assert( isCallable!S); static assert( isCallable!(c.opCall)); + static assert( isCallable!S); static assert( isCallable!(I.value)); static assert( isCallable!((int a) { return a; })); static assert(!isCallable!I); } +/// Templates +@safe unittest +{ + void f()() { } + T g(T = int)(T x) { return x; } + struct S1 { static void opCall()() { } } + struct S2 { static T opCall(T = int)(T x) {return x; } } + + static assert( isCallable!f); + static assert( isCallable!g); + static assert( isCallable!S1); + static assert( isCallable!S2); +} + +/// Overloaded functions and function templates. +@safe unittest +{ + static struct Wrapper + { + void f() { } + int f(int x) { return x; } + + void g()() { } + T g(T = int)(T x) { return x; } + } + + static assert(isCallable!(Wrapper.f)); + static assert(isCallable!(Wrapper.g)); +} + /** - * Detect whether $(D T) is an abstract function. +Detect whether `T` is an abstract function. + +Params: + T = The type to check +Returns: + A `bool` */ template isAbstractFunction(T...) - if (T.length == 1) +if (T.length == 1) { enum bool isAbstractFunction = __traits(isAbstractFunction, T[0]); } +/// @safe unittest { struct S { void foo() { } } @@ -6767,10 +7642,10 @@ template isAbstractFunction(T...) } /** - * Detect whether $(D T) is a final function. + * Detect whether `T` is a final function. */ template isFinalFunction(T...) - if (T.length == 1) +if (T.length == 1) { enum bool isFinalFunction = __traits(isFinalFunction, T[0]); } @@ -6793,26 +7668,56 @@ template isFinalFunction(T...) } /** -Determines whether function $(D f) requires a context pointer. +Determines if `f` is a function that requires a context pointer. + +Params: + f = The type to check +Returns + A `bool` */ template isNestedFunction(alias f) { - enum isNestedFunction = __traits(isNested, f); + enum isNestedFunction = __traits(isNested, f) && isSomeFunction!(f); } +/// @safe unittest { - static void f() { } - void g() { } + static void f() {} + static void fun() + { + int i; + int f() { return i; } + + static assert(isNestedFunction!(f)); + } + static assert(!isNestedFunction!f); - static assert( isNestedFunction!g); +} + +// https://issues.dlang.org/show_bug.cgi?id=18669 +@safe unittest +{ + static class Outer + { + class Inner + { + } + } + int i; + struct SS + { + int bar() { return i; } + } + static assert(!isNestedFunction!(Outer.Inner)); + static assert(!isNestedFunction!(SS)); } /** - * Detect whether $(D T) is an abstract class. + * Detect whether `T` is an abstract class. */ template isAbstractClass(T...) - if (T.length == 1) +if (T.length == 1) { enum bool isAbstractClass = __traits(isAbstractClass, T[0]); } @@ -6833,10 +7738,10 @@ template isAbstractClass(T...) } /** - * Detect whether $(D T) is a final class. + * Detect whether `T` is a final class. */ template isFinalClass(T...) - if (T.length == 1) +if (T.length == 1) { enum bool isFinalClass = __traits(isFinalClass, T[0]); } @@ -6863,30 +7768,47 @@ template isFinalClass(T...) //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// /** -Removes all qualifiers, if any, from type $(D T). +Removes `const`, `inout` and `immutable` qualifiers, if any, from type `T`. + */ +template Unconst(T) +{ + import core.internal.traits : CoreUnconst = Unconst; + alias Unconst = CoreUnconst!(T); +} + +/// +@safe unittest +{ + static assert(is(Unconst!int == int)); + static assert(is(Unconst!(const int) == int)); + static assert(is(Unconst!(immutable int) == int)); + static assert(is(Unconst!(shared int) == shared int)); + static assert(is(Unconst!(shared(const int)) == shared int)); +} + +@safe unittest +{ + static assert(is(Unconst!( int) == int)); + static assert(is(Unconst!( const int) == int)); + static assert(is(Unconst!( inout int) == int)); + static assert(is(Unconst!( inout const int) == int)); + static assert(is(Unconst!(shared int) == shared int)); + static assert(is(Unconst!(shared const int) == shared int)); + static assert(is(Unconst!(shared inout int) == shared int)); + static assert(is(Unconst!(shared inout const int) == shared int)); + static assert(is(Unconst!( immutable int) == int)); + + alias ImmIntArr = immutable(int[]); + static assert(is(Unconst!ImmIntArr == immutable(int)[])); +} + +/** +Removes all qualifiers, if any, from type `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; - } + import core.internal.traits : CoreUnqual = Unqual; + alias Unqual = CoreUnqual!(T); } /// @@ -6944,14 +7866,14 @@ package template ModifyTypePreservingTQ(alias Modifier, T) } /** - * Copies type qualifiers from $(D FromType) to $(D ToType). + * Copies type qualifiers from `FromType` to `ToType`. * * Supported type qualifiers: * $(UL - * $(LI $(D const)) - * $(LI $(D inout)) - * $(LI $(D immutable)) - * $(LI $(D shared)) + * $(LI `const`) + * $(LI `inout`) + * $(LI `immutable`) + * $(LI `shared`) * ) */ template CopyTypeQualifiers(FromType, ToType) @@ -6980,9 +7902,9 @@ template CopyTypeQualifiers(FromType, ToType) } /** -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`. +Returns the type of `ToType` with the "constness" of `FromType`. A type's $(B constness) +refers to whether it is `const`, `immutable`, or `inout`. If `FromType` has no constness, the +returned type will be the same as `ToType`. */ template CopyConstness(FromType, ToType) { @@ -7054,9 +7976,9 @@ template CopyConstness(FromType, ToType) /** 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 +is iterated over using a `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 +`std.range.ElementType!Range` in the case of narrow strings, or if T has both opApply and a range interface. */ template ForeachType(T) @@ -7083,23 +8005,30 @@ template ForeachType(T) /** - * Strips off all $(D enum)s from type $(D T). + * Strips off all `enum`s from type `T`. */ template OriginalType(T) { - template Impl(T) + static if (is(T == enum)) { - static if (is(T U == enum)) alias Impl = OriginalType!U; - else alias Impl = T; - } + template Impl(T) + { + static if (is(T U == enum)) alias Impl = OriginalType!U; + else alias Impl = T; + } - alias OriginalType = ModifyTypePreservingTQ!(Impl, T); + alias OriginalType = ModifyTypePreservingTQ!(Impl, T); + } + else + { + alias OriginalType = T; + } } /// @safe unittest { - enum E : real { a } + enum E : real { a = 0 } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle enum F : E { a = E.a } alias G = const(F); static assert(is(OriginalType!E == real)); @@ -7115,7 +8044,6 @@ 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)); @@ -7131,7 +8059,6 @@ 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)); @@ -7140,8 +8067,14 @@ alias ValueType(V : V[K], K) = V; } /** - * Returns the corresponding unsigned type for T. T must be a numeric - * integral type, otherwise a compile-time error occurs. +Params: + T = A built in integral or vector type. + +Returns: + The corresponding unsigned numeric type for `T` with the + same type qualifiers. + + If `T` is not a integral or vector, a compile-time error is given. */ template Unsigned(T) { @@ -7167,6 +8100,27 @@ template Unsigned(T) alias Unsigned = ModifyTypePreservingTQ!(Impl, OriginalType!T); } +/// +@safe unittest +{ + static assert(is(Unsigned!(int) == uint)); + static assert(is(Unsigned!(long) == ulong)); + static assert(is(Unsigned!(const short) == const ushort)); + static assert(is(Unsigned!(immutable byte) == immutable ubyte)); + static assert(is(Unsigned!(inout int) == inout uint)); +} + + +/// Unsigned types are forwarded +@safe unittest +{ + static assert(is(Unsigned!(uint) == uint)); + static assert(is(Unsigned!(const uint) == const uint)); + + static assert(is(Unsigned!(ubyte) == ubyte)); + static assert(is(Unsigned!(immutable uint) == immutable uint)); +} + @safe unittest { alias U1 = Unsigned!int; @@ -7201,7 +8155,8 @@ 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) +template Largest(T...) +if (T.length >= 1) { static if (T.length == 1) { @@ -7296,7 +8251,7 @@ template Signed(T) Returns the most negative value of the numeric type T. */ template mostNegative(T) - if (isNumeric!T || isSomeChar!T || isBoolean!T) +if (isNumeric!T || isSomeChar!T || isBoolean!T) { static if (is(typeof(T.min_normal))) enum mostNegative = -T.max; @@ -7318,10 +8273,12 @@ template mostNegative(T) /// @safe unittest { - foreach (T; AliasSeq!(bool, byte, short, int, long)) + import std.meta : AliasSeq; + + static 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 foreach (T; AliasSeq!(ubyte, ushort, uint, ulong, char, wchar, dchar)) static assert(mostNegative!T == 0); } @@ -7330,7 +8287,7 @@ Get the type that a scalar type `T` will $(LINK2 $(ROOT_DIR)spec/type.html#integ to in multi-term arithmetic expressions. */ template Promoted(T) - if (isScalarType!T) +if (isScalarType!T) { alias Promoted = CopyTypeQualifiers!(T, typeof(T.init + T.init)); } @@ -7350,14 +8307,14 @@ template Promoted(T) @safe unittest { // promote to int: - foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, char, wchar)) + static 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 foreach (T; AliasSeq!(int, uint, long, ulong, float, double, real)) { static assert(is(Promoted!T == T)); static assert(is(Promoted!(immutable(T)) == immutable(T))); @@ -7369,14 +8326,14 @@ template Promoted(T) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// /** -Returns the mangled name of symbol or type $(D sth). +Returns the mangled name of symbol or type `sth`. -$(D mangledName) is the same as builtin $(D .mangleof) property, but +`mangledName` is the same as builtin `.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) +if (sth.length == 1) { enum string mangledName = sth[0].mangleof; } @@ -7384,11 +8341,12 @@ template mangledName(sth...) /// @safe unittest { + import std.meta : AliasSeq; alias TL = staticMap!(mangledName, int, const int, immutable int); static assert(TL == AliasSeq!("i", "xi", "yi")); } -version (unittest) void freeFunc(string); +version (StdUnittest) private void freeFunc(string); @safe unittest { @@ -7400,7 +8358,8 @@ version (unittest) void freeFunc(string); static assert(mangledName!mangledName == "3std6traits11mangledName"); static assert(mangledName!freeFunc == "_D3std6traits8freeFuncFAyaZv"); int x; - static if (is(typeof({ return x; }) : int delegate() pure)) // issue 9148 + // https://issues.dlang.org/show_bug.cgi?id=9148 + static if (is(typeof({ return x; }) : int delegate() pure)) 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 @@ -7409,7 +8368,7 @@ version (unittest) void freeFunc(string); @system unittest { // @system due to demangle - // Test for bug 5718 + // Test for https://issues.dlang.org/show_bug.cgi?id=5718 import std.demangle : demangle; int foo; auto foo_demangled = demangle(mangledName!foo); @@ -7426,10 +8385,11 @@ version (unittest) void freeFunc(string); // 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. +Aliases itself to `T[0]` if the boolean `condition` is `true` +and to `T[1]` otherwise. */ -template Select(bool condition, T...) if (T.length == 2) +template Select(bool condition, T...) +if (T.length == 2) { import std.meta : Alias; alias Select = Alias!(T[!condition]); @@ -7458,19 +8418,28 @@ template Select(bool condition, T...) if (T.length == 2) } /** -If $(D cond) is $(D true), returns $(D a) without evaluating $(D -b). Otherwise, returns $(D b) without evaluating $(D a). +Select one of two functions to run via template parameter. + +Params: + cond = A `bool` which determines which function is run + a = The first function + b = The second function + +Returns: + `a` without evaluating `b` if `cond` is `true`. + Otherwise, returns `b` without evaluating `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()); + real run() { return 0; } + int fail() { assert(0); } + auto a = select!true(run(), fail()); + auto b = select!false(fail(), run()); static assert(is(typeof(a) == real)); static assert(is(typeof(b) == real)); } @@ -7591,28 +8560,7 @@ 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)); + alias getUDAs = Filter!(isDesiredUDA!attribute, __traits(getAttributes, symbol)); } /// @@ -7717,37 +8665,46 @@ template getUDAs(alias symbol, alias attribute) 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) +private template isDesiredUDA(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...) + template isDesiredUDA(alias toCheck) { - static if (names.length == 0) - alias toSymbols = AliasSeq!(); + 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 - mixin("alias toSymbols = AliasSeq!(symbol.%s, toSymbols!(names[1..$]));" - .format(names[0])); + enum isDesiredUDA = is(toCheck == attribute); } +} - // filtering inaccessible members - enum isAccessibleMember(string name) = __traits(compiles, __traits(getMember, symbol, name)); - alias accessibleMembers = Filter!(isAccessibleMember, __traits(allMembers, symbol)); +/** +Params: + symbol = The aggregate type or module to search + attribute = The user-defined attribute to search for - // 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)); +Returns: + All symbols within `symbol` that have the given UDA `attribute`. - alias correctMembers = Filter!(isCorrectMember, accessibleMembers); - alias membersWithUDA = toSymbols!(Filter!(hasSpecificUDA, correctMembers)); +Note: + This is not recursive; it will not search for symbols within symbols such as + nested structs or unions. + */ +template getSymbolsByUDA(alias symbol, alias attribute) +{ + alias membersWithUDA = getSymbolsByUDAImpl!(symbol, attribute, __traits(allMembers, symbol)); // if the symbol itself has the UDA, tack it on to the front of the list static if (hasUDA!(symbol, attribute)) @@ -7760,6 +8717,20 @@ template getSymbolsByUDA(alias symbol, alias attribute) @safe unittest { enum Attr; + struct A + { + @Attr int a; + int b; + } + + static assert(getSymbolsByUDA!(A, Attr).length == 1); + static assert(hasUDA!(getSymbolsByUDA!(A, Attr)[0], Attr)); +} + +/// +@safe unittest +{ + enum Attr; static struct A { @@ -7780,7 +8751,11 @@ template getSymbolsByUDA(alias symbol, alias attribute) // 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)); +} +/// Finds multiple attributes +@safe unittest +{ static struct UDA { string name; } static struct B @@ -7799,6 +8774,12 @@ template getSymbolsByUDA(alias symbol, alias 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"); +} + +/// Checks for UDAs on the aggregate symbol itself +@safe unittest +{ + static struct UDA { string name; } @UDA("A") static struct C @@ -7807,77 +8788,175 @@ template getSymbolsByUDA(alias symbol, alias attribute) 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"); +} + +/// Finds nothing if there is no member with specific UDA +@safe unittest +{ + static struct UDA { string name; } static struct D { int x; } - //Finds nothing if there is no member with specific UDA - static assert(getSymbolsByUDA!(D,UDA).length == 0); + static assert(getSymbolsByUDA!(D, UDA).length == 0); +} + +// https://issues.dlang.org/show_bug.cgi?id=18314 +@safe unittest +{ + enum attr1; + enum attr2; + + struct A + { + @attr1 + int n; + // Removed due to https://issues.dlang.org/show_bug.cgi?id=16206 + //@attr1 + //void foo()(string){} + @attr1 + void foo(); + @attr2 + void foo(int a); + } + + static assert(getSymbolsByUDA!(A, attr1).length == 2); + static assert(getSymbolsByUDA!(A, attr2).length == 1); } -// #15335: getSymbolsByUDA fails if type has private members +// getSymbolsByUDA fails if type has private members +// https://issues.dlang.org/show_bug.cgi?id=15335 @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); + // This line is commented because `__traits(getMember)` should also consider + // private members; this is not currently the case, but the PR that + // fixes `__traits(getMember)` is blocked by this specific test. + //static assert(getSymbolsByUDA!(HasPrivateMembers, Attr).length == 1); static assert(hasUDA!(getSymbolsByUDA!(HasPrivateMembers, Attr)[0], Attr)); } -/// +// getSymbolsByUDA works with structs but fails with classes +// https://issues.dlang.org/show_bug.cgi?id=16387 @safe unittest { enum Attr; - struct A + class A { - alias int INT; - alias void function(INT) SomeFunction; - @Attr int a; - int b; - @Attr private int c; - private int d; + @Attr uint a; } - // 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)); + alias res = getSymbolsByUDA!(A, Attr); + static assert(res.length == 1); + static assert(res[0].stringof == "a"); } -// #16387: getSymbolsByUDA works with structs but fails with classes +// getSymbolsByUDA fails on AliasSeq members +// https://issues.dlang.org/show_bug.cgi?id=18884 +@safe unittest +{ + struct X + { + alias A = AliasSeq!(ulong, uint); + } + + static assert(is(getSymbolsByUDA!(X, X) == AliasSeq!())); +} + +// getSymbolsByUDA produces wrong result if one of the symbols having the UDA is a function +// https://issues.dlang.org/show_bug.cgi?id=18624 @safe unittest { enum Attr; - class A + struct A { - @Attr uint a; + @Attr void a(); + @Attr void a(int n); + void b(); + @Attr void c(); } - alias res = getSymbolsByUDA!(A, Attr); - static assert(res.length == 1); - static assert(res[0].stringof == "a"); + static assert(getSymbolsByUDA!(A, Attr).stringof == "tuple(a, a, c)"); +} + +// getSymbolsByUDA no longer works on modules +// https://issues.dlang.org/show_bug.cgi?id=20054 +version (StdUnittest) +{ + @("Issue20054") + void issue20054() {} + static assert(__traits(compiles, getSymbolsByUDA!(mixin(__MODULE__), "Issue20054"))); +} + +private template getSymbolsByUDAImpl(alias symbol, alias attribute, names...) +{ + import std.meta : Alias, AliasSeq, Filter; + static if (names.length == 0) + { + alias getSymbolsByUDAImpl = AliasSeq!(); + } + else + { + alias tail = getSymbolsByUDAImpl!(symbol, attribute, names[1 .. $]); + + // Filtering inaccessible members. + static if (!__traits(compiles, __traits(getMember, symbol, names[0]))) + { + alias getSymbolsByUDAImpl = tail; + } + else + { + alias member = __traits(getMember, symbol, names[0]); + + // Filtering not compiled members such as alias of basic types. + static if (!__traits(compiles, hasUDA!(member, attribute))) + { + alias getSymbolsByUDAImpl = tail; + } + // Get overloads for functions, in case different overloads have different sets of UDAs. + else static if (isFunction!member) + { + enum hasSpecificUDA(alias member) = hasUDA!(member, attribute); + alias overloadsWithUDA = Filter!(hasSpecificUDA, __traits(getOverloads, symbol, names[0])); + alias getSymbolsByUDAImpl = AliasSeq!(overloadsWithUDA, tail); + } + else static if (hasUDA!(member, attribute)) + { + alias getSymbolsByUDAImpl = AliasSeq!(member, tail); + } + else + { + alias getSymbolsByUDAImpl = tail; + } + } + } } /** - Returns: $(D true) iff all types $(D T) are the same. + Returns: `true` iff all types `T` are the same. */ template allSameType(T...) { - static if (T.length <= 1) + static foreach (idx, Ti; T) { - enum bool allSameType = true; + static if (idx + 1 < T.length && + !is(typeof(allSameType) == bool) && + !is(T[idx] == T[idx + 1])) + { + enum bool allSameType = false; + } } - else + static if (!is(typeof(allSameType) == bool)) { - enum bool allSameType = is(T[0] == T[1]) && allSameType!(T[1..$]); + enum bool allSameType = true; } } @@ -7894,7 +8973,7 @@ template allSameType(T...) } /** - Returns: $(D true) iff the type $(D T) can be tested in an $(D + Returns: `true` iff the type `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)) {} }); @@ -7914,7 +8993,8 @@ enum ifTestable(T, alias pred = a => a) = __traits(compiles, { if (pred(T.init)) * Returns: * `true` if `X` is a type, `false` otherwise */ -template isType(X...) if (X.length == 1) +template isType(X...) +if (X.length == 1) { enum isType = is(X[0]); } @@ -7957,7 +9037,8 @@ template isType(X...) if (X.length == 1) * Use $(LREF isFunctionPointer) or $(LREF isDelegate) for detecting those types * respectively. */ -template isFunction(X...) if (X.length == 1) +template isFunction(X...) +if (X.length == 1) { static if (is(typeof(&X[0]) U : U*) && is(U == function) || is(typeof(&X[0]) U == delegate)) @@ -7993,7 +9074,8 @@ template isFunction(X...) if (X.length == 1) * Returns: * `true` if `X` is final, `false` otherwise */ -template isFinal(X...) if (X.length == 1) +template isFinal(X...) +if (X.length == 1) { static if (is(X[0] == class)) enum isFinal = __traits(isFinalClass, X[0]); @@ -8033,16 +9115,14 @@ template isFinal(X...) if (X.length == 1) + Returns: + `true` if `S` can be copied. `false` otherwise. + ++/ -enum isCopyable(S) = is(typeof( - { S foo = S.init; S copy = foo; } -)); +enum isCopyable(S) = __traits(isCopyable, S); /// @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 S3 {@disable this(this); } // Not fine. Copying is disabled. struct S4 {S3 s;} // Not fine. A field has copying disabled. class C1 {} diff --git a/libphobos/src/std/typecons.d b/libphobos/src/std/typecons.d index 84e876f..feedf90 100644 --- a/libphobos/src/std/typecons.d +++ b/libphobos/src/std/typecons.d @@ -5,8 +5,9 @@ This module implements a variety of type constructors, i.e., templates that allow construction of new, useful general-purpose types. $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE, -$(TR $(TH Category) $(TH Functions)) +$(TR $(TH Category) $(TH Symbols)) $(TR $(TD Tuple) $(TD $(LREF isTuple) $(LREF Tuple) @@ -55,11 +56,11 @@ $(TR $(TD Types) $(TD $(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) +Source: $(PHOBOSSRC std/typecons.d) Authors: $(HTTP erdani.org, Andrei Alexandrescu), $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), Don Clugston, @@ -68,9 +69,12 @@ Authors: $(HTTP erdani.org, Andrei Alexandrescu), */ module std.typecons; -import core.stdc.stdint : uintptr_t; -import std.meta; // : AliasSeq, allSatisfy; +import std.format.spec : singleSpec, FormatSpec; +import std.format.write : formatValue; +import std.meta : AliasSeq, allSatisfy; +import std.range.primitives : isOutputRange; import std.traits; +import std.internal.attributes : betterC; /// @safe unittest @@ -104,33 +108,31 @@ import std.traits; } } -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 +When a `Unique!T` goes out of scope it will call `destroy` +on the resource `T` that it manages, unless it is transferred. +One important consequence of `destroy` is that it will call the +destructor of the resource `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 +`T`, such as file handles or pointers to `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. +`T` to deallocate or clean up any non-GC resources. -If it is desirable to persist a $(D Unique!T) outside of its original +If it is desirable to persist a `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 +calling `release`, or implicit, when returning Unique from a +function. The resource `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). +If `T` is a value type, then `Unique!T` will be implemented +as a reference to a `T`. */ struct Unique(T) { -/** Represents a reference to $(D T). Resolves to $(D T*) if $(D T) is a value type. */ +/** Represents a reference to `T`. Resolves to `T*` if `T` is a value type. */ static if (is(T == class) || is(T == interface)) alias RefT = T; else @@ -140,12 +142,12 @@ 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)). + Allows safe construction of `Unique`. It creates the resource and + guarantees unique ownership of it (unless `T` publishes aliases of + `this`). Note: Nested structs/classes cannot be created. Params: - args = Arguments to pass to $(D T)'s constructor. + args = Arguments to pass to `T`'s constructor. --- static class C {} auto u = Unique!(C).create(); @@ -154,7 +156,6 @@ public: 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; @@ -171,7 +172,6 @@ public: */ this(RefT p) { - debug(Unique) writeln("Unique constructor with rvalue"); _p = p; } /** @@ -182,15 +182,14 @@ public: 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. + Constructor that takes a `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. + Typically used to transfer a `Unique` rvalue of derived type to + a `Unique` of base type. Example: --- class C : Object {} @@ -202,16 +201,14 @@ public: 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. + /// Transfer ownership from a `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; @@ -220,7 +217,6 @@ public: ~this() { - debug(Unique) writeln("Unique destructor of ", (_p is null)? null: _p); if (_p !is null) { destroy(_p); @@ -233,12 +229,11 @@ public: { return _p is null; } - /** Transfer ownership to a $(D Unique) rvalue. Nullifies the current contents. + /** Transfer ownership to a `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; } @@ -247,7 +242,7 @@ public: mixin Proxy!_p; /** - Postblit operator is undefined to prevent the cloning of $(D Unique) objects. + Postblit operator is undefined to prevent the cloning of `Unique` objects. */ @disable this(this); @@ -256,7 +251,7 @@ private: } /// -@system unittest +@safe unittest { static struct S { @@ -318,7 +313,6 @@ private: @system unittest { - debug(Unique) writeln("Unique class"); class Bar { ~this() { debug(Unique) writeln(" Bar destructor"); } @@ -334,16 +328,13 @@ private: 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; @@ -377,21 +368,18 @@ private: 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 +@safe unittest { - debug(Unique) writeln("Unique struct"); struct Foo { - ~this() { debug(Unique) writeln(" Foo destructor"); } + ~this() { } int val() const { return 3; } @disable this(this); } @@ -399,7 +387,6 @@ private: UFoo f(UFoo u) { - debug(Unique) writeln("inside f"); return u.release; } @@ -407,9 +394,7 @@ private: 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); } @@ -436,36 +421,48 @@ private: // Used in Tuple.toString private template sharedToString(alias field) - if (is(typeof(field) == shared)) +if (is(typeof(field) == shared)) { static immutable sharedToString = typeof(field).stringof; } private template sharedToString(alias field) - if (!is(typeof(field) == shared)) +if (!is(typeof(field) == shared)) { alias sharedToString = field; } +private enum bool distinctFieldNames(names...) = __traits(compiles, +{ + static foreach (__name; names) + static if (is(typeof(__name) : string)) + mixin("enum int " ~ __name ~ " = 0;"); +}); + +@safe unittest +{ + static assert(!distinctFieldNames!(string, "abc", string, "abc")); + static assert(distinctFieldNames!(string, "abc", int, "abd")); + static assert(!distinctFieldNames!(int, "abc", string, "abd", int, "abc")); + // https://issues.dlang.org/show_bug.cgi?id=19240 + static assert(!distinctFieldNames!(int, "int")); +} + /** _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 +stores an `int` and a `string`. `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]) +function. If `obj` is a `Tuple`, the individual members are +accessible with the syntax `obj[0]` for the first field, `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...) +if (distinctFieldNames!(Specs)) { import std.meta : staticMap; @@ -517,21 +514,20 @@ template Tuple(Specs...) // : // NOTE: field[k] is an expression (which yields a symbol of a // variable) and can't be aliased directly. - string injectNamedFields() + enum 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) + static foreach (i, val; fieldSpecs) + {{ + immutable si = i.stringof; + decl ~= "alias _" ~ si ~ " = Identity!(field[" ~ si ~ "]);"; + if (val.name.length != 0) { - decl ~= format("alias %s = _%s;", name, i); + decl ~= "alias " ~ val.name ~ " = _" ~ si ~ ";"; } - } + }} return decl; - } + }; // Returns Specs for a subtuple this[from .. to] preserving field // names if any. @@ -550,35 +546,35 @@ template Tuple(Specs...) } } - enum areCompatibleTuples(Tup1, Tup2, string op) = isTuple!Tup2 && is(typeof( + enum areCompatibleTuples(Tup1, Tup2, string op) = isTuple!(OriginalType!Tup2) && is(typeof( (ref Tup1 tup1, ref Tup2 tup2) { static assert(tup1.field.length == tup2.field.length); - foreach (i, _; Tup1.Types) - { + static foreach (i; 0 .. Tup1.Types.length) + {{ 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 foreach (i; 0 .. Tup1.Types.length) static assert(isBuildable!(Tup1.Types[i], Tup2.Types[i])); })); - /+ Returns $(D true) iff a $(D T) can be initialized from a $(D U). +/ + /+ Returns `true` iff a `T` can be initialized from a `U`. +/ enum isBuildable(T, U) = is(typeof( { U u = U.init; T t = u; })); - /+ Helper for partial instanciation +/ + /+ Helper for partial instantiation +/ template isBuildableFrom(U) { enum isBuildableFrom(T) = isBuildable!(T, U); @@ -591,9 +587,12 @@ template Tuple(Specs...) */ alias Types = staticMap!(extractType, fieldSpecs); + private alias _Fields = Specs; + /// static if (Specs.length == 0) @safe unittest { + import std.meta : AliasSeq; alias Fields = Tuple!(int, "id", string, float); static assert(is(Fields.Types == AliasSeq!(int, string, float))); } @@ -606,14 +605,15 @@ template Tuple(Specs...) /// static if (Specs.length == 0) @safe unittest { + import std.meta : AliasSeq; 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 + * Use `t.expand` for a `Tuple` `t` to expand it into its + * components. The result of `expand` acts as if the `Tuple`'s components + * were listed as a list of values. (Ordinarily, a `Tuple` acts as a * single value.) */ Types expand; @@ -622,8 +622,8 @@ template Tuple(Specs...) /// 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)`); + auto t1 = tuple(1, " hello ", 'a'); + assert(t1.toString() == `Tuple!(int, string, char)(1, " hello ", 'a')`); void takeSeveralTypes(int n, string s, bool b) { @@ -645,7 +645,7 @@ template Tuple(Specs...) @property ref inout(Tuple!Types) _Tuple_super() inout @trusted { - foreach (i, _; Types) // Rely on the field layout + static foreach (i; 0 .. Types.length) // Rely on the field layout { static assert(typeof(return).init.tupleof[i].offsetof == expand[i].offsetof); @@ -695,7 +695,7 @@ template Tuple(Specs...) this(U, size_t n)(U[n] values) if (n == Types.length && allSatisfy!(isBuildableFrom!U, Types)) { - foreach (i, _; Types) + static foreach (i; 0 .. Types.length) { field[i] = values[i]; } @@ -771,6 +771,17 @@ template Tuple(Specs...) return field[] == rhs.field[]; } + /// ditto + bool opEquals(R...)(auto ref R rhs) + if (R.length > 1 && areCompatibleTuples!(typeof(this), Tuple!R, "==")) + { + static foreach (i; 0 .. Types.length) + if (field[i] != rhs[i]) + return false; + + return true; + } + /// static if (Specs.length == 0) @safe unittest { @@ -789,21 +800,42 @@ template Tuple(Specs...) * for comparison between `Tuple`s. * * Returns: - * For any values `v1` on the right-hand side and `v2` on the - * left-hand side: + * For any values `v1` contained by the left-hand side tuple and any + * values `v2` contained by the right-hand side: + * + * 0 if `v1 == v2` for all members or the following value for the + * first position were the mentioned criteria is not satisfied: * * $(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.)) + * $(LI NaN, in case one of the operands is a NaN.) + * $(LI A negative number if the expression `v1 < v2` is true.) + * $(LI A positive number if the expression `v1 > v2` is true.)) */ - int opCmp(R)(R rhs) + auto opCmp(R)(R rhs) if (areCompatibleTuples!(typeof(this), R, "<")) { - foreach (i, Unused; Types) + static foreach (i; 0 .. Types.length) { if (field[i] != rhs.field[i]) { + import std.math.traits : isNaN; + static if (isFloatingPoint!(Types[i])) + { + if (isNaN(field[i])) + return float.nan; + } + static if (isFloatingPoint!(typeof(rhs.field[i]))) + { + if (isNaN(rhs.field[i])) + return float.nan; + } + static if (is(typeof(field[i].opCmp(rhs.field[i]))) && + isFloatingPoint!(typeof(field[i].opCmp(rhs.field[i])))) + { + if (isNaN(field[i].opCmp(rhs.field[i]))) + return float.nan; + } + return field[i] < rhs.field[i] ? -1 : 1; } } @@ -811,13 +843,31 @@ template Tuple(Specs...) } /// ditto - int opCmp(R)(R rhs) const + auto opCmp(R)(R rhs) const if (areCompatibleTuples!(typeof(this), R, "<")) { - foreach (i, Unused; Types) + static foreach (i; 0 .. Types.length) { if (field[i] != rhs.field[i]) { + import std.math.traits : isNaN; + static if (isFloatingPoint!(Types[i])) + { + if (isNaN(field[i])) + return float.nan; + } + static if (isFloatingPoint!(typeof(rhs.field[i]))) + { + if (isNaN(rhs.field[i])) + return float.nan; + } + static if (is(typeof(field[i].opCmp(rhs.field[i]))) && + isFloatingPoint!(typeof(field[i].opCmp(rhs.field[i])))) + { + if (isNaN(field[i].opCmp(rhs.field[i]))) + return float.nan; + } + return field[i] < rhs.field[i] ? -1 : 1; } } @@ -840,6 +890,49 @@ template Tuple(Specs...) } /** + Concatenate Tuples. + Tuple concatenation is only allowed if all named fields are distinct (no named field of this tuple occurs in `t` + and no named field of `t` occurs in this tuple). + + Params: + t = The `Tuple` to concatenate with + + Returns: A concatenation of this tuple and `t` + */ + auto opBinary(string op, T)(auto ref T t) + if (op == "~" && !(is(T : U[], U) && isTuple!U)) + { + static if (isTuple!T) + { + static assert(distinctFieldNames!(_Fields, T._Fields), + "Cannot concatenate tuples with duplicate fields: " ~ fieldNames.stringof ~ + " - " ~ T.fieldNames.stringof); + return Tuple!(_Fields, T._Fields)(expand, t.expand); + } + else + { + return Tuple!(_Fields, T)(expand, t); + } + } + + /// ditto + auto opBinaryRight(string op, T)(auto ref T t) + if (op == "~" && !(is(T : U[], U) && isTuple!U)) + { + static if (isTuple!T) + { + static assert(distinctFieldNames!(_Fields, T._Fields), + "Cannot concatenate tuples with duplicate fields: " ~ T.stringof ~ + " - " ~ fieldNames.fieldNames.stringof); + return Tuple!(T._Fields, _Fields)(t.expand, expand); + } + else + { + return Tuple!(T, _Fields)(t, expand); + } + } + + /** * Assignment from another `Tuple`. * * Params: @@ -847,12 +940,12 @@ template Tuple(Specs...) * source `Tuple` must be implicitly assignable to each * respective element of the target `Tuple`. */ - void opAssign(R)(auto ref R rhs) + ref Tuple 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)) + static if (is(R : Tuple!Types) && !__traits(isRef, rhs) && isTuple!R) { if (__ctfe) { @@ -870,6 +963,7 @@ template Tuple(Specs...) // Do not swap; opAssign should be called on the fields. field[] = rhs.field[]; } + return this; } /** @@ -884,11 +978,11 @@ template Tuple(Specs...) * It is an compile-time error to pass more names than * there are members of the $(LREF Tuple). */ - ref rename(names...)() return + ref rename(names...)() inout return if (names.length == 0 || allSatisfy!(isSomeString, typeof(names))) { import std.algorithm.comparison : equal; - // to circumvent bug 16418 + // to circumvent https://issues.dlang.org/show_bug.cgi?id=16418 static if (names.length == 0 || equal([names], [fieldNames])) return this; else @@ -898,6 +992,8 @@ template Tuple(Specs...) static assert(nN <= nT, "Cannot have more names than tuple members"); alias allNames = AliasSeq!(names, fieldNames[nN .. $]); + import std.meta : Alias, aliasSeqOf; + template GetItem(size_t idx) { import std.array : empty; @@ -938,7 +1034,7 @@ template Tuple(Specs...) t2 = tuple(3,4,5); auto t2Named = t2.rename!("", "b"); // "a" no longer has a name - static assert(!hasMember!(typeof(t2Named), "a")); + static assert(!__traits(hasMember, typeof(t2Named), "a")); assert(t2Named[0] == 3); assert(t2Named.b == 4); assert(t2Named.c == 5); @@ -954,6 +1050,10 @@ template Tuple(Specs...) .map!(t => t.a * t.b) .sum; assert(res == 68); + + const tup = Tuple!(int, "a", int, "b")(2, 3); + const renamed = tup.rename!("c", "d"); + assert(renamed.c + renamed.d == 5); } /** @@ -966,10 +1066,11 @@ template Tuple(Specs...) * The same rules for empty strings apply as for the variadic * template overload of $(LREF _rename). */ - ref rename(alias translate)() + ref rename(alias translate)() inout if (is(typeof(translate) : V[K], V, K) && isSomeString!V && (isSomeString!K || is(K : size_t))) { + import std.meta : aliasSeqOf; import std.range : ElementType; static if (isSomeString!(ElementType!(typeof(translate.keys)))) { @@ -1033,6 +1134,11 @@ template Tuple(Specs...) auto t2Named = t2.rename!(["a": "b", "b": "c"]); assert(t2Named.b == 3); assert(t2Named.c == 4); + + const t3 = Tuple!(int, "a", int, "b")(3, 4); + const t3Named = t3.rename!(["a": "b", "b": "c"]); + assert(t3Named.b == 3); + assert(t3Named.c == 4); } /// @@ -1094,7 +1200,8 @@ template Tuple(Specs...) 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.)"); + "Slicing by reference is impossible because of an alignment mistmatch" ~ + " (See https://issues.dlang.org/show_bug.cgi?id=15645)."); return *cast(typeof(return)*) &(field[from]); } @@ -1109,7 +1216,7 @@ template Tuple(Specs...) static assert(is(typeof(s) == Tuple!(string, float))); assert(s[0] == "abc" && s[1] == 4.5); - // Phobos issue #15645 + // https://issues.dlang.org/show_bug.cgi?id=15645 Tuple!(int, short, bool, double) b; static assert(!__traits(compiles, b.slice!(2, 4))); } @@ -1120,145 +1227,181 @@ template Tuple(Specs...) Returns: A `size_t` representing the hash of this `Tuple`. */ - size_t toHash() const nothrow @trusted + size_t toHash() const nothrow @safe { size_t h = 0; - foreach (i, T; Types) - h += typeid(T).getHash(cast(const void*)&field[i]); + static foreach (i, T; Types) + {{ + static if (__traits(compiles, h = .hashOf(field[i]))) + const k = .hashOf(field[i]); + else + { + // Workaround for when .hashOf is not both @safe and nothrow. + static if (is(T : shared U, U) && __traits(compiles, (U* a) nothrow @safe => .hashOf(*a)) + && !__traits(hasMember, T, "toHash")) + // BUG: Improperly casts away `shared`! + const k = .hashOf(*(() @trusted => cast(U*) &field[i])()); + else + // BUG: Improperly casts away `shared`! + const k = typeid(T).getHash((() @trusted => cast(const void*) &field[i])()); + } + static if (i == 0) + h = k; + else + // As in boost::hash_combine + // https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine + h ^= k + 0x9e3779b9 + (h << 6) + (h >>> 2); + }} 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; - } + /** + * 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()); - } + import std.format.spec : 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`$(COMMA) so + * it may contain as many formats as the `Tuple` has fields.)) + * $(TROW $(P `%(inner%|sep%)`), $(P The format `inner` is one format$(COMMA) that is applied + * on all fields of the `Tuple`. The inner format must be compatible to all + * of them.))) + * + * Params: + * sink = A `char` accepting delegate + * fmt = A $(REF FormatSpec, std,format) + */ + void toString(DG)(scope DG sink) const + { + auto f = FormatSpec!char(); + toString(sink, f); + } - /// ditto - void toString(DG, Char)(scope DG sink, FormatSpec!Char fmt) const + /// ditto + void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt) const + { + import std.format : format, FormatException; + import std.format.write : formattedWrite; + import std.range : only; + if (fmt.nested) { - 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') + if (fmt.sep) { - enum header = Unqual!(typeof(this)).stringof ~ "(", - footer = ")", - separator = ", "; - sink(header); foreach (i, Type; Types) { static if (i > 0) { - sink(separator); + sink(fmt.sep); } - // TODO: Change this once formatElement() works for shared objects. + // TODO: Change this once formattedWrite() works for shared objects. static if (is(Type == class) && is(Type == shared)) { sink(Type.stringof); } else { - FormatSpec!Char f; - formatElement(sink, field[i], f); + formattedWrite(sink, fmt.nested, this.field[i]); } } - sink(footer); } else { - throw new FormatException( - "Expected '%s' or '%(...%)' or '%(...%|...%)' format specifier for type '" ~ - Unqual!(typeof(this)).stringof ~ "', not '%" ~ fmt.spec ~ "'."); + 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 format() works for shared objects. + static if (is(Type == class) && is(Type == shared)) + { + sink(Type.stringof); + } + else + { + sink(format!("%(%s%)")(only(field[i]))); + } } + sink(footer); + } + else + { + const spec = fmt.spec; + throw new FormatException( + "Expected '%s' or '%(...%)' or '%(...%|...%)' format specifier for type '" ~ + Unqual!(typeof(this)).stringof ~ "', not '%" ~ spec ~ "'."); } } + + /// + static if (Types.length == 0) + @safe unittest + { + import std.format : format; + + 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`); + } + + /// + static if (Types.length == 0) + @safe unittest + { + import std.exception : assertThrown; + import std.format : format, FormatException; + + // 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` + ); + } } } @@ -1301,6 +1444,193 @@ template Tuple(Specs...) assert(!is(typeof(point1) == typeof(point2))); } +/// Use tuples as ranges +@safe unittest +{ + import std.algorithm.iteration : sum; + import std.range : only; + auto t = tuple(1, 2); + assert(t.expand.only.sum == 3); +} + +// https://issues.dlang.org/show_bug.cgi?id=4582 +@safe unittest +{ + static assert(!__traits(compiles, Tuple!(string, "id", int, "id"))); + static assert(!__traits(compiles, Tuple!(string, "str", int, "i", string, "str", float))); +} + +/// Concatenate tuples +@safe unittest +{ + import std.meta : AliasSeq; + auto t = tuple(1, "2") ~ tuple(ushort(42), true); + static assert(is(t.Types == AliasSeq!(int, string, ushort, bool))); + assert(t[1] == "2"); + assert(t[2] == 42); + assert(t[3] == true); +} + +// https://issues.dlang.org/show_bug.cgi?id=14637 +// tuple concat +@safe unittest +{ + auto t = tuple!"foo"(1.0) ~ tuple!"bar"("3"); + static assert(is(t.Types == AliasSeq!(double, string))); + static assert(t.fieldNames == tuple("foo", "bar")); + assert(t.foo == 1.0); + assert(t.bar == "3"); +} + +// https://issues.dlang.org/show_bug.cgi?id=18824 +// tuple concat +@safe unittest +{ + alias Type = Tuple!(int, string); + Type[] arr; + auto t = tuple(2, "s"); + // Test opBinaryRight + arr = arr ~ t; + // Test opBinary + arr = t ~ arr; + static assert(is(typeof(arr) == Type[])); + immutable Type[] b; + auto c = b ~ t; + static assert(is(typeof(c) == immutable(Type)[])); +} + +// tuple concat +@safe unittest +{ + auto t = tuple!"foo"(1.0) ~ "3"; + static assert(is(t.Types == AliasSeq!(double, string))); + assert(t.foo == 1.0); + assert(t[1]== "3"); +} + +// tuple concat +@safe unittest +{ + auto t = "2" ~ tuple!"foo"(1.0); + static assert(is(t.Types == AliasSeq!(string, double))); + assert(t.foo == 1.0); + assert(t[0]== "2"); +} + +// tuple concat +@safe unittest +{ + auto t = "2" ~ tuple!"foo"(1.0) ~ tuple(42, 3.0f) ~ real(1) ~ "a"; + static assert(is(t.Types == AliasSeq!(string, double, int, float, real, string))); + assert(t.foo == 1.0); + assert(t[0] == "2"); + assert(t[1] == 1.0); + assert(t[2] == 42); + assert(t[3] == 3.0f); + assert(t[4] == 1.0); + assert(t[5] == "a"); +} + +// ensure that concatenation of tuples with non-distinct fields is forbidden +@safe unittest +{ + static assert(!__traits(compiles, + tuple!("a")(0) ~ tuple!("a")("1"))); + static assert(!__traits(compiles, + tuple!("a", "b")(0, 1) ~ tuple!("b", "a")("3", 1))); + static assert(!__traits(compiles, + tuple!("a")(0) ~ tuple!("b", "a")("3", 1))); + static assert(!__traits(compiles, + tuple!("a1", "a")(1.0, 0) ~ tuple!("a2", "a")("3", 0))); +} + +// Ensure that Tuple comparison with non-const opEquals works +@safe unittest +{ + static struct Bad + { + int a; + + bool opEquals(Bad b) + { + return a == b.a; + } + } + + auto t = Tuple!(int, Bad, string)(1, Bad(1), "asdf"); + + //Error: mutable method Bad.opEquals is not callable using a const object + assert(t == AliasSeq!(1, Bad(1), "asdf")); +} + +// Ensure Tuple.toHash works +@safe unittest +{ + Tuple!(int, int) point; + assert(point.toHash == typeof(point).init.toHash); + assert(tuple(1, 2) != point); + assert(tuple(1, 2) == tuple(1, 2)); + point[0] = 1; + assert(tuple(1, 2) != point); + point[1] = 2; + assert(tuple(1, 2) == point); +} + +@safe @betterC unittest +{ + auto t = tuple(1, 2); + assert(t == tuple(1, 2)); + auto t3 = tuple(1, 'd'); +} + +// https://issues.dlang.org/show_bug.cgi?id=20850 +// Assignment to enum tuple +@safe unittest +{ + enum T : Tuple!(int*) { a = T(null) } + T t; + t = T.a; +} + +// https://issues.dlang.org/show_bug.cgi?id=13663 +@safe unittest +{ + auto t = tuple(real.nan); + assert(!(t > t)); + assert(!(t < t)); + assert(!(t == t)); +} + +@safe unittest +{ + struct S + { + float opCmp(S s) { return float.nan; } + bool opEquals(S s) { return false; } + } + + auto t = tuple(S()); + assert(!(t > t)); + assert(!(t < t)); + assert(!(t == t)); +} + +// https://issues.dlang.org/show_bug.cgi?id=8015 +@safe unittest +{ + struct MyStruct + { + string str; + @property string toStr() + { + return str; + } + alias toStr this; + } + + Tuple!(MyStruct) t; +} + /** Creates a copy of a $(LREF Tuple) with its fields in _reverse order. @@ -1311,7 +1641,7 @@ template Tuple(Specs...) A new `Tuple`. */ auto reverse(T)(T t) - if (isTuple!T) +if (isTuple!T) { import std.meta : Reverse; // @@@BUG@@@ Cannot be an internal function due to forward reference issues. @@ -1334,7 +1664,7 @@ auto reverse(T)(T t) /* Get a Tuple type with the reverse specification of Tuple T. */ private template ReverseTupleType(T) - if (isTuple!T) +if (isTuple!T) { static if (is(T : Tuple!A, A...)) alias ReverseTupleType = Tuple!(ReverseTupleSpecs!A); @@ -1479,7 +1809,7 @@ private template ReverseTupleSpecs(T...) ssCopy[1] = ssCopy[0]; assert(ssCopy[1].count == 2); } - // bug 2800 + // https://issues.dlang.org/show_bug.cgi?id=2800 { static struct R { @@ -1499,7 +1829,7 @@ private template ReverseTupleSpecs(T...) { auto t1 = Tuple!(int, double)(1, 1); - // 8702 + // https://issues.dlang.org/show_bug.cgi?id=8702 auto t8702a = tuple(tuple(1)); auto t8702b = Tuple!(Tuple!(int))(Tuple!(int)(1)); } @@ -1514,19 +1844,19 @@ private template ReverseTupleSpecs(T...) // incompatible static assert(!__traits(compiles, Tuple!(int, int)(y))); } - // 6275 + // https://issues.dlang.org/show_bug.cgi?id=6275 { const int x = 1; auto t1 = tuple(x); alias T = Tuple!(const(int)); auto t2 = T(1); } - // 9431 + // https://issues.dlang.org/show_bug.cgi?id=9431 { alias T = Tuple!(int[1][]); auto t = T([[10]]); } - // 7666 + // https://issues.dlang.org/show_bug.cgi?id=7666 { auto tup = tuple(1, "2"); assert(tup.reverse == tuple("2", 1)); @@ -1565,8 +1895,9 @@ private template ReverseTupleSpecs(T...) static assert( is(typeof(tc2 == tm2))); static assert( is(typeof(tc2 == tc2))); + // https://issues.dlang.org/show_bug.cgi?id=8686 struct Equ3 { bool opEquals(T)(T) { return true; } } - auto tm3 = tuple(Equ3.init); // bugzilla 8686 + auto tm3 = tuple(Equ3.init); const tc3 = tuple(Equ3.init); static assert( is(typeof(tm3 == tm3))); static assert( is(typeof(tm3 == tc3))); @@ -1615,7 +1946,7 @@ private template ReverseTupleSpecs(T...) static assert( is(typeof(tc4 < tm4))); static assert( is(typeof(tc4 < tc4))); } - // Bugzilla 14890 + // https://issues.dlang.org/show_bug.cgi?id=14890 static void test14890(inout int[] dummy) { alias V = Tuple!(int, int); @@ -1626,8 +1957,8 @@ private template ReverseTupleSpecs(T...) 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)) + static foreach (v1; AliasSeq!(mv, cv, iv, wv, wcv)) + static foreach (v2; AliasSeq!(mv, cv, iv, wv, wcv)) { assert(!(v1 < v2)); } @@ -1657,7 +1988,7 @@ private template ReverseTupleSpecs(T...) } @safe unittest { - // Bugzilla 10686 + // https://issues.dlang.org/show_bug.cgi?id=10686 immutable Tuple!(int) t1; auto r1 = t1[0]; // OK immutable Tuple!(int, "x") t2; @@ -1667,7 +1998,7 @@ private template ReverseTupleSpecs(T...) { import std.exception : assertCTFEable; - // Bugzilla 10218 + // https://issues.dlang.org/show_bug.cgi?id=10218 assertCTFEable!( { auto t = tuple(1); @@ -1702,7 +2033,7 @@ private template ReverseTupleSpecs(T...) TISIS e = TISIS(ss); } -// Bugzilla #9819 +// https://issues.dlang.org/show_bug.cgi?id=9819 @safe unittest { alias T = Tuple!(int, "x", double, "foo"); @@ -1713,7 +2044,7 @@ private template ReverseTupleSpecs(T...) static assert(Fields.fieldNames == AliasSeq!("id", "", "")); } -// Bugzilla 13837 +// https://issues.dlang.org/show_bug.cgi?id=13837 @safe unittest { // New behaviour, named arguments. @@ -1749,7 +2080,7 @@ private template ReverseTupleSpecs(T...) @safe unittest { - class C {} + class C { override size_t toHash() const nothrow @safe { return 0; } } Tuple!(Rebindable!(const C)) a; Tuple!(const C) b; a = b; @@ -1767,39 +2098,24 @@ private template ReverseTupleSpecs(T...) import std.format : format, FormatException; import std.exception : assertThrown; - // enum tupStr = tuple(1, 1.0).toString; // toString is *impure*. + //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` - ); +// https://issues.dlang.org/show_bug.cgi?id=17803, parte uno +@safe unittest +{ + auto a = tuple(3, "foo"); + assert(__traits(compiles, { a = (a = a); })); +} +// Ditto +@safe unittest +{ + Tuple!(int[]) a, b, c; + a = tuple([0, 1, 2]); + c = b = a; + assert(a[0].length == b[0].length && b[0].length == c[0].length); + assert(a[0].ptr == b[0].ptr && b[0].ptr == c[0].ptr); } /** @@ -1807,11 +2123,15 @@ private template ReverseTupleSpecs(T...) 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`. + Names = An optional list of strings naming each successive field of the `Tuple` + or a list of types that the elements are being casted to. + For a list of names, + 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. + For a list of types, + there must be exactly as many types as parameters. */ template tuple(Names...) { @@ -1877,7 +2197,7 @@ template tuple(Names...) } /** - Returns $(D true) if and only if $(D T) is an instance of $(D std.typecons.Tuple). + Returns `true` if and only if `T` is an instance of `std.typecons.Tuple`. Params: T = The type to check. @@ -1915,7 +2235,7 @@ enum isTuple(T) = __traits(compiles, // used by both Rebindable and UnqualRef private mixin template RebindableCommon(T, U, alias This) - if (is(T == class) || is(T == interface) || isAssociativeArray!T) +if (is(T == class) || is(T == interface) || isAssociativeArray!T) { private union { @@ -1923,58 +2243,75 @@ private mixin template RebindableCommon(T, U, alias This) U stripped; } - @trusted pure nothrow @nogc + void opAssign(T another) pure nothrow @nogc { - void opAssign(T another) + // If `T` defines `opCast` we must infer the safety + static if (hasMember!(T, "opCast")) { - stripped = cast(U) another; + // This will allow the compiler to infer the safety of `T.opCast!U` + // without generating any runtime cost + if (false) { stripped = cast(U) another; } } + () @trusted { stripped = cast(U) another; }(); + } - void opAssign(typeof(this) another) + void opAssign(typeof(this) another) @trusted pure nothrow @nogc + { + 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) @trusted pure nothrow @nogc { 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) pure nothrow @nogc + { + // Infer safety from opAssign + opAssign(initializer); + } - this(T initializer) - { - opAssign(initializer); - } + @property inout(T) get() @trusted pure nothrow @nogc inout + { + return original; + } - @property inout(T) get() inout - { - return original; - } + bool opEquals()(auto ref const(typeof(this)) rhs) const + { + // Must forward explicitly because 'stripped' is part of a union. + // The necessary 'toHash' is forwarded to the class via alias this. + return stripped == rhs.stripped; + } + + bool opEquals(const(U) rhs) const + { + return stripped == rhs; } 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 +`Rebindable!(T)` is a simple, efficient wrapper that behaves just +like an object of type `T`, except that you can reassign it to +refer to another object. For completeness, `Rebindable!(T)` aliases +itself away to `T` if `T` is a non-const object type. + +You may want to use `Rebindable` when you want to have mutable +storage referring to `const` objects, for example an array of +references that must be sorted in place. `Rebindable` does not break the soundness of D's type system and does not incur any of the -risks usually associated with $(D cast). +risks usually associated with `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) +if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) { static if (is(T == const U, U) || is(T == immutable U, U)) { @@ -1997,10 +2334,10 @@ template Rebindable(T) } } -///Regular $(D const) object references cannot be reassigned. -@system unittest +///Regular `const` object references cannot be reassigned. +@safe unittest { - class Widget { int x; int y() const { return x; } } + class Widget { int x; int y() @safe const { return x; } } const a = new Widget; // Fine a.y(); @@ -2011,12 +2348,12 @@ template Rebindable(T) } /** - However, $(D Rebindable!(Widget)) does allow reassignment, + However, `Rebindable!(Widget)` does allow reassignment, while otherwise behaving exactly like a $(D const Widget). */ -@system unittest +@safe unittest { - class Widget { int x; int y() const { return x; } } + class Widget { int x; int y() const @safe { return x; } } auto a = Rebindable!(const Widget)(new Widget); // Fine a.y(); @@ -2026,15 +2363,94 @@ template Rebindable(T) a = new Widget; } -@safe unittest // issue 16054 +// https://issues.dlang.org/show_bug.cgi?id=16054 +@safe unittest { Rebindable!(immutable Object) r; static assert(__traits(compiles, r.get())); static assert(!__traits(compiles, &r.get())); } +@safe unittest +{ + class CustomToHash + { + override size_t toHash() const nothrow @trusted { return 42; } + } + Rebindable!(immutable(CustomToHash)) a = new immutable CustomToHash(); + assert(a.toHash() == 42, "Rebindable!A should offer toHash()" + ~ " by forwarding to A.toHash()."); +} + +// https://issues.dlang.org/show_bug.cgi?id=18615 +// Rebindable!A should use A.opEqualsa +@system unittest +{ + class CustomOpEq + { + int x; + override bool opEquals(Object rhsObj) + { + if (auto rhs = cast(const(CustomOpEq)) rhsObj) + return this.x == rhs.x; + else + return false; + } + } + CustomOpEq a = new CustomOpEq(); + CustomOpEq b = new CustomOpEq(); + assert(a !is b); + assert(a == b, "a.x == b.x should be true (0 == 0)."); + + Rebindable!(const(CustomOpEq)) ra = a; + Rebindable!(const(CustomOpEq)) rb = b; + assert(ra !is rb); + assert(ra == rb, "Rebindable should use CustomOpEq's opEquals, not 'is'."); + assert(ra == b, "Rebindable!(someQualifier(A)) should be comparable" + ~ " against const(A) via A.opEquals."); + assert(a == rb, "Rebindable!(someQualifier(A)) should be comparable" + ~ " against const(A) via A.opEquals."); + + b.x = 1; + assert(a != b); + assert(ra != b, "Rebindable!(someQualifier(A)) should be comparable" + ~ " against const(A) via A.opEquals."); + assert(a != rb, "Rebindable!(someQualifier(A)) should be comparable" + ~ " against const(A) via A.opEquals."); + + Rebindable!(const(Object)) o1 = new Object(); + Rebindable!(const(Object)) o2 = new Object(); + assert(o1 !is o2); + assert(o1 == o1, "When the class doesn't provide its own opEquals," + ~ " Rebindable treats 'a == b' as 'a is b' like Object.opEquals."); + assert(o1 != o2, "When the class doesn't provide its own opEquals," + ~ " Rebindable treats 'a == b' as 'a is b' like Object.opEquals."); + assert(o1 != new Object(), "Rebindable!(const(Object)) should be" + ~ " comparable against Object itself and use Object.opEquals."); +} + +// https://issues.dlang.org/show_bug.cgi?id=18755 +@safe unittest +{ + static class Foo + { + auto opCast(T)() @system immutable pure nothrow + { + *(cast(uint*) 0xdeadbeef) = 0xcafebabe; + return T.init; + } + } + + static assert(!__traits(compiles, () @safe { + auto r = Rebindable!(immutable Foo)(new Foo); + })); + static assert(__traits(compiles, () @system { + auto r = Rebindable!(immutable Foo)(new Foo); + })); +} + /** -Convenience function for creating a $(D Rebindable) using automatic type +Convenience function for creating a `Rebindable` using automatic type inference. Params: @@ -2045,17 +2461,39 @@ 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) +if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) { typeof(return) ret; ret = obj; return ret; } +/// +@system unittest +{ + class C + { + int payload; + this(int p) { payload = p; } + } + const c = new C(1); + + auto c2 = c.rebindable; + assert(c2.payload == 1); + // passing Rebindable to rebindable + c2 = c2.rebindable; + + c2 = new C(2); + assert(c2.payload == 2); + + const c3 = c2.get; + assert(c3.payload == 2); +} + /** -This function simply returns the $(D Rebindable) object passed in. It's useful +This function simply returns the `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). +`class` or a `Rebindable`. Params: obj = An instance of Rebindable!T. @@ -2068,6 +2506,24 @@ Rebindable!T rebindable(T)(Rebindable!T obj) return obj; } +// TODO: remove me once the rebindable overloads have been joined +/// +@system unittest +{ + class C + { + int payload; + this(int p) { payload = p; } + } + const c = new C(1); + + auto c2 = c.rebindable; + assert(c2.payload == 1); + // passing Rebindable to rebindable + c2 = c2.rebindable; + assert(c2.payload == 1); +} + @system unittest { interface CI { int foo() const; } @@ -2135,18 +2591,18 @@ Rebindable!T rebindable(T)(Rebindable!T obj) assert(rebindable(arr) == arr); assert(rebindable(arrConst) == arr); - // Issue 7654 + // https://issues.dlang.org/show_bug.cgi?id=7654 immutable(char[]) s7654; Rebindable!(typeof(s7654)) r7654 = s7654; - foreach (T; AliasSeq!(char, wchar, char, int)) + static 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 + // https://issues.dlang.org/show_bug.cgi?id=12046 static assert(!__traits(compiles, Rebindable!(int[1]))); static assert(!__traits(compiles, Rebindable!(const int[1]))); @@ -2160,7 +2616,7 @@ Rebindable!T rebindable(T)(Rebindable!T obj) } /** - Similar to $(D Rebindable!(T)) but strips all qualifiers from the reference as + Similar to `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) @@ -2168,12 +2624,12 @@ Rebindable!T rebindable(T)(Rebindable!T obj) T = A class or interface type. */ template UnqualRef(T) - if (is(T == class) || is(T == interface)) +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)) + static if (is(T == immutable U, U) + || is(T == const shared U, U) + || is(T == const U, U) + || is(T == shared U, U)) { struct UnqualRef { @@ -2271,20 +2727,20 @@ string alignForSize(E...)(const char[][] names...) { 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 y = alignForSize!(ubyte, Foo, double)("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 passNormalY = y == "double 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 + enum passAbnormalY = y == "Foo y;\ndouble z;\nubyte x;\n"; + // ^ blame https://issues.dlang.org/show_bug.cgi?id=231 static assert(passNormalX || passAbnormalX && double.alignof <= (int[]).alignof); static assert(passNormalY || passAbnormalY && double.alignof <= int.alignof); } -// Issue 12914 +// https://issues.dlang.org/show_bug.cgi?id=12914 @safe unittest { immutable string[] fieldNames = ["x", "y"]; @@ -2298,46 +2754,72 @@ string alignForSize(E...)(const char[][] names...) 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. +non-null. Calling `nullify` can nullify it again. -Practically $(D Nullable!T) stores a $(D T) and a $(D bool). +Practically `Nullable!T` stores a `T` and a `bool`. */ struct Nullable(T) { - private T _value; - private bool _isNull = true; + private union DontCallDestructorT + { + T payload; + } -/** -Constructor initializing $(D this) with $(D value). + private DontCallDestructorT _value = DontCallDestructorT.init; -Params: - value = The value to initialize this `Nullable` with. - */ + private bool _isNull = true; + + /** + * Constructor initializing `this` with `value`. + * + * Params: + * value = The value to initialize this `Nullable` with. + */ this(inout T value) inout { - _value = value; + _value.payload = value; _isNull = false; } + static if (hasElaborateDestructor!T) + { + ~this() + { + if (!_isNull) + { + destroy(_value.payload); + } + } + } + /** - 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 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(this This, Rhs)(auto ref Rhs rhs) + if (!is(CommonType!(This, Rhs) == void)) { - if (_isNull) - return rhs._isNull; - if (rhs._isNull) - return false; - return _value == rhs._value; + static if (is(This == Rhs)) + { + if (_isNull) + return rhs._isNull; + if (rhs._isNull) + return false; + return _value.payload == rhs._value.payload; + } + else + { + alias Common = CommonType!(This, Rhs); + return cast(Common) this == cast(Common) rhs; + } } /// Ditto - bool opEquals(U)(auto ref const(U) rhs) const - if (is(typeof(this.get == rhs))) + bool opEquals(this This, Rhs)(auto ref Rhs rhs) + if (is(CommonType!(This, Rhs) == void) && is(typeof(this.get == rhs))) { - return _isNull ? false : rhs == _value; + return _isNull ? false : rhs == _value.payload; } /// @@ -2383,7 +2865,7 @@ Params: assert(a != Nullable!int(29)); } - // Issue 17482 + // https://issues.dlang.org/show_bug.cgi?id=17482 @system unittest { import std.variant : Variant; @@ -2393,158 +2875,214 @@ Params: assert(e != 12); } - template toString() + size_t toHash() const @safe nothrow { - 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); - } - } + static if (__traits(compiles, .hashOf(_value.payload))) + return _isNull ? 0 : .hashOf(_value.payload); + else + // Workaround for when .hashOf is not both @safe and nothrow. + return _isNull ? 0 : typeid(T).getHash(&_value.payload); + } - // 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); - } - } + /** + * Gives the string `"Nullable.null"` if `isNull` is `true`. Otherwise, the + * result is equivalent to calling $(REF formattedWrite, std,format) on the + * underlying value. + * + * Params: + * writer = A `char` accepting + * $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) + * fmt = A $(REF FormatSpec, std,format) which is used to represent + * the value if this Nullable is not null + * Returns: + * A `string` if `writer` and `fmt` are not set; `void` otherwise. + */ + string toString() + { + import std.array : appender; + auto app = appender!string(); + auto spec = singleSpec("%s"); + toString(app, spec); + return app.data; } -/** -Check if `this` is in the null state. + /// ditto + string toString() const + { + import std.array : appender; + auto app = appender!string(); + auto spec = singleSpec("%s"); + toString(app, spec); + return app.data; + } -Returns: - true $(B iff) `this` is in the null state, otherwise false. - */ + /// ditto + void toString(W)(ref W writer, scope const ref FormatSpec!char fmt) + if (isOutputRange!(W, char)) + { + import std.range.primitives : put; + if (isNull) + put(writer, "Nullable.null"); + else + formatValue(writer, _value.payload, fmt); + } + + /// ditto + void toString(W)(ref W writer, scope const ref FormatSpec!char fmt) const + if (isOutputRange!(W, char)) + { + import std.range.primitives : put; + if (isNull) + put(writer, "Nullable.null"); + else + formatValue(writer, _value.payload, 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); + /// + @safe unittest + { + Nullable!int ni; + assert(ni.isNull); - ni = 0; - assert(!ni.isNull); -} + ni = 0; + assert(!ni.isNull); + } -// Issue 14940 -@safe unittest -{ - import std.array : appender; - import std.format : formattedWrite; + // https://issues.dlang.org/show_bug.cgi?id=14940 + @safe unittest + { + import std.array : appender; + import std.format.write : formattedWrite; - auto app = appender!string(); - Nullable!int a = 1; - formattedWrite(app, "%s", a); - assert(app.data == "1"); -} + auto app = appender!string(); + Nullable!int a = 1; + formattedWrite(app, "%s", a); + assert(app.data == "1"); + } -/** -Forces $(D this) to the null state. - */ + // https://issues.dlang.org/show_bug.cgi?id=19799 + @safe unittest + { + import std.format : format; + + const Nullable!string a = const(Nullable!string)(); + + format!"%s"(a); + } + + /** + * Forces `this` to the null state. + */ void nullify()() { - .destroy(_value); + static if (is(T == class) || is(T == interface)) + _value.payload = null; + else + .destroy(_value.payload); _isNull = true; } -/// -@safe unittest -{ - Nullable!int ni = 0; - assert(!ni.isNull); - - ni.nullify(); - assert(ni.isNull); -} + /// + @safe unittest + { + Nullable!int ni = 0; + assert(!ni.isNull); -/** -Assigns $(D value) to the internally-held state. If the assignment -succeeds, $(D this) becomes non-null. + ni.nullify(); + assert(ni.isNull); + } -Params: - value = A value of type `T` to assign to this `Nullable`. - */ - void opAssign()(T value) + /** + * Assigns `value` to the internally-held state. If the assignment + * succeeds, `this` becomes non-null. + * + * Params: + * value = A value of type `T` to assign to this `Nullable`. + */ + Nullable opAssign()(T value) { - _value = value; + import std.algorithm.mutation : moveEmplace, move; + + // the lifetime of the value in copy shall be managed by + // this Nullable, so we must avoid calling its destructor. + auto copy = DontCallDestructorT(value); + + if (_isNull) + { + // trusted since payload is known to be T.init here. + () @trusted { moveEmplace(copy.payload, _value.payload); }(); + } + else + { + move(copy.payload, _value.payload); + } _isNull = false; + return this; } -/** - 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); -} + /** + * 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); -/** -Gets the value. $(D this) must not be in the null state. -This function is also called for the implicit conversion to $(D T). + //Passes?! + npi = null; + assert(!npi.isNull); + } -Returns: - The value held internally by this `Nullable`. - */ + /** + * Gets the value if not null. If `this` is in the null state, and the optional + * parameter `fallback` was provided, it will be returned. Without `fallback`, + * calling `get` with a null state is invalid. + * + * When the fallback type is different from the Nullable type, `get(T)` returns + * the common type. + * + * Params: + * fallback = the value to return in case the `Nullable` is null. + * + * 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; + return _value.payload; } -/// -@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); -} + /// ditto + @property inout(T) get()(inout(T) fallback) inout + { + return isNull ? fallback : _value.payload; + } -/** -Implicitly converts to $(D T). -$(D this) must not be in the null state. - */ - alias get this; + /// ditto + @property auto get(U)(inout(U) fallback) inout + { + return isNull ? fallback : _value.payload; + } } /// ditto @@ -2574,8 +3112,8 @@ auto nullable(T)(T t) if (!queryResult.isNull) { //Process Mr. Doe's customer record - auto address = queryResult.address; - auto customerNum = queryResult.customerNum; + auto address = queryResult.get.address; + auto customerNum = queryResult.get.customerNum; //Do some things with this customer's info } @@ -2598,30 +3136,6 @@ auto nullable(T)(T t) 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); @@ -2631,7 +3145,7 @@ auto nullable(T)(T t) } @safe unittest { - static int f(in Nullable!int x) { + static int f(scope const Nullable!int x) { return x.isNull ? 42 : x.get; } Nullable!int a; @@ -2652,10 +3166,10 @@ auto nullable(T)(T t) assert(s == S(6)); assert(s != S(0)); assert(s.get != S(0)); - s.x = 9190; - assert(s.x == 9190); + s.get.x = 9190; + assert(s.get.x == 9190); s.nullify(); - assertThrown!Throwable(s.x = 9441); + assertThrown!Throwable(s.get.x = 9441); } @safe unittest { @@ -2684,13 +3198,14 @@ auto nullable(T)(T t) assert(s.isNull); s = S(5); assert(!s.isNull); - assert(s.x == 5); + assert(s.get.x == 5); s.nullify(); assert(s.isNull); } + +// https://issues.dlang.org/show_bug.cgi?id=9404 @safe unittest { - // Bugzilla 9404 alias N = Nullable!int; void foo(N a) @@ -2778,17 +3293,18 @@ auto nullable(T)(T t) ni = other.ni; } } - foreach (S; AliasSeq!(S1, S2)) - { + static foreach (S; AliasSeq!(S1, S2)) + {{ S a; S b = a; S c; c = a; - } + }} } + +// https://issues.dlang.org/show_bug.cgi?id=10268 @system unittest { - // Bugzilla 10268 import std.json; JSONValue value = null; auto na = Nullable!JSONValue(value); @@ -2804,10 +3320,10 @@ auto nullable(T)(T t) 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); + assert(x1.get.val == 1); + assert(x2.get.val == 1); + assert(x3.get.val == 1); + assert(x4.get.val == 1); } auto nm = 10; @@ -2820,8 +3336,8 @@ auto nullable(T)(T t) 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); + assert(*x1.get.val == 10); + assert(*x4.get.val == 10); } { @@ -2831,34 +3347,42 @@ auto nullable(T)(T t) 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); + assert(*x1.get.val == 10); + assert(*x2.get.val == 10); + assert(*x3.get.val == 10); + assert(*x4.get.val == 10); } } + +// https://issues.dlang.org/show_bug.cgi?id=10357 @safe unittest { - // Bugzila 10357 import std.datetime; Nullable!SysTime time = SysTime(0); } + +// https://issues.dlang.org/show_bug.cgi?id=10915 @system unittest { import std.conv : to; import std.array; - // Bugzilla 10915 Appender!string buffer; Nullable!int ni; assert(ni.to!string() == "Nullable.null"); + assert((cast(const) ni).to!string() == "Nullable.null"); struct Test { string s; } alias NullableTest = Nullable!Test; NullableTest nt = Test("test"); + // test output range version assert(nt.to!string() == `Test("test")`); + // test appender version + assert(nt.toString() == `Test("test")`); + // test const version + assert((cast(const) nt).toString() == `const(Test)("test")`); NullableTest ntn = Test("null"); assert(ntn.to!string() == `Test("null")`); @@ -2881,12 +3405,219 @@ auto nullable(T)(T t) assert(ntts.to!string() == "2.5"); } +// https://issues.dlang.org/show_bug.cgi?id=14477 +@safe unittest +{ + static struct DisabledDefaultConstructor + { + @disable this(); + this(int i) { } + } + Nullable!DisabledDefaultConstructor var; + var = DisabledDefaultConstructor(5); + var.nullify; +} + +// https://issues.dlang.org/show_bug.cgi?id=17440 +@system unittest +{ + static interface I { } + + static class C : I + { + int canary; + ~this() + { + canary = 0x5050DEAD; + } + } + auto c = new C; + c.canary = 0xA71FE; + auto nc = nullable(c); + nc.nullify; + assert(c.canary == 0xA71FE); + + I i = c; + auto ni = nullable(i); + ni.nullify; + assert(c.canary == 0xA71FE); +} + +// https://issues.dlang.org/show_bug.cgi?id=19037 +@safe unittest +{ + import std.datetime : SysTime; + + struct Test + { + bool b; + + nothrow invariant { assert(b == true); } + + SysTime _st; + + static bool destroyed; + + @disable this(); + this(bool b) { this.b = b; } + ~this() @safe { destroyed = true; } + + // mustn't call opAssign on Test.init in Nullable!Test, because the invariant + // will be called before opAssign on the Test.init that is in Nullable + // and Test.init violates its invariant. + void opAssign(Test rhs) @safe { assert(false); } + } + + { + Nullable!Test nt; + + nt = Test(true); + + // destroy value + Test.destroyed = false; + + nt.nullify; + + assert(Test.destroyed); + + Test.destroyed = false; + } + // don't run destructor on T.init in Nullable on scope exit! + assert(!Test.destroyed); +} +// check that the contained type's destructor is called on assignment +@system unittest +{ + struct S + { + // can't be static, since we need a specific value's pointer + bool* destroyedRef; + + ~this() + { + if (this.destroyedRef) + { + *this.destroyedRef = true; + } + } + } + + Nullable!S ns; + + bool destroyed; + + ns = S(&destroyed); + + // reset from rvalue destruction in Nullable's opAssign + destroyed = false; + + // overwrite Nullable + ns = S(null); + + // the original S should be destroyed. + assert(destroyed == true); +} +// check that the contained type's destructor is still called when required +@system unittest +{ + bool destructorCalled = false; + + struct S + { + bool* destroyed; + ~this() { *this.destroyed = true; } + } + + { + Nullable!S ns; + } + assert(!destructorCalled); + { + Nullable!S ns = Nullable!S(S(&destructorCalled)); + + destructorCalled = false; // reset after S was destroyed in the NS constructor + } + assert(destructorCalled); +} + +// check that toHash on Nullable is forwarded to the contained type +@system unittest +{ + struct S + { + size_t toHash() const @safe pure nothrow { return 5; } + } + + Nullable!S s1 = S(); + Nullable!S s2 = Nullable!S(); + + assert(typeid(Nullable!S).getHash(&s1) == 5); + assert(typeid(Nullable!S).getHash(&s2) == 0); +} + +// https://issues.dlang.org/show_bug.cgi?id=21704 +@safe unittest +{ + import std.array : staticArray; + + bool destroyed; + + struct Probe + { + ~this() { destroyed = true; } + } + + { + Nullable!(Probe[1]) test = [Probe()].staticArray; + destroyed = false; + } + assert(destroyed); +} + +// https://issues.dlang.org/show_bug.cgi?id=21705 +@safe unittest +{ + static struct S + { + int n; + bool opEquals(S rhs) { return n == rhs.n; } + } + + Nullable!S test1 = S(1), test2 = S(1); + S s = S(1); + + assert(test1 == s); + assert(test1 == test2); +} + +// https://issues.dlang.org/show_bug.cgi?id=22101 +@safe unittest +{ + static int impure; + + struct S + { + ~this() { impure++; } + } + + Nullable!S s; + s.get(S()); +} + +// https://issues.dlang.org/show_bug.cgi?id=22100 +@safe unittest +{ + Nullable!int a, b, c; + a = b = c = 5; + a = b = c = nullable(5); +} + /** -Just like $(D Nullable!T), except that the null state is defined as a +Just like `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 +`uint` that sets aside the value `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). +Nullable!T) because it does not need to store an extra `bool`. Params: T = The wrapped type for which Nullable provides a null value. @@ -2899,7 +3630,7 @@ struct Nullable(T, T nullValue) private T _value = nullValue; /** -Constructor initializing $(D this) with $(D value). +Constructor initializing `this` with `value`. Params: value = The value to initialize this `Nullable` with. @@ -2911,9 +3642,10 @@ Params: 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) + import std.format.spec : FormatSpec; + import std.format.write : formatValue; + // Needs to be a template because of https://issues.dlang.org/show_bug.cgi?id=13737. + void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) { if (isNull) { @@ -2942,7 +3674,7 @@ Returns: } //Need to use 'is' if T is a float type //because NaN != NaN - else static if (isFloatingPoint!T) + else static if (__traits(isFloating, T) || __traits(compiles, { static assert(!(nullValue == nullValue)); })) { return _value is nullValue; } @@ -2953,7 +3685,7 @@ Returns: } /// -@system unittest +@safe unittest { Nullable!(int, -1) ni; //Initialized to "null" state @@ -2963,12 +3695,20 @@ Returns: assert(!ni.isNull); } +@system unittest +{ + assert(typeof(this).init.isNull, typeof(this).stringof ~ + ".isNull does not work correctly because " ~ T.stringof ~ + " has an == operator that is non-reflexive and could not be" ~ + " determined before runtime to be non-reflexive!"); +} + // 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)) - { + static foreach (T; AliasSeq!(float, double, real)) + {{ Nullable!(T, T.init) nf; //Initialized to "null" state assert(nf.isNull); @@ -2979,11 +3719,11 @@ version (none) @system unittest nf.nullify(); assert(nf.isNull); - } + }} } /** -Forces $(D this) to the null state. +Forces `this` to the null state. */ void nullify()() { @@ -2991,7 +3731,7 @@ Forces $(D this) to the null state. } /// -@system unittest +@safe unittest { Nullable!(int, -1) ni = 0; assert(!ni.isNull); @@ -3001,9 +3741,9 @@ Forces $(D this) to the null state. } /** -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. +Assigns `value` to the internally-held state. If the assignment +succeeds, `this` becomes non-null. No null checks are made. Note +that the assignment may leave `this` in the null state. Params: value = A value of type `T` to assign to this `Nullable`. @@ -3012,7 +3752,9 @@ Params: */ void opAssign()(T value) { - _value = value; + import std.algorithm.mutation : swap; + + swap(value, _value); } /** @@ -3037,9 +3779,10 @@ Params: } /** -Gets the value. $(D this) must not be in the null state. -This function is also called for the implicit conversion to $(D T). +Gets the value. `this` must not be in the null state. +This function is also called for the implicit conversion to `T`. +Preconditions: `isNull` must be `false`. Returns: The value held internally by this `Nullable`. */ @@ -3067,15 +3810,15 @@ Returns: } /** -Implicitly converts to $(D T). -$(D this) must not be in the null state. +Implicitly converts to `T`. +`this` must not be in the null state. */ alias get this; } /// ditto auto nullable(alias nullValue, T)(T t) - if (is (typeof(nullValue) == T)) +if (is (typeof(nullValue) == T)) { return Nullable!(T, nullValue)(t); } @@ -3134,9 +3877,29 @@ auto nullable(alias nullValue, T)(T t) assert(a.isNull); } +@nogc nothrow pure @safe unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=19226 + // fully handle non-self-equal nullValue + static struct Fraction + { + int denominator; + bool isNaN() const + { + return denominator == 0; + } + bool opEquals(const Fraction rhs) const + { + return !isNaN && denominator == rhs.denominator; + } + } + alias N = Nullable!(Fraction, Fraction.init); + assert(N.init.isNull); +} + @safe unittest { - static int f(in Nullable!(int, int.min) x) { + static int f(scope const Nullable!(int, int.min) x) { return x.isNull ? 42 : x.get; } Nullable!(int, int.min) a; @@ -3196,19 +3959,19 @@ auto nullable(alias nullValue, T)(T t) ni = other.ni; } } - foreach (S; AliasSeq!(S1, S2)) - { + static foreach (S; AliasSeq!(S1, S2)) + {{ S a; S b = a; S c; c = a; - } + }} } @system unittest { import std.conv : to; - // Bugzilla 10915 + // https://issues.dlang.org/show_bug.cgi?id=10915 Nullable!(int, 1) ni = 1; assert(ni.to!string() == "Nullable.null"); @@ -3241,19 +4004,160 @@ auto nullable(alias nullValue, T)(T t) assert(ntts.to!string() == "2.5"); } +// apply +/** +Unpacks the content of a `Nullable`, performs an operation and packs it again. Does nothing if isNull. + +When called on a `Nullable`, `apply` will unpack the value contained in the `Nullable`, +pass it to the function you provide and wrap the result in another `Nullable` (if necessary). +If the `Nullable` is null, `apply` will return null itself. + +Params: + t = a `Nullable` + fun = a function operating on the content of the nullable + +Returns: + `fun(t.get).nullable` if `!t.isNull`, else `Nullable.init`. + +See also: + $(HTTPS en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad, The `Maybe` monad) + */ +template apply(alias fun) +{ + import std.functional : unaryFun; + + auto apply(T)(auto ref T t) + if (isInstanceOf!(Nullable, T) && is(typeof(unaryFun!fun(T.init.get)))) + { + alias FunType = typeof(unaryFun!fun(T.init.get)); + + enum MustWrapReturn = !isInstanceOf!(Nullable, FunType); + + static if (MustWrapReturn) + { + alias ReturnType = Nullable!FunType; + } + else + { + alias ReturnType = FunType; + } + + if (!t.isNull) + { + static if (MustWrapReturn) + { + return unaryFun!fun(t.get).nullable; + } + else + { + return unaryFun!fun(t.get); + } + } + else + { + return ReturnType.init; + } + } +} + +/// +nothrow pure @nogc @safe unittest +{ + alias toFloat = i => cast(float) i; + + Nullable!int sample; + + // apply(null) results in a null `Nullable` of the function's return type. + Nullable!float f = sample.apply!toFloat; + assert(sample.isNull && f.isNull); + + sample = 3; + + // apply(non-null) calls the function and wraps the result in a `Nullable`. + f = sample.apply!toFloat; + assert(!sample.isNull && !f.isNull); + assert(f.get == 3.0f); +} + +/// +nothrow pure @nogc @safe unittest +{ + alias greaterThree = i => (i > 3) ? i.nullable : Nullable!(typeof(i)).init; + + Nullable!int sample; + + // when the function already returns a `Nullable`, that `Nullable` is not wrapped. + auto result = sample.apply!greaterThree; + assert(sample.isNull && result.isNull); + + // The function may decide to return a null `Nullable`. + sample = 3; + result = sample.apply!greaterThree; + assert(!sample.isNull && result.isNull); + + // Or it may return a value already wrapped in a `Nullable`. + sample = 4; + result = sample.apply!greaterThree; + assert(!sample.isNull && !result.isNull); + assert(result.get == 4); +} + +// test that Nullable.get(default) can merge types +@safe @nogc nothrow pure +unittest +{ + Nullable!ubyte sample = Nullable!ubyte(); + + // Test that get(U) returns the common type of the Nullable type and the parameter type. + assert(sample.get(1000) == 1000); +} + +// Workaround for https://issues.dlang.org/show_bug.cgi?id=20670 +@safe @nogc nothrow pure +unittest +{ + immutable struct S { } + + S[] array = Nullable!(S[])().get(S[].init); +} + +// regression test for https://issues.dlang.org/show_bug.cgi?id=21199 +@safe @nogc nothrow pure +unittest +{ + struct S { int i; } + assert(S(5).nullable.apply!"a.i" == 5); +} + +// regression test for https://issues.dlang.org/show_bug.cgi?id=22176 +@safe @nogc nothrow pure +unittest +{ + struct S + { + int i; + invariant(i != 0); + + // Nullable shouldn't cause S to generate an + // opAssign that would check the invariant. + Nullable!int j; + } + S s; + s = S(5); +} /** -Just like $(D Nullable!T), except that the object refers to a value +Just like `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)). +initially assigned value. Internally `NullableRef!T` only stores a +pointer to `T` (i.e., $(D Nullable!T.sizeof == (T*).sizeof)). */ struct NullableRef(T) { private T* _value; /** -Constructor binding $(D this) to $(D value). +Constructor binding `this` to `value`. Params: value = The value to bind to. @@ -3265,9 +4169,10 @@ Params: 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) + import std.format.spec : FormatSpec; + import std.format.write : formatValue; + // Needs to be a template because of https://issues.dlang.org/show_bug.cgi?id=13737. + void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) { if (isNull) { @@ -3281,7 +4186,7 @@ Params: } /** -Binds the internal state to $(D value). +Binds the internal state to `value`. Params: value = A pointer to a value of type `T` to bind this `NullableRef` to. @@ -3303,7 +4208,7 @@ Params: } /** -Returns $(D true) if and only if $(D this) is in the null state. +Returns `true` if and only if `this` is in the null state. Returns: true if `this` is in the null state, otherwise false. @@ -3325,7 +4230,7 @@ Returns: } /** -Forces $(D this) to the null state. +Forces `this` to the null state. */ void nullify() @safe pure nothrow { @@ -3343,7 +4248,7 @@ Forces $(D this) to the null state. } /** -Assigns $(D value) to the internally-held state. +Assigns `value` to the internally-held state. Params: value = A value of type `T` to assign to this `NullableRef`. @@ -3375,8 +4280,8 @@ Params: } /** -Gets the value. $(D this) must not be in the null state. -This function is also called for the implicit conversion to $(D T). +Gets the value. `this` must not be in the null state. +This function is also called for the implicit conversion to `T`. */ @property ref inout(T) get() inout @safe pure nothrow { @@ -3400,8 +4305,8 @@ This function is also called for the implicit conversion to $(D T). } /** -Implicitly converts to $(D T). -$(D this) must not be in the null state. +Implicitly converts to `T`. +`this` must not be in the null state. */ alias get this; } @@ -3438,7 +4343,7 @@ auto nullableRef(T)(T* t) } @system unittest { - static int f(in NullableRef!int x) { + static int f(scope const NullableRef!int x) { return x.isNull ? 42 : x.get; } int x = 5; @@ -3505,19 +4410,20 @@ auto nullableRef(T)(T* t) ni = other.ni; } } - foreach (S; AliasSeq!(S1, S2)) - { + static foreach (S; AliasSeq!(S1, S2)) + {{ S a; S b = a; S c; c = a; - } + }} } + +// https://issues.dlang.org/show_bug.cgi?id=10915 @system unittest { import std.conv : to; - // Bugzilla 10915 NullableRef!int nri; assert(nri.to!string() == "Nullable.null"); @@ -3549,8 +4455,8 @@ auto nullableRef(T)(T* t) /** -$(D BlackHole!Base) is a subclass of $(D Base) which automatically implements -all abstract member functions in $(D Base) as do-nothing functions. Each +`BlackHole!Base` is a subclass of `Base` which automatically implements +all abstract member functions in `Base` as do-nothing functions. Each auto-implemented function just returns the default value of the return type without doing anything. @@ -3569,7 +4475,7 @@ alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction, isAbstractFu /// @system unittest { - import std.math : isNaN; + import std.math.traits : isNaN; static abstract class C { @@ -3592,7 +4498,7 @@ alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction, isAbstractFu @system unittest { - import std.math : isNaN; + import std.math.traits : isNaN; // return default { @@ -3619,7 +4525,7 @@ alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction, isAbstractFu c.doSomething(); } - // Bugzilla 12058 + // https://issues.dlang.org/show_bug.cgi?id=12058 interface Foo { inout(Object) foo() inout; @@ -3627,11 +4533,22 @@ alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction, isAbstractFu BlackHole!Foo o; } +nothrow pure @nogc @safe unittest +{ + static interface I + { + I foo() nothrow pure @nogc @safe return scope; + } + + scope cb = new BlackHole!I(); + cb.foo(); +} + /** -$(D WhiteHole!Base) is a subclass of $(D Base) which automatically implements +`WhiteHole!Base` is a subclass of `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 +simply throw an `Error` and never return. `Whitehole` is useful for trapping the use of class member functions that haven't been implemented. The name came from @@ -3660,10 +4577,25 @@ alias WhiteHole(Base) = AutoImplement!(Base, generateAssertTrap, isAbstractFunct assertThrown!NotImplementedError(c.notYetImplemented()); // throws an Error } +// https://issues.dlang.org/show_bug.cgi?id=20232 +nothrow pure @safe unittest +{ + static interface I + { + I foo() nothrow pure @safe return scope; + } + + if (0) // Just checking attribute interference + { + scope cw = new WhiteHole!I(); + cw.foo(); + } +} + // / ditto class NotImplementedError : Error { - this(string method) + this(string method) nothrow pure @safe { super(method ~ " is not implemented"); } @@ -3702,25 +4634,25 @@ class NotImplementedError : Error /** -$(D AutoImplement) automatically implements (by default) all abstract member -functions in the class or interface $(D Base) in specified way. +`AutoImplement` automatically implements (by default) all abstract member +functions in the class or interface `Base` in specified way. -The second version of $(D AutoImplement) automatically implements -$(D Interface), while deriving from $(D BaseClass). +The second version of `AutoImplement` automatically implements +`Interface`, while deriving from `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 + Two arguments are passed to `how`: the type `Base` and an alias + to an implemented function. Then `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).) + $(LI `a0`, `a1`, …: arguments passed to the function;) + $(LI `args`: a tuple of the arguments;) + $(LI `self`: an alias to the function itself;) + $(LI `parent`: an alias to the overridden function (if any).) ) You may want to use templated property functions (instead of Implicit @@ -3753,9 +4685,9 @@ string generateLogger(C, alias fun)() @property 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 + An argument is passed to `what`: an alias to a non-final member + function in `Base`. Then `what` must return a boolean value. + Return `true` to indicate that the passed function should be implemented/overridden. -------------------- @@ -3766,10 +4698,10 @@ 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 +Generated code is inserted in the scope of `std.typecons` module. Thus, +any useful functions outside `std.typecons` cannot be used in the generated +code. To workaround this problem, you may `import` necessary things in a +local struct, as done in the `generateLogger()` template in the above example. @@ -3779,16 +4711,16 @@ $(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' + does not override any function". [$(BUGZILLA 2525)] ) + $(LI The `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 + $(LI Using alias template parameter in `how` and/or `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)) +if (!is(how == class)) { private alias autoImplement_helper_ = AutoImplement_Helper!("autoImplement_helper_", "Base", Base, typeof(this), how, what); @@ -3799,13 +4731,69 @@ class AutoImplement(Base, alias how, alias what = isAbstractFunction) : Base class AutoImplement( Interface, BaseClass, alias how, alias what = isAbstractFunction) : BaseClass, Interface - if (is(Interface == interface) && is(BaseClass == class)) +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); } +/// +@system unittest +{ + interface PackageSupplier + { + int foo(); + int bar(); + } + + static abstract class AbstractFallbackPackageSupplier : PackageSupplier + { + protected PackageSupplier default_, fallback; + + this(PackageSupplier default_, PackageSupplier fallback) + { + this.default_ = default_; + this.fallback = fallback; + } + + abstract int foo(); + abstract int bar(); + } + + template fallback(T, alias func) + { + import std.format : format; + // for all implemented methods: + // - try default first + // - only on a failure run & return fallback + enum fallback = q{ + scope (failure) return fallback.%1$s(args); + return default_.%1$s(args); + }.format(__traits(identifier, func)); + } + + // combines two classes and use the second one as fallback + alias FallbackPackageSupplier = AutoImplement!(AbstractFallbackPackageSupplier, fallback); + + class FailingPackageSupplier : PackageSupplier + { + int foo(){ throw new Exception("failure"); } + int bar(){ return 2;} + } + + class BackupPackageSupplier : PackageSupplier + { + int foo(){ return -1; } + int bar(){ return -1;} + } + + auto registry = new FallbackPackageSupplier(new FailingPackageSupplier(), new BackupPackageSupplier()); + + assert(registry.foo() == -1); + assert(registry.bar() == 2); +} + /* * Code-generating stuffs are encupsulated in this helper template so that * namespace pollution, which can cause name confliction with Base's public @@ -3847,7 +4835,7 @@ private static: //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// // Add a non-final check to the cherrypickMethod. - enum bool canonicalPicker(fun.../+[BUG 4217]+/) = + enum bool canonicalPicker(fun.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/) = !__traits(isFinalFunction, fun[0]) && cherrypickMethod!(fun); /* @@ -3949,7 +4937,7 @@ private static: mixin CommonGeneratingPolicy; /* Generates constructor body. Just forward to the base class' one. */ - string generateFunctionBody(ctor.../+[BUG 4217]+/)() @property + string generateFunctionBody(ctor.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/)() @property { enum varstyle = variadicFunctionStyle!(typeof(&ctor[0])); @@ -3970,7 +4958,7 @@ private static: mixin CommonGeneratingPolicy; /* Geneartes method body. */ - string generateFunctionBody(func.../+[BUG 4217]+/)() @property + string generateFunctionBody(func.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/)() @property { return generateMethodBody!(Base, func); // given } @@ -4101,7 +5089,8 @@ private static: } /+ // deep inheritance { - // XXX [BUG 2525,3525] + // https://issues.dlang.org/show_bug.cgi?id=2525 + // https://issues.dlang.org/show_bug.cgi?id=3525 // NOTE: [r494] func.c(504-571) FuncDeclaration::semantic() interface I { void foo(); } interface J : I {} @@ -4111,7 +5100,9 @@ private static: }+/ } -// Issue 17177 - AutoImplement fails on function overload sets with "cannot infer type from overloaded function symbol" +// https://issues.dlang.org/show_bug.cgi?id=17177 +// AutoImplement fails on function overload sets with +// "cannot infer type from overloaded function symbol" @system unittest { static class Issue17177 @@ -4149,13 +5140,15 @@ private static: } } + import std.meta : templateNot; alias Implementation = AutoImplement!(Issue17177, how, templateNot!isFinalFunction); } -version (unittest) +version (StdUnittest) { - // Issue 10647 - // Add prefix "issue10647_" as a workaround for issue 1238 + // https://issues.dlang.org/show_bug.cgi?id=10647 + // Add prefix "issue10647_" as a workaround for + // https://issues.dlang.org/show_bug.cgi?id=1238 private string issue10647_generateDoNothing(C, alias fun)() @property { string stmt; @@ -4307,7 +5300,8 @@ private static: // declaration here to reveal possible hidden functions. code ~= format("alias %s = %s.%s;\n", oset.name, - Policy.BASE_CLASS_ID, // [BUG 2540] super. + // super: https://issues.dlang.org/show_bug.cgi?id=2540 + Policy.BASE_CLASS_ID, oset.name); } } @@ -4380,6 +5374,8 @@ private static: if (atts & FA.property) poatts ~= " @property"; if (atts & FA.safe ) poatts ~= " @safe"; if (atts & FA.trusted ) poatts ~= " @trusted"; + if (atts & FA.scope_ ) poatts ~= " scope"; + if (atts & FA.return_ ) poatts ~= " return"; return poatts; } enum postAtts = make_postAtts(); @@ -4420,7 +5416,7 @@ private static: { preamble ~= "alias self = " ~ name ~ ";\n"; if (WITH_BASE_CLASS && !__traits(isAbstractFunction, func)) - preamble ~= "alias parent = AliasSeq!(__traits(getMember, super, \"" ~ name ~ "\"))[0];"; + preamble ~= `alias parent = __traits(getMember, super, "` ~ name ~ `");`; } // Function body @@ -4458,6 +5454,7 @@ private static: // Parameter storage classes. if (stc & STC.scope_) params ~= "scope "; + if (stc & STC.in_) params ~= "in "; if (stc & STC.out_ ) params ~= "out "; if (stc & STC.ref_ ) params ~= "ref "; if (stc & STC.lazy_ ) params ~= "lazy "; @@ -4508,10 +5505,10 @@ private static: /** -Predefined how-policies for $(D AutoImplement). These templates are also used by -$(D BlackHole) and $(D WhiteHole), respectively. +Predefined how-policies for `AutoImplement`. These templates are also used by +`BlackHole` and `WhiteHole`, respectively. */ -template generateEmptyFunction(C, func.../+[BUG 4217]+/) +template generateEmptyFunction(C, func.../+[https://issues.dlang.org/show_bug.cgi?id=4217]+/) { static if (is(ReturnType!(func) == void)) enum string generateEmptyFunction = q{ @@ -4527,6 +5524,23 @@ template generateEmptyFunction(C, func.../+[BUG 4217]+/) }; } +/// +@system unittest +{ + alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction); + + interface I + { + int foo(); + string bar(); + } + + auto i = new BlackHole!I(); + // generateEmptyFunction returns the default value of the return type without doing anything + assert(i.foo == 0); + assert(i.bar is null); +} + /// ditto template generateAssertTrap(C, func...) { @@ -4535,6 +5549,25 @@ template generateAssertTrap(C, func...) ~ __traits(identifier, func) ~ `");`; } +/// +@system unittest +{ + import std.exception : assertThrown; + + alias WhiteHole(Base) = AutoImplement!(Base, generateAssertTrap); + + interface I + { + int foo(); + string bar(); + } + + auto i = new WhiteHole!I(); + // generateAssertTrap throws an exception for every unimplemented function of the interface + assertThrown!NotImplementedError(i.foo); + assertThrown!NotImplementedError(i.bar); +} + private { pragma(mangle, "_d_toObject") @@ -4568,14 +5601,14 @@ if (is(T == class) || is(T == interface)) @system unittest { - class C { @disable opCast(T)() {} } + class C { @disable void opCast(T)(); } auto c = new C; static assert(!__traits(compiles, cast(Object) c)); auto o = dynamicCast!Object(c); assert(c is o); - interface I { @disable opCast(T)() {} Object instance(); } - interface J { @disable opCast(T)() {} Object instance(); } + interface I { @disable void opCast(T)(); Object instance(); } + interface J { @disable void opCast(T)(); Object instance(); } class D : I, J { Object instance() { return this; } } I i = new D(); static assert(!__traits(compiles, cast(J) i)); @@ -4584,12 +5617,14 @@ if (is(T == class) || is(T == interface)) } /** - * 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. - */ +Supports structural based typesafe conversion. + +If `Source` has structural conformance with the `interface` `Targets`, +wrap creates an internal wrapper class which inherits `Targets` and +wraps the `src` object, then returns it. + +`unwrap` can be used to extract objects which have been wrapped by `wrap`. +*/ template wrap(Targets...) if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) { @@ -4623,6 +5658,14 @@ if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) alias type = F; } + // https://issues.dlang.org/show_bug.cgi?id=12064: Remove NVI members + template OnlyVirtual(members...) + { + enum notFinal(alias T) = !__traits(isFinalFunction, T); + import std.meta : Filter; + alias OnlyVirtual = Filter!(notFinal, members); + } + // Concat all Targets function members into one tuple template Concat(size_t i = 0) { @@ -4630,9 +5673,10 @@ if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) alias Concat = AliasSeq!(); else { - alias Concat = AliasSeq!(GetOverloadedMethods!(Targets[i]), Concat!(i + 1)); + alias Concat = AliasSeq!(OnlyVirtual!(GetOverloadedMethods!(Targets[i]), Concat!(i + 1))); } } + // Remove duplicated functions based on the identifier name and function type covariance template Uniq(members...) { @@ -4708,7 +5752,7 @@ if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) } import std.conv : to; - import std.functional : forward; + import core.lifetime : forward; template generateFun(size_t i) { enum name = TargetMembers[i].name; @@ -4757,8 +5801,8 @@ if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) } public: - mixin mixinAll!( - staticMap!(generateFun, staticIota!(0, TargetMembers.length))); + static foreach (i; 0 .. TargetMembers.length) + mixin(generateFun!i); } } } @@ -4771,15 +5815,7 @@ if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets)) 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). - */ +/// ditto template unwrap(Target) if (isMutable!Target) { @@ -4811,6 +5847,7 @@ if (isMutable!Target) return null; } } + /// ditto template unwrap(Target) if (!isMutable!Target) @@ -4848,6 +5885,7 @@ if (!isMutable!Target) { int reflesh(); } + // does not have structural conformance static assert(!__traits(compiles, d1.wrap!Refleshable)); static assert(!__traits(compiles, h1.wrap!Refleshable)); @@ -4871,14 +5909,15 @@ if (!isMutable!Target) 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) + // structural 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) + // structural downcast (one step) Human hz = fx.unwrap!Human; // Flyer -> Human assert(hz is h1); } + /// @system unittest { @@ -4901,6 +5940,13 @@ if (!isMutable!Target) assert(b.status == 3); static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property); } + +// Internal class to support dynamic cross-casting +private interface Structural +{ + inout(Object) _wrap_getSource() inout @safe pure nothrow; +} + @system unittest { class A @@ -4947,9 +5993,10 @@ if (!isMutable!Target) assert(d.draw(10) == 10); } } + +// https://issues.dlang.org/show_bug.cgi?id=10377 @system unittest { - // Bugzilla 10377 import std.range, std.algorithm; interface MyInputRange(T) @@ -4964,9 +6011,10 @@ if (!isMutable!Target) auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)(); assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); } + +// https://issues.dlang.org/show_bug.cgi?id=10536 @system unittest { - // Bugzilla 10536 interface Interface { int foo(); @@ -4998,12 +6046,39 @@ if (!isMutable!Target) assert(i.bar(10) == 100); } +// https://issues.dlang.org/show_bug.cgi?id=12064 +@system unittest +{ + interface I + { + int foo(); + final int nvi1(){return foo();} + } + + interface J + { + int bar(); + final int nvi2(){return bar();} + } + + class Baz + { + int foo() { return 42;} + int bar() { return 12064;} + } + + auto baz = new Baz(); + auto foobar = baz.wrap!(I, J)(); + assert(foobar.nvi1 == 42); + assert(foobar.nvi2 == 12064); +} + // Make a tuple of non-static function symbols package template GetOverloadedMethods(T) { import std.meta : Filter; - alias allMembers = AliasSeq!(__traits(allMembers, T)); + alias allMembers = __traits(allMembers, T); template follows(size_t i = 0) { static if (i >= allMembers.length) @@ -5091,16 +6166,14 @@ private template TypeMod(T) enum TypeMod = cast(TypeModifier)(mod1 | mod2); } -version (unittest) +@system unittest { - private template UnittestFuncInfo(alias f) + template UnittestFuncInfo(alias f) { enum name = __traits(identifier, f); alias type = FunctionTypeOf!f; } -} -@system unittest -{ + class A { int draw() { return 1; } @@ -5246,47 +6319,6 @@ package template DerivedFunctionType(T...) 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); @@ -5294,8 +6326,8 @@ package template Bind(alias Template, args1...) /** -Options regarding auto-initialization of a $(D RefCounted) object (see -the definition of $(D RefCounted) below). +Options regarding auto-initialization of a `RefCounted` object (see +the definition of `RefCounted` below). */ enum RefCountedAutoInitialize { @@ -5305,58 +6337,94 @@ enum RefCountedAutoInitialize yes, } +/// +@system unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown; + + struct Foo + { + int a = 42; + } + + RefCounted!(Foo, RefCountedAutoInitialize.yes) rcAuto; + RefCounted!(Foo, RefCountedAutoInitialize.no) rcNoAuto; + + assert(rcAuto.refCountedPayload.a == 42); + + assertThrown!AssertError(rcNoAuto.refCountedPayload); + rcNoAuto.refCountedStore.ensureInitialized; + assert(rcNoAuto.refCountedPayload.a == 42); +} + /** -Defines a reference-counted object containing a $(D T) value as +Defines a reference-counted object containing a `T` value as payload. -An instance of $(D RefCounted) is a reference to a structure, +An instance of `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 +and the `T` payload. `RefCounted` uses `malloc` to allocate +the store. As instances of `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 +count. When the reference count goes down to zero, `RefCounted` +will call `destroy` against the payload and call `free` to +deallocate the store. If the `T` payload contains any references to GC-allocated memory, then `RefCounted` will add it to the GC memory that is scanned for pointers, and remove it from GC scanning before -$(D free) is called on the store. +`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 +One important consequence of `destroy` is that it will call the +destructor of the `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 +`T`, such as file handles or pointers to `malloc` memory, will +still be valid during the destructor call. This allows the `T` to deallocate or clean up any non-GC resources immediately after the reference count has reached zero. -$(D RefCounted) is unsafe and should be used with care. No references -to the payload should be escaped outside the $(D RefCounted) object. +`RefCounted` is unsafe and should be used with care. No references +to the payload should be escaped outside the `RefCounted` object. -The $(D autoInit) option makes the object ensure the store is +The `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) +`refCountedStore.isInitialized` or `refCountedStore.ensureInitialized` before attempting to access the payload. Not doing so results in null pointer dereference. + +If `T.this()` is annotated with `@disable` then `autoInit` must be +`RefCountedAutoInitialize.no` in order to compile. */ 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 + version (D_BetterC) + { + private enum enableGCScan = false; + } + else + { + private enum enableGCScan = hasIndirections!T; + } + + // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed + extern(C) private pure nothrow @nogc static { pragma(mangle, "free") void pureFree( void *ptr ); - 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 ); + static if (enableGCScan) + { + pragma(mangle, "gc_addRange") void pureGcAddRange( in void* p, size_t sz, const TypeInfo ti = null ); + pragma(mangle, "gc_removeRange") void pureGcRemoveRange( in void* p ); + } } - /// $(D RefCounted) storage implementation. + /// `RefCounted` storage implementation. struct RefCountedStore { - import core.memory : pureMalloc; private struct Impl { T _payload; @@ -5367,61 +6435,51 @@ if (!is(T == class) && !(is(T == interface))) private void initialize(A...)(auto ref A args) { - import core.exception : onOutOfMemoryError; - import std.conv : emplace; + import core.lifetime : emplace, forward; - _store = cast(Impl*) pureMalloc(Impl.sizeof); - if (_store is null) - onOutOfMemoryError(); - static if (hasIndirections!T) - pureGcAddRange(&_store._payload, T.sizeof); - emplace(&_store._payload, args); + allocateStore(); + version (D_Exceptions) scope(failure) deallocateStore(); + emplace(&_store._payload, forward!args); _store._count = 1; } - private void move(ref T source) + private void move(ref T source) nothrow pure { - 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); + import std.algorithm.mutation : moveEmplace; - // 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` + allocateStore(); + moveEmplace(source, _store._payload); + _store._count = 1; + } - // Can avoid destructing result. - static if (hasElaborateAssign!T || !isAssignable!T) - memcpy(&_store._payload, &source, T.sizeof); + // 'nothrow': can only generate an Error + private void allocateStore() nothrow pure + { + static if (enableGCScan) + { + import std.internal.memory : enforceCalloc; + _store = cast(Impl*) enforceCalloc(1, Impl.sizeof); + pureGcAddRange(&_store._payload, T.sizeof); + } else - _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); + import std.internal.memory : enforceMalloc; + _store = cast(Impl*) enforceMalloc(Impl.sizeof); } + } - _store._count = 1; + private void deallocateStore() nothrow pure + { + static if (enableGCScan) + { + pureGcRemoveRange(&this._store._payload); + } + pureFree(_store); + _store = null; } /** - Returns $(D true) if and only if the underlying store has been + Returns `true` if and only if the underlying store has been allocated and initialized. */ @property nothrow @safe pure @nogc @@ -5432,7 +6490,7 @@ if (!is(T == class) && !(is(T == interface))) /** Returns underlying reference count if it is allocated and initialized - (a positive integer), and $(D 0) otherwise. + (a positive integer), and `0` otherwise. */ @property nothrow @safe pure @nogc size_t refCount() const @@ -5443,9 +6501,18 @@ if (!is(T == class) && !(is(T == interface))) /** Makes sure the payload was properly initialized. Such a call is typically inserted before using the payload. + + This function is unavailable if `T.this()` is annotated with + `@disable`. */ - void ensureInitialized() - { + void ensureInitialized()() + { + // By checking for `@disable this()` and failing early we can + // produce a clearer error message. + static assert(__traits(compiles, { static T t; }), + "Cannot automatically initialize `" ~ fullyQualifiedName!T ~ + "` because `" ~ fullyQualifiedName!T ~ + ".this()` is annotated with `@disable`."); if (!isInitialized) initialize(); } @@ -5462,11 +6529,17 @@ if (!is(T == class) && !(is(T == interface))) /** Constructor that initializes the payload. -Postcondition: $(D refCountedStore.isInitialized) +Postcondition: `refCountedStore.isInitialized` */ this(A...)(auto ref A args) if (A.length > 0) + out + { + assert(refCountedStore.isInitialized); + } + do { - _refCounted.initialize(args); + import core.lifetime : forward; + _refCounted.initialize(forward!args); } /// Ditto @@ -5488,7 +6561,7 @@ Constructor that tracks the reference count appropriately. If $(D /** 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) +down to zero, calls `destroy` agaist the payload and calls `free` to deallocate the corresponding resource. */ ~this() @@ -5497,15 +6570,9 @@ to deallocate the corresponding resource. assert(_refCounted._store._count > 0); if (--_refCounted._store._count) return; - // Done, deallocate + // Done, destroy and deallocate .destroy(_refCounted._store._payload); - static if (hasIndirections!T) - { - pureGcRemoveRange(&_refCounted._store._payload); - } - - pureFree(_refCounted._store); - _refCounted._store = null; + _refCounted.deallocateStore(); } /** @@ -5542,16 +6609,16 @@ Assignment operators RefCountedAutoInitialize.yes), calls $(D refCountedStore.ensureInitialized). Otherwise, just issues $(D assert(refCountedStore.isInitialized)). Used with $(D alias - refCountedPayload this;), so callers can just use the $(D RefCounted) - object as a $(D T). + refCountedPayload this;), so callers can just use the `RefCounted` + object as a `T`. $(BLUE The first overload exists only if $(D autoInit == RefCountedAutoInitialize.yes).) So if $(D autoInit == RefCountedAutoInitialize.no) or called for a constant or immutable object, then - $(D refCountedPayload) will also be qualified as safe and nothrow + `refCountedPayload` will also be qualified as safe and nothrow (but will still assert if not initialized). */ - @property + @property @trusted ref T refCountedPayload() return; /// ditto @@ -5586,10 +6653,28 @@ refCountedStore.ensureInitialized). Otherwise, just issues $(D assert(refCountedStore.isInitialized)). */ alias refCountedPayload this; + + static if (is(T == struct) && !is(typeof((ref T t) => t.toString()))) + { + string toString(this This)() + { + import std.conv : to; + + static if (autoInit) + return to!string(refCountedPayload); + else + { + if (!_refCounted.isInitialized) + return This.stringof ~ "(RefCountedStore(null))"; + else + return to!string(_refCounted._store._payload); + } + } + } } /// -pure @system nothrow @nogc unittest +@betterC pure @system nothrow @nogc unittest { // A pair of an `int` and a `size_t` - the latter being the // reference count - will be dynamically allocated @@ -5639,10 +6724,11 @@ pure @system unittest } auto a = A(4); auto b = a.copy(); - assert(a.x._refCounted._store._count == 2, "BUG 4356 still unfixed"); + assert(a.x._refCounted._store._count == 2, + "https://issues.dlang.org/show_bug.cgi?id=4356 still unfixed"); } -pure @system nothrow @nogc unittest +@betterC pure @system nothrow @nogc unittest { import std.algorithm.mutation : swap; @@ -5650,8 +6736,8 @@ pure @system nothrow @nogc unittest swap(p1, p2); } -// 6606 -@safe pure nothrow @nogc unittest +// https://issues.dlang.org/show_bug.cgi?id=6606 +@betterC @safe pure nothrow @nogc unittest { union U { size_t i; @@ -5665,25 +6751,30 @@ pure @system nothrow @nogc unittest alias SRC = RefCounted!S; } -// 6436 -@system pure unittest +// https://issues.dlang.org/show_bug.cgi?id=6436 +@betterC @system pure unittest { - struct S { this(ref int val) { assert(val == 3); ++val; } } + struct S + { + this(int rval) { assert(rval == 1); } + this(ref int lval) { assert(lval == 3); ++lval; } + } - int val = 3; - auto s = RefCounted!S(val); - assert(val == 4); + auto s1 = RefCounted!S(1); + int lval = 3; + auto s2 = RefCounted!S(lval); + assert(lval == 4); } // gc_addRange coverage -@system pure unittest +@betterC @system pure unittest { struct S { int* p; } auto s = RefCounted!S(null); } -@system pure nothrow @nogc unittest +@betterC @system pure nothrow @nogc unittest { RefCounted!int a; a = 5; //This should not assert @@ -5696,6 +6787,44 @@ pure @system nothrow @nogc unittest RefCounted!(int*) c; } +// https://issues.dlang.org/show_bug.cgi?id=21638 +@betterC @system pure nothrow @nogc unittest +{ + static struct NoDefaultCtor + { + @disable this(); + this(int x) @nogc nothrow pure { this.x = x; } + int x; + } + auto rc = RefCounted!(NoDefaultCtor, RefCountedAutoInitialize.no)(5); + assert(rc.x == 5); +} + +// https://issues.dlang.org/show_bug.cgi?id=20502 +@system unittest +{ + import std.conv : to; + // Check that string conversion is transparent for refcounted + // structs that do not have either toString or alias this. + static struct A { Object a; } + auto a = A(new Object()); + auto r = refCounted(a); + assert(to!string(r) == to!string(a)); + assert(to!string(cast(const) r) == to!string(cast(const) a)); + // Check that string conversion is still transparent for refcounted + // structs that have alias this. + static struct B { int b; alias b this; } + static struct C { B b; alias b this; } + assert(to!string(refCounted(C(B(123)))) == to!string(C(B(123)))); + // https://issues.dlang.org/show_bug.cgi?id=22093 + // Check that uninitialized refcounted structs that previously could be + // converted to strings still can be. + alias R = typeof(r); + R r2; + cast(void) (((const ref R a) => to!string(a))(r2)); + cast(void) to!string(RefCounted!(A, RefCountedAutoInitialize.no).init); +} + /** * Initializes a `RefCounted` with `val`. The template parameter * `T` of `RefCounted` is inferred from `val`. @@ -5705,7 +6834,7 @@ pure @system nothrow @nogc unittest * Params: * val = The value to be reference counted * Returns: - * An initialized $(D RefCounted) containing $(D val). + * An initialized `RefCounted` containing `val`. * See_Also: * $(HTTP en.cppreference.com/w/cpp/memory/shared_ptr/make_shared, C++'s make_shared) */ @@ -5816,13 +6945,20 @@ mixin template Proxy(alias a) static if (accessibleFrom!(const typeof(this))) { - override hash_t toHash() const nothrow @trusted + override size_t toHash() const nothrow @safe { - static if (is(typeof(&a) == ValueType*)) - alias v = a; + static if (__traits(compiles, .hashOf(a))) + return .hashOf(a); else - auto v = a; // if a is (property) function - return typeid(ValueType).getHash(cast(const void*)&v); + // Workaround for when .hashOf is not both @safe and nothrow. + { + static if (is(typeof(&a) == ValueType*)) + alias v = a; + else + auto v = a; // if a is (property) function + // BUG: Improperly casts away `shared`! + return typeid(ValueType).getHash((() @trusted => cast(const void*) &v)()); + } } } } @@ -5839,7 +6975,6 @@ mixin template Proxy(alias a) } 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); @@ -5853,13 +6988,20 @@ mixin template Proxy(alias a) static if (accessibleFrom!(const typeof(this))) { - hash_t toHash() const nothrow @trusted + size_t toHash() const nothrow @safe { - static if (is(typeof(&a) == ValueType*)) - alias v = a; + static if (__traits(compiles, .hashOf(a))) + return .hashOf(a); else - auto v = a; // if a is (property) function - return typeid(ValueType).getHash(cast(const void*)&v); + // Workaround for when .hashOf is not both @safe and nothrow. + { + static if (is(typeof(&a) == ValueType*)) + alias v = a; + else + auto v = a; // if a is (property) function + // BUG: Improperly casts away `shared`! + return typeid(ValueType).getHash((() @trusted => cast(const void*) &v)()); + } } } } @@ -5908,7 +7050,7 @@ mixin template Proxy(alias a) auto ref opOpAssign (string op, this X, V )(auto ref V v) { - return mixin("a " ~op~"= v"); + return mixin("a = a "~op~" v"); } auto ref opIndexOpAssign(string op, this X, V, D...)(auto ref V v, auto ref D i) { @@ -6040,7 +7182,7 @@ mixin template Proxy(alias a) */ @safe unittest { - import std.math; + import std.math.traits : isInfinity; float f = 1.0; assert(!f.isInfinity); @@ -6069,8 +7211,8 @@ mixin template Proxy(alias a) static immutable arr = [1,2,3]; } - foreach (T; AliasSeq!(MyInt, const MyInt, immutable MyInt)) - { + static 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); })); @@ -6102,7 +7244,7 @@ mixin template Proxy(alias a) static assert(T.init == int.init); static assert(T.str == "str"); static assert(T.arr == [1,2,3]); - } + }} } @system unittest { @@ -6114,8 +7256,8 @@ mixin template Proxy(alias a) this(immutable int[] arr) immutable { value = arr; } } - foreach (T; AliasSeq!(MyArray, const MyArray, immutable MyArray)) - { + static 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 @@ -6142,7 +7284,7 @@ mixin template Proxy(alias a) a[] *= 2; assert(a == [8,4,4,2]); a[0 .. 2] /= 2; assert(a == [4,2,4,2]); } - } + }} } @system unittest { @@ -6209,7 +7351,7 @@ mixin template Proxy(alias a) h.ifti2(4); h.ifti3!int(4, 3); - // bug5896 test + // https://issues.dlang.org/show_bug.cgi?id=5896 test assert(h.opCast!int() == 0); assert(cast(int) h == 0); const ih = new const Hoge(new Foo()); @@ -6370,9 +7512,10 @@ mixin template Proxy(alias a) MyFoo2 f2; f2 = f2; } + +// https://issues.dlang.org/show_bug.cgi?id=8613 @safe unittest { - // bug8613 static struct Name { mixin Proxy!val; @@ -6385,28 +7528,41 @@ mixin template Proxy(alias a) bool* b = Name("a") in names; } +// workaround for https://issues.dlang.org/show_bug.cgi?id=19669 +private enum isDIP1000 = __traits(compiles, () @safe { + int x; + int* p; + p = &x; +}); +// excludes struct S; it's 'mixin Proxy!foo' doesn't compile with -dip1000 +static if (isDIP1000) {} else @system unittest { - // bug14213, using function for the payload + // https://issues.dlang.org/show_bug.cgi?id=14213 + // using function for the payload static struct S { int foo() { return 12; } mixin Proxy!foo; } + S s; + assert(s + 1 == 13); + assert(s * 2 == 24); +} + +@system unittest +{ 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 +// as possible. https://issues.dlang.org/show_bug.cgi?id=15561 @safe unittest { static struct MyFloatImpl @@ -6422,11 +7578,11 @@ mixin template Proxy(alias a) assert(!(a>b)); assert(!(a >= b)); } - foreach (T1; AliasSeq!(MyFloatImpl, Typedef!float, Typedef!double, + static foreach (T1; AliasSeq!(MyFloatImpl, Typedef!float, Typedef!double, float, real, Typedef!int, int)) { - foreach (T2; AliasSeq!(MyFloatImpl, Typedef!float)) - { + static foreach (T2; AliasSeq!(MyFloatImpl, Typedef!float)) + {{ T1 a; T2 b; @@ -6448,65 +7604,36 @@ mixin template Proxy(alias a) 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, +based on an existing type. Unlike the `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"); +Params: -// The two Typedefs are _not_ the same type. -static assert(!is(MoneyEuros == MoneyDollars)); ----- + init = Optional initial value for the new type. + cookie = Optional, used to create multiple unique types which are + based on the same origin type `T` Note: If a library routine cannot handle the Typedef type, -you can use the $(D TypedefType) template to extract the +you can use the `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; + // https://issues.dlang.org/show_bug.cgi?id=18415 + // prevent default construction if original type does too. + static if ((is(T == struct) || is(T == union)) && !is(typeof({T t;}))) + { + @disable this(); + } + this(T init) { Typedef_payload = init; @@ -6556,11 +7683,99 @@ struct Typedef(T, T init = T.init, string cookie=null) TD im() {return TD(Typedef_payload.im);} } } + + /** + * Convert wrapped value to a human readable string + */ + string toString(this T)() + { + import std.array : appender; + auto app = appender!string(); + auto spec = singleSpec("%s"); + toString(app, spec); + return app.data; + } + + /// ditto + void toString(this T, W)(ref W writer, scope const ref FormatSpec!char fmt) + if (isOutputRange!(W, char)) + { + formatValue(writer, Typedef_payload, fmt); + } + + /// + @safe unittest + { + import std.conv : to; + + int i = 123; + auto td = Typedef!int(i); + assert(i.to!string == td.to!string); + } +} + +/// +@safe unittest +{ + alias MyInt = Typedef!int; + MyInt foo = 10; + foo++; + assert(foo == 11); +} + +/// custom initialization values +@safe unittest +{ + alias MyIntInit = Typedef!(int, 42); + static assert(is(TypedefType!MyIntInit == int)); + static assert(MyIntInit() == 42); +} + +/// Typedef creates a new type +@safe unittest +{ + alias MyInt = Typedef!int; + static void takeInt(int) {} + static void takeMyInt(MyInt) {} + + int i; + takeInt(i); // ok + static assert(!__traits(compiles, takeMyInt(i))); + + MyInt myInt; + static assert(!__traits(compiles, takeInt(myInt))); + takeMyInt(myInt); // ok +} + +/// Use the optional `cookie` argument to create different types of the same base type +@safe unittest +{ + 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)); +} + +// https://issues.dlang.org/show_bug.cgi?id=12461 +@safe unittest +{ + alias Int = Typedef!int; + + Int a, b; + a += b; + assert(a == 0); } /** -Get the underlying type which a $(D Typedef) wraps. -If $(D T) is not a $(D Typedef) it will alias itself to $(D T). +Get the underlying type which a `Typedef` wraps. +If `T` is not a `Typedef` it will alias itself to `T`. */ template TypedefType(T) { @@ -6573,7 +7788,6 @@ template TypedefType(T) /// @safe unittest { - import std.typecons : Typedef, TypedefType; import std.conv : to; alias MyInt = Typedef!int; @@ -6658,7 +7872,17 @@ template TypedefType(T) assert(drange3[$] == 123); } -@safe @nogc pure nothrow unittest // Bugzilla 11703 +// https://issues.dlang.org/show_bug.cgi?id=18415 +@safe @nogc pure nothrow unittest +{ + struct NoDefCtorS{@disable this();} + union NoDefCtorU{@disable this();} + static assert(!is(typeof({Typedef!NoDefCtorS s;}))); + static assert(!is(typeof({Typedef!NoDefCtorU u;}))); +} + +// https://issues.dlang.org/show_bug.cgi?id=11703 +@safe @nogc pure nothrow unittest { alias I = Typedef!int; static assert(is(typeof(I.min) == I)); @@ -6675,7 +7899,7 @@ template TypedefType(T) @safe unittest { - // bug8655 + // https://issues.dlang.org/show_bug.cgi?id=8655 import std.typecons; import std.bitmanip; static import core.stdc.config; @@ -6691,7 +7915,8 @@ template TypedefType(T) } } -@safe unittest // Issue 12596 +// https://issues.dlang.org/show_bug.cgi?id=12596 +@safe unittest { import std.typecons; alias TD = Typedef!int; @@ -6770,9 +7995,56 @@ template TypedefType(T) assert(s2 == cs2); } +@system unittest // toString +{ + import std.meta : AliasSeq; + import std.conv : to; + + struct TestS {} + class TestC {} + + static foreach (T; AliasSeq!(int, bool, float, double, real, + char, dchar, wchar, + TestS, TestC, + int*, int[], int[2], int[int])) + {{ + T t; + + Typedef!T td; + Typedef!(const T) ctd; + Typedef!(immutable T) itd; + + assert(t.to!string() == td.to!string()); + + static if (!(is(T == TestS) || is(T == TestC))) + { + assert(t.to!string() == ctd.to!string()); + assert(t.to!string() == itd.to!string()); + } + }} +} + +@safe @nogc unittest // typedef'ed type with custom operators +{ + static struct MyInt + { + int value; + int opCmp(MyInt other) + { + if (value < other.value) + return -1; + return !(value == other.value); + } + } + + auto m1 = Typedef!MyInt(MyInt(1)); + auto m2 = Typedef!MyInt(MyInt(2)); + assert(m1 < m2); +} + /** -Allocates a $(D class) object right inside the current scope, -therefore avoiding the overhead of $(D new). This facility is unsafe; +Allocates a `class` object right inside the current scope, +therefore avoiding the overhead of `new`. This facility is unsafe; it is the responsibility of the user to not escape a reference to the object outside the scope. @@ -6789,7 +8061,7 @@ 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)) +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. @@ -6803,7 +8075,7 @@ template scoped(T) @property inout(T) Scoped_payload() inout { - void* alignedStore = cast(void*) aligned(cast(uintptr_t) Scoped_store.ptr); + void* alignedStore = cast(void*) aligned(cast(size_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]; @@ -6829,17 +8101,17 @@ template scoped(T) } /** Returns the _scoped object. - Params: args = Arguments to pass to $(D T)'s constructor. + Params: args = Arguments to pass to `T`'s constructor. */ @system auto scoped(Args...)(auto ref Args args) { - import std.conv : emplace; + import core.lifetime : emplace, forward; Scoped result = void; - void* alignedStore = cast(void*) aligned(cast(uintptr_t) result.Scoped_store.ptr); + void* alignedStore = cast(void*) aligned(cast(size_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); + emplace!(Unqual!T)(result.Scoped_store[d .. $ - size_t.sizeof], forward!args); return result; } } @@ -6926,14 +8198,15 @@ template scoped(T) 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)) +private size_t _alignUp(size_t alignment)(size_t n) +if (alignment > 0 && !((alignment - 1) & alignment)) { enum badEnd = alignment - 1; // 0b11, 0b111, ... return (n + badEnd) & ~badEnd; } -@system unittest // Issue 6580 testcase +// https://issues.dlang.org/show_bug.cgi?id=6580 testcase +@system unittest { enum alignment = (void*).alignof; @@ -6978,7 +8251,7 @@ private uintptr_t _alignUp(uintptr_t alignment)(uintptr_t n) void test(size_t size) { import core.stdc.stdlib; - alloca(size); + cast(void) alloca(size); alignmentTest(); } foreach (i; 0 .. 10) @@ -6991,12 +8264,13 @@ private uintptr_t _alignUp(uintptr_t alignment)(uintptr_t n) byte[size] arr; alignmentTest(); } - foreach (i; AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + static foreach (i; 0 .. 11) test!i(); } } -@system unittest // Original Issue 6580 testcase +// Original https://issues.dlang.org/show_bug.cgi?id=6580 testcase +@system unittest { class C { int i; byte b; } @@ -7049,7 +8323,8 @@ private uintptr_t _alignUp(uintptr_t alignment)(uintptr_t n) assert(A.dead, "asdasd"); } -@system unittest // Issue 8039 testcase +// https://issues.dlang.org/show_bug.cgi?id=8039 testcase +@system unittest { static int dels; static struct S { ~this(){ ++dels; } } @@ -7074,7 +8349,7 @@ private uintptr_t _alignUp(uintptr_t alignment)(uintptr_t n) @system unittest { - // bug4500 + // https://issues.dlang.org/show_bug.cgi?id=4500 class A { this() { a = this; } @@ -7153,11 +8428,16 @@ private uintptr_t _alignUp(uintptr_t alignment)(uintptr_t n) @system unittest { - class C { this(ref int val) { assert(val == 3); ++val; } } + class C + { + this(int rval) { assert(rval == 1); } + this(ref int lval) { assert(lval == 3); ++lval; } + } - int val = 3; - auto s = scoped!C(val); - assert(val == 4); + auto c1 = scoped!C(1); + int lval = 3; + auto c2 = scoped!C(lval); + assert(lval == 4); } @system unittest @@ -7182,13 +8462,13 @@ private uintptr_t _alignUp(uintptr_t alignment)(uintptr_t n) 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 +enumerated type separately. Using `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 +Code calling `getLine` (usually far away from its definition) can't be understood without looking at the documentation, even by users familiar with the API: ---- @@ -7205,8 +8485,8 @@ 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 +After replacing the boolean parameter with an instantiation of `Flag`, code +calling `getLine` can be easily read and understood even by people not fluent with the API: ---- @@ -7220,17 +8500,17 @@ string getLine(Flag!"keepTerminator" 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 +The structs `Yes` and `No` are provided as shorthand for +`Flag!"Name".yes` and `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 +counterproductive to create an alias of a `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) +Passing categorical data by means of unstructured `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 +coupling has a negative effect on code quality. `Flag` offers a simple structuring method for passing yes/no flags to APIs. */ template Flag(string name) { @@ -7238,24 +8518,46 @@ template Flag(string name) { enum Flag : bool { /** - When creating a value of type $(D Flag!"Name"), use $(D + When creating a value of type `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). */ + of type `Flag!"Name"`, compare it against $(D + Flag!"Name".no) or just `false` or `0`. */ no = false, - /** When creating a value of type $(D Flag!"Name"), use $(D + /** When creating a value of type `Flag!"Name"`, use $(D Flag!"Name".yes) for the affirmative option. When using a - value of type $(D Flag!"Name"), compare it against $(D + value of type `Flag!"Name"`, compare it against $(D Flag!"Name".yes). */ yes = true } } +/// +@safe unittest +{ + Flag!"abc" flag; + + assert(flag == Flag!"abc".no); + assert(flag == No.abc); + assert(!flag); + if (flag) assert(0); +} + +/// +@safe unittest +{ + auto flag = Yes.abc; + + assert(flag); + assert(flag == Yes.abc); + if (!flag) assert(0); + if (flag) {} else assert(0); +} + /** -Convenience names that allow using e.g. $(D Yes.encryption) instead of -$(D Flag!"encryption".yes) and $(D No.encryption) instead of $(D +Convenience names that allow using e.g. `Yes.encryption` instead of +`Flag!"encryption".yes` and `No.encryption` instead of $(D Flag!"encryption".no). */ struct Yes @@ -7279,16 +8581,23 @@ struct 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); + Flag!"abc" flag; + + assert(flag == Flag!"abc".no); + assert(flag == No.abc); + assert(!flag); + if (flag) assert(0); +} + +/// +@safe unittest +{ + auto flag = Yes.abc; + + assert(flag); + assert(flag == Yes.abc); + if (!flag) assert(0); + if (flag) {} else assert(0); } /** @@ -7303,12 +8612,12 @@ template isBitFlagEnum(E) { enum isBitFlagEnum = (E.min >= 0) && { - foreach (immutable flag; EnumMembers!E) - { + static foreach (immutable flag; EnumMembers!E) + {{ Base value = flag; value &= value - 1; if (value != 0) return false; - } + }} return true; }(); } @@ -7331,7 +8640,11 @@ template isBitFlagEnum(E) } static assert(isBitFlagEnum!A); +} +/// Test an enum with default (consecutive) values +@safe pure nothrow unittest +{ enum B { A, @@ -7341,7 +8654,11 @@ template isBitFlagEnum(E) } static assert(!isBitFlagEnum!B); +} +/// Test an enum with non-integral values +@safe pure nothrow unittest +{ enum C: double { A = 1 << 0, @@ -7356,7 +8673,7 @@ 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 +a bit count of at most 1, or if the `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: @@ -7377,7 +8694,8 @@ final switch (e) } ---- */ -struct BitFlags(E, Flag!"unsafe" unsafe = No.unsafe) if (unsafe || isBitFlagEnum!(E)) +struct BitFlags(E, Flag!"unsafe" unsafe = No.unsafe) +if (unsafe || isBitFlagEnum!(E)) { @safe @nogc pure nothrow: private: @@ -7502,14 +8820,52 @@ public: { return opBinary!op(flag); } + + bool opDispatch(string name)() const + if (__traits(hasMember, E, name)) + { + enum e = __traits(getMember, E, name); + return (mValue & e) == e; + } + + void opDispatch(string name)(bool set) + if (__traits(hasMember, E, name)) + { + enum e = __traits(getMember, E, name); + if (set) + mValue |= e; + else + mValue &= ~e; + } } -/// BitFlags can be manipulated with the usual operators +/// Set values with the | operator and test with & @safe @nogc pure nothrow unittest { - import std.traits : EnumMembers; + enum Enum + { + A = 1 << 0, + } + + // A default constructed BitFlags has no value set + immutable BitFlags!Enum flags_empty; + assert(!flags_empty.A); + + // Value can be set with the | operator + immutable flags_A = flags_empty | Enum.A; + + // and tested using property access + assert(flags_A.A); + + // or the & operator + assert(flags_A & Enum.A); + // which commutes. + assert(Enum.A & flags_A); +} - // You can use such an enum with BitFlags straight away +/// A default constructed BitFlags has no value set +@safe @nogc pure nothrow unittest +{ enum Enum { None, @@ -7517,82 +8873,153 @@ public: 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 | Enum.B | Enum.C))); 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; +// BitFlags can be variadically initialized +@safe @nogc pure nothrow unittest +{ + import std.traits : EnumMembers; - // And tested with the & operator - assert(flags_A & Enum.A); + enum Enum + { + A = 1 << 0, + B = 1 << 1, + C = 1 << 2 + } - // Which commutes - assert(Enum.A & flags_A); + // Values can also be set using property access + BitFlags!Enum flags; + flags.A = true; + assert(flags & Enum.A); + flags.A = false; + assert(!(flags & Enum.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)); + assert(flags_AB.A && flags_AB.B && !flags_AB.C); // You can use the EnumMembers template to set all flags immutable BitFlags!Enum flags_all = EnumMembers!Enum; + assert(flags_all.A && flags_all.B && flags_all.C); +} - // use & between BitFlags for intersection +/// Binary operations: subtracting and intersecting flags +@safe @nogc pure nothrow unittest +{ + enum Enum + { + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, + } + immutable BitFlags!Enum flags_AB = BitFlags!Enum(Enum.A, Enum.B); immutable BitFlags!Enum flags_BC = BitFlags!Enum(Enum.B, Enum.C); + + // Use the ~ operator for subtracting flags + immutable BitFlags!Enum flags_B = flags_AB & ~BitFlags!Enum(Enum.A); + assert(!flags_B.A && flags_B.B && !flags_B.C); + + // use & between BitFlags for intersection assert(flags_B == (flags_BC & flags_AB)); +} + +/// All the binary operators work in their assignment version +@safe @nogc pure nothrow unittest +{ + enum Enum + { + A = 1 << 0, + B = 1 << 1, + } + + BitFlags!Enum flags_empty, temp, flags_AB; + flags_AB = Enum.A | Enum.B; - // 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)); +} + +/// Conversion to bool and int +@safe @nogc pure nothrow unittest +{ + enum Enum + { + A = 1 << 0, + B = 1 << 1, + } + + BitFlags!Enum flags; // BitFlags with no value set evaluate to false - assert(!flags_empty); + assert(!flags); // BitFlags with at least one value set evaluate to true - assert(flags_A); + flags |= Enum.A; + assert(flags); // This can be useful to check intersection between BitFlags - assert(flags_A & flags_AB); - assert(flags_AB & Enum.A); + BitFlags!Enum flags_AB = Enum.A | Enum.B; + assert(flags & flags_AB); + assert(flags & Enum.A); - // Finally, you can of course get you raw value out of flags - auto value = cast(int) flags_A; + // You can of course get you raw value out of flags + auto value = cast(int) flags; assert(value == Enum.A); } +/// You need to specify the `unsafe` parameter for enums with custom values +@safe @nogc pure nothrow unittest +{ + enum UnsafeEnum + { + A = 1, + B = 2, + C = 4, + BC = B|C + } + static assert(!__traits(compiles, { BitFlags!UnsafeEnum flags; })); + BitFlags!(UnsafeEnum, Yes.unsafe) flags; + + // property access tests for exact match of unsafe enums + flags.B = true; + assert(!flags.BC); // only B + flags.C = true; + assert(flags.BC); // both B and C + flags.B = false; + assert(!flags.BC); // only C + + // property access sets all bits of unsafe enum group + flags = flags.init; + flags.BC = true; + assert(!flags.A && flags.B && flags.C); + flags.A = true; + flags.BC = false; + assert(flags.A && !flags.B && !flags.C); +} + +private enum false_(T) = false; + // 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 +example, `ReplaceType!(int, uint, Tuple!(int, float)[string])` yields +`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. @@ -7605,85 +9032,109 @@ 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...) +alias ReplaceType(From, To, T...) = ReplaceTypeUnless!(false_, From, To, T); + +/// +@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)) + ); +} + +/** +Like $(LREF ReplaceType), but does not perform replacement in types for which +`pred` evaluates to `true`. +*/ +template ReplaceTypeUnless(alias pred, From, To, T...) { + import std.meta; + static if (T.length == 1) { - static if (is(T[0] == From)) - alias ReplaceType = To; + static if (pred!(T[0])) + alias ReplaceTypeUnless = T[0]; + else static if (is(T[0] == From)) + alias ReplaceTypeUnless = To; else static if (is(T[0] == const(U), U)) - alias ReplaceType = const(ReplaceType!(From, To, U)); + alias ReplaceTypeUnless = const(ReplaceTypeUnless!(pred, From, To, U)); else static if (is(T[0] == immutable(U), U)) - alias ReplaceType = immutable(ReplaceType!(From, To, U)); + alias ReplaceTypeUnless = immutable(ReplaceTypeUnless!(pred, From, To, U)); else static if (is(T[0] == shared(U), U)) - alias ReplaceType = shared(ReplaceType!(From, To, U)); + alias ReplaceTypeUnless = shared(ReplaceTypeUnless!(pred, From, To, U)); else static if (is(T[0] == U*, U)) { static if (is(U == function)) - alias ReplaceType = replaceTypeInFunctionType!(From, To, T[0]); + alias ReplaceTypeUnless = replaceTypeInFunctionTypeUnless!(pred, From, To, T[0]); else - alias ReplaceType = ReplaceType!(From, To, U)*; + alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)*; } else static if (is(T[0] == delegate)) { - alias ReplaceType = replaceTypeInFunctionType!(From, To, T[0]); + alias ReplaceTypeUnless = replaceTypeInFunctionTypeUnless!(pred, 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...)) + 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 replaceTemplateArgs = ReplaceTypeUnless!(pred, From, To, T[0]); } - alias ReplaceType = U!(staticMap!(replaceTemplateArgs, V)); + alias ReplaceTypeUnless = 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]; + // don't match with alias this struct below + // https://issues.dlang.org/show_bug.cgi?id=15168 + alias ReplaceTypeUnless = T[0]; else static if (is(T[0] == U[], U)) - alias ReplaceType = ReplaceType!(From, To, U)[]; + alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)[]; else static if (is(T[0] == U[n], U, size_t n)) - alias ReplaceType = ReplaceType!(From, To, U)[n]; + alias ReplaceTypeUnless = ReplaceTypeUnless!(pred, From, To, U)[n]; else static if (is(T[0] == U[V], U, V)) - alias ReplaceType = - ReplaceType!(From, To, U)[ReplaceType!(From, To, V)]; + alias ReplaceTypeUnless = + ReplaceTypeUnless!(pred, From, To, U)[ReplaceTypeUnless!(pred, From, To, V)]; else - alias ReplaceType = T[0]; + alias ReplaceTypeUnless = T[0]; } else static if (T.length > 1) { - alias ReplaceType = AliasSeq!(ReplaceType!(From, To, T[0]), - ReplaceType!(From, To, T[1 .. $])); + alias ReplaceTypeUnless = AliasSeq!(ReplaceTypeUnless!(pred, From, To, T[0]), + ReplaceTypeUnless!(pred, From, To, T[1 .. $])); } else { - alias ReplaceType = AliasSeq!(); + alias ReplaceTypeUnless = AliasSeq!(); } } /// @safe unittest { + import std.traits : isArray; + 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)) - ); + is(ReplaceTypeUnless!(isArray, int, string, int*) == string*) && + is(ReplaceTypeUnless!(isArray, int, string, int[]) == int[]) && + is(ReplaceTypeUnless!(isArray, int, string, Tuple!(int, int[])) + == Tuple!(string, int[])) + ); } -private template replaceTypeInFunctionType(From, To, fun) +private template replaceTypeInFunctionTypeUnless(alias pred, From, To, fun) { - alias RX = ReplaceType!(From, To, ReturnType!fun); - alias PX = AliasSeq!(ReplaceType!(From, To, Parameters!fun)); + alias RX = ReplaceTypeUnless!(pred, From, To, ReturnType!fun); + alias PX = AliasSeq!(ReplaceTypeUnless!(pred, From, To, Parameters!fun)); // Wrapping with AliasSeq is neccesary because ReplaceType doesn't return // tuple if Parameters!fun.length == 1 @@ -7709,12 +9160,14 @@ private template replaceTypeInFunctionType(From, To, fun) result ~= " function"; result ~= "("; - foreach (i, _; PX) + static foreach (i; 0 .. PX.length) { if (i) result ~= ", "; if (storageClasses[i] & ParameterStorageClass.scope_) result ~= "scope "; + if (storageClasses[i] & ParameterStorageClass.in_) + result ~= "in "; if (storageClasses[i] & ParameterStorageClass.out_) result ~= "out "; if (storageClasses[i] & ParameterStorageClass.ref_) @@ -7759,9 +9212,8 @@ private template replaceTypeInFunctionType(From, To, fun) return result; } - //pragma(msg, "gen ==> ", gen()); - mixin("alias replaceTypeInFunctionType = " ~ gen() ~ ";"); + mixin("alias replaceTypeInFunctionTypeUnless = " ~ gen() ~ ";"); } @safe unittest @@ -7807,6 +9259,10 @@ private template replaceTypeInFunctionType(From, To, fun) float function(lazy float, long), int, float, int function(out long, ref const int), float function(out long, ref const float), + int, float, int function(in long, ref const int), + float function(in long, ref const float), + int, float, int function(long, in int), + float function(long, in float), int, int, int, int, int, float, int, float, int, float, const int, const float, @@ -7841,7 +9297,7 @@ private template replaceTypeInFunctionType(From, To, fun) string[3] function(string[] arr, string[2] ...) pure @trusted, ); - // Bugzilla 15168 + // https://issues.dlang.org/show_bug.cgi?id=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; } @@ -7852,7 +9308,8 @@ private template replaceTypeInFunctionType(From, To, fun) ); } -@safe unittest // Bugzilla 17116 +// https://issues.dlang.org/show_bug.cgi?id=17116 +@safe unittest { alias ConstDg = void delegate(float) const; alias B = void delegate(int) const; @@ -7860,6 +9317,30 @@ private template replaceTypeInFunctionType(From, To, fun) static assert(is(B == A)); } + // https://issues.dlang.org/show_bug.cgi?id=19696 +@safe unittest +{ + static struct T(U) {} + static struct S { T!int t; alias t this; } + static assert(is(ReplaceType!(float, float, S) == S)); +} + + // https://issues.dlang.org/show_bug.cgi?id=19697 +@safe unittest +{ + class D(T) {} + class C : D!C {} + static assert(is(ReplaceType!(float, float, C))); +} + +// https://issues.dlang.org/show_bug.cgi?id=16132 +@safe unittest +{ + interface I(T) {} + class C : I!int {} + static assert(is(ReplaceType!(int, string, C) == C)); +} + /** Ternary type with three truth values: @@ -7946,6 +9427,13 @@ struct Ternary { return make((26_504 >> (value + rhs.value)) & 6); } + + /// ditto + Ternary opBinary(string s)(bool rhs) + if (s == "|" || s == "&" || s == "^") + { + return this.opBinary!s(Ternary(rhs)); + } } /// @@ -8030,3 +9518,14 @@ unittest assert(~Ternary.no == Ternary.yes); assert(~Ternary.unknown == Ternary.unknown); } + +@safe @nogc nothrow pure +unittest +{ + Ternary a = Ternary(true); + assert(a == Ternary.yes); + assert((a & false) == Ternary.no); + assert((a | false) == Ternary.yes); + assert((a ^ true) == Ternary.no); + assert((a ^ false) == Ternary.yes); +} diff --git a/libphobos/src/std/typetuple.d b/libphobos/src/std/typetuple.d index dedbdc2..ecf2cc2 100644 --- a/libphobos/src/std/typetuple.d +++ b/libphobos/src/std/typetuple.d @@ -2,10 +2,10 @@ * This module was renamed to disambiguate the term tuple, use * $(MREF std, meta) instead. * - * Copyright: Copyright Digital Mars 2005 - 2015. + * Copyright: Copyright The D Language Foundation 2005 - 2015. * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: - * Source: $(PHOBOSSRC std/_typetuple.d) + * Source: $(PHOBOSSRC std/typetuple.d) * * $(SCRIPT inhibitQuickIndex = 1;) */ @@ -28,6 +28,7 @@ alias TypeTuple = AliasSeq; { return td[0] + cast(int) td[1]; } + assert(foo(1, 2.5) == 3); } /// diff --git a/libphobos/src/std/uni.d b/libphobos/src/std/uni.d deleted file mode 100644 index 0b3da58..0000000 --- a/libphobos/src/std/uni.d +++ /dev/null @@ -1,9768 +0,0 @@ -// 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 =

$0

-DEF = -S_LINK = $+ -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 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)<= 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 - { - T ret; - version (LittleEndian) - ret = __ctfe ? simpleIndex(idx) : - cast(inout(T))(cast(U*) origin)[idx]; - else - ret = simpleIndex(idx); - return ret; - } - - 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) - { - version (LittleEndian) - { - if (__ctfe) - simpleWrite(val, idx); - else - (cast(U*) origin)[idx] = cast(U) val; - } - else - simpleWrite(val, idx); - } - } - 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= 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= 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 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 [%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]< 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 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 sequence - immutable cmpLR = fullCasedCmp(lhs, rhs, str2); - if (!cmpLR) - continue; - // then rhs to 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); decomposition - return Grapheme(partL, partV, jamoTBase + idxT); - else // 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/uni/package.d b/libphobos/src/std/uni/package.d new file mode 100644 index 0000000..318bcb3 --- /dev/null +++ b/libphobos/src/std/uni/package.d @@ -0,0 +1,10637 @@ +// Written in the D programming language. + +/++ + $(P The `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;) +$(DIVC quickindex, +$(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 (`char`), + 16-bit code units in the UTF-16 (`wchar`), + and 32-bit code units in the UTF-32 (`dchar`). + $(I Note that in UTF-32, a code unit is a code point + and is represented by the 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 `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 `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 `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 `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/package.d) + Standards: $(HTTP www.unicode.org/versions/Unicode6.2.0/, Unicode v6.2) + +Macros: + +SECTION =

$0

+DEF = +S_LINK = $+ +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, hasLength, hasSlicing, isForwardRange, isInputRange, + isRandomAccessRange, popFront, put, save; +import std.traits : isConvertibleToString, isIntegral, isSomeChar, + isSomeString, Unqual, isDynamicArray; +// debug = std_uni; + +debug(std_uni) import std.stdio; // writefln, writeln + +private: + + +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 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)<= 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, + return scope const(size_t)[] data) return scope 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.write : 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.algebraic : 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.algebraic : 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.algebraic : 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); + } + do + { + 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 + { + T ret; + version (LittleEndian) + ret = __ctfe ? simpleIndex(idx) : + cast(inout(T))(cast(U*) origin)[idx]; + else + ret = simpleIndex(idx); + return ret; + } + + 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) + { + version (LittleEndian) + { + if (__ctfe) + simpleWrite(val, idx); + else + (cast(U*) origin)[idx] = cast(U) val; + } + else + simpleWrite(val, idx); + } + } + 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); + } + do + { + 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= 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= 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 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 +@safe 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) pure // pure required for -dip25, inferred for -dip1000 + 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) pure // pure required for -dip25, inferred for -dip1000 + if (isDynamicArray!T && !is(Unqual!T == T)) + { + arr = null; + } +} + +// ditto +@safe 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) @trusted + { + import std.internal.memory : enforceMalloc; + + import core.checkedint : mulu; + bool overflow; + size_t nbytes = mulu(size, T.sizeof, overflow); + if (overflow) assert(0); + + auto ptr = cast(T*) enforceMalloc(nbytes); + return ptr[0 .. size]; + } + + static T[] realloc(T)(return scope T[] arr, size_t size) @trusted + { + import std.internal.memory : enforceRealloc; + 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*) enforceRealloc(arr.ptr, nbytes); + 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); + } + + pure @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..$]); + } + + pure @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)(scope ref T[] arr) @trusted + { + import core.memory : pureFree; + if (arr.ptr) + pureFree(arr.ptr); + arr = null; + } +} + +//build hack +alias _RealArray = CowArray!ReallocPolicy; + +pure @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 `T` is a pair of integers that implicitly convert to `V`. + The following code must compile for any pair `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 + `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 `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 `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. + ) +*/ +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)); + } + } + do + { + 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)); + } + } + do + { + data = CowArray!(SP)(intervals); + sanitize(); //enforce invariant: sort intervals etc. + } + + /// + pure @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)); + // test constructor this(Range)(Range intervals) + auto chessPiecesWhite = CodepointInterval(9812, 9818); + auto chessPiecesBlack = CodepointInterval(9818, 9824); + auto set3 = CodepointSet([chessPiecesWhite, chessPiecesBlack]); + foreach (v; '♔'..'♟'+1) + assert(set3[v]); + } + + /** + Get range that spans all of the $(CODEPOINT) intervals in this $(LREF InversionList). + */ + @property auto byInterval() scope + { + // TODO: change this to data[] once the -dip1000 errors have been fixed + // see e.g. https://github.com/dlang/phobos/pull/6638 + import std.array : array; + return Intervals!(typeof(data.array))(data.array); + } + + @safe unittest + { + 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')])); + } + + package(std) @property const(CodepointInterval)[] intervals() const + { + import std.array : array; + return Intervals!(typeof(data[]))(data[]).array; + } + + /** + Tests the presence of code point `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; + } + + /// + pure @safe unittest + { + auto gothic = unicode.Gothic; + // Gothic letter ahsa + assert(gothic['\U00010330']); + // no ascii in Gothic obviously + assert(!gothic['$']); + } + + + // Linear scan for `ch`. Useful only for small sets. + // TODO: + // used internally in std.regex + // should be properly exposed in a public API ? + package(std) 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"); + } + + /// + pure @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 `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]; + } + + /// + pure @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() + { + 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); + } + + /// + pure @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 `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.spec : 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, scope const ref FormatSpec!char fmt) /* const */ + { + import std.format.write : 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, " "); + } + } + + /// + pure @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)"); + } + + pure @safe unittest + { + import std.exception : assertThrown; + import std.format : format, FormatException; + assertThrown!FormatException(format("%z", unicode.ASCII)); + } + + + /** + Add an interval [a, b$(RPAREN) to this set. + */ + ref add()(uint a, uint b) + { + addInterval(a, b); + return this; + } + + /// + pure @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; + } + + pure @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; + } + + /// + pure @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); + } + + package(std) static string toSourceCode(const(CodepointInterval)[] range, string funcName) + { + import std.algorithm.searching : countUntil; + 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) @safe + { + // 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); + // 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; + } + + /** + Generates string with D source code of unary function with name of + `funcName` taking a single `dchar` argument. If `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.array : array; + auto range = byInterval.array(); + return toSourceCode(range, funcName); + } + + /** + True if this set doesn't contain any $(CODEPOINTS). + */ + @property bool empty() const + { + return data.length == 0; + } + + /// + pure @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) + { + import std.range.primitives : hasAssignableElements; + + this(Range sp) scope + { + slice = sp; + start = 0; + end = sp.length; + } + + this(Range sp, size_t s, size_t e) scope + { + 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(std) protection attribute + static if (hasAssignableElements!Range) + package(std) @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 + static if (hasAssignableElements!Range) + package(std) @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 + static if (hasAssignableElements!Range) + package(std) 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 + // https://issues.dlang.org/show_bug.cgi?id=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) scope + in + { + assert(a <= b); + } + do + { + 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 + } + do + { + 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) + } + do + { + 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; +} + +pure @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); + } + do + { + 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; +} + +pure @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); + } + + static 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[])); + }} +} + +pure @safe unittest// core set primitives test +{ + import std.conv : text; + alias AllSets = AliasSeq!(InversionList!GcPolicy, InversionList!ReallocPolicy); + 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 +pure @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)])); +} + + +pure @safe unittest +{ // full set operations + import std.conv : text; + alias AllSets = AliasSeq!(InversionList!GcPolicy, InversionList!ReallocPolicy); + 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)); + } +} + +} + +pure @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)); +} + +pure @safe unittest// iteration & opIndex +{ + import std.algorithm.comparison : equal; + import std.conv : text; + import std.typecons : tuple, Tuple; + + static 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 https://issues.dlang.org/show_bug.cgi?id=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 `Key` to a suitable integer index within the range of `size_t`. + The mapping is constructed by applying predicates from `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; + } +} + +/* + `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 [%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 `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 `v` into interval as + mapped by keys from `a` to `b`. + All slots prior to `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 `v` into slot mapped by `key`. + All slots prior to `key` are filled with the + default filler. + */ + void putValue(Key key, Value v) + { + auto idx = getIndex(key); + enforce(idx >= curIndex, errMsg); + 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 `key` in this `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 `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]< 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 `Prefix` is a valid set of predicates + for `Trie` template having `Key` as the type of keys. + This requires all predicates to be callable, take + single argument of type `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 `Args` is a set of maximum key value followed by valid predicates + for `Trie` template having `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 `CodepointSet`. `sizes` are numbers of bits per level, + with the most significant bits used first. + + Note: The sum of `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 `Trie` + for the Unicode data. + + Specifically unlike `codepointSetTrie` it's allows creating mappings + of `dchar` to an arbitrary type `T`. + + Note: Overload taking `CodepointSet`s will naturally convert + only to bool mapping `Trie`s. + + CodepointTrie is the type of Trie as generated by codepointTrie function. +*/ +public template codepointTrie(T, sizes...) +if (sumOfIntegerTuple!sizes == 21) +{ + alias Prefix = GetBitSlicing!(21, sizes); + + static if (is(TypeOfBitPacked!T == bool)) + { + auto codepointTrie(Set)(const scope 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); + +} + +/// ditto +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(std) 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 `Trie`s + short of using `TrieBuilder` directly. + + Provides a number of convenience overloads. + `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 `Key` + then the whole tuple of `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 `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 `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 `Value` is bool (or BitPacked!(bool, x)) then it's possible + to build `Trie` from a range of open-right intervals of `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 !`filler` i.e. the opposite of filler. + If no filler provided keys inside of the intervals map to true, + and `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 `Value` is bool (or BitPacked!(bool, x)) then it's possible + to build `Trie` simply from an input range of `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 !`filler` i.e. the opposite of filler. + If no filler provided keys map to true, and `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 `Key` is unsigned integer `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 `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 `inp` and testing if + it belongs to the set of $(CODEPOINTS) of this matcher. ) + + $(P The effect on `inp` depends on the kind of function called:) + + $(P Match. If the codepoint is found in the set then range `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); + } + /// + pure @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 `test`/`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; + } + + pure @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 `M` is an UTF Matcher for ranges of `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)); +}); + +pure @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 @trusted + { + 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 + if (isRandomAccessRange!Range && is(ElementType!Range : char) && + !isDynamicArray!Range) + { + 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 + if (isRandomAccessRange!Range && is(ElementType!Range : char) && + !isDynamicArray!Range) + { + 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 + if (isRandomAccessRange!Range && is(ElementType!Range : char) && + !isDynamicArray!Range) + { + 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 + if (isSomeChar!C) + { + return fwdStr!"match"(str); + } + + bool skip(C)(ref C[] str) const + if (isSomeChar!C) + { + return fwdStr!"skip"(str); + } + + bool test(C)(ref C[] str) const + 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(std) @property CherryPick!(Impl, SizesToPick) subMatcher(SizesToPick...)() + { + return CherryPick!(Impl, SizesToPick)(&this); + } + + bool lookup(int size, Mode mode, Range)(ref Range inp) const + { + import std.range : popFrontN; + if (inp.length < size) + { + badEncoding(); + return false; + } + char[size] needle = void; + needle[0] = leadMask!size & inp[0]; + static foreach (i; 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 auto tab(int i)() const { return m.tables[i - 1]; } + bool lookup(int size, Mode mode, Range)(ref Range inp) const + { + return m.lookup!(size, mode)(inp); + } + mixin DefMatcher; + } +} + +template Utf16Matcher() +{ + enum validSize(int sz) = sz >= 1 && sz <= 2; + + void badEncoding() pure @safe + { + 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 + if (isRandomAccessRange!Range && is(ElementType!Range : wchar) && + !isDynamicArray!Range) + { + 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 + if (isRandomAccessRange!Range && is(ElementType!Range : wchar) && + !isDynamicArray!Range) + { + 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 + if (isRandomAccessRange!Range && is(ElementType!Range : wchar) && + !isDynamicArray!Range) + { + 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 + if (isSomeChar!C) + { + return fwdStr!"match"(str); + } + + bool skip(C)(ref C[] str) const + if (isSomeChar!C) + { + return fwdStr!"skip"(str); + } + + bool test(C)(ref C[] str) const + 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(std) @property CherryPick!(Impl, SizesToPick) subMatcher(SizesToPick...)() + { + return CherryPick!(Impl, SizesToPick)(&this); + } + + bool lookupUni(Mode mode, Range)(ref Range inp) const + { + 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 + { + import std.range : popFrontN; + 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 auto ascii()() const { return m.ascii; } + } + + bool lookupUni(Mode mode, Range)(ref Range inp) const + { + 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) +{ + return Utf8Matcher!().build(set); +} + +private auto utf16Matcher(Set)(Set set) +{ + return Utf16Matcher!().build(set); +} + +/** + Constructs a matcher object + to classify $(CODEPOINTS) from the `set` for encoding + that has `Char` as code unit. + + See $(LREF MatcherConcept) for API outline. +*/ +public auto utfMatcher(Char, Set)(Set set) +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(std) auto decoder(C)(C[] s, size_t offset=0) +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); +} + +pure @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); +} + +pure @safe unittest +{ + import std.range : stride; + static bool testAll(Matcher, Range)(scope 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 +pure @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 `set` of $(CODEPOINTS). + + The parameter `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 `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 `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 `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 `T` + but not vise-versa. Users have to ensure the value fits in + the range required and use the `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 `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 `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 `U` from $(LREF BitPacked)!(U, x) + or `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 `Trie` template. + Applying it to a unary lambda function indicates that the returned value always + fits within `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 `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)); + } + do + { + 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); + +@safe pure nothrow @nogc unittest +{ + 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---"); + static foreach (i; 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):"); + static foreach (i; 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(const scope T[] arr){ return arr[idx]; } + alias useItemAt = assumeSize!(impl, 8*T.sizeof); +} + +template useLastItem(T) +{ + size_t impl(const scope 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(std) 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 `CodepointInterval` that lazily decodes compressed data. +@safe package(std) 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 scope { return this; } +} + +@safe pure nothrow @nogc unittest +{ + static assert(isInputRange!DecompressedIntervals); + static assert(isForwardRange!DecompressedIntervals); +} + +//============================================================================ + +version (std_uni_bootstrap){} +else +{ + +// helper for looking up code point sets +ptrdiff_t findUnicodeSet(alias table, C)(const scope C[] name) +{ + 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 +bool loadUnicodeSet(alias table, Set, C)(const scope C[] name, ref Set dest) +{ + auto idx = findUnicodeSet!table(name); + if (idx >= 0) + { + dest = Set(asSet(table[idx].compressed)); + return true; + } + return false; +} + +bool loadProperty(Set=CodepointSet, C) + (const scope 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)(const scope 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)(const scope C[] name) +{ + return findUnicodeSet!table(name) >= 0; +} + +template SetSearcher(alias table, string kind) +{ + /// Run-time checked search. + static auto opCall(C)(const scope 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."); + } +} + +// Characters that need escaping in string posed as regular expressions +package(std) alias Escapables = AliasSeq!('[', ']', '\\', '^', '$', '.', '|', '?', ',', '-', + ';', ':', '#', '&', '%', '/', '<', '>', '`', '*', '+', '(', ')', '{', '}', '~'); + +package(std) CodepointSet 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 +package(std) @property CodepointSet wordCharacter() @safe +{ + return memoizeExpr!("unicode.Alphabetic | unicode.Mn | unicode.Mc + | unicode.Me | unicode.Nd | unicode.Pc")(); +} + +//basic stack, just in case it gets used anywhere else then Parser +package(std) struct Stack(T) +{ +@safe: + T[] data; + @property bool empty(){ return data.empty; } + + @property size_t length(){ return data.length; } + + void push(T val){ data ~= val; } + + @trusted 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]; + } +} + +//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 +package(std) dchar parseUniHex(Range)(ref Range str, size_t maxDigit) +{ + import std.exception : enforce; + //std.conv.parse is both @system and bogus + uint val; + for (int k = 0; k < maxDigit; k++) + { + enforce(!str.empty, "incomplete escape sequence"); + //accepts ascii only, so it's OK to index directly + immutable current = str.front; + 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"); + str.popFront(); + } + enforce(val <= 0x10FFFF, "invalid codepoint"); + return val; +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.exception : collectException; + 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) ++/ +CodepointSet getUnicodeSet(const scope char[] name, bool negated, bool casefold) @safe +{ + 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; +} + +struct UnicodeSetParser(Range) +{ + import std.exception : enforce; + import std.typecons : tuple, Tuple; + Range range; + bool casefold_; + + @property bool empty(){ return range.empty; } + @property dchar front(){ return range.front; } + void popFront(){ range.popFront(); } + + //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() + { + import std.range : drop; + enum privateUseStart = '\U000F0000', privateUseEnd ='\U000FFFFD'; + enum State{ Start, Char, Escape, CharDash, CharDashEscape, + PotentialTwinSymbolOperator } + Operator op = Operator.None; + dchar last; + CodepointSet set; + State state = State.Start; + + void addWithFlags(ref CodepointSet set, uint ch) + { + if (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 (front) + { + case '|': + case '-': + case '~': + case '&': + state = State.PotentialTwinSymbolOperator; + last = front; + break; + case '[': + op = Operator.Union; + goto case; + case ']': + break L_CharTermLoop; + case '\\': + state = State.Escape; + break; + default: + state = State.Char; + last = front; + } + break; + case State.Char: + // xxx last front xxx + switch (front) + { + case '|': + case '~': + case '&': + // then last is treated as normal char and added as implicit union + state = State.PotentialTwinSymbolOperator; + addWithFlags(set, last); + last = front; + 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); + break L_CharTermLoop; + default: + state = State.Char; + addWithFlags(set, last); + last = front; + } + break; + case State.PotentialTwinSymbolOperator: + // xxx last front xxxx + // where last = [|-&~] + if (front == last) + { + op = twinSymbolOperator(last); + popFront();//skip second twin char + break L_CharTermLoop; + } + goto case State.Char; + case State.Escape: + // xxx \ front xxx + switch (front) + { + 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 = unicode.parseControlCode(this); + state = State.Char; + break; + foreach (val; Escapables) + { + case val: + } + last = front; + state = State.Char; + break; + case 'p': + set.add(unicode.parsePropertySpec(this, false, casefold_)); + state = State.Start; + continue L_CharTermLoop; //next char already fetched + case 'P': + set.add(unicode.parsePropertySpec(this, true, casefold_)); + state = State.Start; + continue L_CharTermLoop; //next char already fetched + case 'x': + popFront(); + last = parseUniHex(this, 2); + state = State.Char; + continue L_CharTermLoop; + case 'u': + popFront(); + last = parseUniHex(this, 4); + state = State.Char; + continue L_CharTermLoop; + case 'U': + popFront(); + last = parseUniHex(this, 8); + state = State.Char; + continue L_CharTermLoop; + 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 (front >= privateUseStart && front <= privateUseEnd) + enforce(false, "no matching ']' found while parsing character class"); + enforce(false, "invalid escape sequence"); + } + break; + case State.CharDash: + // xxx last - front xxx + switch (front) + { + case '[': + op = Operator.Union; + goto case; + case ']': + //means dash is a single char not an interval specifier + addWithFlags(set, last); + addWithFlags(set, '-'); + break L_CharTermLoop; + case '-'://set Difference again + addWithFlags(set, last); + op = Operator.Difference; + popFront();//skip '-' + break L_CharTermLoop; + case '\\': + state = State.CharDashEscape; + break; + default: + enforce(last <= front, "inverted range"); + if (casefold_) + { + for (uint ch = last; ch <= front; ch++) + addWithFlags(set, ch); + } + else + set.add(last, front + 1); + state = State.Start; + } + break; + case State.CharDashEscape: + //xxx last - \ front xxx + uint end; + switch (front) + { + 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 = front; + break; + case 'c': + end = unicode.parseControlCode(this); + break; + case 'x': + popFront(); + end = parseUniHex(this, 2); + enforce(last <= end,"inverted range"); + set.add(last, end + 1); + state = State.Start; + continue L_CharTermLoop; + case 'u': + popFront(); + end = parseUniHex(this, 4); + enforce(last <= end,"inverted range"); + set.add(last, end + 1); + state = State.Start; + continue L_CharTermLoop; + case 'U': + popFront(); + end = parseUniHex(this, 8); + enforce(last <= end,"inverted range"); + set.add(last, end + 1); + state = State.Start; + continue L_CharTermLoop; + default: + if (front >= privateUseStart && front <= privateUseEnd) + enforce(false, "no matching ']' found while parsing character class"); + enforce(false, "invalid escape sequence"); + } + // Lookahead to check if it's a \T + // where T is sub-pattern terminator in multi-pattern scheme + auto lookahead = range.save.drop(1); + if (end == '\\' && !lookahead.empty) + { + if (lookahead.front >= privateUseStart && lookahead.front <= privateUseEnd) + enforce(false, "no matching ']' found while parsing character class"); + } + enforce(last <= end,"inverted range"); + set.add(last, end + 1); + state = State.Start; + break; + } + popFront(); + enforce(!empty, "unexpected end of CodepointSet"); + } + return tuple(set, op); + } + + alias ValStack = Stack!(CodepointSet); + alias OpStack = Stack!(Operator); + + CodepointSet parseSet() + { + ValStack vstack; + OpStack opstack; + import std.functional : unaryFun; + enforce(!empty, "unexpected end of input"); + enforce(front == '[', "expected '[' at the start of unicode set"); + // + 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 (front) + { + case '[': + opstack.push(Operator.Open); + popFront(); + enforce(!empty, "unexpected end of character class"); + if (front == '^') + { + opstack.push(Operator.Negate); + popFront(); + enforce(!empty, "unexpected end of character class"); + } + else if (front == ']') // []...] is special cased + { + popFront(); + enforce(!empty, "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(); + popFront(); + if (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) + apply(opstack.pop(),vstack); + assert(vstack.length == 1); + return vstack.top; + } +} + +/** + 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 +{ + import std.exception : enforce; + /** + 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 + `unicode.InBlockName`, to search a script + use `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 `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)(const scope 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 `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 - `unicode.propertyName.propertyValue` for compile-time + checked access and `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); + } + + //parse control code of form \cXXX, c assumed to be the current symbol + static package(std) dchar parseControlCode(Parser)(ref Parser p) + { + with(p) + { + popFront(); + enforce(!empty, "Unfinished escape sequence"); + enforce(('a' <= front && front <= 'z') + || ('A' <= front && front <= 'Z'), + "Only letters are allowed after \\c"); + return front & 0x1f; + } + } + + //parse and return a CodepointSet for \p{...Property...} and \P{...Property..}, + //\ - assumed to be processed, p - is current + static package(std) CodepointSet parsePropertySpec(Range)(ref Range p, + bool negated, bool casefold) + { + static import std.ascii; + with(p) + { + enum MAX_PROPERTY = 128; + char[MAX_PROPERTY] result; + uint k = 0; + popFront(); + enforce(!empty, "eof parsing unicode property spec"); + if (front == '{') + { + popFront(); + while (k < MAX_PROPERTY && !empty && front !='}' + && front !=':') + { + if (front != '-' && front != ' ' && front != '_') + result[k++] = cast(char) std.ascii.toLower(front); + popFront(); + } + enforce(k != MAX_PROPERTY, "invalid property name"); + enforce(front == '}', "} expected "); + } + else + {//single char properties e.g.: \pL, \pN ... + enforce(front < 0x80, "invalid property name"); + result[k++] = cast(char) front; + } + auto s = getUnicodeSet(result[0 .. k], negated, casefold); + enforce(!s.empty, "unrecognized unicode property spec"); + popFront(); + return s; + } + } + + /** + Parse unicode codepoint set from given `range` using standard regex + syntax '[...]'. The range is advanced skiping over regex set definition. + `casefold` parameter determines if the set should be casefolded - that is + include both lower and upper case versions for any letters in the set. + */ + static CodepointSet parseSet(Range)(ref Range range, bool casefold=false) + if (isInputRange!Range && is(ElementType!Range : dchar)) + { + auto usParser = UnicodeSetParser!Range(range, casefold); + auto set = usParser.parseSet(); + range = usParser.range; + return set; + } + + /// + @safe unittest + { + import std.uni : unicode; + string pat = "[a-zA-Z0-9]hello"; + auto set = unicode.parseSet(pat); + // check some of the codepoints + assert(set['a'] && set['A'] && set['9']); + assert(pat == "hello"); + } + +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)(const scope 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 `index`. + Both the resulting length and the `index` are measured + in $(S_LINK Code unit, code units). + + Params: + C = type that is implicitly convertible to `dchars` + input = array of grapheme clusters + index = starting index into `input[]` + + Returns: + length of grapheme cluster ++/ +size_t graphemeStride(C)(const scope C[] input, size_t index) @safe pure +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"); +} + +@safe unittest +{ + // Ensure that graphemeStride is usable from CTFE. + enum c1 = graphemeStride("A", 0); + static assert(c1 == 1); + + enum c2 = graphemeStride("A\u0301", 0); + static assert(c2 == 3); // \u0301 has 2 UTF-8 code units +} + +/++ + Reads one full grapheme cluster from an + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of dchar `inp`. + + For examples see the $(LREF Grapheme) below. + + Note: + This function modifies `inp` and thus `inp` + must be an L-value. ++/ +Grapheme decodeGrapheme(Input)(ref Input inp) +if (isInputRange!Input && is(immutable ElementType!Input == immutable 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 $(LREF 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(immutable ElementType!Range == immutable 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 (StdUnittest) +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); + + static 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 If passed in a range of code points, returns a range with equivalent capabilities.) ++/ +auto byCodePoint(Range)(Range range) +if (isInputRange!Range && is(immutable ElementType!Range == immutable 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 +auto byCodePoint(Range)(Range range) +if (isInputRange!Range && is(immutable ElementType!Range == immutable dchar)) +{ + import std.range.primitives : isBidirectionalRange, popBack; + import std.traits : isNarrowString; + static if (isNarrowString!Range) + { + static struct Result + { + private Range _range; + @property bool empty() { return _range.empty; } + @property dchar front(){ return _range.front; } + void popFront(){ _range.popFront; } + @property auto save() { return Result(_range.save); } + @property dchar back(){ return _range.back; } + void popBack(){ _range.popBack; } + } + static assert(isBidirectionalRange!(Result)); + return Result(range); + } + else + 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; + import std.range : retro; + assert("".byGrapheme.byCodePoint.equal("")); + + string text = "noe\u0308l"; + static assert(!__traits(compiles, "noe\u0308l".byCodePoint.length)); + + auto gText = InputRangeString(text).byGrapheme; + static assert(!isForwardRange!(typeof(gText))); + + auto cpText = gText.byCodePoint; + static assert(!isForwardRange!(typeof(cpText))); + + assert(cpText.walkLength == text.walkLength); + + auto plainCp = text.byCodePoint; + static assert(isForwardRange!(typeof(plainCp))); + assert(equal(plainCp, text)); + assert(equal(retro(plainCp.save), retro(text.save))); + // Check that we still have length for dstring + assert("абвгд"d.byCodePoint.length == 5); +} + +/++ + $(P A structure designed to effectively pack $(CHARACTERS) + of a $(CLUSTER). + ) + + $(P `Grapheme` has value semantics so 2 copies of a `Grapheme` + always refer to distinct objects. In most actual scenarios a `Grapheme` + fits on the stack and avoids memory allocation overhead for all but quite + long clusters. + ) + + See_Also: $(LREF decodeGrapheme), $(LREF graphemeStride) ++/ +@safe struct Grapheme +{ + import std.exception : enforce; + import std.traits : isDynamicArray; + +public: + /// Ctor + this(C)(const scope 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 @nogc nothrow pure @trusted + { + assert(index < length); + return read24(isBig ? ptr_ : small_.ptr, index); + } + + /++ + Writes a $(CODEPOINT) `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) @nogc nothrow pure @trusted + { + 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. + +/ + SliceOverIndexed!Grapheme opSlice(size_t a, size_t b) @nogc nothrow pure return + { + return sliceOverIndexed(a, b, &this); + } + + /// ditto + SliceOverIndexed!Grapheme opSlice() @nogc nothrow pure return + { + return sliceOverIndexed(0, length, &this); + } + + /// Grapheme cluster length in $(CODEPOINTS). + @property size_t length() const @nogc nothrow pure + { + return isBig ? len_ : slen_ & 0x7F; + } + + /++ + Append $(CHARACTER) `ch` to this grapheme. + Warning: + Use of this facility may invalidate grapheme cluster, + see also `valid`. + + See_Also: $(LREF Grapheme.valid) + +/ + ref opOpAssign(string op)(dchar ch) @trusted + { + static if (op == "~") + { + import std.internal.memory : enforceRealloc; + 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*) enforceRealloc(ptr_, nelems); + } + 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 `inp` to this Grapheme. + ref opOpAssign(string op, Input)(scope 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 `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) @nogc nothrow pure @trusted + { + import std.internal.memory : enforceMalloc; + 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*) enforceMalloc(raw_cap); + p[0 .. raw_cap] = ptr_[0 .. raw_cap]; + ptr_ = p; + } + } + + ~this() @nogc nothrow pure @trusted + { + import core.memory : pureFree; + 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() @nogc nothrow pure @trusted + { + import std.internal.memory : enforceMalloc; + static assert(grow.max / 3 - 1 >= grow); + enum nbytes = 3 * (grow + 1); + size_t k = smallLength; + ubyte* p = cast(ubyte*) enforceMalloc(nbytes); + for (int i=0; i len_); + cap_ = grow; + setBig(); + } + + void setBig() @nogc nothrow pure { slen_ |= small_flag; } + + @property size_t smallLength() const @nogc nothrow pure + { + return slen_ & small_mask; + } + @property ubyte isBig() const @nogc nothrow pure + { + 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 `r1` and `r2`. + This function uses simpler comparison rule thus achieving better performance + than $(LREF icmp). However keep in mind the warning below.) + + Params: + r1 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of characters + r2 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of characters + + Returns: + An `int` that is 0 if the strings match, + <0 if `r1` is lexicographically "less" than `r2`, + >0 if `r1` is lexicographically "greater" than `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)(scope S1 r1, scope S2 r2) +if (isInputRange!S1 && isSomeChar!(ElementEncodingType!S1) + && isInputRange!S2 && isSomeChar!(ElementEncodingType!S2)) +{ + import std.internal.unicode_tables : sTable = simpleCaseTable; // generated file + import std.range.primitives : isInfinite; + import std.utf : decodeFront; + import std.traits : isDynamicArray; + import std.typecons : Yes; + static import std.ascii; + + static if ((isDynamicArray!S1 || isRandomAccessRange!S1) + && (isDynamicArray!S2 || isRandomAccessRange!S2) + && !(isInfinite!S1 && isInfinite!S2) + && __traits(compiles, + { + size_t s = size_t.sizeof / 2; + r1 = r1[s .. $]; + r2 = r2[s .. $]; + })) + {{ + // ASCII optimization for dynamic arrays & similar. + size_t i = 0; + static if (isInfinite!S1) + immutable end = r2.length; + else static if (isInfinite!S2) + immutable end = r1.length; + else + immutable end = r1.length > r2.length ? r2.length : r1.length; + for (; i < end; ++i) + { + auto lhs = r1[i]; + auto rhs = r2[i]; + if ((lhs | rhs) >= 0x80) goto NonAsciiPath; + if (lhs == rhs) continue; + auto lowDiff = std.ascii.toLower(lhs) - std.ascii.toLower(rhs); + if (lowDiff) return lowDiff; + } + static if (isInfinite!S1) + return 1; + else static if (isInfinite!S2) + return -1; + else + return (r1.length > r2.length) - (r2.length > r1.length); + + NonAsciiPath: + r1 = r1[i .. $]; + r2 = r2[i .. $]; + // Fall through to standard case. + }} + + while (!r1.empty) + { + immutable lhs = decodeFront!(Yes.useReplacementDchar)(r1); + if (r2.empty) + return 1; + immutable rhs = decodeFront!(Yes.useReplacementDchar)(r2); + int diff = lhs - rhs; + if (!diff) + continue; + if ((lhs | rhs) < 0x80) + { + immutable d = std.ascii.toLower(lhs) - std.ascii.toLower(rhs); + if (!d) continue; + return d; + } + 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 int(r2.empty) - 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(scope const(char)[] str1, scope const(char)[] str2) + { return sicmp!(const(char)[], const(char)[])(str1, str2); } + + int sicmp(scope const(wchar)[] str1, scope const(wchar)[] str2) + { return sicmp!(const(wchar)[], const(wchar)[])(str1, str2); } + + int sicmp(scope const(dchar)[] str1, scope 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 r2.length ? r2.length : r1.length; + for (; i < end; ++i) + { + auto lhs = r1[i]; + auto rhs = r2[i]; + if ((lhs | rhs) >= 0x80) goto NonAsciiPath; + if (lhs == rhs) continue; + auto lowDiff = std.ascii.toLower(lhs) - std.ascii.toLower(rhs); + if (lowDiff) return lowDiff; + } + static if (isInfinite!S1) + return 1; + else static if (isInfinite!S2) + return -1; + else + return (r1.length > r2.length) - (r2.length > r1.length); + + NonAsciiPath: + r1 = r1[i .. $]; + r2 = r2[i .. $]; + // Fall through to standard case. + }} + + 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 sequence + immutable cmpLR = fullCasedCmp(lhs, rhs, str2); + if (!cmpLR) + continue; + // then rhs to 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!( + { + static foreach (cfunc; AliasSeq!(icmp, sicmp)) + {{ + static foreach (S1; AliasSeq!(string, wstring, dstring)) + static foreach (S2; AliasSeq!(string, wstring, dstring)) + { + 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); + // https://issues.dlang.org/show_bug.cgi?id=11057 + assert( icmp("K", "L") < 0 ); + }); +} + +// https://issues.dlang.org/show_bug.cgi?id=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(std) 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 `ch`. +*/ +package(std) 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 `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 `first` comes before `second` in the original text, + usually meaning that the first is a starter. + + Note: Hangul syllables are not covered by this function. + See `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) `ch`. + If no decomposition is available returns a $(LREF Grapheme) + with the `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 `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 `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 `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 `ch` is not a composed syllable + then this function returns $(LREF Grapheme) containing only `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); decomposition + return Grapheme(partL, partV, jamoTBase + idxT); + else // 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 (`lead`), + a `vowel` and optional `trailing` consonant jamos. + + On success returns the composed LV or LVT hangul syllable. + + If any of `lead` and `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 `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)(return scope 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; + () @trusted { + 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 () @trusted inout { 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)(scope 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, const scope 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 `ch` is always allowed (Quick_Check=YES) in normalization + form `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 + // https://issues.dlang.org/show_bug.cgi?id=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 `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 `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 `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 ElementEncodingType!S[] toCase(alias indexFn, uint maxIdx, alias tableFn, alias asciiConvert, S)(S s) +if (isSomeString!S || (isRandomAccessRange!S && hasLength!S && hasSlicing!S && isSomeChar!(ElementType!S))) +{ + import std.array : appender, array; + import std.ascii : isASCII; + import std.utf : byDchar, codeLength; + + alias C = ElementEncodingType!S; + + auto r = s.byDchar; + for (size_t i; !r.empty; i += r.front.codeLength!C , r.popFront()) + { + auto cOuter = r.front; + ushort idx = indexFn(cOuter); + if (idx == ushort.max) + continue; + auto result = appender!(C[])(); + result.reserve(s.length); + result.put(s[0 .. i]); + foreach (dchar c; s[i .. $].byDchar) + { + 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; + } + + static if (isSomeString!S) + return s; + else + return s.array; +} + +// https://issues.dlang.org/show_bug.cgi?id=12428 +@safe unittest +{ + import std.array : replicate; + auto s = "abcdefghij".replicate(300); + s = s[0 .. 10]; + + toUpper(s); + + assert(s == "abcdefghij"); +} + +// https://issues.dlang.org/show_bug.cgi?id=18993 +@safe unittest +{ + static assert(`몬스터/A`.toLower.length == `몬스터/a`.toLower.length); +} + + +// 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 an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * or a 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 input range of `dchar`s + * + * 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 +{ + static struct TestAliasedString + { + string get() @safe @nogc pure nothrow { return _s; } + alias get this; + @disable this(this); + string _s; + } + + static 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; + } + } + assert(testAliasedString!asLowerCase("hEllo")); + assert(testAliasedString!asUpperCase("hEllo")); + assert(testAliasedString!asCapitalized("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 an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * 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 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)(const scope 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 `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 `s` does not have any uppercase characters, then `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 `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 `s` does not have any lowercase characters, then `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 `c` is a Unicode uppercase $(CHARACTER), then its lowercase equivalent + is returned. Otherwise `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; +} + +/++ + Creates a new array which is identical to `s` except that all of its + characters are converted to lowercase (by preforming Unicode lowercase mapping). + If none of `s` characters were affected, then `s` itself is returned if `s` is a + `string`-like type. + + Params: + s = A $(REF_ALTTEXT random access range, isRandomAccessRange, std,range,primitives) + of characters + Returns: + An array with the same element type as `s`. ++/ +ElementEncodingType!S[] toLower(S)(S s) +if (isSomeString!S || (isRandomAccessRange!S && hasLength!S && hasSlicing!S && isSomeChar!(ElementType!S))) +{ + static import std.ascii; + + static if (isSomeString!S) + return () @trusted { return toCase!(LowerTriple, std.ascii.toLower)(s); } (); + else + 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("")); + } + } +} + + +@safe unittest +{ + 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"); +} + +// https://issues.dlang.org/show_bug.cgi?id=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); + + // https://issues.dlang.org/show_bug.cgi?id=12455 + dchar c = 'İ'; // '\U0130' LATIN CAPITAL LETTER I WITH DOT ABOVE + assert(isUpper(c)); + assert(toLower(c) == 'i'); + // extends on https://issues.dlang.org/show_bug.cgi?id=12455 report + // check simple-case toUpper too + c = '\u1f87'; + assert(isLower(c)); + assert(toUpper(c) == '\u1F8F'); +} + +@safe pure unittest +{ + import std.algorithm.comparison : cmp, equal; + import std.utf : byCodeUnit; + auto r1 = "FoL".byCodeUnit; + assert(r1.toLower.cmp("fol") == 0); + auto r2 = "A\u0460B\u0461d".byCodeUnit; + assert(r2.toLower.cmp("a\u0461b\u0461d") == 0); +} + +/++ + If `c` is a Unicode lowercase $(CHARACTER), then its uppercase equivalent + is returned. Otherwise `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; +} + +/// +@safe 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)); + } +} + +/++ + Allocates a new array which is identical to `s` except that all of its + characters are converted to uppercase (by preforming Unicode uppercase mapping). + If none of `s` characters were affected, then `s` itself is returned if `s` + is a `string`-like type. + + Params: + s = A $(REF_ALTTEXT random access range, isRandomAccessRange, std,range,primitives) + of characters + Returns: + An new array with the same element type as `s`. ++/ +ElementEncodingType!S[] toUpper(S)(S s) +if (isSomeString!S || (isRandomAccessRange!S && hasLength!S && hasSlicing!S && isSomeChar!(ElementType!S))) +{ + static import std.ascii; + + static if (isSomeString!S) + return () @trusted { return toCase!(UpperTriple, std.ascii.toUpper)(s); } (); + else + 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)); + } + static 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; [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]); + } + }} +} + +// test random access ranges +@safe pure unittest +{ + import std.algorithm.comparison : cmp; + import std.utf : byCodeUnit; + auto s1 = "FoL".byCodeUnit; + assert(s1.toUpper.cmp("FOL") == 0); + auto s2 = "a\u0460B\u0461d".byCodeUnit; + assert(s2.toUpper.cmp("A\u0460B\u0460D") == 0); +} + +/++ + Returns whether `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 `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 `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 `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 `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 `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 `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 `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 `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 `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 `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 `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 `c` is a Unicode high surrogate (lead surrogate). ++/ +@safe pure nothrow @nogc +bool isSurrogateHi(dchar c) +{ + return (0xD800 <= c && c <= 0xDBFF); +} + +/++ + Returns whether `c` is a Unicode low surrogate (trail surrogate). ++/ +@safe pure nothrow @nogc +bool isSurrogateLo(dchar c) +{ + return (0xDC00 <= c && c <= 0xDFFF); +} + +/++ + Returns whether `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...)(const scope 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 index fcc902c..bf7cbc0 100644 --- a/libphobos/src/std/uri.d +++ b/libphobos/src/std/uri.d @@ -9,14 +9,14 @@ * Escape sequences consist of $(B %) followed by two hex digits. * * See_Also: - * $(LINK2 http://www.ietf.org/rfc/rfc3986.txt, RFC 3986)
+ * $(LINK2 https://www.ietf.org/rfc/rfc3986.txt, RFC 3986)
* $(LINK2 http://en.wikipedia.org/wiki/Uniform_resource_identifier, Wikipedia) - * Copyright: Copyright Digital Mars 2000 - 2009. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/uri.d) */ -/* Copyright Digital Mars 2000 - 2009. +/* Copyright The D Language Foundation 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) @@ -36,6 +36,13 @@ class URIException : Exception mixin basicExceptionCtors; } +/// +@safe unittest +{ + import std.exception : assertThrown; + assertThrown!URIException("%ab".decode); +} + private enum { URI_Alpha = 1, @@ -65,11 +72,8 @@ private immutable ubyte[128] uri_flags = // indexed by character return uflags; })(); -private string URI_Encode(dstring str, uint unescapedSet) +private string URI_Encode(dstring str, uint unescapedSet) @safe pure { - import core.exception : OutOfMemoryError; - import core.stdc.stdlib : alloca; - uint j; uint k; dchar V; @@ -77,13 +81,13 @@ private string URI_Encode(dstring str, uint unescapedSet) // result buffer char[50] buffer = void; - char* R; + char[] R; uint Rlen; uint Rsize; // alloc'd size immutable len = str.length; - R = buffer.ptr; + R = buffer[]; Rsize = buffer.length; Rlen = 0; @@ -95,19 +99,10 @@ private string URI_Encode(dstring str, uint unescapedSet) { if (Rlen == Rsize) { - char* R2; + 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 = new char[Rsize]; R2[0 .. Rlen] = R[0 .. Rlen]; R = R2; } @@ -155,19 +150,10 @@ private string URI_Encode(dstring str, uint unescapedSet) if (Rlen + L * 3 > Rsize) { - char *R2; + 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 = new char[Rsize]; R2[0 .. Rlen] = R[0 .. Rlen]; R = R2; } @@ -186,6 +172,18 @@ private string URI_Encode(dstring str, uint unescapedSet) return R[0 .. Rlen].idup; } +@safe pure unittest +{ + import std.exception : assertThrown; + + assert(URI_Encode("", 0) == ""); + assert(URI_Encode(URI_Decode("%F0%BF%BF%BF", 0), 0) == "%F0%BF%BF%BF"); + dstring a; + a ~= cast(dchar) 0xFFFFFFFF; + assertThrown(URI_Encode(a, 0)); + assert(URI_Encode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0).length == 3 * 60); +} + private uint ascii2hex(dchar c) @nogc @safe pure nothrow { return (c <= '9') ? c - '0' : @@ -193,11 +191,9 @@ private uint ascii2hex(dchar c) @nogc @safe pure nothrow c - 'a' + 10; } -private dstring URI_Decode(Char)(in Char[] uri, uint reservedSet) +private dstring URI_Decode(Char)(scope const(Char)[] uri, uint reservedSet) if (isSomeChar!Char) { - import core.exception : OutOfMemoryError; - import core.stdc.stdlib : alloca; import std.ascii : isHexDigit; uint j; @@ -205,25 +201,12 @@ if (isSomeChar!Char) uint V; dchar C; - // Result array, allocated on stack - dchar* R; uint Rlen; - immutable len = uri.length; - auto s = uri.ptr; + auto s = uri; - // 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"); - } + dchar[] R = new dchar[Rsize]; Rlen = 0; for (k = 0; k != len; k++) @@ -307,13 +290,28 @@ if (isSomeChar!Char) return R[0 .. Rlen].idup; } +@safe pure unittest +{ + import std.exception : assertThrown; + + assert(URI_Decode("", 0) == ""); + assertThrown!URIException(URI_Decode("%", 0)); + assertThrown!URIException(URI_Decode("%xx", 0)); + assertThrown!URIException(URI_Decode("%FF", 0)); + assertThrown!URIException(URI_Decode("%C0", 0)); + assertThrown!URIException(URI_Decode("%C0000000", 0)); + assertThrown!URIException(URI_Decode("%C0%xx0000", 0)); + assertThrown!URIException(URI_Decode("%C0%C00000", 0)); + assertThrown!URIException(URI_Decode("%F7%BF%BF%BF", 0)); + assert(URI_Decode("%23", URI_Hash) == "%23"); +} + /************************************* * 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) +string decode(Char)(scope const(Char)[] encodedURI) if (isSomeChar!Char) { import std.algorithm.iteration : each; @@ -324,12 +322,20 @@ if (isSomeChar!Char) return r; } +/// +@safe unittest +{ + assert("foo%20bar".decode == "foo bar"); + assert("%3C%3E.@.%E2%84%A2".decode == "<>.@.™"); + assert("foo&/".decode == "foo&/"); + assert("!@#$&*(".decode == "!@#$&*("); +} + /******************************* * Decodes the URI string encodedURI into a UTF-8 string and returns it. All * escape sequences are decoded. */ - -string decodeComponent(Char)(in Char[] encodedURIComponent) +string decodeComponent(Char)(scope const(Char)[] encodedURIComponent) if (isSomeChar!Char) { import std.algorithm.iteration : each; @@ -340,12 +346,19 @@ if (isSomeChar!Char) return r; } +/// +@safe unittest +{ + assert("foo%2F%26".decodeComponent == "foo/&"); + assert("dl%C3%A4ng%20r%C3%B6cks".decodeComponent == "dläng röcks"); + assert("!%40%23%24%25%5E%26*(".decodeComponent == "!@#$%^&*("); +} + /***************************** * 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) +string encode(Char)(scope const(Char)[] uri) if (isSomeChar!Char) { import std.utf : toUTF32; @@ -353,12 +366,21 @@ if (isSomeChar!Char) return URI_Encode(s, URI_Reserved | URI_Hash | URI_Alpha | URI_Digit | URI_Mark); } +/// +@safe unittest +{ + assert("foo bar".encode == "foo%20bar"); + assert("<>.@.™".encode == "%3C%3E.@.%E2%84%A2"); + assert("foo/#?a=1&b=2".encode == "foo/#?a=1&b=2"); + assert("dlang+rocks!".encode == "dlang+rocks!"); + assert("!@#$%^&*(".encode == "!@#$%25%5E&*("); +} + /******************************** * 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) +string encodeComponent(Char)(scope const(Char)[] uriComponent) if (isSomeChar!Char) { import std.utf : toUTF32; @@ -366,6 +388,16 @@ if (isSomeChar!Char) return URI_Encode(s, URI_Alpha | URI_Digit | URI_Mark); } +/// +@safe unittest +{ + assert("!@#$%^&*(".encodeComponent == "!%40%23%24%25%5E%26*("); + assert("<>.@.™".encodeComponent == "%3C%3E.%40.%E2%84%A2"); + assert("foo/&".encodeComponent == "foo%2F%26"); + assert("dläng röcks".encodeComponent == "dl%C3%A4ng%20r%C3%B6cks"); + assert("dlang+rocks!".encodeComponent == "dlang%2Brocks!"); +} + /* Encode associative array using www-form-urlencoding * * Params: @@ -374,13 +406,13 @@ if (isSomeChar!Char) * Returns: * A string encoded using www-form-urlencoding. */ -package string urlEncode(in string[string] values) +package string urlEncode(scope string[string] values) @safe pure { if (values.length == 0) return ""; import std.array : Appender; - import std.format : formattedWrite; + import std.format.write : formattedWrite; Appender!string enc; enc.reserve(values.length * 128); @@ -396,7 +428,7 @@ package string urlEncode(in string[string] values) return enc.data; } -@system unittest +@safe pure unittest { // @system because urlEncode -> encodeComponent -> URI_Encode // URI_Encode uses alloca and pointer slicing @@ -414,7 +446,7 @@ package string urlEncode(in string[string] values) * len it does, and s[0 .. len] is the slice of s[] that is that URL */ -ptrdiff_t uriLength(Char)(in Char[] s) +ptrdiff_t uriLength(Char)(scope const(Char)[] s) if (isSomeChar!Char) { /* Must start with one of: @@ -467,7 +499,7 @@ if (isSomeChar!Char) } /// -@safe unittest +@safe pure unittest { string s1 = "http://www.digitalmars.com/~fred/fredsRX.html#foo end!"; assert(uriLength(s1) == 49); @@ -476,6 +508,11 @@ if (isSomeChar!Char) assert(uriLength("issue 14924") < 0); } +@safe pure nothrow @nogc unittest +{ + assert(uriLength("") == -1); + assert(uriLength("https://www") == -1); +} /*************************** * Does string s[] start with an email address? @@ -485,13 +522,16 @@ if (isSomeChar!Char) * References: * RFC2822 */ -ptrdiff_t emailLength(Char)(in Char[] s) +ptrdiff_t emailLength(Char)(scope const(Char)[] s) if (isSomeChar!Char) { import std.ascii : isAlpha, isAlphaNum; ptrdiff_t i; + if (s.length == 0) + return -1; + if (!isAlpha(s[0])) return -1; @@ -534,7 +574,7 @@ if (isSomeChar!Char) } /// -@safe unittest +@safe pure unittest { string s1 = "my.e-mail@www.example-domain.com with garbage added"; assert(emailLength(s1) == 32); @@ -543,8 +583,7 @@ if (isSomeChar!Char) assert(emailLength("issue 14924") < 0); } - -@system unittest +@safe pure unittest { //@system because of encode -> URI_Encode debug(uri) writeln("uri.encodeURI.unittest"); @@ -575,8 +614,8 @@ if (isSomeChar!Char) debug(uri) writeln(result); import std.meta : AliasSeq; - foreach (StringType; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static foreach (StringType; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + {{ import std.conv : to; StringType decoded1 = source.to!StringType; string encoded1 = encode(decoded1); @@ -589,5 +628,15 @@ if (isSomeChar!Char) assert(encoded2 == target.to!StringType); // check that `encoded2` wasn't changed assert(decoded2 == source); assert(encoded2 == encode(decoded2).to!StringType); - } + }} +} + +@safe pure nothrow @nogc unittest +{ + assert(emailLength("") == -1); + assert(emailLength("@") == -1); + assert(emailLength("abcd") == -1); + assert(emailLength("blah@blub") == -1); + assert(emailLength("blah@blub.") == -1); + assert(emailLength("blah@blub.domain") == -1); } diff --git a/libphobos/src/std/utf.d b/libphobos/src/std/utf.d index beb4d8f..be60e7a 100644 --- a/libphobos/src/std/utf.d +++ b/libphobos/src/std/utf.d @@ -7,6 +7,7 @@ $(D '\u0000' <= character <= '\U0010FFFF'). $(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD Decode) $(TD @@ -40,6 +41,7 @@ $(TR $(TD Index) $(TD )) $(TR $(TD Validation) $(TD $(LREF isValidDchar) + $(LREF isValidCodepoint) $(LREF validate) )) $(TR $(TD Miscellaneous) $(TD @@ -47,29 +49,32 @@ $(TR $(TD Miscellaneous) $(TD $(LREF UseReplacementDchar) $(LREF UTFException) )) -) +)) See_Also: $(LINK2 http://en.wikipedia.org/wiki/Unicode, Wikipedia)
$(LINK http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8)
$(LINK http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335) - Copyright: Copyright Digital Mars 2000 - 2012. + Copyright: Copyright The D Language Foundation 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) + Authors: $(HTTP digitalmars.com, Walter Bright) and + $(HTTP jmdavisprog.com, Jonathan M Davis) + Source: $(PHOBOSSRC std/utf.d) +/ module std.utf; -import std.exception; // basicExceptionCtors -import std.meta; // AliasSeq +import std.exception : basicExceptionCtors; +import core.exception : UnicodeException; +import std.meta : AliasSeq; import std.range.primitives; -import std.traits; // isSomeChar, isSomeString -import std.typecons; // Flag, Yes, No +import std.traits : isAutodecodableString, isPointer, isSomeChar, + isSomeString, isStaticArray, Unqual, isConvertibleToString; +import std.typecons : Flag, Yes, No; /++ Exception thrown on errors in std.utf functions. +/ -class UTFException : Exception +class UTFException : UnicodeException { import core.internal.string : unsignedToTempString, UnsignedStringBuf; @@ -77,7 +82,7 @@ class UTFException : Exception size_t len; @safe pure nothrow @nogc - UTFException setSequence(scope uint[] data...) + UTFException setSequence(scope uint[] data...) return { assert(data.length <= 4); @@ -87,23 +92,30 @@ class UTFException : Exception return this; } - // FIXME: Use std.exception.basicExceptionCtors here once bug #11500 is fixed + // FIXME: Use std.exception.basicExceptionCtors here once + // https://issues.dlang.org/show_bug.cgi?id=11500 is fixed + /** + Standard exception constructors. + */ this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @nogc @safe pure nothrow { - super(msg, file, line, next); + super(msg, 0, file, line, next); } - + /// ditto 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); + msg ~= " (at index " ~ unsignedToTempString(index, buf) ~ ")"; + super(msg, index, file, line, next); } - + /** + Returns: + A `string` detailing the invalid UTF sequence. + */ override string toString() const { if (len == 0) @@ -122,7 +134,7 @@ class UTFException : Exception { UnsignedStringBuf buf = void; result ~= ' '; - auto h = unsignedToTempString(i, buf, 16); + auto h = unsignedToTempString!16(i, buf); if (h.length == 1) result ~= '0'; result ~= h; @@ -139,6 +151,19 @@ class UTFException : Exception } } +/// +@safe unittest +{ + import std.exception : assertThrown; + + char[4] buf; + 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)); +} + /* Provide array of invalidly encoded UTF strings. Useful for testing. @@ -240,10 +265,10 @@ if (isSomeChar!Char) c = code point to check Returns: - $(D true) iff $(D c) is a valid Unicode code point + `true` if and only if `c` is a valid Unicode code point Note: - $(D '\uFFFE') and $(D '\uFFFF') are considered valid by $(D isValidDchar), + `'\uFFFE'` and `'\uFFFF'` are considered valid by `isValidDchar`, as they are permitted for internal use by an application, but they are not allowed for interchange by the Unicode standard. +/ @@ -252,6 +277,15 @@ bool isValidDchar(dchar c) pure nothrow @safe @nogc return c < 0xD800 || (c > 0xDFFF && c <= 0x10FFFF); } +/// +@safe @nogc pure nothrow unittest +{ + assert( isValidDchar(cast(dchar) 0x41)); + assert( isValidDchar(cast(dchar) 0x00)); + assert(!isValidDchar(cast(dchar) 0xD800)); + assert(!isValidDchar(cast(dchar) 0x11FFFF)); +} + pure nothrow @safe @nogc unittest { import std.exception; @@ -273,15 +307,63 @@ pure nothrow @safe @nogc unittest }); } +/** +Checks if a single character forms a valid code point. + +When standing alone, some characters are invalid code points. For +example the `wchar` `0xD800` is a so called high surrogate, which can +only be interpreted together with a low surrogate following it. As a +standalone character it is considered invalid. + +See $(LINK2 http://www.unicode.org/versions/Unicode13.0.0/, +Unicode Standard, D90, D91 and D92) for more details. + +Params: + c = character to test + Char = character type of `c` + +Returns: + `true`, if `c` forms a valid code point. + */ +bool isValidCodepoint(Char)(Char c) +if (isSomeChar!Char) +{ + alias UChar = Unqual!Char; + static if (is(UChar == char)) + { + return c <= 0x7F; + } + else static if (is(UChar == wchar)) + { + return c <= 0xD7FF || c >= 0xE000; + } + else static if (is(UChar == dchar)) + { + return isValidDchar(c); + } + else + static assert(false, "unknown character type: `" ~ Char.stringof ~ "`"); +} + +/// +@safe pure nothrow unittest +{ + assert( isValidCodepoint(cast(char) 0x40)); + assert(!isValidCodepoint(cast(char) 0x80)); + assert( isValidCodepoint(cast(wchar) 0x1234)); + assert(!isValidCodepoint(cast(wchar) 0xD800)); + assert( isValidCodepoint(cast(dchar) 0x0010FFFF)); + assert(!isValidCodepoint(cast(dchar) 0x12345678)); +} /++ - Calculate the length of the UTF sequence starting at $(D index) - in $(D str). + Calculate the length of the UTF sequence starting at `index` + in `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)) + str = $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of UTF code units. Must be random access if `index` is passed + index = starting index of UTF sequence (default: `0`) Returns: The number of code units in the UTF sequence. For UTF-8, this is a @@ -289,18 +371,18 @@ pure nothrow @safe @nogc unittest 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 + May throw a `UTFException` if `str[index]` is not the start of a valid UTF sequence. Note: - $(D stride) will only analyze the first $(D str[index]) element. It + `stride` will only analyze the first `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))) + (isRandomAccessRange!S && is(immutable ElementType!S == immutable char))) { static if (is(typeof(str.length) : ulong)) assert(index < str.length, "Past the end of the UTF-8 sequence"); @@ -315,7 +397,7 @@ if (is(S : const char[]) || /// Ditto uint stride(S)(auto ref S str) if (is(S : const char[]) || - (isInputRange!S && is(Unqual!(ElementType!S) == char))) + (isInputRange!S && is(immutable ElementType!S == immutable char))) { static if (is(S : const char[])) immutable c = str[0]; @@ -328,23 +410,13 @@ if (is(S : const char[]) || 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; + import std.traits : FunctionAttribute, functionAttributes, isSafe; static void test(string s, dchar c, size_t i = 0, size_t line = __LINE__) { enforce(stride(s, i) == codeLength!char(c), @@ -423,7 +495,7 @@ body /// Ditto uint stride(S)(auto ref S str, size_t index) if (is(S : const wchar[]) || - (isRandomAccessRange!S && is(Unqual!(ElementType!S) == wchar))) + (isRandomAccessRange!S && is(immutable ElementType!S == immutable wchar))) { static if (is(typeof(str.length) : ulong)) assert(index < str.length, "Past the end of the UTF-16 sequence"); @@ -440,7 +512,8 @@ if (is(S : const wchar[])) /// Ditto uint stride(S)(auto ref S str) -if (isInputRange!S && is(Unqual!(ElementType!S) == wchar)) +if (isInputRange!S && is(immutable ElementType!S == immutable wchar) && + !is(S : const wchar[])) { assert(!str.empty, "UTF-16 sequence is empty"); immutable uint u = str.front; @@ -453,6 +526,7 @@ if (isInputRange!S && is(Unqual!(ElementType!S) == wchar)) import std.conv : to; import std.exception; import std.string : format; + import std.traits : FunctionAttribute, functionAttributes, isSafe; static void test(wstring s, dchar c, size_t i = 0, size_t line = __LINE__) { enforce(stride(s, i) == codeLength!wchar(c), @@ -517,7 +591,7 @@ if (isInputRange!S && is(Unqual!(ElementType!S) == wchar)) /// Ditto uint stride(S)(auto ref S str, size_t index = 0) if (is(S : const dchar[]) || - (isInputRange!S && is(Unqual!(ElementEncodingType!S) == dchar))) + (isInputRange!S && is(immutable ElementEncodingType!S == immutable dchar))) { static if (is(typeof(str.length) : ulong)) assert(index < str.length, "Past the end of the UTF-32 sequence"); @@ -526,12 +600,23 @@ if (is(S : const dchar[]) || return 1; } +/// +@safe unittest +{ + assert("a".stride == 1); + assert("λ".stride == 2); + assert("aλ".stride == 1); + assert("aλ".stride(1) == 2); + assert("𐐷".stride == 4); +} + @system unittest { import core.exception : AssertError; import std.conv : to; import std.exception; import std.string : format; + import std.traits : FunctionAttribute, functionAttributes, isSafe; static void test(dstring s, dchar c, size_t i = 0, size_t line = __LINE__) { enforce(stride(s, i) == codeLength!dchar(c), @@ -593,14 +678,25 @@ if (is(S : const dchar[]) || }); } +private uint strideImpl(char c, size_t index) @trusted pure +in { assert(c & 0x80); } +do +{ + 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; +} + /++ Calculate the length of the UTF sequence ending one code unit before - $(D index) in $(D str). + `index` in `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)) + `index` is passed + index = index one past end of UTF sequence (default: `str.length`) Returns: The number of code units in the UTF sequence. For UTF-8, this is a @@ -608,18 +704,18 @@ if (is(S : const dchar[]) || 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 + May throw a `UTFException` if `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]) + `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))) + (isRandomAccessRange!S && is(immutable ElementType!S == immutable char))) { static if (is(typeof(str.length) : ulong)) assert(index <= str.length, "Past the end of the UTF-8 sequence"); @@ -630,7 +726,7 @@ if (is(S : const char[]) || if (index >= 4) //single verification for most common case { - foreach (i; AliasSeq!(2, 3, 4)) + static foreach (i; 2 .. 5) { if ((str[index-i] & 0b1100_0000) != 0b1000_0000) return i; @@ -638,7 +734,7 @@ if (is(S : const char[]) || } else { - foreach (i; AliasSeq!(2, 3)) + static foreach (i; 2 .. 4) { if (index >= i && (str[index-i] & 0b1100_0000) != 0b1000_0000) return i; @@ -650,14 +746,14 @@ if (is(S : const char[]) || /// Ditto uint strideBack(S)(auto ref S str) if (is(S : const char[]) || - (isRandomAccessRange!S && hasLength!S && is(Unqual!(ElementType!S) == char))) + (isRandomAccessRange!S && hasLength!S && is(immutable ElementType!S == immutable char))) { return strideBack(str, str.length); } /// Ditto uint strideBack(S)(auto ref S str) -if (isBidirectionalRange!S && is(Unqual!(ElementType!S) == char) && !isRandomAccessRange!S) +if (isBidirectionalRange!S && is(immutable ElementType!S == immutable char) && !isRandomAccessRange!S) { assert(!str.empty, "Past the end of the UTF-8 sequence"); auto temp = str.save; @@ -678,6 +774,7 @@ if (isBidirectionalRange!S && is(Unqual!(ElementType!S) == char) && !isRandomAcc import std.conv : to; import std.exception; import std.string : format; + import std.traits : FunctionAttribute, functionAttributes, isSafe; 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), @@ -744,7 +841,7 @@ if (isBidirectionalRange!S && is(Unqual!(ElementType!S) == char) && !isRandomAcc /// Ditto uint strideBack(S)(auto ref S str, size_t index) if (is(S : const wchar[]) || - (isRandomAccessRange!S && is(Unqual!(ElementType!S) == wchar))) + (isRandomAccessRange!S && is(immutable ElementType!S == immutable wchar))) { static if (is(typeof(str.length) : ulong)) assert(index <= str.length, "Past the end of the UTF-16 sequence"); @@ -757,7 +854,7 @@ if (is(S : const wchar[]) || /// Ditto uint strideBack(S)(auto ref S str) if (is(S : const wchar[]) || - (isBidirectionalRange!S && is(Unqual!(ElementType!S) == wchar))) + (isBidirectionalRange!S && is(immutable ElementType!S == immutable wchar))) { assert(!str.empty, "UTF-16 sequence is empty"); @@ -775,6 +872,7 @@ if (is(S : const wchar[]) || import std.conv : to; import std.exception; import std.string : format; + import std.traits : FunctionAttribute, functionAttributes, isSafe; 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), @@ -838,7 +936,7 @@ if (is(S : const wchar[]) || /// Ditto uint strideBack(S)(auto ref S str, size_t index) -if (isRandomAccessRange!S && is(Unqual!(ElementEncodingType!S) == dchar)) +if (isRandomAccessRange!S && is(immutable ElementEncodingType!S == immutable dchar)) { static if (is(typeof(str.length) : ulong)) assert(index <= str.length, "Past the end of the UTF-32 sequence"); @@ -848,18 +946,29 @@ if (isRandomAccessRange!S && is(Unqual!(ElementEncodingType!S) == dchar)) /// Ditto uint strideBack(S)(auto ref S str) -if (isBidirectionalRange!S && is(Unqual!(ElementEncodingType!S) == dchar)) +if (isBidirectionalRange!S && is(immutable ElementEncodingType!S == immutable dchar)) { assert(!str.empty, "Empty UTF-32 sequence"); return 1; } +/// +@safe unittest +{ + assert("a".strideBack == 1); + assert("λ".strideBack == 2); + assert("aλ".strideBack == 2); + assert("aλ".strideBack(1) == 1); + assert("𐐷".strideBack == 4); +} + @system unittest { import core.exception : AssertError; import std.conv : to; import std.exception; import std.string : format; + import std.traits : FunctionAttribute, functionAttributes, isSafe; 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), @@ -923,16 +1032,16 @@ if (isBidirectionalRange!S && is(Unqual!(ElementEncodingType!S) == dchar)) /++ - 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 + Given `index` into `str` and assuming that `index` is at the start + of a UTF sequence, `toUCSindex` determines the number of UCS characters + up to `index`. So, `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)) + static if (is(immutable C == immutable dchar)) return index; else { @@ -944,7 +1053,7 @@ if (isSomeChar!C) if (j > index) { - static if (is(Unqual!C == char)) + static if (is(immutable C == immutable char)) throw new UTFException("Invalid UTF-8 sequence", index); else throw new UTFException("Invalid UTF-16 sequence", index); @@ -972,14 +1081,14 @@ if (isSomeChar!C) /++ - 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 + Given a UCS index `n` into `str`, returns the UTF index. + So, `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)) + static if (is(immutable C == immutable dchar)) { return n; } @@ -1017,9 +1126,9 @@ if (isSomeChar!C) alias UseReplacementDchar = Flag!"useReplacementDchar"; /++ - Decodes and returns the code point starting at $(D str[index]). $(D index) + Decodes and returns the code point starting at `str[index]`. `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 + well-formed, then a `UTFException` is thrown and `index` remains unchanged. decode will only work with strings and random access ranges of code units @@ -1035,8 +1144,8 @@ alias UseReplacementDchar = Flag!"useReplacementDchar"; decoded character Throws: - $(LREF UTFException) if $(D str[index]) is not the start of a valid UTF - sequence and useReplacementDchar is $(D No.useReplacementDchar) + $(LREF UTFException) if `str[index]` is not the start of a valid UTF + sequence and useReplacementDchar is `No.useReplacementDchar` +/ dchar decode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)(auto ref S str, ref size_t index) if (!isSomeString!S && @@ -1049,7 +1158,7 @@ out (result) { assert(isValidDchar(result)); } -body +do { if (str[index] < codeUnitLimit!S) return str[index++]; @@ -1057,6 +1166,7 @@ body return decodeImpl!(true, useReplacementDchar)(str, index); } +/// ditto dchar decode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( auto ref S str, ref size_t index) @trusted pure if (isSomeString!S) @@ -1068,20 +1178,44 @@ out (result) { assert(isValidDchar(result)); } -body +do { if (str[index] < codeUnitLimit!S) return str[index++]; - else - return decodeImpl!(true, useReplacementDchar)(str, index); + else static if (is(immutable S == immutable C[], C)) + return decodeImpl!(true, useReplacementDchar)(cast(const(C)[]) str, index); +} + +/// +@safe pure unittest +{ + size_t i; + + assert("a".decode(i) == 'a' && i == 1); + i = 0; + assert("å".decode(i) == 'å' && i == 2); + i = 1; + assert("aå".decode(i) == 'å' && i == 3); + i = 0; + assert("å"w.decode(i) == 'å' && i == 1); + + // ë as a multi-code point grapheme + i = 0; + assert("e\u0308".decode(i) == 'e' && i == 1); + // ë as a single code point grapheme + i = 0; + assert("ë".decode(i) == 'ë' && i == 2); + i = 0; + assert("ë"w.decode(i) == 'ë' && i == 1); } /++ - $(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 + `decodeFront` is a variant of $(LREF decode) which specifically decodes + the first code point. Unlike $(LREF decode), `decodeFront` accepts any + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of code units (rather than just a string or random access + range). It also takes the range by `ref` and pops off the elements as it + decodes them. If `numCodeUnits` is passed in, it gets set to the number of code units which were in the code point which was decoded. Params: @@ -1093,7 +1227,7 @@ body decoded character Throws: - $(LREF UTFException) if $(D str.front) is not the start of a valid UTF + $(LREF UTFException) if `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 @@ -1110,7 +1244,7 @@ out (result) { assert(isValidDchar(result)); } -body +do { immutable fst = str.front; @@ -1122,10 +1256,12 @@ body } 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; + // https://issues.dlang.org/show_bug.cgi?id=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 https://issues.dlang.org/show_bug.cgi?id=8521 + // has been fixed. + enum canIndex = is(S : const char[]) || isRandomAccessRange!S && hasSlicing!S && hasLength!S; immutable retval = decodeImpl!(canIndex, useReplacementDchar)(str, numCodeUnits); // The other range types were already popped by decodeImpl. @@ -1136,6 +1272,7 @@ body } } +/// ditto dchar decodeFront(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( ref S str, out size_t numCodeUnits) @trusted pure if (isSomeString!S) @@ -1147,7 +1284,7 @@ out (result) { assert(isValidDchar(result)); } -body +do { if (str[0] < codeUnitLimit!S) { @@ -1156,9 +1293,9 @@ body str = str[1 .. $]; return retval; } - else + else static if (is(immutable S == immutable C[], C)) { - immutable retval = decodeImpl!(true, useReplacementDchar)(str, numCodeUnits); + immutable retval = decodeImpl!(true, useReplacementDchar)(cast(const(C)[]) str, numCodeUnits); str = str[numCodeUnits .. $]; return retval; } @@ -1172,12 +1309,26 @@ if (isInputRange!S && isSomeChar!(ElementType!S)) return decodeFront!useReplacementDchar(str, numCodeUnits); } +/// +@safe pure unittest +{ + import std.range.primitives; + string str = "Hello, World!"; + + assert(str.decodeFront == 'H' && str == "ello, World!"); + str = "å"; + assert(str.decodeFront == 'å' && str.empty); + str = "å"; + size_t i; + assert(str.decodeFront(i) == 'å' && i == 2 && str.empty); +} + /++ - $(D decodeBack) is a variant of $(LREF decode) which specifically decodes - the last code point. Unlike $(LREF decode), $(D decodeBack) accepts any + `decodeBack` is a variant of $(LREF decode) which specifically decodes + the last code point. Unlike $(LREF decode), `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 + range). It also takes the range by `ref` and pops off the elements as it + decodes them. If `numCodeUnits` is passed in, it gets set to the number of code units which were in the code point which was decoded. Params: @@ -1189,9 +1340,9 @@ if (isInputRange!S && isSomeChar!(ElementType!S)) 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). + $(LREF UTFException) if `str.back` is not the end of a valid UTF + sequence. If an exception is thrown, the `str` itself remains unchanged, + but there is no guarantee as to the value of `numCodeUnits` (when passed). +/ dchar decodeBack(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( ref S str, out size_t numCodeUnits) @@ -1204,7 +1355,7 @@ out (result) { assert(isValidDchar(result)); } -body +do { if (str[$ - 1] < codeUnitLimit!S) { @@ -1213,12 +1364,12 @@ body str = str[0 .. $ - 1]; return retval; } - else + else static if (is(immutable S == immutable C[], C)) { numCodeUnits = strideBack(str); immutable newLength = str.length - numCodeUnits; size_t index = newLength; - immutable retval = decodeImpl!(true, useReplacementDchar)(str, index); + immutable retval = decodeImpl!(true, useReplacementDchar)(cast(const(C)[]) str, index); str = str[0 .. newLength]; return retval; } @@ -1237,7 +1388,7 @@ out (result) { assert(isValidDchar(result)); } -body +do { if (str.back < codeUnitLimit!S) { @@ -1288,19 +1439,34 @@ out (result) { assert(isValidDchar(result)); } -body +do { size_t numCodeUnits; return decodeBack!useReplacementDchar(str, numCodeUnits); } -// Gives the maximum value that a code unit for the given range type can hold. +/// +@system pure unittest +{ + import std.range.primitives; + string str = "Hello, World!"; + + assert(str.decodeBack == '!' && str == "Hello, World"); + str = "å"; + assert(str.decodeBack == 'å' && str.empty); + str = "å"; + size_t i; + assert(str.decodeBack(i) == 'å' && i == 2 && str.empty); +} + +// For the given range, code unit values less than this +// are guaranteed to be valid single-codepoint encodings. package template codeUnitLimit(S) if (isSomeChar!(ElementEncodingType!S)) { - static if (is(Unqual!(ElementEncodingType!S) == char)) + static if (is(immutable ElementEncodingType!S == immutable char)) enum char codeUnitLimit = 0x80; - else static if (is(Unqual!(ElementEncodingType!S) == wchar)) + else static if (is(immutable ElementEncodingType!S == immutable wchar)) enum wchar codeUnitLimit = 0xD800; else enum dchar codeUnitLimit = 0xD800; @@ -1326,7 +1492,7 @@ if (isSomeChar!(ElementEncodingType!S)) 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))) + is(S : const char[]) || (isInputRange!S && is(immutable ElementEncodingType!S == immutable char))) { /* The following encodings are valid, except for the 5 and 6 byte * combinations: @@ -1349,7 +1515,8 @@ if ( else alias pstr = str; - //@@@BUG@@@ 14447 forces this to be done outside of decodeImpl + // https://issues.dlang.org/show_bug.cgi?id=14447 forces this to be done + // outside of decodeImpl //enum canIndex = is(S : const char[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S); static if (canIndex) @@ -1556,7 +1723,7 @@ unittest 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))) +if (is(S : const wchar[]) || (isInputRange!S && is(immutable ElementEncodingType!S == immutable wchar))) { static if (is(S : const wchar[])) auto pstr = str.ptr + index; @@ -1565,7 +1732,8 @@ if (is(S : const wchar[]) || (isInputRange!S && is(Unqual!(ElementEncodingType!S else alias pstr = str; - //@@@BUG@@@ 14447 forces this to be done outside of decodeImpl + // https://issues.dlang.org/show_bug.cgi?id=14447 forces this to be done + // outside of decodeImpl //enum canIndex = is(S : const wchar[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S); static if (canIndex) @@ -1673,7 +1841,7 @@ unittest 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))) +if (is(S : const dchar[]) || (isInputRange!S && is(immutable ElementEncodingType!S == immutable dchar))) { static if (is(S : const dchar[])) auto pstr = str.ptr; @@ -1736,19 +1904,21 @@ unittest } -version (unittest) private void testDecode(R)(R range, +version (StdUnittest) private void testDecode(R)(R range, size_t index, dchar expectedChar, size_t expectedIndex, size_t line = __LINE__) { import core.exception : AssertError; + import std.exception : enforce; import std.string : format; + import std.traits : isNarrowString; static if (hasLength!R) immutable lenBefore = range.length; - static if (isRandomAccessRange!R) + static if (isRandomAccessRange!R && !isNarrowString!R) { { immutable result = decode(range, index); @@ -1765,12 +1935,13 @@ version (unittest) private void testDecode(R)(R range, } } -version (unittest) private void testDecodeFront(R)(ref R range, +version (StdUnittest) private void testDecodeFront(R)(ref R range, dchar expectedChar, size_t expectedNumCodeUnits, size_t line = __LINE__) { import core.exception : AssertError; + import std.exception : enforce; import std.string : format; static if (hasLength!R) @@ -1790,7 +1961,7 @@ version (unittest) private void testDecodeFront(R)(ref R range, } } -version (unittest) private void testDecodeBack(R)(ref R range, +version (StdUnittest) private void testDecodeBack(R)(ref R range, dchar expectedChar, size_t expectedNumCodeUnits, size_t line = __LINE__) @@ -1801,6 +1972,7 @@ version (unittest) private void testDecodeBack(R)(ref R range, else { import core.exception : AssertError; + import std.exception : enforce; import std.string : format; static if (hasLength!R) @@ -1821,7 +1993,7 @@ version (unittest) private void testDecodeBack(R)(ref R range, } } -version (unittest) private void testAllDecode(R)(R range, +version (StdUnittest) private void testAllDecode(R)(R range, dchar expectedChar, size_t expectedIndex, size_t line = __LINE__) @@ -1835,9 +2007,10 @@ version (unittest) private void testAllDecode(R)(R range, testDecodeFront(range, expectedChar, expectedIndex, line); } -version (unittest) private void testBadDecode(R)(R range, size_t index, size_t line = __LINE__) +version (StdUnittest) private void testBadDecode(R)(R range, size_t index, size_t line = __LINE__) { import core.exception : AssertError; + import std.exception : assertThrown, enforce; import std.string : format; immutable initialIndex = index; @@ -1861,7 +2034,7 @@ version (unittest) private void testBadDecode(R)(R range, size_t index, size_t l assertThrown!UTFException(decodeFront(range, index), null, __FILE__, line); } -version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LINE__) +version (StdUnittest) 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) @@ -1869,6 +2042,7 @@ version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LI else { import core.exception : AssertError; + import std.exception : assertThrown, enforce; import std.string : format; static if (hasLength!R) @@ -1972,11 +2146,10 @@ version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LI @system unittest { - import std.conv : to; import std.exception; assertCTFEable!( { - foreach (S; AliasSeq!(to!wstring, InputCU!wchar, RandomCU!wchar, + foreach (S; AliasSeq!((wstring s) => s, InputCU!wchar, RandomCU!wchar, (wstring s) => new RefBidirCU!wchar(s), (wstring s) => new RefRandomCU!wchar(s))) { @@ -2011,7 +2184,7 @@ version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LI } } - foreach (S; AliasSeq!(to!wstring, RandomCU!wchar, (wstring s) => new RefRandomCU!wchar(s))) + foreach (S; AliasSeq!((wchar[] s) => s.idup, RandomCU!wchar, (wstring s) => new RefRandomCU!wchar(s))) { auto str = S([cast(wchar) 0xD800, cast(wchar) 0xDC00, cast(wchar) 0x1400, @@ -2028,11 +2201,10 @@ version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LI @system unittest { - import std.conv : to; import std.exception; assertCTFEable!( { - foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, InputCU!dchar, + foreach (S; AliasSeq!((dstring s) => s, RandomCU!dchar, InputCU!dchar, (dstring s) => new RefBidirCU!dchar(s), (dstring s) => new RefRandomCU!dchar(s))) { @@ -2069,7 +2241,7 @@ version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LI } } - foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, (dstring s) => new RefRandomCU!dchar(s))) + foreach (S; AliasSeq!((dchar[] s) => s.idup, 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); @@ -2085,6 +2257,7 @@ version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LI @safe unittest { import std.exception; + import std.traits : FunctionAttribute, functionAttributes, isSafe; assertCTFEable!( { foreach (S; AliasSeq!( char[], const( char)[], string, @@ -2129,13 +2302,13 @@ private dchar _utfException(UseReplacementDchar useReplacementDchar)(string msg, } /++ - 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). + Encodes `c` into the static array, `buf`, and returns the actual + length of the encoded character (a number between `1` and `4` for + `char[4]` buffers and a number between `1` and `2` for + `wchar[2]` buffers). Throws: - $(D UTFException) if $(D c) is not a valid UTF code point. + `UTFException` if `c` is not a valid UTF code point. +/ size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( out char[4] buf, dchar c) @safe pure @@ -2180,6 +2353,64 @@ size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( goto L3; } +/// +@safe unittest +{ + import std.exception : assertThrown; + import std.typecons : Yes; + + 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, '\uE000') == 3 && buf[0 .. 3] == "\uE000"); + assert(encode(buf, 0xFFFE) == 3 && buf[0 .. 3] == "\xEF\xBF\xBE"); + assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); + + encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); + auto slice = buf[]; + assert(slice.decodeFront == replacementDchar); +} + +/// +@safe unittest +{ + import std.exception : assertThrown; + import std.typecons : Yes; + + 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, '\U00010000') == 2 && buf[0 .. 2] == "\U00010000"); + assert(encode(buf, '\U0010FFFF') == 2 && buf[0 .. 2] == "\U0010FFFF"); + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + + encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); + auto slice = buf[]; + assert(slice.decodeFront == replacementDchar); +} + +/// +@safe unittest +{ + import std.exception : assertThrown; + import std.typecons : Yes; + + dchar[1] buf; + + assert(encode(buf, '\u0000') == 1 && buf[0] == '\u0000'); + assert(encode(buf, '\uD7FF') == 1 && buf[0] == '\uD7FF'); + assert(encode(buf, '\uE000') == 1 && buf[0] == '\uE000'); + assert(encode(buf, '\U0010FFFF') == 1 && buf[0] == '\U0010FFFF'); + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + + encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); + assert(buf[0] == replacementDchar); +} + @safe unittest { import std.exception; @@ -2206,7 +2437,8 @@ size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); assert(encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000) == buf.stride); - assert(buf.front == replacementDchar); + enum replacementDcharString = "\uFFFD"; + assert(buf[0 .. replacementDcharString.length] == replacementDcharString); }); } @@ -2303,10 +2535,10 @@ size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( /++ - Encodes $(D c) in $(D str)'s encoding and appends it to $(D str). + Encodes `c` in `str`'s encoding and appends it to `str`. Throws: - $(D UTFException) if $(D c) is not a valid UTF code point. + `UTFException` if `c` is not a valid UTF code point. +/ void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( ref char[] str, dchar c) @safe pure @@ -2362,6 +2594,21 @@ void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( str = r; } +/// +@safe unittest +{ + char[] s = "abcd".dup; + dchar d1 = 'a'; + dchar d2 = 'ø'; + + encode(s, d1); + assert(s.length == 5); + assert(s == "abcda"); + encode(s, d2); + assert(s.length == 7); + assert(s == "abcdaø"); +} + @safe unittest { import std.exception; @@ -2409,9 +2656,11 @@ void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); - assert(buf.back != replacementDchar); + enum replacementDcharString = "\uFFFD"; + enum rdcslen = replacementDcharString.length; + assert(buf[$ - rdcslen .. $] != replacementDcharString); encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); - assert(buf.back == replacementDchar); + assert(buf[$ - rdcslen .. $] == replacementDcharString); }); } @@ -2516,7 +2765,7 @@ void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( /++ 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. + `c` when `C` is the character type used to encode it. +/ ubyte codeLength(C)(dchar c) @safe pure nothrow @nogc if (isSomeChar!C) @@ -2554,19 +2803,20 @@ if (isSomeChar!C) /++ - 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 + Returns the number of code units that are required to encode `str` + in a string whose character type is `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 + input = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + 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)) +if (isInputRange!InputRange && !isInfinite!InputRange && isSomeChar!(ElementType!InputRange)) { alias EncType = Unqual!(ElementEncodingType!InputRange); static if (isSomeString!InputRange && is(EncType == C) && is(typeof(input.length))) @@ -2575,7 +2825,7 @@ if (isInputRange!InputRange && !isInfinite!InputRange && is(ElementType!InputRan { size_t total = 0; - foreach (dchar c; input) + foreach (c; input.byDchar) total += codeLength!C(c); return total; @@ -2585,20 +2835,19 @@ if (isInputRange!InputRange && !isInfinite!InputRange && is(ElementType!InputRan /// @safe unittest { - import std.conv : to; assert(codeLength!char("hello world") == - to!string("hello world").length); + "hello world".length); assert(codeLength!wchar("hello world") == - to!wstring("hello world").length); + "hello world"w.length); assert(codeLength!dchar("hello world") == - to!dstring("hello world").length); + "hello world"d.length); assert(codeLength!char(`プログラミング`) == - to!string(`プログラミング`).length); + `プログラミング`.length); assert(codeLength!wchar(`プログラミング`) == - to!wstring(`プログラミング`).length); + `プログラミング`w.length); assert(codeLength!dchar(`プログラミング`) == - to!dstring(`プログラミング`).length); + `プログラミング`d.length); string haystack = `Être sans la verité, ça, ce ne serait pas bien.`; wstring needle = `Être sans la verité`; @@ -2634,11 +2883,11 @@ if (isInputRange!InputRange && !isInfinite!InputRange && is(ElementType!InputRan /+ Internal helper function: -Returns true if it is safe to search for the Codepoint $(D c) inside +Returns true if it is safe to search for the Codepoint `c` inside code units, without decoding. This is a runtime check that is used an optimization in various functions, -particularly, in $(D std.string). +particularly, in `std.string`. +/ package bool canSearchInCodeUnits(C)(dchar c) if (isSomeChar!C) @@ -2674,10 +2923,10 @@ if (isSomeChar!C) /* =================== Validation ======================= */ /++ - Checks to see if $(D str) is well-formed unicode or not. + Checks to see if `str` is well-formed unicode or not. Throws: - $(D UTFException) if $(D str) is not well-formed. + `UTFException` if `str` is not well-formed. +/ void validate(S)(in S str) @safe pure if (isSomeString!S) @@ -2689,8 +2938,16 @@ if (isSomeString!S) } } +/// +@safe unittest +{ + import std.exception : assertThrown; + char[] a = [167, 133, 175]; + assertThrown!UTFException(validate(a)); +} -@safe unittest // bugzilla 12923 +// https://issues.dlang.org/show_bug.cgi?id=12923 +@safe unittest { import std.exception; assertThrown((){ @@ -2733,8 +2990,9 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) import std.algorithm.comparison : equal; import std.internal.test.dummyrange : ReferenceInputRange; - auto r1 = new ReferenceInputRange!dchar("Hellø"); - auto r2 = new ReferenceInputRange!dchar("𐐷"); + alias RT = ReferenceInputRange!(ElementType!(string)); + auto r1 = new RT("Hellø"); + auto r2 = new RT("𐐷"); assert(r1.toUTF8.equal(['H', 'e', 'l', 'l', 0xC3, 0xB8])); assert(r2.toUTF8.equal([0xF0, 0x90, 0x90, 0xB7])); @@ -2775,8 +3033,9 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) import std.algorithm.comparison : equal; import std.internal.test.dummyrange : ReferenceInputRange; - auto r1 = new ReferenceInputRange!dchar("𤭢"); - auto r2 = new ReferenceInputRange!dchar("𐐷"); + alias RT = ReferenceInputRange!(ElementType!(string)); + auto r1 = new RT("𤭢"); + auto r2 = new RT("𐐷"); assert(r1.toUTF16.equal([0xD852, 0xDF62])); assert(r2.toUTF16.equal([0xD801, 0xDC37])); @@ -2794,13 +3053,26 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) * See_Also: * For a lazy, non-allocating version of these functions, see $(LREF byUTF). */ -dstring toUTF32(S)(S s) +dstring toUTF32(S)(scope S s) if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) { return toUTFImpl!dstring(s); } -private T toUTFImpl(T, S)(S s) +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // these graphemes are two code units in UTF-16 and one in UTF-32 + assert("𤭢"w.length == 2); + assert("𐐷"w.length == 2); + + assert("𤭢"w.toUTF32.equal([0x00024B62])); + assert("𐐷"w.toUTF32.equal([0x00010437])); +} + +private T toUTFImpl(T, S)(scope S s) { static if (is(S : T)) { @@ -2811,7 +3083,7 @@ private T toUTFImpl(T, S)(S s) import std.array : appender; auto app = appender!T(); - static if (hasLength!S || isSomeString!S) + static if (is(S == C[], C) || hasLength!S) app.reserve(s.length); foreach (c; s.byUTF!(Unqual!(ElementEncodingType!T))) @@ -2824,36 +3096,36 @@ private T toUTFImpl(T, S)(S s) /* =================== 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. + Returns a C-style zero-terminated string equivalent to `str`. `str` + must not contain embedded `'\0'`'s as any C function will treat the first + `'\0'` that it sees as the end of the string. If `str.empty` is + `true`, then a string containing only `'\0'` is returned. - $(D toUTFz) accepts any type of string and is templated on the type of + `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). + other than `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 + $(RED Warning 1:) If the result of `toUTFz` equals `str.ptr`, then if + anything alters the character one past the end of `str` (which is the + `'\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 + append to `str` and no reallocation takes place or when `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 + which is one character past the end of `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 + `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 + character array happened to start with `'\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. + `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). + `'\0'` to the string and use its `ptr` property rather than calling + `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 @@ -2861,8 +3133,10 @@ private T toUTFImpl(T, S)(S s) collection cycle and cause a nasty bug when the C code tries to use it. +/ template toUTFz(P) +if (isPointer!P && isSomeChar!(typeof(*P.init))) { P toUTFz(S)(S str) @safe pure + if (isSomeString!S) { return toUTFzImpl!(P, S)(str); } @@ -2879,10 +3153,8 @@ template toUTFz(P) 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)) +private P toUTFzImpl(P, S)(return scope S str) @safe pure +if (is(immutable typeof(*P.init) == typeof(str[0]))) //immutable(C)[] -> C*, const(C)*, or immutable(C)* { if (str.empty) @@ -2924,13 +3196,11 @@ if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) && } } -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)) +private P toUTFzImpl(P, S)(return scope S str) @safe pure +if (is(typeof(str[0]) C) && is(immutable typeof(*P.init) == immutable C) && !is(C == immutable)) //C[] or const(C)[] -> C*, const(C)*, or immutable(C)* { - alias InChar = ElementEncodingType!S; + alias InChar = typeof(str[0]); alias OutChar = typeof(*P.init); //const(C)[] -> const(C)* or @@ -2965,8 +3235,7 @@ if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) && } 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))) +if (!is(immutable typeof(*P.init) == immutable typeof(str[0]))) //C1[], const(C1)[], or immutable(C1)[] -> C2*, const(C2)*, or immutable(C2)* { import std.array : appender; @@ -3068,11 +3337,11 @@ if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) && /++ - $(D toUTF16z) is a convenience function for $(D toUTFz!(const(wchar)*)). + `toUTF16z` is a convenience function for `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. + Encodes string `s` into UTF-16 and returns the encoded string. + `toUTF16z` is suitable for calling the 'W' functions in the Win32 API + that take an `LPCWSTR` argument. +/ const(wchar)* toUTF16z(C)(const(C)[] str) @safe pure if (isSomeChar!C) @@ -3080,6 +3349,14 @@ if (isSomeChar!C) return toUTFz!(const(wchar)*)(str); } +/// +@system unittest +{ + string str = "Hello, World!"; + const(wchar)* p = str.toUTF16z; + assert(p[str.length] == '\0'); +} + @safe pure unittest { import std.conv : to; @@ -3123,19 +3400,28 @@ if (isSomeChar!C) /++ - Returns the total number of code points encoded in $(D str). + Returns the total number of code points encoded in `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. + `UTFException` if `str` is not well-formed. +/ -size_t count(C)(const(C)[] str) @trusted pure nothrow @nogc +size_t count(C)(const(C)[] str) @safe pure nothrow @nogc if (isSomeChar!C) { - return walkLength(str); + return walkLength(str.byDchar); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(count("") == 0); + assert(count("a") == 1); + assert(count("abc") == 3); + assert(count("\u20AC100") == 4); } @safe pure nothrow @nogc unittest @@ -3152,8 +3438,9 @@ if (isSomeChar!C) // Ranges of code units for testing. -version (unittest) +version (StdUnittest) { +private: struct InputCU(C) { import std.conv : to; @@ -3277,12 +3564,12 @@ enum dchar replacementDchar = '\uFFFD'; * 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. + * r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * 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. + * 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 @@ -3293,6 +3580,11 @@ enum dchar replacementDchar = '\uFFFD'; * 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. + * + * If `r` is wrapped in a new range, then that range has a `source` + * property for returning the string that's currently contained within that + * range. + * * See_Also: * Refer to the $(MREF std, uni) docs for a reference on Unicode * terminology. @@ -3301,12 +3593,11 @@ enum dchar replacementDchar = '\uFFFD'; * $(REF byGrapheme, std,uni). */ auto byCodeUnit(R)(R r) -if (isAutodecodableString!R || - isInputRange!R && isSomeChar!(ElementEncodingType!R) || - (is(R : const dchar[]) && !isStaticArray!R)) +if ((isConvertibleToString!R && !isStaticArray!R) || + (isInputRange!R && isSomeChar!(ElementEncodingType!R))) { - static if (isNarrowString!R || - // This would be cleaner if we had a way to check whether a type + import std.traits : StringTypeOf; + static if (// 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"))) @@ -3315,31 +3606,31 @@ if (isAutodecodableString!R || { @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 bool empty() const { return source.length == 0; } + @property auto ref front() inout { return source[0]; } + void popFront() { source = source[1 .. $]; } - @property auto save() { return ByCodeUnitImpl(str.save); } + @property auto save() { return ByCodeUnitImpl(source.save); } - @property auto ref back() inout { return str[$ - 1]; } - void popBack() { str = str[0 .. $-1]; } + @property auto ref back() inout { return source[$ - 1]; } + void popBack() { source = source[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]); } + auto ref opIndex(size_t index) inout { return source[index]; } + auto opSlice(size_t lower, size_t upper) { return ByCodeUnitImpl(source[lower .. upper]); } - @property size_t length() const { return str.length; } + @property size_t length() const { return source.length; } alias opDollar = length; - private: - StringTypeOf!R str; + StringTypeOf!R source; } 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")) + else static if (!isInputRange!R || + (is(R : const dchar[]) && !__traits(hasMember, R, "empty") && + !__traits(hasMember, R, "front") && !__traits(hasMember, R, "popFront"))) { return cast(StringTypeOf!R) r; } @@ -3354,6 +3645,7 @@ if (isAutodecodableString!R || @safe unittest { import std.range.primitives; + import std.traits : isAutodecodableString; auto r = "Hello, World!".byCodeUnit(); static assert(hasLength!(typeof(r))); @@ -3361,14 +3653,27 @@ if (isAutodecodableString!R || static assert(isRandomAccessRange!(typeof(r))); static assert(is(ElementType!(typeof(r)) == immutable char)); - // contrast with the range capabilities of standard strings + // contrast with the range capabilities of standard strings (with or + // without autodecoding enabled). 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))); + static if (isAutodecodableString!(typeof(s))) + { + // with autodecoding enabled, strings are non-random-access ranges of + // dchar. + static assert(is(ElementType!(typeof(s)) == dchar)); + static assert(!isRandomAccessRange!(typeof(s))); + static assert(!hasSlicing!(typeof(s))); + static assert(!hasLength!(typeof(s))); + } + else + { + // without autodecoding, strings are normal arrays. + static assert(is(ElementType!(typeof(s)) == immutable char)); + static assert(isRandomAccessRange!(typeof(s))); + static assert(hasSlicing!(typeof(s))); + static assert(hasLength!(typeof(s))); + } } /// `byCodeUnit` does no Unicode decoding @@ -3384,6 +3689,29 @@ if (isAutodecodableString!R || assert(noel2.byCodeUnit[2] != 'ë'); } +/// `byCodeUnit` exposes a `source` property when wrapping narrow strings. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : popFrontN; + import std.traits : isAutodecodableString; + { + auto range = byCodeUnit("hello world"); + range.popFrontN(3); + assert(equal(range.save, "lo world")); + static if (isAutodecodableString!string) // only enabled with autodecoding + { + string str = range.source; + assert(str == "lo world"); + } + } + // source only exists if the range was wrapped + { + auto range = byCodeUnit("hello world"d); + static assert(!__traits(compiles, range.source)); + } +} + @safe pure nothrow @nogc unittest { import std.range; @@ -3434,7 +3762,7 @@ if (isAutodecodableString!R || { auto bcu = "hello".byCodeUnit().byCodeUnit(); static assert(isForwardRange!(typeof(bcu))); - static assert(is(typeof(bcu) == struct)); + static assert(is(typeof(bcu) == struct) == isAutodecodableString!string); auto s = bcu.save; bcu.popFront(); assert(s.front == 'h'); @@ -3443,7 +3771,7 @@ if (isAutodecodableString!R || 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) == struct) == isAutodecodableString!string); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); auto ret = bcu.retro; assert(ret.front == 'o'); @@ -3454,7 +3782,7 @@ if (isAutodecodableString!R || 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) == struct) == isAutodecodableString!wstring); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); auto ret = bcu.retro; assert(ret.front == 'ε'); @@ -3471,7 +3799,7 @@ if (isAutodecodableString!R || 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) == Stringish) == isAutodecodableString!Stringish); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); static assert(is(ElementType!(typeof(bcu)) == immutable char)); assert(bcu.front == cast(char) 244); @@ -3486,7 +3814,7 @@ if (isAutodecodableString!R || 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) == WStringish) == isAutodecodableString!WStringish); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); static assert(is(ElementType!(typeof(bcu)) == immutable wchar)); assert(bcu.front == cast(wchar) 56319); @@ -3515,7 +3843,10 @@ if (isAutodecodableString!R || auto orig = FuncStringish("\U0010fff8 𐁊 foo 𐂓"); auto bcu = orig.byCodeUnit(); - static assert(is(typeof(bcu) == struct)); + static if (isAutodecodableString!FuncStringish) + static assert(is(typeof(bcu) == struct)); + else + static assert(is(typeof(bcu) == string)); static assert(!is(typeof(bcu) == FuncStringish)); static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); static assert(is(ElementType!(typeof(bcu)) == immutable char)); @@ -3632,7 +3963,10 @@ if (isAutodecodableString!R || auto orig = Enum.a; auto bcu = orig.byCodeUnit(); static assert(!is(typeof(bcu) == Enum)); - static assert(is(typeof(bcu) == struct)); + static if (isAutodecodableString!Enum) + static assert(is(typeof(bcu) == struct)); + else + static assert(is(typeof(bcu) == string)); static assert(is(ElementType!(typeof(bcu)) == immutable char)); assert(bcu.front == 't'); } @@ -3642,7 +3976,10 @@ if (isAutodecodableString!R || auto orig = WEnum.a; auto bcu = orig.byCodeUnit(); static assert(!is(typeof(bcu) == WEnum)); - static assert(is(typeof(bcu) == struct)); + static if (isAutodecodableString!WEnum) + static assert(is(typeof(bcu) == struct)); + else + static assert(is(typeof(bcu) == wstring)); static assert(is(ElementType!(typeof(bcu)) == immutable wchar)); assert(bcu.front == 't'); } @@ -3656,8 +3993,16 @@ if (isAutodecodableString!R || assert(bcu.front == 't'); } - static assert(!is(typeof(byCodeUnit("hello")) == string)); - static assert(!is(typeof(byCodeUnit("hello"w)) == wstring)); + static if (autodecodeStrings) + { + static assert(!is(typeof(byCodeUnit("hello")) == string)); + static assert(!is(typeof(byCodeUnit("hello"w)) == wstring)); + } + else + { + 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))); @@ -3674,7 +4019,8 @@ if (isAutodecodableString!R || } /**************************** - * Iterate an input range of characters by char, wchar, or dchar. + * Iterate an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * of characters by char, wchar, or dchar. * These aliases simply forward to $(LREF byUTF) with the * corresponding C argument. * @@ -3883,8 +4229,8 @@ pure @safe nothrow @nogc unittest foreach (c; s[].byDchar()) { } } -version (unittest) -int impureVariable; +version (StdUnittest) +private int impureVariable; @system unittest { @@ -3909,23 +4255,33 @@ int impureVariable; } /**************************** - * Iterate an input range of characters by char type `C` by - * encoding the elements of the range. + * Iterate an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + * of characters by char type `C` by encoding the elements of the range. * - * UTF sequences that cannot be converted to the specified encoding are + * UTF sequences that cannot be converted to the specified encoding are either * replaced by U+FFFD per "5.22 Best Practice for U+FFFD Substitution" - * of the Unicode Standard 6.2. Hence byUTF is not symmetric. + * of the Unicode Standard 6.2 or result in a thrown UTFException. + * 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` + * useReplacementDchar = UseReplacementDchar.yes means replace invalid UTF with `replacementDchar`, + * UseReplacementDchar.no means throw `UTFException` for invalid UTF + * + * Throws: + * `UTFException` if invalid UTF sequence and `useReplacementDchar` is set to `UseReplacementDchar.yes` + * + * GC: + * Does not use GC if `useReplacementDchar` is set to `UseReplacementDchar.no` * * 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. + * A bidirectional range if `R` is a bidirectional range and not auto-decodable, + * as defined by $(REF isAutodecodableString, std, traits). + * + * A forward range if `R` is a forward range and not auto-decodable. * * Or, if `R` is a range and it is auto-decodable and * `is(ElementEncodingType!typeof(r) == C)`, then the range is passed @@ -3933,11 +4289,11 @@ int impureVariable; * * Otherwise, an input range of characters. */ -template byUTF(C) +template byUTF(C, UseReplacementDchar useReplacementDchar = Yes.useReplacementDchar) if (isSomeChar!C) { - static if (!is(Unqual!C == C)) - alias byUTF = byUTF!(Unqual!C); + static if (is(immutable C == immutable UC, UC) && !is(C == UC)) + alias byUTF = byUTF!UC; else: auto ref byUTF(R)(R r) @@ -3949,19 +4305,170 @@ if (isSomeChar!C) auto ref byUTF(R)(R r) if (!isAutodecodableString!R && isInputRange!R && isSomeChar!(ElementEncodingType!R)) { - alias RC = Unqual!(ElementEncodingType!R); - - static if (is(RC == C)) + static if (is(immutable ElementEncodingType!R == immutable RC, RC) && is(RC == C)) { return r.byCodeUnit(); } + else static if (is(C == dchar)) + { + static struct Result + { + enum Empty = uint.max; // range is empty or just constructed + + this(return R r) + { + this.r = r; + } + + this(return R r, uint buff) + { + this.r = r; + this.buff = buff; + } + + static if (isBidirectionalRange!R) + { + this(return R r, uint frontBuff, uint backBuff) + { + this.r = r; + this.buff = frontBuff; + this.backBuff = backBuff; + } + } + + @property bool empty() + { + static if (isBidirectionalRange!R) + return buff == Empty && backBuff == Empty && r.empty; + else + return buff == Empty && r.empty; + } + + @property dchar front() scope // 'scope' required by call to decodeFront() below + { + if (buff == Empty) + { + auto c = r.front; + + static if (is(RC == wchar)) + enum firstMulti = 0xD800; // First high surrogate. + else + enum firstMulti = 0x80; // First non-ASCII. + if (c < firstMulti) + { + r.popFront; + buff = cast(dchar) c; + } + else + { + buff = () @trusted { return decodeFront!(useReplacementDchar)(r); }(); + } + } + return cast(dchar) buff; + } + + void popFront() + { + if (buff == Empty) + front(); + buff = Empty; + } + + static if (isForwardRange!R) + { + @property auto save() + { + static if (isBidirectionalRange!R) + { + return Result(r.save, buff, backBuff); + } + else + { + return Result(r.save, buff); + } + } + } + + static if (isBidirectionalRange!R) + { + @property dchar back() scope // 'scope' required by call to decodeBack() below + { + if (backBuff != Empty) + return cast(dchar) backBuff; + + auto c = r.back; + static if (is(RC == wchar)) + enum firstMulti = 0xD800; // First high surrogate. + else + enum firstMulti = 0x80; // First non-ASCII. + if (c < firstMulti) + { + r.popBack; + backBuff = cast(dchar) c; + } + else + { + backBuff = () @trusted { return decodeBack!useReplacementDchar(r); }(); + } + return cast(dchar) backBuff; + + } + + void popBack() + { + if (backBuff == Empty) + back(); + backBuff = Empty; + } + } + + private: + + R r; + uint buff = Empty; // one character lookahead buffer + static if (isBidirectionalRange!R) + uint backBuff = Empty; + } + + return Result(r); + } else { static struct Result { + this(return R r) + { + this.r = r; + } + + this(return R r, ushort pos, ushort fill, C[4 / C.sizeof] buf) + { + this.r = r; + this.pos = pos; + this.fill = fill; + this.buf = buf; + } + + static if (isBidirectionalRange!R) + { + this(return R r, ushort frontPos, ushort frontFill, + ushort backPos, ushort backFill, C[4 / C.sizeof] buf) + { + this.r = r; + this.pos = frontPos; + this.fill = frontFill; + this.backPos = backPos; + this.backFill = backFill; + this.buf = buf; + } + } + @property bool empty() { - return pos == fill && r.empty; + static if (isBidirectionalRange!R) + return pos == fill && backPos == backFill && r.empty; + else + return pos == fill && r.empty; } @property auto front() scope // 'scope' required by call to decodeFront() below @@ -3971,7 +4478,11 @@ if (isSomeChar!C) pos = 0; auto c = r.front; - if (c <= 0x7F) + static if (C.sizeof >= 2 && RC.sizeof >= 2) + enum firstMulti = 0xD800; // First high surrogate. + else + enum firstMulti = 0x80; // First non-ASCII. + if (c < firstMulti) { fill = 1; r.popFront; @@ -3985,8 +4496,8 @@ if (isSomeChar!C) dchar dc = c; } else - dchar dc = () @trusted { return decodeFront!(Yes.useReplacementDchar)(r); }(); - fill = cast(ushort) encode!(Yes.useReplacementDchar)(buf, dc); + dchar dc = () @trusted { return decodeFront!(useReplacementDchar)(r); }(); + fill = cast(ushort) encode!(useReplacementDchar)(buf, dc); } } return buf[pos]; @@ -4001,22 +4512,67 @@ if (isSomeChar!C) 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` - */ + @property auto save() + { + static if (isBidirectionalRange!R) + { + return Result(r.save, pos, fill, backPos, backFill, buf); + } + else + { + return Result(r.save, pos, fill, buf); + } + } + } + + static if (isBidirectionalRange!R) + { + @property auto back() scope // 'scope' required by call to decodeBack() below + { + if (backPos != backFill) + return buf[cast(ushort) (backFill - backPos - 1)]; + + backPos = 0; + auto c = r.back; + static if (C.sizeof >= 2 && RC.sizeof >= 2) + enum firstMulti = 0xD800; // First high surrogate. + else + enum firstMulti = 0x80; // First non-ASCII. + if (c < firstMulti) + { + backFill = 1; + r.popBack; + buf[cast(ushort) (backFill - backPos - 1)] = cast(C) c; + } + else + { + static if (is(RC == dchar)) + { + r.popBack; + dchar dc = c; + } + else + dchar dc = () @trusted { return decodeBack!(useReplacementDchar)(r); }(); + backFill = cast(ushort) encode!(useReplacementDchar)(buf, dc); + } + return buf[cast(ushort) (backFill - backPos - 1)]; + } + + void popBack() { - auto ret = this; - ret.r = r.save; - return ret; + if (backPos == backFill) + back; + ++backPos; } } private: R r; - C[4 / C.sizeof] buf = void; ushort pos, fill; + static if (isBidirectionalRange!R) + ushort backPos, backFill; + C[4 / C.sizeof] buf = void; } return Result(r); @@ -4030,13 +4586,140 @@ if (isSomeChar!C) 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]); + assert("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', 'ö']); + assert("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]); + assert("𐐷".byUTF!char().equal([0xF0, 0x90, 0x90, 0xB7])); + assert("𐐷".byUTF!wchar().equal([0xD801, 0xDC37])); + assert("𐐷".byUTF!dchar().equal([0x00010437])); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.exception : assertThrown; + + assert("hello\xF0betty".byChar.byUTF!(dchar, UseReplacementDchar.yes).equal("hello\uFFFDetty")); + assertThrown!UTFException("hello\xF0betty".byChar.byUTF!(dchar, UseReplacementDchar.no).equal("hello betty")); +} + +@safe unittest +{ + { + wchar[] s = ['a', 'b', 0x219]; + auto r = s.byUTF!char; + assert(isBidirectionalRange!(typeof(r))); + assert(r.back == 0x99); + r.popBack; + assert(r.back == 0xc8); + r.popBack; + assert(r.back == 'b'); + + } + + { + wchar[] s = ['a', 'b', 0x219]; + auto r = s.byUTF!wchar; + uint i; + assert(isBidirectionalRange!(typeof(r))); + assert(r.back == 0x219); + r.popBack; + assert(r.back == 'b'); + } + + { + wchar[] s = ['a', 'b', 0x219]; + auto r = s.byUTF!dchar; + assert(isBidirectionalRange!(typeof(r))); + assert(r.back == 0x219); + r.popBack; + assert(r.back == 'b'); + } + + { + dchar[] s = ['𐐷', '😁']; + auto r = s.byUTF!wchar; + assert(r.back == 0xde01); + r.popBack; + assert(r.back == 0xd83d); + r.popBack; + assert(r.back == 0xdc37); + r.popBack; + assert(r.back == 0xd801); + } + + { + dchar[] s = ['𐐷', '😁']; + auto r = s.byUTF!char; + char[] res; + while (!r.empty) + { + res ~= r.back; + r.popBack; + } + import std.algorithm.comparison : equal; + assert(res.equal([0x81, 0x98, 0x9f, 0xf0, 0xb7, 0x90, 0x90, 0xf0])); + } + + { + dchar[] res; + auto r = ['a', 'b', 'c', 'd', 'e'].byUTF!dchar; + while (!r.empty) + { + res ~= r.back; + r.popBack; + } + import std.algorithm.comparison : equal; + assert(res.equal(['e', 'd', 'c', 'b', 'a'])); + } + + { + //testing the save() function + wchar[] s = ['Ă','ț']; + + auto rc = s.byUTF!char; + rc.popBack; + auto rcCopy = rc.save; + assert(rc.back == rcCopy.back); + assert(rcCopy.back == 0xc8); + + auto rd = s.byUTF!dchar; + rd.popBack; + auto rdCopy = rd.save; + assert(rd.back == rdCopy.back); + assert(rdCopy.back == 'Ă'); + } +} + +/// +@safe pure nothrow unittest +{ + import std.range.primitives; + wchar[] s = ['ă', 'î']; + + auto rc = s.byUTF!char; + static assert(isBidirectionalRange!(typeof(rc))); + assert(rc.back == 0xae); + rc.popBack; + assert(rc.back == 0xc3); + rc.popBack; + assert(rc.back == 0x83); + rc.popBack; + assert(rc.back == 0xc4); + + auto rw = s.byUTF!wchar; + static assert(isBidirectionalRange!(typeof(rw))); + assert(rw.back == 'î'); + rw.popBack; + assert(rw.back == 'ă'); + + auto rd = s.byUTF!dchar; + static assert(isBidirectionalRange!(typeof(rd))); + assert(rd.back == 'î'); + rd.popBack; + assert(rd.back == 'ă'); } diff --git a/libphobos/src/std/uuid.d b/libphobos/src/std/uuid.d index c804e8e..dec2a1c 100644 --- a/libphobos/src/std/uuid.d +++ b/libphobos/src/std/uuid.d @@ -68,11 +68,11 @@ $(TR $(TDNW UUID namespaces) * * 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. + * `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 + * boost.uuid) from the Boost project with some minor additions and API * changes for a more D-like API. * * Standards: @@ -84,11 +84,11 @@ $(TR $(TDNW UUID namespaces) * 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) + * Source: $(PHOBOSSRC std/uuid.d) * * Macros: * MYREF2 = $(TT $1)  - * MYREF3 = $(D $1) + * MYREF3 = `$1` */ /* Copyright Johannes Pfau 2011 - 2012. * Distributed under the Boost Software License, Version 1.0. @@ -178,7 +178,7 @@ public struct UUID * * 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. + * `std.uuid`, but only version 3, 4 and 5 UUIDs can be generated. */ enum Version { @@ -249,12 +249,12 @@ public struct UUID * Construct a UUID struct from the 16 byte representation * of a UUID. */ - @safe pure nothrow @nogc this(ref in ubyte[16] uuidData) + @safe pure nothrow @nogc this(ref const scope ubyte[16] uuidData) { data = uuidData; } /// ditto - @safe pure nothrow @nogc this(in ubyte[16] uuidData) + @safe pure nothrow @nogc this(const ubyte[16] uuidData) { data = uuidData; } @@ -331,7 +331,7 @@ public struct UUID * * For a less strict parser, see $(LREF parseUUID) */ - this(T)(in T[] uuid) if (isSomeChar!(Unqual!T)) + this(T)(in T[] uuid) if (isSomeChar!T) { import std.conv : to, parse; if (uuid.length < 36) @@ -404,13 +404,13 @@ public struct UUID { import std.conv : to; import std.exception; - import std.meta; + import std.meta : AliasSeq; - foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[], + static 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); @@ -456,7 +456,7 @@ public struct UUID == 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 @@ -664,7 +664,7 @@ public struct UUID * All of the standard numeric operators are defined for * the UUID struct. */ - @safe pure nothrow @nogc bool opEquals(in UUID s) const + @safe pure nothrow @nogc bool opEquals(const UUID s) const { return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1]; } @@ -693,7 +693,7 @@ public struct UUID /** * ditto */ - @safe pure nothrow @nogc bool opEquals(ref in UUID s) const + @safe pure nothrow @nogc bool opEquals(ref const scope UUID s) const { return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1]; } @@ -701,7 +701,7 @@ public struct UUID /** * ditto */ - @safe pure nothrow @nogc int opCmp(in UUID s) const + @safe pure nothrow @nogc int opCmp(const UUID s) const { import std.algorithm.comparison : cmp; return cmp(this.data[], s.data[]); @@ -710,7 +710,7 @@ public struct UUID /** * ditto */ - @safe pure nothrow @nogc int opCmp(ref in UUID s) const + @safe pure nothrow @nogc int opCmp(ref const scope UUID s) const { import std.algorithm.comparison : cmp; return cmp(this.data[], s.data[]); @@ -719,7 +719,7 @@ public struct UUID /** * ditto */ - @safe pure nothrow @nogc UUID opAssign(in UUID s) + @safe pure nothrow @nogc UUID opAssign(const UUID s) { ulongs[0] = s.ulongs[0]; ulongs[1] = s.ulongs[1]; @@ -729,7 +729,7 @@ public struct UUID /** * ditto */ - @safe pure nothrow @nogc UUID opAssign(ref in UUID s) + @safe pure nothrow @nogc UUID opAssign(ref const scope UUID s) { ulongs[0] = s.ulongs[0]; ulongs[1] = s.ulongs[1]; @@ -880,13 +880,15 @@ public struct UUID const uint lo = (entry) & 0x0F; result[pos+1] = toChar!char(lo); } - foreach (i, c; result) + static if (!__traits(compiles, put(sink, result[])) || isSomeString!Writer) { - static if (__traits(compiles, put(sink, c))) - put(sink, c); - else + foreach (i, c; result) sink[i] = cast(typeof(sink[i]))c; } + else + { + put(sink, result[]); + } } /** @@ -911,8 +913,8 @@ public struct UUID @safe pure nothrow @nogc unittest { import std.meta : AliasSeq; - foreach (Char; AliasSeq!(char, wchar, dchar)) - { + static foreach (Char; AliasSeq!(char, wchar, dchar)) + {{ alias String = immutable(Char)[]; //CTFE enum String s = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"; @@ -926,7 +928,7 @@ public struct UUID Char[36] str; id.toString(str[]); assert(str == s); - } + }} } @system pure nothrow @nogc unittest @@ -952,7 +954,7 @@ public struct UUID assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); char[] buf; - void sink(const(char)[] data) + void sink(scope const(char)[] data) { buf ~= data; } @@ -961,10 +963,23 @@ public struct UUID } } +/// +@safe unittest +{ + UUID id; + assert(id.empty); + + id = randomUUID; + assert(!id.empty); + + id = UUID(cast(ubyte[16]) [138, 179, 6, 14, 44, 186, 79, + 35, 183, 76, 181, 45, 179, 189, 251, 70]); + assert(id.toString() == "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. + * If no namespace UUID was passed, the empty UUID `UUID.init` is used. * * Note: * The default namespaces ($(LREF dnsNamespace), ...) defined by @@ -980,8 +995,8 @@ public struct UUID * 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 + * and byte arrays used by `std.uuid` is compatible with Boost's implementation. + * `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). * @@ -1078,7 +1093,7 @@ public struct UUID /** * 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. + * If no namespace UUID was passed, the empty UUID `UUID.init` is used. * * Note: * The default namespaces ($(LREF dnsNamespace), ...) defined by @@ -1091,8 +1106,8 @@ public struct UUID * 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 + * and byte arrays used by `std.uuid` is compatible with Boost's implementation. + * `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). * @@ -1104,13 +1119,13 @@ public struct UUID * 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) +@safe pure nothrow @nogc UUID sha1UUID(scope const(char)[] name, scope 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) +@safe pure nothrow @nogc UUID sha1UUID(scope const(ubyte)[] data, scope const UUID namespace = UUID.init) { import std.digest.sha : SHA1; @@ -1195,7 +1210,25 @@ public struct UUID @safe UUID randomUUID() { import std.random : rndGen; - return randomUUID(rndGen); + // A PRNG with fewer than `n` bytes of state cannot produce + // every distinct `n` byte sequence. + static if (typeof(rndGen).sizeof >= UUID.sizeof) + { + return randomUUID(rndGen); + } + else + { + import std.random : unpredictableSeed, Xorshift192; + static assert(Xorshift192.sizeof >= UUID.sizeof); + static Xorshift192 rng; + static bool initialized; + if (!initialized) + { + rng.seed(unpredictableSeed); + initialized = true; + } + return randomUUID(rng); + } } /// ditto @@ -1245,18 +1278,6 @@ if (isInputRange!RNG && isIntegral!(ElementType!RNG)) 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; @@ -1311,8 +1332,7 @@ if (isSomeString!T) ///ditto UUID parseUUID(Range)(ref Range uuidRange) -if (isInputRange!Range - && is(Unqual!(ElementType!Range) == dchar)) +if (isInputRange!Range && isSomeChar!(ElementType!Range)) { import std.ascii : isHexDigit; import std.conv : ConvException, parse; @@ -1527,12 +1547,12 @@ if (isInputRange!Range return parseUUID(to!T(input)); } - foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[], + static 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 @@ -1608,6 +1628,13 @@ if (isInputRange!Range //multiple trailing/leading characters assert(parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||") == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + }} + + // Test input range with non-dchar element type. + { + import std.utf : byCodeUnit; + auto range = "8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46".byCodeUnit; + assert(parseUUID(range).data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, 179, 189, 251, 70]); } } diff --git a/libphobos/src/std/variant.d b/libphobos/src/std/variant.d index 574e2c5..953d6ea 100644 --- a/libphobos/src/std/variant.d +++ b/libphobos/src/std/variant.d @@ -2,7 +2,7 @@ /** This module implements a -$(HTTP erdani.org/publications/cuj-04-2002.html,discriminated union) +$(HTTP erdani.org/publications/cuj-04-2002.php.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)). @@ -13,8 +13,8 @@ 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 +the appropriate type `T`, use the $(LREF get) method. To query whether a +`Variant` currently holds a value of type `T`, use $(LREF peek). To fetch the exact type currently held, call $(LREF type), which returns the `TypeInfo` of the current value. @@ -23,13 +23,16 @@ 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`). +$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new +code. Instead, use $(REF SumType, std,sumtype).) + 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) +Source: $(PHOBOSSRC std/variant.d) */ module std.variant; @@ -73,59 +76,98 @@ import std.meta, std.traits, std.typecons; } /++ - Gives the $(D sizeof) the largest type given. + Gives the `sizeof` the largest type given. + + See_Also: https://forum.dlang.org/thread/wbpnncxepehgcswhuazl@forum.dlang.org?page=1 +/ -template maxSize(T...) +template maxSize(Ts...) { - static if (T.length == 1) - { - enum size_t maxSize = T[0].sizeof; - } - else + align(1) union Impl { - import std.algorithm.comparison : max; - enum size_t maxSize = max(T[0].sizeof, maxSize!(T[1 .. $])); + static foreach (i, T; Ts) + { + static if (!is(T == void)) + mixin("T _field_", i, ";"); + } } + enum maxSize = Impl.sizeof; } /// @safe unittest { + struct Cat { int a, b, c; } + + align(1) struct S + { + long l; + ubyte b; + } + + align(1) struct T + { + ubyte b; + long l; + } + static assert(maxSize!(int, long) == 8); static assert(maxSize!(bool, byte) == 1); - - struct Cat { int a, b, c; } static assert(maxSize!(bool, Cat) == 12); + static assert(maxSize!(char) == 1); + static assert(maxSize!(char, short, ubyte) == 2); + static assert(maxSize!(char, long, ubyte) == 8); + import std.algorithm.comparison : max; + static assert(maxSize!(long, S) == max(long.sizeof, S.sizeof)); + static assert(maxSize!(S, T) == max(S.sizeof, T.sizeof)); + static assert(maxSize!(int, ubyte[7]) == 7); + static assert(maxSize!(int, ubyte[3]) == 4); + static assert(maxSize!(int, int, ubyte[3]) == 4); + static assert(maxSize!(void, int, ubyte[3]) == 4); + static assert(maxSize!(void) == 1); } struct This; -private alias This2Variant(V, T...) = AliasSeq!(ReplaceType!(This, V, T)); +private alias This2Variant(V, T...) = AliasSeq!(ReplaceTypeUnless!(isAlgebraic, This, V, T)); + +// We can't just use maxAlignment because no types might be specified +// to VariantN, so handle that here and then pass along the rest. +private template maxVariantAlignment(U...) +if (isTypeTuple!U) +{ + static if (U.length == 0) + { + import std.algorithm.comparison : max; + enum maxVariantAlignment = max(real.alignof, size_t.alignof); + } + else + enum maxVariantAlignment = maxAlignment!(U); +} /** * Back-end type seldom used directly by user - * code. Two commonly-used types using $(D VariantN) are: + * code. Two commonly-used types using `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) + * unbounded set of types. If any of the types in the `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 + * of a pointer within the `Variant`, but this also implies some + * overhead. `Variant` can accommodate all primitive types and * all user-defined types.)) * - * Both $(D Algebraic) and $(D Variant) share $(D + * Both `Algebraic` and `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 + * `VariantN` is a discriminated union type parameterized + * with the largest size of the types stored (`maxDataSize`) + * and with the list of allowed types (`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 + * `VariantN` object without being boxed (types larger * than this will be boxed). * */ @@ -145,9 +187,9 @@ private: } 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). + /** Tells whether a type `T` is statically _allowed for + * storage inside a `VariantN` object by looking + * `T` up in `AllowedTypes`. */ public template allowed(T) { @@ -165,15 +207,15 @@ private: apply, postblit, destruct } // state - ptrdiff_t function(OpID selector, ubyte[size]* store, void* data) fptr - = &handler!(void); union { - ubyte[size] store; + align(maxVariantAlignment!(AllowedTypes)) ubyte[size] store; // conservatively mark the region as pointers static if (size >= (void*).sizeof) void*[size / (void*).sizeof] p; } + ptrdiff_t function(OpID selector, ubyte[size]* store, void* data) fptr + = &handler!(void); // internals // Handler for an uninitialized value @@ -235,25 +277,35 @@ private: { static if (is(typeof(*rhsPA == *zis))) { - if (*rhsPA == *zis) + enum isEmptyStructWithoutOpEquals = is(A == struct) && A.tupleof.length == 0 && + !__traits(hasMember, A, "opEquals"); + static if (isEmptyStructWithoutOpEquals) { + // The check above will always succeed if A is an empty struct. + // Don't generate unreachable code as seen in + // https://issues.dlang.org/show_bug.cgi?id=21231 return 0; } - static if (is(typeof(*zis < *rhsPA))) + else { - // 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; + 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 - { - // Not equal, and type does not support ordering - // comparisons. - return ptrdiff_t.min; + } } } else @@ -271,7 +323,14 @@ private: static bool tryPutting(A* src, TypeInfo targetType, void* target) { alias UA = Unqual!A; - alias MutaTypes = AliasSeq!(UA, ImplicitConversionTargets!UA); + static if (isStaticArray!A && is(typeof(UA.init[0]))) + { + alias MutaTypes = AliasSeq!(UA, typeof(UA.init[0])[], AllImplicitConversionTargets!UA); + } + else + { + alias MutaTypes = AliasSeq!(UA, AllImplicitConversionTargets!UA); + } alias ConstTypes = staticMap!(ConstOf, MutaTypes); alias SharedTypes = staticMap!(SharedOf, MutaTypes); alias SharedConstTypes = staticMap!(SharedConstOf, MutaTypes); @@ -299,13 +358,20 @@ private: if (targetType != typeid(T)) continue; - static if (is(typeof(*cast(T*) target = *src)) || + // SPECIAL NOTE: variant only will ever create a new value with + // tryPutting (effectively), and T is ALWAYS the same type of + // A, but with different modifiers (and a limited set of + // implicit targets). So this checks to see if we can construct + // a T from A, knowing that prerequisite. This handles issues + // where the type contains some constant data aside from the + // modifiers on the type itself. + static if (is(typeof(delegate T() {return *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; + import core.internal.lifetime : emplaceRef; auto zat = cast(T*) target; if (src) @@ -313,7 +379,15 @@ private: static if (T.sizeof > 0) assert(target, "target must be non-null"); - emplaceRef(*cast(Unqual!T*) zat, *cast(UA*) src); + static if (isStaticArray!A && isDynamicArray!T) + { + auto this_ = (*src)[]; + emplaceRef(*cast(Unqual!T*) zat, cast(Unqual!T) this_); + } + else + { + emplaceRef(*cast(Unqual!T*) zat, *cast(UA*) src); + } } } else @@ -339,7 +413,17 @@ private: static if (target.size < A.sizeof) { if (target.type.tsize < A.sizeof) - *cast(A**)&target.store = new A; + { + static if (is(A == U[n], U, size_t n)) + { + A* p = cast(A*)(new U[n]).ptr; + } + else + { + A* p = new A; + } + *cast(A**)&target.store = p; + } } tryPutting(zis, typeid(A), cast(void*) getPtr(&target.store)) || assert(false); @@ -411,7 +495,7 @@ private: case OpID.index: auto result = cast(Variant*) parm; - static if (isArray!(A) && !is(Unqual!(typeof(A.init[0])) == void)) + static if (isArray!(A) && !is(immutable typeof(A.init[0]) == immutable void)) { // array type; input and output are the same VariantN size_t index = result.convertsTo!(int) @@ -439,7 +523,7 @@ private: (*zis)[index] = args[0].get!(typeof((*zis)[0])); break; } - else static if (isAssociativeArray!(A)) + else static if (isAssociativeArray!(A) && is(typeof((*zis)[A.init.keys[0]] = A.init.values[0]))) { (*zis)[args[1].get!(typeof(A.init.keys[0]))] = args[0].get!(typeof(A.init.values[0])); @@ -451,7 +535,8 @@ private: } case OpID.catAssign: - static if (!is(Unqual!(typeof((*zis)[0])) == void) && is(typeof((*zis)[0])) && is(typeof((*zis) ~= *zis))) + static if (!is(immutable typeof((*zis)[0]) == immutable void) && + is(typeof((*zis)[0])) && is(typeof(*zis ~= *zis))) { // array type; parm is the element to append auto arg = cast(Variant*) parm; @@ -529,14 +614,14 @@ private: case OpID.postblit: static if (hasElaborateCopyConstructor!A) { - typeid(A).postblit(zis); + zis.__xpostblit(); } break; case OpID.destruct: static if (hasElaborateDestructor!A) { - typeid(A).destroy(zis); + zis.__xdtor(); } break; @@ -545,10 +630,8 @@ private: return 0; } - enum doUnittest = is(VariantN == Variant); - public: - /** Constructs a $(D VariantN) value given an argument of a + /** Constructs a `VariantN` value given an argument of a * generic type. Statically rejects disallowed types. */ @@ -578,16 +661,23 @@ public: { ~this() { - fptr(OpID.destruct, &store, null); + // Infer the safety of the provided types + static if (AllowedTypes.length) + { + if (0) + { + AllowedTypes var; + } + } + (() @trusted => fptr(OpID.destruct, &store, null))(); } } - /** Assigns a $(D VariantN) from a generic + /** Assigns a `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); @@ -604,6 +694,8 @@ public: } else { + import core.lifetime : copyEmplace; + static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes)) { // Assignment should destruct previous value @@ -611,37 +703,17 @@ public: } 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); - } - } + copyEmplace(rhs, *cast(T*) &store); else { - import core.stdc.string : memcpy; - static if (__traits(compiles, {new T(T.init);})) - { - auto p = new T(rhs); - } + static if (is(T == U[n], U, size_t n)) + auto p = cast(T*) (new U[n]).ptr; else - { auto p = new T; - *p = rhs; - } - memcpy(&store, &p, p.sizeof); + copyEmplace(rhs, *p); + *(cast(T**) &store) = p; } + fptr = &handler!(T); } return this; @@ -671,7 +743,7 @@ public: return pack[0]; } - /** Returns true if and only if the $(D VariantN) object + /** Returns true if and only if the `VariantN` object * holds a valid value (has been initialized with, or assigned * from, a valid value). */ @@ -682,7 +754,7 @@ public: } /// - static if (doUnittest) + version (StdDdoc) @system unittest { Variant a; @@ -695,10 +767,10 @@ public: } /** - * 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 + * If the `VariantN` object holds a value of the + * $(I exact) type `T`, returns a pointer to that + * value. Otherwise, returns `null`. In cases + * where `T` is statically disallowed, $(D * peek) will not compile. */ @property inout(T)* peek(T)() inout @@ -715,7 +787,7 @@ public: } /// - static if (doUnittest) + version (StdDdoc) @system unittest { Variant a = 5; @@ -726,7 +798,7 @@ public: } /** - * Returns the $(D typeid) of the currently held value. + * Returns the `typeid` of the currently held value. */ @property TypeInfo type() const nothrow @trusted @@ -739,10 +811,10 @@ public: } /** - * Returns $(D true) if and only if the $(D VariantN) + * Returns `true` if and only if the `VariantN` * object holds an object implicitly convertible to type `T`. * Implicit convertibility is defined as per - * $(REF_ALTTEXT ImplicitConversionTargets, ImplicitConversionTargets, std,traits). + * $(REF_ALTTEXT AllImplicitConversionTargets, AllImplicitConversionTargets, std,traits). */ @property bool convertsTo(T)() const @@ -790,11 +862,11 @@ public: } /** - * Returns the value stored in the $(D VariantN) object, + * Returns the value stored in the `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 + * T). If `T` is a string type, the value is formatted as + * a string. If the `VariantN` object is a string, a + * parse of the string to type `T` is attempted. If a * conversion is not possible, throws a $(D * VariantException). */ @@ -862,9 +934,9 @@ public: // returns 1 if the two are equal bool opEquals(T)(auto ref T rhs) const - if (allowed!T || is(Unqual!T == VariantN)) + if (allowed!T || is(immutable T == immutable VariantN)) { - static if (is(Unqual!T == VariantN)) + static if (is(immutable T == immutable VariantN)) alias temp = rhs; else auto temp = VariantN(rhs); @@ -881,7 +953,7 @@ public: /** * Ordering comparison used by the "<", "<=", ">", and ">=" * operators. In case comparison is not sensible between the held - * value and $(D rhs), an exception is thrown. + * value and `rhs`, an exception is thrown. */ int opCmp(T)(T rhs) @@ -985,79 +1057,42 @@ public: } /** - * Arithmetic between $(D VariantN) objects and numeric - * values. All arithmetic operations return a $(D VariantN) + * Arithmetic between `VariantN` objects and numeric + * values. All arithmetic operations return a `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); } + VariantN opBinary(string op, T)(T rhs) + if ((op == "+" || op == "-" || op == "*" || op == "/" || op == "^^" || op == "%") && + is(typeof(opArithmetic!(T, op)(rhs)))) + { return opArithmetic!(T, op)(rhs); } ///ditto - VariantN opOr(T)(T rhs) { return opLogic!(T, "|")(rhs); } + VariantN opBinary(string op, T)(T rhs) + if ((op == "&" || op == "|" || op == "^" || op == ">>" || op == "<<" || op == ">>>") && + is(typeof(opLogic!(T, op)(rhs)))) + { return opLogic!(T, op)(rhs); } ///ditto - VariantN opXor(T)(T rhs) { return opLogic!(T, "^")(rhs); } + VariantN opBinaryRight(string op, T)(T lhs) + if ((op == "+" || op == "*") && + is(typeof(opArithmetic!(T, op)(lhs)))) + { return opArithmetic!(T, op)(lhs); } ///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); - // } + VariantN opBinaryRight(string op, T)(T lhs) + if ((op == "&" || op == "|" || op == "^") && + is(typeof(opLogic!(T, op)(lhs)))) + { return opLogic!(T, op)(lhs); } ///ditto - VariantN opCat(T)(T rhs) + VariantN opBinary(string op, T)(T rhs) + if (op == "~") { auto temp = this; temp ~= rhs; return temp; } // ///ditto - // VariantN opCat_r(T)(T rhs) + // VariantN opBinaryRight(string op, T)(T rhs) + // if (op == "~") // { // VariantN temp = rhs; // temp ~= this; @@ -1065,33 +1100,18 @@ public: // } ///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) + VariantN opOpAssign(string op, T)(T rhs) { - auto toAppend = Variant(rhs); - fptr(OpID.catAssign, &store, &toAppend) == 0 || assert(false); - return this; + static if (op != "~") + { + mixin("return this = this" ~ op ~ "rhs;"); + } + else + { + auto toAppend = Variant(rhs); + fptr(OpID.catAssign, &store, &toAppend) == 0 || assert(false); + return this; + } } /** @@ -1107,7 +1127,7 @@ public: } /// - static if (doUnittest) + version (StdDdoc) @system unittest { Variant a = new int[10]; @@ -1144,7 +1164,7 @@ public: return opIndexAssign(mixin(`opIndex(i)` ~ op ~ `value`), i); } - /** If the $(D VariantN) contains an (associative) array, + /** If the `VariantN` contains an (associative) array, * returns the _length of that array. Otherwise, throws an * exception. */ @@ -1154,7 +1174,7 @@ public: } /** - If the $(D VariantN) contains an array, applies $(D dg) to each + If the `VariantN` contains an array, applies `dg` to each element of the array in turn. Otherwise, throws an exception. */ int opApply(Delegate)(scope Delegate dg) if (is(Delegate == delegate)) @@ -1192,6 +1212,63 @@ public: } } +/// +@system unittest +{ + alias Var = VariantN!(maxSize!(int, double, string)); + + Var a; // Must assign before use, otherwise exception ensues + // Initialize with an integer; make the type int + Var 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 +@system unittest +{ + alias Var = VariantN!(maxSize!(int[])); + + Var a = new int[42]; + assert(a.length == 42); + a[5] = 7; + assert(a[5] == 7); +} + +@safe unittest +{ + alias V = VariantN!24; + const alignMask = V.alignof - 1; + assert(V.sizeof == ((24 + (void*).sizeof + alignMask) & ~alignMask)); +} + +/// Can also assign class values +@system unittest +{ + alias Var = VariantN!(maxSize!(int*)); // classes are pointers + Var a; + + class Foo {} + auto foo = new Foo; + a = foo; + assert(*a.peek!(Foo) == foo); // and full type information is preserved +} + @system unittest { import std.conv : to; @@ -1214,7 +1291,7 @@ public: assert(v[42] == 5); } -// opIndex with static arrays, issue 12771 +// opIndex with static arrays, https://issues.dlang.org/show_bug.cgi?id=12771 @system unittest { int[4] elements = [0, 1, 2, 3]; @@ -1245,7 +1322,33 @@ public: assertThrown!VariantException(v[1] = Variant(null)); } -//Issue# 8195 +// https://issues.dlang.org/show_bug.cgi?id=10879 +@system unittest +{ + int[10] arr = [1,2,3,4,5,6,7,8,9,10]; + Variant v1 = arr; + Variant v2; + v2 = arr; + assert(v1 == arr); + assert(v2 == arr); + foreach (i, e; arr) + { + assert(v1[i] == e); + assert(v2[i] == e); + } + static struct LargeStruct + { + int[100] data; + } + LargeStruct ls; + ls.data[] = 4; + v1 = ls; + Variant v3 = ls; + assert(v1 == ls); + assert(v3 == ls); +} + +// https://issues.dlang.org/show_bug.cgi?id=8195 @system unittest { struct S @@ -1265,7 +1368,7 @@ public: assert(v == S.init); } -// Issue #10961 +// https://issues.dlang.org/show_bug.cgi?id=10961 @system unittest { // Primarily test that we can assign a void[] to a Variant. @@ -1275,7 +1378,7 @@ public: assert(returned == elements); } -// Issue #13352 +// https://issues.dlang.org/show_bug.cgi?id=13352 @system unittest { alias TP = Algebraic!(long); @@ -1291,7 +1394,7 @@ public: assert(a + c == 4L); } -// Issue #13354 +// https://issues.dlang.org/show_bug.cgi?id=13354 @system unittest { alias A = Algebraic!(string[]); @@ -1309,14 +1412,14 @@ public: assert(aa["b"] == 3); } -// Issue #14198 +// https://issues.dlang.org/show_bug.cgi?id=14198 @system unittest { Variant a = true; assert(a.type == typeid(bool)); } -// Issue #14233 +// https://issues.dlang.org/show_bug.cgi?id=14233 @system unittest { alias Atom = Algebraic!(string, This[]); @@ -1333,7 +1436,7 @@ pure nothrow @nogc a = 1.0; } -// Issue 14457 +// https://issues.dlang.org/show_bug.cgi?id=14457 @system unittest { alias A = Algebraic!(int, float, double); @@ -1347,7 +1450,7 @@ pure nothrow @nogc assert(a.get!float == 6f); } -// Issue 14585 +// https://issues.dlang.org/show_bug.cgi?id=14585 @system unittest { static struct S @@ -1358,7 +1461,7 @@ pure nothrow @nogc Variant(S()).get!S; } -// Issue 14586 +// https://issues.dlang.org/show_bug.cgi?id=14586 @system unittest { const Variant v = new immutable Object; @@ -1375,6 +1478,135 @@ pure nothrow @nogc v.get!S; } +// https://issues.dlang.org/show_bug.cgi?id=13262 +@system unittest +{ + static void fun(T)(Variant v){ + T x; + v = x; + auto r = v.get!(T); + } + Variant v; + fun!(shared(int))(v); + fun!(shared(int)[])(v); + + static struct S1 + { + int c; + string a; + } + + static struct S2 + { + string a; + shared int[] b; + } + + static struct S3 + { + string a; + shared int[] b; + int c; + } + + fun!(S1)(v); + fun!(shared(S1))(v); + fun!(S2)(v); + fun!(shared(S2))(v); + fun!(S3)(v); + fun!(shared(S3))(v); + + // ensure structs that are shared, but don't have shared postblits + // can't be used. + static struct S4 + { + int x; + this(this) {x = 0;} + } + + fun!(S4)(v); + static assert(!is(typeof(fun!(shared(S4))(v)))); +} + +@safe unittest +{ + Algebraic!(int) x; + + static struct SafeS + { + @safe ~this() {} + } + + Algebraic!(SafeS) y; +} + +// https://issues.dlang.org/show_bug.cgi?id=19986 +@system unittest +{ + VariantN!32 v; + v = const(ubyte[33]).init; + + struct S + { + ubyte[33] s; + } + + VariantN!32 v2; + v2 = const(S).init; +} + +// https://issues.dlang.org/show_bug.cgi?id=21021 +@system unittest +{ + static struct S + { + int h; + int[5] array; + alias h this; + } + + S msg; + msg.array[] = 3; + Variant a = msg; + auto other = a.get!S; + assert(msg.array[0] == 3); + assert(other.array[0] == 3); +} + +// https://issues.dlang.org/show_bug.cgi?id=21231 +// Compatibility with -preview=fieldwise +@system unittest +{ + static struct Empty + { + bool opCmp(const scope ref Empty) const + { return false; } + } + + Empty a, b; + assert(a == b); + assert(!(a < b)); + + VariantN!(4, Empty) v = a; + assert(v == b); + assert(!(v < b)); +} + +// Compatibility with -preview=fieldwise +@system unittest +{ + static struct Empty + { + bool opEquals(const scope ref Empty) const + { return false; } + } + + Empty a, b; + assert(a != b); + + VariantN!(4, Empty) v = a; + assert(v != b); +} /** _Algebraic data type restricted to a closed set of possible @@ -1384,6 +1616,8 @@ useful when it is desirable to restrict what a discriminated type could hold to the end of defining simpler and more efficient manipulation. +$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new +code. Instead, use $(REF SumType, std,sumtype).) */ template Algebraic(T...) { @@ -1435,20 +1669,70 @@ be arbitrarily complex. assert(obj.get!2["customer"] == "John"); } +private struct FakeComplexReal +{ + real re, im; +} + /** 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 +`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())); +alias Variant = VariantN!(maxSize!(FakeComplexReal, char[], void delegate())); + +/// +@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 +@system unittest +{ + Variant a = new int[42]; + assert(a.length == 42); + a[5] = 7; + assert(a[5] == 7); +} + +/// Can also assign class values +@system unittest +{ + Variant a; + + class Foo {} + auto foo = new Foo; + a = foo; + assert(*a.peek!(Foo) == foo); // and full type information is preserved +} /** - * Returns an array of variants constructed from $(D args). + * Returns an array of variants constructed from `args`. * - * This is by design. During construction the $(D Variant) needs + * This is by design. During construction the `Variant` needs * static type information about the type being held, so as to store a * pointer to function for fast retrieval. */ @@ -1475,9 +1759,9 @@ Variant[] variantArray(T...)(T args) * 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 + * assignment and `hasValue`;) $(LI A `get` or + * `coerce` is attempted with an incompatible target type;) + * $(LI A comparison between `Variant` objects of * incompatible types is attempted.)) * */ @@ -1503,6 +1787,24 @@ static class VariantException : Exception } } +/// +@system unittest +{ + import std.exception : assertThrown; + + Variant v; + + // uninitialized use + assertThrown!VariantException(v + 1); + assertThrown!VariantException(v.length); + + // .get with an incompatible target type + assertThrown!VariantException(Variant("a").get!int); + + // comparison between incompatible types + assertThrown!VariantException(Variant(3) < Variant("a")); +} + @system unittest { alias W1 = This2Variant!(char, int, This[int]); @@ -1660,7 +1962,7 @@ static class VariantException : Exception assert( v.peek!(double) ); assert( v.convertsTo!(real) ); //@@@ BUG IN COMPILER: DOUBLE SHOULD NOT IMPLICITLY CONVERT TO FLOAT - assert( !v.convertsTo!(float) ); + assert( v.convertsTo!(float) ); assert( *v.peek!(double) == 3.1413 ); auto u = Variant(v); @@ -1721,16 +2023,13 @@ static class VariantException : Exception { 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 ); } { @@ -1759,9 +2058,9 @@ static class VariantException : Exception static assert(!__traits(compiles, {v > null;})); } +// https://issues.dlang.org/show_bug.cgi?id=1558 @system unittest { - // bug 1558 Variant va=1; Variant vb=-2; assert((va+vb).get!(int) == -1); @@ -1826,7 +2125,7 @@ static class VariantException : Exception assert(v.convertsTo!(char[])); } -// http://d.puremagic.com/issues/show_bug.cgi?id=5424 +// https://issues.dlang.org/show_bug.cgi?id=5424 @system unittest { interface A { @@ -1842,14 +2141,14 @@ static class VariantException : Exception Variant b = Variant(a); } +// https://issues.dlang.org/show_bug.cgi?id=7070 @system unittest { - // bug 7070 Variant v; v = null; } -// Class and interface opEquals, issue 12157 +// Class and interface opEquals, https://issues.dlang.org/show_bug.cgi?id=12157 @system unittest { class Foo { } @@ -1867,7 +2166,7 @@ static class VariantException : Exception assert(v2 == f2); } -// Const parameters with opCall, issue 11361. +// Const parameters with opCall, https://issues.dlang.org/show_bug.cgi?id=11361 @system unittest { static string t1(string c) { @@ -1898,7 +2197,7 @@ static class VariantException : Exception assert(v3(4).type == typeid(char[])); } -// issue 12071 +// https://issues.dlang.org/show_bug.cgi?id=12071 @system unittest { static struct Structure { int data; } @@ -1915,7 +2214,8 @@ static class VariantException : Exception assert(called); } -// Ordering comparisons of incompatible types, e.g. issue 7990. +// Ordering comparisons of incompatible types +// e.g. https://issues.dlang.org/show_bug.cgi?id=7990 @system unittest { import std.exception : assertThrown; @@ -1927,7 +2227,8 @@ static class VariantException : Exception assertThrown!VariantException(Variant(3) < Variant.init); } -// Handling of unordered types, e.g. issue 9043. +// Handling of unordered types +// https://issues.dlang.org/show_bug.cgi?id=9043 @system unittest { import std.exception : assertThrown; @@ -1940,7 +2241,8 @@ static class VariantException : Exception assertThrown!VariantException(Variant(A(3)) < Variant(A(4))); } -// Handling of empty types and arrays, e.g. issue 10958 +// Handling of empty types and arrays +// https://issues.dlang.org/show_bug.cgi?id=10958 @system unittest { class EmptyClass { } @@ -1971,7 +2273,8 @@ static class VariantException : Exception assert(a.get!EmptyArray == arr); } -// Handling of void function pointers / delegates, e.g. issue 11360 +// Handling of void function pointers / delegates +// https://issues.dlang.org/show_bug.cgi?id=11360 @system unittest { static void t1() { } @@ -1983,7 +2286,8 @@ static class VariantException : Exception assert(v2() == 3); } -// Using peek for large structs, issue 8580 +// Using peek for large structs +// https://issues.dlang.org/show_bug.cgi?id=8580 @system unittest { struct TestStruct(bool pad) @@ -2014,16 +2318,25 @@ static class VariantException : Exception testPeekWith!(TestStruct!true)(); } +// https://issues.dlang.org/show_bug.cgi?id=18780 +@system unittest +{ + int x = 7; + Variant a = x; + assert(a.convertsTo!ulong); + assert(a.convertsTo!uint); +} + /** * 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 + * with `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 + * `variant` are handled across all handlers. + * `visit` allows delegates and static functions to be passed * as parameters. * * If a function with an untyped parameter is specified, this function is called @@ -2164,11 +2477,11 @@ if (Handlers.length > 0) 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))); + static assert( __traits(compiles, maybenumber.visit!((string x) => x ~ "a", x => "foobar"[0 .. x + 1]))); // bad, x ~ "a" valid for string but not int - static assert(!__traits(compiles, number.visit!(x => x ~ "a"))); + static assert(!__traits(compiles, maybenumber.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"))); + static assert(!__traits(compiles, maybenumber.visit!(x => x + 1, x => x ~ "a"))); } /** @@ -2176,7 +2489,7 @@ if (Handlers.length > 0) * 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 + * either `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 @@ -2260,11 +2573,11 @@ if (isAlgebraic!VariantType && Handler.length > 0) /** - * Returns: Struct where $(D indices) is an array which + * Returns: Struct where `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; + * have parameters, the field `exceptionFuncIdx` is set; * otherwise it's -1. */ auto visitGetOverloadMap() @@ -2277,6 +2590,24 @@ if (isAlgebraic!VariantType && Handler.length > 0) Result result; + enum int nonmatch = () + { + foreach (int dgidx, dg; Handler) + { + bool found = false; + foreach (T; AllowedTypes) + { + found |= __traits(compiles, { static assert(isSomeFunction!(dg!T)); }); + found |= __traits(compiles, (T t) { dg(t); }); + found |= __traits(compiles, dg()); + } + if (!found) return dgidx; + } + return -1; + }(); + static assert(nonmatch == -1, "No match for visit handler #"~ + nonmatch.stringof~" ("~Handler[nonmatch].stringof~")"); + foreach (tidx, T; AllowedTypes) { bool added = false; @@ -2308,7 +2639,7 @@ if (isAlgebraic!VariantType && Handler.length > 0) result.indices[tidx] = dgidx; } } - else static if (isSomeFunction!(dg!T)) + else static if (__traits(compiles, { static assert(isSomeFunction!(dg!T)); })) { assert(result.generalFuncIdx == -1 || result.generalFuncIdx == dgidx, @@ -2316,10 +2647,6 @@ if (isAlgebraic!VariantType && Handler.length > 0) result.generalFuncIdx = dgidx; } // Handle composite visitors with opCall overloads - else - { - static assert(false, dg.stringof ~ " is not a function or delegate"); - } } if (!added) @@ -2373,6 +2700,19 @@ if (isAlgebraic!VariantType && Handler.length > 0) assert(false); } +// https://issues.dlang.org/show_bug.cgi?id=21253 +@system unittest +{ + static struct A { int n; } + static struct B { } + + auto a = Algebraic!(A, B)(B()); + assert(a.visit!( + (B _) => 42, + (a ) => a.n + ) == 42); +} + @system unittest { // validate that visit can be called with a const type @@ -2389,9 +2729,9 @@ if (isAlgebraic!VariantType && Handler.length > 0) assert(depth(fb) == 3); } +// https://issues.dlang.org/show_bug.cgi?id=16383 @system unittest { - // https://issues.dlang.org/show_bug.cgi?id=16383 class Foo {this() immutable {}} alias V = Algebraic!(immutable Foo); @@ -2401,9 +2741,9 @@ if (isAlgebraic!VariantType && Handler.length > 0) assert(x == 3); } +// https://issues.dlang.org/show_bug.cgi?id=5310 @system unittest { - // http://d.puremagic.com/issues/show_bug.cgi?id=5310 const Variant a; assert(a == a); Variant b; @@ -2417,9 +2757,9 @@ if (isAlgebraic!VariantType && Handler.length > 0) assert(a[0] == 2); } +// https://issues.dlang.org/show_bug.cgi?id=10017 @system unittest { - // http://d.puremagic.com/issues/show_bug.cgi?id=10017 static struct S { ubyte[Variant.size + 1] s; @@ -2430,32 +2770,33 @@ if (isAlgebraic!VariantType && Handler.length > 0) v2 = v1; // AssertError: target must be non-null assert(v1 == v2); } + +// https://issues.dlang.org/show_bug.cgi?id=7069 @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)) + static foreach (qual; AliasSeq!(Alias, ConstOf)) { assert(v.get!(qual!int) == 10); assert(v.get!(qual!float) == 10.0f); } - foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) + static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) { assertThrown!VariantException(v.get!(qual!int)); } const(int) ci = 20; v = ci; - foreach (qual; AliasSeq!(ConstOf)) + static foreach (qual; AliasSeq!(ConstOf)) { assert(v.get!(qual!int) == 20); assert(v.get!(qual!float) == 20.0f); } - foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf)) + static foreach (qual; AliasSeq!(Alias, ImmutableOf, SharedOf, SharedConstOf)) { assertThrown!VariantException(v.get!(qual!int)); assertThrown!VariantException(v.get!(qual!float)); @@ -2463,12 +2804,12 @@ if (isAlgebraic!VariantType && Handler.length > 0) immutable(int) ii = ci; v = ii; - foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf)) + static foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf)) { assert(v.get!(qual!int) == 20); assert(v.get!(qual!float) == 20.0f); } - foreach (qual; AliasSeq!(MutableOf, SharedOf)) + static foreach (qual; AliasSeq!(Alias, SharedOf)) { assertThrown!VariantException(v.get!(qual!int)); assertThrown!VariantException(v.get!(qual!float)); @@ -2476,12 +2817,12 @@ if (isAlgebraic!VariantType && Handler.length > 0) int[] ai = [1,2,3]; v = ai; - foreach (qual; AliasSeq!(MutableOf, ConstOf)) + static foreach (qual; AliasSeq!(Alias, ConstOf)) { assert(v.get!(qual!(int[])) == [1,2,3]); assert(v.get!(qual!(int)[]) == [1,2,3]); } - foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) + static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) { assertThrown!VariantException(v.get!(qual!(int[]))); assertThrown!VariantException(v.get!(qual!(int)[])); @@ -2489,12 +2830,12 @@ if (isAlgebraic!VariantType && Handler.length > 0) const(int[]) cai = [4,5,6]; v = cai; - foreach (qual; AliasSeq!(ConstOf)) + static 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)) + static foreach (qual; AliasSeq!(Alias, ImmutableOf, SharedOf, SharedConstOf)) { assertThrown!VariantException(v.get!(qual!(int[]))); assertThrown!VariantException(v.get!(qual!(int)[])); @@ -2508,7 +2849,7 @@ if (isAlgebraic!VariantType && Handler.length > 0) 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)) + static foreach (qual; AliasSeq!(Alias)) { assertThrown!VariantException(v.get!(qual!(int[]))); assertThrown!VariantException(v.get!(qual!(int)[])); @@ -2518,13 +2859,13 @@ if (isAlgebraic!VariantType && Handler.length > 0) class B : A {} B b = new B(); v = b; - foreach (qual; AliasSeq!(MutableOf, ConstOf)) + static foreach (qual; AliasSeq!(Alias, 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)) + static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) { assertThrown!VariantException(v.get!(qual!B)); assertThrown!VariantException(v.get!(qual!A)); @@ -2533,13 +2874,13 @@ if (isAlgebraic!VariantType && Handler.length > 0) const(B) cb = new B(); v = cb; - foreach (qual; AliasSeq!(ConstOf)) + static 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)) + static foreach (qual; AliasSeq!(Alias, ImmutableOf, SharedOf, SharedConstOf)) { assertThrown!VariantException(v.get!(qual!B)); assertThrown!VariantException(v.get!(qual!A)); @@ -2548,13 +2889,13 @@ if (isAlgebraic!VariantType && Handler.length > 0) immutable(B) ib = new immutable(B)(); v = ib; - foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf)) + static 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)) + static foreach (qual; AliasSeq!(Alias, SharedOf)) { assertThrown!VariantException(v.get!(qual!B)); assertThrown!VariantException(v.get!(qual!A)); @@ -2563,13 +2904,13 @@ if (isAlgebraic!VariantType && Handler.length > 0) shared(B) sb = new shared B(); v = sb; - foreach (qual; AliasSeq!(SharedOf, SharedConstOf)) + static 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)) + static foreach (qual; AliasSeq!(Alias, ImmutableOf, ConstOf)) { assertThrown!VariantException(v.get!(qual!B)); assertThrown!VariantException(v.get!(qual!A)); @@ -2578,13 +2919,13 @@ if (isAlgebraic!VariantType && Handler.length > 0) shared(const(B)) scb = new shared const B(); v = scb; - foreach (qual; AliasSeq!(SharedConstOf)) + static 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)) + static foreach (qual; AliasSeq!(Alias, ConstOf, ImmutableOf, SharedOf)) { assertThrown!VariantException(v.get!(qual!B)); assertThrown!VariantException(v.get!(qual!A)); @@ -2592,11 +2933,11 @@ if (isAlgebraic!VariantType && Handler.length > 0) } } +// https://issues.dlang.org/show_bug.cgi?id=12540 @system unittest { static struct DummyScope { - // https://d.puremagic.com/issues/show_bug.cgi?id=12540 alias Alias12540 = Algebraic!Class12540; static class Class12540 @@ -2654,7 +2995,7 @@ if (isAlgebraic!VariantType && Handler.length > 0) @system unittest { - // Bugzilla 13300 + // https://issues.dlang.org/show_bug.cgi?id=13300 static struct S { this(this) {} @@ -2682,9 +3023,9 @@ if (isAlgebraic!VariantType && Handler.length > 0) auto a = appender!(T[]); } +// https://issues.dlang.org/show_bug.cgi?id=13871 @system unittest { - // Bugzilla 13871 alias A = Algebraic!(int, typeof(null)); static struct B { A value; } alias C = std.variant.Algebraic!B; @@ -2721,9 +3062,9 @@ if (isAlgebraic!VariantType && Handler.length > 0) assertThrown!VariantException(v.length); } +// https://issues.dlang.org/show_bug.cgi?id=13534 @system unittest { - // Bugzilla 13534 static assert(!__traits(compiles, () @safe { auto foo() @system { return 3; } auto v = Variant(&foo); @@ -2731,9 +3072,9 @@ if (isAlgebraic!VariantType && Handler.length > 0) })); } +// https://issues.dlang.org/show_bug.cgi?id=15039 @system unittest { - // Bugzilla 15039 import std.typecons; import std.variant; @@ -2749,9 +3090,9 @@ if (isAlgebraic!VariantType && Handler.length > 0) ); } +// https://issues.dlang.org/show_bug.cgi?id=15791 @system unittest { - // Bugzilla 15791 int n = 3; struct NS1 { int foo() { return n + 10; } } struct NS2 { int foo() { return n * 10; } } @@ -2763,9 +3104,103 @@ if (isAlgebraic!VariantType && Handler.length > 0) assert(v.get!NS2.foo() == 30); } +// https://issues.dlang.org/show_bug.cgi?id=15827 @system unittest { - // Bugzilla 15827 static struct Foo15827 { Variant v; this(Foo15827 v) {} } Variant v = Foo15827.init; } + +// https://issues.dlang.org/show_bug.cgi?id=18934 +@system unittest +{ + static struct S + { + const int x; + } + + auto s = S(42); + Variant v = s; + auto s2 = v.get!S; + assert(s2.x == 42); + Variant v2 = v; // support copying from one variant to the other + v2 = S(2); + v = v2; + assert(v.get!S.x == 2); +} + +// https://issues.dlang.org/show_bug.cgi?id=19200 +@system unittest +{ + static struct S + { + static int opBinaryRight(string op : "|", T)(T rhs) + { + return 3; + } + } + + S s; + Variant v; + auto b = v | s; + assert(b == 3); +} + +// https://issues.dlang.org/show_bug.cgi?id=11061 +@system unittest +{ + int[4] el = [0, 1, 2, 3]; + int[3] nl = [0, 1, 2]; + Variant v1 = el; + assert(v1 == el); // Compare Var(static) to static + assert(v1 != nl); // Compare static arrays of different length + assert(v1 == [0, 1, 2, 3]); // Compare Var(static) to dynamic. + assert(v1 != [0, 1, 2]); + int[] dyn = [0, 1, 2, 3]; + v1 = dyn; + assert(v1 == el); // Compare Var(dynamic) to static. + assert(v1 == [0, 1] ~ [2, 3]); // Compare Var(dynamic) to dynamic +} + +// https://issues.dlang.org/show_bug.cgi?id=15940 +@system unittest +{ + class C { } + struct S + { + C a; + alias a this; + } + S s = S(new C()); + auto v = Variant(s); // compile error +} + +@system unittest +{ + // Test if we don't have scoping issues. + Variant createVariant(int[] input) + { + int[2] el = [input[0], input[1]]; + Variant v = el; + return v; + } + Variant v = createVariant([0, 1]); + createVariant([2, 3]); + assert(v == [0,1]); +} + +// https://issues.dlang.org/show_bug.cgi?id=19994 +@safe unittest +{ + alias Inner = Algebraic!(This*); + alias Outer = Algebraic!(Inner, This*); + + static assert(is(Outer.AllowedTypes == AliasSeq!(Inner, Outer*))); +} + +// https://issues.dlang.org/show_bug.cgi?id=21296 +@system unittest +{ + immutable aa = ["0": 0]; + auto v = Variant(aa); // compile error +} diff --git a/libphobos/src/std/windows/charset.d b/libphobos/src/std/windows/charset.d index ee7211d..69626b5 100644 --- a/libphobos/src/std/windows/charset.d +++ b/libphobos/src/std/windows/charset.d @@ -3,11 +3,11 @@ /** * Support UTF-8 on Windows 95, 98 and ME systems. * - * Copyright: Copyright Digital Mars 2005 - 2009. + * Copyright: Copyright The D Language Foundation" 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. +/* Copyright The D Language Foundation" 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) @@ -30,7 +30,7 @@ version (StdDdoc) * Authors: * yaneurao, Walter Bright, Stewart Gordon */ - const(char)* toMBSz(in char[] s, uint codePage = 0); + const(char)* toMBSz(scope const(char)[] s, uint codePage = 0); /********************************************** * Converts the null-terminated string s from a Windows 8-bit character set @@ -50,14 +50,14 @@ else: version (Windows): -import core.sys.windows.windows; +import core.sys.windows.winbase, core.sys.windows.winnls; import std.conv; import std.string; import std.windows.syserror; import std.internal.cstring; -const(char)* toMBSz(in char[] s, uint codePage = 0) +const(char)* toMBSz(scope const(char)[] s, uint codePage = 0) { // Only need to do this if any chars have the high bit set foreach (char c; s) @@ -88,7 +88,7 @@ const(char)* toMBSz(in char[] s, uint codePage = 0) return std.string.toStringz(s); } -string fromMBSz(immutable(char)* s, int codePage = 0) +string fromMBSz(return scope immutable(char)* s, int codePage = 0) { const(char)* c; diff --git a/libphobos/src/std/windows/registry.d b/libphobos/src/std/windows/registry.d index 7293d2d..cdf37c1 100644 --- a/libphobos/src/std/windows/registry.d +++ b/libphobos/src/std/windows/registry.d @@ -12,7 +12,7 @@ Created 15th March 2003, Updated 25th April 2004, - Source: $(PHOBOSSRC std/windows/_registry.d) + Source: $(PHOBOSSRC std/windows/registry.d) */ /* ///////////////////////////////////////////////////////////////////////////// * @@ -38,7 +38,7 @@ module std.windows.registry; version (Windows): -import core.sys.windows.windows; +import core.sys.windows.winbase, core.sys.windows.windef, core.sys.windows.winreg; import std.array; import std.conv; import std.exception; @@ -81,7 +81,7 @@ class Win32Exception : WindowsException @property int error() { return super.code; } } -version (unittest) import std.string : startsWith, endsWith; +version (StdUnittest) import std.string : startsWith, endsWith; @safe unittest { @@ -274,7 +274,7 @@ in { assert(hkey !is null); } -body +do { /* No need to attempt to close any of the standard hive keys. * Although it's documented that calling RegCloseKey() on any of @@ -309,7 +309,7 @@ in { assert(hkey !is null); } -body +do { immutable res = RegFlushKey(hkey); enforceSucc(res, "Key cannot be flushed"); @@ -322,7 +322,7 @@ in assert(hkey !is null); assert(subKey !is null); } -body +do { HKEY hkeyResult; enforceSucc(RegCreateKeyExW( @@ -340,7 +340,7 @@ in assert(hkey !is null); assert(subKey !is null); } -body +do { LONG res; if (haveWoW64Job(samDesired)) @@ -361,7 +361,7 @@ in assert(hkey !is null); assert(valueName !is null); } -body +do { enforceSucc(RegDeleteValueW(hkey, valueName.tempCStringW()), "Value cannot be deleted: \"" ~ valueName ~ "\""); @@ -372,7 +372,7 @@ in { assert(hkey !is null); } -body +do { /* Can't duplicate standard keys, but don't need to, so can just return */ if (cast(uint) hkey & 0x80000000) @@ -422,7 +422,7 @@ out(res) { assert(res != ERROR_MORE_DATA); } -body +do { // 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 @@ -447,7 +447,7 @@ in { assert(hkey !is null); } -body +do { for (;;) { @@ -467,7 +467,7 @@ in { assert(hkey !is null); } -body +do { return RegQueryInfoKeyW(hkey, null, null, null, &cSubKeys, &cchSubKeyMaxLen, null, null, null, null, null, null); @@ -478,7 +478,7 @@ in { assert(hkey !is null); } -body +do { return RegQueryInfoKeyW(hkey, null, null, null, null, null, null, &cValues, &cchValueMaxLen, null, null, null); @@ -489,7 +489,7 @@ in { assert(hkey !is null); } -body +do { REG_VALUE_TYPE type; enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, null, null), @@ -504,7 +504,7 @@ in assert(hkey !is null); assert(subKey !is null); } -body +do { HKEY hkeyResult; enforceSucc(RegOpenKeyExW(hkey, subKey.tempCStringW(), 0, compatibleRegsam(samDesired), &hkeyResult), @@ -518,13 +518,13 @@ in { assert(hkey !is null); } -body +do { import core.bitop : bswap; REG_VALUE_TYPE type; - // See bugzilla 961 on this + // See https://issues.dlang.org/show_bug.cgi?id=961 on this union U { uint dw; @@ -589,7 +589,7 @@ in { assert(hkey !is null); } -body +do { REG_VALUE_TYPE type; @@ -631,7 +631,7 @@ in { assert(hkey !is null); } -body +do { import core.bitop : bswap; @@ -669,7 +669,7 @@ in { assert(hkey !is null); } -body +do { REG_VALUE_TYPE type; @@ -694,7 +694,7 @@ in { assert(hkey !is null); } -body +do { REG_VALUE_TYPE type; @@ -729,7 +729,7 @@ in { assert(hkey !is null); } -body +do { enforceSucc(RegSetValueExW(hkey, subKey.tempCStringW(), 0, type, cast(BYTE*) lpData, cbData), "Value cannot be set: \"" ~ subKey ~ "\""); @@ -801,7 +801,7 @@ private: { assert(hkey !is null); } - body + do { m_hkey = hkey; m_name = name; @@ -886,11 +886,11 @@ public: Returns the named sub-key of this key. Params: - name = The name of the subkey to create. May not be $(D null). + name = The name of the subkey to create. May not be `null`. Returns: The created key. Throws: - $(D RegistryException) is thrown if the key cannot be created. + `RegistryException` is thrown if the key cannot be created. */ Key createKey(string name, REGSAM access = REGSAM.KEY_ALL_ACCESS) { @@ -926,12 +926,12 @@ public: 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. + access = The desired access; one of the `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. + This function never returns `null`. If a key corresponding to + the requested name is not found, `RegistryException` is thrown. */ Key getKey(string name, REGSAM access = REGSAM.KEY_READ) { @@ -965,7 +965,7 @@ public: Deletes the named key. Params: - name = The name of the key to delete. May not be $(D null). + name = The name of the key to delete. May not be `null`. */ void deleteKey(string name, REGSAM access = cast(REGSAM) 0) { @@ -976,11 +976,11 @@ public: /** Returns the named value. - If $(D name) is the empty string, then the default value is returned. + If `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. + This function never returns `null`. If a value corresponding + to the requested name is not found, `RegistryException` is thrown. */ Value getValue(string name) { @@ -996,7 +996,7 @@ public: value = The 32-bit unsigned value to set. Throws: If a value corresponding to the requested name is not found, - $(D RegistryException) is thrown. + `RegistryException` is thrown. */ void setValue(string name, uint value) { @@ -1011,10 +1011,10 @@ public: 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). + endian = Can be `Endian.BigEndian` or `Endian.LittleEndian`. Throws: If a value corresponding to the requested name is not found, - $(D RegistryException) is thrown. + `RegistryException` is thrown. */ void setValue(string name, uint value, Endian endian) { @@ -1035,7 +1035,7 @@ public: value = The 64-bit unsigned value to set. Throws: If a value corresponding to the requested name is not found, - $(D RegistryException) is thrown. + `RegistryException` is thrown. */ void setValue(string name, ulong value) { @@ -1051,7 +1051,7 @@ public: value = The string value to set. Throws: If a value corresponding to the requested name is not found, - $(D RegistryException) is thrown. + `RegistryException` is thrown. */ void setValue(string name, string value) { @@ -1065,11 +1065,11 @@ public: 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 + asEXPAND_SZ = If `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. + `RegistryException` is thrown. */ void setValue(string name, string value, bool asEXPAND_SZ) { @@ -1092,7 +1092,7 @@ public: value = The multiple-strings value to set. Throws: If a value corresponding to the requested name is not found, - $(D RegistryException) is thrown. + `RegistryException` is thrown. */ void setValue(string name, string[] value) { @@ -1116,7 +1116,7 @@ public: value = The binary value to set. Throws: If a value corresponding to the requested name is not found, - $(D RegistryException) is thrown. + `RegistryException` is thrown. */ void setValue(string name, byte[] value) { @@ -1127,10 +1127,10 @@ public: Deletes the named value. Params: - name = The name of the value to delete. May not be $(D null). + name = The name of the value to delete. May not be `null`. Throws: If a value of the requested name is not found, - $(D RegistryException) is thrown. + `RegistryException` is thrown. */ void deleteValue(string name) { @@ -1168,7 +1168,7 @@ private: { assert(null !is key); } - body + do { m_key = key; m_type = type; @@ -1197,12 +1197,12 @@ public: /** Obtains the current value of the value as a string. If the value's type is REG_EXPAND_SZ the returned value is not - expanded; $(D value_EXPAND_SZ) should be called + expanded; `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, + `RegistryException` if the type of the value is not REG_SZ, REG_EXPAND_SZ, REG_DWORD, or REG_QWORD. */ @property string value_SZ() const @@ -1217,7 +1217,7 @@ public: /** 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). + This function works with the same value-types as `value_SZ`. Returns: The contents of the value. @@ -1245,7 +1245,7 @@ public: Returns: The contents of the value. Throws: - $(D RegistryException) if the type of the value is not REG_MULTI_SZ. + `RegistryException` if the type of the value is not REG_MULTI_SZ. */ @property string[] value_MULTI_SZ() const { @@ -1263,7 +1263,7 @@ public: Returns: The contents of the value. Throws: - $(D RegistryException) is thrown for all types other than + `RegistryException` is thrown for all types other than REG_DWORD, REG_DWORD_LITTLE_ENDIAN and REG_DWORD_BIG_ENDIAN. */ @property uint value_DWORD() const @@ -1282,7 +1282,7 @@ public: Returns: The contents of the value. Throws: - $(D RegistryException) if the type of the value is not REG_QWORD. + `RegistryException` if the type of the value is not REG_QWORD. */ @property ulong value_QWORD() const { @@ -1299,7 +1299,7 @@ public: Returns: The contents of the value. Throws: - $(D RegistryException) if the type of the value is not REG_BINARY. + `RegistryException` if the type of the value is not REG_BINARY. */ @property byte[] value_BINARY() const { @@ -1322,7 +1322,7 @@ private: final class Registry { private: - @disable this() { } + @disable this(); public: /// Returns the root key for the HKEY_CLASSES_ROOT hive @@ -1405,7 +1405,7 @@ public: Returns: The name of the key corresponding to the given index. Throws: - $(D RegistryException) if no corresponding key is retrieved. + `RegistryException` if no corresponding key is retrieved. */ string opIndex(size_t index) { @@ -1482,7 +1482,7 @@ public: Returns: The key corresponding to the given index. Throws: - $(D RegistryException) if no corresponding key is retrieved. + `RegistryException` if no corresponding key is retrieved. */ Key getKey(size_t index) { @@ -1502,7 +1502,7 @@ public: Returns: The key corresponding to the given index. Throws: - $(D RegistryException) if no corresponding key is retrieved. + `RegistryException` if no corresponding key is retrieved. */ Key opIndex(size_t index) { @@ -1591,7 +1591,7 @@ public: Returns: The name of the value corresponding to the given index. Throws: - $(D RegistryException) if no corresponding value is retrieved. + `RegistryException` if no corresponding value is retrieved. */ string getValueName(size_t index) { @@ -1611,7 +1611,7 @@ public: Returns: The name of the value corresponding to the given index. Throws: - $(D RegistryException) if no corresponding value is retrieved. + `RegistryException` if no corresponding value is retrieved. */ string opIndex(size_t index) { @@ -1678,14 +1678,14 @@ public: } /** - The value at the given $(D index). + The value at the given `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 + `RegistryException` if no corresponding value is retrieved */ Value getValue(size_t index) { @@ -1698,14 +1698,14 @@ public: } /** - The value at the given $(D index). + The value at the given `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. + `RegistryException` if no corresponding value is retrieved. */ Value opIndex(size_t index) { diff --git a/libphobos/src/std/windows/syserror.d b/libphobos/src/std/windows/syserror.d index 7386360..94f8ee5 100644 --- a/libphobos/src/std/windows/syserror.d +++ b/libphobos/src/std/windows/syserror.d @@ -3,12 +3,12 @@ /** * Convert Win32 error code to string. * - * Copyright: Copyright Digital Mars 2006 - 2013. + * Copyright: Copyright The D Language Foundation" 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. +/* Copyright The D Language Foundation" 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) @@ -26,7 +26,7 @@ version (StdDdoc) /** 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. + `GetLastError`), as a D string. */ string sysErrorString( DWORD errCode, @@ -37,20 +37,20 @@ version (StdDdoc) /********************* Thrown if errors that set $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx, - $(D GetLastError)) occur. + `GetLastError`) occur. */ class WindowsException : Exception { private alias DWORD = int; - final @property DWORD code(); /// $(D GetLastError)'s return value. + final @property DWORD code(); /// `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, + If `!!value` is true, `value` is returned. Otherwise, $(D new WindowsException(GetLastError(), msg)) is thrown. - $(D WindowsException) assumes that the last operation set - $(D GetLastError()) appropriately. + `WindowsException` assumes that the last operation set + `GetLastError()` appropriately. Example: -------------------- @@ -65,10 +65,10 @@ else: version (Windows): -import core.sys.windows.windows; +import core.sys.windows.winbase, core.sys.windows.winnt; import std.array : appender; import std.conv : to; -import std.format : formattedWrite; +import std.format.write : formattedWrite; import std.windows.charset; string sysErrorString( @@ -117,9 +117,9 @@ bool putSysError(Writer)(DWORD code, Writer w, /*WORD*/int langId = 0) class WindowsException : Exception { - import core.sys.windows.windows : DWORD; + import core.sys.windows.windef : DWORD; - final @property DWORD code() { return _code; } /// $(D GetLastError)'s return value. + final @property DWORD code() { return _code; } /// `GetLastError`'s return value. private DWORD _code; this(DWORD code, string str=null, string file = null, size_t line = 0) @trusted diff --git a/libphobos/src/std/xml.d b/libphobos/src/std/xml.d index 13241f5..37fab6d 100644 --- a/libphobos/src/std/xml.d +++ b/libphobos/src/std/xml.d @@ -2,9 +2,11 @@ /** $(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.) + current standards. It will be removed from Phobos in 2.101.0. + If you still need it, go to $(LINK https://github.com/DigitalMars/undeaD)) + */ +/* Classes and functions for creating and parsing XML The basic architecture of this module is that there are standalone functions, @@ -115,7 +117,7 @@ void main() 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) +Source: $(PHOBOSSRC std/xml.d) */ /* Copyright Janice Caron 2008 - 2009. @@ -123,11 +125,12 @@ Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ +deprecated("Will be removed from Phobos in 2.101.0. If you still need it, go to https://github.com/DigitalMars/undeaD") module std.xml; enum cdata = " b" * -------------- */ -string decode(string s, DecodeMode mode=DecodeMode.LOOSE) @safe pure +string decode(return scope string s, DecodeMode mode=DecodeMode.LOOSE) @safe pure { import std.algorithm.searching : startsWith; @@ -521,7 +524,7 @@ string decode(string s, DecodeMode mode=DecodeMode.LOOSE) @safe pure assertNot("G;"); } -/** +/* * Class representing an XML document. * * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) @@ -529,18 +532,18 @@ string decode(string s, DecodeMode mode=DecodeMode.LOOSE) @safe pure */ class Document : Element { - /** + /* * Contains all text which occurs before the root element. * Defaults to <?xml version="1.0"?> */ string prolog = ""; - /** + /* * Contains all text which occurs after the root element. * Defaults to the empty string */ string epilog; - /** + /* * Constructs a Document by parsing XML text. * * This function creates a complete DOM (Document Object Model) tree. @@ -556,7 +559,7 @@ class Document : Element { assert(s.length != 0); } - body + do { auto xml = new DocumentParser(s); string tagString = xml.tag.tagString; @@ -567,7 +570,7 @@ class Document : Element epilog = *xml.s; } - /** + /* * Constructs a Document from a Tag. * * Params: @@ -580,7 +583,7 @@ class Document : Element const { - /** + /* * Compares two Documents for equality * * Example: @@ -597,7 +600,7 @@ class Document : Element && epilog == doc.epilog; } - /** + /* * Compares two Documents * * You should rarely need to call this function. It exists so that @@ -621,7 +624,7 @@ class Document : Element return 0; } - /** + /* * Returns the hash of a Document * * You should rarely need to call this function. It exists so that @@ -632,7 +635,7 @@ class Document : Element return hash(prolog, hash(epilog, (cast() this).Element.toHash())); } - /** + /* * Returns the string representation of a Document. (That is, the * complete XML of a document). */ @@ -661,22 +664,22 @@ class Document : Element 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 - - /** + 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. * @@ -693,10 +696,10 @@ class Element : Item this(string name, string interior=null) @safe pure { this(new Tag(name)); - if (interior.length != 0) opCatAssign(new Text(interior)); + if (interior.length != 0) opOpAssign!("~")(new Text(interior)); } - /** + /* * Constructs an Element from a Tag. * * Params: @@ -710,7 +713,7 @@ class Element : Item tag.tagString = tag_.tagString; } - /** + /* * Append a text item to the interior of this element * * Params: @@ -722,13 +725,14 @@ class Element : Item * element ~= new Text("hello"); * -------------- */ - void opCatAssign(Text item) @safe pure + void opOpAssign(string op)(Text item) @safe pure + if (op == "~") { texts ~= item; appendItem(item); } - /** + /* * Append a CData item to the interior of this element * * Params: @@ -740,13 +744,14 @@ class Element : Item * element ~= new CData("hello"); * -------------- */ - void opCatAssign(CData item) @safe pure + void opOpAssign(string op)(CData item) @safe pure + if (op == "~") { cdatas ~= item; appendItem(item); } - /** + /* * Append a comment to the interior of this element * * Params: @@ -758,13 +763,14 @@ class Element : Item * element ~= new Comment("hello"); * -------------- */ - void opCatAssign(Comment item) @safe pure + void opOpAssign(string op)(Comment item) @safe pure + if (op == "~") { comments ~= item; appendItem(item); } - /** + /* * Append a processing instruction to the interior of this element * * Params: @@ -776,13 +782,14 @@ class Element : Item * element ~= new ProcessingInstruction("hello"); * -------------- */ - void opCatAssign(ProcessingInstruction item) @safe pure + void opOpAssign(string op)(ProcessingInstruction item) @safe pure + if (op == "~") { pis ~= item; appendItem(item); } - /** + /* * Append a complete element to the interior of this element * * Params: @@ -796,7 +803,8 @@ class Element : Item * // appends element representing
* -------------- */ - void opCatAssign(Element item) @safe pure + void opOpAssign(string op)(Element item) @safe pure + if (op == "~") { elements ~= item; appendItem(item); @@ -811,22 +819,22 @@ class Element : Item 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.onText = (string s) { opOpAssign!("~")(new Text(s)); }; + xml.onCData = (string s) { opOpAssign!("~")(new CData(s)); }; + xml.onComment = (string s) { opOpAssign!("~")(new Comment(s)); }; + xml.onPI = (string s) { opOpAssign!("~")(new ProcessingInstruction(s)); }; xml.onStartTag[null] = (ElementParser xml) { auto e = new Element(xml.tag); e.parse(xml); - opCatAssign(e); + opOpAssign!("~")(e); }; xml.parse(); } - /** + /* * Compares two Elements for equality * * Example: @@ -847,7 +855,7 @@ class Element : Item return true; } - /** + /* * Compares two Elements * * You should rarely need to call this function. It exists so that Elements @@ -872,7 +880,7 @@ class Element : Item } } - /** + /* * Returns the hash of an Element * * You should rarely need to call this function. It exists so that Elements @@ -887,7 +895,7 @@ class Element : Item const { - /** + /* * Returns the decoded interior of an element. * * The element is assumed to contain text only. So, for @@ -911,7 +919,7 @@ class Element : Item return buffer; } - /** + /* * Returns an indented string representation of this item * * Params: @@ -947,7 +955,7 @@ class Element : Item return a; } - /** + /* * Returns the string representation of an Element * * Example: @@ -970,7 +978,7 @@ class Element : Item } } -/** +/* * Tag types. * * $(DDOC_ENUM_MEMBERS START) Used for start tags @@ -980,7 +988,7 @@ class Element : Item */ enum TagType { START, END, EMPTY } -/** +/* * Class representing an XML tag. * * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) @@ -994,9 +1002,9 @@ enum TagType { START, END, EMPTY } */ class Tag { - TagType type = TagType.START; /// Type of tag - string name; /// Tag name - string[string] attr; /// Associative array of attributes + TagType type = TagType.START; // Type of tag + string name; // Tag name + string[string] attr; // Associative array of attributes private string tagString; invariant() @@ -1017,11 +1025,11 @@ class Tag s = k; try { checkName(s,t); } catch (Err e) - { assert(false,"Invalid atrribute name:" ~ e.toString()); } + { assert(false,"Invalid attribute name:" ~ e.toString()); } } } - /** + /* * Constructs an instance of Tag with a specified name and type * * The constructor does not initialize the attributes. To initialize the @@ -1111,7 +1119,7 @@ class Tag const { - /** + /* * Compares two Tags for equality * * You should rarely need to call this function. It exists so that Tags @@ -1133,7 +1141,7 @@ class Tag true ))); } - /** + /* * Compares two Tags * * Example: @@ -1153,7 +1161,7 @@ class Tag 0 ))); } - /** + /* * Returns the hash of a Tag * * You should rarely need to call this function. It exists so that Tags @@ -1161,10 +1169,10 @@ class Tag */ override size_t toHash() { - return typeid(name).getHash(&name); + return .hashOf(name); } - /** + /* * Returns the string representation of a Tag * * Example: @@ -1198,7 +1206,7 @@ class Tag string toEmptyString() @safe { return toNonEndString() ~ " />"; } } - /** + /* * Returns true if the Tag is a start tag * * Example: @@ -1208,7 +1216,7 @@ class Tag */ @property bool isStart() @safe @nogc pure nothrow { return type == TagType.START; } - /** + /* * Returns true if the Tag is an end tag * * Example: @@ -1218,7 +1226,7 @@ class Tag */ @property bool isEnd() @safe @nogc pure nothrow { return type == TagType.END; } - /** + /* * Returns true if the Tag is an empty tag * * Example: @@ -1230,14 +1238,14 @@ class Tag } } -/** +/* * Class representing a comment */ class Comment : Item { private string content; - /** + /* * Construct a comment * * Params: @@ -1261,7 +1269,7 @@ class Comment : Item this.content = content; } - /** + /* * Compares two comments for equality * * Example: @@ -1277,7 +1285,7 @@ class 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 @@ -1297,7 +1305,7 @@ class Comment : Item ? (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 @@ -1305,15 +1313,16 @@ class Comment : Item */ 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 + override @property @safe @nogc pure nothrow scope bool isEmptyXML() const { return false; } // Returns false always } -@safe unittest // issue 16241 +// https://issues.dlang.org/show_bug.cgi?id=16241 +@safe unittest { import std.exception : assertThrown; auto c = new Comment("=="); @@ -1321,14 +1330,14 @@ class Comment : Item assertThrown!CommentException(new Comment("--")); } -/** +/* * Class representing a Character Data section */ class CData : Item { private string content; - /** + /* * Construct a character data section * * Params: @@ -1349,7 +1358,7 @@ class CData : Item this.content = content; } - /** + /* * Compares two CDatas for equality * * Example: @@ -1365,7 +1374,7 @@ class 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 @@ -1385,7 +1394,7 @@ class CData : Item ? (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 @@ -1393,22 +1402,22 @@ class CData : Item */ 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 + 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: @@ -1426,7 +1435,7 @@ class Text : Item this.content = encode(content); } - /** + /* * Compares two text sections for equality * * Example: @@ -1442,7 +1451,7 @@ class 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 @@ -1462,7 +1471,7 @@ class Text : Item && (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 @@ -1470,25 +1479,25 @@ class Text : Item */ 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: @@ -1509,7 +1518,7 @@ class XMLInstruction : Item this.content = content; } - /** + /* * Compares two XML instructions for equality * * Example: @@ -1525,7 +1534,7 @@ class 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 @@ -1545,7 +1554,7 @@ class XMLInstruction : Item && (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 @@ -1553,22 +1562,22 @@ class XMLInstruction : Item */ 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 + 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: @@ -1589,7 +1598,7 @@ class ProcessingInstruction : Item this.content = content; } - /** + /* * Compares two processing instructions for equality * * Example: @@ -1605,7 +1614,7 @@ class 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 @@ -1625,7 +1634,7 @@ class ProcessingInstruction : Item && (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 @@ -1633,32 +1642,32 @@ class ProcessingInstruction : Item */ 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 + 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 + // 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 + // Compares with another Item of same type abstract override int opCmp(scope const Object o) @safe const; - /// Returns the hash of this item + // Returns the hash of this item abstract override size_t toHash() @safe scope const; - /// Returns a string representation of this item + // Returns a string representation of this item abstract override string toString() @safe scope const; - /** + /* * Returns an indented string representation of this item * * Params: @@ -1671,11 +1680,11 @@ abstract class Item return s.length == 0 ? [] : [ s ]; } - /// Returns true if the item represents empty XML text + // 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 @@ -1693,7 +1702,7 @@ class DocumentParser : ElementParser { string xmlText; - /** + /* * Constructs a DocumentParser. * * The input to this function MUST be valid XML. @@ -1718,7 +1727,7 @@ class DocumentParser : ElementParser assert(false, "\n" ~ e.toString()); } } - body + do { xmlText = xmlText_; s = &xmlText; @@ -1735,7 +1744,7 @@ class DocumentParser : ElementParser 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) @@ -1782,13 +1791,13 @@ class ElementParser } } - /** + /* * 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 @@ -1824,7 +1833,7 @@ class ElementParser */ 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 @@ -1860,7 +1869,7 @@ class ElementParser elementStart = *s; } - /** + /* * Register a handler which will be called whenever text is encountered. * * Example: @@ -1880,7 +1889,7 @@ class ElementParser */ @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 @@ -1906,7 +1915,7 @@ class ElementParser */ @safe @nogc pure nothrow void onTextRaw(Handler handler) { rawTextHandler = handler; } - /** + /* * Register a handler which will be called whenever a character data * segment is encountered. * @@ -1927,7 +1936,7 @@ class ElementParser */ @property @safe @nogc pure nothrow void onCData(Handler handler) { cdataHandler = handler; } - /** + /* * Register a handler which will be called whenever a comment is * encountered. * @@ -1948,7 +1957,7 @@ class ElementParser */ @property @safe @nogc pure nothrow void onComment(Handler handler) { commentHandler = handler; } - /** + /* * Register a handler which will be called whenever a processing * instruction is encountered. * @@ -1969,7 +1978,7 @@ class ElementParser */ @property @safe @nogc pure nothrow void onPI(Handler handler) { piHandler = handler; } - /** + /* * Register a handler which will be called whenever an XML instruction is * encountered. * @@ -1992,7 +2001,7 @@ class ElementParser */ @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 @@ -2091,8 +2100,8 @@ class ElementParser { Tag startTag = new Tag(tag_.name); - // FIX by hed010gy, for bug 2979 - // http://d.puremagic.com/issues/show_bug.cgi?id=2979 + // FIX by hed010gy + // https://issues.dlang.org/show_bug.cgi?id=2979 if (tag_.attr.length > 0) foreach (tn,tv; tag_.attr) startTag.attr[tn]=tv; // END FIX @@ -2130,7 +2139,7 @@ class ElementParser } } - /** + /* * Returns that part of the element which has already been parsed */ override string toString() const @nogc @safe pure nothrow @@ -2716,7 +2725,7 @@ private } } -/** +/* * Check an entire XML document for well-formedness * * Params: @@ -2858,57 +2867,57 @@ EOS"; assert(doc.toString() == s); } -/** The base class for exceptions thrown by this module */ +/* 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 +// Thrown during Comment constructor class CommentException : XMLException { private this(string msg) @safe pure { super(msg); } } -/// Thrown during CData constructor +// Thrown during CData constructor class CDataException : XMLException { private this(string msg) @safe pure { super(msg); } } -/// Thrown during XMLInstruction constructor +// Thrown during XMLInstruction constructor class XIException : XMLException { private this(string msg) @safe pure { super(msg); } } -/// Thrown during ProcessingInstruction constructor +// Thrown during ProcessingInstruction constructor class PIException : XMLException { private this(string msg) @safe pure { super(msg); } } -/// Thrown during Text constructor +// Thrown during Text constructor class TextException : XMLException { private this(string msg) @safe pure { super(msg); } } -/// Thrown during decode() +// Thrown during decode() class DecodeException : XMLException { private this(string msg) @safe pure { super(msg); } } -/// Thrown if comparing with wrong type +// Thrown if comparing with wrong type class InvalidTypeException : XMLException { private this(string msg) @safe pure { super(msg); } } -/// Thrown when parsing for Tags +// 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 + 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 + 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 { @@ -2950,7 +2959,7 @@ private alias Err = CheckException; private { - inout(T) toType(T)(inout Object o) + inout(T) toType(T)(inout return scope Object o) { T t = cast(T)(o); if (t is null) @@ -2993,10 +3002,7 @@ private return ch; } - size_t hash(string s,size_t h=0) @trusted nothrow - { - return typeid(s).getHash(&s) + h; - } + alias hash = .hashOf; // Definitions from the XML specification immutable CharTable=[0x9,0x9,0xA,0xA,0xD,0xD,0x20,0xD7FF,0xE000,0xFFFD, diff --git a/libphobos/src/std/zip.d b/libphobos/src/std/zip.d index 9e55d19..4d7422b 100644 --- a/libphobos/src/std/zip.d +++ b/libphobos/src/std/zip.d @@ -1,114 +1,169 @@ // 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; +Read and write data in the +$(LINK2 https://en.wikipedia.org/wiki/Zip_%28file_format%29, zip archive) +format. + +Standards: + +The current implementation mostly conforms to +$(LINK2 https://www.iso.org/standard/60101.html, ISO/IEC 21320-1:2015), +which means, +$(UL +$(LI that files can only be stored uncompressed or using the deflate mechanism,) +$(LI that encryption features are not used,) +$(LI that digital signature features are not used,) +$(LI that patched data features are not used, and) +$(LI that archives may not span multiple volumes.) +) + +Additionally, archives are checked for malware attacks and rejected if detected. +This includes +$(UL +$(LI $(LINK2 https://news.ycombinator.com/item?id=20352439, zip bombs) which + generate gigantic amounts of unpacked data) +$(LI zip archives that contain overlapping records) +$(LI chameleon zip archives which generate different unpacked data, depending + on the implementation of the unpack algorithm) +) + +The current implementation makes use of the zlib compression library. + +Usage: + +There are two main ways of usage: Extracting files from a zip archive +and storing files into a zip archive. These can be mixed though (e.g. +read an archive, remove some files, add others and write the new +archive). + +Examples: + +Example for reading an existing zip archive: +--- +import std.stdio : writeln, writefln; +import std.file : read; +import 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 + writefln("%-10s %-8s Name", "Length", "CRC-32"); 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. +Example for writing files into a zip archive: +--- import std.file : write; import std.string : representation; +import std.zip; 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 ArchiveMembers for each file. + ArchiveMember file1 = new ArchiveMember(); + file1.name = "test1.txt"; + file1.expandedData("Test data.\n".dup.representation); + file1.compressionMethod = CompressionMethod.none; // don't compress + + ArchiveMember file2 = new ArchiveMember(); + file2.name = "test2.txt"; + file2.expandedData("More test data.\n".dup.representation); + file2.compressionMethod = CompressionMethod.deflate; // compress + // Create an archive and add the member. ZipArchive zip = new ZipArchive(); - zip.addMember(am); + + // add ArchiveMembers + zip.addMember(file1); + zip.addMember(file2); + // Build the archive void[] compressed_data = zip.build(); + // Write to a file write("test.zip", compressed_data); } - * --- - * - * Copyright: Copyright Digital Mars 2000 - 2009. +--- + + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/zip.d) */ -/* Copyright Digital Mars 2000 - 2009. +/* Copyright The D Language Foundation 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; +import std.exception : enforce; + +// Non-Android/Apple ARM POSIX-only, because we can't rely on the unzip +// command being available on Android, Apple ARM or Windows +version (Android) {} +else version (iOS) {} +else version (TVOS) {} +else version (WatchOS) {} +else version (Posix) + version = HasUnzip; + //debug=print; -/** Thrown on error. - */ +/// Thrown on error. class ZipException : Exception { - this(string msg) @safe - { - super("ZipException: " ~ msg); - } + import std.exception : basicExceptionCtors; + /// + mixin basicExceptionCtors; } -/** - * Compression method used by ArchiveMember - */ +/// Compression method used by `ArchiveMember`. enum CompressionMethod : ushort { - none = 0, /// No compression, just archiving - deflate = 8 /// Deflate algorithm. Use zlib library to compress + none = 0, /// No compression, just archiving. + deflate = 8 /// Deflate algorithm. Use zlib library to compress. } -/** - * A member of the ZipArchive. - */ +/// A single file or directory inside the archive. 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. + * The 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. + /** + * The content of the extra data field for this member. See + * $(LINK2 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT, + * original documentation) + * for a description of the general format of this data. May contain + * undocumented 3rd-party data. + */ + ubyte[] extra; + + string comment; /// Comment associated with this member. private ubyte[] _compressedData; private ubyte[] _expandedData; @@ -119,32 +174,80 @@ final class ArchiveMember 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 + /** + * Contains some information on how to extract this archive. See + * $(LINK2 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT, + * original documentation) + * for details. + */ + ushort flags; + + /** + * Internal attributes. Bit 1 is set, if the member is apparently in binary format + * and bit 2 is set, if each record is preceded by the length of the record. + */ + ushort internalAttributes; + + /** + * The zip file format version needed to extract this member. + * + * Returns: Format version needed to extract this member. + */ + @property @safe pure nothrow @nogc ushort extractVersion() const { return _extractVersion; } - @property ushort extractVersion() { return _extractVersion; } /// Read Only - @property uint crc32() { return _crc32; } /// Read Only: cyclic redundancy check (CRC) value + /** + * Cyclic redundancy check (CRC) value. + * + * Returns: CRC32 value. + */ + @property @safe pure nothrow @nogc uint crc32() const { return _crc32; } + + /** + * Size of data of member in compressed form. + * + * Returns: Size of the compressed archive. + */ + @property @safe pure nothrow @nogc uint compressedSize() const { return _compressedSize; } - /// Read Only: size of data of member in compressed form. - @property uint compressedSize() { return _compressedSize; } + /** + * Size of data of member in uncompressed form. + * + * Returns: Size of uncompressed archive. + */ + @property @safe pure nothrow @nogc uint expandedSize() const { return _expandedSize; } - /// 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. + /** + * Should be 0. + * + * Returns: The number of the disk where this member can be found. + */ + deprecated("Multidisk not supported; will be removed in 2.099.0") + @property @safe pure nothrow @nogc ushort diskNumber() const { return 0; } - /// Read Only: data of member in compressed form. - @property ubyte[] compressedData() { return _compressedData; } + /** + * Data of member in compressed form. + * + * Returns: The file data in compressed form. + */ + @property @safe pure nothrow @nogc ubyte[] compressedData() { return _compressedData; } - /// Read data of member in uncompressed form. - @property ubyte[] expandedData() { return _expandedData; } + /** + * Get or set data of member in uncompressed form. When an existing archive is + * read `ZipArchive.expand` needs to be called before this can be accessed. + * + * Params: + * ed = Expanded Data. + * + * Returns: The file data. + */ + @property @safe pure nothrow @nogc ubyte[] expandedData() { return _expandedData; } - /// Write data of member in uncompressed form. + /// ditto @property @safe void expandedData(ubyte[] ed) { _expandedData = ed; @@ -156,8 +259,14 @@ final class ArchiveMember } /** - * Set the OS specific file attributes, as obtained by - * $(REF getAttributes, std,file) or $(REF DirEntry.attributes, std,file), for this archive member. + * Get or set the OS specific file attributes for this archive member. + * + * Params: + * attr = Attributes as obtained by $(REF getAttributes, std,file) or + * $(REF DirEntry.attributes, std,file). + * + * Returns: The file attributes or 0 if the file attributes were + * encoded for an incompatible OS (Windows vs. POSIX). */ @property @safe void fileAttributes(uint attr) { @@ -186,14 +295,8 @@ final class ArchiveMember 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 + /// ditto + @property @nogc nothrow uint fileAttributes() const { version (Posix) { @@ -213,51 +316,66 @@ final class ArchiveMember } } - /// Set the last modification time for this member. - @property void time(SysTime time) + /** + * Get or set the last modification time for this member. + * + * Params: + * time = Time to set (will be saved as DosFileTime, which is less accurate). + * + * Returns: + * The last modification time in DosFileFormat. + */ + @property DosFileTime time() const @safe pure nothrow @nogc { - _time = SysTimeToDosFileTime(time); + return _time; } /// ditto - @property void time(DosFileTime time) + @property void time(SysTime time) { - _time = time; + _time = SysTimeToDosFileTime(time); } - /// Get the last modification time for this member. - @property DosFileTime time() const + /// ditto + @property void time(DosFileTime time) @safe pure nothrow @nogc { - return _time; + _time = time; } /** - * Read compression method used for this member + * Get or set compression method used for this member. + * + * Params: + * cm = Compression method. + * + * Returns: Compression method. + * * See_Also: - * CompressionMethod + * $(LREF CompressionMethod) **/ - @property @safe CompressionMethod compressionMethod() { return _compressionMethod; } + @property @safe @nogc pure nothrow CompressionMethod compressionMethod() const { return _compressionMethod; } - /** - * Write compression method used for this member - * See_Also: - * CompressionMethod - **/ - @property void compressionMethod(CompressionMethod cm) + /// ditto + @property @safe pure void compressionMethod(CompressionMethod cm) { if (cm == _compressionMethod) return; - if (_compressedSize > 0) - throw new ZipException("Can't change compression method for a compressed element"); + enforce!ZipException(_compressedSize == 0, "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; } + * The index of this archive member within the archive. Set this to a + * different value for reordering the members of an archive. + * + * Params: + * value = Index value to set. + * + * Returns: The index. + */ + @property uint index(uint value) @safe pure nothrow @nogc { return _index = value; } + @property uint index() const @safe pure nothrow @nogc { return _index; } /// ditto debug(print) { @@ -280,6 +398,21 @@ final class ArchiveMember } } +@safe pure unittest +{ + import std.exception : assertThrown, assertNotThrown; + + auto am = new ArchiveMember(); + + assertNotThrown(am.compressionMethod(CompressionMethod.deflate)); + assertNotThrown(am.compressionMethod(CompressionMethod.none)); + + am._compressedData = [0x65]; // not strictly necessary, but for consistency + am._compressedSize = 1; + + assertThrown!ZipException(am.compressionMethod(CompressionMethod.deflate)); +} + /** * Object representing the entire archive. * ZipArchives are collections of ArchiveMembers. @@ -291,42 +424,93 @@ final class ZipArchive 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: + // names are taken directly from the specification + // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + static immutable ubyte[] centralFileHeaderSignature = [ 0x50, 0x4b, 0x01, 0x02 ]; + static immutable ubyte[] localFileHeaderSignature = [ 0x50, 0x4b, 0x03, 0x04 ]; + static immutable ubyte[] endOfCentralDirSignature = [ 0x50, 0x4b, 0x05, 0x06 ]; + static immutable ubyte[] archiveExtraDataSignature = [ 0x50, 0x4b, 0x06, 0x08 ]; + static immutable ubyte[] digitalSignatureSignature = [ 0x50, 0x4b, 0x05, 0x05 ]; + static immutable ubyte[] zip64EndOfCentralDirSignature = [ 0x50, 0x4b, 0x06, 0x06 ]; + static immutable ubyte[] zip64EndOfCentralDirLocatorSignature = [ 0x50, 0x4b, 0x06, 0x07 ]; + + enum centralFileHeaderLength = 46; + enum localFileHeaderLength = 30; + enum endOfCentralDirLength = 22; + enum archiveExtraDataLength = 8; + enum digitalSignatureLength = 6; + enum zip64EndOfCentralDirLength = 56; + enum zip64EndOfCentralDirLocatorLength = 20; + enum dataDescriptorLength = 12; + +public: + string comment; /// 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; + + deprecated("Use digitalSignatureLength instead; will be removed in 2.098.0") static const int digiSignLength = 6; + deprecated("Use zip64EndOfCentralDirLocatorLength instead; will be removed in 2.098.0") static const int eocd64LocLength = 20; + deprecated("Use zip64EndOfCentralDirLength instead; will be removed in 2.098.0") static const int eocd64Length = 56; - /// Read Only: array representing the entire contents of the archive. - @property @safe ubyte[] data() { return _data; } + private Segment[] _segs; - /// Read Only: 0 since multi-disk zip archives are not supported. - @property @safe uint diskNumber() { return _diskNumber; } + /** + * Array representing the entire contents of the archive. + * + * Returns: Data of the entire contents of the archive. + */ + @property @safe @nogc pure nothrow ubyte[] data() { return _data; } - /// Read Only: 0 since multi-disk zip archives are not supported - @property @safe uint diskStartDir() { return _diskStartDir; } + /** + * 0 since multi-disk zip archives are not supported. + * + * Returns: Number of this disk. + */ + deprecated("Multidisk not supported; will be removed in 2.099.0") + @property @safe @nogc pure nothrow uint diskNumber() const { return 0; } - /// Read Only: number of ArchiveMembers in the directory. - @property @safe uint numEntries() { return _numEntries; } - @property @safe uint totalEntries() { return _totalEntries; } /// ditto + /** + * 0 since multi-disk zip archives are not supported. + * + * Returns: Number of the disk, where the central directory starts. + */ + deprecated("Multidisk not supported; will be removed in 2.099.0") + @property @safe @nogc pure nothrow uint diskStartDir() const { return 0; } - /// True when the archive is in Zip64 format. - @property @safe bool isZip64() { return _isZip64; } + /** + * Number of ArchiveMembers in the directory. + * + * Returns: The number of files in this archive. + */ + deprecated("Use totalEntries instead; will be removed in 2.099.0") + @property @safe @nogc pure nothrow uint numEntries() const { return cast(uint) _directory.length; } + @property @safe @nogc pure nothrow uint totalEntries() const { return cast(uint) _directory.length; } /// ditto - /// 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: + * True when the archive is in Zip64 format. Set this to true to force building a Zip64 archive. + * + * Params: + * value = True, when the archive is forced to be build in Zip64 format. + * + * Returns: True, when the archive is in Zip64 format. + */ + @property @safe @nogc pure nothrow bool isZip64() const { return _isZip64; } + + /// ditto + @property @safe @nogc pure nothrow void isZip64(bool value) { _isZip64 = value; } + + /** + * Associative 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); @@ -335,8 +519,10 @@ final class ZipArchive * writefln("member name is '%s'", am.name); * } * -------------------- + * + * Returns: Associative array with all archive members. */ - @property @safe ArchiveMember[string] directory() { return _directory; } + @property @safe @nogc pure nothrow ArchiveMember[string] directory() { return _directory; } private ArchiveMember[string] _directory; @@ -354,13 +540,21 @@ final class ZipArchive /* ============ Creating a new archive =================== */ - /** Constructor to use when creating a new archive. + /** + * Constructor to use when creating a new archive. */ - this() @safe + this() @safe @nogc pure nothrow { } - /** Add de to the archive. The file is compressed on the fly. + /** + * Add a member to the archive. The file is compressed on the fly. + * + * Params: + * de = Member to be added. + * + * Throws: ZipException when an unsupported compression method is used or when + * compression failed. */ @safe void addMember(ArchiveMember de) { @@ -390,61 +584,101 @@ final class ZipArchive import std.zlib : crc32; () @trusted { de._crc32 = crc32(0, cast(void[]) de._expandedData); }(); } - assert(de._compressedData.length == de._compressedSize); + assert(de._compressedData.length == de._compressedSize, "Archive member compressed failed."); + } + + @safe unittest + { + import std.exception : assertThrown; + + ArchiveMember am = new ArchiveMember(); + am.compressionMethod = cast(CompressionMethod) 3; + + ZipArchive zip = new ZipArchive(); + + assertThrown!ZipException(zip.addMember(am)); } - /** Delete de from the archive. + /** + * Delete member `de` from the archive. Uses the name of the member + * to detect which element to delete. + * + * Params: + * de = Member to be deleted. */ @safe void deleteMember(ArchiveMember de) { _directory.remove(de.name); } + // https://issues.dlang.org/show_bug.cgi?id=20398 + @safe unittest + { + import std.string : representation; + + ArchiveMember file1 = new ArchiveMember(); + file1.name = "test1.txt"; + file1.expandedData("Test data.\n".dup.representation); + + ZipArchive zip = new ZipArchive(); + + zip.addMember(file1); + assert(zip.totalEntries == 1); + + zip.deleteMember(file1); + assert(zip.totalEntries == 0); + } + /** - * Construct an archive out of the current members of the archive. + * Construct the entire contents of the current members of the archive. * - * Fills in the properties data[], diskNumber, diskStartDir, numEntries, - * totalEntries, and directory[]. + * Fills in the properties data[], totalEntries, and directory[]. * For each ArchiveMember, fills in properties crc32, compressedSize, * compressedData[]. * - * Returns: array representing the entire archive. + * Returns: Array representing the entire archive. + * + * Throws: ZipException when the archive could not be build. */ - void[] build() + void[] build() @safe pure { + import std.array : array, uninitializedArray; import std.algorithm.sorting : sort; + import std.string : representation; + uint i; uint directoryOffset; - if (comment.length > 0xFFFF) - throw new ZipException("archive comment longer than 65535"); + enforce!ZipException(comment.length <= 0xFFFF, "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; + auto directory = _directory.byValue.array.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 + + enforce!ZipException(to!ulong(archiveSize) + localFileHeaderLength + de.name.length + + de.extra.length + de.compressedSize + directorySize + + centralFileHeaderLength + de.name.length + de.extra.length + + de.comment.length + endOfCentralDirLength + comment.length + + zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength <= uint.max, + "zip files bigger than 4 GB are unsupported"); + + archiveSize += localFileHeaderLength + de.name.length + de.extra.length + de.compressedSize; - directorySize += 46 + de.name.length + + directorySize += centralFileHeaderLength + 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; + uint dataSize = archiveSize + directorySize + endOfCentralDirLength + cast(uint) comment.length; if (isZip64) - dataSize += eocd64LocLength + eocd64Length; + dataSize += zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength; - _data = new ubyte[dataSize]; + _data = uninitializedArray!(ubyte[])(dataSize); // Populate the data[] @@ -453,7 +687,7 @@ final class ZipArchive foreach (ArchiveMember de; directory) { de.offset = i; - _data[i .. i + 4] = cast(ubyte[])"PK\x03\x04"; + _data[i .. i + 4] = localFileHeaderSignature; putUshort(i + 4, de.extractVersion); putUshort(i + 6, de.flags); putUshort(i + 8, de._compressionMethod); @@ -463,9 +697,9 @@ final class ZipArchive 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; + i += localFileHeaderLength; - _data[i .. i + de.name.length] = (cast(ubyte[]) de.name)[]; + _data[i .. i + de.name.length] = (de.name.representation)[]; i += de.name.length; _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[]; i += de.extra.length; @@ -475,10 +709,9 @@ final class ZipArchive // Write directory directoryOffset = i; - _numEntries = 0; foreach (ArchiveMember de; directory) { - _data[i .. i + 4] = cast(ubyte[])"PK\x01\x02"; + _data[i .. i + 4] = centralFileHeaderSignature; putUshort(i + 4, de._madeVersion); putUshort(i + 6, de.extractVersion); putUshort(i + 8, de.flags); @@ -490,180 +723,175 @@ final class ZipArchive 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 + 34, cast(ushort) 0); putUshort(i + 36, de.internalAttributes); putUint (i + 38, de._externalAttributes); putUint (i + 42, de.offset); - i += 46; + i += centralFileHeaderLength; - _data[i .. i + de.name.length] = (cast(ubyte[]) de.name)[]; + _data[i .. i + de.name.length] = (de.name.representation)[]; 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)[]; + _data[i .. i + de.comment.length] = (de.comment.representation)[]; 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); + _data[i .. i + 4] = zip64EndOfCentralDirSignature; + putUlong (i + 4, zip64EndOfCentralDirLength - 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); + putUint (i + 16, cast(ushort) 0); + putUint (i + 20, cast(ushort) 0); + putUlong (i + 24, directory.length); + putUlong (i + 32, directory.length); putUlong (i + 40, directorySize); putUlong (i + 48, directoryOffset); - i += eocd64Length; + i += zip64EndOfCentralDirLength; // Write zip64 end of central directory record locator - _data[i .. i + 4] = cast(ubyte[])"PK\x06\x07"; - putUint (i + 4, diskNumber); + _data[i .. i + 4] = zip64EndOfCentralDirLocatorSignature; + putUint (i + 4, cast(ushort) 0); putUlong (i + 8, eocd64Offset); putUint (i + 16, 1); - i += eocd64LocLength; + i += zip64EndOfCentralDirLocatorLength; } // 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)); + _data[i .. i + 4] = endOfCentralDirSignature; + putUshort(i + 4, cast(ushort) 0); + putUshort(i + 6, cast(ushort) 0); + putUshort(i + 8, (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries)); 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; + i += endOfCentralDirLength; // Write archive comment - assert(i + comment.length == data.length); - _data[i .. data.length] = (cast(ubyte[]) comment)[]; + assert(i + comment.length == data.length, "Writing the archive comment failed."); + _data[i .. data.length] = (comment.representation)[]; return cast(void[]) data; } + @safe pure unittest + { + import std.exception : assertNotThrown; + + ZipArchive zip = new ZipArchive(); + zip.comment = "A"; + assertNotThrown(zip.build()); + } + + @safe pure unittest + { + import std.range : repeat, array; + import std.exception : assertThrown; + + ZipArchive zip = new ZipArchive(); + zip.comment = 'A'.repeat(70_000).array; + assertThrown!ZipException(zip.build()); + } + /* ============ Reading an existing archive =================== */ /** * Constructor to use when reading an existing archive. * - * Fills in the properties data[], diskNumber, diskStartDir, numEntries, - * totalEntries, comment[], and directory[]. + * Fills in the properties data[], totalEntries, comment[], and directory[]. * For each ArchiveMember, fills in * properties madeVersion, extractVersion, flags, compressionMethod, time, - * crc32, compressedSize, expandedSize, compressedData[], diskNumber, + * crc32, compressedSize, expandedSize, compressedData[], * internalAttributes, externalAttributes, name[], extra[], comment[]. * Use expand() to get the expanded data for each ArchiveMember. * * Params: - * buffer = the entire contents of the archive. + * buffer = The entire contents of the archive. + * + * Throws: ZipException when the archive was invalid or when malware was detected. */ - 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"); + enforce!ZipException(data.length <= uint.max - 2, "zip files bigger than 4 GB are unsupported"); + + _segs = [Segment(0, cast(uint) data.length)]; + + uint i = findEndOfCentralDirRecord(); - // 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--) + int endCommentLength = getUshort(i + 20); + comment = cast(string)(_data[i + endOfCentralDirLength .. i + endOfCentralDirLength + endCommentLength]); + + // end of central dir record + removeSegment(i, i + endOfCentralDirLength + endCommentLength); + + uint k = i - zip64EndOfCentralDirLocatorLength; + if (k < i && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature) { - if (i < iend || i >= data.length) - throw new ZipException("no end record"); + _isZip64 = true; + i = k; - 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; - } + // zip64 end of central dir record locator + removeSegment(k, k + zip64EndOfCentralDirLocatorLength); } + uint directorySize; + uint directoryOffset; + uint directoryCount; + if (isZip64) { // Read Zip64 record data ulong eocdOffset = getUlong(i + 8); - if (eocdOffset + eocd64Length > _data.length) - throw new ZipException("corrupted directory"); + enforce!ZipException(eocdOffset + zip64EndOfCentralDirLength <= _data.length, + "corrupted directory"); i = to!uint(eocdOffset); - if (_data[i .. i + 4] != cast(ubyte[])"PK\x06\x06") - throw new ZipException("invalid Zip EOCD64 signature"); + enforce!ZipException(_data[i .. i + 4] == zip64EndOfCentralDirSignature, + "invalid Zip EOCD64 signature"); ulong eocd64Size = getUlong(i + 4); - if (eocd64Size + i - 12 > data.length) - throw new ZipException("invalid Zip EOCD64 size"); + enforce!ZipException(eocd64Size + i - 12 <= data.length, + "invalid Zip EOCD64 size"); - _diskNumber = getUint(i + 16); - _diskStartDir = getUint(i + 20); + // zip64 end of central dir record + removeSegment(i, cast(uint) (i + 12 + eocd64Size)); 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"); + enforce!ZipException(numEntriesUlong <= uint.max, + "supposedly more than 4294967296 files in archive"); - if (numEntriesUlong != totalEntriesUlong) - throw new ZipException("multiple disk zips not supported"); + enforce!ZipException(numEntriesUlong == totalEntriesUlong, + "multiple disk zips not supported"); - if (directorySizeUlong > i || directoryOffsetUlong > i - || directorySizeUlong + directoryOffsetUlong > i) - throw new ZipException("corrupted directory"); + enforce!ZipException(directorySizeUlong <= i && directoryOffsetUlong <= i + && directorySizeUlong + directoryOffsetUlong <= i, + "corrupted directory"); - _numEntries = to!uint(numEntriesUlong); - _totalEntries = to!uint(totalEntriesUlong); + directoryCount = 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"); + // Read end record data + directoryCount = getUshort(i + 10); + directorySize = getUint(i + 12); + directoryOffset = getUint(i + 16); } i = directoryOffset; - for (int n = 0; n < numEntries; n++) + for (int n = 0; n < directoryCount; n++) { /* The format of an entry is: * 'PK' 1, 2 @@ -677,8 +905,8 @@ final class ZipArchive uint extralen; uint commentlen; - if (_data[i .. i + 4] != cast(ubyte[])"PK\x01\x02") - throw new ZipException("invalid directory entry 1"); + enforce!ZipException(_data[i .. i + 4] == centralFileHeaderSignature, + "wrong central file header signature found"); ArchiveMember de = new ArchiveMember(); de._index = n; de._madeVersion = getUshort(i + 4); @@ -692,14 +920,17 @@ final class ZipArchive 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"); + // central file header + removeSegment(i, i + centralFileHeaderLength + namelen + extralen + commentlen); + + i += centralFileHeaderLength; + + enforce!ZipException(i + namelen + extralen + commentlen <= directoryOffset + directorySize, + "invalid field lengths in file header found"); de.name = cast(string)(_data[i .. i + namelen]); i += namelen; @@ -708,31 +939,288 @@ final class ZipArchive 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."); + auto localFileHeaderNamelen = getUshort(de.offset + 26); + auto localFileHeaderExtralen = getUshort(de.offset + 28); + + // file data + removeSegment(de.offset, de.offset + localFileHeaderLength + localFileHeaderNamelen + + localFileHeaderExtralen + de._compressedSize); + + immutable uint dataOffset = de.offset + localFileHeaderLength + + localFileHeaderNamelen + localFileHeaderExtralen; de._compressedData = _data[dataOffset .. dataOffset + de.compressedSize]; _directory[de.name] = de; + } + + enforce!ZipException(i == directoryOffset + directorySize, "invalid directory entry 3"); + } + + @system unittest + { + import std.exception : assertThrown; + + // contains wrong directorySize (extra byte 0xff) + auto file = + "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4b\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // wrong eocdOffset + auto file = + "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // wrong signature of zip64 end of central directory + auto file = + "\x50\x4b\x06\x07\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // wrong size of zip64 end of central directory + auto file = + "\x50\x4b\x06\x06\xff\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // too many entries in zip64 end of central directory + auto file = + "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // zip64: numEntries and totalEntries differ + auto file = + "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // zip64: directorySize too large + auto file = + "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + + // zip64: directoryOffset too large + file = + "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\xff\xff\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + + // zip64: directorySize + directoryOffset too large + // we need to add a useless byte at the beginning to avoid that one of the other two checks allready fires + file = + "\x00\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~ + "\x01\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // wrong central file header signature + auto file = + "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x03\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + @system unittest + { + import std.exception : assertThrown; + + // invalid field lengths in file header + auto file = + "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x01\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + } + + private uint findEndOfCentralDirRecord() + { + // end of central dir record can be followed by a comment of up to 2^^16-1 bytes + // therefore we have to scan 2^^16 positions + + uint endrecOffset = to!uint(data.length); + foreach (i; 0 .. 2 ^^ 16) + { + if (endOfCentralDirLength + i > data.length) break; + uint start = to!uint(data.length) - endOfCentralDirLength - i; + + if (data[start .. start + 4] != endOfCentralDirSignature) continue; + + auto numberOfThisDisc = getUshort(start + 4); + if (numberOfThisDisc != 0) continue; // no support for multiple volumes yet + + auto numberOfStartOfCentralDirectory = getUshort(start + 6); + if (numberOfStartOfCentralDirectory != 0) continue; // dito + + if (numberOfThisDisc < numberOfStartOfCentralDirectory) continue; + + uint k = start - zip64EndOfCentralDirLocatorLength; + auto maybeZip64 = k < start && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature; + + auto totalNumberOfEntriesOnThisDisk = getUshort(start + 8); + auto totalNumberOfEntriesInCentralDir = getUshort(start + 10); + + if (totalNumberOfEntriesOnThisDisk > totalNumberOfEntriesInCentralDir && + (!maybeZip64 || totalNumberOfEntriesOnThisDisk < 0xffff)) continue; + + auto sizeOfCentralDirectory = getUint(start + 12); + if (sizeOfCentralDirectory > start && + (!maybeZip64 || sizeOfCentralDirectory < 0xffff)) continue; + auto offsetOfCentralDirectory = getUint(start + 16); + if (offsetOfCentralDirectory > start - sizeOfCentralDirectory && + (!maybeZip64 || offsetOfCentralDirectory < 0xffff)) continue; + + auto zipfileCommentLength = getUshort(start + 20); + if (start + zipfileCommentLength + endOfCentralDirLength != data.length) continue; + + enforce!ZipException(endrecOffset == to!uint(data.length), + "found more than one valid 'end of central dir record'"); + + endrecOffset = start; } - if (i != directoryOffset + directorySize) - throw new ZipException("invalid directory entry 3"); + + enforce!ZipException(endrecOffset != to!uint(data.length), + "found no valid 'end of central dir record'"); + + return endrecOffset; } - /***** - * Decompress the contents of archive member de and return the expanded - * data. + /** + * Decompress the contents of a member. * * Fills in properties extractVersion, flags, compressionMethod, time, * crc32, compressedSize, expandedSize, expandedData[], name[], extra[]. + * + * Params: + * de = Member to be decompressed. + * + * Returns: The expanded data. + * + * Throws: ZipException when the entry is invalid or the compression method is not supported. */ ubyte[] expand(ArchiveMember de) - { uint namelen; + { + import std.string : representation; + + uint namelen; uint extralen; - if (_data[de.offset .. de.offset + 4] != cast(ubyte[])"PK\x03\x04") - throw new ZipException("invalid directory entry 4"); + enforce!ZipException(_data[de.offset .. de.offset + 4] == localFileHeaderSignature, + "wrong local file header signature found"); // These values should match what is in the main zip archive directory de._extractVersion = getUshort(de.offset + 4); @@ -753,16 +1241,7 @@ final class ZipArchive 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); + enforce!ZipException((de.flags & 1) == 0, "encryption not supported"); switch (de.compressionMethod) { @@ -783,40 +1262,183 @@ final class ZipArchive } } + @system unittest + { + import std.exception : assertThrown; + + // check for correct local file header signature + auto file = + "\x50\x4b\x04\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + auto za = new ZipArchive(cast(void[]) file); + + assertThrown!ZipException(za.expand(za._directory["file"])); + } + + @system unittest + { + import std.exception : assertThrown; + + // check for encryption flag + auto file = + "\x50\x4b\x03\x04\x0a\x00\x01\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + auto za = new ZipArchive(cast(void[]) file); + + assertThrown!ZipException(za.expand(za._directory["file"])); + } + + @system unittest + { + import std.exception : assertThrown; + + // check for invalid compression method + auto file = + "\x50\x4b\x03\x04\x0a\x00\x00\x00\x03\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + auto za = new ZipArchive(cast(void[]) file); + + assertThrown!ZipException(za.expand(za._directory["file"])); + } + /* ============ Utility =================== */ - @safe ushort getUshort(int i) + @safe @nogc pure nothrow ushort getUshort(uint i) { ubyte[2] result = data[i .. i + 2]; return littleEndianToNative!ushort(result); } - @safe uint getUint(int i) + @safe @nogc pure nothrow uint getUint(uint i) { ubyte[4] result = data[i .. i + 4]; return littleEndianToNative!uint(result); } - @safe ulong getUlong(int i) + @safe @nogc pure nothrow ulong getUlong(uint i) { ubyte[8] result = data[i .. i + 8]; return littleEndianToNative!ulong(result); } - @safe void putUshort(int i, ushort us) + @safe @nogc pure nothrow void putUshort(uint i, ushort us) { data[i .. i + 2] = nativeToLittleEndian(us); } - @safe void putUint(int i, uint ui) + @safe @nogc pure nothrow void putUint(uint i, uint ui) { data[i .. i + 4] = nativeToLittleEndian(ui); } - @safe void putUlong(int i, ulong ul) + @safe @nogc pure nothrow void putUlong(uint i, ulong ul) { data[i .. i + 8] = nativeToLittleEndian(ul); } + + /* ============== for detecting overlaps =============== */ + +private: + + // defines a segment of the zip file, including start, excluding end + struct Segment + { + uint start; + uint end; + } + + // removes Segment start .. end from _segs + // throws zipException if start .. end is not completely available in _segs; + void removeSegment(uint start, uint end) pure @safe + in (start < end, "segment invalid") + { + auto found = false; + size_t pos; + foreach (i,seg;_segs) + if (seg.start <= start && seg.end >= end + && (!found || seg.start > _segs[pos].start)) + { + found = true; + pos = i; + } + + enforce!ZipException(found, "overlapping data detected"); + + if (start>_segs[pos].start) + _segs ~= Segment(_segs[pos].start, start); + if (end<_segs[pos].end) + _segs ~= Segment(end, _segs[pos].end); + _segs = _segs[0 .. pos] ~ _segs[pos + 1 .. $]; + } + + pure @safe unittest + { + with (new ZipArchive()) + { + _segs = [Segment(0,100)]; + removeSegment(10,20); + assert(_segs == [Segment(0,10),Segment(20,100)]); + + _segs = [Segment(0,100)]; + removeSegment(0,20); + assert(_segs == [Segment(20,100)]); + + _segs = [Segment(0,100)]; + removeSegment(10,100); + assert(_segs == [Segment(0,10)]); + + _segs = [Segment(0,100), Segment(200,300), Segment(400,500)]; + removeSegment(220,230); + assert(_segs == [Segment(0,100),Segment(400,500),Segment(200,220),Segment(230,300)]); + + _segs = [Segment(200,300), Segment(0,100), Segment(400,500)]; + removeSegment(20,30); + assert(_segs == [Segment(200,300),Segment(400,500),Segment(0,20),Segment(30,100)]); + + import std.exception : assertThrown; + + _segs = [Segment(0,100), Segment(200,300), Segment(400,500)]; + assertThrown(removeSegment(120,230)); + + _segs = [Segment(0,100), Segment(200,300), Segment(400,500)]; + removeSegment(0,100); + assertThrown(removeSegment(0,100)); + + _segs = [Segment(0,100)]; + removeSegment(0,100); + assertThrown(removeSegment(0,100)); + } + } } debug(print) @@ -963,10 +1585,135 @@ the quick brown fox jumps over the lazy dog\r 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 +@system unittest +{ + // invalid format of end of central directory entry + import std.exception : assertThrown; + assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06aaaaaaaaaaaaaaaaaaaa")); +} + +@system unittest +{ + // minimum (empty) archive should pass + auto za = new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"); + assert(za.directory.length == 0); + + // one byte too short or too long should not pass + import std.exception : assertThrown; + assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")); + assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")); +} + +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=20239 + // chameleon file, containing two valid end of central directory entries + auto file = + "\x50\x4B\x03\x04\x0A\x00\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00"~ + "\x00\x00\x01\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75"~ + "\x61\x67\x65\x55\x54\x09\x00\x03\x82\xF2\x8A\x5D\x82\xF2\x8A\x5D\x75\x78\x0B\x00"~ + "\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x44\x50\x4B\x01\x02\x1E\x03\x0A\x00"~ + "\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00\x00\x00\x01\x00\x00\x00"~ + "\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\x00\x00\x00\x00\x62\x65"~ + "\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x82\xF2\x8A\x5D"~ + "\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B\x05\x06\x00"~ + "\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\x48\x00\x00\x00\xB7\x00\x50\x4B\x03"~ + "\x04\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~ + "\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65"~ + "\x55\x54\x09\x00\x03\x97\xF2\x8A\x5D\x8C\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB"~ + "\x03\x00\x00\x04\xEB\x03\x00\x00\x46\x4F\x52\x54\x52\x41\x4E\x50\x4B\x01\x02\x1E"~ + "\x03\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~ + "\x00\x00\x00\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\xB1\x00\x00"~ + "\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x97"~ + "\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B"~ + "\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\xFF\x00\x00\x00\x00\x00"; + + import std.exception : assertThrown; + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); +} + +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=20287 + // check for correct compressed data + auto file = + "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + auto za = new ZipArchive(cast(void[]) file); + assert(za.directory["file"].compressedData == [104, 101, 108, 108, 111]); +} + +// https://issues.dlang.org/show_bug.cgi?id=20027 +@system unittest +{ + // central file header overlaps end of central directory + auto file = + // lfh + "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + import std.exception : assertThrown; + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); + + // local file header and file data overlap second local file header and file data + file = + "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ + "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1e\x00\x66\x69"~ + "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ + "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ + "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ + "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ + "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ + "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ + "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ + "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ + "\x00\x00\x00"; + + assertThrown!ZipException(new ZipArchive(cast(void[]) file)); +} + +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=20295 + // zip64 with 0xff bytes in end of central dir record do not work + // minimum (empty zip64) archive should pass + auto file = + "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ + "\x00\x00"; + + auto za = new ZipArchive(cast(void[]) file); + assert(za.directory.length == 0); +} + +version (HasUnzip) +@system unittest { import std.datetime, std.file, std.format, std.path, std.process, std.stdio; diff --git a/libphobos/src/std/zlib.d b/libphobos/src/std/zlib.d index e6cce24..beb280f 100644 --- a/libphobos/src/std/zlib.d +++ b/libphobos/src/std/zlib.d @@ -1,7 +1,7 @@ // Written in the D programming language. /** - * Compress/decompress data using the $(HTTP www._zlib.net, _zlib library). + * Compress/decompress data using the $(HTTP www.zlib.net, zlib library). * * Examples: * @@ -43,12 +43,12 @@ * References: * $(HTTP en.wikipedia.org/wiki/Zlib, Wikipedia) * - * Copyright: Copyright Digital Mars 2000 - 2011. + * Copyright: Copyright The D Language Foundation 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) + * Source: $(PHOBOSSRC std/zlib.d) */ -/* Copyright Digital Mars 2000 - 2011. +/* Copyright The D Language Foundation 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) @@ -75,9 +75,9 @@ enum class ZlibException : Exception { - this(int errnum) - { string msg; - + private static string getmsg(int errnum) nothrow @nogc pure @safe + { + string msg; switch (errnum) { case Z_STREAM_END: msg = "stream end"; break; @@ -90,7 +90,12 @@ class ZlibException : Exception case Z_VERSION_ERROR: msg = "version error"; break; default: msg = "unknown error"; break; } - super(msg); + return msg; + } + + this(int errnum) + { + super(getmsg(errnum)); } } @@ -104,7 +109,7 @@ class ZlibException : Exception * buf = buffer containing input data * * Returns: - * A $(D uint) checksum for the provided input data and starting checksum + * A `uint` checksum for the provided input data and starting checksum * * See_Also: * $(LINK http://en.wikipedia.org/wiki/Adler-32) @@ -147,7 +152,7 @@ uint adler32(uint adler, const(void)[] buf) * buf = buffer containing input data * * Returns: - * A $(D uint) checksum for the provided input data and starting checksum + * A `uint` checksum for the provided input data and starting checksum * * See_Also: * $(LINK http://en.wikipedia.org/wiki/Cyclic_redundancy_check) @@ -191,13 +196,14 @@ uint crc32(uint crc, const(void)[] buf) ubyte[] compress(const(void)[] srcbuf, int level) in { - assert(-1 <= level && level <= 9); + assert(-1 <= level && level <= 9, "Compression level needs to be within [-1, 9]."); } -body +do { import core.memory : GC; + import std.array : uninitializedArray; auto destlen = srcbuf.length + ((srcbuf.length + 1023) / 1024) + 12; - auto destbuf = new ubyte[destlen]; + auto destbuf = uninitializedArray!(ubyte[])(destlen); auto err = etc.c.zlib.compress2(destbuf.ptr, &destlen, cast(ubyte *) srcbuf.ptr, srcbuf.length, level); if (err) { @@ -276,7 +282,7 @@ void[] uncompress(const(void)[] srcbuf, size_t destlen = 0u, int winbits = 15) throw new ZlibException(err); } } - assert(0); + assert(0, "Unreachable code"); } @system unittest @@ -370,9 +376,9 @@ class Compress this(int level, HeaderFormat header = HeaderFormat.deflate) in { - assert(1 <= level && level <= 9); + assert(1 <= level && level <= 9, "Legal compression level are in [1, 9]."); } - body + do { this.level = level; this.gzip = header == HeaderFormat.gzip; @@ -406,6 +412,7 @@ class Compress const(void)[] compress(const(void)[] buf) { import core.memory : GC; + import std.array : uninitializedArray; int err; ubyte[] destbuf; @@ -420,7 +427,7 @@ class Compress inited = 1; } - destbuf = new ubyte[zs.avail_in + buf.length]; + destbuf = uninitializedArray!(ubyte[])(zs.avail_in + buf.length); zs.next_out = destbuf.ptr; zs.avail_out = to!uint(destbuf.length); @@ -461,9 +468,10 @@ class Compress void[] flush(int mode = Z_FINISH) in { - assert(mode == Z_FINISH || mode == Z_SYNC_FLUSH || mode == Z_FULL_FLUSH); + assert(mode == Z_FINISH || mode == Z_SYNC_FLUSH || mode == Z_FULL_FLUSH, + "Mode must be either Z_FINISH, Z_SYNC_FLUSH or Z_FULL_FLUSH."); } - body + do { import core.memory : GC; ubyte[] destbuf; @@ -523,6 +531,7 @@ class UnCompress z_stream zs; int inited; int done; + bool inputEnded; size_t destbufsize; HeaderFormat format; @@ -571,16 +580,16 @@ class UnCompress const(void)[] uncompress(const(void)[] buf) in { - assert(!done); + assert(!done, "Buffer has been flushed."); } - body + do { + if (inputEnded || !buf.length) + return null; + import core.memory : GC; + import std.array : uninitializedArray; int err; - ubyte[] destbuf; - - if (buf.length == 0) - return null; if (!inited) { @@ -598,26 +607,153 @@ class UnCompress 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; + auto destbuf = uninitializedArray!(ubyte[])(destbufsize); + size_t destFill; 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) + while (true) { - GC.free(destbuf.ptr); - error(err); + auto oldAvailIn = zs.avail_in; + + zs.next_out = destbuf[destFill .. $].ptr; + zs.avail_out = to!uint(destbuf.length - destFill); + + err = inflate(&zs, Z_NO_FLUSH); + if (err == Z_STREAM_END) + { + inputEnded = true; + break; + } + else if (err != Z_OK) + { + GC.free(destbuf.ptr); + error(err); + } + else if (!zs.avail_in) + break; + + /* + According to the zlib manual inflate() stops when either there's + no more data to uncompress or the output buffer is full + So at this point, the output buffer is too full + */ + + destFill = destbuf.length; + + if (destbuf.capacity) + { + if (destbuf.length < destbuf.capacity) + destbuf.length = destbuf.capacity; + else + { + auto newLength = GC.extend(destbuf.ptr, destbufsize, destbufsize); + + if (newLength && destbuf.length < destbuf.capacity) + destbuf.length = destbuf.capacity; + else + destbuf.length += destbufsize; + } + } + else + destbuf.length += destbufsize; } + destbuf.length = destbuf.length - zs.avail_out; return destbuf; } + // Test for https://issues.dlang.org/show_bug.cgi?id=3191 and + // https://issues.dlang.org/show_bug.cgi?id=9505 + @system unittest + { + import std.algorithm.comparison; + import std.array; + import std.file; + import std.zlib; + + // Data that can be easily compressed + ubyte[1024] originalData; + + // This should yield a compression ratio of at least 1/2 + auto compressedData = compress(originalData, 9); + assert(compressedData.length < originalData.length / 2, + "The compression ratio is too low to accurately test this situation"); + + auto chunkSize = compressedData.length / 4; + assert(chunkSize < compressedData.length, + "The length of the compressed data is too small to accurately test this situation"); + + auto decompressor = new UnCompress(); + ubyte[originalData.length] uncompressedData; + ubyte[] reusedBuf; + int progress; + + reusedBuf.length = chunkSize; + + for (int i = 0; i < compressedData.length; i += chunkSize) + { + auto len = min(chunkSize, compressedData.length - i); + // simulate reading from a stream in small chunks + reusedBuf[0 .. len] = compressedData[i .. i + len]; + + // decompress using same input buffer + auto chunk = decompressor.uncompress(reusedBuf); + assert(progress + chunk.length <= originalData.length, + "The uncompressed result is bigger than the original data"); + + uncompressedData[progress .. progress + chunk.length] = cast(const ubyte[]) chunk[]; + progress += chunk.length; + } + + auto chunk = decompressor.flush(); + assert(progress + chunk.length <= originalData.length, + "The uncompressed result is bigger than the original data"); + + uncompressedData[progress .. progress + chunk.length] = cast(const ubyte[]) chunk[]; + progress += chunk.length; + + assert(progress == originalData.length, + "The uncompressed and the original data sizes differ"); + assert(originalData[] == uncompressedData[], + "The uncompressed and the original data differ"); + } + + @system unittest + { + ubyte[1024] invalidData; + auto decompressor = new UnCompress(); + + try + { + auto uncompressedData = decompressor.uncompress(invalidData); + } + catch (ZlibException e) + { + assert(e.msg == "data error"); + return; + } + + assert(false, "Corrupted data didn't result in an error"); + } + + @system unittest + { + ubyte[2014] originalData = void; + auto compressedData = compress(originalData, 9); + + auto decompressor = new UnCompress(); + auto uncompressedData = decompressor.uncompress(compressedData ~ cast(ubyte[]) "whatever"); + + assert(originalData.length == uncompressedData.length, + "The uncompressed and the original data sizes differ"); + assert(originalData[] == uncompressedData[], + "The uncompressed and the original data differ"); + assert(!decompressor.uncompress("whatever").length, + "Compression continued after the end"); + } + /** * Decompress and return any remaining data. * The returned data should be appended to that returned by uncompress(). @@ -626,49 +762,40 @@ class UnCompress void[] flush() in { - assert(!done); + assert(!done, "Buffer has been flushed before."); } out { - assert(done); + assert(done, "Flushing failed."); } - body + do { - import core.memory : GC; - ubyte[] extra; - ubyte[] destbuf; - int err; - done = 1; - if (!inited) - return null; + return null; + } - L1: - destbuf = new ubyte[zs.avail_in * 2 + 100]; - zs.next_out = destbuf.ptr; - zs.avail_out = to!uint(destbuf.length); + /// Returns true if all input data has been decompressed and no further data + /// can be decompressed (inflate() returned Z_STREAM_END) + @property bool empty() const + { + return inputEnded; + } - 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; + /// + @system unittest + { + // some random data + ubyte[1024] originalData = void; + + // append garbage data (or don't, this works in both cases) + auto compressedData = cast(ubyte[]) compress(originalData) ~ cast(ubyte[]) "whatever"; + + auto decompressor = new UnCompress(); + auto uncompressedData = decompressor.uncompress(compressedData); + + assert(uncompressedData[] == originalData[], + "The uncompressed and the original data differ"); + assert(decompressor.empty, "The UnCompressor reports not being done"); } } @@ -754,7 +881,8 @@ import std.stdio; assert( output[] == input[] ); } +// https://issues.dlang.org/show_bug.cgi?id=15457 @system unittest { - static assert(__traits(compiles, etc.c.zlib.gzclose(null))); // bugzilla 15457 + static assert(__traits(compiles, etc.c.zlib.gzclose(null))); } diff --git a/libphobos/testsuite/lib/libphobos.exp b/libphobos/testsuite/lib/libphobos.exp index 2af430a..66e3e80 100644 --- a/libphobos/testsuite/lib/libphobos.exp +++ b/libphobos/testsuite/lib/libphobos.exp @@ -54,6 +54,10 @@ proc libphobos-dg-test { prog do_what extra_tool_flags } { # Set up the compiler flags, based on what we're going to do. switch $do_what { + "compile" { + set compile_type "assembly" + set output_file "[file rootname [file tail $prog]].s" + } "run" { set compile_type "executable" # FIXME: "./" is to cope with "." not being in $PATH. @@ -89,8 +93,52 @@ proc libphobos-dg-test { prog do_what extra_tool_flags } { return [list $comp_output $output_file] } +# Override the DejaGnu dg-test in order to clear flags after a test, as +# is done for compiler tests in gcc-dg.exp. + +if { [info procs saved-dg-test] == [list] } { + rename dg-test saved-dg-test + + proc dg-test { args } { + global additional_prunes + global errorInfo + global testname_with_flags + global shouldfail + + if { [ catch { eval saved-dg-test $args } errmsg ] } { + set saved_info $errorInfo + set additional_prunes "" + set shouldfail 0 + if [info exists testname_with_flags] { + unset testname_with_flags + } + unset_timeout_vars + error $errmsg $saved_info + } + set additional_prunes "" + set shouldfail 0 + unset_timeout_vars + if [info exists testname_with_flags] { + unset testname_with_flags + } + } +} + +# Prune messages from gdc that aren't useful. + +set additional_prunes "" + proc libphobos-dg-prune { system text } { + global additional_prunes + + foreach p $additional_prunes { + if { [string length $p] > 0 } { + # Following regexp matches a complete line containing $p. + regsub -all "(^|\n)\[^\n\]*$p\[^\n\]*" $text "" text + } + } + # Ignore harmless warnings from Xcode. regsub -all "(^|\n)\[^\n\]*ld: warning: could not create compact unwind for\[^\n\]*" $text "" text @@ -281,6 +329,18 @@ proc libphobos_skipped_test_p { test } { return "skipped test" } +# Prune any messages matching ARGS[1] (a regexp) from test output. +proc dg-prune-output { args } { + global additional_prunes + + if { [llength $args] != 2 } { + error "[lindex $args 1]: need one argument" + return + } + + lappend additional_prunes [lindex $args 1] +} + # Return true if the curl library is supported on the target. proc check_effective_target_libcurl_available { } { return [check_no_compiler_messages libcurl_available executable { diff --git a/libphobos/testsuite/libphobos.aa/test_aa.d b/libphobos/testsuite/libphobos.aa/test_aa.d index d6222b1..11ad2f9 100644 --- a/libphobos/testsuite/libphobos.aa/test_aa.d +++ b/libphobos/testsuite/libphobos.aa/test_aa.d @@ -30,6 +30,8 @@ void main() issue15367(); issue16974(); issue18071(); + issue20440(); + issue21442(); testIterationWithConst(); testStructArrayKey(); miscTests1(); @@ -286,21 +288,20 @@ void testUpdate2() assert(updated); } -void testByKey1() +void testByKey1() @safe { - static assert(!__traits(compiles, - () @safe { - struct BadValue - { - int x; - this(this) @safe { *(cast(ubyte*)(null) + 100000) = 5; } // not @safe - alias x this; - } + static struct BadValue + { + int x; + this(this) @system { *(cast(ubyte*)(null) + 100000) = 5; } // not @safe + alias x this; + } - BadValue[int] aa; - () @safe { auto x = aa.byKey.front; } (); - } - )); + BadValue[int] aa; + + // FIXME: Should be @system because of the postblit + if (false) + auto x = aa.byKey.front; } void testByKey2() nothrow pure @@ -690,6 +691,58 @@ void issue18071() () @safe { assert(f.byKey.empty); }(); } +/// Test that `require` works even with types whose opAssign +/// doesn't return a reference to the receiver. +/// https://issues.dlang.org/show_bug.cgi?id=20440 +void issue20440() @safe +{ + static struct S + { + int value; + auto opAssign(S s) { + this.value = s.value; + return this; + } + } + S[S] aa; + assert(aa.require(S(1), S(2)) == S(2)); + assert(aa[S(1)] == S(2)); +} + +/// +void issue21442() +{ + import core.memory; + + size_t[size_t] glob; + + class Foo + { + size_t count; + + this (size_t entries) @safe + { + this.count = entries; + foreach (idx; 0 .. entries) + glob[idx] = idx; + } + + ~this () @safe + { + foreach (idx; 0 .. this.count) + glob.remove(idx); + } + } + + void bar () @safe + { + Foo f = new Foo(16); + } + + bar(); + GC.collect(); // Needs to happen from a GC collection +} + /// Verify iteration with const. void testIterationWithConst() { diff --git a/libphobos/testsuite/libphobos.allocations/alloc_from_assert.d b/libphobos/testsuite/libphobos.allocations/alloc_from_assert.d new file mode 100644 index 0000000..a377cd9 --- /dev/null +++ b/libphobos/testsuite/libphobos.allocations/alloc_from_assert.d @@ -0,0 +1,25 @@ +import core.exception; +import core.memory; + +class FailFinalization +{ + int magic; + + ~this () @nogc nothrow + { + try + assert(this.magic == 42); + catch (AssertError) {} + } +} + +void foo () +{ + auto dangling = new FailFinalization(); +} + +void main() +{ + foo(); + GC.collect(); +} diff --git a/libphobos/testsuite/libphobos.betterc/betterc.exp b/libphobos/testsuite/libphobos.betterc/betterc.exp new file mode 100644 index 0000000..e5e9b84 --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/betterc.exp @@ -0,0 +1,27 @@ +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +# Initialize dg. +dg-init + +# Gather a list of all tests. +set tests [lsort [find $srcdir/$subdir *.d]] + +# Main loop. +dg-runtest $tests "-fno-druntime" $DEFAULT_DFLAGS + +# All done. +dg-finish diff --git a/libphobos/testsuite/libphobos.betterc/test18828.d b/libphobos/testsuite/libphobos.betterc/test18828.d new file mode 100644 index 0000000..db0530d --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/test18828.d @@ -0,0 +1,10 @@ +/*******************************************/ +// https://issues.dlang.org/show_bug.cgi?id=18828 + +struct S18828 { } + +extern(C) void main() +{ + S18828 s; + destroy(s); +} diff --git a/libphobos/testsuite/libphobos.betterc/test19416.d b/libphobos/testsuite/libphobos.betterc/test19416.d new file mode 100644 index 0000000..aff93d3 --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/test19416.d @@ -0,0 +1,14 @@ +/*******************************************/ +// https://issues.dlang.org/show_bug.cgi?id=19416 + +import core.stdc.stdlib : malloc, free; +import core.exception : onOutOfMemoryError; + +extern(C) void main() +{ + auto m = malloc(1); + if (!m) + onOutOfMemoryError(); + else + free(m); +} diff --git a/libphobos/testsuite/libphobos.betterc/test19421.d b/libphobos/testsuite/libphobos.betterc/test19421.d new file mode 100644 index 0000000..2427c5e --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/test19421.d @@ -0,0 +1,13 @@ +/*******************************************/ +// https://issues.dlang.org/show_bug.cgi?id=19421 + +import core.memory; + +extern(C) void main() @nogc nothrow pure +{ + auto p = pureMalloc(1); + p = pureRealloc(p, 2); + if (p) pureFree(p); + p = pureCalloc(1, 1); + if (p) pureFree(p); +} diff --git a/libphobos/testsuite/libphobos.betterc/test19561.d b/libphobos/testsuite/libphobos.betterc/test19561.d new file mode 100644 index 0000000..96ecec51 --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/test19561.d @@ -0,0 +1,16 @@ +/*******************************************/ +// https://issues.dlang.org/show_bug.cgi?id=19561 + +import core.memory; + +extern(C) void main() @nogc nothrow pure +{ + int[3] a, b; + a[] = 0; + a[] = b[]; + //FIXME: Next line requires compiler change. + //a[] = 1; // error: undefined reference to '_memset32' + a[] += 1; + a[] += b[]; + int[3] c = a[] + b[]; +} diff --git a/libphobos/testsuite/libphobos.betterc/test19924.d b/libphobos/testsuite/libphobos.betterc/test19924.d new file mode 100644 index 0000000..e9a93ca --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/test19924.d @@ -0,0 +1,15 @@ +/*******************************************/ +// https://issues.dlang.org/show_bug.cgi?id=19924 + +import core.bitop; + +extern(C) void main() +{ + uint a = 0x01_23_45_67; + a = bswap(a); + assert(a == 0x67_45_23_01); + + ulong b = 0x01_23_45_67_89_ab_cd_ef; + b = bswap(b); + assert(b == 0xef_cd_ab_89_67_45_23_01); +} diff --git a/libphobos/testsuite/libphobos.betterc/test20088.d b/libphobos/testsuite/libphobos.betterc/test20088.d new file mode 100644 index 0000000..a809041 --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/test20088.d @@ -0,0 +1,14 @@ +/*******************************************/ +// https://issues.dlang.org/show_bug.cgi?id=20088 + +struct S { + int i; +} + +extern(C) int main() @nogc nothrow pure +{ + S[2] s = [S(1),S(2)]; + void[] v = cast(void[])s; + S[] p = cast(S[])v; // cast of void[] to S[] triggers __ArrayCast template function + return 0; +} diff --git a/libphobos/testsuite/libphobos.betterc/test20613.d b/libphobos/testsuite/libphobos.betterc/test20613.d new file mode 100644 index 0000000..b03e2d17 --- /dev/null +++ b/libphobos/testsuite/libphobos.betterc/test20613.d @@ -0,0 +1,18 @@ +/*******************************************/ +// https://issues.dlang.org/show_bug.cgi?id=20613 + +extern(C) int main() @nogc nothrow pure +{ + auto s = "F"; + final switch(s) + { + case "A": break; + case "B": break; + case "C": break; + case "D": break; + case "E": break; + case "F": break; + case "G": break; + } + return 0; +} diff --git a/libphobos/testsuite/libphobos.config/config.exp b/libphobos/testsuite/libphobos.config/config.exp new file mode 100644 index 0000000..e8f4d94 --- /dev/null +++ b/libphobos/testsuite/libphobos.config/config.exp @@ -0,0 +1,46 @@ +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +load_lib libphobos-dg.exp + +set dg-output-text [list] + +# Test, arguments to pass to the test program, and return code. +set config_test_list [list \ + { test19433 "--DRT-dont-eat-me" 0 } \ + { test20459 "foo bar -- --DRT-gcopts=profile:1" 0 } \ +] + +# Initialize dg. +dg-init + +# Main loop. +foreach config_test $config_test_list { + set test "$srcdir/$subdir/[lindex $config_test 0].d" + + set libphobos_run_args "[lindex $config_test 1]" + set shouldfail [lindex $config_test 2] + set libphobos_test_name "[dg-trim-dirname $srcdir $test] $libphobos_run_args" + + dg-runtest $test "" $DEFAULT_DFLAGS + + set libphobos_test_name "" + set shouldfail 0 + set libphobos_run_args "" +} + +# All done. +dg-finish diff --git a/libphobos/testsuite/libphobos.config/test19433.d b/libphobos/testsuite/libphobos.config/test19433.d new file mode 100644 index 0000000..1c58145 --- /dev/null +++ b/libphobos/testsuite/libphobos.config/test19433.d @@ -0,0 +1,7 @@ +extern(C) __gshared bool rt_cmdline_enabled = false; + +void main(string[] args) +{ + assert(args.length == 2); + assert(args[1] == "--DRT-dont-eat-me"); +} diff --git a/libphobos/testsuite/libphobos.config/test20459.d b/libphobos/testsuite/libphobos.config/test20459.d new file mode 100644 index 0000000..248720d --- /dev/null +++ b/libphobos/testsuite/libphobos.config/test20459.d @@ -0,0 +1,5 @@ +void main (string[] args) +{ + assert(args.length == 5); + assert(args[1 .. $] == [ "foo", "bar", "--", "--DRT-gcopts=profile:1" ]); +} diff --git a/libphobos/testsuite/libphobos.druntime/druntime.exp b/libphobos/testsuite/libphobos.druntime/druntime.exp index daedfd7..5342d45 100644 --- a/libphobos/testsuite/libphobos.druntime/druntime.exp +++ b/libphobos/testsuite/libphobos.druntime/druntime.exp @@ -22,7 +22,7 @@ if { ![isnative] } { # Gather a list of all tests. set tests [lsort [filter_libphobos_unittests [find $srcdir/../libdruntime "*.d"]]] -set version_flags "" +set version_flags "-fversion=CoreUnittest" if { [is-effective-target linux_pre_2639] } { lappend version_flags "-fversion=Linux_Pre_2639" diff --git a/libphobos/testsuite/libphobos.druntime_shared/druntime_shared.exp b/libphobos/testsuite/libphobos.druntime_shared/druntime_shared.exp index 51f9c2c..67edab9 100644 --- a/libphobos/testsuite/libphobos.druntime_shared/druntime_shared.exp +++ b/libphobos/testsuite/libphobos.druntime_shared/druntime_shared.exp @@ -22,7 +22,7 @@ if { ![isnative] || ![is-effective-target shared] } { # Gather a list of all tests. set tests [lsort [filter_libphobos_unittests [find $srcdir/../libdruntime "*.d"]]] -set version_flags "" +set version_flags "-fversion=CoreUnittest -fversion=Shared" if { [is-effective-target linux_pre_2639] } { lappend version_flags "-fversion=Linux_Pre_2639" diff --git a/libphobos/testsuite/libphobos.exceptions/assert_fail.d b/libphobos/testsuite/libphobos.exceptions/assert_fail.d new file mode 100644 index 0000000..79b3cb8 --- /dev/null +++ b/libphobos/testsuite/libphobos.exceptions/assert_fail.d @@ -0,0 +1,564 @@ +import core.stdc.stdio : fprintf, stderr; +import core.internal.dassert : _d_assert_fail; + +void test(string comp = "==", A, B)(A a, B b, string msg, size_t line = __LINE__) +{ + test(_d_assert_fail!(A)(comp, a, b), msg, line); +} + +void test(const string actual, const string expected, size_t line = __LINE__) +{ + import core.exception : AssertError; + + if (actual != expected) + { + const msg = "Mismatch!\nExpected: <" ~ expected ~ ">\nActual: <" ~ actual ~ '>'; + throw new AssertError(msg, __FILE__, line); + } +} + +void testIntegers() +{ + test(1, 2, "1 != 2"); + test(-10, 8, "-10 != 8"); + test(byte.min, byte.max, "-128 != 127"); + test(ubyte.min, ubyte.max, "0 != 255"); + test(short.min, short.max, "-32768 != 32767"); + test(ushort.min, ushort.max, "0 != 65535"); + test(int.min, int.max, "-2147483648 != 2147483647"); + test(uint.min, uint.max, "0 != 4294967295"); + test(long.min, long.max, "-9223372036854775808 != 9223372036854775807"); + test(ulong.min, ulong.max, "0 != 18446744073709551615"); + test(shared(ulong).min, shared(ulong).max, "0 != 18446744073709551615"); + + int testFun() { return 1; } + test(testFun(), 2, "1 != 2"); +} + +void testIntegerComparisons() +{ + test!"!="(2, 2, "2 == 2"); + test!"<"(2, 1, "2 >= 1"); + test!"<="(2, 1, "2 > 1"); + test!">"(1, 2, "1 <= 2"); + test!">="(1, 2, "1 < 2"); +} + +void testFloatingPoint() +{ + if (__ctfe) + { + test(float.max, -float.max, " != "); + test(double.max, -double.max, " != "); + test(real(1), real(-1), " != "); + } + else + { + test(1.5, 2.5, "1.5 != 2.5"); + test(float.max, -float.max, "3.40282e+38 != -3.40282e+38"); + test(double.max, -double.max, "1.79769e+308 != -1.79769e+308"); + test(real(1), real(-1), "1 != -1"); + } +} + +void testPointers() +{ + static struct S + { + string toString() const { return "S(...)"; } + } + + static if ((void*).sizeof == 4) + enum ptr = "0x12345670"; + else + enum ptr = "0x123456789abcdef0"; + + int* p = cast(int*) mixin(ptr); + test(cast(S*) p, p, ptr ~ " != " ~ ptr); +} + +void testStrings() +{ + test("foo", "bar", `"foo" != "bar"`); + test("", "bar", `"" != "bar"`); + + char[] dlang = "dlang".dup; + const(char)[] rust = "rust"; + test(dlang, rust, `"dlang" != "rust"`); + + // https://issues.dlang.org/show_bug.cgi?id=20322 + test("left"w, "right"w, `"left" != "right"`); + test("left"d, "right"d, `"left" != "right"`); + + test('A', 'B', "'A' != 'B'"); + test(wchar('❤'), wchar('∑'), "'❤' != '∑'"); + test(dchar('❤'), dchar('∑'), "'❤' != '∑'"); + + // Detect invalid code points + test(char(255), 'B', "cast(char) 255 != 'B'"); + test(wchar(0xD888), wchar('∑'), "cast(wchar) 55432 != '∑'"); + test(dchar(0xDDDD), dchar('∑'), "cast(dchar) 56797 != '∑'"); +} + +void testToString() +{ + class Foo + { + this(string payload) { + this.payload = payload; + } + + string payload; + override string toString() { + return "Foo(" ~ payload ~ ")"; + } + } + test(new Foo("a"), new Foo("b"), "Foo(a) != Foo(b)"); + + scope f = cast(shared) new Foo("a"); + if (!__ctfe) // Ref somehow get's lost in CTFE + test!"!="(f, f, "Foo(a) == Foo(a)"); + + // Verifiy that the const toString is selected if present + static struct Overloaded + { + string toString() + { + return "Mutable"; + } + + string toString() const + { + return "Const"; + } + } + + test!"!="(Overloaded(), Overloaded(), "Const == Const"); + + Foo fnull = null; + test!"!is"(fnull, fnull, "`null` is `null`"); +} + + +void testArray() +{ + test([1], [0], "[1] != [0]"); + test([1, 2, 3], [0], "[1, 2, 3] != [0]"); + + // test with long arrays + int[] arr; + foreach (i; 0 .. 100) + arr ~= i; + test(arr, [0], "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ...] != [0]"); + + // Ignore fake arrays + static struct S + { + int[2] arr; + int[] get() return { return arr[]; } + alias get this; + } + + const a = S([1, 2]); + test(a, S([3, 4]), "S([1, 2]) != S([3, 4])"); +} + +void testStruct() +{ + struct S { int s; } + struct T { T[] t; } + test(S(0), S(1), "S(0) != S(1)"); + test(T([T(null)]), T(null), "T([T([])]) != T([])"); + + // https://issues.dlang.org/show_bug.cgi?id=20323 + static struct NoCopy + { + @disable this(this); + } + + NoCopy n; + test(_d_assert_fail!(typeof(n))("!=", n, n), "NoCopy() == NoCopy()"); + + shared NoCopy sn; + test(_d_assert_fail!(typeof(sn))("!=", sn, sn), "NoCopy() == NoCopy()"); +} + +void testAA() +{ + test([1:"one"], [2: "two"], `[1: "one"] != [2: "two"]`); + test!"in"(1, [2: 3], "1 !in [2: 3]"); + test!"in"("foo", ["bar": true], `"foo" !in ["bar": true]`); +} + +void testAttributes() @safe pure @nogc nothrow +{ + int a; + string s = _d_assert_fail!(int, char)("==", a, 'c', 1, 'd'); + assert(s == `(0, 'c') != (1, 'd')`); + + string s2 = _d_assert_fail!int("", a); + assert(s2 == `0 != true`); +} + +// https://issues.dlang.org/show_bug.cgi?id=20066 +void testVoidArray() +{ + test!"!is"([], null, (__ctfe ? "" : "[]") ~ " is `null`"); + test!"!is"(null, null, "`null` is `null`"); + test([1], null, "[1] != `null`"); + test("s", null, "\"s\" != `null`"); + test(['c'], null, "\"c\" != `null`"); + test!"!="(null, null, "`null` == `null`"); + + const void[] chunk = [byte(1), byte(2), byte(3)]; + test(chunk, null, (__ctfe ? "" : "[1, 2, 3]") ~ " != `null`"); +} + +void testTemporary() +{ + static struct Bad + { + ~this() @system {} + } + + test!"!="(Bad(), Bad(), "Bad() == Bad()"); +} + +void testEnum() +{ + static struct UUID { + union + { + ubyte[] data = [1]; + } + } + + ubyte[] data; + enum ctfe = UUID(); + test(_d_assert_fail!(ubyte[])("==", ctfe.data, data), "[1] != []"); +} + +void testUnary() +{ + test(_d_assert_fail!int("", 9), "9 != true"); + test(_d_assert_fail!(int[])("!", [1, 2, 3]), "[1, 2, 3] == true"); +} + +void testTuple() +{ + test(_d_assert_fail("=="), "() != ()"); + test(_d_assert_fail("!="), "() == ()"); + test(_d_assert_fail(">="), "() < ()"); +} + +void testStructEquals() +{ + struct T { + bool b; + int i; + float f1 = 2.5; + float f2 = 0; + string s1 = "bar"; + string s2; + } + + T t1; + test!"!="(t1, t1, `T(false, 0, 2.5, 0, "bar", "") == T(false, 0, 2.5, 0, "bar", "")`); + T t2 = {s1: "bari"}; + test(t1, t2, `T(false, 0, 2.5, 0, "bar", "") != T(false, 0, 2.5, 0, "bari", "")`); +} + +void testStructEquals2() +{ + struct T { + bool b; + int i; + float f1 = 2.5; + float f2 = 0; + } + + T t1; + test!"!="(t1, t1, `T(false, 0, 2.5, 0) == T(false, 0, 2.5, 0)`); + T t2 = {i: 2}; + test(t1, t2, `T(false, 0, 2.5, 0) != T(false, 2, 2.5, 0)`); +} + +void testStructEquals3() +{ + struct T { + bool b; + int i; + string s1 = "bar"; + string s2; + } + + T t1; + test!"!="(t1, t1, `T(false, 0, "bar", "") == T(false, 0, "bar", "")`); + T t2 = {s1: "bari"}; + test(t1, t2, `T(false, 0, "bar", "") != T(false, 0, "bari", "")`); +} + +void testStructEquals4() +{ + struct T { + float f1 = 2.5; + float f2 = 0; + string s1 = "bar"; + string s2; + } + + T t1; + test!"!="(t1, t1, `T(2.5, 0, "bar", "") == T(2.5, 0, "bar", "")`); + T t2 = {s1: "bari"}; + test(t1, t2, `T(2.5, 0, "bar", "") != T(2.5, 0, "bari", "")`); +} + +void testStructEquals5() +{ + struct T { + bool b; + int i; + float f2 = 0; + string s2; + } + + T t1; + test!"!="(t1, t1, `T(false, 0, 0, "") == T(false, 0, 0, "")`); + T t2 = {b: true}; + test(t1, t2, `T(false, 0, 0, "") != T(true, 0, 0, "")`); +} + +void testStructEquals6() +{ + class C { override string toString() { return "C()"; }} + struct T { + bool b; + int i; + float f2 = 0; + string s2; + int[] arr; + C c; + } + + T t1; + test!"!="(t1, t1, "T(false, 0, 0, \"\", [], `null`) == T(false, 0, 0, \"\", [], `null`)"); + T t2 = {arr: [1]}; + test(t1, t2, "T(false, 0, 0, \"\", [], `null`) != T(false, 0, 0, \"\", [1], `null`)"); + T t3 = {c: new C()}; + test(t1, t3, "T(false, 0, 0, \"\", [], `null`) != T(false, 0, 0, \"\", [], C())"); +} + +void testContextPointer() +{ + int i; + struct T + { + int j; + int get() + { + return i * j; + } + } + T t = T(1); + t.tupleof[$-1] = cast(void*) 0xABCD; // Deterministic context pointer + test(t, t, `T(1, : 0xabcd) != T(1, : 0xabcd)`); +} + +void testExternClasses() +{ + { + extern(C++) static class Cpp + { + int a; + this(int a) { this.a = a; } + } + scope a = new Cpp(1); + scope b = new Cpp(2); + test(a, b, "Cpp(1) != Cpp(2)"); + test(a, Cpp.init, "Cpp(1) != null"); + } + { + extern(C++) static class CppToString + { + int a; + this(int a) { this.a = a; } + extern(D) string toString() const { return a == 0 ? "hello" : "world"; } + } + scope a = new CppToString(0); + scope b = new CppToString(1); + test(a, b, "hello != world"); + } + if (!__ctfe) + { + extern(C++) static class Opaque; + Opaque null_ = null; + Opaque notNull = cast(Opaque) &null_; + test(null_, notNull, "null != "); + } + { + extern(C++) static interface Stuff {} + scope Stuff stuff = new class Stuff {}; + test(stuff, Stuff.init, "Stuff() != null"); + } +} + +void testShared() +{ + static struct Small + { + int i; + } + + auto s1 = shared Small(1); + const s2 = shared Small(2); + test(s1, s2, "Small(1) != Small(2)"); + + static struct Big + { + long[10] l; + } + + auto b1 = shared Big([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const b2 = shared Big(); + test(b1, b2, "Big([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) != Big([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])"); + + // Sanity check: Big shouldn't be supported by atomicLoad + import core.atomic : atomicLoad; + static assert( __traits(compiles, atomicLoad(s1))); + static assert(!__traits(compiles, atomicLoad(b1))); +} + +void testException() +{ + static struct MayThrow + { + int i; + string toString() + { + if (i == 1) + throw new Exception("Error"); + return "Some message"; + } + } + + test(MayThrow(0), MayThrow(1), `Some message != `); +} + +void testOverlappingFields() +{ + static struct S + { + union + { + double num; + immutable(char)[] name; + } + } + + test(S(1.0), S(2.0), "S(, ) != S(, )"); + + static struct S2 + { + int valid; + union + { + double num; + immutable(char)[] name; + } + } + + test(S2(4, 1.0), S2(5, 2.0), "S2(4, , ) != S2(5, , )"); + + static struct S3 + { + union + { + double num; + immutable(char)[] name; + } + int valid; + } + S3 a = { + num: 1.0, + valid: 8 + }; + + S3 b = { + num: 1.0, + valid: 8 + }; + test(a, b, "S3(, , 8) != S3(, , 8)"); +} + +void testDestruction() +{ + static class Test + { + __gshared string unary, binary; + __gshared bool run; + + ~this() + { + run = true; + unary = _d_assert_fail!int("", 1); + binary = _d_assert_fail!int("==", 1, 2); + } + } + + static void createGarbage() + { + new Test(); + new long[100]; + } + + import core.memory : GC; + createGarbage(); + GC.collect(); + + assert(Test.run); + assert(Test.unary == "Assertion failed (rich formatting is disabled in finalizers)"); + assert(Test.binary == "Assertion failed (rich formatting is disabled in finalizers)"); +} + +int main() +{ + testIntegers(); + testIntegerComparisons(); + testFloatingPoint(); + testPointers(); + testStrings(); + testToString(); + testArray(); + testStruct(); + testAA(); + testAttributes(); + testVoidArray(); + testTemporary(); + testEnum(); + testUnary(); + testTuple(); + if (!__ctfe) + testStructEquals(); + if (!__ctfe) + testStructEquals2(); + testStructEquals3(); + if (!__ctfe) + testStructEquals4(); + if (!__ctfe) + testStructEquals5(); + if (!__ctfe) + testStructEquals6(); + testContextPointer(); + testExternClasses(); + testShared(); + testException(); + testOverlappingFields(); + if (!__ctfe) + testDestruction(); + + if (!__ctfe) + fprintf(stderr, "success.\n"); + return 0; +} + +enum forceCTFE = main(); diff --git a/libphobos/testsuite/libphobos.exceptions/catch_in_finally.d b/libphobos/testsuite/libphobos.exceptions/catch_in_finally.d new file mode 100644 index 0000000..88bd739 --- /dev/null +++ b/libphobos/testsuite/libphobos.exceptions/catch_in_finally.d @@ -0,0 +1,191 @@ +import core.stdc.stdio : fprintf, stderr; + +class MyException : Exception +{ + this() { super(typeof(this).stringof); } +} + +void throw_catch() +{ + try + { + throw new MyException; + } + catch (MyException) + { + } + catch (Exception) + { + assert(false); + } +} + +// Test that exceptions that are entirely thrown and caught in finally blocks don't affect exception handling. +void test1() +{ + try + { + try + { + throw new Exception("p"); + } + finally + { + throw_catch(); + } + } + catch (Exception e) + { + assert(e.msg == "p"); + } +} + +// Test that exceptions that are entirely thrown and caught in finally blocks don't interfere with chaining. +void test2() +{ + try + { + try + { + try + { + throw new Exception("p"); + } + finally + { + throw new Exception("q"); + } + } + finally + { + throw_catch(); + } + } + catch(Exception e) + { + assert(e.msg == "p"); + assert(e.next.msg == "q"); + assert(!e.next.next); + } +} + +void test3() +{ + try + { + try + { + try + { + throw new Exception("p"); + } + finally + { + throw_catch(); + } + } + finally + { + throw new Exception("q"); + } + } + catch(Exception e) + { + assert(e.msg == "p"); + assert(e.next.msg == "q"); + assert(!e.next.next); + } +} + +// Test order of exception handler operations. +void test4() +{ + string result; + void throw_catch() + { + pragma(inline, false); + try + { + result ~= "b"; + throw new MyException; + } + catch (MyException) + { + result ~= "c"; + } + catch (Exception) + { + assert(false); + } + } + try + { + try + { + result ~= "a"; + throw new Exception(""); + } + finally + { + throw_catch(); + } + } + catch(Exception e) + { + result ~= "d"; + } + assert(result == "abcd"); +} + +void test5() +{ + string result; + void fail() + { + result ~= "b"; + throw new Exception("a"); + } + + void throw_catch() + { + pragma(inline, false); + try + { + fail(); + } + catch(Exception e) + { + assert(e.msg == "a"); + assert(!e.next); + result ~= "c"; + } + } + try + { + try + { + result ~= "a"; + throw new Exception("x"); + } + finally + { + throw_catch(); + } + } + catch (Exception e) + { + assert(e.msg == "x"); + assert(!e.next); + result ~= "d"; + } + assert(result == "abcd"); +} + +void main() { + test1(); + test2(); + test3(); + test4(); + test5(); + fprintf(stderr, "success.\n"); +} diff --git a/libphobos/testsuite/libphobos.exceptions/future_message.d b/libphobos/testsuite/libphobos.exceptions/future_message.d new file mode 100644 index 0000000..61b1034 --- /dev/null +++ b/libphobos/testsuite/libphobos.exceptions/future_message.d @@ -0,0 +1,71 @@ +// { dg-options "-Wno-deprecated" } +import core.stdc.stdio; + +// Make sure basic stuff works with future Throwable.message +class NoMessage : Throwable +{ + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + super(msg, next); + } +} + +class WithMessage : Throwable +{ + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + super(msg, next); + } + + override const(char)[] message() const + { + return "I have a custom message."; + } +} + +class WithMessageNoOverride : Throwable +{ + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + super(msg, next); + } + + const(char)[] message() const + { + return "I have a custom message and no override."; + } +} + +class WithMessageNoOverrideAndDifferentSignature : Throwable +{ + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + super(msg, next); + } + + immutable(char)[] message() + { + return "I have a custom message and I'm nothing like Throwable.message."; + } +} + +void test(Throwable t) +{ + try + { + throw t; + } + catch (Throwable e) + { + fprintf(stderr, "%.*s ", cast(int)e.message.length, e.message.ptr); + } +} + +void main() +{ + test(new NoMessage("exception")); + test(new WithMessage("exception")); + test(new WithMessageNoOverride("exception")); + test(new WithMessageNoOverrideAndDifferentSignature("exception")); + fprintf(stderr, "\n"); +} diff --git a/libphobos/testsuite/libphobos.exceptions/long_backtrace_trunc.d b/libphobos/testsuite/libphobos.exceptions/long_backtrace_trunc.d new file mode 100644 index 0000000..3ff45e5 --- /dev/null +++ b/libphobos/testsuite/libphobos.exceptions/long_backtrace_trunc.d @@ -0,0 +1,37 @@ +struct AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(T) { +struct BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB { +struct CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC { +struct DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD { +struct EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE { +struct FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF { +struct GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG { +struct HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH { + T tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt(int x) { + throw new Exception("test"); + } +} +} +} +} +} +} +} +} + +void main() { + try { + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!int. + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB. + CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC. + DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD. + EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE. + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF. + GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG. + HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH x; + x.tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt(1); + } catch (Exception e) { + import core.stdc.stdio; + auto str = e.toString(); + printf("%.*s\n", cast(int)str.length, str.ptr); + } +} diff --git a/libphobos/testsuite/libphobos.exceptions/refcounted.d b/libphobos/testsuite/libphobos.exceptions/refcounted.d new file mode 100644 index 0000000..2b7e79b --- /dev/null +++ b/libphobos/testsuite/libphobos.exceptions/refcounted.d @@ -0,0 +1,96 @@ +// { dg-options "-fpreview=dip1008" } +class E : Exception +{ + static int instances; + this(string msg = "") + { + super(msg); + instances++; + } + + ~this() + { + instances--; + } +} + +void main() +{ + alias chain = Exception.chainTogether; + + assert(chain(null, null) is null); + + try + { + throw new E(); + } + catch (E e) + { + assert(E.instances == 1); + assert(e.refcount == 2); + } + + assert(E.instances == 0); + + try + { + throw new E(); + } + catch (E e) + { + assert(chain(null, e) is e); + assert(e.refcount == 2); // "Owned by e" + 1 + } + + assert(E.instances == 0); + + try + { + throw new E(); + } + catch (E e) + { + assert(chain(e, null) is e); + assert(e.refcount == 2); // "Owned by e" + 1 + } + + assert(E.instances == 0); + + try + { + throw new E("first"); + } + catch (E first) + { + try + { + throw new E("second"); + } + catch (E second) + { + try + { + throw new E("third"); + } + catch (E third) + { + assert(chain(first, second) is first); + assert(first.next is second); + assert(second.next is null); + + assert(chain(first, third) is first); + assert(first.next is second); + assert(second.next is third); + assert(third.next is null); + + assert(first.refcount == 2); + assert(second.refcount == 3); + assert(third.refcount == 3); + } + } + + assert(E.instances == 3); + } + + assert(E.instances == 0); +} diff --git a/libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions.d b/libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions.d new file mode 100644 index 0000000..bd0c227 --- /dev/null +++ b/libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions.d @@ -0,0 +1,15 @@ +// { dg-shouldfail "uncaught exception" } +// { dg-output "gcc.deh.*: uncaught exception" } +// Code adapted from +// http://arsdnet.net/this-week-in-d/2016-aug-07.html +extern extern(C) __gshared bool rt_trapExceptions; +extern extern(C) int _d_run_main(int, char**, void*) @system; + +extern(C) int main(int argc, char** argv) { + rt_trapExceptions = false; + return _d_run_main(argc, argv, &_main); +} + +int _main() { + throw new Exception("this will abort"); +} diff --git a/libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions_drt.d b/libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions_drt.d new file mode 100644 index 0000000..fc4448c --- /dev/null +++ b/libphobos/testsuite/libphobos.exceptions/rt_trap_exceptions_drt.d @@ -0,0 +1,11 @@ +// { dg-shouldfail "uncaught exception" } +void test() +{ + int innerLocal = 20; + throw new Exception("foo"); +} +void main(string[] args) +{ + string myLocal = "bar"; + test(); +} diff --git a/libphobos/testsuite/libphobos.exceptions/unknown_gc.d b/libphobos/testsuite/libphobos.exceptions/unknown_gc.d index eb95aee..183c0f2 100644 --- a/libphobos/testsuite/libphobos.exceptions/unknown_gc.d +++ b/libphobos/testsuite/libphobos.exceptions/unknown_gc.d @@ -2,8 +2,12 @@ // { dg-options "-shared-libphobos" } // { dg-shouldfail "unknowngc" } // { dg-output "No GC was initialized, please recheck the name of the selected GC \\('unknowngc'\\)." } +import core.memory; + extern(C) __gshared string[] rt_options = [ "gcopt=gc:unknowngc" ]; void main() { + // GC initialized upon first call -> Unknown GC error is thrown + GC.enable(); } diff --git a/libphobos/testsuite/libphobos.gc/attributes.d b/libphobos/testsuite/libphobos.gc/attributes.d new file mode 100644 index 0000000..a7acd6c --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/attributes.d @@ -0,0 +1,30 @@ +import core.memory; + +// TODO: The following should work, but L10 (second assert) fails. +version(none) void dotest(T) (T* ptr) +{ + GC.clrAttr(ptr, uint.max); + assert(GC.getAttr(ptr) == 0); + + GC.setAttr(ptr, GC.BlkAttr.NO_MOVE); + assert(GC.getAttr(ptr) == GC.BlkAttr.NO_MOVE); + + GC.clrAttr(ptr, GC.BlkAttr.NO_MOVE); + assert(GC.getAttr(ptr) == 0); + GC.clrAttr(ptr, GC.BlkAttr.NO_MOVE); + assert(GC.getAttr(ptr) == 0); +} +else void dotest(T) (T* ptr) +{ + // https://issues.dlang.org/show_bug.cgi?id=21484 + GC.clrAttr(ptr, uint.max); + GC.setAttr(ptr, GC.BlkAttr.NO_MOVE); + GC.getAttr(ptr); +} + +void main () +{ + auto ptr = new int; + dotest!(const(int))(ptr); + dotest!(int)(ptr); +} diff --git a/libphobos/testsuite/libphobos.gc/forkgc.d b/libphobos/testsuite/libphobos.gc/forkgc.d new file mode 100644 index 0000000..9c18dc2 --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/forkgc.d @@ -0,0 +1,36 @@ +import core.memory; +import core.stdc.stdio; +import core.sys.posix.sys.wait; +import core.sys.posix.unistd; + +void main() +{ + printf("[parent] Creating garbage...\n"); + foreach (n; 0 .. 1_000) + new uint[10_000]; + printf("[parent] Collecting garbage...\n"); + GC.collect(); + printf("[parent] Forking...\n"); + auto i = fork(); + if (i < 0) + assert(false, "Fork failed"); + if (i == 0) + { + printf("[child] In fork.\n"); + printf("[child] Creating garbage...\n"); + foreach (n; 0 .. 1_000) + new uint[10_000]; + printf("[child] Collecting garbage...\n"); + GC.collect(); + printf("[child] Exiting fork.\n"); + } + else + { + printf("[parent] Waiting for fork (PID %d).\n", i); + int status; + i = waitpid(i, &status, 0); + printf("[parent] Fork %d exited (%d).\n", i, status); + if (status != 0) + assert(false, "child had errors"); + } +} diff --git a/libphobos/testsuite/libphobos.gc/forkgc2.d b/libphobos/testsuite/libphobos.gc/forkgc2.d new file mode 100644 index 0000000..de7796c --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/forkgc2.d @@ -0,0 +1,22 @@ +import core.stdc.stdlib : exit; +import core.sys.posix.sys.wait : waitpid; +import core.sys.posix.unistd : fork; +import core.thread : Thread; + +void main() +{ + foreach (t; 0 .. 10) + new Thread({ + foreach (n; 0 .. 100) + { + foreach (x; 0 .. 100) + new ubyte[x]; + auto f = fork(); + assert(f >= 0); + if (f == 0) + exit(0); + else + waitpid(f, null, 0); + } + }).start(); +} diff --git a/libphobos/testsuite/libphobos.gc/gc.exp b/libphobos/testsuite/libphobos.gc/gc.exp new file mode 100644 index 0000000..cb78538 --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/gc.exp @@ -0,0 +1,27 @@ +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +# Initialize dg. +dg-init + +# Gather a list of all tests. +set tests [lsort [find $srcdir/$subdir *.d]] + +# Main loop. +dg-runtest $tests "" $DEFAULT_DFLAGS + +# All done. +dg-finish diff --git a/libphobos/testsuite/libphobos.gc/nocollect.d b/libphobos/testsuite/libphobos.gc/nocollect.d new file mode 100644 index 0000000..5df1483 --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/nocollect.d @@ -0,0 +1,15 @@ +// https://issues.dlang.org/show_bug.cgi?id=20567 + +import core.memory; + +void main() +{ + auto stats = GC.profileStats(); + assert(stats.numCollections == 0); + + char[] sbuf = new char[256]; // small pool + char[] lbuf = new char[2049]; // large pool + + stats = GC.profileStats(); + assert(stats.numCollections == 0); +} \ No newline at end of file diff --git a/libphobos/testsuite/libphobos.gc/precisegc.d b/libphobos/testsuite/libphobos.gc/precisegc.d new file mode 100644 index 0000000..9bcaf3f --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/precisegc.d @@ -0,0 +1,126 @@ +// precise GC related: +// https://issues.dlang.org/show_bug.cgi?id=3463 +// https://issues.dlang.org/show_bug.cgi?id=4358 +// https://issues.dlang.org/show_bug.cgi?id=9094 +// https://issues.dlang.org/show_bug.cgi?id=13801 +// https://issues.dlang.org/show_bug.cgi?id=18900 +module testgc; + +import core.memory; +import core.stdc.stdio; + +class C +{ + __gshared int dtors; + ~this() { dtors++; } + + C next; + size_t val; +} + +struct S +{ + __gshared int dtors; + ~this() { dtors++; } + + size_t val; + S* next; +} + +struct L +{ + __gshared int dtors; + ~this() { dtors++; } + + size_t[1000] data; + S* node; +} + +struct Roots +{ + C c; + S *s; + L *l; +}; + +Roots* roots; +size_t iroots; + +void init() +{ + roots = new Roots; + roots.c = new C; + roots.c.next = new C; + + roots.s = new S; + roots.s.next = new S; + + roots.l = new L; + roots.l.node = new S; +} + +void verifyPointers() +{ + assert(C.dtors == 0); + assert(S.dtors == 0); + assert(L.dtors == 0); +} + +// compiling with -gx should help eliminating false pointers on the stack +Roots makeFalsePointers() +{ + roots.c.val = cast(size_t) cast(void*) roots.c.next; + roots.c.next = null; + roots.s.val = cast(size_t) cast(void*) roots.s.next; + roots.s.next = null; + roots.l.data[7] = cast(size_t) cast(void*) roots.l.node; + roots.l.node = null; + + return Roots(null, null, null); // try to spill register contents +} + +Roots moveRoot() +{ + iroots = cast(size_t)roots; + roots = null; + + return Roots(null, null, null); // try to spill register contents +} + +// compiling with -gx should help eliminating false pointers on the stack +void verifyFalsePointers() +{ + assert(C.dtors <= 1); + if (C.dtors < 1) printf ("False pointers? C.dtors = %d, 1 expected\n", C.dtors); + assert(S.dtors <= 2); + if (S.dtors < 2) printf ("False pointers? S.dtors = %d, 2 expected\n", S.dtors); + assert(L.dtors == 0); +} + +extern(C) __gshared string[] rt_options = [ "gcopt=gc:precise", "scanDataSeg=precise" ]; + +void main() +{ + GC.collect(); // cleanup from unittests + + init(); + GC.collect(); // should collect nothing + verifyPointers(); + + makeFalsePointers(); + GC.collect(); // should collect roots.c.next, roots.s.next and roots.l.node + verifyFalsePointers(); + + moveRoot(); + GC.collect(); // should collect all + + version(Windows) // precise DATA scanning only implemented on Windows + { + assert(C.dtors <= 2); + if (C.dtors < 2) printf ("False DATA pointers? C.dtors = %d, 2 expected\n", C.dtors); + assert(S.dtors <= 3); + if (S.dtors < 3) printf ("False DATA pointers? S.dtors = %d, 2 expected\n", S.dtors); + assert(L.dtors <= 1); + if (L.dtors < 1) printf ("False DATA pointers? L.dtors = %d, 1 expected\n", L.dtors); + } +} diff --git a/libphobos/testsuite/libphobos.gc/recoverfree.d b/libphobos/testsuite/libphobos.gc/recoverfree.d new file mode 100644 index 0000000..59c3b4a --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/recoverfree.d @@ -0,0 +1,13 @@ +// https://issues.dlang.org/show_bug.cgi?id=20438 +import core.stdc.stdio; +import core.memory; + +void main() +{ + auto used0 = GC.stats.usedSize; + void* z = GC.malloc(100); + GC.free(z); + GC.collect(); + auto used1 = GC.stats.usedSize; + used1 <= used0 || assert(false); +} diff --git a/libphobos/testsuite/libphobos.gc/sigmaskgc.d b/libphobos/testsuite/libphobos.gc/sigmaskgc.d new file mode 100644 index 0000000..eb46316 --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/sigmaskgc.d @@ -0,0 +1,42 @@ + +// https://issues.dlang.org/show_bug.cgi?id=20256 + +extern(C) __gshared string[] rt_options = [ "gcopt=parallel:1" ]; + +void main() +{ + version (Posix) + { + import core.sys.posix.signal; + import core.sys.posix.unistd; + import core.thread; + import core.memory; + + sigset_t m; + sigemptyset(&m); + sigaddset(&m, SIGHUP); + + auto x = new int[](10000); + foreach (i; 0 .. 10000) + { + x ~= i; + } + GC.collect(); // GC create thread + + sigprocmask(SIG_BLOCK, &m, null); // block SIGHUP from delivery to main thread + + auto parent_pid = getpid(); + auto child_pid = fork(); + assert(child_pid >= 0); + if (child_pid == 0) + { + kill(parent_pid, SIGHUP); // send signal to parent + _exit(0); + } + // parent + Thread.sleep(100.msecs); + // if we are here, then GC threads didn't receive SIGHUP, + // otherwise whole process killed + _exit(0); + } +} diff --git a/libphobos/testsuite/libphobos.gc/startbackgc.d b/libphobos/testsuite/libphobos.gc/startbackgc.d new file mode 100644 index 0000000..bf51fbd --- /dev/null +++ b/libphobos/testsuite/libphobos.gc/startbackgc.d @@ -0,0 +1,22 @@ +// https://issues.dlang.org/show_bug.cgi?id=20270 +import core.sys.posix.sys.wait : waitpid; +import core.sys.posix.unistd : fork, _exit; +import core.thread : Thread; + +void main() +{ + foreach (t; 0 .. 10) + new Thread({ + foreach (n; 0 .. 100) + { + foreach (x; 0 .. 100) + new ubyte[x]; + auto f = fork(); + assert(f >= 0); + if (f == 0) + _exit(0); + else + waitpid(f, null, 0); + } + }).start(); +} diff --git a/libphobos/testsuite/libphobos.hash/test_hash.d b/libphobos/testsuite/libphobos.hash/test_hash.d index b406266..d0a8e5fb 100644 --- a/libphobos/testsuite/libphobos.hash/test_hash.d +++ b/libphobos/testsuite/libphobos.hash/test_hash.d @@ -1,3 +1,5 @@ +// { dg-prune-output "Warning: struct HasNonConstToHash has method toHash" } +// { dg-prune-output "HasNonConstToHash.toHash defined here:" } void main() { issue19562(); @@ -8,8 +10,14 @@ void main() issue19005(); issue19204(); issue19262(); + issue19282(); + issue19332(); // Support might be removed in the future! issue19568(); issue19582(); + issue20034(); + issue21642(); + issue22024(); + issue22076(); testTypeInfoArrayGetHash1(); testTypeInfoArrayGetHash2(); pr2243(); @@ -130,6 +138,30 @@ void issue19262() nothrow h = hashOf(aa, h); } +extern(C++) class Issue19282CppClass {} + +/// test that hashOf doesn't crash for non-null C++ objects. +void issue19282() +{ + Issue19282CppClass c = new Issue19282CppClass(); + size_t h = hashOf(c); + h = hashOf(c, h); +} + +/// Ensure hashOf works for const struct that has non-const toHash & has all +/// fields bitwise-hashable. (Support might be removed in the future!) +void issue19332() +{ + static struct HasNonConstToHash + { + int a; + size_t toHash() { return a; } + } + const HasNonConstToHash val; + size_t h = hashOf(val); + h = hashOf!(const HasNonConstToHash)(val); // Ensure doesn't match more than one overload. +} + /// hashOf should not unnecessarily call a struct's fields' postblits & dtors in CTFE void issue19568() { @@ -189,11 +221,99 @@ void issue19582() } } enum b2 = () { - S[10] a; - return ((const S[] a) @nogc nothrow pure @safe => toUbyte(a))(a); + return ((const S[] a) @nogc nothrow pure @safe => toUbyte(a))(new S[10]); }(); } +/// Check core.internal.hash.hashOf works with enums of non-scalar values +void issue20034() +{ + enum E + { + a = "foo" + } + // should compile + assert(hashOf(E.a, 1)); +} + +/// [REG 2.084] hashOf will fail to compile for some structs/unions that recursively contain shared enums +void issue21642() @safe nothrow pure +{ + enum C : char { _ = 1, } + union U { C c; void[0] _; } + shared union V { U u; } + cast(void) hashOf(V.init); + // Also test the underlying reason the above was failing. + import core.internal.convert : toUbyte; + shared C c; + assert(toUbyte(c) == [ubyte(1)]); +} + +/// Accept enum type whose ultimate base type is a SIMD vector. +void issue22024() @nogc nothrow pure @safe +{ + static if (is(__vector(float[2]))) + { + enum E2 : __vector(float[2]) { a = __vector(float[2]).init, } + enum F2 : E2 { a = E2.init, } + assert(hashOf(E2.init) == hashOf(F2.init)); + assert(hashOf(E2.init, 1) == hashOf(F2.init, 1)); + } + static if (is(__vector(float[4]))) + { + enum E4 : __vector(float[4]) { a = __vector(float[4]).init, } + enum F4 : E4 { a = E4.init, } + assert(hashOf(E4.init) == hashOf(F4.init)); + assert(hashOf(E4.init, 1) == hashOf(F4.init, 1)); + } +} + +/// hashOf(S) can segfault if S.toHash is forwarded via `alias this` to a +/// receiver which may be null. +void issue22076() +{ + static struct S0 { Object a; alias a this; } + + static struct S1 + { + S0 a; + inout(S0)* b() inout nothrow { return &a; } + alias b this; + } + + static struct S2 + { + S0 a; + S1 b; + } + + extern(C++) static class C0 + { + int foo() { return 0; } // Need at least one function in vtable. + S0 a; alias a this; + } + + extern(C++) static class C1 + { + S1 a; + inout(S1)* b() inout nothrow { return &a; } + alias b this; + } + + cast(void) hashOf(S0.init); + cast(void) hashOf(S0.init, 0); + cast(void) hashOf(S1.init); + cast(void) hashOf(S1.init, 0); + cast(void) hashOf(S2.init); + cast(void) hashOf(S2.init, 0); + auto c0 = new C0(); + cast(void) hashOf(c0); + cast(void) hashOf(c0, 0); + auto c1 = new C1(); + cast(void) hashOf(c1); + cast(void) hashOf(c1, 0); +} + /// Tests ensure TypeInfo_Array.getHash uses element hash functions instead /// of hashing array data. void testTypeInfoArrayGetHash1() @@ -300,11 +420,9 @@ void pr2243() enum Bar vsexpr = Bar(); enum int[int] aaexpr = [99:2, 12:6, 45:4]; enum Gun eexpr = Gun.A; - enum cdouble cexpr = 7+4i; enum Foo[] staexpr = [Foo(), Foo(), Foo()]; enum Bar[] vsaexpr = [Bar(), Bar(), Bar()]; enum realexpr = 7.88; - enum raexpr = [8.99L+86i, 3.12L+99i, 5.66L+12i]; enum nullexpr = null; enum plstr = Plain(); enum plarrstr = [Plain(), Plain(), Plain()]; @@ -328,7 +446,6 @@ void pr2243() enum h10 = vsexpr.hashOf(); enum h11 = aaexpr.hashOf(); enum h12 = eexpr.hashOf(); - enum h13 = cexpr.hashOf(); enum h14 = hashOf(new Boo); enum h15 = staexpr.hashOf(); enum h16 = hashOf([new Boo, new Boo, new Boo]); @@ -349,7 +466,6 @@ void pr2243() auto h27 = ptrexpr.hashOf(); enum h28 = realexpr.hashOf(); - enum h29 = raexpr.hashOf(); enum h30 = nullexpr.hashOf(); enum h31 = plstr.hashOf(); enum h32 = plarrstr.hashOf(); @@ -367,7 +483,6 @@ void pr2243() auto v10 = vsexpr; auto v11 = aaexpr; auto v12 = eexpr; - auto v13 = cexpr; auto v14 = new Boo; auto v15 = staexpr; auto v16 = [new Boo, new Boo, new Boo]; @@ -389,7 +504,6 @@ void pr2243() auto v26 = dgexpr; auto v27 = ptrexpr; auto v28 = realexpr; - auto v29 = raexpr; //runtime hashes auto rth1 = hashOf(v1); @@ -404,7 +518,6 @@ void pr2243() auto rth10 = hashOf(v10); auto rth11 = hashOf(v11); auto rth12 = hashOf(v12); - auto rth13 = hashOf(v13); auto rth14 = hashOf(v14); auto rth15 = hashOf(v15); auto rth16 = hashOf(v16); @@ -422,7 +535,6 @@ void pr2243() auto rth26 = hashOf(v26); auto rth27 = hashOf(v27); auto rth28 = hashOf(v28); - auto rth29 = hashOf(v29); auto rth31 = hashOf(v31); auto rth32 = hashOf(v32); @@ -440,7 +552,6 @@ void pr2243() assert(h10 == rth10); assert(h11 == rth11); assert(h12 == rth12); - assert(h13 == rth13); assert(h14 == rth14); assert(h15 == rth15); assert(h16 == rth16); @@ -455,8 +566,7 @@ void pr2243() assert(h25 == rth25); assert(h26 == rth26); assert(h27 == rth27); - assert(h28 == rth28); - assert(h29 == rth29);*/ + assert(h28 == rth28);*/ assert(h30 == rth30); assert(h31 == rth31); assert(h32 == rth32); @@ -482,7 +592,6 @@ void pr2243() auto tih10 = tiHashOf(v10); auto tih11 = tiHashOf(v11); auto tih12 = tiHashOf(v12); - auto tih13 = tiHashOf(v13); auto tih14 = tiHashOf(v14); auto tih15 = tiHashOf(v15); auto tih16 = tiHashOf(v16); @@ -498,7 +607,6 @@ void pr2243() auto tih26 = tiHashOf(v26); auto tih27 = tiHashOf(v27); auto tih28 = tiHashOf(v28); - auto tih29 = tiHashOf(v29); auto tih30 = tiHashOf(v30); auto tih31 = tiHashOf(v31); auto tih32 = tiHashOf(v32); @@ -516,7 +624,6 @@ void pr2243() //assert(tih10 == rth10); // need compiler-generated __xtoHash changes assert(tih11 == rth11); assert(tih12 == rth12); - assert(tih13 == rth13); assert(tih14 == rth14); assert(tih15 == rth15); assert(tih16 == rth16); @@ -532,7 +639,6 @@ void pr2243() assert(tih26 == rth26); assert(tih27 == rth27); assert(tih28 == rth28); - assert(tih29 == rth29); assert(tih30 == rth30); assert(tih31 == rth31); assert(tih32 == rth32); diff --git a/libphobos/testsuite/libphobos.imports/bug18193.d b/libphobos/testsuite/libphobos.imports/bug18193.d new file mode 100644 index 0000000..fc8f5ca --- /dev/null +++ b/libphobos/testsuite/libphobos.imports/bug18193.d @@ -0,0 +1,4 @@ +// { dg-options "-fversion=Shared" } +// { dg-do compile } +import core.runtime; +import core.thread; diff --git a/libphobos/testsuite/libphobos.imports/imports.exp b/libphobos/testsuite/libphobos.imports/imports.exp new file mode 100644 index 0000000..344e415 --- /dev/null +++ b/libphobos/testsuite/libphobos.imports/imports.exp @@ -0,0 +1,29 @@ +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +load_lib libphobos-dg.exp + +# Initialize dg. +dg-init + +# Gather a list of all tests. +set tests [lsort [find $srcdir/$subdir *.d]] + +# Main loop. +dg-runtest $tests "" $DEFAULT_DFLAGS + +# All done. +dg-finish diff --git a/libphobos/testsuite/libphobos.init_fini/custom_gc.d b/libphobos/testsuite/libphobos.init_fini/custom_gc.d new file mode 100644 index 0000000..a5e2bf4 --- /dev/null +++ b/libphobos/testsuite/libphobos.init_fini/custom_gc.d @@ -0,0 +1,203 @@ +import core.gc.registry; +import core.gc.gcinterface; +import core.stdc.stdlib; + +static import core.memory; + +extern (C) __gshared string[] rt_options = ["gcopt=gc:malloc"]; + +extern (C) pragma(crt_constructor) void register_mygc() +{ + registerGCFactory("malloc", &MallocGC.initialize); +} + +extern (C) void register_default_gcs() +{ + // remove default GCs +} + +/** Simple GC that requires any pointers passed to it's API + to point to start of the allocation. + */ +class MallocGC : GC +{ +nothrow @nogc: + static GC initialize() + { + import core.stdc.string : memcpy; + + __gshared ubyte[__traits(classInstanceSize, MallocGC)] buf; + + auto init = typeid(MallocGC).initializer(); + assert(init.length == buf.length); + auto instance = cast(MallocGC) memcpy(buf.ptr, init.ptr, init.length); + instance.__ctor(); + return instance; + } + + this() + { + } + + void Dtor() + { + } + + void enable() + { + } + + void disable() + { + } + + void collect() nothrow + { + } + + void collectNoStack() nothrow + { + } + + void minimize() nothrow + { + } + + uint getAttr(void* p) nothrow + { + return 0; + } + + uint setAttr(void* p, uint mask) nothrow + { + return mask; + } + + uint clrAttr(void* p, uint mask) nothrow + { + return mask; + } + + void* malloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + return sentinelAdd(.malloc(size + sentinelSize), size); + } + + BlkInfo qalloc(size_t size, uint bits, const scope TypeInfo ti) nothrow + { + return BlkInfo(malloc(size, bits, ti), size); + } + + void* calloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + return sentinelAdd(.calloc(1, size + sentinelSize), size); + } + + void* realloc(void* p, size_t size, uint bits, const TypeInfo ti) nothrow + { + return sentinelAdd(.realloc(p - sentinelSize, size + sentinelSize), size); + } + + size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow + { + return 0; + } + + size_t reserve(size_t size) nothrow + { + return 0; + } + + void free(void* p) nothrow + { + free(p - sentinelSize); + } + + void* addrOf(void* p) nothrow + { + return p; + } + + size_t sizeOf(void* p) nothrow + { + return query(p).size; + } + + BlkInfo query(void* p) nothrow + { + return p ? BlkInfo(p, sentinelGet(p)) : BlkInfo.init; + } + + core.memory.GC.Stats stats() nothrow + { + return core.memory.GC.Stats.init; + } + + core.memory.GC.ProfileStats profileStats() nothrow + { + return typeof(return).init; + } + + void addRoot(void* p) nothrow @nogc + { + } + + void removeRoot(void* p) nothrow @nogc + { + } + + @property RootIterator rootIter() @nogc + { + return null; + } + + void addRange(void* p, size_t sz, const TypeInfo ti) nothrow @nogc + { + } + + void removeRange(void* p) nothrow @nogc + { + } + + @property RangeIterator rangeIter() @nogc + { + return null; + } + + void runFinalizers(const scope void[] segment) nothrow + { + } + + bool inFinalizer() nothrow + { + return false; + } + + ulong allocatedInCurrentThread() nothrow + { + return stats().allocatedInCurrentThread; + } + +private: + // doesn't care for alignment + static void* sentinelAdd(void* p, size_t value) + { + *cast(size_t*) p = value; + return p + sentinelSize; + } + + static size_t sentinelGet(void* p) + { + return *cast(size_t*)(p - sentinelSize); + } + + enum sentinelSize = size_t.sizeof; +} + +void main() +{ + // test array append cache + char[] s; + foreach (char c; char.min .. char.max + 1) + s ~= c; +} diff --git a/libphobos/testsuite/libphobos.init_fini/test18996.d b/libphobos/testsuite/libphobos.init_fini/test18996.d new file mode 100644 index 0000000..01d514c --- /dev/null +++ b/libphobos/testsuite/libphobos.init_fini/test18996.d @@ -0,0 +1,13 @@ +// Issue https://issues.dlang.org/show_bug.cgi?id=18996 +// Array!string calls removeRange without first adding the range, but never +// initializes the GC. The behavior of the default GC is to ignore removing +// ranges when the range wasn't added. The ProtoGC originally would crash when +// this happened. + +import core.memory; + +void main() +{ + GC.removeRange(null); + GC.removeRoot(null); +} diff --git a/libphobos/testsuite/libphobos.lifetime/large_aggregate_destroy_21097.d b/libphobos/testsuite/libphobos.lifetime/large_aggregate_destroy_21097.d new file mode 100644 index 0000000..bc0695e --- /dev/null +++ b/libphobos/testsuite/libphobos.lifetime/large_aggregate_destroy_21097.d @@ -0,0 +1,78 @@ +// https://issues.dlang.org/show_bug.cgi?id=21097 + +// The crucial part of the test cases is testing `destroy`. At the same time, we test +// `core.internal.lifetime.emplaceInitializer` (which is currently called by `destroy`). + +enum SIZE = 10_000_000; // 10 MB should exhaust the stack on most if not all test systems. + +import core.internal.lifetime; + +void test_largestruct() +{ + static struct LargeStruct + { + int[SIZE/2] a1; + int b = 42; + int[SIZE/2] a2; + } + static LargeStruct s = void; + emplaceInitializer(s); + assert(s.b == 42); + s.b = 101; + destroy(s); + assert(s.b == 42); +} + +void test_largestruct_w_opassign() +{ + static struct LargeStructOpAssign + { + int[SIZE/2] a1; + int b = 420; // non-zero init + int[SIZE/2] a2; + + void opAssign(typeof(this)) {} // hasElaborateAssign == true + } + static LargeStructOpAssign s = void; + emplaceInitializer(s); + assert(s.b == 420); + s.b = 101; + destroy(s); + assert(s.b == 420); +} + +void test_largearray() { + static struct NonZero + { + int i = 123; + } + static NonZero[SIZE] s = void; + emplaceInitializer(s); + assert(s[SIZE/2] == NonZero.init); + s[10] = NonZero(101); + destroy(s); + assert(s[10] == NonZero.init); +} + +void test_largearray_w_opassign() { + static struct NonZeroWithOpAssign + { + int i = 123; + void opAssign(typeof(this)) {} // hasElaborateAssign == true + } + static NonZeroWithOpAssign[SIZE] s = void; + emplaceInitializer(s); + assert(s[SIZE/2] == NonZeroWithOpAssign.init); + s[10] = NonZeroWithOpAssign(101); + destroy(s); + assert(s[10] == NonZeroWithOpAssign.init); +} + +int main() +{ + test_largestruct(); + test_largestruct_w_opassign(); + test_largearray(); + test_largearray_w_opassign(); + return 0; +} diff --git a/libphobos/testsuite/libphobos.lifetime/lifetime.exp b/libphobos/testsuite/libphobos.lifetime/lifetime.exp new file mode 100644 index 0000000..cb78538 --- /dev/null +++ b/libphobos/testsuite/libphobos.lifetime/lifetime.exp @@ -0,0 +1,27 @@ +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +# Initialize dg. +dg-init + +# Gather a list of all tests. +set tests [lsort [find $srcdir/$subdir *.d]] + +# Main loop. +dg-runtest $tests "" $DEFAULT_DFLAGS + +# All done. +dg-finish diff --git a/libphobos/testsuite/libphobos.phobos/phobos.exp b/libphobos/testsuite/libphobos.phobos/phobos.exp index 937849e..84e9898 100644 --- a/libphobos/testsuite/libphobos.phobos/phobos.exp +++ b/libphobos/testsuite/libphobos.phobos/phobos.exp @@ -27,7 +27,7 @@ if { ![is-effective-target d_runtime_has_std_library] } { # Gather a list of all tests. set tests [lsort [filter_libphobos_unittests [find $srcdir/../src "*.d"]]] -set version_flags "" +set version_flags "-fversion=StdUnittest" if { [is-effective-target linux_pre_2639] } { lappend version_flags "-fversion=Linux_Pre_2639" diff --git a/libphobos/testsuite/libphobos.phobos_shared/phobos_shared.exp b/libphobos/testsuite/libphobos.phobos_shared/phobos_shared.exp index 8498522..b8f8e42 100644 --- a/libphobos/testsuite/libphobos.phobos_shared/phobos_shared.exp +++ b/libphobos/testsuite/libphobos.phobos_shared/phobos_shared.exp @@ -27,7 +27,7 @@ if { ![is-effective-target d_runtime_has_std_library] } { # Gather a list of all tests. set tests [lsort [filter_libphobos_unittests [find $srcdir/../src "*.d"]]] -set version_flags "" +set version_flags "-fversion=StdUnittest" if { [is-effective-target linux_pre_2639] } { lappend version_flags "-fversion=Linux_Pre_2639" diff --git a/libphobos/testsuite/libphobos.shared/host.c b/libphobos/testsuite/libphobos.shared/host.c index 81e896a..395ad0c 100644 --- a/libphobos/testsuite/libphobos.shared/host.c +++ b/libphobos/testsuite/libphobos.shared/host.c @@ -10,6 +10,11 @@ int main(int argc, char* argv[]) void *druntime = dlopen(argv[1], RTLD_LAZY); // load druntime assert(druntime); #endif +#if defined(__DragonFly__) + // workaround for Bugzilla 14824 + void *druntime = dlopen(argv[1], RTLD_LAZY); // load druntime + assert(druntime); +#endif const size_t pathlen = strrchr(argv[0], '/') - argv[0] + 1; char *name = malloc(pathlen + sizeof("plugin1.so")); @@ -56,5 +61,8 @@ int main(int argc, char* argv[]) #if defined(__FreeBSD__) dlclose(druntime); #endif +#if defined(__DragonFly__) + dlclose(druntime); +#endif return EXIT_SUCCESS; } diff --git a/libphobos/testsuite/libphobos.shared/link_mod_collision.d b/libphobos/testsuite/libphobos.shared/link_mod_collision.d deleted file mode 100644 index 9c3d1c7..0000000 --- a/libphobos/testsuite/libphobos.shared/link_mod_collision.d +++ /dev/null @@ -1,5 +0,0 @@ -module lib; // module collides with lib.so - -void main() -{ -} diff --git a/libphobos/testsuite/libphobos.shared/load.d b/libphobos/testsuite/libphobos.shared/load.d index 5a2dd01..0d3ffa6 100644 --- a/libphobos/testsuite/libphobos.shared/load.d +++ b/libphobos/testsuite/libphobos.shared/load.d @@ -2,7 +2,6 @@ import core.runtime; import core.stdc.stdio; import core.stdc.string; import core.thread; - import core.sys.posix.dlfcn; version (DragonFlyBSD) import core.sys.dragonflybsd.dlfcn : RTLD_NOLOAD; diff --git a/libphobos/testsuite/libphobos.shared/load_13414.d b/libphobos/testsuite/libphobos.shared/load_13414.d index f7cbf45..047d507 100644 --- a/libphobos/testsuite/libphobos.shared/load_13414.d +++ b/libphobos/testsuite/libphobos.shared/load_13414.d @@ -16,8 +16,17 @@ void runTest(string name) *cast(void function()*).dlsym(h, "_D9lib_1341420sharedStaticDtorHookOPFZv") = &sharedStaticDtorHook; Runtime.unloadLibrary(h); - assert(tlsDtor == 1); - assert(dtor == 1); + version (CRuntime_Musl) + { + // On Musl, unloadLibrary is a no-op because dlclose is a no-op + assert(tlsDtor == 0); + assert(dtor == 0); + } + else + { + assert(tlsDtor == 1); + assert(dtor == 1); + } } void main(string[] args) diff --git a/libphobos/testsuite/libphobos.shared/load_mod_collision.d b/libphobos/testsuite/libphobos.shared/load_mod_collision.d deleted file mode 100644 index 64243d4..0000000 --- a/libphobos/testsuite/libphobos.shared/load_mod_collision.d +++ /dev/null @@ -1,14 +0,0 @@ -module lib; // module collides with lib.so - -import core.runtime; -import core.stdc.stdio; -import core.stdc.string; -import core.sys.posix.dlfcn; - -void main(string[] args) -{ - auto name = args[0] ~ '\0'; - const pathlen = strrchr(name.ptr, '/') - name.ptr + 1; - name = name[0 .. pathlen] ~ "lib.so"; - auto lib = Runtime.loadLibrary(name); -} diff --git a/libphobos/testsuite/libphobos.thread/external_threads.d b/libphobos/testsuite/libphobos.thread/external_threads.d new file mode 100644 index 0000000..9c98a3f --- /dev/null +++ b/libphobos/testsuite/libphobos.thread/external_threads.d @@ -0,0 +1,50 @@ +import core.sys.posix.pthread; +import core.memory; +import core.thread; + +extern (C) void rt_moduleTlsCtor(); +extern (C) void rt_moduleTlsDtor(); + +extern(C) +void* entry_point1(void*) +{ + // try collecting - GC must ignore this call because this thread + // is not registered in runtime + GC.collect(); + return null; +} + +extern(C) +void* entry_point2(void*) +{ + // This thread gets registered in druntime, does some work and gets + // unregistered to be cleaned up manually + thread_attachThis(); + rt_moduleTlsCtor(); + + auto x = new int[10]; + + rt_moduleTlsDtor(); + thread_detachThis(); + return null; +} + +void main() +{ + // allocate some garbage + auto x = new int[1000]; + + { + pthread_t thread; + auto status = pthread_create(&thread, null, &entry_point1, null); + assert(status == 0); + pthread_join(thread, null); + } + + { + pthread_t thread; + auto status = pthread_create(&thread, null, &entry_point2, null); + assert(status == 0); + pthread_join(thread, null); + } +} diff --git a/libphobos/testsuite/libphobos.thread/fiber_guard_page.d b/libphobos/testsuite/libphobos.thread/fiber_guard_page.d index ca54a19..dbdd0f9 100644 --- a/libphobos/testsuite/libphobos.thread/fiber_guard_page.d +++ b/libphobos/testsuite/libphobos.thread/fiber_guard_page.d @@ -4,6 +4,9 @@ import core.thread; import core.sys.posix.signal; import core.sys.posix.sys.mman; +version (LDC) import ldc.attributes; +else struct optStrategy { string a; } + // this should be true for most architectures // (taken from core.thread) version (GNU_StackGrowsDown) @@ -12,6 +15,7 @@ version (GNU_StackGrowsDown) enum stackSize = MINSIGSTKSZ; // Simple method that causes a stack overflow +@optStrategy("none") void stackMethod() { // Over the stack size, so it overflows the stack diff --git a/libphobos/testsuite/libphobos.thread/join_detach.d b/libphobos/testsuite/libphobos.thread/join_detach.d new file mode 100644 index 0000000..f151519 --- /dev/null +++ b/libphobos/testsuite/libphobos.thread/join_detach.d @@ -0,0 +1,20 @@ +import core.thread; +import core.sync.semaphore; + +__gshared Semaphore sem; + +void thread_main () +{ + sem.notify(); +} + +void main() +{ + auto th = new Thread(&thread_main); + sem = new Semaphore(); + th.start(); + sem.wait(); + while (th.isRunning()) {} + destroy(th); // force detach + th.join(); +} diff --git a/libphobos/testsuite/libphobos.thread/test_import.d b/libphobos/testsuite/libphobos.thread/test_import.d new file mode 100644 index 0000000..dfa0487 --- /dev/null +++ b/libphobos/testsuite/libphobos.thread/test_import.d @@ -0,0 +1,7 @@ +// https://issues.dlang.org/show_bug.cgi?id=20447 +void main() +{ + import core.thread; + int[] x; + auto b = x.dup; +} diff --git a/libphobos/testsuite/libphobos.thread/tlsgc_sections.d b/libphobos/testsuite/libphobos.thread/tlsgc_sections.d index 1421d92..1bd3f26 100644 --- a/libphobos/testsuite/libphobos.thread/tlsgc_sections.d +++ b/libphobos/testsuite/libphobos.thread/tlsgc_sections.d @@ -1,39 +1,70 @@ -final class Class +import core.memory; +import core.sync.condition; +import core.sync.mutex; +import core.thread; + +__gshared Condition g_cond; +__gshared Mutex g_mutex; +__gshared int g_step = 0; + +class C { - // This gets triggered although the instance always stays referenced. ~this() { import core.stdc.stdlib; - abort(); + abort(); // this gets triggered although the instance always stays referenced } } -Class obj; +C c; static this() { - obj = new Class; + c = new C; } static ~this() { - // Free without destruction to avoid triggering abort() import core.memory; - GC.free(cast(void*)obj); + GC.free(cast(void*)c); // free without destruction to avoid triggering abort() } -void doit() +void test() { - foreach (i; 0 .. 10_000) - new ubyte[](100_000); + assert(c !is null); + + // notify the main thread of the finished initialization + synchronized (g_mutex) g_step = 1; + g_cond.notifyAll(); + + // wait until the GC collection is done + synchronized (g_mutex) { + while (g_step != 2) + g_cond.wait(); + } } + void main() { - import core.thread; - auto t = new Thread(&doit); - t.start(); + g_mutex = new Mutex; + g_cond = new Condition(g_mutex); + + auto th = new Thread(&test); + th.start(); + + // wait for thread to be fully initialized + synchronized (g_mutex) { + while (g_step != 1) + g_cond.wait(); + } + + // this causes the other thread's C instance to be reaped with the bug present + GC.collect(); + + // allow the thread to shut down + synchronized (g_mutex) g_step = 2; + g_cond.notifyAll(); - // This triggers the GC that frees the still referenced Class instance. - doit(); + th.join(); } diff --git a/libphobos/testsuite/libphobos.thread/tlsstack.d b/libphobos/testsuite/libphobos.thread/tlsstack.d new file mode 100644 index 0000000..dbd9321 --- /dev/null +++ b/libphobos/testsuite/libphobos.thread/tlsstack.d @@ -0,0 +1,38 @@ +module core.thread.test; // needs access to getStackTop()/getStackBottom() + +import core.stdc.stdio; +import core.thread; + +ubyte[16384] data; + +void showThreadInfo() nothrow +{ + try + { + auto top = getStackTop(); + auto bottom = getStackBottom(); + printf("tlsdata: %p\n", data.ptr); + printf("stack top: %p\n", getStackTop()); + printf("stack bottom:%p\n", getStackBottom()); + printf("used stack: %lld\n", cast(ulong)(bottom - top)); + } + catch(Exception e) + { + assert(false, e.msg); + } +} + +void main() +{ + printf("### main\n"); + showThreadInfo(); + + printf("### thread\n"); + auto th = new Thread(&showThreadInfo, 16384); + th.start(); + th.join(); + + printf("### lowlevel thread\n"); + auto llth = createLowLevelThread(() { showThreadInfo(); }); + joinLowLevelThread(llth); +} diff --git a/libphobos/testsuite/libphobos.typeinfo/enum_.d b/libphobos/testsuite/libphobos.typeinfo/enum_.d new file mode 100644 index 0000000..58cfbe3 --- /dev/null +++ b/libphobos/testsuite/libphobos.typeinfo/enum_.d @@ -0,0 +1,21 @@ +// https://issues.dlang.org/show_bug.cgi?id=21441 + +int dtorCount; +int postblitCount; + +struct S +{ + this(this) { ++postblitCount; } + ~this() { ++dtorCount; } +} + +enum E : S { _ = S.init } + +void main() +{ + E e; + typeid(e).destroy(&e); + assert(dtorCount == 1); + typeid(e).postblit(&e); + assert(postblitCount == 1); +} diff --git a/libphobos/testsuite/libphobos.typeinfo/isbaseof.d b/libphobos/testsuite/libphobos.typeinfo/isbaseof.d new file mode 100644 index 0000000..26dd5a6 --- /dev/null +++ b/libphobos/testsuite/libphobos.typeinfo/isbaseof.d @@ -0,0 +1,46 @@ +// https://issues.dlang.org/show_bug.cgi?id=20178 + +interface I {} +interface J : I {} +interface K(T) {} +class C1 : I {} +class C2 : C1 {} +class C3 : J {} +class C4(T) : C3, K!T {} +class C5(T) : C4!T {} + +void main() @nogc nothrow pure @safe +{ + assert(typeid(C1).isBaseOf(typeid(C1))); + assert(typeid(C1).isBaseOf(typeid(C2))); + + assert(!typeid(C2).isBaseOf(typeid(C1))); + assert(typeid(C2).isBaseOf(typeid(C2))); + + assert(!typeid(C1).isBaseOf(typeid(Object))); + assert(!typeid(C2).isBaseOf(typeid(Object))); + assert(typeid(Object).isBaseOf(typeid(C1))); + assert(typeid(Object).isBaseOf(typeid(C2))); + + assert(typeid(I).isBaseOf(typeid(I))); + assert(typeid(I).isBaseOf(typeid(J))); + assert(typeid(I).isBaseOf(typeid(C1))); + assert(typeid(I).isBaseOf(typeid(C2))); + assert(typeid(I).isBaseOf(typeid(C3))); + assert(!typeid(I).isBaseOf(typeid(Object))); + + assert(!typeid(J).isBaseOf(typeid(I))); + assert(typeid(J).isBaseOf(typeid(J))); + assert(!typeid(J).isBaseOf(typeid(C1))); + assert(!typeid(J).isBaseOf(typeid(C2))); + assert(typeid(J).isBaseOf(typeid(C3))); + assert(!typeid(J).isBaseOf(typeid(Object))); + + assert(typeid(C4!int).isBaseOf(typeid(C5!int))); + assert(typeid(K!int).isBaseOf(typeid(C5!int))); + assert(!typeid(C4!Object).isBaseOf(typeid(C5!int))); + assert(!typeid(K!Object).isBaseOf(typeid(C5!int))); + + static assert(!__traits(compiles, TypeInfo.init.isBaseOf(typeid(C1)))); + static assert(!__traits(compiles, typeid(C1).isBaseOf(TypeInfo.init))); +} diff --git a/libphobos/testsuite/libphobos.unittest/customhandler.d b/libphobos/testsuite/libphobos.unittest/customhandler.d new file mode 100644 index 0000000..f5a0435 --- /dev/null +++ b/libphobos/testsuite/libphobos.unittest/customhandler.d @@ -0,0 +1,21 @@ +import core.runtime; + +UnitTestResult customModuleUnitTester() +{ + version(GoodTests) return UnitTestResult(100, 100, false, true); + version(FailedTests) return UnitTestResult(100, 0, false, true); + version(NoTests) return UnitTestResult(0, 0, true, false); + version(FailNoPrintout) return UnitTestResult(100, 0, false, false); + version(PassNoPrintout) return UnitTestResult(100, 100, false, false); +} + +shared static this() +{ + Runtime.extendedModuleUnitTester = &customModuleUnitTester; +} + +void main() +{ + import core.stdc.stdio; + fprintf(stderr, "main\n"); +} diff --git a/libphobos/testsuite/libphobos.unittest/unittest.exp b/libphobos/testsuite/libphobos.unittest/unittest.exp new file mode 100644 index 0000000..ba2fc6e --- /dev/null +++ b/libphobos/testsuite/libphobos.unittest/unittest.exp @@ -0,0 +1,53 @@ +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +load_lib libphobos-dg.exp + +set dg-output-text [list] + +# Arguments to pass to the compiler, expected output, and return code. +set unit_test_list [list \ + { "-fversion=PassNoPrintout" 0 } \ + { "-fversion=GoodTests" 0 } \ + { "-fversion=FailNoPrintout" 1 } \ + { "-fversion=FailedTests" 1 } \ + { "-fversion=NoTests" 0 } \ +] + +# Initialize dg. +dg-init + +# Gather a list of all tests. +set tests [lsort [find $srcdir/$subdir *.d]] + +# Main loop. +foreach unit_test $unit_test_list { + # The version flags to build the program with. + set test_flags [lindex $unit_test 0] + + # Whether the program is expected to fail. + set expected_fail [lindex $unit_test 1] + + foreach test $tests { + set shouldfail $expected_fail + dg-test $test "" $test_flags + } + + set shouldfail 0 +} + +# All done. +dg-finish diff --git a/libphobos/testsuite/testsuite_flags.in b/libphobos/testsuite/testsuite_flags.in index bafd5ad4..93bf7cb 100755 --- a/libphobos/testsuite/testsuite_flags.in +++ b/libphobos/testsuite/testsuite_flags.in @@ -29,7 +29,7 @@ case ${query} in --gdcflags) GDCFLAGS_default="-fmessage-length=0 -fno-show-column" GDCFLAGS_config="@WARN_DFLAGS@ @GDCFLAGS@ @CET_DFLAGS@ - @phobos_compiler_shared_flag@ -fno-release -funittest" + @phobos_compiler_shared_flag@ -fpreview=dip1000 -fno-release -funittest" echo ${GDCFLAGS_default} ${GDCFLAGS_config} ;; --gdcpaths) -- cgit v1.1