Squashed 'third_party/allwpilib/' changes from 66b57f032..e473a00f9

e473a00f9 [wpiutil] Base64: Add unsigned span/vector variants (#3702)
52f2d580e [wpiutil] raw_uv_ostream: Add reset() (#3701)
d7b1e3576 [wpiutil] WebSocket: move std::function (#3700)
93799fbe9 [examples] Fix description of TrapezoidProfileSubsystem (#3699)
b84644740 [wpimath] Document pose estimator states, inputs, and outputs (#3698)
2dc35c139 [wpimath] Fix classpaths for JNI class loads (#3697)
2cb171f6f [docs] Set Doxygen extract_all to true and fix Doxygen failures (#3695)
a939cd9c8 [wpimath] Print uncontrollable/unobservable models in LQR and KF (#3694)
d5270d113 [wpimath] Clean up C++ StateSpaceUtil tests (#3692)
b20903960 [wpimath] Remove redundant discretization tests from StateSpaceUtilTest (#3689)
c0cb545b4 [wpilibc] Add deprecated Doxygen attribute to SpeedController (#3691)
35c9f66a7 [wpilib] Rename PneumaticsHub to PneumaticHub (#3686)
796d03d10 [wpiutil] Remove unused LLVM header (#3688)
8723caf78 [wpilibj] Make Java TrapezoidProfile.Constraints an immutable class (#3687)
187f50a34 [wpimath] Catch incorrect parameters to state-space models earlier (#3680)
8d04606c4 Replace instances of frc-characterization with SysId (NFC) (#3681)
b82d4f6e5 [hal, cscore, ntcore] Use WPI common handle type base
87e34967e [wpiutil] Add synchronization primitives
e32499c54 [wpiutil] Add ParallelTcpConnector (#3655)
aa0b49228 [wpilib] Remove redundant "quick turn" docs for curvature drive (NFC) (#3674)
57301a7f9 [hal] REVPH: Start closed-loop compressor control on init (#3673)
d1842ea8f [wpilib] Improve interrupt docs (NFC) (#3679)
558151061 [wpiutil] Add DsClient (#3654)
181723e57 Replace `.to<double>()` and `.template to<double>()` with `.value()` (#3667)
6bc1db44b [commands] Add pointer overload of AddRequirements (artf6003) (#3669)
737b57ed5 [wpimath] Update to drake v0.35.0 (#3665)
4d287d1ae [build] Upgrade WPIJREArtifact to JRE 2022-11.0.12u5 (#3666)
f26eb5ada [hal] Fix another typo (get -> gets) (NFC) (#3663)
94ed275ba [hal] Fix misspelling (numer -> number) (NFC) (#3662)
ac2f44da3 [wpiutil] uv: use move for std::function (#3653)
75fa1fbfb [wpiutil] json::serializer: Optimize construction (#3647)
5e689faea [wpiutil] Import MessagePack implementation (mpack) (#3650)
649a50b40 [wpiutil] Add LEB128 byte-by-byte reader (#3652)
e94397a97 [wpiutil] Move json_serializer.h to public headers (#3646)
4ec58724d [wpiutil] uv::Tcp: Clarify SetNoDelay documentation (#3649)
8cb294aa4 [wpiutil] WebSocket: Make Shutdown() public (#3651)
2b3a9a52b [wpiutil] json: Fix map iterator key() for std::string_view (#3645)
138cbb94b [wpiutil] uv::Async: Add direct call for no-parameter specialization (#3648)
e56d6dea8 [ci] Update testbench pool image to ubuntu-latest (#3643)
43f30e44e [build] Enable comments in doxygen source files (#3644)
9e6db17ef [build] Enable doxygen preprocessor expansion of WPI_DEPRECATED (#3642)
0e631ad2f Add WPILib version to issue template (#3641)
6229d8d2f [build] Docs: set case_sense_names to false (#3392)
4647d09b5 [docs] Fix Doxygen warnings, add CI docs lint job (#3639)
4ad3a5402 [hal] Fix PWM allocation channel (#3637)
05e5feac4 [docs] Fix brownout docs (NFC) (#3638)
67df469c5 [examples] Remove old command-based templates and examples (#3263)
689e9ccfb [hal, wpilib] Add brownout voltage configuration (#3632)
9cd4bc407 [docs] Add useLocal to avoid using installer artifacts (#3634)
61996c2bb [cscore] Fix Java direct callback notifications (#3631)
6d3dd99eb [build] Update to newest native-utils (#3633)
f0b484892 [wpiutil] Fix StringMap iterator equality check (#3629)
8352cbb7a Update development build instructions for 2022 (#3616)
6da08b71d [examples] Fix Intermediate Vision Java Example description (#3628)
5d99059bf [wpiutil] Remove optional.h (#3627)
fa41b106a [glass, wpiutil] Add missing format args (#3626)
4e3fd7d42 [build] Enable Zc:__cplusplus for Windows (#3625)
791d8354d [build] Suppress deprecation/removal warnings for old commands (#3618)
10f19e6fc [hal, wpilib] Add REV PneumaticsHub (#3600)
4c61a1305 [ntcore] Revert to per-element copy for toNative() (#3621)
7b3f62244 [wpiutil] SendableRegistry: Print exception stacktrace (#3620)
d347928e4 [hal] Use better error for when console out is enabled while attempting to use onboard serial port (#3622)
cc31079a1 [hal] Use setcap instead of setuid for setting thread priorities (#3613)
4676648b7 [wpimath] Upgrade to Drake v0.34.0 (#3607)
c7594c911 [build] Allow building wpilibc in cmake without cscore and opencv (#3605)
173cb7359 [wpilib] Add TimesliceRobot (#3502)
af295879f [hal] Set error status for I2C port out of range (#3603)
95dd20a15 [build] Enable spotbugs (#3601)
b65fce86b [wpilib] Remove Timer lock in wpilibj and update docs (#3602)
3b8d3bbcb Remove unused and add missing deprecated.h includes (#3599)
f9e976467 [examples] Rename DriveTrain classes to Drivetrain (#3594)
118a27be2 [wpilib] Add Timer tests (#3595)
59c89428e [wpilib] Deprecate Timer::HasPeriodPassed() (#3598)
202ca5e78 Force C++17 in .clang-format (#3597)
d6f185d8e Rename tests for consistency (#3592)
54ca474db [ci] Enable asan and tsan in CI for tests that pass (#3591)
1ca383b23 Add Debouncer (#3590)
179fde3a7 [build] Update to 2022 native utils and gradle 7 (#3588)
50198ffcf [examples] Add Mechanism2d visualization to Elevator Sim (#3587)
a446c2559 [examples] Synchronize C++ and Java Mechanism2d examples (#3589)
a7fb83103 [ci] clang-tidy: Generate compilation commands DB with Gradle (#3585)
4f5e0c9f8 [examples] Update ArmSimulation example to use Mechanism2d (#3572)
8164b91dc [CI] Print CMake test output on failure (#3583)
4d5fca27e [wpilib] Impove Mechanism2D documentation (NFC) (#3584)
fe59e4b9f Make C++ test names more consistent (#3586)
5c8868549 [wpilibc] Fix C++ MechanisimRoot2D to use same NT entries as Java/Glass (#3582)
9359431ba [wpimath] Clean up Eigen usage
72716f51c [wpimath] Upgrade to Eigen 3.4
382deef75 [wpimath] Explicitly export wpimath symbols
161e21173 [ntcore] Match standard handle layout, only allow 16 instances (#3577)
263a24811 [wpimath] Use jinja for codegen (#3574)
725251d29 [wpilib] Increase tolerances of DifferentialDriveSimTest (#3581)
4dff87301 [wpimath] Make LinearFilter::Factorial() constexpr (#3579)
60ede67ab [hal, wpilib] Switch PCM to be a single object that is allowed to be duplicated (#3475)
906bfc846 [build] Add CMake build support for sanitizers (#3576)
0d4f08ad9 [hal] Simplify string copy of joystick name (#3575)
a52bf87b7 [wpiutil] Add Java function package (#3570)
40c7645d6 [wpiutil] UidVector: Return old object from erase() (#3571)
5b886a23f [wpiutil] jni_util: Add size, operator[] to JArrayRef (#3569)
65797caa7 [sim] Fix halsim_ds_socket stringop overflow warning from GCC 10 (#3568)
66abb3988 [hal] Update runtime enum to allow selecting roborio 2 (#3565)
95a12e0ee [hal] UidSetter: Don't revert euid if its already current value (#3566)
27951442b [wpimath] Use external Eigen headers only (#3564)
c42e053ae [docs] Update to doxygen 1.9.2 (#3562)
e7048c8c8 [docs] Disable doxygen linking for common words that are also classes (#3563)
d8e0b6c97 [wpilibj] Fix java async interrupts (#3559)
5e6c34c61 Update to 2022 roborio image (#3537)
828f073eb [wpiutil] Fix uv::Buffer memory leaks caught by asan (#3555)
2dd5701ac [cscore] Fix mutex use-after-free in cscore test (#3557)
531439198 [ntcore] Fix NetworkTables memory leaks caught by asan (#3556)
3d9a4d585 [wpilibc] Fix AnalogTriggerOutput memory leak reported by asan (#3554)
54eda5928 [wpiutil] Ignore ubsan vptr upcast warning in SendableHelper moves (#3553)
5a4f75c9f [wpilib] Replace Speed controller comments with motor controller (NFC) (#3551)
7810f665f [wpiutil] Fix bug in uleb128 (#3540)
697e2dd33 [wpilib] Fix errant jaguar reference in comments (NFC) (#3550)
936c64ff5 [docs] Enable -linksource for javadocs (#3549)
1ea654954 [build] Upgrade CMake build to googletest 1.11.0 (#3548)
32d9949e4 [wpimath] Move controller tests to wpimath (#3541)
01ba56a8a [hal] Replace strncpy with memcpy (#3539)
e109c4251 [build] Rename makeSim flag to forceGazebo to better describe what it does (#3535)
e4c709164 [docs] Use a doxygen theme and add logo (#3533)
960b6e589 [wpimath] Fix Javadoc warning (#3532)
82eef8d5e [hal] Remove over current fault HAL functions from REV PDH (#3526)
aa3848b2c [wpimath] Move RobotDriveBase::ApplyDeadband() to MathUtil (#3529)
3b5d0d141 [wpimath] Add LinearFilter::BackwardFiniteDifference() (#3528)
c8fc715fe [wpimath] Upgrade drake files to v0.33.0 (#3531)
e5fe3a8e1 [build] Treat javadoc warnings as errors in CI and fix warnings (#3530)
e0c6cd3dc [wpimath] Add an operator for composing two Transform2ds (#3527)
2edd510ab [sim] Add sim wrappers for sensors that use SimDevice (#3517)
2b3e2ebc1 [hal] Fix HAL Notifier thread priority setting (#3522)
ab4cb5932 [gitignore] Update gitignore to ignore bazel / clion files (#3524)
57c8615af [build] Generate spotless patch on failure (#3523)
b90317321 Replace std::cout and std::cerr with fmt::print() (#3519)
10cc8b89c [hal] [wpilib] Add initial support for the REV PDH (#3503)
5d9ae3cdb [hal] Set HAL Notifier thread as RT by default (#3482)
192d251ee [wpilibcIntegrationTests] Properly disable DMA integration tests (#3514)
031962608 [wpilib] Add PS4Controller, remove Hand from GenericHID/XboxController (#3345)
25f6f478a [wpilib] Rename DriverStation::IsOperatorControl() to IsTeleop() (#3505)
e80f09f84 [wpilibj] Add unit tests (#3501)
c159f91f0 [wpilib] Only read DS control word once in IterativeRobotBase (#3504)
eb790a74d Add rio development docs documenting myRobot deploy tasks (#3508)
e47451f5a [wpimath] Replace auto with Eigen types (#3511)
252b8c83b Remove Java formatting from build task in CI (#3507)
09666ff29 Shorten Gazebo CI build (#3506)
baf2e501d Update myRobot to use 2021 java (#3509)
5ac60f0a2 [wpilib] Remove IterativeRobotBase mode init prints (#3500)
fb2ee8ec3 [wpilib] Add TimedRobot functions for running code on mode exit (#3499)
94e0db796 [wpilibc] Add more unit tests (#3494)
b25324695 [wpilibj] Add units to parameter names (NFC) (#3497)
1ac73a247 [hal] Rename PowerDistributionPanel to PowerDistribution (#3466)
2014115bc [examples] frisbeebot: Fix typo and reflow comments (NFC) (#3498)
4a944dc39 [examples] Consistently use 0 for controller port (#3496)
3838cc4ec Use unicode characters in docs equations (#3487)
85748f2e6 [examples] Add C++ TankDrive example (#3493)
d7b8aa56d [wpilibj] Rename DriverStation In[Mode] functions to follow style guide (#3488)
16e096cf8 [build] Fix CMake Windows CI (#3490)
50af74c38 [wpimath] Clean up NumericalIntegration and add Discretization tests (#3489)
bfc209b12 Automate fmt update (#3486)
e7f9331e4 [build] Update to Doxygen 1.9.1 (#3008)
ab8e8aa2a [wpimath] Update drake with upstream (#3484)
1ef826d1d [wpimath] Fix IOException path in WPIMath JNI (#3485)
52bddaa97 [wpimath] Disable iostream support for units and enable fmtlib (#3481)
e4dc3908b [wpiutil] Upgrade to fmtlib 8.0.1 (#3483)
1daadb812 [wpimath] Implement Dormand-Prince integration method (#3476)
9c2723391 [cscore] Add [[nodiscard]] to GrabFrame functions (#3479)
7a8796414 [wpilib] Add Notifier integration tests (#3480)
f8f13c536 [wpilibcExamples] Prefix decimal numbers with 0 (#3478)
1adb69c0f [ntcore] Use "NetworkTables" instead of "Network Tables" in NT specs (#3477)
5f5830b96 Upload wpiformat diff if one exists (#3474)
9fb4f35bb [wpimath] Add tests for DARE overload with Q, R, and N matrices (#3472)
c002e6f92 Run wpiformat (#3473)
c154e5262 [wpilib] Make solenoids exclusive use, PCM act like old sendable compressor (#3464)
6ddef1cca [hal] JNI setDIO: use a boolean and not a short (#3469)
9d68d9582 Remove extra newlines after open curly braces (NFC) (#3471)
a4233e1a1 [wpimath] Add script for updating Drake (#3470)
39373c6d2 Update README.md for new GCC version requirement (#3467)
d29acc90a [wpigui] Add option to reset UI on exit (#3463)
a371235b0 [ntcore] Fix dangling pointer in logger (#3465)
53b4891a5 [wpilibcintegrationtests] Fix deprecated Preferences usage (#3461)
646ded912 [wpimath] Remove incorrect discretization in pose estimators (#3460)
ea0b8f48e Fix some deprecation warnings due to fmtlib upgrade (#3459)
2067d7e30 [wpilibjexamples] Add wpimathjni, wpiutiljni to library path (#3455)
866571ab4 [wpiutil] Upgrade to fmtlib 8.0.0 (#3457)
4e1fa0308 [build] Skip PDB copy on windows build servers (#3458)
b45572167 [build] Change CI back to 18.04 docker images (#3456)
57a160f1b [wpilibc] Fix LiveWindow deprecation warning in RobotBase skeleton template (#3454)
29ae8640d [HLT] Implement duty cycle cross connect tests (#3453)
ee6377e54 [HLT] Add relay and analog cross connects (#3452)
b0f1ae7ea [build] CMake: Build the HAL even if WITH_CSCORE=OFF (#3449)
7aae2b72d Replace std::to_string() with fmt::format() (#3451)
73fcbbd74 [HLT] Add relay digital cross connect tests (#3450)
e7bedde83 [HLT] Add PWM tests that use DMA as the back end (#3447)
7253edb1e [wpilibc] Timer: Fix deprecated warning (#3446)
efa28125c [wpilibc] Add message to RobotBase on how to read stacktrace (#3444)
9832fcfe1 [hal] Fix DIO direction getter (#3445)
49c71f9f2 [wpilibj] Clarify robot quit message (#3364)
791770cf6 [wpimath] Move controller from wpilibj to wpimath (#3439)
9ce9188ff [wpimath] Add ReportWarning to MathShared (#3441)
362066a9b [wpilib] Deprecate getInstance() in favor of static functions (#3440)
26ff9371d Initial commit of cross connect integration test project (#3434)
4a36f86c8 [hal] Add support for DMA to Java (#3158)
85144e47f [commands] Unbreak build (#3438)
b417d961e Split Sendable into NT and non-NT portions (#3432)
ef4ea84cb [commands] Change grouping decorator impl to flatten nested group structures (#3335)
b422665a3 [examples] Invert right side of drive subsystems (#3437)
186dadf14 [hal] Error if attempting to set DIO output on an input port (#3436)
04e64db94 Remove redundant C++ lambda parentheses (NFC) (#3433)
f60994ad2 [wpiutil] Rename Java package to edu.wpi.first.util (#3431)
cfa1ca96f [wpilibc] Make ShuffleboardValue non-copyable (#3430)
4d9ff7643 Fix documentation warnings generated by JavaDoc (NFC) (#3428)
9e1b7e046 [build] Fix clang-tidy and clang-format (#3429)
a77c6ff3a [build] Upgrade clang-format and clang-tidy (NFC) (#3422)
099fde97d [wpilib] Improve PDP comments (NFC) (#3427)
f8fc2463e [wpilibc, wpiutil] Clean up includes (NFC) (#3426)
e246b7884 [wpimath] Clean up member initialization in feedforward classes (#3425)
c1e128bd5 Disable frivolous PMD warnings and enable PMD in ntcore (#3419)
8284075ee Run "Lint and Format" CI job on push as well as pull request (#3412)
f7db09a12 [wpimath] Move C++ filters into filter folder to match Java (#3417)
f9c3d54bd [wpimath] Reset error covariance in pose estimator ResetPosition() (#3418)
0773f4033 [hal] Ensure HAL status variables are initialized to zero (#3421)
d068fb321 [build] Upgrade CI to use 20.04 docker images (#3420)
8d054c940 [wpiutil] Remove STLExtras.h
80f1d7921 [wpiutil] Split function_ref to a separate header
64f541325 Use wpi::span instead of wpi::ArrayRef across all libraries (#3414)
2abbbd9e7 [build] clang-tidy: Remove bugprone-exception-escape (#3415)
a5c471af7 [wpimath] Add LQR template specialization for 2x2 system
edd2f0232 [wpimath] Add DARE solver for Q, R, and N with LQR ctor overloads
b2c3b2dd8 Use std::string_view and fmtlib across all libraries (#3402)
4f1cecb8e [wpiutil] Remove Path.h (#3413)
b336eac34 [build] Publish halsim_ws_core to Maven
2a09f6fa4 [build] Also build sim modules as static libraries
0e702eb79 [hal] Add a unified PCM object (#3331)
dea841103 [wpimath] Add fmtlib formatter overloads for Eigen::Matrix and units (#3409)
82856cf81 [wpiutil] Improve wpi::circular_buffer iterators (#3410)
8aecda03e [wpilib] Fix a documentation typo (#3408)
5c817082a [wpilib] Remove InterruptableSensorBase and replace with interrupt classes (#2410)
15c521a7f [wpimath] Fix drivetrain system identification (#3406)
989de4a1b [build] Force all linker warnings to be fatal for rio builds (#3407)
d9eeb45b0 [wpilibc] Add units to Ultrasonic class API (#3403)
fe570e000 [wpiutil] Replace llvm filesystem with C++17 filesystem (#3401)
01dc0249d [wpimath] Move SlewRateLimiter from wpilib to wpimath (#3399)
93523d572 [wpilibc] Clean up integration tests (#3400)
4f7a4464d [wpiutil] Rewrite StringExtras for std::string_view (#3394)
e09293a15 [wpilibc] Transition C++ classes to units::second_t (#3396)
827b17a52 [build] Create run tasks for Glass and OutlineViewer (#3397)
a61037996 [wpiutil] Avoid MSVC warning on span include (#3393)
4e2c3051b [wpilibc] Use std::string_view instead of Twine (#3380)
50915cb7e [wpilibc] MotorSafety::GetDescription(): Return std::string (#3390)
f4e2d26d5 [wpilibc] Move NullDeleter from frc/Base.h to wpi/NullDeleter.h (#3387)
cb0051ae6 [wpilibc] SimDeviceSim: use fmtlib (#3389)
a238cec12 [wpiutil] Deprecate wpi::math constants in favor of wpi::numbers (#3383)
393bf23c0 [ntcore, cscore, wpiutil] Standardize template impl files on .inc extension (NFC) (#3124)
e7d9ba135 [sim] Disable flaky web server integration tests (#3388)
0a0003c11 [wpilibjExamples] Fix name of Java swerve drive pose estimator example (#3382)
7e1b27554 [wpilibc] Use default copies and moves when possible (#3381)
fb2a56e2d [wpilibc] Remove START_ROBOT_CLASS macro (#3384)
84218bfb4 [wpilibc] Remove frc namespace shim (#3385)
dd7824340 [wpilibc] Remove C++ compiler version static asserts (#3386)
484cf9c0e [wpimath] Suppress the -Wmaybe-uninitialized warning in Eigen (#3378)
a04d1b4f9 [wpilibc] DriverStation: Remove ReportError and ReportWarning
831c10bdf [wpilibc] Errors: Use fmtlib
87603e400 [wpiutil] Import fmtlib (#3375)
442621672 [wpiutil] Add ArrayRef/std::span/wpi::span implicit conversions
bc15b953b [wpiutil] Add std::span implementation
6d20b1204 [wpiutil] StringRef, Twine, raw_ostream: Add std::string_view support (#3373)
2385c2a43 [wpilibc] Remove Utility.h (#3376)
87384ea68 [wpilib] Fix PIDController continuous range error calculations (#3170)
04dae799a [wpimath] Add SimpleMotorFeedforward::Calculate(velocity, nextVelocity) overload (#3183)
0768c3903 [wpilib] DifferentialDrive: Remove right side inversion (#3340)
8dd8d4d2d [wpimath] Fix redundant nested math package introduced by #3316 (#3368)
49b06beed [examples] Add Field2d to RamseteController example (#3371)
4c562a445 [wpimath] Fix typo in comment of update_eigen.py (#3369)
fdbbf1188 [wpimath] Add script for updating Eigen
f1e64b349 [wpimath] Move Eigen unsupported folder into eigeninclude
224f3a05c [sim] Fix build error when building with GCC 11.1 (#3361)
ff56d6861 [wpilibj] Fix SpeedController deprecated warnings (#3360)
1873fbefb [examples] Fix Swerve and Mecanum examples (#3359)
80b479e50 [examples] Fix SwerveBot example to use unique encoder ports (#3358)
1f7c9adee [wpilibjExamples] Fix pose estimator examples (#3356)
9ebc3b058 [outlineviewer] Change default size to 600x400 (#3353)
e21b443a4 [build] Gradle: Make C++ examples runnable (#3348)
da590120c [wpilibj] Add MotorController.setVoltage default (#3347)
561d53885 [build] Update opencv to 4.5.2, imgui/implot to latest (#3344)
44ad67ca8 [wpilibj] Preferences: Add missing Deprecated annotation (#3343)
3fe8fc75a [wpilibc] Revert "Return reference from GetInstance" (#3342)
3cc2da332 Merge branch '2022'
a3cd90dd7 [wpimath] Fix classpath used by generate_numbers.py (#3339)
d6cfdd3ba [wpilib] Preferences: Deprecate Put* in favor of Set* (#3337)
ba08baabb [wpimath] Update Drake DARE solver to v0.29.0 (#3336)
497b712f6 [wpilib] Make IterativeRobotBase::m_period private with getter
f00dfed7a [wpilib] Remove IterativeRobot base class
3c0846168 [hal] Use last error reporting instead of PARAMETER_OUT_OF_RANGE (#3328)
5ef2b4fdc [wpilibj] Fix @deprecated warning for SerialPort constructor (#3329)
23d2326d1 [hal] Report previous allocation location for indexed resource duplicates (#3322)
e338f9f19 [build] Fix wpilibc runCpp task (#3327)
c8ff626fe [wpimath] Move Java classes to edu.wpi.first.math (#3316)
4e424d51f [wpilibj] DifferentialDrivetrainSim: Rename constants to match the style guide (#3312)
6b50323b0 [cscore] Use Lock2DSize if possible for Windows USB cameras (#3326)
65c148536 [wpilibc] Fix "control reaches end of non-void function" warning (#3324)
f99f62bee [wpiutil] uv Handle: Use malloc/free instead of new/delete (#3325)
365f5449c [wpimath] Fix MecanumDriveKinematics (#3266)
ff52f207c [glass, wpilib] Rewrite Mechanism2d (#3281)
ee0eed143 [wpimath] Add DCMotor factory function for Romi motors (#3319)
512738072 [hal] Add HAL_GetLastError to enable better error messages from HAL calls (#3320)
ced654880 [glass, outlineviewer] Update Mac icons to macOS 11 style (#3313)
936d3b9f8 [templates] Add Java template for educational robot (#3309)
6e31230ad [examples] Fix odometry update in SwerveControllerCommand example (#3310)
05ebe9318 Merge branch 'main' into 2022
aaf24e255 [wpilib] Fix initial heading behavior in HolonomicDriveController (#3290)
8d961dfd2 [wpilibc] Remove ErrorBase (#3306)
659b37ef9 [wpiutil] StackTrace: Include offset on Linux (#3305)
0abf6c904 [wpilib] Move motor controllers to motorcontrol package (#3302)
4630191fa [wpiutil] circular_buffer: Use value initialization instead of passing zero (#3303)
b7b178f49 [wpilib] Remove Potentiometer interface
687066af3 [wpilib] Remove GyroBase
6b168ab0c [wpilib] Remove PIDController, PIDOutput, PIDSource
948625de9 [wpimath] Document conversion from filter cutoff frequency to time constant (#3299)
3848eb8b1 [wpilibc] Fix flywhel -> flywheel typo in FlywheelSim (#3298)
3abe0b9d4 [cscore] Move java package to edu.wpi.first.cscore (#3294)
d7fabe81f [wpilib] Remove RobotDrive (#3295)
1dc81669c [wpilib] Remove GearTooth (#3293)
01d0e1260 [wpilib] Revert move of RomiGyro into main wpilibc/j (#3296)
397e569aa [ntcore] Remove "using wpi" from nt namespace
79267f9e6 [ntcore] Remove NetworkTable -> nt::NetworkTable shim
48ebe5736 [ntcore] Remove deprecated Java interfaces and classes
c2064c78b [ntcore] Remove deprecated ITable interfaces
36608a283 [ntcore] Remove deprecated C++ APIs
a1c87e1e1 [glass] LogView: Add "copy to clipboard" button (#3274)
fa7240a50 [wpimath] Fix typo in quintic spline basis matrix
ffb4d38e2 [wpimath] Add derivation for spline basis matrices
f57c188f2 [wpilib] Add AnalogEncoder(int) ctor (#3273)
8471c4fb2 [wpilib] FieldObject2d: Add setTrajectory() method (#3277)
c97acd18e [glass] Field2d enhancements (#3234)
ffb590bfc [wpilib] Fix Compressor sendable properties (#3269)
6137f98eb [hal] Rename SimValueCallback2 to SimValueCallback (#3212)
a6f653969 [hal] Move registerSimPeriodic functions to HAL package (#3211)
10c038d9b [glass] Plot: Fix window creation after removal (#3264)
2d2eaa3ef [wpigui] Ensure window will be initially visible (#3256)
4d28b1f0c [wpimath] Use JNI for trajectory serialization (#3257)
3de800a60 [wpimath] TrajectoryUtil.h: Comment formatting (NFC) (#3262)
eff592377 [glass] Plot: Don't overwrite series ID (#3260)
a79faace1 [wpilibc] Return reference from GetInstance (#3247)
9550777b9 [wpilib] PWMSpeedController: Use PWM by composition (#3248)
c8521a3c3 [glass] Plot: Set reasonable default window size (#3261)
d71eb2cf3 [glass] Plot: Show full source name as tooltip and in popup (#3255)
160fb740f [hal] Use std::lround() instead of adding 0.5 and truncating (#3012)
48e9f3951 [wpilibj] Remove wpilibj package CameraServer (#3213)
8afa596fd [wpilib] Remove deprecated Sendable functions and SendableBase (#3210)
d3e45c297 [wpimath] Make C++ geometry classes immutable (#3249)
2c98939c1 [glass] StringChooser: Don't call SameLine() at end
a18a7409f [glass] NTStringChooser: Clear value of deleted entries
2f19cf452 [glass] NetworkTablesHelper: listen to delete events
da96707dc Merge branch 'main' into 2022
c3a8bdc24 [build] Fix clang-tidy action (#3246)
21624ef27 Add ImGui OutlineViewer (#3220)
1032c9b91 [wpiutil] Unbreak wpi::Format on Windows (#3242)
2e07902d7 [glass] NTField2D: Fix name lookup (#3233)
6e23e1840 [wpilibc] Remove WPILib.h (#3235)
3e22e4506 [wpilib] Make KoP drivetrain simulation weight 60 lbs (#3228)
79d1bd6c8 [glass] NetworkTablesSetting: Allow disable of server option (#3227)
fe341a16f [examples] Use more logical elevator setpoints in GearsBot (#3198)
62abf46b3 [glass] NetworkTablesSettings: Don't block GUI (#3226)
a95a5e0d9 [glass] Move NetworkTablesSettings to libglassnt (#3224)
d6f6ceaba [build] Run Spotless formatter (NFC) (#3221)
0922f8af5 [commands] CommandScheduler.requiring(): Note return can be null (NFC) (#2934)
6812302ff [examples] Make DriveDistanceOffboard example work in sim (#3199)
f3f86b8e7 [wpimath] Add pose estimator overload for vision + std dev measurement (#3200)
1a2680b9e [wpilibj] Change CommandBase.withName() to return CommandBase (#3209)
435bbb6a8 [command] RamseteCommand: Output 0 if interrupted (#3216)
3cf44e0a5 [hal] Add function for changing HAL Notifier thread priority (#3218)
40b367513 [wpimath] Units.java: Add kg-lb conversions (#3203)
9f563d584 [glass] NT: Fix return value in StringToDoubleArray (#3208)
af4adf537 [glass] Auto-size plots to fit window (#3193)
2560146da [sim] GUI: Add option to show prefix in Other Devices (#3186)
eae3a6397 gitignore: Ignore .cache directory (#3196)
959611420 [wpilib] Require non-zero positive value for PIDController.period (#3175)
9522f2e8c [wpimath] Add methods to concatenate trajectories (#3139)
e42a0b6cf [wpimath] Rotation2d comment formatting (NFC) (#3162)
d1c7032de [wpimath] Fix order of setting gyro offset in pose estimators (#3176)
d241bc81a [sim] Add DoubleSolenoidSim and SolenoidSim classes (#3177)
cb7f39afa [wpilibc] Add RobotController::GetBatteryVoltage() to C++ (#3179)
99b5ad9eb [wpilibj] Fix warnings that are not unused variables or deprecation (#3161)
c14b23775 [build] Fixup doxygen generated include dirs to match what users would need (#3154)
d447c7dc3 [sim] Add SimDeviceSim ctor overloads (#3134)
247420c9c [build] Remove jcenter repo (#3157)
04b112e00 [build] Include debug info in plugin published artifacts (#3149)
be0ce9900 [examples] Use PWMSparkMax instead of PWMVictorSPX (#3156)
69e8d0b65 [wpilib] Move RomiGyro into main wpilibc/j (#3143)
94e685e1b [wpimath] Add custom residual support to EKF (#3148)
5899f3dd2 [sim] GUI: Make keyboard settings loading more robust (#3167)
f82aa1d56 [wpilib] Fix HolonomicDriveController atReference() behavior (#3163)
fe5c2cf4b [wpimath] Remove ControllerUtil.java (#3169)
43d40c6e9 [wpiutil] Suppress unchecked cast in CombinedRuntimeLoader (#3155)
3d44d8f79 [wpimath] Fix argument order in UKF docs (NFC) (#3147)
ba6fe8ff2 [cscore] Add USB camera change event (#3123)
533725888 [build] Tweak OpenCV cmake search paths to work better on Linux (#3144)
29bf9d6ef [cscore] Add polled support to listener
483beb636 [ntcore] Move CallbackManager to wpiutil
fdaec7759 [examples] Instantiate m_ramseteController in example (#3142)
8494a5761 Rename default branch to main (#3140)
45590eea2 [wpigui] Hardcode window scale to 1 on macOS (#3135)
834a64920 [build] Publish libglass and libglassnt to Maven (#3127)
2c2ccb361 [wpimath] Fix Rotation2d equality operator (#3128)
fb5c8c39a [wpigui] clang-tidy: readability-braces-around-statements
f7d39193a [wpigui] Fix copyright in pfd and wpigui_metal.mm
aec796b21 [ntcore] Fix conditional jump on uninitialized value (#3125)
fb13bb239 [sim] GUI: Add right click popup for keyboard joystick settings (#3119)
c517ec677 [build] Update thirdparty-imgui to 1.79-2 (#3118)
e8cbf2a71 [wpimath] Fix typo in SwerveDrivePoseEstimator doc (NFC) (#3112)
e9c86df46 [wpimath] Add tests for swerve module optimization (#3100)
6ba8c289c [examples] Remove negative of ArcadeDrive(fwd, ..) in the C++ Getting Started Example (#3102)
3f1672e89 [hal] Add SimDevice createInt() and createLong() (#3110)
15be5cbf1 [examples] Fix segfault in GearsBot C++ example (#3111)
4cf0e5e6d Add quick links to API documentation in README (#3082)
6b1898f12 Fix RT priority docs (NFC) (#3098)
b3426e9c0 [wpimath] Fix missing whitespace in pose estimator doc (#3097)
38c1a1f3e [examples] Fix feildRelative -> fieldRelative typo in XControllerCommand examples (#3104)
4488e25f1 [glass] Shorten SmartDashboard window names (#3096)
cfdb3058e [wpilibj] Update SimDeviceSimTest (#3095)
64adff5fe [examples] Fix typo in ArcadeDrive constructor parameter name (#3092)
6efc58e3d [build] Fix issues with build on windows, deprecations, and native utils (#3090)
f393989a5 [wpimath, wpiutil] Add wpi::array for compile time size checking (#3087)
d6ed20c1e [build] Set macOS deployment target to 10.14 (#3088)
7c524014c [hal] Add [[nodiscard]] to HAL_WaitForNotifierAlarm() (#3085)
406d055f0 [wpilib] Fixup wouldHitLowerLimit in elevator and arm simulation classes. (#3076)
04a90b5dd [examples] Don't continually set setpoint in PotentiometerPID Examples (#3084)
8c5bfa013 [sim] GUI: Add max value setting for keyboard joysticks (#3083)
bc80c5535 [hal] Add SimValue reset() function (#3064)
9c3b51ca0 [wpilib] Document simulation APIs (#3079)
26584ff14 [wpimath] Add model description to LinearSystemId Javadocs (#3080)
42c3d5286 [examples] Sync Java and C++ trajectories in sim example (#3081)
64e72f710 [wpilibc] Add missing function RoboRioSim::ResetData (#3073)
e95503798 [wpimath] Add optimize() to SwerveModuleState (#3065)
fb99910c2 [hal] Add SimInt and SimLong wrappers for int/long SimValue (#3066)
e620bd4d3 [doc] Add machine-readable websocket specification (#3059)
a44e761d9 [glass] Add support for plot Y axis labels
ea1974d57 [wpigui] Update imgui and implot to latest
85a0bd43c [wpimath] Add RKF45 integration (#3047)
278e0f126 [glass] Use .controllable to set widgets' read-only state (#3035)
d8652cfd4 [wpimath] Make Java DCMotor API consistent with C++ and fix motor calcs (#3046)
377b7065a [build] Add toggleOffOn to Java spotless (#3053)
1e9c79c58 [sim] Use plant output to retrieve simulated position (#3043)
78147aa34 [sim] GUI: Fix Keyboard Joystick (#3052)
cd4a2265b [ntcore] Fix NetworkTableEntry::GetRaw() (#3051)
767ac1de1 [build] Use deploy key for doc publish (#3048)
d762215d1 [build] Add publish documentation script (#3040)
1fd09593c [examples] Add missing TestInit method to GettingStarted Example (#3039)
e45a0f6ce [examples] Add RomiGyro to the Romi Reference example (#3037)
94f852572 Update imaging link and fix typo (#3038)
d73cf64e5 [examples] Update RomiReference to match motor directions (#3036)
f945462ba Bump copyright year to 2021 (#3033)
b05946175 [wpimath] Catch Drake JNI exceptions and rethrow them (#3032)
62f0f8190 [wpimath] Deduplicate angle modulus functions (#2998)
bf8c0da4b [glass] Add "About" popup with version number (#3031)
dfdd6b389 [build] Increase Gradle heap size in Gazebo build (#3028)
f5e0fc3e9 Finish clang-tidy cleanups (#3003)
d741101fe [sim] Revert accidental commit of WSProvider_PDP.h (#3027)
e1620799c [examples] Add C++ RomiReference example (#2969)
749c7adb1 [command] Fix use-after-free in CommandScheduler (#3024)
921a73391 [sim] Add WS providers for AddressableLED, PCM, and Solenoid (#3026)
26d0004fe [build] Split Actions into different yml files (#3025)
948af6d5b [wpilib] PWMSpeedController.get(): Apply Inversion (#3016)
670a187a3 [wpilibc] SuppliedValueWidget.h: Forward declare ShuffleboardContainer (#3021)
be9f72502 [ntcore] NetworkTableValue: Use std::forward instead of std::move (#3022)
daf3f4cb1 [cscore] cscore_raw_cv.h: Fix error in PutFrame() (#3019)
5acda4cc7 [wpimath] ElevatorFeedforward.h: Add time.h include
8452af606 [wpimath] units/radiation.h: Add mass.h include
630d44952 [hal] ErrorsInternal.h: Add stdint.h include
7372cf7d9 [cscore] Windows NetworkUtil.cpp: Add missing include
b7e46c558 Include .h from .inc/.inl files (NFC) (#3017)
bf8f8710e [examples] Update Romi template and example (#2996)
6ffe5b775 [glass] Ensure NetworkTableTree parent context menu has an id (#3015)
be0805b85 [build] Update to WPILibVersioningPlugin 4.1.0 (#3014)
65b2359b2 [build] Add spotless for other files (#3007)
8651aa73e [examples] Enable NT Flush in Field2d examples (#3013)
78b542737 [build] Add Gazebo build to Actions CI (#3004)
fccf86532 [sim] DriverStationGui: Fix two bugs (#3010)
185741760 [sim] WSProvider_Joystick: Fix off-by-1 in incoming buttons (#3011)
ee7114a58 [glass] Add drive class widgets (#2975)
00fa91d0d [glass] Use ImGui style for gyro widget colors (#3009)
b7a25bfc3 ThirdPartyNotices: Add portable file dialogs license (#3005)
a2e46b9a1 [glass] modernize-use-nullptr (NFC) (#3006)
a751fa22d [build] Apply spotless for java formatting (#1768)
e563a0b7d [wpimath] Make LinearSystemLoop move-constructible and move-assignable (#2967)
49085ca94 [glass] Add context menus to remove and add NetworkTables values (#2979)
560a850a2 [glass] Add NetworkTables Log window (#2997)
66782e231 [sim] Create Left/Right drivetrain current accessors (#3001)
b60eb1544 clang-tidy: bugprone-virtual-near-miss
cbe59fa3b clang-tidy: google-explicit-constructor
c97c6dc06 clang-tidy: google-readability-casting (NFC)
32fa97d68 clang-tidy: modernize-use-nullptr (NFC)
aee460326 clang-tidy: modernize-pass-by-value
29c7da5f1 clang-tidy: modernize-make-unique
6131f4e32 clang-tidy: modernize-concat-nested-namespaces (NFC)
67e03e625 clang-tidy: modernize-use-equals-default
b124f9101 clang-tidy: modernize-use-default-member-init
d11a3a638 clang-tidy: modernize-use-override (NFC)
4cc0706b0 clang-tidy: modernize-use-using (NFC)
885f5a978 [wpilibc] Speed up ScopedTracerTest (#2999)
60b596457 [wpilibj] Fix typos (NFC) (#3000)
6e1919414 [build] Bring naming checkstyle rules up to date with Google Style guide (#1781)
8c8ec5e63 [wpilibj] Suppress unchecked cast warnings (#2995)
b8413ddd5 [wpiutil] Add noexcept to timestamp static functions (#2994)
5d976b6e1 [glass] Load NetworkTableView settings on first draw (#2993)
2b4317452 Replace NOLINT(runtime/explicit) comments with NOLINT (NFC) (#2992)
1c3011ba4 [glass] Fix handling of "/" NetworkTables key (#2991)
574a42f3b [hal] Fix UnsafeManipulateDIO status check (#2987)
9005cd59e [wpilib] Clamp input voltage in sim classes (#2955)
dd494d4ab [glass] NetworkTablesModel::Update(): Avoid use-after-move (#2988)
7cca469a1 [wpimath] NormalizeAngle: Make inline, remove unnamed namespace (#2986)
2aed432b4 Add braces to C++ single-line loops and conditionals (NFC) (#2973)
0291a3ff5 [wpiutil] StringRef: Add noexcept to several constructors (#2984)
5d7315280 [wpimath] Update UnitsTest.cpp copyright (#2985)
254931b9a [wpimath] Remove LinearSystem from LinearSystemLoop (#2968)
aa89744c9 Update OtherVersions.md to include wpimath info (#2983)
1cda3f5ad [glass] Fix styleguide (#2976)
8f1f64ffb Remove year from file copyright message (NFC) (#2972)
2bc0a7795 [examples] Fix wpiformat warning about utility include (#2971)
4204da6ad [glass] Add application icon
7ac39b10f [wpigui] Add icon support
6b567e006 [wpimath] Add support for varying vision standard deviations in pose estimators (#2956)
df299d6ed [wpimath] Add UnscentedKalmanFilter::Correct() overload (#2966)
4e34f0523 [examples] Use ADXRS450_GyroSim class in simulation example (#2964)
9962f6fd7 [wpilib] Give Field2d a default Sendable name (#2953)
f9d492f4b [sim] GUI: Show "Other Devices" window by default (#2961)
a8bb2ef1c [sim] Fix ADXRS450_GyroSim and DutyCycleEncoderSim (#2963)
240c629cd [sim] Try to guess "Map Gamepad" setting (#2960)
952567dd3 [wpilibc] Add missing move constructors and assignment operators (#2959)
10b396b4c [sim] Various WebSockets fixes and enhancements (#2952)
699bbe21a [examples] Fix comments in Gearsbot to match implementation (NFC) (#2957)
27b67deca [glass] Add more widgets (#2947)
581b7ec55 [wpilib] Add option to flush NetworkTables every iterative loop
acfbb1a44 [ntcore] DispatcherBase::Flush: Use wpi::Now()
d85a6d8fe [ntcore] Reduce limit on flush and update rate to 5 ms
20fbb5c63 [sim] Fix stringop truncation warning from GCC 10 (#2945)
1051a06a7 [glass] Show NT timestamps in seconds (#2944)
98dfc2620 [glass] Fix plots (#2943)
1ba0a2ced [sim] GUI: Add keyboard virtual joystick support (#2940)
4afb13f98 [examples] Replace M_PI with wpi::math::pi (#2938)
b27d33675 [examples] Enhance Romi templates (#2931)
00b9ae77f [sim] Change default WS port number to 3300 (#2932)
65219f309 [examples] Update Field2d position in periodic() (#2928)
f78d1d434 [sim] Process WS Encoder reset internally (#2927)
941edca59 [hal] Add Java SimDeviceDataJNI.getSimDeviceName (#2924)
a699435ed [wpilibj] Fix FlywheelSim argument order in constructor (#2922)
66d641718 [examples] Add tasks to run Java examples (#2920)
558e37c41 [examples] Add simple differential drive simulation example (#2918)
4f40d991e [glass] Switch name of Glass back to glass (#2919)
549af9900 [build] Update native-utils to 2021.0.6 (#2914)
b33693009 [glass] Change basename of glass to Glass (#2915)
c9a0edfb8 [glass] Package macOS application bundle
2c5668af4 [wpigui] Add platform-specific preferences save
751dea32a [wpilibc] Try to work around ABI break introduced in #2901 (#2917)
cd8f4bfb1 [build] Package up msvc runtime into maven artifact (#2913)
a6cfcc686 [wpilibc] Move SendableChooser Doxygen comments to header (NFC) (#2911)
b8c4f603d [wpimath] Upgrade to Eigen 3.3.9 (#2910)
0075e4b39 [wpilibj] Fix NPE in Field2d (#2909)
125af556c [simulation] Fix halsim_gui ntcore and wpiutil deps (#2908)
963ad5c25 [wpilib] Add noise to Differential Drive simulator (#2903)
387f56cb7 [examples] Add Romi reference Java example and templates (#2905)
b3deda38c [examples] Zero motors on disabledInit() in sim physics examples (#2906)
2a5ca7745 [glass] Add glass: an application for display of robot data
727940d84 [wpilib] Move Field2d to SmartDashboard
8cd42478e [wpilib] SendableBuilder: Make GetTable() visible
c11d34b26 [command] Use addCommands in command group templates (#2900)
339d7445b [sim] Add HAL hooks for simulationPeriodic (#2881)
d16f05f2c [wpilib] Fix SmartDashboard update order (#2896)
5427b32a4 [wpiutil] unique_function: Restrict implicit conversion (#2899)
f73701239 [ntcore] Add missing SetDefault initializer_list functions (#2898)
f5a6fc070 [sim] Add initialized flag for all solenoids on a PCM (#2897)
bdf5ba91a [wpilibj] Fix typo in ElevatorSim (#2895)
bc8f33877 [wpilib] Add pose estimators (#2867)
3413bfc06 [wpilib] PIDController: Recompute the error in AtSetpoint() (#2822)
2056f0ce0 [wpilib] Fix bugs in Hatchbot examples (#2893)
5eb8cfd69 [wpilibc] Fix MatchDataSender (#2892)
e6a425448 [build] Delete test folders after tests execute (#2891)
d478ad00d [imgui] Allow usage of imgui_stdlib (#2889)
53eda861d [build] Add unit-testing infrastructure to examples (#2863)
cc1d86ba6 [sim] Add title to simulator GUI window (#2888)
f0528f00e [build] CMake: Use project-specific binary and source dirs (#2886)
5cd2ad124 [wpilibc] Add Color::operator!= (#2887)
6c00e7a90 [build] CI CMake: build with GUI enabled (#2884)
53170bbb5 Update roboRIO toolchain installation instructions (#2883)
467258e05 [sim] GUI: Add option to not zero disconnected joysticks (#2876)
129be23c9 Clarify JDK installation instructions in readme (#2882)
8e9290e86 [build] Add separate CMake setting for wpimath (#2885)
7cf5bebf8 [wpilibj] Cache NT writes from DriverStation (#2780)
f7f9087fb [command] Fix timing issue in RamseteCommand (#2871)
256e7904f [wpilibj] SimDeviceSim: Fix sim value changed callback (#2880)
c8ea1b6c3 [wpilib] Add function to adjust LQR controller gain for pure time delay (#2878)
2816b06c0 [sim] HAL_GetControlWord: Fully zero output (#2873)
4c695ea08 Add toolchain installation instructions to README (#2875)
a14d51806 [wpimath] DCMotor: fix doc typo (NFC) (#2868)
017097791 [build] CMake: build sim extensions as shared libs (#2866)
f61726b5a [build] Fix cmake-config files (#2865)
fc27fdac5 [wpilibc] Cache NT values from driver station (#2768)
47c59859e [sim] Make SimDevice callbacks synchronous (#2861)
6e76ab9c0 [build] Turn on WITH_GUI for Windows cmake CI
5f78b7670 [build] Set GLFW_INSTALL to OFF
5e0808c84 [wpigui] Fix Windows cmake build
508f05a47 [imgui] Fix typo in Windows CMake target sources

Change-Id: I1737b45965f31803a96676bedc7dc40e337aa321
git-subtree-dir: third_party/allwpilib
git-subtree-split: e473a00f9785f9949e5ced30901baeaf426d2fc9
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/wpiutil/src/dev/java/edu/wpi/first/util/DevMain.java b/wpiutil/src/dev/java/edu/wpi/first/util/DevMain.java
new file mode 100644
index 0000000..3d5b134
--- /dev/null
+++ b/wpiutil/src/dev/java/edu/wpi/first/util/DevMain.java
@@ -0,0 +1,15 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util;
+
+public final class DevMain {
+  /** Main entry point. */
+  public static void main(String[] args) {
+    System.out.println("Hello World!");
+    System.out.println(RuntimeDetector.getPlatformPath());
+  }
+
+  private DevMain() {}
+}
diff --git a/wpiutil/src/dev/java/edu/wpi/first/wpiutil/DevMain.java b/wpiutil/src/dev/java/edu/wpi/first/wpiutil/DevMain.java
deleted file mode 100644
index c5f1754..0000000
--- a/wpiutil/src/dev/java/edu/wpi/first/wpiutil/DevMain.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-package edu.wpi.first.wpiutil;
-
-public final class DevMain {
-  /**
-   * Main entry point.
-   */
-  public static void main(String[] args) {
-    System.out.println("Hello World!");
-    System.out.println(RuntimeDetector.getPlatformPath());
-  }
-
-  private DevMain() {
-  }
-}
diff --git a/wpiutil/src/dev/native/cpp/main.cpp b/wpiutil/src/dev/native/cpp/main.cpp
index 9ac3bab..14ddda2 100644
--- a/wpiutil/src/dev/native/cpp/main.cpp
+++ b/wpiutil/src/dev/native/cpp/main.cpp
@@ -1,17 +1,11 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
-#include <iostream>
-
-#include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
-#include "wpi/hostname.h"
+#include "fmt/core.h"
+#include "wpi/SmallString.h"
 
 int main() {
-  wpi::StringRef v1("Hello");
-  std::cout << v1.lower() << std::endl;
+  wpi::SmallString<128> v1("Hello");
+  fmt::print("{}\n", v1.str());
 }
diff --git a/wpiutil/src/main/java/edu/wpi/first/wpiutil/CircularBuffer.java b/wpiutil/src/main/java/edu/wpi/first/util/CircularBuffer.java
similarity index 84%
rename from wpiutil/src/main/java/edu/wpi/first/wpiutil/CircularBuffer.java
rename to wpiutil/src/main/java/edu/wpi/first/util/CircularBuffer.java
index 5325b26..f9129c7 100644
--- a/wpiutil/src/main/java/edu/wpi/first/wpiutil/CircularBuffer.java
+++ b/wpiutil/src/main/java/edu/wpi/first/util/CircularBuffer.java
@@ -1,15 +1,10 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
-package edu.wpi.first.wpiutil;
+package edu.wpi.first.util;
 
-/**
- * This is a simple circular buffer so we don't need to "bucket brigade" copy old values.
- */
+/** This is a simple circular buffer so we don't need to "bucket brigade" copy old values. */
 public class CircularBuffer {
   private double[] m_data;
 
@@ -66,6 +61,8 @@
   /**
    * Push new value onto front of the buffer. The value at the back is overwritten if the buffer is
    * full.
+   *
+   * @param value The value to push.
    */
   public void addFirst(double value) {
     if (m_data.length == 0) {
@@ -84,6 +81,8 @@
   /**
    * Push new value onto back of the buffer. The value at the front is overwritten if the buffer is
    * full.
+   *
+   * @param value The value to push.
    */
   public void addLast(double value) {
     if (m_data.length == 0) {
@@ -117,9 +116,10 @@
     return temp;
   }
 
-
   /**
    * Pop value at back of buffer.
+   *
+   * @return value at back of buffer
    */
   public double removeLast() {
     // If there are no elements in the buffer, do nothing
@@ -135,6 +135,8 @@
    * Resizes internal buffer to given size.
    *
    * <p>A new buffer is allocated because arrays are not resizable.
+   *
+   * @param size New buffer size.
    */
   void resize(int size) {
     double[] newBuffer = new double[size];
@@ -146,9 +148,7 @@
     m_front = 0;
   }
 
-  /**
-   * Sets internal buffer contents to zero.
-   */
+  /** Sets internal buffer contents to zero. */
   public void clear() {
     for (int i = 0; i < m_data.length; i++) {
       m_data[i] = 0.0;
@@ -160,6 +160,7 @@
   /**
    * Get the element at the provided index relative to the start of the buffer.
    *
+   * @param index Index into the buffer.
    * @return Element at index starting from front of buffer.
    */
   public double get(int index) {
@@ -168,6 +169,8 @@
 
   /**
    * Increment an index modulo the length of the m_data buffer.
+   *
+   * @param index Index into the buffer.
    */
   private int moduloInc(int index) {
     return (index + 1) % m_data.length;
@@ -175,6 +178,8 @@
 
   /**
    * Decrement an index modulo the length of the m_data buffer.
+   *
+   * @param index Index into the buffer.
    */
   private int moduloDec(int index) {
     if (index == 0) {
diff --git a/wpiutil/src/main/java/edu/wpi/first/wpiutil/CombinedRuntimeLoader.java b/wpiutil/src/main/java/edu/wpi/first/util/CombinedRuntimeLoader.java
similarity index 75%
rename from wpiutil/src/main/java/edu/wpi/first/wpiutil/CombinedRuntimeLoader.java
rename to wpiutil/src/main/java/edu/wpi/first/util/CombinedRuntimeLoader.java
index 8f0d53e..9b23144 100644
--- a/wpiutil/src/main/java/edu/wpi/first/wpiutil/CombinedRuntimeLoader.java
+++ b/wpiutil/src/main/java/edu/wpi/first/util/CombinedRuntimeLoader.java
@@ -1,12 +1,11 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
-package edu.wpi.first.wpiutil;
+package edu.wpi.first.util;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -17,12 +16,8 @@
 import java.util.Map;
 import java.util.Objects;
 
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 public final class CombinedRuntimeLoader {
-  private CombinedRuntimeLoader() {
-  }
+  private CombinedRuntimeLoader() {}
 
   private static String extractionDirectory;
 
@@ -38,32 +33,34 @@
 
   private static String getLoadErrorMessage(String libraryName, UnsatisfiedLinkError ule) {
     StringBuilder msg = new StringBuilder(512);
-    msg.append(libraryName).append(" could not be loaded from path\n"
-        + "\tattempted to load for platform ")
+    msg.append(libraryName)
+        .append(" could not be loaded from path\n" + "\tattempted to load for platform ")
         .append(RuntimeDetector.getPlatformPath())
         .append("\nLast Load Error: \n")
         .append(ule.getMessage())
         .append('\n');
     if (RuntimeDetector.isWindows()) {
-      msg.append("A common cause of this error is missing the C++ runtime.\n"
-          + "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
+      msg.append(
+          "A common cause of this error is missing the C++ runtime.\n"
+              + "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
     }
     return msg.toString();
   }
 
   /**
    * Extract a list of native libraries.
-   * @param <T>             The class where the resources would be located
-   * @param clazz           The actual class object
-   * @param resourceName    The resource name on the classpath to use for file lookup
-   * @return                List of all libraries that were extracted
-   * @throws IOException    Thrown if resource not found or file could not be extracted
+   *
+   * @param <T> The class where the resources would be located
+   * @param clazz The actual class object
+   * @param resourceName The resource name on the classpath to use for file lookup
+   * @return List of all libraries that were extracted
+   * @throws IOException Thrown if resource not found or file could not be extracted
    */
-  @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UnnecessaryCastRule"})
+  @SuppressWarnings({"PMD.UnnecessaryCastRule", "unchecked"})
   public static <T> List<String> extractLibraries(Class<T> clazz, String resourceName)
       throws IOException {
-    TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {
-    };
+    TypeReference<HashMap<String, Object>> typeRef =
+        new TypeReference<HashMap<String, Object>>() {};
     ObjectMapper mapper = new ObjectMapper();
     Map<String, Object> map;
     try (var stream = clazz.getResourceAsStream(resourceName)) {
@@ -103,7 +100,11 @@
         if (outputFile.toFile().exists()) {
           continue;
         }
-        outputFile.getParent().toFile().mkdirs();
+        var parent = outputFile.getParent();
+        if (parent == null) {
+          throw new IOException("Output file has no parent");
+        }
+        parent.toFile().mkdirs();
 
         try (var os = Files.newOutputStream(outputFile)) {
           int readBytes;
@@ -119,6 +120,7 @@
 
   /**
    * Load a single library from a list of extracted files.
+   *
    * @param libraryName The library name to load
    * @param extractedFiles The extracted files to search
    * @throws IOException If library was not found
@@ -153,8 +155,8 @@
   /**
    * Load a list of native libraries out of a single directory.
    *
-   * @param <T>             The class where the resources would be located
-   * @param clazz           The actual class object
+   * @param <T> The class where the resources would be located
+   * @param clazz The actual class object
    * @param librariesToLoad List of libraries to load
    * @throws IOException Throws an IOException if not found
    */
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/ErrorMessages.java b/wpiutil/src/main/java/edu/wpi/first/util/ErrorMessages.java
new file mode 100644
index 0000000..9292ed9
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/ErrorMessages.java
@@ -0,0 +1,41 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util;
+
+import static java.util.Objects.requireNonNull;
+
+/** Utility class for common WPILib error messages. */
+public final class ErrorMessages {
+  /** Utility class, so constructor is private. */
+  private ErrorMessages() {
+    throw new UnsupportedOperationException("This is a utility class!");
+  }
+
+  /**
+   * Requires that a parameter of a method not be null; prints an error message with helpful
+   * debugging instructions if the parameter is null.
+   *
+   * @param <T> Type of object.
+   * @param obj The parameter that must not be null.
+   * @param paramName The name of the parameter.
+   * @param methodName The name of the method.
+   * @return The object parameter confirmed not to be null.
+   */
+  public static <T> T requireNonNullParam(T obj, String paramName, String methodName) {
+    return requireNonNull(
+        obj,
+        "Parameter "
+            + paramName
+            + " in method "
+            + methodName
+            + " was null when it"
+            + " should not have been!  Check the stacktrace to find the responsible line of code - "
+            + "usually, it is the first line of user-written code indicated in the stacktrace.  "
+            + "Make sure all objects passed to the method in question were properly initialized -"
+            + " note that this may not be obvious if it is being called under "
+            + "dynamically-changing conditions!  Please do not seek additional technical assistance"
+            + " without doing this first!");
+  }
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/wpiutil/RuntimeDetector.java b/wpiutil/src/main/java/edu/wpi/first/util/RuntimeDetector.java
similarity index 81%
rename from wpiutil/src/main/java/edu/wpi/first/wpiutil/RuntimeDetector.java
rename to wpiutil/src/main/java/edu/wpi/first/util/RuntimeDetector.java
index b4c07b7..320bc5b 100644
--- a/wpiutil/src/main/java/edu/wpi/first/wpiutil/RuntimeDetector.java
+++ b/wpiutil/src/main/java/edu/wpi/first/util/RuntimeDetector.java
@@ -1,11 +1,8 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
-package edu.wpi.first.wpiutil;
+package edu.wpi.first.util;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -18,13 +15,11 @@
   private static String fileExtension;
   private static String filePath;
 
-  @SuppressWarnings("PMD.CyclomaticComplexity")
   private static synchronized void computePlatform() {
     if (fileExtension != null && filePath != null && filePrefix != null) {
       return;
     }
 
-
     boolean intel32 = is32BitIntel();
     boolean intel64 = is64BitIntel();
 
@@ -67,6 +62,8 @@
 
   /**
    * Get the file prefix for the current system.
+   *
+   * @return The file prefix.
    */
   public static synchronized String getFilePrefix() {
     computePlatform();
@@ -76,6 +73,8 @@
 
   /**
    * Get the file extension for the current system.
+   *
+   * @return The file extension.
    */
   public static synchronized String getFileExtension() {
     computePlatform();
@@ -85,6 +84,8 @@
 
   /**
    * Get the platform path for the current system.
+   *
+   * @return The platform path.
    */
   public static synchronized String getPlatformPath() {
     computePlatform();
@@ -94,6 +95,9 @@
 
   /**
    * Get the path to the requested resource.
+   *
+   * @param libName Library name.
+   * @return The path to the requested resource.
    */
   public static synchronized String getLibraryResource(String libName) {
     computePlatform();
@@ -103,6 +107,9 @@
 
   /**
    * Get the path to the hash to the requested resource.
+   *
+   * @param libName Library name.
+   * @return The path to the hash to the requested resource.
    */
   public static synchronized String getHashLibraryResource(String libName) {
     computePlatform();
@@ -110,25 +117,35 @@
     return filePath + libName + ".hash";
   }
 
+  /**
+   * Check if hardware platform is Athena.
+   *
+   * @return True if hardware platform is Athena.
+   */
   public static boolean isAthena() {
     File runRobotFile = new File("/usr/local/frc/bin/frcRunRobot.sh");
     return runRobotFile.exists();
   }
 
-  /** check if os is raspbian.
+  /**
+   * Check if OS is Raspbian.
    *
-   * @return if os is raspbian
+   * @return True if OS is Raspbian.
    */
   public static boolean isRaspbian() {
     try (BufferedReader reader = Files.newBufferedReader(Paths.get("/etc/os-release"))) {
       String value = reader.readLine();
+      if (value == null) {
+        return false;
+      }
       return value.contains("Raspbian");
     } catch (IOException ex) {
       return false;
     }
   }
 
-  /** check if architecture is aarch64.
+  /**
+   * check if architecture is aarch64.
    *
    * @return if architecture is aarch64
    */
@@ -158,7 +175,5 @@
     return "amd64".equals(arch) || "x86_64".equals(arch);
   }
 
-  private RuntimeDetector() {
-
-  }
+  private RuntimeDetector() {}
 }
diff --git a/wpiutil/src/main/java/edu/wpi/first/wpiutil/RuntimeLoader.java b/wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java
similarity index 75%
rename from wpiutil/src/main/java/edu/wpi/first/wpiutil/RuntimeLoader.java
rename to wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java
index 21193a2..8a2c11a 100644
--- a/wpiutil/src/main/java/edu/wpi/first/wpiutil/RuntimeLoader.java
+++ b/wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java
@@ -1,11 +1,8 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
-package edu.wpi.first.wpiutil;
+package edu.wpi.first.util;
 
 import java.io.File;
 import java.io.IOException;
@@ -25,6 +22,8 @@
 
   /**
    * Gets the default extration root location (~/.wpilib/nativecache).
+   *
+   * @return The default extraction root location.
    */
   public static synchronized String getDefaultExtractionRoot() {
     if (defaultExtractionRoot != null) {
@@ -42,8 +41,9 @@
   /**
    * Creates a new library loader.
    *
-   * <p>Resources loaded on disk from extractionRoot, and from classpath from the
-   * passed in class. Library name is the passed in name.
+   * @param libraryName Name of library to load.
+   * @param extractionRoot Location from which to load the library.
+   * @param cls Class whose classpath the given library belongs.
    */
   public RuntimeLoader(String libraryName, String extractionRoot, Class<T> cls) {
     m_libraryName = libraryName;
@@ -51,24 +51,34 @@
     m_extractionRoot = extractionRoot;
   }
 
+  /**
+   * Returns a load error message given the information in the provided UnsatisfiedLinkError.
+   *
+   * @param ule UnsatisfiedLinkError object.
+   * @return A load error message.
+   */
   private String getLoadErrorMessage(UnsatisfiedLinkError ule) {
     StringBuilder msg = new StringBuilder(512);
     msg.append(m_libraryName)
-       .append(" could not be loaded from path or an embedded resource.\n"
-               + "\tattempted to load for platform ")
-       .append(RuntimeDetector.getPlatformPath())
-       .append("\nLast Load Error: \n")
-       .append(ule.getMessage())
-       .append('\n');
+        .append(
+            " could not be loaded from path or an embedded resource.\n"
+                + "\tattempted to load for platform ")
+        .append(RuntimeDetector.getPlatformPath())
+        .append("\nLast Load Error: \n")
+        .append(ule.getMessage())
+        .append('\n');
     if (RuntimeDetector.isWindows()) {
-      msg.append("A common cause of this error is missing the C++ runtime.\n"
-                 + "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
+      msg.append(
+          "A common cause of this error is missing the C++ runtime.\n"
+              + "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
     }
     return msg.toString();
   }
 
   /**
    * Loads a native library.
+   *
+   * @throws IOException if the library fails to load
    */
   @SuppressWarnings("PMD.PreserveStackTrace")
   public void loadLibrary() throws IOException {
@@ -95,7 +105,13 @@
               if (resIs == null) {
                 throw new IOException(getLoadErrorMessage(ule));
               }
-              jniLibrary.getParentFile().mkdirs();
+
+              var parentFile = jniLibrary.getParentFile();
+              if (parentFile == null) {
+                throw new IOException("JNI library has no parent file");
+              }
+              parentFile.mkdirs();
+
               try (OutputStream os = Files.newOutputStream(jniLibrary.toPath())) {
                 byte[] buffer = new byte[0xFFFF]; // 64K copy buffer
                 int readBytes;
@@ -113,9 +129,10 @@
 
   /**
    * Load a native library by directly hashing the file.
+   *
+   * @throws IOException if the library failed to load
    */
-  @SuppressWarnings({"PMD.NPathComplexity", "PMD.PreserveStackTrace", "PMD.EmptyWhileStmt",
-                     "PMD.AvoidThrowingRawExceptionTypes", "PMD.CyclomaticComplexity"})
+  @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.EmptyWhileStmt"})
   public void loadLibraryHashed() throws IOException {
     try {
       // First, try loading path
@@ -160,7 +177,13 @@
           if (resIs == null) {
             throw new IOException(getLoadErrorMessage(ule));
           }
-          jniLibrary.getParentFile().mkdirs();
+
+          var parentFile = jniLibrary.getParentFile();
+          if (parentFile == null) {
+            throw new IOException("JNI library has no parent file");
+          }
+          parentFile.mkdirs();
+
           try (OutputStream os = Files.newOutputStream(jniLibrary.toPath())) {
             byte[] buffer = new byte[0xFFFF]; // 64K copy buffer
             int readBytes;
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java b/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java
new file mode 100644
index 0000000..b7b2f59
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java
@@ -0,0 +1,125 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public final class WPIUtilJNI {
+  static boolean libraryLoaded = false;
+  static RuntimeLoader<WPIUtilJNI> loader = null;
+
+  public static class Helper {
+    private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
+
+    public static boolean getExtractOnStaticLoad() {
+      return extractOnStaticLoad.get();
+    }
+
+    public static void setExtractOnStaticLoad(boolean load) {
+      extractOnStaticLoad.set(load);
+    }
+  }
+
+  static {
+    if (Helper.getExtractOnStaticLoad()) {
+      try {
+        loader =
+            new RuntimeLoader<>(
+                "wpiutiljni", RuntimeLoader.getDefaultExtractionRoot(), WPIUtilJNI.class);
+        loader.loadLibrary();
+      } catch (IOException ex) {
+        ex.printStackTrace();
+        System.exit(1);
+      }
+      libraryLoaded = true;
+    }
+  }
+
+  /**
+   * Force load the library.
+   *
+   * @throws IOException if the library failed to load
+   */
+  public static synchronized void forceLoad() throws IOException {
+    if (libraryLoaded) {
+      return;
+    }
+    loader =
+        new RuntimeLoader<>(
+            "wpiutiljni", RuntimeLoader.getDefaultExtractionRoot(), WPIUtilJNI.class);
+    loader.loadLibrary();
+    libraryLoaded = true;
+  }
+
+  public static native void enableMockTime();
+
+  public static native void setMockTime(long time);
+
+  public static native long now();
+
+  public static native void addPortForwarder(int port, String remoteHost, int remotePort);
+
+  public static native void removePortForwarder(int port);
+
+  public static native int createEvent(boolean manualReset, boolean initialState);
+
+  public static native void destroyEvent(int eventHandle);
+
+  public static native void setEvent(int eventHandle);
+
+  public static native void resetEvent(int eventHandle);
+
+  public static native int createSemaphore(int initialCount, int maximumCount);
+
+  public static native void destroySemaphore(int semHandle);
+
+  public static native boolean releaseSemaphore(int semHandle, int releaseCount);
+
+  /**
+   * Waits for an handle to be signaled.
+   *
+   * @param handle handle to wait on
+   * @throws InterruptedException on failure (e.g. object was destroyed)
+   */
+  public static native void waitForObject(int handle) throws InterruptedException;
+
+  /**
+   * Waits for an handle to be signaled, with timeout.
+   *
+   * @param handle handle to wait on
+   * @param timeout timeout in seconds
+   * @return True if timeout reached without handle being signaled
+   * @throws InterruptedException on failure (e.g. object was destroyed)
+   */
+  public static native boolean waitForObjectTimeout(int handle, double timeout)
+      throws InterruptedException;
+
+  /**
+   * Waits for one or more handles to be signaled.
+   *
+   * <p>Invalid handles are treated as signaled; the returned array will have the handle error bit
+   * set for any invalid handles.
+   *
+   * @param handles array of handles to wait on
+   * @return array of signaled handles
+   * @throws InterruptedException on failure (e.g. no objects were signaled)
+   */
+  public static native int[] waitForObjects(int[] handles) throws InterruptedException;
+
+  /**
+   * Waits for one or more handles to be signaled, with timeout.
+   *
+   * <p>Invalid handles are treated as signaled; the returned array will have the handle error bit
+   * set for any invalid handles.
+   *
+   * @param handles array of handles to wait on
+   * @param timeout timeout in seconds
+   * @return array of signaled handles; empty if timeout reached without any handle being signaled
+   * @throws InterruptedException on failure (e.g. no objects were signaled and no timeout)
+   */
+  public static native int[] waitForObjectsTimeout(int[] handles, double timeout)
+      throws InterruptedException;
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Event.java b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Event.java
new file mode 100644
index 0000000..bda2e88
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Event.java
@@ -0,0 +1,70 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.concurrent;
+
+import edu.wpi.first.util.WPIUtilJNI;
+
+/**
+ * An atomic signaling event for synchronization.
+ *
+ * <p>Events have binary state (signaled or not signaled) and may be either automatically reset or
+ * manually reset. Automatic-reset events go to non-signaled state when a waitForObject is woken up
+ * by the event; manual-reset events require reset() to be called to set the event to non-signaled
+ * state; if reset() is not called, any waiter on that event will immediately wake when called.
+ */
+public final class Event implements AutoCloseable {
+  /**
+   * Constructor.
+   *
+   * @param manualReset true for manual reset, false for automatic reset
+   * @param initialState true to make the event initially in signaled state
+   */
+  public Event(boolean manualReset, boolean initialState) {
+    m_handle = WPIUtilJNI.createEvent(manualReset, initialState);
+  }
+
+  /**
+   * Constructor. Initial state is false.
+   *
+   * @param manualReset true for manual reset, false for automatic reset
+   */
+  public Event(boolean manualReset) {
+    this(manualReset, false);
+  }
+
+  /** Constructor. Automatic reset, initial state is false. */
+  public Event() {
+    this(false, false);
+  }
+
+  @Override
+  public void close() {
+    if (m_handle != 0) {
+      WPIUtilJNI.destroyEvent(m_handle);
+      m_handle = 0;
+    }
+  }
+
+  /**
+   * Gets the event handle (e.g. for waitForObject).
+   *
+   * @return handle
+   */
+  public int getHandle() {
+    return m_handle;
+  }
+
+  /** Sets the event to signaled state. */
+  public void set() {
+    WPIUtilJNI.setEvent(m_handle);
+  }
+
+  /** Sets the event to non-signaled state. */
+  public void reset() {
+    WPIUtilJNI.resetEvent(m_handle);
+  }
+
+  private int m_handle;
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Semaphore.java b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Semaphore.java
new file mode 100644
index 0000000..4e9e85f
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Semaphore.java
@@ -0,0 +1,80 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.concurrent;
+
+import edu.wpi.first.util.WPIUtilJNI;
+
+/**
+ * A semaphore for synchronization.
+ *
+ * <p>Semaphores keep an internal counter. Releasing the semaphore increases the count. A semaphore
+ * with a non-zero count is considered signaled. When a waiter wakes up it atomically decrements the
+ * count by 1. This is generally useful in a single-supplier, multiple-consumer scenario.
+ */
+public final class Semaphore implements AutoCloseable {
+  /**
+   * Constructor.
+   *
+   * @param initialCount initial value for the semaphore's internal counter
+   * @param maximumCount maximum value for the samephore's internal counter
+   */
+  public Semaphore(int initialCount, int maximumCount) {
+    m_handle = WPIUtilJNI.createSemaphore(initialCount, maximumCount);
+  }
+
+  /**
+   * Constructor. Maximum count is Integer.MAX_VALUE.
+   *
+   * @param initialCount initial value for the semaphore's internal counter
+   */
+  public Semaphore(int initialCount) {
+    this(initialCount, Integer.MAX_VALUE);
+  }
+
+  /** Constructor. Initial count is 0, maximum count is Integer.MAX_VALUE. */
+  public Semaphore() {
+    this(0, Integer.MAX_VALUE);
+  }
+
+  @Override
+  public void close() {
+    if (m_handle != 0) {
+      WPIUtilJNI.destroySemaphore(m_handle);
+      m_handle = 0;
+    }
+  }
+
+  /**
+   * Gets the semaphore handle (e.g. for waitForObject).
+   *
+   * @return handle
+   */
+  public int getHandle() {
+    return m_handle;
+  }
+
+  /**
+   * Releases N counts of the semaphore.
+   *
+   * @param releaseCount amount to add to semaphore's internal counter; must be positive
+   * @return True on successful release, false on failure (e.g. release count would exceed maximum
+   *     value, or handle invalid)
+   */
+  public boolean release(int releaseCount) {
+    return WPIUtilJNI.releaseSemaphore(m_handle, releaseCount);
+  }
+
+  /**
+   * Releases 1 count of the semaphore.
+   *
+   * @return True on successful release, false on failure (e.g. release count would exceed maximum
+   *     value, or handle invalid)
+   */
+  public boolean release() {
+    return release(1);
+  }
+
+  private int m_handle;
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/function/BooleanConsumer.java b/wpiutil/src/main/java/edu/wpi/first/util/function/BooleanConsumer.java
new file mode 100644
index 0000000..98fd6a2
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/function/BooleanConsumer.java
@@ -0,0 +1,22 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.function;
+
+/**
+ * Represents an operation that accepts a single boolean-valued argument and returns no result. This
+ * is the primitive type specialization of {@link java.util.function.Consumer} for boolean. Unlike
+ * most other functional interfaces, BooleanConsumer is expected to operate via side-effects.
+ *
+ * <p>This is a functional interface whose functional method is {@link #accept(boolean)}.
+ */
+@FunctionalInterface
+public interface BooleanConsumer {
+  /**
+   * Performs this operation on the given argument.
+   *
+   * @param value the input argument
+   */
+  void accept(boolean value);
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/function/FloatConsumer.java b/wpiutil/src/main/java/edu/wpi/first/util/function/FloatConsumer.java
new file mode 100644
index 0000000..0fc7ff7
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/function/FloatConsumer.java
@@ -0,0 +1,22 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.function;
+
+/**
+ * Represents an operation that accepts a single float-valued argument and returns no result. This
+ * is the primitive type specialization of {@link java.util.function.Consumer} for float. Unlike
+ * most other functional interfaces, BooleanConsumer is expected to operate via side-effects.
+ *
+ * <p>This is a functional interface whose functional method is {@link #accept(float)}.
+ */
+@FunctionalInterface
+public interface FloatConsumer {
+  /**
+   * Performs this operation on the given argument.
+   *
+   * @param value the input argument
+   */
+  void accept(float value);
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/function/FloatSupplier.java b/wpiutil/src/main/java/edu/wpi/first/util/function/FloatSupplier.java
new file mode 100644
index 0000000..eb94b2b
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/function/FloatSupplier.java
@@ -0,0 +1,24 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.function;
+
+/**
+ * Represents a supplier of float-valued results.
+ *
+ * <p>This is the float-producing primitive specialization of {@link java.util.function.Supplier}.
+ *
+ * <p>There is no requirement that a distinct result be returned each time the supplier is invoked.
+ *
+ * <p>This is a functional interface whose functional method is {@link #getAsFloat()}.
+ */
+@FunctionalInterface
+public interface FloatSupplier {
+  /**
+   * Gets a result.
+   *
+   * @return a result
+   */
+  float getAsFloat();
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/net/PortForwarder.java b/wpiutil/src/main/java/edu/wpi/first/util/net/PortForwarder.java
new file mode 100644
index 0000000..5c4c20a
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/net/PortForwarder.java
@@ -0,0 +1,38 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.net;
+
+import edu.wpi.first.util.WPIUtilJNI;
+
+/**
+ * Forward ports to another host. This is primarily useful for accessing Ethernet-connected devices
+ * from a computer tethered to the RoboRIO USB port.
+ */
+public final class PortForwarder {
+  private PortForwarder() {
+    throw new UnsupportedOperationException("This is a utility class!");
+  }
+
+  /**
+   * Forward a local TCP port to a remote host and port. Note that local ports less than 1024 won't
+   * work as a normal user.
+   *
+   * @param port local port number
+   * @param remoteHost remote IP address / DNS name
+   * @param remotePort remote port number
+   */
+  public static void add(int port, String remoteHost, int remotePort) {
+    WPIUtilJNI.addPortForwarder(port, remoteHost, remotePort);
+  }
+
+  /**
+   * Stop TCP forwarding on a port.
+   *
+   * @param port local port number
+   */
+  public static void remove(int port) {
+    WPIUtilJNI.removePortForwarder(port);
+  }
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable/Sendable.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable/Sendable.java
new file mode 100644
index 0000000..ad1fe28
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable/Sendable.java
@@ -0,0 +1,15 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.sendable;
+
+/** The base interface for objects that can be sent over the network. */
+public interface Sendable {
+  /**
+   * Initializes this {@link Sendable} object.
+   *
+   * @param builder sendable builder
+   */
+  void initSendable(SendableBuilder builder);
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable/SendableBuilder.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable/SendableBuilder.java
new file mode 100644
index 0000000..35d98fa
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable/SendableBuilder.java
@@ -0,0 +1,127 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.sendable;
+
+import edu.wpi.first.util.function.BooleanConsumer;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.DoubleConsumer;
+import java.util.function.DoubleSupplier;
+import java.util.function.Supplier;
+
+public interface SendableBuilder {
+  /** The backend kinds used for the sendable builder. */
+  enum BackendKind {
+    kUnknown,
+    kNetworkTables
+  }
+
+  /**
+   * Set the string representation of the named data type that will be used by the smart dashboard
+   * for this sendable.
+   *
+   * @param type data type
+   */
+  void setSmartDashboardType(String type);
+
+  /**
+   * Set a flag indicating if this sendable should be treated as an actuator. By default this flag
+   * is false.
+   *
+   * @param value true if actuator, false if not
+   */
+  void setActuator(boolean value);
+
+  /**
+   * Set the function that should be called to set the Sendable into a safe state. This is called
+   * when entering and exiting Live Window mode.
+   *
+   * @param func function
+   */
+  void setSafeState(Runnable func);
+
+  /**
+   * Add a boolean property.
+   *
+   * @param key property name
+   * @param getter getter function (returns current value)
+   * @param setter setter function (sets new value)
+   */
+  void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter);
+
+  /**
+   * Add a double property.
+   *
+   * @param key property name
+   * @param getter getter function (returns current value)
+   * @param setter setter function (sets new value)
+   */
+  void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter);
+
+  /**
+   * Add a string property.
+   *
+   * @param key property name
+   * @param getter getter function (returns current value)
+   * @param setter setter function (sets new value)
+   */
+  void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter);
+
+  /**
+   * Add a boolean array property.
+   *
+   * @param key property name
+   * @param getter getter function (returns current value)
+   * @param setter setter function (sets new value)
+   */
+  void addBooleanArrayProperty(String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter);
+
+  /**
+   * Add a double array property.
+   *
+   * @param key property name
+   * @param getter getter function (returns current value)
+   * @param setter setter function (sets new value)
+   */
+  void addDoubleArrayProperty(String key, Supplier<double[]> getter, Consumer<double[]> setter);
+
+  /**
+   * Add a string array property.
+   *
+   * @param key property name
+   * @param getter getter function (returns current value)
+   * @param setter setter function (sets new value)
+   */
+  void addStringArrayProperty(String key, Supplier<String[]> getter, Consumer<String[]> setter);
+
+  /**
+   * Add a raw property.
+   *
+   * @param key property name
+   * @param getter getter function (returns current value)
+   * @param setter setter function (sets new value)
+   */
+  void addRawProperty(String key, Supplier<byte[]> getter, Consumer<byte[]> setter);
+
+  /**
+   * Gets the kind of backend being used.
+   *
+   * @return Backend kind
+   */
+  BackendKind getBackendKind();
+
+  /**
+   * Return whether this sendable has been published.
+   *
+   * @return True if it has been published, false if not.
+   */
+  boolean isPublished();
+
+  /** Update the published values by calling the getters for all properties. */
+  void update();
+
+  /** Clear properties. */
+  void clearProperties();
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable/SendableRegistry.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable/SendableRegistry.java
new file mode 100644
index 0000000..9d919af
--- /dev/null
+++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable/SendableRegistry.java
@@ -0,0 +1,513 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util.sendable;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * The SendableRegistry class is the public interface for registering sensors and actuators for use
+ * on dashboards and LiveWindow.
+ */
+public class SendableRegistry {
+  private static class Component {
+    Component() {}
+
+    Component(Sendable sendable) {
+      m_sendable = new WeakReference<>(sendable);
+    }
+
+    WeakReference<Sendable> m_sendable;
+    SendableBuilder m_builder;
+    String m_name;
+    String m_subsystem = "Ungrouped";
+    WeakReference<Sendable> m_parent;
+    boolean m_liveWindow;
+    Object[] m_data;
+
+    void setName(String moduleType, int channel) {
+      m_name = moduleType + "[" + channel + "]";
+    }
+
+    void setName(String moduleType, int moduleNumber, int channel) {
+      m_name = moduleType + "[" + moduleNumber + "," + channel + "]";
+    }
+  }
+
+  private static Supplier<SendableBuilder> liveWindowFactory;
+  private static final Map<Object, Component> components = new WeakHashMap<>();
+  private static int nextDataHandle;
+
+  private static Component getOrAdd(Sendable sendable) {
+    Component comp = components.get(sendable);
+    if (comp == null) {
+      comp = new Component(sendable);
+      components.put(sendable, comp);
+    } else {
+      if (comp.m_sendable == null) {
+        comp.m_sendable = new WeakReference<>(sendable);
+      }
+    }
+    return comp;
+  }
+
+  private SendableRegistry() {
+    throw new UnsupportedOperationException("This is a utility class!");
+  }
+
+  /**
+   * Sets the factory for LiveWindow builders.
+   *
+   * @param factory factory function
+   */
+  public static synchronized void setLiveWindowBuilderFactory(Supplier<SendableBuilder> factory) {
+    liveWindowFactory = factory;
+  }
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable object to add
+   * @param name component name
+   */
+  public static synchronized void add(Sendable sendable, String name) {
+    Component comp = getOrAdd(sendable);
+    comp.m_name = name;
+  }
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable object to add
+   * @param moduleType A string that defines the module name in the label for the value
+   * @param channel The channel number the device is plugged into
+   */
+  public static synchronized void add(Sendable sendable, String moduleType, int channel) {
+    Component comp = getOrAdd(sendable);
+    comp.setName(moduleType, channel);
+  }
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable object to add
+   * @param moduleType A string that defines the module name in the label for the value
+   * @param moduleNumber The number of the particular module type
+   * @param channel The channel number the device is plugged into
+   */
+  public static synchronized void add(
+      Sendable sendable, String moduleType, int moduleNumber, int channel) {
+    Component comp = getOrAdd(sendable);
+    comp.setName(moduleType, moduleNumber, channel);
+  }
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable object to add
+   * @param subsystem subsystem name
+   * @param name component name
+   */
+  public static synchronized void add(Sendable sendable, String subsystem, String name) {
+    Component comp = getOrAdd(sendable);
+    comp.m_name = name;
+    comp.m_subsystem = subsystem;
+  }
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable object to add
+   * @param name component name
+   */
+  public static synchronized void addLW(Sendable sendable, String name) {
+    Component comp = getOrAdd(sendable);
+    if (liveWindowFactory != null) {
+      comp.m_builder = liveWindowFactory.get();
+    }
+    comp.m_liveWindow = true;
+    comp.m_name = name;
+  }
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable object to add
+   * @param moduleType A string that defines the module name in the label for the value
+   * @param channel The channel number the device is plugged into
+   */
+  public static synchronized void addLW(Sendable sendable, String moduleType, int channel) {
+    Component comp = getOrAdd(sendable);
+    if (liveWindowFactory != null) {
+      comp.m_builder = liveWindowFactory.get();
+    }
+    comp.m_liveWindow = true;
+    comp.setName(moduleType, channel);
+  }
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable object to add
+   * @param moduleType A string that defines the module name in the label for the value
+   * @param moduleNumber The number of the particular module type
+   * @param channel The channel number the device is plugged into
+   */
+  public static synchronized void addLW(
+      Sendable sendable, String moduleType, int moduleNumber, int channel) {
+    Component comp = getOrAdd(sendable);
+    if (liveWindowFactory != null) {
+      comp.m_builder = liveWindowFactory.get();
+    }
+    comp.m_liveWindow = true;
+    comp.setName(moduleType, moduleNumber, channel);
+  }
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable object to add
+   * @param subsystem subsystem name
+   * @param name component name
+   */
+  public static synchronized void addLW(Sendable sendable, String subsystem, String name) {
+    Component comp = getOrAdd(sendable);
+    if (liveWindowFactory != null) {
+      comp.m_builder = liveWindowFactory.get();
+    }
+    comp.m_liveWindow = true;
+    comp.m_name = name;
+    comp.m_subsystem = subsystem;
+  }
+
+  /**
+   * Adds a child object to an object. Adds the child object to the registry if it's not already
+   * present.
+   *
+   * @param parent parent object
+   * @param child child object
+   */
+  public static synchronized void addChild(Sendable parent, Object child) {
+    Component comp = components.get(child);
+    if (comp == null) {
+      comp = new Component();
+      components.put(child, comp);
+    }
+    comp.m_parent = new WeakReference<>(parent);
+  }
+
+  /**
+   * Removes an object from the registry.
+   *
+   * @param sendable object to remove
+   * @return true if the object was removed; false if it was not present
+   */
+  public static synchronized boolean remove(Sendable sendable) {
+    return components.remove(sendable) != null;
+  }
+
+  /**
+   * Determines if an object is in the registry.
+   *
+   * @param sendable object to check
+   * @return True if in registry, false if not.
+   */
+  public static synchronized boolean contains(Sendable sendable) {
+    return components.containsKey(sendable);
+  }
+
+  /**
+   * Gets the name of an object.
+   *
+   * @param sendable object
+   * @return Name (empty if object is not in registry)
+   */
+  public static synchronized String getName(Sendable sendable) {
+    Component comp = components.get(sendable);
+    if (comp == null) {
+      return "";
+    }
+    return comp.m_name;
+  }
+
+  /**
+   * Sets the name of an object.
+   *
+   * @param sendable object
+   * @param name name
+   */
+  public static synchronized void setName(Sendable sendable, String name) {
+    Component comp = components.get(sendable);
+    if (comp != null) {
+      comp.m_name = name;
+    }
+  }
+
+  /**
+   * Sets the name of an object with a channel number.
+   *
+   * @param sendable object
+   * @param moduleType A string that defines the module name in the label for the value
+   * @param channel The channel number the device is plugged into
+   */
+  public static synchronized void setName(Sendable sendable, String moduleType, int channel) {
+    Component comp = components.get(sendable);
+    if (comp != null) {
+      comp.setName(moduleType, channel);
+    }
+  }
+
+  /**
+   * Sets the name of an object with a module and channel number.
+   *
+   * @param sendable object
+   * @param moduleType A string that defines the module name in the label for the value
+   * @param moduleNumber The number of the particular module type
+   * @param channel The channel number the device is plugged into
+   */
+  public static synchronized void setName(
+      Sendable sendable, String moduleType, int moduleNumber, int channel) {
+    Component comp = components.get(sendable);
+    if (comp != null) {
+      comp.setName(moduleType, moduleNumber, channel);
+    }
+  }
+
+  /**
+   * Sets both the subsystem name and device name of an object.
+   *
+   * @param sendable object
+   * @param subsystem subsystem name
+   * @param name device name
+   */
+  public static synchronized void setName(Sendable sendable, String subsystem, String name) {
+    Component comp = components.get(sendable);
+    if (comp != null) {
+      comp.m_name = name;
+      comp.m_subsystem = subsystem;
+    }
+  }
+
+  /**
+   * Gets the subsystem name of an object.
+   *
+   * @param sendable object
+   * @return Subsystem name (empty if object is not in registry)
+   */
+  public static synchronized String getSubsystem(Sendable sendable) {
+    Component comp = components.get(sendable);
+    if (comp == null) {
+      return "";
+    }
+    return comp.m_subsystem;
+  }
+
+  /**
+   * Sets the subsystem name of an object.
+   *
+   * @param sendable object
+   * @param subsystem subsystem name
+   */
+  public static synchronized void setSubsystem(Sendable sendable, String subsystem) {
+    Component comp = components.get(sendable);
+    if (comp != null) {
+      comp.m_subsystem = subsystem;
+    }
+  }
+
+  /**
+   * Gets a unique handle for setting/getting data with setData() and getData().
+   *
+   * @return Handle
+   */
+  public static synchronized int getDataHandle() {
+    return nextDataHandle++;
+  }
+
+  /**
+   * Associates arbitrary data with an object in the registry.
+   *
+   * @param sendable object
+   * @param handle data handle returned by getDataHandle()
+   * @param data data to set
+   * @return Previous data (may be null)
+   */
+  public static synchronized Object setData(Sendable sendable, int handle, Object data) {
+    Component comp = components.get(sendable);
+    if (comp == null) {
+      return null;
+    }
+    Object rv = null;
+    if (comp.m_data == null) {
+      comp.m_data = new Object[handle + 1];
+    } else if (handle < comp.m_data.length) {
+      rv = comp.m_data[handle];
+    } else {
+      comp.m_data = Arrays.copyOf(comp.m_data, handle + 1);
+    }
+    comp.m_data[handle] = data;
+    return rv;
+  }
+
+  /**
+   * Gets arbitrary data associated with an object in the registry.
+   *
+   * @param sendable object
+   * @param handle data handle returned by getDataHandle()
+   * @return data (may be null if none associated)
+   */
+  public static synchronized Object getData(Sendable sendable, int handle) {
+    Component comp = components.get(sendable);
+    if (comp == null || comp.m_data == null || handle >= comp.m_data.length) {
+      return null;
+    }
+    return comp.m_data[handle];
+  }
+
+  /**
+   * Enables LiveWindow for an object.
+   *
+   * @param sendable object
+   */
+  public static synchronized void enableLiveWindow(Sendable sendable) {
+    Component comp = components.get(sendable);
+    if (comp != null) {
+      comp.m_liveWindow = true;
+    }
+  }
+
+  /**
+   * Disables LiveWindow for an object.
+   *
+   * @param sendable object
+   */
+  public static synchronized void disableLiveWindow(Sendable sendable) {
+    Component comp = components.get(sendable);
+    if (comp != null) {
+      comp.m_liveWindow = false;
+    }
+  }
+
+  /**
+   * Publishes an object in the registry to a builder.
+   *
+   * @param sendable object
+   * @param builder sendable builder
+   */
+  public static synchronized void publish(Sendable sendable, SendableBuilder builder) {
+    Component comp = getOrAdd(sendable);
+    if (comp.m_builder != null) {
+      comp.m_builder.clearProperties();
+    }
+    comp.m_builder = builder; // clear any current builder
+    sendable.initSendable(comp.m_builder);
+    comp.m_builder.update();
+  }
+
+  /**
+   * Updates network table information from an object.
+   *
+   * @param sendable object
+   */
+  public static synchronized void update(Sendable sendable) {
+    Component comp = components.get(sendable);
+    if (comp != null && comp.m_builder != null) {
+      comp.m_builder.update();
+    }
+  }
+
+  /** Data passed to foreachLiveWindow() callback function. */
+  public static class CallbackData {
+    /** Sendable object. */
+    @SuppressWarnings("MemberName")
+    public Sendable sendable;
+
+    /** Name. */
+    @SuppressWarnings("MemberName")
+    public String name;
+
+    /** Subsystem. */
+    @SuppressWarnings("MemberName")
+    public String subsystem;
+
+    /** Parent sendable object. */
+    @SuppressWarnings("MemberName")
+    public Sendable parent;
+
+    /** Data stored in object with setData(). Update this to change the data. */
+    @SuppressWarnings("MemberName")
+    public Object data;
+
+    /** Sendable builder for the sendable. */
+    @SuppressWarnings("MemberName")
+    public SendableBuilder builder;
+  }
+
+  // As foreachLiveWindow is single threaded, cache the components it
+  // iterates over to avoid risk of ConcurrentModificationException
+  private static List<Component> foreachComponents = new ArrayList<>();
+
+  /**
+   * Iterates over LiveWindow-enabled objects in the registry. It is *not* safe to call other
+   * SendableRegistry functions from the callback.
+   *
+   * @param dataHandle data handle to get data object passed to callback
+   * @param callback function to call for each object
+   */
+  @SuppressWarnings("PMD.AvoidCatchingThrowable")
+  public static synchronized void foreachLiveWindow(
+      int dataHandle, Consumer<CallbackData> callback) {
+    CallbackData cbdata = new CallbackData();
+    foreachComponents.clear();
+    foreachComponents.addAll(components.values());
+    for (Component comp : foreachComponents) {
+      if (comp.m_builder == null || comp.m_sendable == null) {
+        continue;
+      }
+      cbdata.sendable = comp.m_sendable.get();
+      if (cbdata.sendable != null && comp.m_liveWindow) {
+        cbdata.name = comp.m_name;
+        cbdata.subsystem = comp.m_subsystem;
+        if (comp.m_parent != null) {
+          cbdata.parent = comp.m_parent.get();
+        } else {
+          cbdata.parent = null;
+        }
+        if (comp.m_data != null && dataHandle < comp.m_data.length) {
+          cbdata.data = comp.m_data[dataHandle];
+        } else {
+          cbdata.data = null;
+        }
+        cbdata.builder = comp.m_builder;
+        try {
+          callback.accept(cbdata);
+        } catch (Throwable throwable) {
+          Throwable cause = throwable.getCause();
+          if (cause != null) {
+            throwable = cause;
+          }
+          System.err.println("Unhandled exception calling LiveWindow for " + comp.m_name + ": ");
+          throwable.printStackTrace();
+          comp.m_liveWindow = false;
+        }
+        if (cbdata.data != null) {
+          if (comp.m_data == null) {
+            comp.m_data = new Object[dataHandle + 1];
+          } else if (dataHandle >= comp.m_data.length) {
+            comp.m_data = Arrays.copyOf(comp.m_data, dataHandle + 1);
+          }
+          comp.m_data[dataHandle] = cbdata.data;
+        }
+      }
+    }
+    foreachComponents.clear();
+  }
+}
diff --git a/wpiutil/src/main/java/edu/wpi/first/wpiutil/ErrorMessages.java b/wpiutil/src/main/java/edu/wpi/first/wpiutil/ErrorMessages.java
deleted file mode 100644
index 70387eb..0000000
--- a/wpiutil/src/main/java/edu/wpi/first/wpiutil/ErrorMessages.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-package edu.wpi.first.wpiutil;
-
-import static java.util.Objects.requireNonNull;
-
-/**
- * Utility class for common WPILib error messages.
- */
-public final class ErrorMessages {
-  /**
-   * Utility class, so constructor is private.
-   */
-  private ErrorMessages() {
-    throw new UnsupportedOperationException("This is a utility class!");
-  }
-
-  /**
-   * Requires that a parameter of a method not be null; prints an error message with
-   * helpful debugging instructions if the parameter is null.
-   *
-   * @param obj The parameter that must not be null.
-   * @param paramName The name of the parameter.
-   * @param methodName The name of the method.
-   */
-  public static <T> T requireNonNullParam(T obj, String paramName, String methodName) {
-    return requireNonNull(obj,
-        "Parameter " + paramName + " in method " + methodName + " was null when it"
-            + " should not have been!  Check the stacktrace to find the responsible line of code - "
-            + "usually, it is the first line of user-written code indicated in the stacktrace.  "
-            + "Make sure all objects passed to the method in question were properly initialized -"
-            + " note that this may not be obvious if it is being called under "
-            + "dynamically-changing conditions!  Please do not seek additional technical assistance"
-            + " without doing this first!");
-  }
-}
diff --git a/wpiutil/src/main/java/edu/wpi/first/wpiutil/WPIUtilJNI.java b/wpiutil/src/main/java/edu/wpi/first/wpiutil/WPIUtilJNI.java
deleted file mode 100644
index 3a22c7b..0000000
--- a/wpiutil/src/main/java/edu/wpi/first/wpiutil/WPIUtilJNI.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-package edu.wpi.first.wpiutil;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public final class WPIUtilJNI {
-  static boolean libraryLoaded = false;
-  static RuntimeLoader<WPIUtilJNI> loader = null;
-
-  public static class Helper {
-    private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
-
-    public static boolean getExtractOnStaticLoad() {
-      return extractOnStaticLoad.get();
-    }
-
-    public static void setExtractOnStaticLoad(boolean load) {
-      extractOnStaticLoad.set(load);
-    }
-  }
-
-  static {
-    if (Helper.getExtractOnStaticLoad()) {
-      try {
-        loader = new RuntimeLoader<>("wpiutiljni", RuntimeLoader.getDefaultExtractionRoot(), WPIUtilJNI.class);
-        loader.loadLibrary();
-      } catch (IOException ex) {
-        ex.printStackTrace();
-        System.exit(1);
-      }
-      libraryLoaded = true;
-    }
-  }
-
-  /**
-   * Force load the library.
-   */
-  public static synchronized void forceLoad() throws IOException {
-    if (libraryLoaded) {
-      return;
-    }
-    loader = new RuntimeLoader<>("wpiutiljni", RuntimeLoader.getDefaultExtractionRoot(), WPIUtilJNI.class);
-    loader.loadLibrary();
-    libraryLoaded = true;
-  }
-
-  public static native long now();
-
-  public static native void addPortForwarder(int port, String remoteHost, int remotePort);
-  public static native void removePortForwarder(int port);
-}
diff --git a/wpiutil/src/main/java/edu/wpi/first/wpiutil/net/PortForwarder.java b/wpiutil/src/main/java/edu/wpi/first/wpiutil/net/PortForwarder.java
deleted file mode 100644
index 4fbdbf5..0000000
--- a/wpiutil/src/main/java/edu/wpi/first/wpiutil/net/PortForwarder.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-package edu.wpi.first.wpiutil.net;
-
-import edu.wpi.first.wpiutil.WPIUtilJNI;
-
-/**
- * Forward ports to another host. This is primarily useful for accessing
- * Ethernet-connected devices from a computer tethered to the RoboRIO USB port.
- */
-public final class PortForwarder {
-  private PortForwarder() {
-    throw new UnsupportedOperationException("This is a utility class!");
-  }
-
-  /**
-   * Forward a local TCP port to a remote host and port.
-   * Note that local ports less than 1024 won't work as a normal user.
-   *
-   * @param port       local port number
-   * @param remoteHost remote IP address / DNS name
-   * @param remotePort remote port number
-   */
-  public static void add(int port, String remoteHost, int remotePort) {
-    WPIUtilJNI.addPortForwarder(port, remoteHost, remotePort);
-  }
-
-  /**
-   * Stop TCP forwarding on a port.
-   *
-   * @param port local port number
-   */
-  public static void remove(int port) {
-    WPIUtilJNI.removePortForwarder(port);
-  }
-}
diff --git a/wpiutil/src/main/native/cpp/Base64.cpp b/wpiutil/src/main/native/cpp/Base64.cpp
index 35ac76c..8f1f810 100644
--- a/wpiutil/src/main/native/cpp/Base64.cpp
+++ b/wpiutil/src/main/native/cpp/Base64.cpp
@@ -80,13 +80,19 @@
     64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
     64, 64, 64, 64, 64, 64, 64, 64, 64};
 
-size_t Base64Decode(raw_ostream& os, StringRef encoded) {
-  const unsigned char* end = encoded.bytes_begin();
-  while (pr2six[*end] <= 63 && end != encoded.bytes_end()) ++end;
-  size_t nprbytes = end - encoded.bytes_begin();
-  if (nprbytes == 0) return 0;
+size_t Base64Decode(raw_ostream& os, std::string_view encoded) {
+  auto bytes_begin = reinterpret_cast<const unsigned char*>(encoded.data());
+  auto bytes_end = bytes_begin + encoded.size();
+  const unsigned char* end = bytes_begin;
+  while (pr2six[*end] <= 63 && end != bytes_end) {
+    ++end;
+  }
+  size_t nprbytes = end - bytes_begin;
+  if (nprbytes == 0) {
+    return 0;
+  }
 
-  const unsigned char* cur = encoded.bytes_begin();
+  const unsigned char* cur = bytes_begin;
 
   while (nprbytes > 4) {
     os << static_cast<unsigned char>(pr2six[cur[0]] << 2 | pr2six[cur[1]] >> 4);
@@ -97,17 +103,20 @@
   }
 
   // Note: (nprbytes == 1) would be an error, so just ignore that case
-  if (nprbytes > 1)
+  if (nprbytes > 1) {
     os << static_cast<unsigned char>(pr2six[cur[0]] << 2 | pr2six[cur[1]] >> 4);
-  if (nprbytes > 2)
+  }
+  if (nprbytes > 2) {
     os << static_cast<unsigned char>(pr2six[cur[1]] << 4 | pr2six[cur[2]] >> 2);
-  if (nprbytes > 3)
+  }
+  if (nprbytes > 3) {
     os << static_cast<unsigned char>(pr2six[cur[2]] << 6 | pr2six[cur[3]]);
+  }
 
-  return (end - encoded.bytes_begin()) + ((4 - nprbytes) & 3);
+  return (end - bytes_begin) + ((4 - nprbytes) & 3);
 }
 
-size_t Base64Decode(StringRef encoded, std::string* plain) {
+size_t Base64Decode(std::string_view encoded, std::string* plain) {
   plain->resize(0);
   raw_string_ostream os(*plain);
   size_t rv = Base64Decode(os, encoded);
@@ -115,19 +124,35 @@
   return rv;
 }
 
-StringRef Base64Decode(StringRef encoded, size_t* num_read,
-                       SmallVectorImpl<char>& buf) {
+std::string_view Base64Decode(std::string_view encoded, size_t* num_read,
+                              SmallVectorImpl<char>& buf) {
   buf.clear();
   raw_svector_ostream os(buf);
   *num_read = Base64Decode(os, encoded);
   return os.str();
 }
 
+size_t Base64Decode(std::string_view encoded, std::vector<uint8_t>* plain) {
+  plain->resize(0);
+  raw_uvector_ostream os(*plain);
+  return Base64Decode(os, encoded);
+}
+
+span<uint8_t> Base64Decode(std::string_view encoded, size_t* num_read,
+                           SmallVectorImpl<uint8_t>& buf) {
+  buf.clear();
+  raw_usvector_ostream os(buf);
+  *num_read = Base64Decode(os, encoded);
+  return os.array();
+}
+
 static const char basis_64[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
-void Base64Encode(raw_ostream& os, StringRef plain) {
-  if (plain.empty()) return;
+void Base64Encode(raw_ostream& os, std::string_view plain) {
+  if (plain.empty()) {
+    return;
+  }
   size_t len = plain.size();
 
   size_t i;
@@ -153,14 +178,35 @@
   }
 }
 
-void Base64Encode(StringRef plain, std::string* encoded) {
+void Base64Encode(std::string_view plain, std::string* encoded) {
   encoded->resize(0);
   raw_string_ostream os(*encoded);
   Base64Encode(os, plain);
   os.flush();
 }
 
-StringRef Base64Encode(StringRef plain, SmallVectorImpl<char>& buf) {
+std::string_view Base64Encode(std::string_view plain,
+                              SmallVectorImpl<char>& buf) {
+  buf.clear();
+  raw_svector_ostream os(buf);
+  Base64Encode(os, plain);
+  return os.str();
+}
+
+void Base64Encode(raw_ostream& os, span<const uint8_t> plain) {
+  Base64Encode(os, std::string_view{reinterpret_cast<const char*>(plain.data()),
+                                    plain.size()});
+}
+
+void Base64Encode(span<const uint8_t> plain, std::string* encoded) {
+  encoded->resize(0);
+  raw_string_ostream os(*encoded);
+  Base64Encode(os, plain);
+  os.flush();
+}
+
+std::string_view Base64Encode(span<const uint8_t> plain,
+                              SmallVectorImpl<char>& buf) {
   buf.clear();
   raw_svector_ostream os(buf);
   Base64Encode(os, plain);
diff --git a/wpiutil/src/main/native/cpp/DsClient.cpp b/wpiutil/src/main/native/cpp/DsClient.cpp
new file mode 100644
index 0000000..455f10c
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/DsClient.cpp
@@ -0,0 +1,107 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/DsClient.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+#include <wpi/json.h>
+#include <wpi/uv/Tcp.h>
+#include <wpi/uv/Timer.h>
+
+#include "wpi/Logger.h"
+
+using namespace wpi;
+
+static constexpr uv::Timer::Time kReconnectTime{500};
+
+DsClient::DsClient(wpi::uv::Loop& loop, wpi::Logger& logger,
+                   const private_init&)
+    : m_logger{logger},
+      m_tcp{uv::Tcp::Create(loop)},
+      m_timer{uv::Timer::Create(loop)} {
+  m_tcp->end.connect([this] {
+    WPI_DEBUG4(m_logger, "{}", "DS connection closed");
+    clearIp();
+    // try to connect again
+    m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
+  });
+  m_tcp->data.connect([this](wpi::uv::Buffer buf, size_t len) {
+    HandleIncoming({buf.base, len});
+  });
+  m_timer->timeout.connect([this] { Connect(); });
+  Connect();
+}
+
+DsClient::~DsClient() = default;
+
+void DsClient::Close() {
+  m_tcp->Close();
+  m_timer->Close();
+  clearIp();
+}
+
+void DsClient::Connect() {
+  auto connreq = std::make_shared<uv::TcpConnectReq>();
+  connreq->connected.connect([this] {
+    m_json.clear();
+    m_tcp->StopRead();
+    m_tcp->StartRead();
+  });
+
+  connreq->error = [this](uv::Error err) {
+    WPI_DEBUG4(m_logger, "DS connect failure: {}", err.str());
+    // try to connect again
+    m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
+  };
+
+  WPI_DEBUG4(m_logger, "{}", "Starting DS connection attempt");
+  m_tcp->Connect("127.0.0.1", 1742, connreq);
+}
+
+void DsClient::HandleIncoming(std::string_view in) {
+  // this is very bare-bones, as there are never nested {} in these messages
+  while (!in.empty()) {
+    // if json is empty, look for the first { (and discard)
+    if (m_json.empty()) {
+      auto start = in.find('{');
+      in = wpi::slice(in, start, std::string_view::npos);
+    }
+
+    // look for the terminating } (and save)
+    auto end = in.find('}');
+    if (end == std::string_view::npos) {
+      m_json.append(in);
+      return;  // nothing left to read
+    }
+
+    // have complete json message
+    ++end;
+    m_json.append(wpi::slice(in, 0, end));
+    in = wpi::slice(in, end, std::string_view::npos);
+    ParseJson();
+    m_json.clear();
+  }
+}
+
+void DsClient::ParseJson() {
+  WPI_DEBUG4(m_logger, "DsClient JSON: {}", m_json);
+  unsigned int ip = 0;
+  try {
+    ip = wpi::json::parse(m_json).at("robotIP").get<unsigned int>();
+  } catch (wpi::json::exception& e) {
+    WPI_INFO(m_logger, "DsClient JSON error: {}", e.what());
+    return;
+  }
+
+  if (ip == 0) {
+    clearIp();
+  } else {
+    // Convert number into dotted quad
+    auto newip = fmt::format("{}.{}.{}.{}", (ip >> 24) & 0xff,
+                             (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
+    WPI_INFO(m_logger, "DS received server IP: {}", newip);
+    setIp(newip);
+  }
+}
diff --git a/wpiutil/src/main/native/cpp/EventLoopRunner.cpp b/wpiutil/src/main/native/cpp/EventLoopRunner.cpp
index 1c54bdf..c86176e 100644
--- a/wpiutil/src/main/native/cpp/EventLoopRunner.cpp
+++ b/wpiutil/src/main/native/cpp/EventLoopRunner.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/EventLoopRunner.h"
 
@@ -21,7 +18,9 @@
 
   Thread() : m_loop(uv::Loop::Create()) {
     // set up async handles
-    if (!m_loop) return;
+    if (!m_loop) {
+      return;
+    }
 
     // run function
     m_doExec = UvExecFunc::Create(
@@ -31,8 +30,10 @@
         });
   }
 
-  void Main() {
-    if (m_loop) m_loop->Run();
+  void Main() override {
+    if (m_loop) {
+      m_loop->Run();
+    }
   }
 
   // the loop
@@ -42,9 +43,13 @@
   std::weak_ptr<UvExecFunc> m_doExec;
 };
 
-EventLoopRunner::EventLoopRunner() { m_owner.Start(); }
+EventLoopRunner::EventLoopRunner() {
+  m_owner.Start();
+}
 
-EventLoopRunner::~EventLoopRunner() { Stop(); }
+EventLoopRunner::~EventLoopRunner() {
+  Stop();
+}
 
 void EventLoopRunner::Stop() {
   ExecAsync([](uv::Loop& loop) {
@@ -60,7 +65,7 @@
 void EventLoopRunner::ExecAsync(LoopFunc func) {
   if (auto thr = m_owner.GetThread()) {
     if (auto doExec = thr->m_doExec.lock()) {
-      doExec->Call(func);
+      doExec->Call(std::move(func));
     }
   }
 }
@@ -69,13 +74,17 @@
   wpi::future<void> f;
   if (auto thr = m_owner.GetThread()) {
     if (auto doExec = thr->m_doExec.lock()) {
-      f = doExec->Call(func);
+      f = doExec->Call(std::move(func));
     }
   }
-  if (f.valid()) f.wait();
+  if (f.valid()) {
+    f.wait();
+  }
 }
 
 std::shared_ptr<uv::Loop> EventLoopRunner::GetLoop() {
-  if (auto thr = m_owner.GetThread()) return thr->m_loop;
+  if (auto thr = m_owner.GetThread()) {
+    return thr->m_loop;
+  }
   return nullptr;
 }
diff --git a/wpiutil/src/main/native/cpp/HttpParser.cpp b/wpiutil/src/main/native/cpp/HttpParser.cpp
index 4560b38..3c18e0f 100644
--- a/wpiutil/src/main/native/cpp/HttpParser.cpp
+++ b/wpiutil/src/main/native/cpp/HttpParser.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/HttpParser.h"
 
@@ -37,8 +34,10 @@
   m_settings.on_url = [](http_parser* p, const char* at, size_t length) -> int {
     auto& self = *static_cast<HttpParser*>(p->data);
     // append to buffer
-    if ((self.m_urlBuf.size() + length) > self.m_maxLength) return 1;
-    self.m_urlBuf += StringRef{at, length};
+    if ((self.m_urlBuf.size() + length) > self.m_maxLength) {
+      return 1;
+    }
+    self.m_urlBuf += std::string_view{at, length};
     self.m_state = kUrl;
     return 0;
   };
@@ -48,8 +47,10 @@
                             size_t length) -> int {
     auto& self = *static_cast<HttpParser*>(p->data);
     // use valueBuf for the status
-    if ((self.m_valueBuf.size() + length) > self.m_maxLength) return 1;
-    self.m_valueBuf += StringRef{at, length};
+    if ((self.m_valueBuf.size() + length) > self.m_maxLength) {
+      return 1;
+    }
+    self.m_valueBuf += std::string_view{at, length};
     self.m_state = kStatus;
     return 0;
   };
@@ -62,19 +63,25 @@
     // once we're in header, we know the URL is complete
     if (self.m_state == kUrl) {
       self.url(self.m_urlBuf);
-      if (self.m_aborted) return 1;
+      if (self.m_aborted) {
+        return 1;
+      }
     }
 
     // once we're in header, we know the status is complete
     if (self.m_state == kStatus) {
       self.status(self.m_valueBuf);
-      if (self.m_aborted) return 1;
+      if (self.m_aborted) {
+        return 1;
+      }
     }
 
     // if we previously were in value state, that means we finished a header
     if (self.m_state == kValue) {
       self.header(self.m_fieldBuf, self.m_valueBuf);
-      if (self.m_aborted) return 1;
+      if (self.m_aborted) {
+        return 1;
+      }
     }
 
     // clear field and value when we enter this state
@@ -85,8 +92,10 @@
     }
 
     // append data to field buffer
-    if ((self.m_fieldBuf.size() + length) > self.m_maxLength) return 1;
-    self.m_fieldBuf += StringRef{at, length};
+    if ((self.m_fieldBuf.size() + length) > self.m_maxLength) {
+      return 1;
+    }
+    self.m_fieldBuf += std::string_view{at, length};
     return 0;
   };
 
@@ -102,8 +111,10 @@
     }
 
     // append data to value buffer
-    if ((self.m_valueBuf.size() + length) > self.m_maxLength) return 1;
-    self.m_valueBuf += StringRef{at, length};
+    if ((self.m_valueBuf.size() + length) > self.m_maxLength) {
+      return 1;
+    }
+    self.m_valueBuf += std::string_view{at, length};
     return 0;
   };
 
@@ -114,19 +125,25 @@
     // if we previously were in url state, that means we finished the url
     if (self.m_state == kUrl) {
       self.url(self.m_urlBuf);
-      if (self.m_aborted) return 1;
+      if (self.m_aborted) {
+        return 1;
+      }
     }
 
     // if we previously were in status state, that means we finished the status
     if (self.m_state == kStatus) {
       self.status(self.m_valueBuf);
-      if (self.m_aborted) return 1;
+      if (self.m_aborted) {
+        return 1;
+      }
     }
 
     // if we previously were in value state, that means we finished a header
     if (self.m_state == kValue) {
       self.header(self.m_fieldBuf, self.m_valueBuf);
-      if (self.m_aborted) return 1;
+      if (self.m_aborted) {
+        return 1;
+      }
     }
 
     self.headersComplete(self.ShouldKeepAlive());
@@ -137,7 +154,7 @@
   m_settings.on_body = [](http_parser* p, const char* at,
                           size_t length) -> int {
     auto& self = *static_cast<HttpParser*>(p->data);
-    self.body(StringRef{at, length}, self.IsBodyFinal());
+    self.body(std::string_view{at, length}, self.IsBodyFinal());
     return self.m_aborted;
   };
 
diff --git a/wpiutil/src/main/native/cpp/HttpServerConnection.cpp b/wpiutil/src/main/native/cpp/HttpServerConnection.cpp
index 0308e8c..716d2af 100644
--- a/wpiutil/src/main/native/cpp/HttpServerConnection.cpp
+++ b/wpiutil/src/main/native/cpp/HttpServerConnection.cpp
@@ -1,14 +1,15 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/HttpServerConnection.h"
 
+#include "fmt/format.h"
 #include "wpi/SmallString.h"
 #include "wpi/SmallVector.h"
+#include "wpi/SpanExtras.h"
+#include "wpi/StringExtras.h"
+#include "wpi/fmt/raw_ostream.h"
 #include "wpi/raw_uv_ostream.h"
 
 using namespace wpi;
@@ -24,16 +25,18 @@
 
   // look for Accept-Encoding headers to determine if gzip is acceptable
   m_request.messageBegin.connect([this] { m_acceptGzip = false; });
-  m_request.header.connect([this](StringRef name, StringRef value) {
-    if (name.equals_lower("accept-encoding") && value.contains("gzip")) {
-      m_acceptGzip = true;
-    }
-  });
+  m_request.header.connect(
+      [this](std::string_view name, std::string_view value) {
+        if (wpi::equals_lower(name, "accept-encoding") &&
+            wpi::contains(value, "gzip")) {
+          m_acceptGzip = true;
+        }
+      });
 
   // pass incoming data to HTTP parser
   m_dataConn =
       stream->data.connect_connection([this](uv::Buffer& buf, size_t size) {
-        m_request.Execute(StringRef{buf.base, size});
+        m_request.Execute({buf.base, size});
         if (m_request.HasError()) {
           // could not parse; just close the connection
           m_stream.Close();
@@ -57,37 +60,46 @@
 }
 
 void HttpServerConnection::BuildHeader(raw_ostream& os, int code,
-                                       const Twine& codeText,
-                                       const Twine& contentType,
+                                       std::string_view codeText,
+                                       std::string_view contentType,
                                        uint64_t contentLength,
-                                       const Twine& extra) {
-  os << "HTTP/" << m_request.GetMajor() << '.' << m_request.GetMinor() << ' '
-     << code << ' ' << codeText << "\r\n";
-  if (contentLength == 0) m_keepAlive = false;
-  if (!m_keepAlive) os << "Connection: close\r\n";
+                                       std::string_view extra) {
+  fmt::print(os, "HTTP/{}.{} {} {}\r\n", m_request.GetMajor(),
+             m_request.GetMinor(), code, codeText);
+  if (contentLength == 0) {
+    m_keepAlive = false;
+  }
+  if (!m_keepAlive) {
+    os << "Connection: close\r\n";
+  }
   BuildCommonHeaders(os);
   os << "Content-Type: " << contentType << "\r\n";
-  if (contentLength != 0) os << "Content-Length: " << contentLength << "\r\n";
+  if (contentLength != 0) {
+    fmt::print(os, "Content-Length: {}\r\n", contentLength);
+  }
   os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
-  SmallString<128> extraBuf;
-  StringRef extraStr = extra.toStringRef(extraBuf);
-  if (!extraStr.empty()) os << extraStr;
+  if (!extra.empty()) {
+    os << extra;
+  }
   os << "\r\n";  // header ends with a blank line
 }
 
-void HttpServerConnection::SendData(ArrayRef<uv::Buffer> bufs,
+void HttpServerConnection::SendData(span<const uv::Buffer> bufs,
                                     bool closeAfter) {
-  m_stream.Write(bufs, [closeAfter, stream = &m_stream](
-                           MutableArrayRef<uv::Buffer> bufs, uv::Error) {
-    for (auto&& buf : bufs) buf.Deallocate();
-    if (closeAfter) stream->Close();
+  m_stream.Write(bufs, [closeAfter, stream = &m_stream](auto bufs, uv::Error) {
+    for (auto&& buf : bufs) {
+      buf.Deallocate();
+    }
+    if (closeAfter) {
+      stream->Close();
+    }
   });
 }
 
-void HttpServerConnection::SendResponse(int code, const Twine& codeText,
-                                        const Twine& contentType,
-                                        StringRef content,
-                                        const Twine& extraHeader) {
+void HttpServerConnection::SendResponse(int code, std::string_view codeText,
+                                        std::string_view contentType,
+                                        std::string_view content,
+                                        std::string_view extraHeader) {
   SmallVector<uv::Buffer, 4> toSend;
   raw_uv_ostream os{toSend, 4096};
   BuildHeader(os, code, codeText, contentType, content.size(), extraHeader);
@@ -96,33 +108,37 @@
   SendData(os.bufs(), !m_keepAlive);
 }
 
-void HttpServerConnection::SendStaticResponse(int code, const Twine& codeText,
-                                              const Twine& contentType,
-                                              StringRef content, bool gzipped,
-                                              const Twine& extraHeader) {
+void HttpServerConnection::SendStaticResponse(
+    int code, std::string_view codeText, std::string_view contentType,
+    std::string_view content, bool gzipped, std::string_view extraHeader) {
   // TODO: handle remote side not accepting gzip (very rare)
 
-  StringRef contentEncodingHeader;
-  if (gzipped /* && m_acceptGzip*/)
+  std::string_view contentEncodingHeader;
+  if (gzipped /* && m_acceptGzip*/) {
     contentEncodingHeader = "Content-Encoding: gzip\r\n";
+  }
 
   SmallVector<uv::Buffer, 4> bufs;
   raw_uv_ostream os{bufs, 4096};
   BuildHeader(os, code, codeText, contentType, content.size(),
-              extraHeader + contentEncodingHeader);
+              fmt::format("{}{}", extraHeader, contentEncodingHeader));
   // can send content without copying
   bufs.emplace_back(content);
 
   m_stream.Write(bufs, [closeAfter = !m_keepAlive, stream = &m_stream](
-                           MutableArrayRef<uv::Buffer> bufs, uv::Error) {
+                           auto bufs, uv::Error) {
     // don't deallocate the static content
-    for (auto&& buf : bufs.drop_back()) buf.Deallocate();
-    if (closeAfter) stream->Close();
+    for (auto&& buf : wpi::drop_back(bufs)) {
+      buf.Deallocate();
+    }
+    if (closeAfter) {
+      stream->Close();
+    }
   });
 }
 
-void HttpServerConnection::SendError(int code, const Twine& message) {
-  StringRef codeText, extra, baseMessage;
+void HttpServerConnection::SendError(int code, std::string_view message) {
+  std::string_view codeText, extra, baseMessage;
   switch (code) {
     case 401:
       codeText = "Unauthorized";
@@ -155,8 +171,8 @@
       baseMessage = "501: Not Implemented!";
       break;
   }
-  SmallString<256> content = baseMessage;
+  SmallString<256> content{baseMessage};
   content += "\r\n";
-  message.toVector(content);
-  SendResponse(code, codeText, "text/plain", content, extra);
+  content += message;
+  SendResponse(code, codeText, "text/plain", content.str(), extra);
 }
diff --git a/wpiutil/src/main/native/cpp/HttpUtil.cpp b/wpiutil/src/main/native/cpp/HttpUtil.cpp
index 866ee10..afce3a6 100644
--- a/wpiutil/src/main/native/cpp/HttpUtil.cpp
+++ b/wpiutil/src/main/native/cpp/HttpUtil.cpp
@@ -1,70 +1,64 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/HttpUtil.h"
 
 #include <cctype>
 
+#include "fmt/format.h"
 #include "wpi/Base64.h"
-#include "wpi/STLExtras.h"
 #include "wpi/StringExtras.h"
 #include "wpi/TCPConnector.h"
 #include "wpi/raw_ostream.h"
 
 namespace wpi {
 
-StringRef UnescapeURI(const Twine& str, SmallVectorImpl<char>& buf,
-                      bool* error) {
-  SmallString<128> strBuf;
-  StringRef strStr = str.toStringRef(strBuf);
+std::string_view UnescapeURI(std::string_view str, SmallVectorImpl<char>& buf,
+                             bool* error) {
   buf.clear();
-  for (auto i = strStr.begin(), end = strStr.end(); i != end; ++i) {
+  for (auto i = str.begin(), end = str.end(); i != end; ++i) {
     // pass non-escaped characters to output
     if (*i != '%') {
       // decode + to space
-      if (*i == '+')
+      if (*i == '+') {
         buf.push_back(' ');
-      else
+      } else {
         buf.push_back(*i);
+      }
       continue;
     }
 
     // are there enough characters left?
     if (i + 2 >= end) {
       *error = true;
-      return StringRef{};
+      return {};
     }
 
     // replace %xx with the corresponding character
     unsigned val1 = hexDigitValue(*++i);
     if (val1 == -1U) {
       *error = true;
-      return StringRef{};
+      return {};
     }
     unsigned val2 = hexDigitValue(*++i);
     if (val2 == -1U) {
       *error = true;
-      return StringRef{};
+      return {};
     }
     buf.push_back((val1 << 4) | val2);
   }
 
   *error = false;
-  return StringRef{buf.data(), buf.size()};
+  return {buf.data(), buf.size()};
 }
 
-StringRef EscapeURI(const Twine& str, SmallVectorImpl<char>& buf,
-                    bool spacePlus) {
+std::string_view EscapeURI(std::string_view str, SmallVectorImpl<char>& buf,
+                           bool spacePlus) {
   static const char* const hexLut = "0123456789ABCDEF";
 
-  SmallString<128> strBuf;
-  StringRef strStr = str.toStringRef(strBuf);
   buf.clear();
-  for (auto i = strStr.begin(), end = strStr.end(); i != end; ++i) {
+  for (auto i = str.begin(), end = str.end(); i != end; ++i) {
     // pass unreserved characters to output
     if (std::isalnum(*i) || *i == '-' || *i == '_' || *i == '.' || *i == '~') {
       buf.push_back(*i);
@@ -83,42 +77,48 @@
     buf.push_back(hexLut[(*i) & 0x0f]);
   }
 
-  return StringRef{buf.data(), buf.size()};
+  return {buf.data(), buf.size()};
 }
 
-HttpQueryMap::HttpQueryMap(wpi::StringRef query) {
-  wpi::SmallVector<wpi::StringRef, 16> queryElems;
-  query.split(queryElems, '&', 100, false);
+HttpQueryMap::HttpQueryMap(std::string_view query) {
+  SmallVector<std::string_view, 16> queryElems;
+  split(query, queryElems, '&', 100, false);
   for (auto elem : queryElems) {
-    auto [nameEsc, valueEsc] = elem.split('=');
-    wpi::SmallString<64> nameBuf;
+    auto [nameEsc, valueEsc] = split(elem, '=');
+    SmallString<64> nameBuf;
     bool err = false;
     auto name = wpi::UnescapeURI(nameEsc, nameBuf, &err);
     // note: ignores duplicates
-    if (!err) m_elems.try_emplace(name, valueEsc);
+    if (!err) {
+      m_elems.try_emplace(name, valueEsc);
+    }
   }
 }
 
-std::optional<wpi::StringRef> HttpQueryMap::Get(
-    wpi::StringRef name, wpi::SmallVectorImpl<char>& buf) const {
+std::optional<std::string_view> HttpQueryMap::Get(
+    std::string_view name, wpi::SmallVectorImpl<char>& buf) const {
   auto it = m_elems.find(name);
-  if (it == m_elems.end()) return {};
+  if (it == m_elems.end()) {
+    return {};
+  }
   bool err = false;
   auto val = wpi::UnescapeURI(it->second, buf, &err);
-  if (err) return {};
+  if (err) {
+    return {};
+  }
   return val;
 }
 
-HttpPath::HttpPath(wpi::StringRef path) {
+HttpPath::HttpPath(std::string_view path) {
   // special-case root path to be a single empty element
   if (path == "/") {
     m_pathEnds.emplace_back(0);
     return;
   }
-  wpi::SmallVector<wpi::StringRef, 16> pathElems;
-  path.split(pathElems, '/', 100, false);
+  wpi::SmallVector<std::string_view, 16> pathElems;
+  split(path, pathElems, '/', 100, false);
   for (auto elem : pathElems) {
-    wpi::SmallString<64> buf;
+    SmallString<64> buf;
     bool err = false;
     auto val = wpi::UnescapeURI(elem, buf, &err);
     if (err) {
@@ -130,59 +130,78 @@
   }
 }
 
-bool HttpPath::startswith(size_t start, ArrayRef<StringRef> match) const {
-  if (m_pathEnds.size() < (start + match.size())) return false;
+bool HttpPath::startswith(size_t start,
+                          span<const std::string_view> match) const {
+  if (m_pathEnds.size() < (start + match.size())) {
+    return false;
+  }
   bool first = start == 0;
   auto p = m_pathEnds.begin() + start;
   for (auto m : match) {
-    auto val = m_pathBuf.slice(first ? 0 : *(p - 1), *p);
-    if (val != m) return false;
+    auto val = slice(m_pathBuf, first ? 0 : *(p - 1), *p);
+    if (val != m) {
+      return false;
+    }
     first = false;
     ++p;
   }
   return true;
 }
 
+std::string_view HttpPath::operator[](size_t n) const {
+  return slice(m_pathBuf, n == 0 ? 0 : m_pathEnds[n - 1], m_pathEnds[n]);
+}
+
 bool ParseHttpHeaders(raw_istream& is, SmallVectorImpl<char>* contentType,
                       SmallVectorImpl<char>* contentLength) {
-  if (contentType) contentType->clear();
-  if (contentLength) contentLength->clear();
+  if (contentType) {
+    contentType->clear();
+  }
+  if (contentLength) {
+    contentLength->clear();
+  }
 
   bool inContentType = false;
   bool inContentLength = false;
   SmallString<64> lineBuf;
   for (;;) {
-    StringRef line = is.getline(lineBuf, 1024).rtrim();
-    if (is.has_error()) return false;
-    if (line.empty()) return true;  // empty line signals end of headers
+    std::string_view line = rtrim(is.getline(lineBuf, 1024));
+    if (is.has_error()) {
+      return false;
+    }
+    if (line.empty()) {
+      return true;  // empty line signals end of headers
+    }
 
     // header fields start at the beginning of the line
     if (!std::isspace(line[0])) {
       inContentType = false;
       inContentLength = false;
-      StringRef field;
-      std::tie(field, line) = line.split(':');
-      field = field.rtrim();
-      if (field.equals_lower("content-type"))
+      std::string_view field;
+      std::tie(field, line) = split(line, ':');
+      field = rtrim(field);
+      if (equals_lower(field, "content-type")) {
         inContentType = true;
-      else if (field.equals_lower("content-length"))
+      } else if (equals_lower(field, "content-length")) {
         inContentLength = true;
-      else
+      } else {
         continue;  // ignore other fields
+      }
     }
 
     // collapse whitespace
-    line = line.ltrim();
+    line = ltrim(line);
 
     // save field data
-    if (inContentType && contentType)
+    if (inContentType && contentType) {
       contentType->append(line.begin(), line.end());
-    else if (inContentLength && contentLength)
+    } else if (inContentLength && contentLength) {
       contentLength->append(line.begin(), line.end());
+    }
   }
 }
 
-bool FindMultipartBoundary(raw_istream& is, StringRef boundary,
+bool FindMultipartBoundary(raw_istream& is, std::string_view boundary,
                            std::string* saveBuf) {
   SmallString<64> searchBuf;
   searchBuf.resize(boundary.size() + 2);
@@ -194,7 +213,9 @@
   if (!saveBuf) {
     do {
       is.read(searchBuf.data(), 1);
-      if (is.has_error()) return false;
+      if (is.has_error()) {
+        return false;
+      }
     } while (searchBuf[0] == '\r' || searchBuf[0] == '\n');
     searchPos = 1;
   }
@@ -205,19 +226,26 @@
   // there's a bunch of continuous -'s in the output, but that's unlikely.
   for (;;) {
     is.read(searchBuf.data() + searchPos, searchBuf.size() - searchPos);
-    if (is.has_error()) return false;
+    if (is.has_error()) {
+      return false;
+    }
 
     // Did we find the boundary?
     if (searchBuf[0] == '-' && searchBuf[1] == '-' &&
-        searchBuf.substr(2) == boundary)
+        searchBuf.substr(2) == boundary) {
       return true;
+    }
 
     // Fast-scan for '-'
     size_t pos = searchBuf.find('-', searchBuf[0] == '-' ? 1 : 0);
-    if (pos == StringRef::npos) {
-      if (saveBuf) saveBuf->append(searchBuf.data(), searchBuf.size());
+    if (pos == std::string_view::npos) {
+      if (saveBuf) {
+        saveBuf->append(searchBuf.data(), searchBuf.size());
+      }
     } else {
-      if (saveBuf) saveBuf->append(searchBuf.data(), pos);
+      if (saveBuf) {
+        saveBuf->append(searchBuf.data(), pos);
+      }
 
       // move '-' and following to start of buffer (next read will fill)
       std::memmove(searchBuf.data(), searchBuf.data() + pos,
@@ -227,63 +255,58 @@
   }
 }
 
-HttpLocation::HttpLocation(const Twine& url_, bool* error,
+HttpLocation::HttpLocation(std::string_view url_, bool* error,
                            std::string* errorMsg)
-    : url{url_.str()} {
+    : url{url_} {
   // Split apart into components
-  StringRef query{url};
+  std::string_view query{url};
 
   // scheme:
-  StringRef scheme;
-  std::tie(scheme, query) = query.split(':');
-  if (!scheme.equals_lower("http")) {
+  std::string_view scheme;
+  std::tie(scheme, query) = split(query, ':');
+  if (!equals_lower(scheme, "http")) {
     *errorMsg = "only supports http URLs";
     *error = true;
     return;
   }
 
   // "//"
-  if (!query.startswith("//")) {
+  if (!starts_with(query, "//")) {
     *errorMsg = "expected http://...";
     *error = true;
     return;
   }
-  query = query.drop_front(2);
+  query.remove_prefix(2);
 
   // user:password@host:port/
-  StringRef authority;
-  std::tie(authority, query) = query.split('/');
+  std::string_view authority;
+  std::tie(authority, query) = split(query, '/');
 
-  StringRef userpass, hostport;
-  std::tie(userpass, hostport) = authority.split('@');
+  auto [userpass, hostport] = split(authority, '@');
   // split leaves the RHS empty if the split char isn't present...
   if (hostport.empty()) {
     hostport = userpass;
-    userpass = StringRef{};
+    userpass = {};
   }
 
   if (!userpass.empty()) {
-    StringRef rawUser, rawPassword;
-    std::tie(rawUser, rawPassword) = userpass.split(':');
+    auto [rawUser, rawPassword] = split(userpass, ':');
     SmallString<64> userBuf, passBuf;
     user = UnescapeURI(rawUser, userBuf, error);
     if (*error) {
-      raw_string_ostream oss(*errorMsg);
-      oss << "could not unescape user \"" << rawUser << "\"";
-      oss.flush();
+      *errorMsg = fmt::format("could not unescape user \"{}\"", rawUser);
       return;
     }
     password = UnescapeURI(rawPassword, passBuf, error);
     if (*error) {
-      raw_string_ostream oss(*errorMsg);
-      oss << "could not unescape password \"" << rawPassword << "\"";
-      oss.flush();
+      *errorMsg =
+          fmt::format("could not unescape password \"{}\"", rawPassword);
       return;
     }
   }
 
-  StringRef portStr;
-  std::tie(host, portStr) = hostport.rsplit(':');
+  std::string_view portStr;
+  std::tie(host, portStr) = rsplit(hostport, ':');
   if (host.empty()) {
     *errorMsg = "host is empty";
     *error = true;
@@ -291,44 +314,42 @@
   }
   if (portStr.empty()) {
     port = 80;
-  } else if (portStr.getAsInteger(10, port)) {
-    raw_string_ostream oss(*errorMsg);
-    oss << "port \"" << portStr << "\" is not an integer";
-    oss.flush();
+  } else if (auto p = parse_integer<int>(portStr, 10)) {
+    port = p.value();
+  } else {
+    *errorMsg = fmt::format("port \"{}\" is not an integer", portStr);
     *error = true;
     return;
   }
 
   // path?query#fragment
-  std::tie(query, fragment) = query.split('#');
-  std::tie(path, query) = query.split('?');
+  std::tie(query, fragment) = split(query, '#');
+  std::tie(path, query) = split(query, '?');
 
   // Split query string into parameters
   while (!query.empty()) {
     // split out next param and value
-    StringRef rawParam, rawValue;
-    std::tie(rawParam, query) = query.split('&');
-    if (rawParam.empty()) continue;  // ignore "&&"
-    std::tie(rawParam, rawValue) = rawParam.split('=');
+    std::string_view rawParam, rawValue;
+    std::tie(rawParam, query) = split(query, '&');
+    if (rawParam.empty()) {
+      continue;  // ignore "&&"
+    }
+    std::tie(rawParam, rawValue) = split(rawParam, '=');
 
     // unescape param
     *error = false;
     SmallString<64> paramBuf;
-    StringRef param = UnescapeURI(rawParam, paramBuf, error);
+    std::string_view param = UnescapeURI(rawParam, paramBuf, error);
     if (*error) {
-      raw_string_ostream oss(*errorMsg);
-      oss << "could not unescape parameter \"" << rawParam << "\"";
-      oss.flush();
+      *errorMsg = fmt::format("could not unescape parameter \"{}\"", rawParam);
       return;
     }
 
     // unescape value
     SmallString<64> valueBuf;
-    StringRef value = UnescapeURI(rawValue, valueBuf, error);
+    std::string_view value = UnescapeURI(rawValue, valueBuf, error);
     if (*error) {
-      raw_string_ostream oss(*errorMsg);
-      oss << "could not unescape value \"" << rawValue << "\"";
-      oss.flush();
+      *errorMsg = fmt::format("could not unescape value \"{}\"", rawValue);
       return;
     }
 
@@ -344,7 +365,7 @@
     userpass += loc.user;
     userpass += ':';
     userpass += loc.password;
-    Base64Encode(userpass, &auth);
+    Base64Encode(userpass.str(), &auth);
   }
 }
 
@@ -353,31 +374,30 @@
   // send GET request
   os << "GET /" << request.path << " HTTP/1.1\r\n";
   os << "Host: " << request.host << "\r\n";
-  if (!request.auth.empty())
+  if (!request.auth.empty()) {
     os << "Authorization: Basic " << request.auth << "\r\n";
+  }
   os << "\r\n";
   os.flush();
 
   // read first line of response
   SmallString<64> lineBuf;
-  StringRef line = is.getline(lineBuf, 1024).rtrim();
+  std::string_view line = rtrim(is.getline(lineBuf, 1024));
   if (is.has_error()) {
     *warnMsg = "disconnected before response";
     return false;
   }
 
   // see if we got a HTTP 200 response
-  StringRef httpver, code, codeText;
-  std::tie(httpver, line) = line.split(' ');
-  std::tie(code, codeText) = line.split(' ');
-  if (!httpver.startswith("HTTP")) {
+  std::string_view httpver, code, codeText;
+  std::tie(httpver, line) = split(line, ' ');
+  std::tie(code, codeText) = split(line, ' ');
+  if (!starts_with(httpver, "HTTP")) {
     *warnMsg = "did not receive HTTP response";
     return false;
   }
   if (code != "200") {
-    raw_string_ostream oss(*warnMsg);
-    oss << "received " << code << " " << codeText << " response";
-    oss.flush();
+    *warnMsg = fmt::format("received {} {} response", code, codeText);
     return false;
   }
 
@@ -390,7 +410,7 @@
   return true;
 }
 
-void HttpMultipartScanner::SetBoundary(StringRef boundary) {
+void HttpMultipartScanner::SetBoundary(std::string_view boundary) {
   m_boundaryWith = "\n--";
   m_boundaryWith += boundary;
   m_boundaryWithout = "\n";
@@ -406,9 +426,13 @@
   m_buf.resize(0);
 }
 
-StringRef HttpMultipartScanner::Execute(StringRef in) {
-  if (m_state == kDone) Reset(m_saveSkipped);
-  if (m_saveSkipped) m_buf += in;
+std::string_view HttpMultipartScanner::Execute(std::string_view in) {
+  if (m_state == kDone) {
+    Reset(m_saveSkipped);
+  }
+  if (m_saveSkipped) {
+    m_buf += in;
+  }
 
   size_t pos = 0;
   if (m_state == kBoundary) {
@@ -449,19 +473,21 @@
   }
 
   if (m_state == kPadding) {
-    for (char ch : in.drop_front(pos)) {
+    for (char ch : drop_front(in, pos)) {
       ++pos;
       if (ch == '\n') {
         // Found the LF; return remaining input buffer (following it)
         m_state = kDone;
-        if (m_saveSkipped) m_buf.resize(m_buf.size() - in.size() + pos);
-        return in.drop_front(pos);
+        if (m_saveSkipped) {
+          m_buf.resize(m_buf.size() - in.size() + pos);
+        }
+        return drop_front(in, pos);
       }
     }
   }
 
   // We consumed the entire input
-  return StringRef{};
+  return {};
 }
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/Logger.cpp b/wpiutil/src/main/native/cpp/Logger.cpp
new file mode 100644
index 0000000..2b8ec6d
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/Logger.cpp
@@ -0,0 +1,26 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/Logger.h"
+
+using namespace wpi;
+
+void Logger::DoLog(unsigned int level, const char* file, unsigned int line,
+                   const char* msg) {
+  if (!m_func || level < m_min_level) {
+    return;
+  }
+  m_func(level, file, line, msg);
+}
+
+void Logger::LogV(unsigned int level, const char* file, unsigned int line,
+                  fmt::string_view format, fmt::format_args args) {
+  if (!m_func || level < m_min_level) {
+    return;
+  }
+  fmt::memory_buffer out;
+  fmt::vformat_to(fmt::appender{out}, format, args);
+  out.push_back('\0');
+  m_func(level, file, line, out.data());
+}
diff --git a/wpiutil/src/main/native/cpp/MimeTypes.cpp b/wpiutil/src/main/native/cpp/MimeTypes.cpp
index 5bf9d29..082dab1 100644
--- a/wpiutil/src/main/native/cpp/MimeTypes.cpp
+++ b/wpiutil/src/main/native/cpp/MimeTypes.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/MimeTypes.h"
 
@@ -13,7 +10,7 @@
 
 // derived partially from
 // https://github.com/DEGoodmanWilson/libmime/blob/stable/0.1.2/mime/mime.cpp
-StringRef MimeTypeFromPath(StringRef path) {
+std::string_view MimeTypeFromPath(std::string_view path) {
   // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
   static StringMap<const char*> mimeTypes{
       // text
@@ -55,11 +52,11 @@
   static const char* defaultType = "application/octet-stream";
 
   auto pos = path.find_last_of("/");
-  if (pos != StringRef::npos) {
+  if (pos != std::string_view::npos) {
     path = path.substr(pos + 1);
   }
   auto dot_pos = path.find_last_of(".");
-  if (dot_pos > 0 && dot_pos != StringRef::npos) {
+  if (dot_pos > 0 && dot_pos != std::string_view::npos) {
     auto type = mimeTypes.find(path.substr(dot_pos + 1));
     if (type != mimeTypes.end()) {
       return type->getValue();
diff --git a/wpiutil/src/main/native/cpp/ParallelTcpConnector.cpp b/wpiutil/src/main/native/cpp/ParallelTcpConnector.cpp
new file mode 100644
index 0000000..5a8394a
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/ParallelTcpConnector.cpp
@@ -0,0 +1,177 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/ParallelTcpConnector.h"
+
+#include <fmt/format.h>
+
+#include "wpi/Logger.h"
+#include "wpi/uv/GetAddrInfo.h"
+#include "wpi/uv/Loop.h"
+#include "wpi/uv/Tcp.h"
+#include "wpi/uv/Timer.h"
+#include "wpi/uv/util.h"
+
+using namespace wpi;
+
+ParallelTcpConnector::ParallelTcpConnector(
+    wpi::uv::Loop& loop, wpi::uv::Timer::Time reconnectRate,
+    wpi::Logger& logger, std::function<void(wpi::uv::Tcp& tcp)> connected,
+    const private_init&)
+    : m_loop{loop},
+      m_logger{logger},
+      m_reconnectRate{reconnectRate},
+      m_connected{std::move(connected)},
+      m_reconnectTimer{uv::Timer::Create(loop)} {
+  m_reconnectTimer->timeout.connect([this] {
+    if (!IsConnected()) {
+      WPI_DEBUG1(m_logger, "{}", "timed out, reconnecting");
+      Connect();
+    }
+  });
+}
+
+ParallelTcpConnector::~ParallelTcpConnector() = default;
+
+void ParallelTcpConnector::Close() {
+  CancelAll();
+  m_reconnectTimer->Close();
+}
+
+void ParallelTcpConnector::SetServers(
+    wpi::span<const std::pair<std::string, unsigned int>> servers) {
+  m_servers.assign(servers.begin(), servers.end());
+  if (!IsConnected()) {
+    Connect();
+  }
+}
+
+void ParallelTcpConnector::Disconnected() {
+  if (m_isConnected) {
+    m_isConnected = false;
+    Connect();
+  }
+}
+
+void ParallelTcpConnector::Succeeded(uv::Tcp& tcp) {
+  if (!m_isConnected) {
+    m_isConnected = true;
+    m_reconnectTimer->Stop();
+    CancelAll(&tcp);
+  }
+}
+
+void ParallelTcpConnector::Connect() {
+  if (IsConnected()) {
+    return;
+  }
+
+  CancelAll();
+  m_reconnectTimer->Start(m_reconnectRate);
+
+  WPI_DEBUG3(m_logger, "{}", "starting new connection attempts");
+
+  // kick off parallel lookups
+  for (auto&& server : m_servers) {
+    auto req = std::make_shared<uv::GetAddrInfoReq>();
+    m_resolvers.emplace_back(req);
+
+    req->resolved.connect(
+        [this, req = req.get()](const addrinfo& addrinfo) {
+          if (IsConnected()) {
+            return;
+          }
+
+          // kick off parallel connection attempts
+          for (auto ai = &addrinfo; ai; ai = ai->ai_next) {
+            auto tcp = uv::Tcp::Create(m_loop);
+            m_attempts.emplace_back(tcp);
+
+            auto connreq = std::make_shared<uv::TcpConnectReq>();
+            connreq->connected.connect(
+                [this, tcp = tcp.get()] {
+                  if (m_logger.min_level() <= wpi::WPI_LOG_DEBUG4) {
+                    std::string ip;
+                    unsigned int port = 0;
+                    uv::AddrToName(tcp->GetPeer(), &ip, &port);
+                    WPI_DEBUG4(m_logger,
+                               "successful connection ({}) to {} port {}",
+                               static_cast<void*>(tcp), ip, port);
+                  }
+                  if (IsConnected()) {
+                    tcp->Shutdown([tcp] { tcp->Close(); });
+                    return;
+                  }
+                  if (m_connected) {
+                    m_connected(*tcp);
+                  }
+                },
+                shared_from_this());
+
+            connreq->error = [selfWeak = weak_from_this(),
+                              tcp = tcp.get()](uv::Error err) {
+              if (auto self = selfWeak.lock()) {
+                WPI_DEBUG1(self->m_logger, "connect failure ({}): {}",
+                           static_cast<void*>(tcp), err.str());
+              }
+            };
+
+            if (m_logger.min_level() <= wpi::WPI_LOG_DEBUG4) {
+              std::string ip;
+              unsigned int port = 0;
+              uv::AddrToName(*reinterpret_cast<sockaddr_storage*>(ai->ai_addr),
+                             &ip, &port);
+              WPI_DEBUG4(
+                  m_logger,
+                  "Info({}) starting connection attempt ({}) to {} port {}",
+                  static_cast<void*>(req), static_cast<void*>(tcp.get()), ip,
+                  port);
+            }
+            tcp->Connect(*ai->ai_addr, connreq);
+          }
+        },
+        shared_from_this());
+
+    req->error = [req = req.get(), selfWeak = weak_from_this()](uv::Error err) {
+      if (auto self = selfWeak.lock()) {
+        WPI_DEBUG1(self->m_logger, "GetAddrInfo({}) failure: {}",
+                   static_cast<void*>(req), err.str());
+      }
+    };
+
+    WPI_DEBUG4(m_logger, "starting GetAddrInfo({}) for {} port {}",
+               static_cast<void*>(req.get()), server.first, server.second);
+    addrinfo hints;
+    std::memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = IPPROTO_TCP;
+    hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;
+    uv::GetAddrInfo(m_loop, req, server.first, fmt::format("{}", server.second),
+                    &hints);
+  }
+}
+
+void ParallelTcpConnector::CancelAll(wpi::uv::Tcp* except) {
+  WPI_DEBUG4(m_logger, "{}", "canceling previous attempts");
+  for (auto&& resolverWeak : m_resolvers) {
+    if (auto resolver = resolverWeak.lock()) {
+      WPI_DEBUG4(m_logger, "canceling GetAddrInfo({})",
+                 static_cast<void*>(resolver.get()));
+      resolver->Cancel();
+    }
+  }
+  m_resolvers.clear();
+
+  for (auto&& tcpWeak : m_attempts) {
+    if (auto tcp = tcpWeak.lock()) {
+      if (tcp.get() != except) {
+        WPI_DEBUG4(m_logger, "canceling connection attempt ({})",
+                   static_cast<void*>(tcp.get()));
+        tcp->Close();
+      }
+    }
+  }
+  m_attempts.clear();
+}
diff --git a/wpiutil/src/main/native/cpp/PortForwarder.cpp b/wpiutil/src/main/native/cpp/PortForwarder.cpp
index fe54b63..a423d48 100644
--- a/wpiutil/src/main/native/cpp/PortForwarder.cpp
+++ b/wpiutil/src/main/native/cpp/PortForwarder.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/PortForwarder.h"
 
+#include "fmt/format.h"
 #include "wpi/DenseMap.h"
 #include "wpi/EventLoopRunner.h"
-#include "wpi/SmallString.h"
-#include "wpi/raw_ostream.h"
 #include "wpi/uv/GetAddrInfo.h"
 #include "wpi/uv/Tcp.h"
 #include "wpi/uv/Timer.h"
@@ -36,16 +32,19 @@
     buf2.len = len;
     auto out = outWeak.lock();
     if (!out) {
+      buf2.Deallocate();
       in.Close();
       return;
     }
-    out->Write(buf2, [](auto bufs, uv::Error) {
-      for (auto buf : bufs) buf.Deallocate();
+    out->Write({buf2}, [](auto bufs, uv::Error) {
+      for (auto buf : bufs) {
+        buf.Deallocate();
+      }
     });
   });
 }
 
-void PortForwarder::Add(unsigned int port, const Twine& remoteHost,
+void PortForwarder::Add(unsigned int port, std::string_view remoteHost,
                         unsigned int remotePort) {
   m_impl->runner.ExecSync([&](uv::Loop& loop) {
     auto server = uv::Tcp::Create(loop);
@@ -55,10 +54,12 @@
 
     // when we get a connection, accept it
     server->connection.connect([serverPtr = server.get(),
-                                host = remoteHost.str(), remotePort] {
+                                host = std::string{remoteHost}, remotePort] {
       auto& loop = serverPtr->GetLoopRef();
       auto client = serverPtr->Accept();
-      if (!client) return;
+      if (!client) {
+        return;
+      }
 
       // close on error
       client->error.connect(
@@ -73,20 +74,20 @@
           [remotePtr = remote.get(),
            clientWeak = std::weak_ptr<uv::Tcp>(client)](uv::Error err) {
             remotePtr->Close();
-            if (auto client = clientWeak.lock()) client->Close();
+            if (auto client = clientWeak.lock()) {
+              client->Close();
+            }
           });
 
-      // convert port to string
-      SmallString<16> remotePortStr;
-      raw_svector_ostream(remotePortStr) << remotePort;
-
       // resolve address
       uv::GetAddrInfo(
           loop,
           [clientWeak = std::weak_ptr<uv::Tcp>(client),
            remoteWeak = std::weak_ptr<uv::Tcp>(remote)](const addrinfo& addr) {
             auto remote = remoteWeak.lock();
-            if (!remote) return;
+            if (!remote) {
+              return;
+            }
 
             // connect to remote address/port
             remote->Connect(*addr.ai_addr, [remotePtr = remote.get(),
@@ -101,11 +102,15 @@
               // close both when either side closes
               client->end.connect([clientPtr = client.get(), remoteWeak] {
                 clientPtr->Close();
-                if (auto remote = remoteWeak.lock()) remote->Close();
+                if (auto remote = remoteWeak.lock()) {
+                  remote->Close();
+                }
               });
               remotePtr->end.connect([remotePtr, clientWeak] {
                 remotePtr->Close();
-                if (auto client = clientWeak.lock()) client->Close();
+                if (auto client = clientWeak.lock()) {
+                  client->Close();
+                }
               });
 
               // copy bidirectionally
@@ -115,7 +120,7 @@
               CopyStream(*remotePtr, clientWeak);
             });
           },
-          host, remotePortStr);
+          host, fmt::to_string(remotePort));
 
       // time out for connection
       uv::Timer::SingleShot(loop, uv::Timer::Time{500},
@@ -124,10 +129,12 @@
                              remoteWeak = std::weak_ptr<uv::Tcp>(remote)] {
                               if (auto connected = connectedWeak.lock()) {
                                 if (!*connected) {
-                                  if (auto client = clientWeak.lock())
+                                  if (auto client = clientWeak.lock()) {
                                     client->Close();
-                                  if (auto remote = remoteWeak.lock())
+                                  }
+                                  if (auto remote = remoteWeak.lock()) {
                                     remote->Close();
+                                  }
                                 }
                               }
                             });
diff --git a/wpiutil/src/main/native/cpp/SafeThread.cpp b/wpiutil/src/main/native/cpp/SafeThread.cpp
index 3a906bd..bbecc5c 100644
--- a/wpiutil/src/main/native/cpp/SafeThread.cpp
+++ b/wpiutil/src/main/native/cpp/SafeThread.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/SafeThread.h"
 
@@ -12,7 +9,9 @@
 detail::SafeThreadProxyBase::SafeThreadProxyBase(
     std::shared_ptr<SafeThread> thr)
     : m_thread(std::move(thr)) {
-  if (!m_thread) return;
+  if (!m_thread) {
+    return;
+  }
   m_lock = std::unique_lock<wpi::mutex>(m_thread->m_mutex);
   if (!m_thread->m_active) {
     m_lock.unlock();
@@ -22,15 +21,18 @@
 }
 
 detail::SafeThreadOwnerBase::~SafeThreadOwnerBase() {
-  if (m_joinAtExit)
+  if (m_joinAtExit) {
     Join();
-  else
+  } else {
     Stop();
+  }
 }
 
 void detail::SafeThreadOwnerBase::Start(std::shared_ptr<SafeThread> thr) {
   std::scoped_lock lock(m_mutex);
-  if (auto thr = m_thread.lock()) return;
+  if (auto thr = m_thread.lock()) {
+    return;
+  }
   m_stdThread = std::thread([=] { thr->Main(); });
   thr->m_threadId = m_stdThread.get_id();
   m_thread = thr;
@@ -43,7 +45,9 @@
     thr->m_cond.notify_all();
     m_thread.reset();
   }
-  if (m_stdThread.joinable()) m_stdThread.detach();
+  if (m_stdThread.joinable()) {
+    m_stdThread.detach();
+  }
 }
 
 void detail::SafeThreadOwnerBase::Join() {
@@ -62,7 +66,9 @@
 
 void detail::swap(SafeThreadOwnerBase& lhs, SafeThreadOwnerBase& rhs) noexcept {
   using std::swap;
-  if (&lhs == &rhs) return;
+  if (&lhs == &rhs) {
+    return;
+  }
   std::scoped_lock lock(lhs.m_mutex, rhs.m_mutex);
   std::swap(lhs.m_stdThread, rhs.m_stdThread);
   std::swap(lhs.m_thread, rhs.m_thread);
diff --git a/wpiutil/src/main/native/cpp/SocketError.cpp b/wpiutil/src/main/native/cpp/SocketError.cpp
index 1695846..3f08d1e 100644
--- a/wpiutil/src/main/native/cpp/SocketError.cpp
+++ b/wpiutil/src/main/native/cpp/SocketError.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/SocketError.h"
 
diff --git a/wpiutil/src/main/native/cpp/StringExtras.cpp b/wpiutil/src/main/native/cpp/StringExtras.cpp
new file mode 100644
index 0000000..45d23ea
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/StringExtras.cpp
@@ -0,0 +1,360 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+//===-- StringExtras.cpp - Implement the StringExtras header --------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the StringExtras.h header
+//
+//===----------------------------------------------------------------------===//
+
+#include "wpi/StringExtras.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <string_view>
+
+#include "wpi/SmallString.h"
+#include "wpi/SmallVector.h"
+
+// strncasecmp() is not available on non-POSIX systems, so define an
+// alternative function here.
+static int ascii_strncasecmp(const char* lhs, const char* rhs,
+                             size_t length) noexcept {
+  for (size_t i = 0; i < length; ++i) {
+    unsigned char lhc = wpi::toLower(lhs[i]);
+    unsigned char rhc = wpi::toLower(rhs[i]);
+    if (lhc != rhc) {
+      return lhc < rhc ? -1 : 1;
+    }
+  }
+  return 0;
+}
+
+int wpi::compare_lower(std::string_view lhs, std::string_view rhs) noexcept {
+  if (int Res = ascii_strncasecmp(lhs.data(), rhs.data(),
+                                  (std::min)(lhs.size(), rhs.size()))) {
+    return Res;
+  }
+  if (lhs.size() == rhs.size()) {
+    return 0;
+  }
+  return lhs.size() < rhs.size() ? -1 : 1;
+}
+
+std::string_view::size_type wpi::find_lower(
+    std::string_view str, char ch, std::string_view::size_type from) noexcept {
+  char lch = toLower(ch);
+  auto s = drop_front(str, from);
+  while (!s.empty()) {
+    if (toLower(s.front()) == lch) {
+      return str.size() - s.size();
+    }
+    s.remove_prefix(1);
+  }
+  return std::string_view::npos;
+}
+
+std::string_view::size_type wpi::find_lower(
+    std::string_view str, std::string_view other,
+    std::string_view::size_type from) noexcept {
+  auto s = str.substr(from);
+  while (s.size() >= other.size()) {
+    if (starts_with_lower(s, other)) {
+      return from;
+    }
+    s.remove_prefix(1);
+    ++from;
+  }
+  return std::string_view::npos;
+}
+
+std::string_view::size_type wpi::rfind_lower(
+    std::string_view str, char ch, std::string_view::size_type from) noexcept {
+  from = (std::min)(from, str.size());
+  auto data = str.data();
+  std::string_view::size_type i = from;
+  while (i != 0) {
+    --i;
+    if (toLower(data[i]) == toLower(ch)) {
+      return i;
+    }
+  }
+  return std::string_view::npos;
+}
+
+std::string_view::size_type wpi::rfind_lower(std::string_view str,
+                                             std::string_view other) noexcept {
+  std::string_view::size_type n = other.size();
+  if (n > str.size()) {
+    return std::string_view::npos;
+  }
+  for (size_t i = str.size() - n + 1, e = 0; i != e;) {
+    --i;
+    if (equals_lower(str.substr(i, n), other)) {
+      return i;
+    }
+  }
+  return std::string_view::npos;
+}
+
+bool wpi::starts_with_lower(std::string_view str,
+                            std::string_view prefix) noexcept {
+  return str.size() >= prefix.size() &&
+         ascii_strncasecmp(str.data(), prefix.data(), prefix.size()) == 0;
+}
+
+bool wpi::ends_with_lower(std::string_view str,
+                          std::string_view suffix) noexcept {
+  return str.size() >= suffix.size() &&
+         ascii_strncasecmp(str.data() + str.size() - suffix.size(),
+                           suffix.data(), suffix.size()) == 0;
+}
+
+void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
+                std::string_view separator, int maxSplit,
+                bool keepEmpty) noexcept {
+  std::string_view s = str;
+
+  // Count down from maxSplit. When maxSplit is -1, this will just split
+  // "forever". This doesn't support splitting more than 2^31 times
+  // intentionally; if we ever want that we can make maxSplit a 64-bit integer
+  // but that seems unlikely to be useful.
+  while (maxSplit-- != 0) {
+    auto idx = s.find(separator);
+    if (idx == std::string_view::npos) {
+      break;
+    }
+
+    // Push this split.
+    if (keepEmpty || idx > 0) {
+      arr.push_back(slice(s, 0, idx));
+    }
+
+    // Jump forward.
+    s = slice(s, idx + separator.size(), std::string_view::npos);
+  }
+
+  // Push the tail.
+  if (keepEmpty || !s.empty()) {
+    arr.push_back(s);
+  }
+}
+
+void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
+                char separator, int maxSplit, bool keepEmpty) noexcept {
+  std::string_view s = str;
+
+  // Count down from maxSplit. When maxSplit is -1, this will just split
+  // "forever". This doesn't support splitting more than 2^31 times
+  // intentionally; if we ever want that we can make maxSplit a 64-bit integer
+  // but that seems unlikely to be useful.
+  while (maxSplit-- != 0) {
+    size_t idx = s.find(separator);
+    if (idx == std::string_view::npos) {
+      break;
+    }
+
+    // Push this split.
+    if (keepEmpty || idx > 0) {
+      arr.push_back(slice(s, 0, idx));
+    }
+
+    // Jump forward.
+    s = slice(s, idx + 1, std::string_view::npos);
+  }
+
+  // Push the tail.
+  if (keepEmpty || !s.empty()) {
+    arr.push_back(s);
+  }
+}
+
+static unsigned GetAutoSenseRadix(std::string_view& str) noexcept {
+  if (str.empty()) {
+    return 10;
+  }
+
+  if (wpi::starts_with(str, "0x") || wpi::starts_with(str, "0X")) {
+    str.remove_prefix(2);
+    return 16;
+  }
+
+  if (wpi::starts_with(str, "0b") || wpi::starts_with(str, "0B")) {
+    str.remove_prefix(2);
+    return 2;
+  }
+
+  if (wpi::starts_with(str, "0o")) {
+    str.remove_prefix(2);
+    return 8;
+  }
+
+  if (str[0] == '0' && str.size() > 1 && wpi::isDigit(str[1])) {
+    str.remove_prefix(1);
+    return 8;
+  }
+
+  return 10;
+}
+
+bool wpi::detail::ConsumeUnsignedInteger(
+    std::string_view& str, unsigned radix,
+    unsigned long long& result) noexcept {  // NOLINT(runtime/int)
+  // Autosense radix if not specified.
+  if (radix == 0) {
+    radix = GetAutoSenseRadix(str);
+  }
+
+  // Empty strings (after the radix autosense) are invalid.
+  if (str.empty()) {
+    return true;
+  }
+
+  // Parse all the bytes of the string given this radix.  Watch for overflow.
+  std::string_view str2 = str;
+  result = 0;
+  while (!str2.empty()) {
+    unsigned charVal;
+    if (str2[0] >= '0' && str2[0] <= '9') {
+      charVal = str2[0] - '0';
+    } else if (str2[0] >= 'a' && str2[0] <= 'z') {
+      charVal = str2[0] - 'a' + 10;
+    } else if (str2[0] >= 'A' && str2[0] <= 'Z') {
+      charVal = str2[0] - 'A' + 10;
+    } else {
+      break;
+    }
+
+    // If the parsed value is larger than the integer radix, we cannot
+    // consume any more characters.
+    if (charVal >= radix) {
+      break;
+    }
+
+    // Add in this character.
+    unsigned long long prevResult = result;  // NOLINT(runtime/int)
+    result = result * radix + charVal;
+
+    // Check for overflow by shifting back and seeing if bits were lost.
+    if (result / radix < prevResult) {
+      return true;
+    }
+
+    str2.remove_prefix(1);
+  }
+
+  // We consider the operation a failure if no characters were consumed
+  // successfully.
+  if (str.size() == str2.size()) {
+    return true;
+  }
+
+  str = str2;
+  return false;
+}
+
+bool wpi::detail::ConsumeSignedInteger(
+    std::string_view& str, unsigned radix,
+    long long& result) noexcept {  // NOLINT(runtime/int)
+  unsigned long long ullVal;       // NOLINT(runtime/int)
+
+  // Handle positive strings first.
+  if (str.empty() || str.front() != '-') {
+    if (wpi::detail::ConsumeUnsignedInteger(str, radix, ullVal) ||
+        // Check for value so large it overflows a signed value.
+        static_cast<long long>(ullVal) < 0) {  // NOLINT(runtime/int)
+      return true;
+    }
+    result = ullVal;
+    return false;
+  }
+
+  // Get the positive part of the value.
+  std::string_view str2 = wpi::drop_front(str, 1);
+  if (wpi::detail::ConsumeUnsignedInteger(str2, radix, ullVal) ||
+      // Reject values so large they'd overflow as negative signed, but allow
+      // "-0".  This negates the unsigned so that the negative isn't undefined
+      // on signed overflow.
+      static_cast<long long>(-ullVal) > 0) {  // NOLINT(runtime/int)
+    return true;
+  }
+
+  str = str2;
+  result = -ullVal;
+  return false;
+}
+
+bool wpi::detail::GetAsUnsignedInteger(
+    std::string_view str, unsigned radix,
+    unsigned long long& result) noexcept {  // NOLINT(runtime/int)
+  if (wpi::detail::ConsumeUnsignedInteger(str, radix, result)) {
+    return true;
+  }
+
+  // For getAsUnsignedInteger, we require the whole string to be consumed or
+  // else we consider it a failure.
+  return !str.empty();
+}
+
+bool wpi::detail::GetAsSignedInteger(
+    std::string_view str, unsigned radix,
+    long long& result) noexcept {  // NOLINT(runtime/int)
+  if (wpi::detail::ConsumeSignedInteger(str, radix, result)) {
+    return true;
+  }
+
+  // For getAsSignedInteger, we require the whole string to be consumed or else
+  // we consider it a failure.
+  return !str.empty();
+}
+
+template <>
+std::optional<float> wpi::parse_float<float>(std::string_view str) noexcept {
+  if (str.empty()) {
+    return std::nullopt;
+  }
+  wpi::SmallString<32> storage{str};
+  char* end;
+  float val = std::strtof(storage.c_str(), &end);
+  if (*end != '\0') {
+    return std::nullopt;
+  }
+  return val;
+}
+
+template <>
+std::optional<double> wpi::parse_float<double>(std::string_view str) noexcept {
+  if (str.empty()) {
+    return std::nullopt;
+  }
+  wpi::SmallString<32> storage{str};
+  char* end;
+  double val = std::strtod(storage.c_str(), &end);
+  if (*end != '\0') {
+    return std::nullopt;
+  }
+  return val;
+}
+
+template <>
+std::optional<long double> wpi::parse_float<long double>(
+    std::string_view str) noexcept {
+  if (str.empty()) {
+    return std::nullopt;
+  }
+  wpi::SmallString<32> storage{str};
+  char* end;
+  long double val = std::strtold(storage.c_str(), &end);
+  if (*end != '\0') {
+    return std::nullopt;
+  }
+  return val;
+}
diff --git a/wpiutil/src/main/native/cpp/Synchronization.cpp b/wpiutil/src/main/native/cpp/Synchronization.cpp
new file mode 100644
index 0000000..da97897
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/Synchronization.cpp
@@ -0,0 +1,368 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/Synchronization.h"
+
+#include <algorithm>
+#include <cstring>
+#include <mutex>
+
+#include "wpi/DenseMap.h"
+#include "wpi/SmallVector.h"
+#include "wpi/UidVector.h"
+#include "wpi/condition_variable.h"
+#include "wpi/mutex.h"
+
+using namespace wpi;
+
+namespace {
+
+struct State {
+  int signaled{0};
+  bool autoReset{false};
+  wpi::SmallVector<wpi::condition_variable*, 2> waiters;
+};
+
+struct HandleManager {
+  wpi::mutex mutex;
+  wpi::UidVector<int, 8> eventIds;
+  wpi::UidVector<int, 8> semaphoreIds;
+  wpi::DenseMap<WPI_Handle, State> states;
+};
+
+}  // namespace
+
+static HandleManager& GetManager() {
+  static HandleManager manager;
+  return manager;
+}
+
+WPI_EventHandle wpi::CreateEvent(bool manualReset, bool initialState) {
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+
+  auto index = manager.eventIds.emplace_back(0);
+  WPI_EventHandle handle = (kHandleTypeEvent << 24) | (index & 0xffffff);
+
+  // configure state data
+  auto& state = manager.states[handle];
+  state.signaled = initialState ? 1 : 0;
+  state.autoReset = !manualReset;
+
+  return handle;
+}
+
+void wpi::DestroyEvent(WPI_EventHandle handle) {
+  if ((handle >> 24) != kHandleTypeEvent) {
+    return;
+  }
+
+  DestroySignalObject(handle);
+
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+  manager.eventIds.erase(handle & 0xffffff);
+}
+
+void wpi::SetEvent(WPI_EventHandle handle) {
+  if ((handle >> 24) != kHandleTypeEvent) {
+    return;
+  }
+
+  SetSignalObject(handle);
+}
+
+void wpi::ResetEvent(WPI_EventHandle handle) {
+  if ((handle >> 24) != kHandleTypeEvent) {
+    return;
+  }
+
+  ResetSignalObject(handle);
+}
+
+WPI_SemaphoreHandle wpi::CreateSemaphore(int initialCount, int maximumCount) {
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+
+  auto index = manager.semaphoreIds.emplace_back(maximumCount);
+  WPI_EventHandle handle = (kHandleTypeSemaphore << 24) | (index & 0xffffff);
+
+  // configure state data
+  auto& state = manager.states[handle];
+  state.signaled = initialCount;
+  state.autoReset = true;
+
+  return handle;
+}
+
+void wpi::DestroySemaphore(WPI_SemaphoreHandle handle) {
+  if ((handle >> 24) != kHandleTypeSemaphore) {
+    return;
+  }
+
+  DestroySignalObject(handle);
+
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+  manager.eventIds.erase(handle & 0xffffff);
+}
+
+bool wpi::ReleaseSemaphore(WPI_SemaphoreHandle handle, int releaseCount,
+                           int* prevCount) {
+  if ((handle >> 24) != kHandleTypeSemaphore) {
+    return false;
+  }
+  if (releaseCount <= 0) {
+    return false;
+  }
+  int index = handle & 0xffffff;
+
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+  auto it = manager.states.find(handle);
+  if (it == manager.states.end()) {
+    return false;
+  }
+  auto& state = it->second;
+  int maxCount = manager.eventIds[index];
+  if (prevCount) {
+    *prevCount = state.signaled;
+  }
+  if ((maxCount - state.signaled) < releaseCount) {
+    return false;
+  }
+  state.signaled += releaseCount;
+  for (auto& waiter : state.waiters) {
+    waiter->notify_all();
+  }
+  return true;
+}
+
+bool wpi::WaitForObject(WPI_Handle handle) {
+  return WaitForObject(handle, -1, nullptr);
+}
+
+bool wpi::WaitForObject(WPI_Handle handle, double timeout, bool* timedOut) {
+  WPI_Handle signaledValue;
+  auto signaled = WaitForObjects(
+      wpi::span(&handle, 1), wpi::span(&signaledValue, 1), timeout, timedOut);
+  if (signaled.empty()) {
+    return false;
+  }
+  return (signaled[0] & 0x80000000ul) == 0;
+}
+
+wpi::span<WPI_Handle> wpi::WaitForObjects(wpi::span<const WPI_Handle> handles,
+                                          wpi::span<WPI_Handle> signaled) {
+  return WaitForObjects(handles, signaled, -1, nullptr);
+}
+
+wpi::span<WPI_Handle> wpi::WaitForObjects(wpi::span<const WPI_Handle> handles,
+                                          wpi::span<WPI_Handle> signaled,
+                                          double timeout, bool* timedOut) {
+  auto& manager = GetManager();
+  std::unique_lock lock{manager.mutex};
+  wpi::condition_variable cv;
+  bool addedWaiters = false;
+  bool timedOutVal = false;
+  size_t count = 0;
+
+  for (;;) {
+    for (auto handle : handles) {
+      auto it = manager.states.find(handle);
+      if (it == manager.states.end()) {
+        if (count < signaled.size()) {
+          // treat a non-existent handle as signaled, but set the error bit
+          signaled[count++] = handle | 0x80000000ul;
+        }
+      } else {
+        auto& state = it->second;
+        if (state.signaled > 0) {
+          if (count < signaled.size()) {
+            signaled[count++] = handle;
+          }
+          if (state.autoReset) {
+            --state.signaled;
+            if (state.signaled < 0) {
+              state.signaled = 0;
+            }
+          }
+        }
+      }
+    }
+
+    if (timedOutVal || count != 0) {
+      break;
+    }
+
+    if (timeout == 0) {
+      timedOutVal = true;
+      break;
+    }
+
+    if (!addedWaiters) {
+      addedWaiters = true;
+      for (auto handle : handles) {
+        auto& state = manager.states[handle];
+        state.waiters.emplace_back(&cv);
+      }
+    }
+
+    if (timeout < 0) {
+      cv.wait(lock);
+    } else {
+      auto timeoutTime = std::chrono::steady_clock::now() +
+                         std::chrono::duration<double>(timeout);
+      if (cv.wait_until(lock, timeoutTime) == std::cv_status::timeout) {
+        timedOutVal = true;
+      }
+    }
+  }
+
+  if (addedWaiters) {
+    for (auto handle : handles) {
+      auto& state = manager.states[handle];
+      auto it = std::find(state.waiters.begin(), state.waiters.end(), &cv);
+      if (it != state.waiters.end()) {
+        state.waiters.erase(it);
+      }
+    }
+  }
+
+  if (timedOut) {
+    *timedOut = timedOutVal;
+  }
+
+  return signaled.subspan(0, count);
+}
+
+void wpi::CreateSignalObject(WPI_Handle handle, bool manualReset,
+                             bool initialState) {
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+  auto& state = manager.states[handle];
+  state.signaled = initialState ? 1 : 0;
+  state.autoReset = !manualReset;
+}
+
+void wpi::SetSignalObject(WPI_Handle handle) {
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+  auto it = manager.states.find(handle);
+  if (it == manager.states.end()) {
+    return;
+  }
+  auto& state = it->second;
+  state.signaled = 1;
+  for (auto& waiter : state.waiters) {
+    waiter->notify_all();
+    if (state.autoReset) {
+      // expect the first waiter to reset it
+      break;
+    }
+  }
+}
+
+void wpi::ResetSignalObject(WPI_Handle handle) {
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+  auto it = manager.states.find(handle);
+  if (it != manager.states.end()) {
+    it->second.signaled = 0;
+  }
+}
+
+void wpi::DestroySignalObject(WPI_Handle handle) {
+  auto& manager = GetManager();
+  std::scoped_lock lock{manager.mutex};
+
+  auto it = manager.states.find(handle);
+  if (it != manager.states.end()) {
+    // wake up any waiters
+    for (auto& waiter : it->second.waiters) {
+      waiter->notify_all();
+    }
+    manager.states.erase(it);
+  }
+}
+
+extern "C" {
+
+WPI_EventHandle WPI_CreateEvent(int manual_reset, int initial_state) {
+  return wpi::CreateEvent(manual_reset != 0, initial_state != 0);
+}
+
+void WPI_DestroyEvent(WPI_EventHandle handle) {
+  wpi::DestroyEvent(handle);
+}
+
+void WPI_SetEvent(WPI_EventHandle handle) {
+  wpi::SetEvent(handle);
+}
+
+void WPI_ResetEvent(WPI_EventHandle handle) {
+  wpi::ResetEvent(handle);
+}
+
+WPI_SemaphoreHandle WPI_CreateSemaphore(int initial_count, int maximum_count) {
+  return wpi::CreateSemaphore(initial_count, maximum_count);
+}
+
+void WPI_DestroySemaphore(WPI_SemaphoreHandle handle) {
+  wpi::DestroySemaphore(handle);
+}
+
+int WPI_ReleaseSemaphore(WPI_SemaphoreHandle handle, int release_count,
+                         int* prev_count) {
+  return wpi::ReleaseSemaphore(handle, release_count, prev_count);
+}
+
+int WPI_WaitForObject(WPI_Handle handle) {
+  return wpi::WaitForObject(handle);
+}
+
+int WPI_WaitForObjectTimeout(WPI_Handle handle, double timeout,
+                             int* timed_out) {
+  bool timedOutBool;
+  int rv = wpi::WaitForObject(handle, timeout, &timedOutBool);
+  *timed_out = timedOutBool ? 1 : 0;
+  return rv;
+}
+
+int WPI_WaitForObjects(const WPI_Handle* handles, int handles_count,
+                       WPI_Handle* signaled) {
+  return wpi::WaitForObjects(wpi::span(handles, handles_count),
+                             wpi::span(signaled, handles_count))
+      .size();
+}
+
+int WPI_WaitForObjectsTimeout(const WPI_Handle* handles, int handles_count,
+                              WPI_Handle* signaled, double timeout,
+                              int* timed_out) {
+  bool timedOutBool;
+  auto signaledResult = wpi::WaitForObjects(wpi::span(handles, handles_count),
+                                            wpi::span(signaled, handles_count),
+                                            timeout, &timedOutBool);
+  *timed_out = timedOutBool ? 1 : 0;
+  return signaledResult.size();
+}
+
+void WPI_CreateSignalObject(WPI_Handle handle, int manual_reset,
+                            int initial_state) {
+  wpi::CreateSignalObject(handle, manual_reset, initial_state);
+}
+
+void WPI_SetSignalObject(WPI_Handle handle) {
+  wpi::SetSignalObject(handle);
+}
+
+void WPI_ResetSignalObject(WPI_Handle handle) {
+  wpi::ResetSignalObject(handle);
+}
+
+void WPI_DestroySignalObject(WPI_Handle handle) {
+  wpi::DestroySignalObject(handle);
+}
+
+}  // extern "C"
diff --git a/wpiutil/src/main/native/cpp/TCPAcceptor.cpp b/wpiutil/src/main/native/cpp/TCPAcceptor.cpp
index 90d9496..8d12ac3 100644
--- a/wpiutil/src/main/native/cpp/TCPAcceptor.cpp
+++ b/wpiutil/src/main/native/cpp/TCPAcceptor.cpp
@@ -44,7 +44,7 @@
 
 using namespace wpi;
 
-TCPAcceptor::TCPAcceptor(int port, const char* address, Logger& logger)
+TCPAcceptor::TCPAcceptor(int port, std::string_view address, Logger& logger)
     : m_lsd(0),
       m_port(port),
       m_address(address),
@@ -73,11 +73,13 @@
 }
 
 int TCPAcceptor::start() {
-  if (m_listening) return 0;
+  if (m_listening) {
+    return 0;
+  }
 
   m_lsd = socket(PF_INET, SOCK_STREAM, 0);
   if (m_lsd < 0) {
-    WPI_ERROR(m_logger, "could not create socket");
+    WPI_ERROR(m_logger, "{}", "could not create socket");
     return -1;
   }
   struct sockaddr_in address;
@@ -93,7 +95,7 @@
     int res = inet_pton(PF_INET, m_address.c_str(), &(address.sin_addr));
 #endif
     if (res != 1) {
-      WPI_ERROR(m_logger, "could not resolve " << m_address << " address");
+      WPI_ERROR(m_logger, "could not resolve {} address", m_address);
       return -1;
     }
   } else {
@@ -114,15 +116,15 @@
   int result = bind(m_lsd, reinterpret_cast<struct sockaddr*>(&address),
                     sizeof(address));
   if (result != 0) {
-    WPI_ERROR(m_logger,
-              "bind() to port " << m_port << " failed: " << SocketStrerror());
+    WPI_ERROR(m_logger, "bind() to port {} failed: {}", m_port,
+              SocketStrerror());
     return result;
   }
 
   result = listen(m_lsd, 5);
   if (result != 0) {
-    WPI_ERROR(m_logger,
-              "listen() on port " << m_port << " failed: " << SocketStrerror());
+    WPI_ERROR(m_logger, "listen() on port {} failed: {}", m_port,
+              SocketStrerror());
     return result;
   }
   m_listening = true;
@@ -153,7 +155,8 @@
   address.sin_port = htons(m_port);
 
   int result = -1, sd = socket(AF_INET, SOCK_STREAM, 0);
-  if (sd < 0) return;
+  if (sd < 0)
+    return;
 
   // Set socket to non-blocking
   u_long mode = 1;
@@ -176,7 +179,9 @@
 }
 
 std::unique_ptr<NetworkStream> TCPAcceptor::accept() {
-  if (!m_listening || m_shutdown) return nullptr;
+  if (!m_listening || m_shutdown) {
+    return nullptr;
+  }
 
   struct sockaddr_in address;
 #ifdef _WIN32
@@ -185,11 +190,12 @@
   socklen_t len = sizeof(address);
 #endif
   std::memset(&address, 0, sizeof(address));
-  int sd = ::accept(m_lsd, (struct sockaddr*)&address, &len);
+  int sd = ::accept(m_lsd, reinterpret_cast<struct sockaddr*>(&address), &len);
   if (sd < 0) {
-    if (!m_shutdown)
-      WPI_ERROR(m_logger, "accept() on port "
-                              << m_port << " failed: " << SocketStrerror());
+    if (!m_shutdown) {
+      WPI_ERROR(m_logger, "accept() on port {} failed: {}", m_port,
+                SocketStrerror());
+    }
     return nullptr;
   }
   if (m_shutdown) {
diff --git a/wpiutil/src/main/native/cpp/TCPConnector.cpp b/wpiutil/src/main/native/cpp/TCPConnector.cpp
index 6110133..ed97962 100644
--- a/wpiutil/src/main/native/cpp/TCPConnector.cpp
+++ b/wpiutil/src/main/native/cpp/TCPConnector.cpp
@@ -96,7 +96,7 @@
     int res = inet_pton(PF_INET, server, &(address.sin_addr));
 #endif
     if (res != 1) {
-      WPI_ERROR(logger, "could not resolve " << server << " address");
+      WPI_ERROR(logger, "could not resolve {} address", server);
       return nullptr;
     }
   }
@@ -105,12 +105,13 @@
   if (timeout == 0) {
     int sd = socket(AF_INET, SOCK_STREAM, 0);
     if (sd < 0) {
-      WPI_ERROR(logger, "could not create socket");
+      WPI_ERROR(logger, "{}", "could not create socket");
       return nullptr;
     }
-    if (::connect(sd, (struct sockaddr*)&address, sizeof(address)) != 0) {
-      WPI_ERROR(logger, "connect() to " << server << " port " << port
-                                        << " failed: " << SocketStrerror());
+    if (::connect(sd, reinterpret_cast<struct sockaddr*>(&address),
+                  sizeof(address)) != 0) {
+      WPI_ERROR(logger, "connect() to {} port {} failed: {}", server, port,
+                SocketStrerror());
 #ifdef _WIN32
       closesocket(sd);
 #else
@@ -126,7 +127,7 @@
   socklen_t len;
   int result = -1, valopt, sd = socket(AF_INET, SOCK_STREAM, 0);
   if (sd < 0) {
-    WPI_ERROR(logger, "could not create socket");
+    WPI_ERROR(logger, "{}", "could not create socket");
     return nullptr;
   }
 
@@ -134,25 +135,26 @@
 #ifdef _WIN32
   u_long mode = 1;
   if (ioctlsocket(sd, FIONBIO, &mode) == SOCKET_ERROR)
-    WPI_WARNING(logger,
-                "could not set socket to non-blocking: " << SocketStrerror());
+    WPI_WARNING(logger, "could not set socket to non-blocking: {}",
+                SocketStrerror());
 #else
   int arg;
   arg = fcntl(sd, F_GETFL, nullptr);
   if (arg < 0) {
-    WPI_WARNING(logger,
-                "could not set socket to non-blocking: " << SocketStrerror());
+    WPI_WARNING(logger, "could not set socket to non-blocking: {}",
+                SocketStrerror());
   } else {
     arg |= O_NONBLOCK;
-    if (fcntl(sd, F_SETFL, arg) < 0)
-      WPI_WARNING(logger,
-                  "could not set socket to non-blocking: " << SocketStrerror());
+    if (fcntl(sd, F_SETFL, arg) < 0) {
+      WPI_WARNING(logger, "could not set socket to non-blocking: {}",
+                  SocketStrerror());
+    }
   }
 #endif
 
   // Connect with time limit
-  if ((result = ::connect(sd, (struct sockaddr*)&address, sizeof(address))) <
-      0) {
+  if ((result = ::connect(sd, reinterpret_cast<struct sockaddr*>(&address),
+                          sizeof(address))) < 0) {
     int my_errno = SocketErrno();
 #ifdef _WIN32
     if (my_errno == WSAEWOULDBLOCK || my_errno == WSAEINPROGRESS) {
@@ -168,21 +170,18 @@
         getsockopt(sd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&valopt),
                    &len);
         if (valopt) {
-          WPI_ERROR(logger, "select() to " << server << " port " << port
-                                           << " error " << valopt << " - "
-                                           << SocketStrerror(valopt));
-        }
-        // connection established
-        else
+          WPI_ERROR(logger, "select() to {} port {} error {} - {}", server,
+                    port, valopt, SocketStrerror(valopt));
+        } else {
+          // connection established
           result = 0;
+        }
       } else {
-        WPI_INFO(logger,
-                 "connect() to " << server << " port " << port << " timed out");
+        WPI_INFO(logger, "connect() to {} port {} timed out", server, port);
       }
     } else {
-      WPI_ERROR(logger, "connect() to " << server << " port " << port
-                                        << " error " << SocketErrno() << " - "
-                                        << SocketStrerror());
+      WPI_ERROR(logger, "connect() to {} port {} error {} - {}", server, port,
+                SocketErrno(), SocketStrerror());
     }
   }
 
@@ -190,18 +189,19 @@
 #ifdef _WIN32
   mode = 0;
   if (ioctlsocket(sd, FIONBIO, &mode) == SOCKET_ERROR)
-    WPI_WARNING(logger,
-                "could not set socket to blocking: " << SocketStrerror());
+    WPI_WARNING(logger, "could not set socket to blocking: {}",
+                SocketStrerror());
 #else
   arg = fcntl(sd, F_GETFL, nullptr);
   if (arg < 0) {
-    WPI_WARNING(logger,
-                "could not set socket to blocking: " << SocketStrerror());
+    WPI_WARNING(logger, "could not set socket to blocking: {}",
+                SocketStrerror());
   } else {
     arg &= (~O_NONBLOCK);
-    if (fcntl(sd, F_SETFL, arg) < 0)
-      WPI_WARNING(logger,
-                  "could not set socket to blocking: " << SocketStrerror());
+    if (fcntl(sd, F_SETFL, arg) < 0) {
+      WPI_WARNING(logger, "could not set socket to blocking: {}",
+                  SocketStrerror());
+    }
   }
 #endif
 
diff --git a/wpiutil/src/main/native/cpp/TCPConnector_parallel.cpp b/wpiutil/src/main/native/cpp/TCPConnector_parallel.cpp
index ee5803f..26258cf 100644
--- a/wpiutil/src/main/native/cpp/TCPConnector_parallel.cpp
+++ b/wpiutil/src/main/native/cpp/TCPConnector_parallel.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/TCPConnector.h"  // NOLINT(build/include_order)
 
@@ -27,9 +24,11 @@
 #endif
 
 std::unique_ptr<NetworkStream> TCPConnector::connect_parallel(
-    ArrayRef<std::pair<const char*, int>> servers, Logger& logger,
+    span<const std::pair<const char*, int>> servers, Logger& logger,
     int timeout) {
-  if (servers.empty()) return nullptr;
+  if (servers.empty()) {
+    return nullptr;
+  }
 
   // structure to make sure we don't start duplicate workers
   struct GlobalState {
@@ -75,13 +74,15 @@
     // attempt to the same server
     {
       std::scoped_lock lock(local->mtx);
-      if (local->active.count(active_tracker) > 0) continue;  // already in set
+      if (local->active.count(active_tracker) > 0) {
+        continue;  // already in set
+      }
     }
 
     ++num_workers;
 
     // start the worker
-    std::thread([=]() {
+    std::thread([=] {
       if (!result->done) {
         // add to global state
         {
@@ -102,7 +103,9 @@
         // successful connection
         if (stream) {
           std::scoped_lock lock(result->mtx);
-          if (!result->done.exchange(true)) result->stream = std::move(stream);
+          if (!result->done.exchange(true)) {
+            result->stream = std::move(stream);
+          }
         }
       }
       ++result->count;
diff --git a/wpiutil/src/main/native/cpp/TCPStream.cpp b/wpiutil/src/main/native/cpp/TCPStream.cpp
index 4af3e6f..4567161 100644
--- a/wpiutil/src/main/native/cpp/TCPStream.cpp
+++ b/wpiutil/src/main/native/cpp/TCPStream.cpp
@@ -57,7 +57,9 @@
   m_peerPort = ntohs(address->sin_port);
 }
 
-TCPStream::~TCPStream() { close(); }
+TCPStream::~TCPStream() {
+  close();
+}
 
 size_t TCPStream::send(const char* buffer, size_t len, Error* err) {
   if (m_sd < 0) {
@@ -101,10 +103,11 @@
   ssize_t rv = ::send(m_sd, buffer, len, 0);
 #endif
   if (rv < 0) {
-    if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK))
+    if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK)) {
       *err = kWouldBlock;
-    else
+    } else {
       *err = kConnectionReset;
+    }
     return 0;
   }
 #endif
@@ -139,13 +142,14 @@
   }
   if (rv < 0) {
 #ifdef _WIN32
-    if (!m_blocking && WSAGetLastError() == WSAEWOULDBLOCK)
+    if (!m_blocking && WSAGetLastError() == WSAEWOULDBLOCK) {
 #else
-    if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK))
+    if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK)) {
 #endif
       *err = kWouldBlock;
-    else
+    } else {
       *err = kConnectionReset;
+    }
     return 0;
   }
   return static_cast<size_t>(rv);
@@ -164,35 +168,52 @@
   m_sd = -1;
 }
 
-StringRef TCPStream::getPeerIP() const { return m_peerIP; }
+std::string_view TCPStream::getPeerIP() const {
+  return m_peerIP;
+}
 
-int TCPStream::getPeerPort() const { return m_peerPort; }
+int TCPStream::getPeerPort() const {
+  return m_peerPort;
+}
 
 void TCPStream::setNoDelay() {
-  if (m_sd < 0) return;
+  if (m_sd < 0) {
+    return;
+  }
   int optval = 1;
   setsockopt(m_sd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&optval),
              sizeof optval);
 }
 
 bool TCPStream::setBlocking(bool enabled) {
-  if (m_sd < 0) return true;  // silently accept
+  if (m_sd < 0) {
+    return true;  // silently accept
+  }
 #ifdef _WIN32
   u_long mode = enabled ? 0 : 1;
-  if (ioctlsocket(m_sd, FIONBIO, &mode) == SOCKET_ERROR) return false;
+  if (ioctlsocket(m_sd, FIONBIO, &mode) == SOCKET_ERROR) {
+    return false;
+  }
 #else
   int flags = fcntl(m_sd, F_GETFL, nullptr);
-  if (flags < 0) return false;
-  if (enabled)
+  if (flags < 0) {
+    return false;
+  }
+  if (enabled) {
     flags &= ~O_NONBLOCK;
-  else
+  } else {
     flags |= O_NONBLOCK;
-  if (fcntl(m_sd, F_SETFL, flags) < 0) return false;
+  }
+  if (fcntl(m_sd, F_SETFL, flags) < 0) {
+    return false;
+  }
 #endif
   return true;
 }
 
-int TCPStream::getNativeHandle() const { return m_sd; }
+int TCPStream::getNativeHandle() const {
+  return m_sd;
+}
 
 bool TCPStream::WaitForReadEvent(int timeout) {
   fd_set sdset;
@@ -202,7 +223,7 @@
   tv.tv_usec = 0;
   FD_ZERO(&sdset);
   FD_SET(m_sd, &sdset);
-  if (select(m_sd + 1, &sdset, NULL, NULL, &tv) > 0) {
+  if (select(m_sd + 1, &sdset, nullptr, nullptr, &tv) > 0) {
     return true;
   }
   return false;
diff --git a/wpiutil/src/main/native/cpp/UDPClient.cpp b/wpiutil/src/main/native/cpp/UDPClient.cpp
index aafdf47..108ef54 100644
--- a/wpiutil/src/main/native/cpp/UDPClient.cpp
+++ b/wpiutil/src/main/native/cpp/UDPClient.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/UDPClient.h"
 
@@ -19,14 +16,15 @@
 #endif
 
 #include "wpi/Logger.h"
+#include "wpi/SmallString.h"
 #include "wpi/SocketError.h"
 
 using namespace wpi;
 
 UDPClient::UDPClient(Logger& logger) : UDPClient("", logger) {}
 
-UDPClient::UDPClient(const Twine& address, Logger& logger)
-    : m_lsd(0), m_port(0), m_address(address.str()), m_logger(logger) {}
+UDPClient::UDPClient(std::string_view address, Logger& logger)
+    : m_lsd(0), m_port(0), m_address(address), m_logger(logger) {}
 
 UDPClient::UDPClient(UDPClient&& other)
     : m_lsd(other.m_lsd),
@@ -44,7 +42,9 @@
 }
 
 UDPClient& UDPClient::operator=(UDPClient&& other) {
-  if (this == &other) return *this;
+  if (this == &other) {
+    return *this;
+  }
   shutdown();
   m_logger = other.m_logger;
   m_lsd = other.m_lsd;
@@ -55,10 +55,14 @@
   return *this;
 }
 
-int UDPClient::start() { return start(0); }
+int UDPClient::start() {
+  return start(0);
+}
 
 int UDPClient::start(int port) {
-  if (m_lsd > 0) return 0;
+  if (m_lsd > 0) {
+    return 0;
+  }
 
 #ifdef _WIN32
   WSAData wsaData;
@@ -69,7 +73,7 @@
   m_lsd = socket(AF_INET, SOCK_DGRAM, 0);
 
   if (m_lsd < 0) {
-    WPI_ERROR(m_logger, "could not create socket");
+    WPI_ERROR(m_logger, "{}", "could not create socket");
     return -1;
   }
 
@@ -85,7 +89,7 @@
     int res = inet_pton(PF_INET, m_address.c_str(), &(addr.sin_addr));
 #endif
     if (res != 1) {
-      WPI_ERROR(m_logger, "could not resolve " << m_address << " address");
+      WPI_ERROR(m_logger, "could not resolve {} address", m_address);
       return -1;
     }
   } else {
@@ -107,7 +111,7 @@
 
   int result = bind(m_lsd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
   if (result != 0) {
-    WPI_ERROR(m_logger, "bind() failed: " << SocketStrerror());
+    WPI_ERROR(m_logger, "bind() failed: {}", SocketStrerror());
     return result;
   }
   m_port = port;
@@ -129,25 +133,25 @@
   }
 }
 
-int UDPClient::send(ArrayRef<uint8_t> data, const Twine& server, int port) {
+int UDPClient::send(span<const uint8_t> data, std::string_view server,
+                    int port) {
   // server must be a resolvable IP address
   struct sockaddr_in addr;
   std::memset(&addr, 0, sizeof(addr));
   addr.sin_family = AF_INET;
-  SmallVector<char, 128> addr_store;
-  StringRef remoteAddr = server.toNullTerminatedStringRef(addr_store);
+  SmallString<128> remoteAddr{server};
   if (remoteAddr.empty()) {
-    WPI_ERROR(m_logger, "server must be passed");
+    WPI_ERROR(m_logger, "{}", "server must be passed");
     return -1;
   }
 
 #ifdef _WIN32
-  int res = InetPton(AF_INET, remoteAddr.data(), &(addr.sin_addr));
+  int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
 #else
-  int res = inet_pton(AF_INET, remoteAddr.data(), &(addr.sin_addr));
+  int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
 #endif
   if (res != 1) {
-    WPI_ERROR(m_logger, "could not resolve " << server << " address");
+    WPI_ERROR(m_logger, "could not resolve {} address", server);
     return -1;
   }
   addr.sin_port = htons(port);
@@ -159,25 +163,24 @@
   return result;
 }
 
-int UDPClient::send(StringRef data, const Twine& server, int port) {
+int UDPClient::send(std::string_view data, std::string_view server, int port) {
   // server must be a resolvable IP address
   struct sockaddr_in addr;
   std::memset(&addr, 0, sizeof(addr));
   addr.sin_family = AF_INET;
-  SmallVector<char, 128> addr_store;
-  StringRef remoteAddr = server.toNullTerminatedStringRef(addr_store);
+  SmallString<128> remoteAddr{server};
   if (remoteAddr.empty()) {
-    WPI_ERROR(m_logger, "server must be passed");
+    WPI_ERROR(m_logger, "{}", "server must be passed");
     return -1;
   }
 
 #ifdef _WIN32
-  int res = InetPton(AF_INET, remoteAddr.data(), &(addr.sin_addr));
+  int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
 #else
-  int res = inet_pton(AF_INET, remoteAddr.data(), &(addr.sin_addr));
+  int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
 #endif
   if (res != 1) {
-    WPI_ERROR(m_logger, "could not resolve " << server << " address");
+    WPI_ERROR(m_logger, "could not resolve {} address", server);
     return -1;
   }
   addr.sin_port = htons(port);
@@ -189,14 +192,18 @@
 }
 
 int UDPClient::receive(uint8_t* data_received, int receive_len) {
-  if (m_port == 0) return -1;  // return if not receiving
+  if (m_port == 0) {
+    return -1;  // return if not receiving
+  }
   return recv(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0);
 }
 
 int UDPClient::receive(uint8_t* data_received, int receive_len,
                        SmallVectorImpl<char>* addr_received,
                        int* port_received) {
-  if (m_port == 0) return -1;  // return if not receiving
+  if (m_port == 0) {
+    return -1;  // return if not receiving
+  }
 
   struct sockaddr_in remote;
   socklen_t remote_len = sizeof(remote);
@@ -225,13 +232,17 @@
 }
 
 int UDPClient::set_timeout(double timeout) {
-  if (timeout < 0) return -1;
+  if (timeout < 0) {
+    return -1;
+  }
   struct timeval tv;
   tv.tv_sec = timeout;             // truncating will give seconds
   timeout -= tv.tv_sec;            // remove seconds portion
   tv.tv_usec = timeout * 1000000;  // fractions of a second to us
   int ret = setsockopt(m_lsd, SOL_SOCKET, SO_RCVTIMEO,
                        reinterpret_cast<char*>(&tv), sizeof(tv));
-  if (ret < 0) WPI_ERROR(m_logger, "set timeout failed");
+  if (ret < 0) {
+    WPI_ERROR(m_logger, "{}", "set timeout failed");
+  }
   return ret;
 }
diff --git a/wpiutil/src/main/native/cpp/WebSocket.cpp b/wpiutil/src/main/native/cpp/WebSocket.cpp
index 82c83bb..4bb49d3 100644
--- a/wpiutil/src/main/native/cpp/WebSocket.cpp
+++ b/wpiutil/src/main/native/cpp/WebSocket.cpp
@@ -1,18 +1,17 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/WebSocket.h"
 
 #include <random>
 
+#include "fmt/format.h"
 #include "wpi/Base64.h"
 #include "wpi/HttpParser.h"
 #include "wpi/SmallString.h"
 #include "wpi/SmallVector.h"
+#include "wpi/StringExtras.h"
 #include "wpi/raw_uv_ostream.h"
 #include "wpi/sha1.h"
 #include "wpi/uv/Stream.h"
@@ -23,14 +22,18 @@
 class WebSocketWriteReq : public uv::WriteReq {
  public:
   explicit WebSocketWriteReq(
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    finish.connect([=](uv::Error err) {
-      MutableArrayRef<uv::Buffer> bufs{m_bufs};
-      for (auto&& buf : bufs.slice(0, m_startUser)) buf.Deallocate();
-      callback(bufs.slice(m_startUser), err);
+      std::function<void(span<uv::Buffer>, uv::Error)> callback)
+      : m_callback{std::move(callback)} {
+    finish.connect([this](uv::Error err) {
+      span<uv::Buffer> bufs{m_bufs};
+      for (auto&& buf : bufs.subspan(0, m_startUser)) {
+        buf.Deallocate();
+      }
+      m_callback(bufs.subspan(m_startUser), err);
     });
   }
 
+  std::function<void(span<uv::Buffer>, uv::Error)> m_callback;
   SmallVector<uv::Buffer, 4> m_bufs;
   size_t m_startUser;
 };
@@ -44,9 +47,11 @@
     static std::default_random_engine gen{rd()};
     std::uniform_int_distribution<unsigned int> dist(0, 255);
     char nonce[16];  // the nonce sent to the server
-    for (char& v : nonce) v = static_cast<char>(dist(gen));
+    for (char& v : nonce) {
+      v = static_cast<char>(dist(gen));
+    }
     raw_svector_ostream os(key);
-    Base64Encode(os, StringRef{nonce, 16});
+    Base64Encode(os, {nonce, 16});
   }
   ~ClientHandshakeData() {
     if (auto t = timer.lock()) {
@@ -66,7 +71,8 @@
   std::weak_ptr<uv::Timer> timer;
 };
 
-static StringRef AcceptHash(StringRef key, SmallVectorImpl<char>& buf) {
+static std::string_view AcceptHash(std::string_view key,
+                                   SmallVectorImpl<char>& buf) {
   SHA1 hash;
   hash.Update(key);
   hash.Update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
@@ -79,7 +85,7 @@
   // Connect closed and error signals to ourselves
   m_stream.closed.connect([this]() { SetClosed(1006, "handle closed"); });
   m_stream.error.connect([this](uv::Error err) {
-    Terminate(1006, "stream error: " + Twine(err.name()));
+    Terminate(1006, fmt::format("stream error: {}", err.name()));
   });
 
   // Start reading
@@ -91,11 +97,11 @@
       [this]() { Terminate(1006, "remote end closed connection"); });
 }
 
-WebSocket::~WebSocket() {}
+WebSocket::~WebSocket() = default;
 
 std::shared_ptr<WebSocket> WebSocket::CreateClient(
-    uv::Stream& stream, const Twine& uri, const Twine& host,
-    ArrayRef<StringRef> protocols, const ClientOptions& options) {
+    uv::Stream& stream, std::string_view uri, std::string_view host,
+    span<const std::string_view> protocols, const ClientOptions& options) {
   auto ws = std::make_shared<WebSocket>(stream, false, private_init{});
   stream.SetData(ws);
   ws->StartClient(uri, host, protocols, options);
@@ -103,35 +109,41 @@
 }
 
 std::shared_ptr<WebSocket> WebSocket::CreateServer(uv::Stream& stream,
-                                                   StringRef key,
-                                                   StringRef version,
-                                                   StringRef protocol) {
+                                                   std::string_view key,
+                                                   std::string_view version,
+                                                   std::string_view protocol) {
   auto ws = std::make_shared<WebSocket>(stream, true, private_init{});
   stream.SetData(ws);
   ws->StartServer(key, version, protocol);
   return ws;
 }
 
-void WebSocket::Close(uint16_t code, const Twine& reason) {
+void WebSocket::Close(uint16_t code, std::string_view reason) {
   SendClose(code, reason);
-  if (m_state != FAILED && m_state != CLOSED) m_state = CLOSING;
+  if (m_state != FAILED && m_state != CLOSED) {
+    m_state = CLOSING;
+  }
 }
 
-void WebSocket::Fail(uint16_t code, const Twine& reason) {
-  if (m_state == FAILED || m_state == CLOSED) return;
+void WebSocket::Fail(uint16_t code, std::string_view reason) {
+  if (m_state == FAILED || m_state == CLOSED) {
+    return;
+  }
   SendClose(code, reason);
   SetClosed(code, reason, true);
   Shutdown();
 }
 
-void WebSocket::Terminate(uint16_t code, const Twine& reason) {
-  if (m_state == FAILED || m_state == CLOSED) return;
+void WebSocket::Terminate(uint16_t code, std::string_view reason) {
+  if (m_state == FAILED || m_state == CLOSED) {
+    return;
+  }
   SetClosed(code, reason);
   Shutdown();
 }
 
-void WebSocket::StartClient(const Twine& uri, const Twine& host,
-                            ArrayRef<StringRef> protocols,
+void WebSocket::StartClient(std::string_view uri, std::string_view host,
+                            span<const std::string_view> protocols,
                             const ClientOptions& options) {
   // Create client handshake data
   m_clientHandshake = std::make_unique<ClientHandshakeData>();
@@ -152,10 +164,11 @@
     os << "Sec-WebSocket-Protocol: ";
     bool first = true;
     for (auto protocol : protocols) {
-      if (!first)
+      if (!first) {
         os << ", ";
-      else
+      } else {
         first = false;
+      }
       os << protocol;
       // also save for later checking against server response
       m_clientHandshake->protocols.emplace_back(protocol);
@@ -164,52 +177,64 @@
   }
 
   // other headers
-  for (auto&& header : options.extraHeaders)
+  for (auto&& header : options.extraHeaders) {
     os << header.first << ": " << header.second << "\r\n";
+  }
 
   // finish headers
   os << "\r\n";
 
   // Send client request
   m_stream.Write(bufs, [](auto bufs, uv::Error) {
-    for (auto& buf : bufs) buf.Deallocate();
+    for (auto& buf : bufs) {
+      buf.Deallocate();
+    }
   });
 
   // Set up client response handling
-  m_clientHandshake->parser.status.connect([this](StringRef status) {
+  m_clientHandshake->parser.status.connect([this](std::string_view status) {
     unsigned int code = m_clientHandshake->parser.GetStatusCode();
-    if (code != 101) Terminate(code, status);
+    if (code != 101) {
+      Terminate(code, status);
+    }
   });
   m_clientHandshake->parser.header.connect(
-      [this](StringRef name, StringRef value) {
-        value = value.trim();
-        if (name.equals_lower("upgrade")) {
-          if (!value.equals_lower("websocket"))
+      [this](std::string_view name, std::string_view value) {
+        value = trim(value);
+        if (equals_lower(name, "upgrade")) {
+          if (!equals_lower(value, "websocket")) {
             return Terminate(1002, "invalid upgrade response value");
+          }
           m_clientHandshake->hasUpgrade = true;
-        } else if (name.equals_lower("connection")) {
-          if (!value.equals_lower("upgrade"))
+        } else if (equals_lower(name, "connection")) {
+          if (!equals_lower(value, "upgrade")) {
             return Terminate(1002, "invalid connection response value");
+          }
           m_clientHandshake->hasConnection = true;
-        } else if (name.equals_lower("sec-websocket-accept")) {
+        } else if (equals_lower(name, "sec-websocket-accept")) {
           // Check against expected response
           SmallString<64> acceptBuf;
-          if (!value.equals(AcceptHash(m_clientHandshake->key, acceptBuf)))
+          if (!equals(value, AcceptHash(m_clientHandshake->key, acceptBuf))) {
             return Terminate(1002, "invalid accept key");
+          }
           m_clientHandshake->hasAccept = true;
-        } else if (name.equals_lower("sec-websocket-extensions")) {
+        } else if (equals_lower(name, "sec-websocket-extensions")) {
           // No extensions are supported
-          if (!value.empty()) return Terminate(1010, "unsupported extension");
-        } else if (name.equals_lower("sec-websocket-protocol")) {
+          if (!value.empty()) {
+            return Terminate(1010, "unsupported extension");
+          }
+        } else if (equals_lower(name, "sec-websocket-protocol")) {
           // Make sure it was one of the provided protocols
           bool match = false;
           for (auto&& protocol : m_clientHandshake->protocols) {
-            if (value.equals_lower(protocol)) {
+            if (equals_lower(value, protocol)) {
               match = true;
               break;
             }
           }
-          if (!match) return Terminate(1003, "unsupported protocol");
+          if (!match) {
+            return Terminate(1003, "unsupported protocol");
+          }
           m_clientHandshake->hasProtocol = true;
           m_protocol = value;
         }
@@ -237,8 +262,8 @@
   }
 }
 
-void WebSocket::StartServer(StringRef key, StringRef version,
-                            StringRef protocol) {
+void WebSocket::StartServer(std::string_view key, std::string_view version,
+                            std::string_view protocol) {
   m_protocol = protocol;
 
   // Build server response
@@ -251,7 +276,9 @@
     os << "Upgrade: WebSocket\r\n";
     os << "Sec-WebSocket-Version: 13\r\n\r\n";
     m_stream.Write(bufs, [this](auto bufs, uv::Error) {
-      for (auto& buf : bufs) buf.Deallocate();
+      for (auto& buf : bufs) {
+        buf.Deallocate();
+      }
       // XXX: Should we support sending a new handshake on the same connection?
       // XXX: "this->" is required by GCC 5.5 (bug)
       this->Terminate(1003, "unsupported protocol version");
@@ -267,14 +294,18 @@
   SmallString<64> acceptBuf;
   os << "Sec-WebSocket-Accept: " << AcceptHash(key, acceptBuf) << "\r\n";
 
-  if (!protocol.empty()) os << "Sec-WebSocket-Protocol: " << protocol << "\r\n";
+  if (!protocol.empty()) {
+    os << "Sec-WebSocket-Protocol: " << protocol << "\r\n";
+  }
 
   // end headers
   os << "\r\n";
 
   // Send server response
   m_stream.Write(bufs, [this](auto bufs, uv::Error) {
-    for (auto& buf : bufs) buf.Deallocate();
+    for (auto& buf : bufs) {
+      buf.Deallocate();
+    }
     if (m_state == CONNECTING) {
       m_state = OPEN;
       open(m_protocol);
@@ -282,25 +313,28 @@
   });
 }
 
-void WebSocket::SendClose(uint16_t code, const Twine& reason) {
+void WebSocket::SendClose(uint16_t code, std::string_view reason) {
   SmallVector<uv::Buffer, 4> bufs;
   if (code != 1005) {
     raw_uv_ostream os{bufs, 4096};
     const uint8_t codeMsb[] = {static_cast<uint8_t>((code >> 8) & 0xff),
                                static_cast<uint8_t>(code & 0xff)};
-    os << ArrayRef<uint8_t>(codeMsb);
-    reason.print(os);
+    os << span{codeMsb};
+    os << reason;
   }
   Send(kFlagFin | kOpClose, bufs, [](auto bufs, uv::Error) {
-    for (auto&& buf : bufs) buf.Deallocate();
+    for (auto&& buf : bufs) {
+      buf.Deallocate();
+    }
   });
 }
 
-void WebSocket::SetClosed(uint16_t code, const Twine& reason, bool failed) {
-  if (m_state == FAILED || m_state == CLOSED) return;
+void WebSocket::SetClosed(uint16_t code, std::string_view reason, bool failed) {
+  if (m_state == FAILED || m_state == CLOSED) {
+    return;
+  }
   m_state = failed ? FAILED : CLOSED;
-  SmallString<64> reasonBuf;
-  closed(code, reason.toStringRef(reasonBuf));
+  closed(code, reason);
 }
 
 void WebSocket::Shutdown() {
@@ -309,18 +343,23 @@
 
 void WebSocket::HandleIncoming(uv::Buffer& buf, size_t size) {
   // ignore incoming data if we're failed or closed
-  if (m_state == FAILED || m_state == CLOSED) return;
+  if (m_state == FAILED || m_state == CLOSED) {
+    return;
+  }
 
-  StringRef data{buf.base, size};
+  std::string_view data{buf.base, size};
 
   // Handle connecting state (mainly on client)
   if (m_state == CONNECTING) {
     if (m_clientHandshake) {
       data = m_clientHandshake->parser.Execute(data);
       // check for parser failure
-      if (m_clientHandshake->parser.HasError())
+      if (m_clientHandshake->parser.HasError()) {
         return Terminate(1003, "invalid response");
-      if (m_state != OPEN) return;  // not done with handshake yet
+      }
+      if (m_state != OPEN) {
+        return;  // not done with handshake yet
+      }
 
       // we're done with the handshake, so release its memory
       m_clientHandshake.reset();
@@ -337,45 +376,58 @@
       // Need at least two bytes to determine header length
       if (m_header.size() < 2u) {
         size_t toCopy = (std::min)(2u - m_header.size(), data.size());
-        m_header.append(data.bytes_begin(), data.bytes_begin() + toCopy);
-        data = data.drop_front(toCopy);
-        if (m_header.size() < 2u) return;  // need more data
+        m_header.append(data.data(), data.data() + toCopy);
+        data.remove_prefix(toCopy);
+        if (m_header.size() < 2u) {
+          return;  // need more data
+        }
 
         // Validate RSV bits are zero
-        if ((m_header[0] & 0x70) != 0) return Fail(1002, "nonzero RSV");
+        if ((m_header[0] & 0x70) != 0) {
+          return Fail(1002, "nonzero RSV");
+        }
       }
 
       // Once we have first two bytes, we can calculate the header size
       if (m_headerSize == 0) {
         m_headerSize = 2;
         uint8_t len = m_header[1] & kLenMask;
-        if (len == 126)
+        if (len == 126) {
           m_headerSize += 2;
-        else if (len == 127)
+        } else if (len == 127) {
           m_headerSize += 8;
+        }
         bool masking = (m_header[1] & kFlagMasking) != 0;
-        if (masking) m_headerSize += 4;  // masking key
+        if (masking) {
+          m_headerSize += 4;  // masking key
+        }
         // On server side, incoming messages MUST be masked
         // On client side, incoming messages MUST NOT be masked
-        if (m_server && !masking) return Fail(1002, "client data not masked");
-        if (!m_server && masking) return Fail(1002, "server data masked");
+        if (m_server && !masking) {
+          return Fail(1002, "client data not masked");
+        }
+        if (!m_server && masking) {
+          return Fail(1002, "server data masked");
+        }
       }
 
       // Need to complete header to calculate message size
       if (m_header.size() < m_headerSize) {
         size_t toCopy = (std::min)(m_headerSize - m_header.size(), data.size());
-        m_header.append(data.bytes_begin(), data.bytes_begin() + toCopy);
-        data = data.drop_front(toCopy);
-        if (m_header.size() < m_headerSize) return;  // need more data
+        m_header.append(data.data(), data.data() + toCopy);
+        data.remove_prefix(toCopy);
+        if (m_header.size() < m_headerSize) {
+          return;  // need more data
+        }
       }
 
       if (m_header.size() >= m_headerSize) {
         // get payload length
         uint8_t len = m_header[1] & kLenMask;
-        if (len == 126)
+        if (len == 126) {
           m_frameSize = (static_cast<uint16_t>(m_header[2]) << 8) |
                         static_cast<uint16_t>(m_header[3]);
-        else if (len == 127)
+        } else if (len == 127) {
           m_frameSize = (static_cast<uint64_t>(m_header[2]) << 56) |
                         (static_cast<uint64_t>(m_header[3]) << 48) |
                         (static_cast<uint64_t>(m_header[4]) << 40) |
@@ -384,20 +436,22 @@
                         (static_cast<uint64_t>(m_header[7]) << 16) |
                         (static_cast<uint64_t>(m_header[8]) << 8) |
                         static_cast<uint64_t>(m_header[9]);
-        else
+        } else {
           m_frameSize = len;
+        }
 
         // limit maximum size
-        if ((m_payload.size() + m_frameSize) > m_maxMessageSize)
+        if ((m_payload.size() + m_frameSize) > m_maxMessageSize) {
           return Fail(1009, "message too large");
+        }
       }
     }
 
     if (m_frameSize != UINT64_MAX) {
       size_t need = m_frameStart + m_frameSize - m_payload.size();
       size_t toCopy = (std::min)(need, data.size());
-      m_payload.append(data.bytes_begin(), data.bytes_begin() + toCopy);
-      data = data.drop_front(toCopy);
+      m_payload.append(data.data(), data.data() + toCopy);
+      data.remove_prefix(toCopy);
       need -= toCopy;
       if (need == 0) {
         // We have a complete frame
@@ -407,10 +461,11 @@
               m_header[m_headerSize - 4], m_header[m_headerSize - 3],
               m_header[m_headerSize - 2], m_header[m_headerSize - 1]};
           int n = 0;
-          for (uint8_t& ch :
-               MutableArrayRef<uint8_t>{m_payload}.slice(m_frameStart)) {
+          for (uint8_t& ch : span{m_payload}.subspan(m_frameStart)) {
             ch ^= key[n++];
-            if (n >= 4) n = 0;
+            if (n >= 4) {
+              n = 0;
+            }
           }
         }
 
@@ -421,36 +476,53 @@
           case kOpCont:
             switch (m_fragmentOpcode) {
               case kOpText:
-                if (!m_combineFragments || fin)
-                  text(StringRef{reinterpret_cast<char*>(m_payload.data()),
-                                 m_payload.size()},
+                if (!m_combineFragments || fin) {
+                  text(std::string_view{reinterpret_cast<char*>(
+                                            m_payload.data()),
+                                        m_payload.size()},
                        fin);
+                }
                 break;
               case kOpBinary:
-                if (!m_combineFragments || fin) binary(m_payload, fin);
+                if (!m_combineFragments || fin) {
+                  binary(m_payload, fin);
+                }
                 break;
               default:
                 // no preceding message?
                 return Fail(1002, "invalid continuation message");
             }
-            if (fin) m_fragmentOpcode = 0;
+            if (fin) {
+              m_fragmentOpcode = 0;
+            }
             break;
           case kOpText:
-            if (m_fragmentOpcode != 0) return Fail(1002, "incomplete fragment");
-            if (!m_combineFragments || fin)
-              text(StringRef{reinterpret_cast<char*>(m_payload.data()),
-                             m_payload.size()},
+            if (m_fragmentOpcode != 0) {
+              return Fail(1002, "incomplete fragment");
+            }
+            if (!m_combineFragments || fin) {
+              text(std::string_view{reinterpret_cast<char*>(m_payload.data()),
+                                    m_payload.size()},
                    fin);
-            if (!fin) m_fragmentOpcode = opcode;
+            }
+            if (!fin) {
+              m_fragmentOpcode = opcode;
+            }
             break;
           case kOpBinary:
-            if (m_fragmentOpcode != 0) return Fail(1002, "incomplete fragment");
-            if (!m_combineFragments || fin) binary(m_payload, fin);
-            if (!fin) m_fragmentOpcode = opcode;
+            if (m_fragmentOpcode != 0) {
+              return Fail(1002, "incomplete fragment");
+            }
+            if (!m_combineFragments || fin) {
+              binary(m_payload, fin);
+            }
+            if (!fin) {
+              m_fragmentOpcode = opcode;
+            }
             break;
           case kOpClose: {
             uint16_t code;
-            StringRef reason;
+            std::string_view reason;
             if (!fin) {
               code = 1002;
               reason = "cannot fragment control frames";
@@ -459,23 +531,31 @@
             } else {
               code = (static_cast<uint16_t>(m_payload[0]) << 8) |
                      static_cast<uint16_t>(m_payload[1]);
-              reason = StringRef{reinterpret_cast<char*>(m_payload.data()),
-                                 m_payload.size()}
-                           .drop_front(2);
+              reason = drop_front(
+                  {reinterpret_cast<char*>(m_payload.data()), m_payload.size()},
+                  2);
             }
             // Echo the close if we didn't previously send it
-            if (m_state != CLOSING) SendClose(code, reason);
+            if (m_state != CLOSING) {
+              SendClose(code, reason);
+            }
             SetClosed(code, reason);
             // If we're the server, shutdown the connection.
-            if (m_server) Shutdown();
+            if (m_server) {
+              Shutdown();
+            }
             break;
           }
           case kOpPing:
-            if (!fin) return Fail(1002, "cannot fragment control frames");
+            if (!fin) {
+              return Fail(1002, "cannot fragment control frames");
+            }
             ping(m_payload);
             break;
           case kOpPong:
-            if (!fin) return Fail(1002, "cannot fragment control frames");
+            if (!fin) {
+              return Fail(1002, "cannot fragment control frames");
+            }
             pong(m_payload);
             break;
           default:
@@ -485,7 +565,9 @@
         // Prepare for next message
         m_header.clear();
         m_headerSize = 0;
-        if (!m_combineFragments || fin) m_payload.clear();
+        if (!m_combineFragments || fin) {
+          m_payload.clear();
+        }
         m_frameStart = m_payload.size();
         m_frameSize = UINT64_MAX;
       }
@@ -494,21 +576,22 @@
 }
 
 void WebSocket::Send(
-    uint8_t opcode, ArrayRef<uv::Buffer> data,
-    std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
+    uint8_t opcode, span<const uv::Buffer> data,
+    std::function<void(span<uv::Buffer>, uv::Error)> callback) {
   // If we're not open, emit an error and don't send the data
   if (m_state != OPEN) {
     int err;
-    if (m_state == CONNECTING)
+    if (m_state == CONNECTING) {
       err = UV_EAGAIN;
-    else
+    } else {
       err = UV_ESHUTDOWN;
+    }
     SmallVector<uv::Buffer, 4> bufs{data.begin(), data.end()};
     callback(bufs, uv::Error{err});
     return;
   }
 
-  auto req = std::make_shared<WebSocketWriteReq>(callback);
+  auto req = std::make_shared<WebSocketWriteReq>(std::move(callback));
   raw_uv_ostream os{req->m_bufs, 4096};
 
   // opcode (includes FIN bit)
@@ -516,14 +599,16 @@
 
   // payload length
   uint64_t size = 0;
-  for (auto&& buf : data) size += buf.len;
+  for (auto&& buf : data) {
+    size += buf.len;
+  }
   if (size < 126) {
     os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | size);
   } else if (size <= 0xffff) {
     os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 126);
     const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 8) & 0xff),
                                static_cast<uint8_t>(size & 0xff)};
-    os << ArrayRef<uint8_t>(sizeMsb);
+    os << span{sizeMsb};
   } else {
     os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 127);
     const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 56) & 0xff),
@@ -534,7 +619,7 @@
                                static_cast<uint8_t>((size >> 16) & 0xff),
                                static_cast<uint8_t>((size >> 8) & 0xff),
                                static_cast<uint8_t>(size & 0xff)};
-    os << ArrayRef<uint8_t>(sizeMsb);
+    os << span{sizeMsb};
   }
 
   // clients need to mask the input data
@@ -544,21 +629,24 @@
     static std::default_random_engine gen{rd()};
     std::uniform_int_distribution<unsigned int> dist(0, 255);
     uint8_t key[4];
-    for (uint8_t& v : key) v = dist(gen);
-    os << ArrayRef<uint8_t>{key, 4};
+    for (uint8_t& v : key) {
+      v = dist(gen);
+    }
+    os << span<const uint8_t>{key, 4};
     // copy and mask data
     int n = 0;
     for (auto&& buf : data) {
       for (auto&& ch : buf.data()) {
         os << static_cast<unsigned char>(static_cast<uint8_t>(ch) ^ key[n++]);
-        if (n >= 4) n = 0;
+        if (n >= 4) {
+          n = 0;
+        }
       }
     }
     req->m_startUser = req->m_bufs.size();
     req->m_bufs.append(data.begin(), data.end());
     // don't send the user bufs as we copied their data
-    m_stream.Write(ArrayRef<uv::Buffer>{req->m_bufs}.slice(0, req->m_startUser),
-                   req);
+    m_stream.Write(span{req->m_bufs}.subspan(0, req->m_startUser), req);
   } else {
     // servers can just send the buffers directly without masking
     req->m_startUser = req->m_bufs.size();
diff --git a/wpiutil/src/main/native/cpp/WebSocketServer.cpp b/wpiutil/src/main/native/cpp/WebSocketServer.cpp
index 056cb62..1562f3b 100644
--- a/wpiutil/src/main/native/cpp/WebSocketServer.cpp
+++ b/wpiutil/src/main/native/cpp/WebSocketServer.cpp
@@ -1,12 +1,13 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/WebSocketServer.h"
 
+#include <utility>
+
+#include "wpi/StringExtras.h"
+#include "wpi/fmt/raw_ostream.h"
 #include "wpi/raw_uv_ostream.h"
 #include "wpi/uv/Buffer.h"
 #include "wpi/uv/Stream.h"
@@ -14,76 +15,92 @@
 using namespace wpi;
 
 WebSocketServerHelper::WebSocketServerHelper(HttpParser& req) {
-  req.header.connect([this](StringRef name, StringRef value) {
-    if (name.equals_lower("host")) {
+  req.header.connect([this](std::string_view name, std::string_view value) {
+    if (equals_lower(name, "host")) {
       m_gotHost = true;
-    } else if (name.equals_lower("upgrade")) {
-      if (value.equals_lower("websocket")) m_websocket = true;
-    } else if (name.equals_lower("sec-websocket-key")) {
+    } else if (equals_lower(name, "upgrade")) {
+      if (equals_lower(value, "websocket")) {
+        m_websocket = true;
+      }
+    } else if (equals_lower(name, "sec-websocket-key")) {
       m_key = value;
-    } else if (name.equals_lower("sec-websocket-version")) {
+    } else if (equals_lower(name, "sec-websocket-version")) {
       m_version = value;
-    } else if (name.equals_lower("sec-websocket-protocol")) {
+    } else if (equals_lower(name, "sec-websocket-protocol")) {
       // Protocols are comma delimited, repeated headers add to list
-      SmallVector<StringRef, 2> protocols;
-      value.split(protocols, ",", -1, false);
+      SmallVector<std::string_view, 2> protocols;
+      split(value, protocols, ",", -1, false);
       for (auto protocol : protocols) {
-        protocol = protocol.trim();
-        if (!protocol.empty()) m_protocols.emplace_back(protocol);
+        protocol = trim(protocol);
+        if (!protocol.empty()) {
+          m_protocols.emplace_back(protocol);
+        }
       }
     }
   });
   req.headersComplete.connect([&req, this](bool) {
-    if (req.IsUpgrade() && IsUpgrade()) upgrade();
+    if (req.IsUpgrade() && IsUpgrade()) {
+      upgrade();
+    }
   });
 }
 
-std::pair<bool, StringRef> WebSocketServerHelper::MatchProtocol(
-    ArrayRef<StringRef> protocols) {
-  if (protocols.empty() && m_protocols.empty())
-    return std::make_pair(true, StringRef{});
+std::pair<bool, std::string_view> WebSocketServerHelper::MatchProtocol(
+    span<const std::string_view> protocols) {
+  if (protocols.empty() && m_protocols.empty()) {
+    return {true, {}};
+  }
   for (auto protocol : protocols) {
     for (auto&& clientProto : m_protocols) {
-      if (protocol == clientProto) return std::make_pair(true, protocol);
+      if (protocol == clientProto) {
+        return {true, protocol};
+      }
     }
   }
-  return std::make_pair(false, StringRef{});
+  return {false, {}};
 }
 
 WebSocketServer::WebSocketServer(uv::Stream& stream,
-                                 ArrayRef<StringRef> protocols,
-                                 const ServerOptions& options,
-                                 const private_init&)
+                                 span<const std::string_view> protocols,
+                                 ServerOptions options, const private_init&)
     : m_stream{stream},
       m_helper{m_req},
       m_protocols{protocols.begin(), protocols.end()},
-      m_options{options} {
+      m_options{std::move(options)} {
   // Header handling
-  m_req.header.connect([this](StringRef name, StringRef value) {
-    if (name.equals_lower("host")) {
+  m_req.header.connect([this](std::string_view name, std::string_view value) {
+    if (equals_lower(name, "host")) {
       if (m_options.checkHost) {
-        if (!m_options.checkHost(value)) Abort(401, "Unrecognized Host");
+        if (!m_options.checkHost(value)) {
+          Abort(401, "Unrecognized Host");
+        }
       }
     }
   });
-  m_req.url.connect([this](StringRef name) {
+  m_req.url.connect([this](std::string_view name) {
     if (m_options.checkUrl) {
-      if (!m_options.checkUrl(name)) Abort(404, "Not Found");
+      if (!m_options.checkUrl(name)) {
+        Abort(404, "Not Found");
+      }
     }
   });
   m_req.headersComplete.connect([this](bool) {
     // We only accept websocket connections
-    if (!m_helper.IsUpgrade() || !m_req.IsUpgrade())
+    if (!m_helper.IsUpgrade() || !m_req.IsUpgrade()) {
       Abort(426, "Upgrade Required");
+    }
   });
 
   // Handle upgrade event
   m_helper.upgrade.connect([this] {
-    if (m_aborted) return;
+    if (m_aborted) {
+      return;
+    }
 
     // Negotiate sub-protocol
-    SmallVector<StringRef, 2> protocols{m_protocols.begin(), m_protocols.end()};
-    StringRef protocol = m_helper.MatchProtocol(protocols).second;
+    SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
+                                               m_protocols.end()};
+    std::string_view protocol = m_helper.MatchProtocol(protocols).second;
 
     // Disconnect our header reader
     m_dataConn.disconnect();
@@ -96,19 +113,24 @@
     auto ws = m_helper.Accept(m_stream, protocol);
 
     // Connect the websocket open event to our connected event.
-    ws->open.connect_extended([self, s = ws.get()](auto conn, StringRef) {
-      self->connected(self->m_req.GetUrl(), *s);
-      conn.disconnect();  // one-shot
-    });
+    ws->open.connect_extended(
+        [self, s = ws.get()](auto conn, std::string_view) {
+          self->connected(self->m_req.GetUrl(), *s);
+          conn.disconnect();  // one-shot
+        });
   });
 
   // Set up stream
   stream.StartRead();
   m_dataConn =
       stream.data.connect_connection([this](uv::Buffer& buf, size_t size) {
-        if (m_aborted) return;
-        m_req.Execute(StringRef{buf.base, size});
-        if (m_req.HasError()) Abort(400, "Bad Request");
+        if (m_aborted) {
+          return;
+        }
+        m_req.Execute(std::string_view{buf.base, size});
+        if (m_req.HasError()) {
+          Abort(400, "Bad Request");
+        }
       });
   m_errorConn =
       stream.error.connect_connection([this](uv::Error) { m_stream.Close(); });
@@ -116,7 +138,7 @@
 }
 
 std::shared_ptr<WebSocketServer> WebSocketServer::Create(
-    uv::Stream& stream, ArrayRef<StringRef> protocols,
+    uv::Stream& stream, span<const std::string_view> protocols,
     const ServerOptions& options) {
   auto server = std::make_shared<WebSocketServer>(stream, protocols, options,
                                                   private_init{});
@@ -124,8 +146,10 @@
   return server;
 }
 
-void WebSocketServer::Abort(uint16_t code, StringRef reason) {
-  if (m_aborted) return;
+void WebSocketServer::Abort(uint16_t code, std::string_view reason) {
+  if (m_aborted) {
+    return;
+  }
   m_aborted = true;
 
   // Build response
@@ -133,11 +157,15 @@
   raw_uv_ostream os{bufs, 1024};
 
   // Handle unsupported version
-  os << "HTTP/1.1 " << code << ' ' << reason << "\r\n";
-  if (code == 426) os << "Upgrade: WebSocket\r\n";
+  fmt::print(os, "HTTP/1.1 {} {}\r\n", code, reason);
+  if (code == 426) {
+    os << "Upgrade: WebSocket\r\n";
+  }
   os << "\r\n";
   m_stream.Write(bufs, [this](auto bufs, uv::Error) {
-    for (auto& buf : bufs) buf.Deallocate();
+    for (auto& buf : bufs) {
+      buf.Deallocate();
+    }
     m_stream.Shutdown([this] { m_stream.Close(); });
   });
 }
diff --git a/wpiutil/src/main/native/cpp/fs.cpp b/wpiutil/src/main/native/cpp/fs.cpp
new file mode 100644
index 0000000..34d6cca
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/fs.cpp
@@ -0,0 +1,326 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include <cassert>
+
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#include <sys/types.h>
+// Require at least Windows 7 API.
+#define _WIN32_WINNT 0x0601
+#define _WIN32_IE 0x0800  // MinGW at it again. FIXME: verify if still needed.
+#define WIN32_LEAN_AND_MEAN
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#define WIN32_NO_STATUS
+#include <windows.h>
+#undef WIN32_NO_STATUS
+#include <winternl.h>
+#include <ntstatus.h>
+
+#include <shellapi.h>
+#include <shlobj.h>
+
+#include "wpi/WindowsError.h"
+
+#else  // _WIN32
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#endif  // _WIN32
+
+#if defined(__APPLE__)
+#include <Availability.h>
+#endif
+#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) \
+     || (defined(__cplusplus) && __cplusplus >= 201703L)) \
+    && defined(__has_include)
+#if __has_include(<filesystem>) \
+    && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \
+        || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) \
+    && (defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 10 \
+        || (__GNUC__ >= 9 && __GNUC_MINOR__ >= 1))
+#define GHC_USE_STD_FS
+#endif
+#endif
+#ifndef GHC_USE_STD_FS
+// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE
+#define GHC_FILESYSTEM_IMPLEMENTATION
+#include "wpi/ghc/filesystem.hpp"
+#endif
+
+#include "wpi/Errno.h"
+#include "wpi/ErrorHandling.h"
+#include "wpi/WindowsError.h"
+#include "wpi/fs.h"
+
+namespace fs {
+
+#ifdef _WIN32
+
+#ifdef _MSC_VER
+#pragma comment(lib, "shell32.lib")
+#pragma comment(lib, "ole32.lib")
+#pragma warning(push)
+#pragma warning(disable : 4244 4267 4146)
+#endif
+
+const file_t kInvalidFile = INVALID_HANDLE_VALUE;
+
+static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) {
+  // This is a compatibility hack.  Really we should respect the creation
+  // disposition, but a lot of old code relied on the implicit assumption that
+  // OF_Append implied it would open an existing file.  Since the disposition is
+  // now explicit and defaults to CD_CreateAlways, this assumption would cause
+  // any usage of OF_Append to append to a new file, even if the file already
+  // existed.  A better solution might have two new creation dispositions:
+  // CD_AppendAlways and CD_AppendNew.  This would also address the problem of
+  // OF_Append being used on a read-only descriptor, which doesn't make sense.
+  if (Flags & OF_Append)
+    return OPEN_ALWAYS;
+
+  switch (Disp) {
+    case CD_CreateAlways:
+      return CREATE_ALWAYS;
+    case CD_CreateNew:
+      return CREATE_NEW;
+    case CD_OpenAlways:
+      return OPEN_ALWAYS;
+    case CD_OpenExisting:
+      return OPEN_EXISTING;
+  }
+  wpi_unreachable("unreachable!");
+}
+
+static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) {
+  DWORD Result = 0;
+  if (Access & FA_Read)
+    Result |= GENERIC_READ;
+  if (Access & FA_Write)
+    Result |= GENERIC_WRITE;
+  if (Flags & OF_Delete)
+    Result |= DELETE;
+  if (Flags & OF_UpdateAtime)
+    Result |= FILE_WRITE_ATTRIBUTES;
+  return Result;
+}
+
+static file_t openFileInternal(const path& Path, std::error_code& EC,
+                               DWORD Disp, DWORD Access, DWORD Flags,
+                               bool Inherit = false) {
+  SECURITY_ATTRIBUTES SA;
+  SA.nLength = sizeof(SA);
+  SA.lpSecurityDescriptor = nullptr;
+  SA.bInheritHandle = Inherit;
+
+  HANDLE H =
+      ::CreateFileW(Path.c_str(), Access,
+                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA,
+                    Disp, Flags, NULL);
+  if (H == INVALID_HANDLE_VALUE) {
+    DWORD LastError = ::GetLastError();
+    EC = wpi::mapWindowsError(LastError);
+    // Provide a better error message when trying to open directories.
+    // This only runs if we failed to open the file, so there is probably
+    // no performances issues.
+    if (LastError != ERROR_ACCESS_DENIED) {
+      return kInvalidFile;
+    }
+    if (is_directory(Path)) {
+      EC = std::make_error_code(std::errc::is_a_directory);
+    }
+    return kInvalidFile;
+  }
+  EC = std::error_code();
+  return H;
+}
+
+static std::error_code setDeleteDisposition(HANDLE Handle, bool Delete) {
+  FILE_DISPOSITION_INFO Disposition;
+  Disposition.DeleteFile = Delete;
+  if (!::SetFileInformationByHandle(Handle, FileDispositionInfo, &Disposition,
+                                    sizeof(Disposition)))
+    return wpi::mapWindowsError(::GetLastError());
+  return std::error_code();
+}
+
+file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp,
+                FileAccess Access, OpenFlags Flags, unsigned Mode) {
+  // Verify that we don't have both "append" and "excl".
+  assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) &&
+         "Cannot specify both 'CreateNew' and 'Append' file creation flags!");
+
+  DWORD NativeDisp = nativeDisposition(Disp, Flags);
+  DWORD NativeAccess = nativeAccess(Access, Flags);
+
+  bool Inherit = false;
+  if (Flags & OF_ChildInherit) {
+    Inherit = true;
+  }
+
+  file_t Result = openFileInternal(Path, EC, NativeDisp, NativeAccess,
+                                   FILE_ATTRIBUTE_NORMAL, Inherit);
+  if (EC) {
+    return Result;
+  }
+
+  if (Flags & OF_UpdateAtime) {
+    FILETIME FileTime;
+    SYSTEMTIME SystemTime;
+    ::GetSystemTime(&SystemTime);
+    if (::SystemTimeToFileTime(&SystemTime, &FileTime) == 0 ||
+        ::SetFileTime(Result, NULL, &FileTime, NULL) == 0) {
+      DWORD LastError = ::GetLastError();
+      ::CloseHandle(Result);
+      EC = wpi::mapWindowsError(LastError);
+      return kInvalidFile;
+    }
+  }
+
+  if (Flags & OF_Delete) {
+    if ((EC = setDeleteDisposition(Result, true))) {
+      ::CloseHandle(Result);
+      return kInvalidFile;
+    }
+  }
+  return Result;
+}
+
+file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) {
+  return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags);
+}
+
+int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) {
+  if (F == kInvalidFile) {
+    EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE);
+    return -1;
+  }
+
+  int CrtOpenFlags = 0;
+  if (Flags & OF_Append) {
+    CrtOpenFlags |= _O_APPEND;
+  }
+
+  if (Flags & OF_Text) {
+    CrtOpenFlags |= _O_TEXT;
+  }
+
+  int ResultFD = ::_open_osfhandle(intptr_t(F), CrtOpenFlags);
+  if (ResultFD == -1) {
+    ::CloseHandle(F);
+    EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE);
+    return -1;
+  }
+
+  EC = std::error_code();
+  F = kInvalidFile;
+  return ResultFD;
+}
+
+void CloseFile(file_t& F) {
+  ::CloseHandle(F);
+  F = kInvalidFile;
+}
+
+#else  // _WIN32
+
+const file_t kInvalidFile = -1;
+
+static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags,
+                           FileAccess Access) {
+  int Result = 0;
+  if (Access == FA_Read) {
+    Result |= O_RDONLY;
+  } else if (Access == FA_Write) {
+    Result |= O_WRONLY;
+  } else if (Access == (FA_Read | FA_Write)) {
+    Result |= O_RDWR;
+  }
+
+  // This is for compatibility with old code that assumed F_Append implied
+  // would open an existing file.  See Windows/Path.inc for a longer comment.
+  if (Flags & F_Append) {
+    Disp = CD_OpenAlways;
+  }
+
+  if (Disp == CD_CreateNew) {
+    Result |= O_CREAT;  // Create if it doesn't exist.
+    Result |= O_EXCL;   // Fail if it does.
+  } else if (Disp == CD_CreateAlways) {
+    Result |= O_CREAT;  // Create if it doesn't exist.
+    Result |= O_TRUNC;  // Truncate if it does.
+  } else if (Disp == CD_OpenAlways) {
+    Result |= O_CREAT;  // Create if it doesn't exist.
+  } else if (Disp == CD_OpenExisting) {
+    // Nothing special, just don't add O_CREAT and we get these semantics.
+  }
+
+  if (Flags & F_Append) {
+    Result |= O_APPEND;
+  }
+
+#ifdef O_CLOEXEC
+  if (!(Flags & OF_ChildInherit)) {
+    Result |= O_CLOEXEC;
+  }
+#endif
+
+  return Result;
+}
+
+file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp,
+                FileAccess Access, OpenFlags Flags, unsigned Mode) {
+  int OpenFlags = nativeOpenFlags(Disp, Flags, Access);
+  file_t ResultFD = kInvalidFile;
+
+  // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal
+  // when open is overloaded, such as in Bionic.
+  auto Open = [&]() { return ::open(Path.c_str(), OpenFlags, Mode); };
+  if ((ResultFD = wpi::sys::RetryAfterSignal(-1, Open)) < 0) {
+    EC = std::error_code(errno, std::generic_category());
+    return kInvalidFile;
+  }
+#ifndef O_CLOEXEC
+  if (!(Flags & OF_ChildInherit)) {
+    int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC);
+    (void)r;
+    assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed");
+  }
+#endif
+  EC = std::error_code();
+  return ResultFD;
+}
+
+file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) {
+  return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags, 0666);
+}
+
+int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) {
+  int fd = F;
+  F = kInvalidFile;
+  EC = std::error_code();
+  return fd;
+}
+
+void CloseFile(file_t& F) {
+  ::close(F);
+  F = kInvalidFile;
+}
+
+#endif  // _WIN32
+
+}  // namespace fs
diff --git a/wpiutil/src/main/native/cpp/future.cpp b/wpiutil/src/main/native/cpp/future.cpp
index 7ce0875..109eeb9 100644
--- a/wpiutil/src/main/native/cpp/future.cpp
+++ b/wpiutil/src/main/native/cpp/future.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/future.h"
 
@@ -28,10 +25,14 @@
 }
 
 bool PromiseFactoryBase::EraseRequest(uint64_t request) {
-  if (request == 0) return false;
+  if (request == 0) {
+    return false;
+  }
   auto it = std::find_if(m_requests.begin(), m_requests.end(),
                          [=](auto r) { return r == request; });
-  if (it == m_requests.end()) return false;  // no waiters
+  if (it == m_requests.end()) {
+    return false;  // no waiters
+  }
   m_requests.erase(it);
   return true;
 }
@@ -47,7 +48,9 @@
 
 void PromiseFactory<void>::SetValue(uint64_t request) {
   std::unique_lock lock(GetResultMutex());
-  if (!EraseRequest(request)) return;
+  if (!EraseRequest(request)) {
+    return;
+  }
   auto it = std::find_if(m_thens.begin(), m_thens.end(),
                          [=](const auto& x) { return x.request == request; });
   if (it != m_thens.end()) {
@@ -105,7 +108,9 @@
     // Did we get a response to *our* request?
     auto it = std::find_if(m_results.begin(), m_results.end(),
                            [=](const auto& r) { return r == request; });
-    if (it != m_results.end()) return;
+    if (it != m_results.end()) {
+      return;
+    }
     // No, keep waiting for a response
     Wait(lock);
   }
diff --git a/wpiutil/src/main/native/cpp/hostname.cpp b/wpiutil/src/main/native/cpp/hostname.cpp
index ffc5cad..d907023 100644
--- a/wpiutil/src/main/native/cpp/hostname.cpp
+++ b/wpiutil/src/main/native/cpp/hostname.cpp
@@ -1,18 +1,15 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/hostname.h"
 
 #include <cstdlib>
 #include <string>
+#include <string_view>
 
 #include "uv.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
 
 namespace wpi {
 
@@ -27,14 +24,16 @@
   } else if (err == UV_ENOBUFS) {
     char* name2 = static_cast<char*>(std::malloc(size));
     err = uv_os_gethostname(name2, &size);
-    if (err == 0) rv.assign(name2, size);
+    if (err == 0) {
+      rv.assign(name2, size);
+    }
     std::free(name2);
   }
 
   return rv;
 }
 
-StringRef GetHostname(SmallVectorImpl<char>& name) {
+std::string_view GetHostname(SmallVectorImpl<char>& name) {
   // Use a tmp array to not require the SmallVector to be too large.
   char tmpName[256];
   size_t size = sizeof(tmpName);
@@ -47,10 +46,12 @@
   } else if (err == UV_ENOBUFS) {
     name.resize(size);
     err = uv_os_gethostname(name.data(), &size);
-    if (err != 0) size = 0;
+    if (err != 0) {
+      size = 0;
+    }
   }
 
-  return StringRef{name.data(), size};
+  return {name.data(), size};
 }
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp
index 90a2291..4fc5f45 100644
--- a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp
+++ b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp
@@ -1,50 +1,94 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include <jni.h>
 
-#include "edu_wpi_first_wpiutil_WPIUtilJNI.h"
+#include "edu_wpi_first_util_WPIUtilJNI.h"
 #include "wpi/PortForwarder.h"
+#include "wpi/Synchronization.h"
 #include "wpi/jni_util.h"
 #include "wpi/timestamp.h"
 
 using namespace wpi::java;
 
+static bool mockTimeEnabled = false;
+static uint64_t mockNow = 0;
+
+static JException interruptedEx;
+
 extern "C" {
 
 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
   JNIEnv* env;
-  if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
+  if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
     return JNI_ERR;
+  }
+
+  interruptedEx = JException(env, "java/lang/InterruptedException");
+  if (!interruptedEx) {
+    return JNI_ERR;
+  }
 
   return JNI_VERSION_1_6;
 }
 
-JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {}
+JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
+  JNIEnv* env;
+  if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+    return;
+  }
+  interruptedEx.free(env);
+}
 
 /*
- * Class:     edu_wpi_first_wpiutil_WPIUtilJNI
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    enableMockTime
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_enableMockTime
+  (JNIEnv*, jclass)
+{
+  mockTimeEnabled = true;
+  wpi::SetNowImpl([] { return mockNow; });
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    setMockTime
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_setMockTime
+  (JNIEnv*, jclass, jlong time)
+{
+  mockNow = time;
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
  * Method:    now
  * Signature: ()J
  */
 JNIEXPORT jlong JNICALL
-Java_edu_wpi_first_wpiutil_WPIUtilJNI_now
+Java_edu_wpi_first_util_WPIUtilJNI_now
   (JNIEnv*, jclass)
 {
-  return wpi::Now();
+  if (mockTimeEnabled) {
+    return mockNow;
+  } else {
+    return wpi::Now();
+  }
 }
 
 /*
- * Class:     edu_wpi_first_wpiutil_WPIUtilJNI
+ * Class:     edu_wpi_first_util_WPIUtilJNI
  * Method:    addPortForwarder
  * Signature: (ILjava/lang/String;I)V
  */
 JNIEXPORT void JNICALL
-Java_edu_wpi_first_wpiutil_WPIUtilJNI_addPortForwarder
+Java_edu_wpi_first_util_WPIUtilJNI_addPortForwarder
   (JNIEnv* env, jclass, jint port, jstring remoteHost, jint remotePort)
 {
   wpi::PortForwarder::GetInstance().Add(static_cast<unsigned int>(port),
@@ -53,15 +97,180 @@
 }
 
 /*
- * Class:     edu_wpi_first_wpiutil_WPIUtilJNI
+ * Class:     edu_wpi_first_util_WPIUtilJNI
  * Method:    removePortForwarder
  * Signature: (I)V
  */
 JNIEXPORT void JNICALL
-Java_edu_wpi_first_wpiutil_WPIUtilJNI_removePortForwarder
+Java_edu_wpi_first_util_WPIUtilJNI_removePortForwarder
   (JNIEnv* env, jclass, jint port)
 {
   wpi::PortForwarder::GetInstance().Remove(port);
 }
 
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    createEvent
+ * Signature: (ZZ)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_createEvent
+  (JNIEnv*, jclass, jboolean manualReset, jboolean initialState)
+{
+  return wpi::CreateEvent(manualReset, initialState);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    destroyEvent
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_destroyEvent
+  (JNIEnv*, jclass, jint eventHandle)
+{
+  wpi::DestroyEvent(eventHandle);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    setEvent
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_setEvent
+  (JNIEnv*, jclass, jint eventHandle)
+{
+  wpi::SetEvent(eventHandle);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    resetEvent
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_resetEvent
+  (JNIEnv*, jclass, jint eventHandle)
+{
+  wpi::ResetEvent(eventHandle);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    createSemaphore
+ * Signature: (II)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_createSemaphore
+  (JNIEnv*, jclass, jint initialCount, jint maximumCount)
+{
+  return wpi::CreateSemaphore(initialCount, maximumCount);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    destroySemaphore
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_destroySemaphore
+  (JNIEnv*, jclass, jint semHandle)
+{
+  wpi::DestroySemaphore(semHandle);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    releaseSemaphore
+ * Signature: (II)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_releaseSemaphore
+  (JNIEnv*, jclass, jint semHandle, jint releaseCount)
+{
+  return wpi::ReleaseSemaphore(semHandle, releaseCount);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    waitForObject
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_waitForObject
+  (JNIEnv* env, jclass, jint handle)
+{
+  if (!wpi::WaitForObject(handle)) {
+    interruptedEx.Throw(env, "WaitForObject interrupted");
+  }
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    waitForObjectTimeout
+ * Signature: (ID)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_waitForObjectTimeout
+  (JNIEnv* env, jclass, jint handle, jdouble timeout)
+{
+  bool timedOut;
+  if (!wpi::WaitForObject(handle, timeout, &timedOut) && !timedOut) {
+    interruptedEx.Throw(env, "WaitForObject interrupted");
+    return false;
+  }
+  return timedOut;
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    waitForObjects
+ * Signature: ([I)[I
+ */
+JNIEXPORT jintArray JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_waitForObjects
+  (JNIEnv* env, jclass, jintArray handles)
+{
+  JIntArrayRef handlesArr{env, handles};
+  wpi::SmallVector<WPI_Handle, 8> signaledBuf;
+  signaledBuf.resize(handlesArr.size());
+  wpi::span<const WPI_Handle> handlesArr2{
+      reinterpret_cast<const WPI_Handle*>(handlesArr.array().data()),
+      handlesArr.size()};
+
+  auto signaled = wpi::WaitForObjects(handlesArr2, signaledBuf);
+  if (signaled.empty()) {
+    interruptedEx.Throw(env, "WaitForObjects interrupted");
+    return nullptr;
+  }
+  return MakeJIntArray(env, signaled);
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    waitForObjectsTimeout
+ * Signature: ([ID)[I
+ */
+JNIEXPORT jintArray JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_waitForObjectsTimeout
+  (JNIEnv* env, jclass, jintArray handles, jdouble timeout)
+{
+  JIntArrayRef handlesArr{env, handles};
+  wpi::SmallVector<WPI_Handle, 8> signaledBuf;
+  signaledBuf.resize(handlesArr.size());
+  wpi::span<const WPI_Handle> handlesArr2{
+      reinterpret_cast<const WPI_Handle*>(handlesArr.array().data()),
+      handlesArr.size()};
+
+  bool timedOut;
+  auto signaled =
+      wpi::WaitForObjects(handlesArr2, signaledBuf, timeout, &timedOut);
+  if (signaled.empty() && !timedOut) {
+    interruptedEx.Throw(env, "WaitForObjects interrupted");
+    return nullptr;
+  }
+  return MakeJIntArray(env, signaled);
+}
+
 }  // extern "C"
diff --git a/wpiutil/src/main/native/cpp/json.cpp b/wpiutil/src/main/native/cpp/json.cpp
index ba9cd8e..f1cfb14 100644
--- a/wpiutil/src/main/native/cpp/json.cpp
+++ b/wpiutil/src/main/native/cpp/json.cpp
@@ -34,39 +34,51 @@
 #define WPI_JSON_IMPLEMENTATION
 #include "wpi/json.h"
 
+#include "fmt/format.h"
 #include "wpi/raw_ostream.h"
 
 namespace wpi {
 namespace detail {
 
-exception::exception(int id_, const Twine& what_arg)
-    : id(id_), m(what_arg.str()) {}
+exception::exception(int id_, std::string_view what_arg)
+    : id(id_), m(std::string{what_arg}) {}
 
-parse_error parse_error::create(int id_, std::size_t byte_, const Twine& what_arg)
+parse_error parse_error::create(int id_, std::size_t byte_, std::string_view what_arg)
 {
-    return parse_error(id_, byte_, "[json.exception.parse_error." + Twine(id_) + "] parse error" +
-                    (byte_ != 0 ? (" at " + Twine(byte_)) : Twine("")) +
-                    ": " + what_arg);
+    if (byte_ != 0)
+        return parse_error(id_, byte_, fmt::format("[json.exception.parse_error.{}] parse error at {}: {}", id_, byte_, what_arg));
+    else
+        return parse_error(id_, byte_, fmt::format("[json.exception.parse_error.{}] parse error: {}", id_, what_arg));
 }
 
-invalid_iterator invalid_iterator::create(int id_, const Twine& what_arg)
+invalid_iterator invalid_iterator::create(int id_, std::string_view what_arg)
 {
-    return invalid_iterator(id_, "[json.exception.invalid_iterator." + Twine(id_) + "] " + what_arg);
+    return invalid_iterator(id_, fmt::format("[json.exception.invalid_iterator.{}] {}", id_, what_arg));
 }
 
-type_error type_error::create(int id_, const Twine& what_arg)
+invalid_iterator invalid_iterator::create(int id_, std::string_view what_arg, std::string_view type_info)
 {
-    return type_error(id_, "[json.exception.type_error." + Twine(id_) + "] " + what_arg);
+    return invalid_iterator(id_, fmt::format("[json.exception.invalid_iterator.{}] {} {}", id_, what_arg, type_info));
 }
 
-out_of_range out_of_range::create(int id_, const Twine& what_arg)
+type_error type_error::create(int id_, std::string_view what_arg)
 {
-    return out_of_range(id_, "[json.exception.out_of_range." + Twine(id_) + "] " + what_arg);
+    return type_error(id_, fmt::format("[json.exception.type_error.{}] {}", id_, what_arg));
 }
 
-other_error other_error::create(int id_, const Twine& what_arg)
+type_error type_error::create(int id_, std::string_view what_arg, std::string_view type_info)
 {
-    return other_error(id_, "[json.exception.other_error." + Twine(id_) + "] " + what_arg);
+    return type_error(id_, fmt::format("[json.exception.type_error.{}] {} {}", id_, what_arg, type_info));
+}
+
+out_of_range out_of_range::create(int id_, std::string_view what_arg)
+{
+    return out_of_range(id_, fmt::format("[json.exception.out_of_range.{}] {}", id_, what_arg));
+}
+
+other_error other_error::create(int id_, std::string_view what_arg)
+{
+    return other_error(id_, fmt::format("[json.exception.other_error.{}] {}", id_, what_arg));
 }
 
 }  // namespace detail
@@ -353,12 +365,12 @@
         JSON_CATCH (std::out_of_range&)
         {
             // create better exception explanation
-            JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range"));
+            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
         }
     }
     else
     {
-        JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
     }
 }
 
@@ -374,16 +386,16 @@
         JSON_CATCH (std::out_of_range&)
         {
             // create better exception explanation
-            JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range"));
+            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
         }
     }
     else
     {
-        JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
     }
 }
 
-json::reference json::at(StringRef key)
+json::reference json::at(std::string_view key)
 {
     // at only works for objects
     if (JSON_LIKELY(is_object()))
@@ -392,17 +404,17 @@
         if (it == m_value.object->end())
         {
             // create better exception explanation
-            JSON_THROW(out_of_range::create(403, "key '" + Twine(key) + "' not found"));
+            JSON_THROW(out_of_range::create(403, fmt::format("key '{}' not found", key)));
         }
         return it->second;
     }
     else
     {
-        JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
     }
 }
 
-json::const_reference json::at(StringRef key) const
+json::const_reference json::at(std::string_view key) const
 {
     // at only works for objects
     if (JSON_LIKELY(is_object()))
@@ -411,13 +423,13 @@
         if (it == m_value.object->end())
         {
             // create better exception explanation
-            JSON_THROW(out_of_range::create(403, "key '" + Twine(key) + "' not found"));
+            JSON_THROW(out_of_range::create(403, fmt::format("key '{}' not found", key)));
         }
         return it->second;
     }
     else
     {
-        JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
     }
 }
 
@@ -445,7 +457,7 @@
         return m_value.array->operator[](idx);
     }
 
-    JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name())));
+    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
 }
 
 json::const_reference json::operator[](size_type idx) const
@@ -456,10 +468,10 @@
         return m_value.array->operator[](idx);
     }
 
-    JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name())));
+    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
 }
 
-json::reference json::operator[](StringRef key)
+json::reference json::operator[](std::string_view key)
 {
     // implicitly convert null value to an empty object
     if (is_null())
@@ -475,10 +487,10 @@
         return m_value.object->operator[](key);
     }
 
-    JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name())));
+    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
 }
 
-json::const_reference json::operator[](StringRef key) const
+json::const_reference json::operator[](std::string_view key) const
 {
     // const operator[] only works for objects
     if (JSON_LIKELY(is_object()))
@@ -487,10 +499,10 @@
         return m_value.object->find(key)->second;
     }
 
-    JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name())));
+    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
 }
 
-json::size_type json::erase(StringRef key)
+json::size_type json::erase(std::string_view key)
 {
     // this erase only works for objects
     if (JSON_LIKELY(is_object()))
@@ -498,7 +510,7 @@
         return m_value.object->erase(key);
     }
 
-    JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name())));
+    JSON_THROW(type_error::create(307, "cannot use erase() with", type_name()));
 }
 
 void json::erase(const size_type idx)
@@ -508,18 +520,18 @@
     {
         if (JSON_UNLIKELY(idx >= size()))
         {
-            JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range"));
+            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
         }
 
         m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx));
     }
     else
     {
-        JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(307, "cannot use erase() with", type_name()));
     }
 }
 
-json::iterator json::find(StringRef key)
+json::iterator json::find(std::string_view key)
 {
     auto result = end();
 
@@ -531,7 +543,7 @@
     return result;
 }
 
-json::const_iterator json::find(StringRef key) const
+json::const_iterator json::find(std::string_view key) const
 {
     auto result = cend();
 
@@ -543,7 +555,7 @@
     return result;
 }
 
-json::size_type json::count(StringRef key) const
+json::size_type json::count(std::string_view key) const
 {
     // return 0 for all nonobject types
     return is_object() ? m_value.object->count(key) : 0;
@@ -689,7 +701,7 @@
     // push_back only works for null objects or arrays
     if (JSON_UNLIKELY(not(is_null() or is_array())))
     {
-        JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(308, "cannot use push_back() with", type_name()));
     }
 
     // transform null object into an array
@@ -711,7 +723,7 @@
     // push_back only works for null objects or arrays
     if (JSON_UNLIKELY(not(is_null() or is_array())))
     {
-        JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(308, "cannot use push_back() with", type_name()));
     }
 
     // transform null object into an array
@@ -731,7 +743,7 @@
     if (is_object() and init.size() == 2 and (*init.begin())->is_string())
     {
         std::string key = init.begin()->moved_or_copied();
-        push_back(std::pair<StringRef, json>(key, (init.begin() + 1)->moved_or_copied()));
+        push_back(std::pair<std::string_view, json>(key, (init.begin() + 1)->moved_or_copied()));
     }
     else
     {
@@ -756,7 +768,7 @@
         return result;
     }
 
-    JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name())));
+    JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
 }
 
 json::iterator json::insert(const_iterator pos, size_type cnt, const json& val)
@@ -776,7 +788,7 @@
         return result;
     }
 
-    JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name())));
+    JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
 }
 
 json::iterator json::insert(const_iterator pos, const_iterator first, const_iterator last)
@@ -784,7 +796,7 @@
     // insert only works for arrays
     if (JSON_UNLIKELY(not is_array()))
     {
-        JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
     }
 
     // check if iterator pos fits to this JSON value
@@ -818,7 +830,7 @@
     // insert only works for arrays
     if (JSON_UNLIKELY(not is_array()))
     {
-        JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
     }
 
     // check if iterator pos fits to this JSON value
@@ -838,7 +850,7 @@
     // insert only works for objects
     if (JSON_UNLIKELY(not is_object()))
     {
-        JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
     }
 
     // check if range iterators belong to the same JSON object
@@ -871,11 +883,11 @@
 
     if (JSON_UNLIKELY(not is_object()))
     {
-        JSON_THROW(type_error::create(312, "cannot use update() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(312, "cannot use update() with", type_name()));
     }
     if (JSON_UNLIKELY(not j.is_object()))
     {
-        JSON_THROW(type_error::create(312, "cannot use update() with " + Twine(j.type_name())));
+        JSON_THROW(type_error::create(312, "cannot use update() with", j.type_name()));
     }
 
     for (auto it = j.cbegin(); it != j.cend(); ++it)
@@ -896,7 +908,7 @@
 
     if (JSON_UNLIKELY(not is_object()))
     {
-        JSON_THROW(type_error::create(312, "cannot use update() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(312, "cannot use update() with", type_name()));
     }
 
     // check if range iterators belong to the same JSON object
@@ -1156,7 +1168,7 @@
                         if (JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size()))
                         {
                             // avoid undefined behavior
-                            JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range"));
+                            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
                         }
                         else
                         {
@@ -1194,7 +1206,7 @@
             }
             else
             {
-                JSON_THROW(out_of_range::create(403, "key '" + Twine(last_path) + "' not found"));
+                JSON_THROW(out_of_range::create(403, fmt::format("key '{}' not found", last_path)));
             }
         }
         else if (parent.is_array())
@@ -1227,13 +1239,13 @@
             // check if desired value is present
             if (JSON_UNLIKELY(it == val.m_value.object->end()))
             {
-                JSON_THROW(parse_error::create(105, 0, Twine(error_msg) + " must have member '" + Twine(member) + "'"));
+                JSON_THROW(parse_error::create(105, 0, fmt::format("{} must have member '{}'", error_msg, member)));
             }
 
             // check if result is of type string
             if (JSON_UNLIKELY(string_type and not it->second.is_string()))
             {
-                JSON_THROW(parse_error::create(105, 0, Twine(error_msg) + " must have string member '" + Twine(member) + "'"));
+                JSON_THROW(parse_error::create(105, 0, fmt::format("{} must have string member '{}'", error_msg, member)));
             }
 
             // no error: return value
@@ -1321,7 +1333,7 @@
                 // throw an exception if test fails
                 if (JSON_UNLIKELY(not success))
                 {
-                    JSON_THROW(other_error::create(501, "unsuccessful: " + Twine(val.dump())));
+                    JSON_THROW(other_error::create(501, fmt::format("unsuccessful: {}", val.dump())));
                 }
 
                 break;
@@ -1331,7 +1343,7 @@
             {
                 // op must be "add", "remove", "replace", "move", "copy", or
                 // "test"
-                JSON_THROW(parse_error::create(105, 0, "operation value '" + Twine(op) + "' is invalid"));
+                JSON_THROW(parse_error::create(105, 0, fmt::format("operation value '{}' is invalid", op)));
             }
         }
     }
@@ -1413,7 +1425,7 @@
                 for (auto it = source.cbegin(); it != source.cend(); ++it)
                 {
                     // escape the key name to be used in a JSON patch
-                    const auto key = json_pointer::escape(it.key());
+                    const auto key = json_pointer::escape(std::string{it.key()});
 
                     if (target.find(it.key()) != target.end())
                     {
@@ -1437,7 +1449,7 @@
                     if (source.find(it.key()) == source.end())
                     {
                         // found a key that is not in this -> add it
-                        const auto key = json_pointer::escape(it.key());
+                        const auto key = json_pointer::escape(std::string{it.key()});
                         result.push_back(
                         {
                             {"op", "add"}, {"path", path + "/" + key},
diff --git a/wpiutil/src/main/native/cpp/json_binary_reader.cpp b/wpiutil/src/main/native/cpp/json_binary_reader.cpp
index 9298e92..cee5e39 100644
--- a/wpiutil/src/main/native/cpp/json_binary_reader.cpp
+++ b/wpiutil/src/main/native/cpp/json_binary_reader.cpp
@@ -36,6 +36,7 @@
 
 #include <cmath>  // ldexp
 
+#include "fmt/format.h"
 #include "wpi/raw_istream.h"
 
 namespace wpi {
@@ -711,7 +712,7 @@
 
         default: // anything else (0xFF is handled inside the other types)
         {
-            JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + Twine::utohexstr(current)));
+            JSON_THROW(parse_error::create(112, chars_read, fmt::format("error reading CBOR; last byte: {:#02x}", current)));
         }
     }
 }
@@ -1034,7 +1035,7 @@
         default: // anything else
         {
             JSON_THROW(parse_error::create(112, chars_read,
-                                           "error reading MessagePack; last byte: 0x" + Twine::utohexstr(current)));
+                fmt::format("error reading MessagePack; last byte: {:#02x}", current)));
         }
     }
 }
@@ -1106,7 +1107,8 @@
 
         default:
         {
-            JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + Twine::utohexstr(current)));
+            JSON_THROW(parse_error::create(113, chars_read,
+                fmt::format("expected a CBOR string; last byte: {:#02x}", current)));
         }
     }
 }
@@ -1172,7 +1174,7 @@
         default:
         {
             JSON_THROW(parse_error::create(113, chars_read,
-                                           "expected a MessagePack string; last byte: 0x" + Twine::utohexstr(current)));
+                fmt::format("expected a MessagePack string; last byte: {:#02x}", current)));
         }
     }
 }
@@ -1200,7 +1202,7 @@
             return get_string(get_number<int64_t>());
         default:
             JSON_THROW(parse_error::create(113, chars_read,
-                                           "expected a UBJSON string; last byte: 0x" + Twine::utohexstr(current)));
+                fmt::format("expected a UBJSON string; last byte: {:#02x}", current)));
     }
 }
 
@@ -1220,7 +1222,7 @@
         if (current != '#')
         {
             JSON_THROW(parse_error::create(112, chars_read,
-                                           "expected '#' after UBJSON type information; last byte: 0x" + Twine::utohexstr(current)));
+                fmt::format("expected '#' after UBJSON type information; last byte: {:#02x}", current)));
         }
         sz = parse_ubjson_internal();
     }
@@ -1269,7 +1271,7 @@
             if (JSON_UNLIKELY(current > 127))
             {
                 JSON_THROW(parse_error::create(113, chars_read,
-                                               "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + Twine::utohexstr(current)));
+                    fmt::format("byte after 'C' must be in range 0x00..0x7F; last byte: {:#02x}", current)));
             }
             return std::string(1, static_cast<char>(current));
         }
@@ -1285,7 +1287,7 @@
 
         default: // anything else
             JSON_THROW(parse_error::create(112, chars_read,
-                                           "error reading UBJSON; last byte: 0x" + Twine::utohexstr(current)));
+                fmt::format("error reading UBJSON; last byte: {:#02x}", current)));
     }
 }
 
@@ -1299,7 +1301,7 @@
         if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
         {
             JSON_THROW(out_of_range::create(408,
-                                            "excessive array size: " + Twine(size_and_type.first)));
+                fmt::format("excessive array size: {}", size_and_type.first)));
         }
 
         if (size_and_type.second != 0)
@@ -1344,7 +1346,7 @@
         if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
         {
             JSON_THROW(out_of_range::create(408,
-                                            "excessive object size: " + Twine(size_and_type.first)));
+                fmt::format("excessive object size: {}", size_and_type.first)));
         }
 
         if (size_and_type.second != 0)
@@ -1382,7 +1384,7 @@
     return binary_reader(is).parse_cbor(strict);
 }
 
-json json::from_cbor(ArrayRef<uint8_t> arr, const bool strict)
+json json::from_cbor(span<const uint8_t> arr, const bool strict)
 {
     raw_mem_istream is(arr);
     return from_cbor(is, strict);
@@ -1393,7 +1395,7 @@
     return binary_reader(is).parse_msgpack(strict);
 }
 
-json json::from_msgpack(ArrayRef<uint8_t> arr, const bool strict)
+json json::from_msgpack(span<const uint8_t> arr, const bool strict)
 {
     raw_mem_istream is(arr);
     return from_msgpack(is, strict);
@@ -1404,7 +1406,7 @@
     return binary_reader(is).parse_ubjson(strict);
 }
 
-json json::from_ubjson(ArrayRef<uint8_t> arr, const bool strict)
+json json::from_ubjson(span<const uint8_t> arr, const bool strict)
 {
     raw_mem_istream is(arr);
     return from_ubjson(is, strict);
diff --git a/wpiutil/src/main/native/cpp/json_binary_writer.cpp b/wpiutil/src/main/native/cpp/json_binary_writer.cpp
index 2c0bbee..db23f8d 100644
--- a/wpiutil/src/main/native/cpp/json_binary_writer.cpp
+++ b/wpiutil/src/main/native/cpp/json_binary_writer.cpp
@@ -34,6 +34,7 @@
 #define WPI_JSON_IMPLEMENTATION
 #include "wpi/json.h"
 
+#include "fmt/format.h"
 #include "wpi/raw_ostream.h"
 
 namespace wpi {
@@ -75,9 +76,9 @@
                       const bool use_type, const bool add_prefix = true);
 
   private:
-    void write_cbor_string(StringRef str);
+    void write_cbor_string(std::string_view str);
 
-    void write_msgpack_string(StringRef str);
+    void write_msgpack_string(std::string_view str);
 
     /*
     @brief write a number to output input
@@ -630,7 +631,7 @@
                 o << static_cast<CharType>('S');
             }
             write_number_with_ubjson_prefix(j.m_value.string->size(), true);
-            o << j.m_value.string;
+            o << *j.m_value.string;
             break;
         }
 
@@ -731,7 +732,7 @@
     }
 }
 
-void json::binary_writer::write_cbor_string(StringRef str)
+void json::binary_writer::write_cbor_string(std::string_view str)
 {
     // step 1: write control byte and the string length
     const auto N = str.size();
@@ -766,7 +767,7 @@
     o << str;
 }
 
-void json::binary_writer::write_msgpack_string(StringRef str)
+void json::binary_writer::write_msgpack_string(std::string_view str)
 {
     // step 1: write control byte and the string length
     const auto N = str.size();
@@ -812,7 +813,7 @@
         std::reverse(vec.begin(), vec.end());
     }
 
-    o << ArrayRef<uint8_t>(vec.data(), sizeof(NumberType));
+    o << span{vec.data(), sizeof(NumberType)};
 }
 
 template<typename NumberType, typename std::enable_if<
@@ -862,7 +863,7 @@
     }
     else
     {
-        JSON_THROW(out_of_range::create(407, "number overflow serializing " + Twine(n)));
+        JSON_THROW(out_of_range::create(407, fmt::format("number overflow serializing {}", n)));
     }
 }
 
@@ -915,7 +916,7 @@
     // LCOV_EXCL_START
     else
     {
-        JSON_THROW(out_of_range::create(407, "number overflow serializing " + Twine(n)));
+        JSON_THROW(out_of_range::create(407, fmt::format("number overflow serializing {}", n)));
     }
     // LCOV_EXCL_STOP
 }
@@ -1003,7 +1004,7 @@
     return result;
 }
 
-ArrayRef<uint8_t> json::to_cbor(const json& j, std::vector<uint8_t>& buf)
+span<uint8_t> json::to_cbor(const json& j, std::vector<uint8_t>& buf)
 {
     buf.clear();
     raw_uvector_ostream os(buf);
@@ -1011,7 +1012,7 @@
     return os.array();
 }
 
-ArrayRef<uint8_t> json::to_cbor(const json& j, SmallVectorImpl<uint8_t>& buf)
+span<uint8_t> json::to_cbor(const json& j, SmallVectorImpl<uint8_t>& buf)
 {
     buf.clear();
     raw_usvector_ostream os(buf);
@@ -1032,7 +1033,7 @@
     return result;
 }
 
-ArrayRef<uint8_t> json::to_msgpack(const json& j, std::vector<uint8_t>& buf)
+span<uint8_t> json::to_msgpack(const json& j, std::vector<uint8_t>& buf)
 {
     buf.clear();
     raw_uvector_ostream os(buf);
@@ -1040,7 +1041,7 @@
     return os.array();
 }
 
-ArrayRef<uint8_t> json::to_msgpack(const json& j, SmallVectorImpl<uint8_t>& buf)
+span<uint8_t> json::to_msgpack(const json& j, SmallVectorImpl<uint8_t>& buf)
 {
     buf.clear();
     raw_usvector_ostream os(buf);
@@ -1063,8 +1064,8 @@
     return result;
 }
 
-ArrayRef<uint8_t> json::to_ubjson(const json& j, std::vector<uint8_t>& buf,
-                                  const bool use_size, const bool use_type)
+span<uint8_t> json::to_ubjson(const json& j, std::vector<uint8_t>& buf,
+                              const bool use_size, const bool use_type)
 {
     buf.clear();
     raw_uvector_ostream os(buf);
@@ -1072,8 +1073,8 @@
     return os.array();
 }
 
-ArrayRef<uint8_t> json::to_ubjson(const json& j, SmallVectorImpl<uint8_t>& buf,
-                                  const bool use_size, const bool use_type)
+span<uint8_t> json::to_ubjson(const json& j, SmallVectorImpl<uint8_t>& buf,
+                              const bool use_size, const bool use_type)
 {
     buf.clear();
     raw_usvector_ostream os(buf);
diff --git a/wpiutil/src/main/native/cpp/json_parser.cpp b/wpiutil/src/main/native/cpp/json_parser.cpp
index e0d7db3..db1ed11 100644
--- a/wpiutil/src/main/native/cpp/json_parser.cpp
+++ b/wpiutil/src/main/native/cpp/json_parser.cpp
@@ -38,7 +38,7 @@
 #include <cmath>
 #include <cstdlib>
 
-#include "wpi/Format.h"
+#include "fmt/format.h"
 #include "wpi/SmallString.h"
 #include "wpi/raw_istream.h"
 #include "wpi/raw_ostream.h"
@@ -336,7 +336,7 @@
     }
 
     /// return current string value
-    StringRef get_string()
+    std::string_view get_string()
     {
         return token_buffer;
     }
@@ -1407,7 +1407,7 @@
         if (c <= '\x1F')
         {
             // escape control characters
-            ss << "<U+" << format_hex_no_prefix(c, 4, true) << '>';
+            ss << fmt::format("<U+{:04X}>", c);
         }
         else
         {
@@ -1605,7 +1605,7 @@
 
                 if (keep and keep_tag and not value.is_discarded())
                 {
-                    result.m_value.object->try_emplace(StringRef(key.data(), key.size()), std::move(value));
+                    result.m_value.object->try_emplace(std::string_view(key.data(), key.size()), std::move(value));
                 }
 
                 // comma -> next value
@@ -1757,8 +1757,8 @@
             {
                 if (allow_exceptions)
                 {
-                    JSON_THROW(out_of_range::create(406, "number overflow parsing '" +
-                                                    Twine(m_lexer.get_token_string()) + "'"));
+                    JSON_THROW(out_of_range::create(406,
+                        fmt::format("number overflow parsing '{}'", m_lexer.get_token_string())));
                 }
                 expect(token_type::uninitialized);
             }
@@ -1917,15 +1917,15 @@
     JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg));
 }
 
-json json::parse(StringRef s,
+json json::parse(std::string_view s,
                         const parser_callback_t cb,
                         const bool allow_exceptions)
 {
-    raw_mem_istream is(makeArrayRef(s.data(), s.size()));
+    raw_mem_istream is(span<const char>(s.data(), s.size()));
     return parse(is, cb, allow_exceptions);
 }
 
-json json::parse(ArrayRef<uint8_t> arr,
+json json::parse(span<const uint8_t> arr,
                         const parser_callback_t cb,
                         const bool allow_exceptions)
 {
@@ -1942,13 +1942,13 @@
     return result;
 }
 
-bool json::accept(StringRef s)
+bool json::accept(std::string_view s)
 {
-    raw_mem_istream is(makeArrayRef(s.data(), s.size()));
+    raw_mem_istream is(span<const char>(s.data(), s.size()));
     return parser(is).accept(true);
 }
 
-bool json::accept(ArrayRef<uint8_t> arr)
+bool json::accept(span<const uint8_t> arr)
 {
     raw_mem_istream is(arr);
     return parser(is).accept(true);
diff --git a/wpiutil/src/main/native/cpp/json_pointer.cpp b/wpiutil/src/main/native/cpp/json_pointer.cpp
index 9cb1ec4..51548b3 100644
--- a/wpiutil/src/main/native/cpp/json_pointer.cpp
+++ b/wpiutil/src/main/native/cpp/json_pointer.cpp
@@ -36,7 +36,9 @@
 
 #include <numeric> // accumulate
 
+#include "fmt/format.h"
 #include "wpi/SmallString.h"
+#include "wpi/StringExtras.h"
 
 namespace wpi {
 
@@ -50,17 +52,16 @@
     });
 }
 
-int json_pointer::array_index(const Twine& s)
+int json_pointer::array_index(std::string_view s)
 {
-    SmallString<128> buf;
-    StringRef str = s.toNullTerminatedStringRef(buf);
+    SmallString<128> str{s};
     std::size_t processed_chars = 0;
-    const int res = std::stoi(str, &processed_chars);
+    const int res = std::stoi(str.c_str(), &processed_chars);
 
     // check if the string was completely read
     if (JSON_UNLIKELY(processed_chars != str.size()))
     {
-        JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(s) + "'"));
+        JSON_THROW(detail::out_of_range::create(404, fmt::format("unresolved reference token '{}'", s)));
     }
 
     return res;
@@ -108,7 +109,7 @@
                 }
                 JSON_CATCH(std::invalid_argument&)
                 {
-                    JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
+                    JSON_THROW(detail::parse_error::create(109, 0, fmt::format("array index '{}' is not a number", reference_token)));
                 }
                 break;
             }
@@ -164,8 +165,7 @@
                 if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
                 {
                     JSON_THROW(detail::parse_error::create(106, 0,
-                                                           "array index '" + Twine(reference_token) +
-                                                           "' must not begin with '0'"));
+                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
                 }
 
                 if (reference_token == "-")
@@ -183,14 +183,16 @@
                     }
                     JSON_CATCH(std::invalid_argument&)
                     {
-                        JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
+                        JSON_THROW(detail::parse_error::create(109, 0,
+                            fmt::format("array index '{}' is not a number", reference_token)));
                     }
                 }
                 break;
             }
 
             default:
-                JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
+                JSON_THROW(detail::out_of_range::create(404,
+                    fmt::format("unresolved reference token '{}'", reference_token)));
         }
     }
 
@@ -217,16 +219,14 @@
                 {
                     // "-" always fails the range check
                     JSON_THROW(detail::out_of_range::create(402,
-                                                            "array index '-' (" + Twine(ptr->m_value.array->size()) +
-                                                            ") is out of range"));
+                        fmt::format("array index '-' ({}) is out of range", ptr->m_value.array->size())));
                 }
 
                 // error condition (cf. RFC 6901, Sect. 4)
                 if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
                 {
                     JSON_THROW(detail::parse_error::create(106, 0,
-                                                           "array index '" + Twine(reference_token) +
-                                                           "' must not begin with '0'"));
+                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
                 }
 
                 // note: at performs range check
@@ -236,13 +236,15 @@
                 }
                 JSON_CATCH(std::invalid_argument&)
                 {
-                    JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
+                    JSON_THROW(detail::parse_error::create(109, 0,
+                        fmt::format("array index '{}' is not a number", reference_token)));
                 }
                 break;
             }
 
             default:
-                JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
+                JSON_THROW(detail::out_of_range::create(404,
+                    fmt::format("unresolved reference token '{}'", reference_token)));
         }
     }
 
@@ -269,16 +271,14 @@
                 {
                     // "-" cannot be used for const access
                     JSON_THROW(detail::out_of_range::create(402,
-                                                            "array index '-' (" + Twine(ptr->m_value.array->size()) +
-                                                            ") is out of range"));
+                        fmt::format("array index '-' ({}) is out of range", ptr->m_value.array->size())));
                 }
 
                 // error condition (cf. RFC 6901, Sect. 4)
                 if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
                 {
                     JSON_THROW(detail::parse_error::create(106, 0,
-                                                           "array index '" + Twine(reference_token) +
-                                                           "' must not begin with '0'"));
+                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
                 }
 
                 // use unchecked array access
@@ -289,13 +289,15 @@
                 }
                 JSON_CATCH(std::invalid_argument&)
                 {
-                    JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
+                    JSON_THROW(detail::parse_error::create(109, 0,
+                        fmt::format("array index '{}' is not a number", reference_token)));
                 }
                 break;
             }
 
             default:
-                JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
+                JSON_THROW(detail::out_of_range::create(404,
+                    fmt::format("unresolved reference token '{}'", reference_token)));
         }
     }
 
@@ -322,16 +324,14 @@
                 {
                     // "-" always fails the range check
                     JSON_THROW(detail::out_of_range::create(402,
-                                                            "array index '-' (" + Twine(ptr->m_value.array->size()) +
-                                                            ") is out of range"));
+                        fmt::format("array index '-' ({}) is out of range", ptr->m_value.array->size())));
                 }
 
                 // error condition (cf. RFC 6901, Sect. 4)
                 if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
                 {
                     JSON_THROW(detail::parse_error::create(106, 0,
-                                                           "array index '" + Twine(reference_token) +
-                                                           "' must not begin with '0'"));
+                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
                 }
 
                 // note: at performs range check
@@ -341,23 +341,23 @@
                 }
                 JSON_CATCH(std::invalid_argument&)
                 {
-                    JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
+                    JSON_THROW(detail::parse_error::create(109, 0,
+                        fmt::format("array index '{}' is not a number", reference_token)));
                 }
                 break;
             }
 
             default:
-                JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
+                JSON_THROW(detail::out_of_range::create(404,
+                    fmt::format("unresolved reference token '{}'", reference_token)));
         }
     }
 
     return *ptr;
 }
 
-std::vector<std::string> json_pointer::split(const Twine& reference_string)
+std::vector<std::string> json_pointer::split(std::string_view ref_str)
 {
-    SmallString<128> ref_str_buf;
-    StringRef ref_str = reference_string.toStringRef(ref_str_buf);
     std::vector<std::string> result;
 
     // special case: empty reference string -> no reference tokens
@@ -370,8 +370,7 @@
     if (JSON_UNLIKELY(ref_str[0] != '/'))
     {
         JSON_THROW(detail::parse_error::create(107, 1,
-                                               "JSON pointer must be empty or begin with '/' - was: '" +
-                                               Twine(ref_str) + "'"));
+            fmt::format("JSON pointer must be empty or begin with '/' - was: '{}'", ref_str)));
     }
 
     // extract the reference tokens:
@@ -392,11 +391,11 @@
     {
         // use the text between the beginning of the reference token
         // (start) and the last slash (slash).
-        auto reference_token = ref_str.slice(start, slash);
+        auto reference_token = slice(ref_str, start, slash);
 
         // check reference tokens are properly escaped
         for (std::size_t pos = reference_token.find_first_of('~');
-                pos != StringRef::npos;
+                pos != std::string_view::npos;
                 pos = reference_token.find_first_of('~', pos + 1))
         {
             assert(reference_token[pos] == '~');
@@ -411,7 +410,7 @@
         }
 
         // finally, store the reference token
-        std::string ref_tok = reference_token;
+        std::string ref_tok{reference_token};
         unescape(ref_tok);
         result.emplace_back(std::move(ref_tok));
     }
@@ -444,7 +443,7 @@
     replace_substring(s, "~0", "~");
 }
 
-void json_pointer::flatten(const Twine& reference_string,
+void json_pointer::flatten(std::string_view reference_string,
                     const json& value,
                     json& result)
 {
@@ -455,15 +454,14 @@
             if (value.m_value.array->empty())
             {
                 // flatten empty array as null
-                SmallString<64> buf;
-                result[reference_string.toStringRef(buf)] = nullptr;
+                result[reference_string] = nullptr;
             }
             else
             {
                 // iterate array and use index as reference string
                 for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
                 {
-                    flatten(reference_string + "/" + Twine(i),
+                    flatten(fmt::format("{}/{}", reference_string, i),
                             value.m_value.array->operator[](i), result);
                 }
             }
@@ -475,15 +473,14 @@
             if (value.m_value.object->empty())
             {
                 // flatten empty object as null
-                SmallString<64> buf;
-                result[reference_string.toStringRef(buf)] = nullptr;
+                result[reference_string] = nullptr;
             }
             else
             {
                 // iterate object and use keys as reference string
                 for (const auto& element : *value.m_value.object)
                 {
-                    flatten(reference_string + "/" + Twine(escape(element.first())), element.second, result);
+                    flatten(fmt::format("{}/{}", reference_string, escape(std::string{element.first()})), element.second, result);
                 }
             }
             break;
@@ -492,8 +489,7 @@
         default:
         {
             // add primitive value with its reference string
-            SmallString<64> buf;
-            result[reference_string.toStringRef(buf)] = value;
+            result[reference_string] = value;
             break;
         }
     }
diff --git a/wpiutil/src/main/native/cpp/json_serializer.cpp b/wpiutil/src/main/native/cpp/json_serializer.cpp
index 8ab92b1..1101f66 100644
--- a/wpiutil/src/main/native/cpp/json_serializer.cpp
+++ b/wpiutil/src/main/native/cpp/json_serializer.cpp
@@ -34,12 +34,11 @@
 #define WPI_JSON_IMPLEMENTATION
 #include "wpi/json.h"
 
-#include "wpi/Format.h"
+#include "fmt/format.h"
 #include "wpi/SmallString.h"
-#include "wpi/StringExtras.h"
 #include "wpi/raw_os_ostream.h"
 
-#include "json_serializer.h"
+#include "wpi/json_serializer.h"
 
 namespace wpi {
 
@@ -1332,7 +1331,7 @@
     }
 }
 
-void json::serializer::dump_escaped(StringRef s, const bool ensure_ascii)
+void json::serializer::dump_escaped(std::string_view s, const bool ensure_ascii)
 {
     uint32_t codepoint;
     uint8_t state = UTF8_ACCEPT;
@@ -1397,12 +1396,12 @@
                         {
                             if (codepoint <= 0xFFFF)
                             {
-                                o << '\\' << 'u' << format_hex_no_prefix(codepoint, 4);
+                                o << fmt::format("\\u{:04x}", codepoint);
                             }
                             else
                             {
-                                o << '\\' << 'u' << format_hex_no_prefix(0xD7C0 + (codepoint >> 10), 4);
-                                o << '\\' << 'u' << format_hex_no_prefix(0xDC00 + (codepoint & 0x3FF), 4);
+                                o << fmt::format("\\u{:04x}", 0xD7C0 + (codepoint >> 10));
+                                o << fmt::format("\\u{:04x}", 0xDC00 + (codepoint & 0x3FF));
                             }
                         }
                         else
@@ -1419,7 +1418,7 @@
 
             case UTF8_REJECT:  // decode found invalid UTF-8 byte
             {
-                JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + Twine(i) + ": 0x" + Twine::utohexstr(byte)));
+                JSON_THROW(type_error::create(316, fmt::format("invalid UTF-8 byte at index {}: {:#02x}", i, byte)));
             }
 
             default:  // decode found yet incomplete multi-byte code point
@@ -1437,7 +1436,7 @@
     if (JSON_UNLIKELY(state != UTF8_ACCEPT))
     {
         // we finish reading, but do not accept: string was incomplete
-        JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + Twine::utohexstr(static_cast<uint8_t>(s.back()))));
+        JSON_THROW(type_error::create(316, fmt::format("incomplete UTF-8 string; last byte: {:#02x}", s.back())));
     }
 }
 
diff --git a/wpiutil/src/main/native/cpp/leb128.cpp b/wpiutil/src/main/native/cpp/leb128.cpp
index 202ee9a..9657883 100644
--- a/wpiutil/src/main/native/cpp/leb128.cpp
+++ b/wpiutil/src/main/native/cpp/leb128.cpp
@@ -1,13 +1,13 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/leb128.h"
 
+#include "wpi/SpanExtras.h"
 #include "wpi/raw_istream.h"
+#include "wpi/raw_ostream.h"
+#include "wpi/span.h"
 
 namespace wpi {
 
@@ -24,11 +24,12 @@
   size_t count = 0;
 
   do {
-    unsigned char byte = val & 0x7f;
+    uint8_t byte = val & 0x7f;
     val >>= 7;
 
-    if (val != 0)
+    if (val != 0) {
       byte |= 0x80;  // mark this byte to show that more bytes will follow
+    }
 
     dest.push_back(byte);
     count++;
@@ -37,6 +38,19 @@
   return count;
 }
 
+void WriteUleb128(raw_ostream& os, uint64_t val) {
+  do {
+    uint8_t byte = val & 0x7f;
+    val >>= 7;
+
+    if (val != 0) {
+      byte |= 0x80;  // mark this byte to show that more bytes will follow
+    }
+
+    os << byte;
+  } while (val != 0);
+}
+
 uint64_t ReadUleb128(const char* addr, uint64_t* ret) {
   uint64_t result = 0;
   int shift = 0;
@@ -47,10 +61,12 @@
     addr++;
     count++;
 
-    result |= (byte & 0x7f) << shift;
+    result |= (byte & 0x7fULL) << shift;
     shift += 7;
 
-    if (!(byte & 0x80)) break;
+    if (!(byte & 0x80)) {
+      break;
+    }
   }
 
   *ret = result;
@@ -65,12 +81,16 @@
   while (1) {
     unsigned char byte;
     is.read(reinterpret_cast<char*>(&byte), 1);
-    if (is.has_error()) return false;
+    if (is.has_error()) {
+      return false;
+    }
 
-    result |= (byte & 0x7f) << shift;
+    result |= (byte & 0x7fULL) << shift;
     shift += 7;
 
-    if (!(byte & 0x80)) break;
+    if (!(byte & 0x80)) {
+      break;
+    }
   }
 
   *ret = result;
@@ -78,4 +98,22 @@
   return true;
 }
 
+std::optional<uint64_t> Uleb128Reader::ReadOne(span<const uint8_t>* in) {
+  while (!in->empty()) {
+    uint8_t byte = in->front();
+    *in = wpi::drop_front(*in);
+
+    m_result |= (byte & 0x7fULL) << m_shift;
+    m_shift += 7;
+
+    if (!(byte & 0x80)) {
+      uint64_t result = m_result;
+      m_result = 0;
+      m_shift = 0;
+      return result;
+    }
+  }
+  return std::nullopt;
+}
+
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/llvm/ConvertUTF.cpp b/wpiutil/src/main/native/cpp/llvm/ConvertUTF.cpp
index abe3744..3050e63 100644
--- a/wpiutil/src/main/native/cpp/llvm/ConvertUTF.cpp
+++ b/wpiutil/src/main/native/cpp/llvm/ConvertUTF.cpp
@@ -52,6 +52,11 @@
 #endif
 #include <assert.h>
 
+#ifdef _WIN32
+#include "wpi/WindowsError.h"
+#include "Windows/WindowsSupport.h"
+#endif
+
 /*
  * This code extensively uses fall-through switches.
  * Keep the compiler from warning about that.
@@ -734,6 +739,96 @@
 
    --------------------------------------------------------------------- */
 
+#ifdef _WIN32
+
+namespace sys {
+namespace windows {
+std::error_code CodePageToUTF16(unsigned codepage,
+                                std::string_view original,
+                                wpi::SmallVectorImpl<wchar_t> &utf16) {
+  if (!original.empty()) {
+    int len = ::MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, original.data(),
+                                    original.size(), utf16.begin(), 0);
+
+    if (len == 0) {
+      return mapWindowsError(::GetLastError());
+    }
+
+    utf16.reserve(len + 1);
+    utf16.set_size(len);
+
+    len = ::MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, original.data(),
+                                original.size(), utf16.begin(), utf16.size());
+
+    if (len == 0) {
+      return mapWindowsError(::GetLastError());
+    }
+  }
+
+  // Make utf16 null terminated.
+  utf16.push_back(0);
+  utf16.pop_back();
+
+  return std::error_code();
+}
+
+std::error_code UTF8ToUTF16(std::string_view utf8,
+                            wpi::SmallVectorImpl<wchar_t> &utf16) {
+  return CodePageToUTF16(CP_UTF8, utf8, utf16);
+}
+
+std::error_code CurCPToUTF16(std::string_view curcp,
+                            wpi::SmallVectorImpl<wchar_t> &utf16) {
+  return CodePageToUTF16(CP_ACP, curcp, utf16);
+}
+
+static
+std::error_code UTF16ToCodePage(unsigned codepage, const wchar_t *utf16,
+                                size_t utf16_len,
+                                wpi::SmallVectorImpl<char> &converted) {
+  if (utf16_len) {
+    // Get length.
+    int len = ::WideCharToMultiByte(codepage, 0, utf16, utf16_len, converted.begin(),
+                                    0, NULL, NULL);
+
+    if (len == 0) {
+      return mapWindowsError(::GetLastError());
+    }
+
+    converted.reserve(len);
+    converted.set_size(len);
+
+    // Now do the actual conversion.
+    len = ::WideCharToMultiByte(codepage, 0, utf16, utf16_len, converted.data(),
+                                converted.size(), NULL, NULL);
+
+    if (len == 0) {
+      return mapWindowsError(::GetLastError());
+    }
+  }
+
+  // Make the new string null terminated.
+  converted.push_back(0);
+  converted.pop_back();
+
+  return std::error_code();
+}
+
+std::error_code UTF16ToUTF8(const wchar_t *utf16, size_t utf16_len,
+                            wpi::SmallVectorImpl<char> &utf8) {
+  return UTF16ToCodePage(CP_UTF8, utf16, utf16_len, utf8);
+}
+
+std::error_code UTF16ToCurCP(const wchar_t *utf16, size_t utf16_len,
+                             wpi::SmallVectorImpl<char> &curcp) {
+  return UTF16ToCodePage(CP_ACP, utf16, utf16_len, curcp);
+}
+
+} // end namespace windows
+} // end namespace sys
+
+#endif  // _WIN32
+
 } // namespace llvm
 
 ConvertUTF_RESTORE_WARNINGS
diff --git a/wpiutil/src/main/native/cpp/llvm/ConvertUTFWrapper.cpp b/wpiutil/src/main/native/cpp/llvm/ConvertUTFWrapper.cpp
index 3402988..db190d0 100644
--- a/wpiutil/src/main/native/cpp/llvm/ConvertUTFWrapper.cpp
+++ b/wpiutil/src/main/native/cpp/llvm/ConvertUTFWrapper.cpp
@@ -8,6 +8,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "wpi/ConvertUTF.h"
+#include "wpi/SmallVector.h"
 #include <string>
 #include <vector>
 
@@ -28,13 +29,13 @@
   return true;
 }
 
-bool hasUTF16ByteOrderMark(ArrayRef<char> S) {
+bool hasUTF16ByteOrderMark(span<const char> S) {
   return (S.size() >= 2 &&
           ((S[0] == '\xff' && S[1] == '\xfe') ||
            (S[0] == '\xfe' && S[1] == '\xff')));
 }
 
-bool convertUTF16ToUTF8String(ArrayRef<UTF16> SrcUTF16,
+bool convertUTF16ToUTF8String(span<const UTF16> SrcUTF16,
                               SmallVectorImpl<char> &DstUTF8) {
   assert(DstUTF8.empty());
 
@@ -80,7 +81,7 @@
   return true;
 }
 
-bool convertUTF8ToUTF16String(StringRef SrcUTF8,
+bool convertUTF8ToUTF16String(std::string_view SrcUTF8,
                               SmallVectorImpl<UTF16> &DstUTF16) {
   assert(DstUTF16.empty());
 
@@ -91,8 +92,8 @@
     return true;
   }
 
-  const UTF8 *Src = reinterpret_cast<const UTF8 *>(SrcUTF8.begin());
-  const UTF8 *SrcEnd = reinterpret_cast<const UTF8 *>(SrcUTF8.end());
+  const UTF8 *Src = reinterpret_cast<const UTF8 *>(SrcUTF8.data());
+  const UTF8 *SrcEnd = reinterpret_cast<const UTF8 *>(SrcUTF8.data() + SrcUTF8.size());
 
   // Allocate the same number of UTF-16 code units as UTF-8 code units. Encoding
   // as UTF-16 should always require the same amount or less code units than the
diff --git a/wpiutil/src/main/native/cpp/llvm/Error.cpp b/wpiutil/src/main/native/cpp/llvm/Error.cpp
deleted file mode 100644
index 7bf9c5f..0000000
--- a/wpiutil/src/main/native/cpp/llvm/Error.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-//===----- lib/Support/Error.cpp - Error and associated utilities ---------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/Error.h"
-#include "wpi/Twine.h"
-#include "wpi/ErrorHandling.h"
-#include "wpi/ManagedStatic.h"
-#include <system_error>
-
-using namespace wpi;
-
-namespace {
-
-  enum class ErrorErrorCode : int {
-    MultipleErrors = 1,
-    FileError,
-    InconvertibleError
-  };
-
-  // FIXME: This class is only here to support the transition to wpi::Error. It
-  // will be removed once this transition is complete. Clients should prefer to
-  // deal with the Error value directly, rather than converting to error_code.
-  class ErrorErrorCategory : public std::error_category {
-  public:
-    const char *name() const noexcept override { return "Error"; }
-
-    std::string message(int condition) const override {
-      switch (static_cast<ErrorErrorCode>(condition)) {
-      case ErrorErrorCode::MultipleErrors:
-        return "Multiple errors";
-      case ErrorErrorCode::InconvertibleError:
-        return "Inconvertible error value. An error has occurred that could "
-               "not be converted to a known std::error_code. Please file a "
-               "bug.";
-      case ErrorErrorCode::FileError:
-          return "A file error occurred.";
-      }
-      wpi_unreachable("Unhandled error code");
-    }
-  };
-
-}
-
-static ManagedStatic<ErrorErrorCategory> ErrorErrorCat;
-
-namespace wpi {
-
-void ErrorInfoBase::anchor() {}
-char ErrorInfoBase::ID = 0;
-char ErrorList::ID = 0;
-void ECError::anchor() {}
-char ECError::ID = 0;
-char StringError::ID = 0;
-char FileError::ID = 0;
-
-void logAllUnhandledErrors(Error E, raw_ostream &OS, Twine ErrorBanner) {
-  if (!E)
-    return;
-  OS << ErrorBanner;
-  handleAllErrors(std::move(E), [&](const ErrorInfoBase &EI) {
-    EI.log(OS);
-    OS << "\n";
-  });
-}
-
-
-std::error_code ErrorList::convertToErrorCode() const {
-  return std::error_code(static_cast<int>(ErrorErrorCode::MultipleErrors),
-                         *ErrorErrorCat);
-}
-
-std::error_code inconvertibleErrorCode() {
-  return std::error_code(static_cast<int>(ErrorErrorCode::InconvertibleError),
-                         *ErrorErrorCat);
-}
-
-std::error_code FileError::convertToErrorCode() const {
-  return std::error_code(static_cast<int>(ErrorErrorCode::FileError),
-                         *ErrorErrorCat);
-}
-
-Error errorCodeToError(std::error_code EC) {
-  if (!EC)
-    return Error::success();
-  return Error(std::make_unique<ECError>(ECError(EC)));
-}
-
-std::error_code errorToErrorCode(Error Err) {
-  std::error_code EC;
-  handleAllErrors(std::move(Err), [&](const ErrorInfoBase &EI) {
-    EC = EI.convertToErrorCode();
-  });
-  if (EC == inconvertibleErrorCode())
-    report_fatal_error(EC.message());
-  return EC;
-}
-
-StringError::StringError(std::error_code EC, const Twine &S)
-    : Msg(S.str()), EC(EC) {}
-
-StringError::StringError(const Twine &S, std::error_code EC)
-    : Msg(S.str()), EC(EC), PrintMsgOnly(true) {}
-
-void StringError::log(raw_ostream &OS) const {
-  if (PrintMsgOnly) {
-    OS << Msg;
-  } else {
-    OS << EC.message();
-    if (!Msg.empty())
-      OS << (" " + Msg);
-  }
-}
-
-std::error_code StringError::convertToErrorCode() const {
-  return EC;
-}
-
-Error createStringError(std::error_code EC, char const *Msg) {
-  return make_error<StringError>(Msg, EC);
-}
-
-void report_fatal_error(Error Err, bool GenCrashDiag) {
-  assert(Err && "report_fatal_error called with success value");
-  std::string ErrMsg;
-  {
-    raw_string_ostream ErrStream(ErrMsg);
-    logAllUnhandledErrors(std::move(Err), ErrStream);
-  }
-  report_fatal_error(ErrMsg);
-}
-
-} // end namespace wpi
diff --git a/wpiutil/src/main/native/cpp/llvm/ErrorHandling.cpp b/wpiutil/src/main/native/cpp/llvm/ErrorHandling.cpp
index ef79456..22ae636 100644
--- a/wpiutil/src/main/native/cpp/llvm/ErrorHandling.cpp
+++ b/wpiutil/src/main/native/cpp/llvm/ErrorHandling.cpp
@@ -14,11 +14,10 @@
 
 #include "wpi/ErrorHandling.h"
 #include "wpi/SmallVector.h"
-#include "wpi/Twine.h"
-#include "wpi/Error.h"
 #include "wpi/WindowsError.h"
-#include "wpi/raw_ostream.h"
+#include "fmt/format.h"
 #include <cassert>
+#include <cstdio>
 #include <cstdlib>
 #include <mutex>
 #include <new>
@@ -68,18 +67,14 @@
 }
 
 void wpi::report_fatal_error(const char *Reason, bool GenCrashDiag) {
-  report_fatal_error(Twine(Reason), GenCrashDiag);
+  report_fatal_error(std::string_view(Reason), GenCrashDiag);
 }
 
 void wpi::report_fatal_error(const std::string &Reason, bool GenCrashDiag) {
-  report_fatal_error(Twine(Reason), GenCrashDiag);
+  report_fatal_error(std::string_view(Reason), GenCrashDiag);
 }
 
-void wpi::report_fatal_error(StringRef Reason, bool GenCrashDiag) {
-  report_fatal_error(Twine(Reason), GenCrashDiag);
-}
-
-void wpi::report_fatal_error(const Twine &Reason, bool GenCrashDiag) {
+void wpi::report_fatal_error(std::string_view Reason, bool GenCrashDiag) {
   wpi::fatal_error_handler_t handler = nullptr;
   void* handlerData = nullptr;
   {
@@ -91,21 +86,9 @@
   }
 
   if (handler) {
-    handler(handlerData, Reason.str(), GenCrashDiag);
+    handler(handlerData, std::string{Reason}, GenCrashDiag);
   } else {
-    // Blast the result out to stderr.  We don't try hard to make sure this
-    // succeeds (e.g. handling EINTR) and we can't use errs() here because
-    // raw ostreams can call report_fatal_error.
-    SmallVector<char, 64> Buffer;
-    raw_svector_ostream OS(Buffer);
-    OS << "LLVM ERROR: " << Reason << "\n";
-    StringRef MessageStr = OS.str();
-#ifdef _WIN32
-    int written = ::_write(2, MessageStr.data(), MessageStr.size());
-#else
-    ssize_t written = ::write(2, MessageStr.data(), MessageStr.size());
-#endif
-    (void)written; // If something went wrong, we deliberately just give up.
+    fmt::print(stderr, "LLVM ERROR: {}\n", Reason);
   }
 
   exit(1);
@@ -185,11 +168,11 @@
   // wpi_unreachable is intended to be used to indicate "impossible"
   // situations, and not legitimate runtime errors.
   if (msg)
-    errs() << msg << "\n";
-  errs() << "UNREACHABLE executed";
+    fmt::print(stderr, "{}\n", msg);
+  std::fputs("UNREACHABLE executed", stderr);
   if (file)
-    errs() << " at " << file << ":" << line;
-  errs() << "!\n";
+    fmt::print(stderr, " at {}:{}", file, line);
+  fmt::print(stderr, "{}", "!\n");
   abort();
 #ifdef LLVM_BUILTIN_UNREACHABLE
   // Windows systems and possibly others don't declare abort() to be noreturn,
diff --git a/wpiutil/src/main/native/cpp/llvm/NativeFormatting.cpp b/wpiutil/src/main/native/cpp/llvm/NativeFormatting.cpp
deleted file mode 100644
index 985e269..0000000
--- a/wpiutil/src/main/native/cpp/llvm/NativeFormatting.cpp
+++ /dev/null
@@ -1,264 +0,0 @@
-//===- NativeFormatting.cpp - Low level formatting helpers -------*- C++-*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/NativeFormatting.h"
-
-#include "wpi/ArrayRef.h"
-#include "wpi/SmallString.h"
-#include "wpi/StringExtras.h"
-#include "wpi/Format.h"
-
-#include <float.h>
-
-using namespace wpi;
-
-template<typename T, std::size_t N>
-static int format_to_buffer(T Value, char (&Buffer)[N]) {
-  char *EndPtr = std::end(Buffer);
-  char *CurPtr = EndPtr;
-
-  do {
-    *--CurPtr = '0' + char(Value % 10);
-    Value /= 10;
-  } while (Value);
-  return EndPtr - CurPtr;
-}
-
-static void writeWithCommas(raw_ostream &S, ArrayRef<char> Buffer) {
-  assert(!Buffer.empty());
-
-  ArrayRef<char> ThisGroup;
-  int InitialDigits = ((Buffer.size() - 1) % 3) + 1;
-  ThisGroup = Buffer.take_front(InitialDigits);
-  S.write(ThisGroup.data(), ThisGroup.size());
-
-  Buffer = Buffer.drop_front(InitialDigits);
-  assert(Buffer.size() % 3 == 0);
-  while (!Buffer.empty()) {
-    S << ',';
-    ThisGroup = Buffer.take_front(3);
-    S.write(ThisGroup.data(), 3);
-    Buffer = Buffer.drop_front(3);
-  }
-}
-
-template <typename T>
-static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits,
-                                IntegerStyle Style, bool IsNegative) {
-  static_assert(std::is_unsigned<T>::value, "Value is not unsigned!");
-
-  char NumberBuffer[128];
-  std::memset(NumberBuffer, '0', sizeof(NumberBuffer));
-
-  size_t Len = 0;
-  Len = format_to_buffer(N, NumberBuffer);
-
-  if (IsNegative)
-    S << '-';
-
-  if (Len < MinDigits && Style != IntegerStyle::Number) {
-    for (size_t I = Len; I < MinDigits; ++I)
-      S << '0';
-  }
-
-  if (Style == IntegerStyle::Number) {
-    writeWithCommas(S, ArrayRef<char>(std::end(NumberBuffer) - Len, Len));
-  } else {
-    S.write(std::end(NumberBuffer) - Len, Len);
-  }
-}
-
-template <typename T>
-static void write_unsigned(raw_ostream &S, T N, size_t MinDigits,
-                           IntegerStyle Style, bool IsNegative = false) {
-  // Output using 32-bit div/mod if possible.
-  if (N == static_cast<uint32_t>(N))
-    write_unsigned_impl(S, static_cast<uint32_t>(N), MinDigits, Style,
-                        IsNegative);
-  else
-    write_unsigned_impl(S, N, MinDigits, Style, IsNegative);
-}
-
-template <typename T>
-static void write_signed(raw_ostream &S, T N, size_t MinDigits,
-                         IntegerStyle Style) {
-  static_assert(std::is_signed<T>::value, "Value is not signed!");
-
-  using UnsignedT = typename std::make_unsigned<T>::type;
-
-  if (N >= 0) {
-    write_unsigned(S, static_cast<UnsignedT>(N), MinDigits, Style);
-    return;
-  }
-
-  UnsignedT UN = -(UnsignedT)N;
-  write_unsigned(S, UN, MinDigits, Style, true);
-}
-
-void wpi::write_integer(raw_ostream &S, unsigned int N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_unsigned(S, N, MinDigits, Style);
-}
-
-void wpi::write_integer(raw_ostream &S, int N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_signed(S, N, MinDigits, Style);
-}
-
-void wpi::write_integer(raw_ostream &S, unsigned long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_unsigned(S, N, MinDigits, Style);
-}
-
-void wpi::write_integer(raw_ostream &S, long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_signed(S, N, MinDigits, Style);
-}
-
-void wpi::write_integer(raw_ostream &S, unsigned long long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_unsigned(S, N, MinDigits, Style);
-}
-
-void wpi::write_integer(raw_ostream &S, long long N, size_t MinDigits,
-                         IntegerStyle Style) {
-  write_signed(S, N, MinDigits, Style);
-}
-
-void wpi::write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style,
-                     std::optional<size_t> Width) {
-  const size_t kMaxWidth = 128u;
-
-  size_t W = std::min(kMaxWidth, Width.value_or(0u));
-
-  unsigned Nibbles = (64 - countLeadingZeros(N) + 3) / 4;
-  bool Prefix = (Style == HexPrintStyle::PrefixLower ||
-                 Style == HexPrintStyle::PrefixUpper);
-  bool Upper =
-      (Style == HexPrintStyle::Upper || Style == HexPrintStyle::PrefixUpper);
-  unsigned PrefixChars = Prefix ? 2 : 0;
-  unsigned NumChars =
-      std::max(static_cast<unsigned>(W), std::max(1u, Nibbles) + PrefixChars);
-
-  char NumberBuffer[kMaxWidth];
-  ::memset(NumberBuffer, '0', wpi::array_lengthof(NumberBuffer));
-  if (Prefix)
-    NumberBuffer[1] = 'x';
-  char *EndPtr = NumberBuffer + NumChars;
-  char *CurPtr = EndPtr;
-  while (N) {
-    unsigned char x = static_cast<unsigned char>(N) % 16;
-    *--CurPtr = hexdigit(x, !Upper);
-    N /= 16;
-  }
-
-  S.write(NumberBuffer, NumChars);
-}
-
-void wpi::write_double(raw_ostream &S, double N, FloatStyle Style,
-                        std::optional<size_t> Precision) {
-  size_t Prec = Precision.value_or(getDefaultPrecision(Style));
-
-  if (std::isnan(N)) {
-    S << "nan";
-    return;
-  } else if (std::isinf(N)) {
-    S << "INF";
-    return;
-  }
-
-  char Letter;
-  if (Style == FloatStyle::Exponent)
-    Letter = 'e';
-  else if (Style == FloatStyle::ExponentUpper)
-    Letter = 'E';
-  else
-    Letter = 'f';
-
-  SmallString<8> Spec;
-  wpi::raw_svector_ostream Out(Spec);
-  Out << "%." << Prec << Letter;
-
-  if (Style == FloatStyle::Exponent || Style == FloatStyle::ExponentUpper) {
-#ifdef _WIN32
-// On MSVCRT and compatible, output of %e is incompatible to Posix
-// by default. Number of exponent digits should be at least 2. "%+03d"
-// FIXME: Implement our formatter to here or Support/Format.h!
-#if defined(__MINGW32__)
-    // FIXME: It should be generic to C++11.
-    if (N == 0.0 && std::signbit(N)) {
-      char NegativeZero[] = "-0.000000e+00";
-      if (Style == FloatStyle::ExponentUpper)
-        NegativeZero[strlen(NegativeZero) - 4] = 'E';
-      S << NegativeZero;
-      return;
-    }
-#else
-    int fpcl = _fpclass(N);
-
-    // negative zero
-    if (fpcl == _FPCLASS_NZ) {
-      char NegativeZero[] = "-0.000000e+00";
-      if (Style == FloatStyle::ExponentUpper)
-        NegativeZero[strlen(NegativeZero) - 4] = 'E';
-      S << NegativeZero;
-      return;
-    }
-#endif
-
-    char buf[32];
-    unsigned len;
-    len = format(Spec.c_str(), N).snprint(buf, sizeof(buf));
-    if (len <= sizeof(buf) - 2) {
-      if (len >= 5 && (buf[len - 5] == 'e' || buf[len - 5] == 'E') &&
-          buf[len - 3] == '0') {
-        int cs = buf[len - 4];
-        if (cs == '+' || cs == '-') {
-          int c1 = buf[len - 2];
-          int c0 = buf[len - 1];
-          if (isdigit(static_cast<unsigned char>(c1)) &&
-              isdigit(static_cast<unsigned char>(c0))) {
-            // Trim leading '0': "...e+012" -> "...e+12\0"
-            buf[len - 3] = c1;
-            buf[len - 2] = c0;
-            buf[--len] = 0;
-          }
-        }
-      }
-      S << buf;
-      return;
-    }
-#endif
-  }
-
-  if (Style == FloatStyle::Percent)
-    N *= 100.0;
-
-  char Buf[32];
-  format(Spec.c_str(), N).snprint(Buf, sizeof(Buf));
-  S << Buf;
-  if (Style == FloatStyle::Percent)
-    S << '%';
-}
-
-bool wpi::isPrefixedHexStyle(HexPrintStyle S) {
-  return (S == HexPrintStyle::PrefixLower || S == HexPrintStyle::PrefixUpper);
-}
-
-size_t wpi::getDefaultPrecision(FloatStyle Style) {
-  switch (Style) {
-  case FloatStyle::Exponent:
-  case FloatStyle::ExponentUpper:
-    return 6; // Number of decimal places.
-  case FloatStyle::Fixed:
-  case FloatStyle::Percent:
-    return 2; // Number of decimal places.
-  }
-  LLVM_BUILTIN_UNREACHABLE;
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/Path.cpp b/wpiutil/src/main/native/cpp/llvm/Path.cpp
deleted file mode 100644
index 49f92d3..0000000
--- a/wpiutil/src/main/native/cpp/llvm/Path.cpp
+++ /dev/null
@@ -1,833 +0,0 @@
-//===-- Path.cpp - Implement OS Path Concept ------------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-//  This file implements the operating system Path API.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/Path.h"
-#include "wpi/ArrayRef.h"
-#include "wpi/Endian.h"
-#include "wpi/Errc.h"
-#include "wpi/ErrorHandling.h"
-#include "wpi/FileSystem.h"
-#include "wpi/SmallString.h"
-#include <cctype>
-#include <cstring>
-
-#if !defined(_MSC_VER) && !defined(__MINGW32__)
-#include <unistd.h>
-#else
-#include <io.h>
-#endif
-
-using namespace wpi;
-using namespace wpi::support::endian;
-
-namespace {
-  using wpi::StringRef;
-  using wpi::sys::path::is_separator;
-  using wpi::sys::path::Style;
-
-  inline Style real_style(Style style) {
-#ifdef _WIN32
-    return (style == Style::posix) ? Style::posix : Style::windows;
-#else
-    return (style == Style::windows) ? Style::windows : Style::posix;
-#endif
-  }
-
-  inline const char *separators(Style style) {
-    if (real_style(style) == Style::windows)
-      return "\\/";
-    return "/";
-  }
-
-  inline char preferred_separator(Style style) {
-    if (real_style(style) == Style::windows)
-      return '\\';
-    return '/';
-  }
-
-  StringRef find_first_component(StringRef path, Style style) {
-    // Look for this first component in the following order.
-    // * empty (in this case we return an empty string)
-    // * either C: or {//,\\}net.
-    // * {/,\}
-    // * {file,directory}name
-
-    if (path.empty())
-      return path;
-
-    if (real_style(style) == Style::windows) {
-      // C:
-      if (path.size() >= 2 &&
-          std::isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':')
-        return path.substr(0, 2);
-    }
-
-    // //net
-    if ((path.size() > 2) && is_separator(path[0], style) &&
-        path[0] == path[1] && !is_separator(path[2], style)) {
-      // Find the next directory separator.
-      size_t end = path.find_first_of(separators(style), 2);
-      return path.substr(0, end);
-    }
-
-    // {/,\}
-    if (is_separator(path[0], style))
-      return path.substr(0, 1);
-
-    // * {file,directory}name
-    size_t end = path.find_first_of(separators(style));
-    return path.substr(0, end);
-  }
-
-  // Returns the first character of the filename in str. For paths ending in
-  // '/', it returns the position of the '/'.
-  size_t filename_pos(StringRef str, Style style) {
-    if (str.size() > 0 && is_separator(str[str.size() - 1], style))
-      return str.size() - 1;
-
-    size_t pos = str.find_last_of(separators(style), str.size() - 1);
-
-    if (real_style(style) == Style::windows) {
-      if (pos == StringRef::npos)
-        pos = str.find_last_of(':', str.size() - 2);
-    }
-
-    if (pos == StringRef::npos || (pos == 1 && is_separator(str[0], style)))
-      return 0;
-
-    return pos + 1;
-  }
-
-  // Returns the position of the root directory in str. If there is no root
-  // directory in str, it returns StringRef::npos.
-  size_t root_dir_start(StringRef str, Style style) {
-    // case "c:/"
-    if (real_style(style) == Style::windows) {
-      if (str.size() > 2 && str[1] == ':' && is_separator(str[2], style))
-        return 2;
-    }
-
-    // case "//net"
-    if (str.size() > 3 && is_separator(str[0], style) && str[0] == str[1] &&
-        !is_separator(str[2], style)) {
-      return str.find_first_of(separators(style), 2);
-    }
-
-    // case "/"
-    if (str.size() > 0 && is_separator(str[0], style))
-      return 0;
-
-    return StringRef::npos;
-  }
-
-  // Returns the position past the end of the "parent path" of path. The parent
-  // path will not end in '/', unless the parent is the root directory. If the
-  // path has no parent, 0 is returned.
-  size_t parent_path_end(StringRef path, Style style) {
-    size_t end_pos = filename_pos(path, style);
-
-    bool filename_was_sep =
-        path.size() > 0 && is_separator(path[end_pos], style);
-
-    // Skip separators until we reach root dir (or the start of the string).
-    size_t root_dir_pos = root_dir_start(path, style);
-    while (end_pos > 0 &&
-           (root_dir_pos == StringRef::npos || end_pos > root_dir_pos) &&
-           is_separator(path[end_pos - 1], style))
-      --end_pos;
-
-    if (end_pos == root_dir_pos && !filename_was_sep) {
-      // We've reached the root dir and the input path was *not* ending in a
-      // sequence of slashes. Include the root dir in the parent path.
-      return root_dir_pos + 1;
-    }
-
-    // Otherwise, just include before the last slash.
-    return end_pos;
-  }
-} // end unnamed namespace
-
-namespace wpi {
-namespace sys  {
-namespace path {
-
-const_iterator begin(StringRef path, Style style) {
-  const_iterator i;
-  i.Path      = path;
-  i.Component = find_first_component(path, style);
-  i.Position  = 0;
-  i.S = style;
-  return i;
-}
-
-const_iterator end(StringRef path) {
-  const_iterator i;
-  i.Path      = path;
-  i.Position  = path.size();
-  return i;
-}
-
-const_iterator &const_iterator::operator++() {
-  assert(Position < Path.size() && "Tried to increment past end!");
-
-  // Increment Position to past the current component
-  Position += Component.size();
-
-  // Check for end.
-  if (Position == Path.size()) {
-    Component = StringRef();
-    return *this;
-  }
-
-  // Both POSIX and Windows treat paths that begin with exactly two separators
-  // specially.
-  bool was_net = Component.size() > 2 && is_separator(Component[0], S) &&
-                 Component[1] == Component[0] && !is_separator(Component[2], S);
-
-  // Handle separators.
-  if (is_separator(Path[Position], S)) {
-    // Root dir.
-    if (was_net ||
-        // c:/
-        (real_style(S) == Style::windows && Component.endswith(":"))) {
-      Component = Path.substr(Position, 1);
-      return *this;
-    }
-
-    // Skip extra separators.
-    while (Position != Path.size() && is_separator(Path[Position], S)) {
-      ++Position;
-    }
-
-    // Treat trailing '/' as a '.', unless it is the root dir.
-    if (Position == Path.size() && Component != "/") {
-      --Position;
-      Component = ".";
-      return *this;
-    }
-  }
-
-  // Find next component.
-  size_t end_pos = Path.find_first_of(separators(S), Position);
-  Component = Path.slice(Position, end_pos);
-
-  return *this;
-}
-
-bool const_iterator::operator==(const const_iterator &RHS) const {
-  return Path.begin() == RHS.Path.begin() && Position == RHS.Position;
-}
-
-ptrdiff_t const_iterator::operator-(const const_iterator &RHS) const {
-  return Position - RHS.Position;
-}
-
-reverse_iterator rbegin(StringRef Path, Style style) {
-  reverse_iterator I;
-  I.Path = Path;
-  I.Position = Path.size();
-  I.S = style;
-  return ++I;
-}
-
-reverse_iterator rend(StringRef Path) {
-  reverse_iterator I;
-  I.Path = Path;
-  I.Component = Path.substr(0, 0);
-  I.Position = 0;
-  return I;
-}
-
-reverse_iterator &reverse_iterator::operator++() {
-  size_t root_dir_pos = root_dir_start(Path, S);
-
-  // Skip separators unless it's the root directory.
-  size_t end_pos = Position;
-  while (end_pos > 0 && (end_pos - 1) != root_dir_pos &&
-         is_separator(Path[end_pos - 1], S))
-    --end_pos;
-
-  // Treat trailing '/' as a '.', unless it is the root dir.
-  if (Position == Path.size() && !Path.empty() &&
-      is_separator(Path.back(), S) &&
-      (root_dir_pos == StringRef::npos || end_pos - 1 > root_dir_pos)) {
-    --Position;
-    Component = ".";
-    return *this;
-  }
-
-  // Find next separator.
-  size_t start_pos = filename_pos(Path.substr(0, end_pos), S);
-  Component = Path.slice(start_pos, end_pos);
-  Position = start_pos;
-  return *this;
-}
-
-bool reverse_iterator::operator==(const reverse_iterator &RHS) const {
-  return Path.begin() == RHS.Path.begin() && Component == RHS.Component &&
-         Position == RHS.Position;
-}
-
-ptrdiff_t reverse_iterator::operator-(const reverse_iterator &RHS) const {
-  return Position - RHS.Position;
-}
-
-StringRef root_path(StringRef path, Style style) {
-  const_iterator b = begin(path, style), pos = b, e = end(path);
-  if (b != e) {
-    bool has_net =
-        b->size() > 2 && is_separator((*b)[0], style) && (*b)[1] == (*b)[0];
-    bool has_drive = (real_style(style) == Style::windows) && b->endswith(":");
-
-    if (has_net || has_drive) {
-      if ((++pos != e) && is_separator((*pos)[0], style)) {
-        // {C:/,//net/}, so get the first two components.
-        return path.substr(0, b->size() + pos->size());
-      } else {
-        // just {C:,//net}, return the first component.
-        return *b;
-      }
-    }
-
-    // POSIX style root directory.
-    if (is_separator((*b)[0], style)) {
-      return *b;
-    }
-  }
-
-  return StringRef();
-}
-
-StringRef root_name(StringRef path, Style style) {
-  const_iterator b = begin(path, style), e = end(path);
-  if (b != e) {
-    bool has_net =
-        b->size() > 2 && is_separator((*b)[0], style) && (*b)[1] == (*b)[0];
-    bool has_drive = (real_style(style) == Style::windows) && b->endswith(":");
-
-    if (has_net || has_drive) {
-      // just {C:,//net}, return the first component.
-      return *b;
-    }
-  }
-
-  // No path or no name.
-  return StringRef();
-}
-
-StringRef root_directory(StringRef path, Style style) {
-  const_iterator b = begin(path, style), pos = b, e = end(path);
-  if (b != e) {
-    bool has_net =
-        b->size() > 2 && is_separator((*b)[0], style) && (*b)[1] == (*b)[0];
-    bool has_drive = (real_style(style) == Style::windows) && b->endswith(":");
-
-    if ((has_net || has_drive) &&
-        // {C:,//net}, skip to the next component.
-        (++pos != e) && is_separator((*pos)[0], style)) {
-      return *pos;
-    }
-
-    // POSIX style root directory.
-    if (!has_net && is_separator((*b)[0], style)) {
-      return *b;
-    }
-  }
-
-  // No path or no root.
-  return StringRef();
-}
-
-StringRef relative_path(StringRef path, Style style) {
-  StringRef root = root_path(path, style);
-  return path.substr(root.size());
-}
-
-void append(SmallVectorImpl<char> &path, Style style, const Twine &a,
-            const Twine &b, const Twine &c, const Twine &d) {
-  SmallString<32> a_storage;
-  SmallString<32> b_storage;
-  SmallString<32> c_storage;
-  SmallString<32> d_storage;
-
-  SmallVector<StringRef, 4> components;
-  if (!a.isTriviallyEmpty()) components.push_back(a.toStringRef(a_storage));
-  if (!b.isTriviallyEmpty()) components.push_back(b.toStringRef(b_storage));
-  if (!c.isTriviallyEmpty()) components.push_back(c.toStringRef(c_storage));
-  if (!d.isTriviallyEmpty()) components.push_back(d.toStringRef(d_storage));
-
-  for (auto &component : components) {
-    bool path_has_sep =
-        !path.empty() && is_separator(path[path.size() - 1], style);
-    if (path_has_sep) {
-      // Strip separators from beginning of component.
-      size_t loc = component.find_first_not_of(separators(style));
-      StringRef c = component.substr(loc);
-
-      // Append it.
-      path.append(c.begin(), c.end());
-      continue;
-    }
-
-    bool component_has_sep =
-        !component.empty() && is_separator(component[0], style);
-    if (!component_has_sep &&
-        !(path.empty() || has_root_name(component, style))) {
-      // Add a separator.
-      path.push_back(preferred_separator(style));
-    }
-
-    path.append(component.begin(), component.end());
-  }
-}
-
-void append(SmallVectorImpl<char> &path, const Twine &a, const Twine &b,
-            const Twine &c, const Twine &d) {
-  append(path, Style::native, a, b, c, d);
-}
-
-void append(SmallVectorImpl<char> &path, const_iterator begin,
-            const_iterator end, Style style) {
-  for (; begin != end; ++begin)
-    path::append(path, style, *begin);
-}
-
-StringRef parent_path(StringRef path, Style style) {
-  size_t end_pos = parent_path_end(path, style);
-  if (end_pos == StringRef::npos)
-    return StringRef();
-  else
-    return path.substr(0, end_pos);
-}
-
-void remove_filename(SmallVectorImpl<char> &path, Style style) {
-  size_t end_pos = parent_path_end(StringRef(path.begin(), path.size()), style);
-  if (end_pos != StringRef::npos)
-    path.set_size(end_pos);
-}
-
-void replace_extension(SmallVectorImpl<char> &path, const Twine &extension,
-                       Style style) {
-  StringRef p(path.begin(), path.size());
-  SmallString<32> ext_storage;
-  StringRef ext = extension.toStringRef(ext_storage);
-
-  // Erase existing extension.
-  size_t pos = p.find_last_of('.');
-  if (pos != StringRef::npos && pos >= filename_pos(p, style))
-    path.set_size(pos);
-
-  // Append '.' if needed.
-  if (ext.size() > 0 && ext[0] != '.')
-    path.push_back('.');
-
-  // Append extension.
-  path.append(ext.begin(), ext.end());
-}
-
-void replace_path_prefix(SmallVectorImpl<char> &Path,
-                         const StringRef &OldPrefix, const StringRef &NewPrefix,
-                         Style style) {
-  if (OldPrefix.empty() && NewPrefix.empty())
-    return;
-
-  StringRef OrigPath(Path.begin(), Path.size());
-  if (!OrigPath.startswith(OldPrefix))
-    return;
-
-  // If prefixes have the same size we can simply copy the new one over.
-  if (OldPrefix.size() == NewPrefix.size()) {
-    wpi::copy(NewPrefix, Path.begin());
-    return;
-  }
-
-  StringRef RelPath = OrigPath.substr(OldPrefix.size());
-  SmallString<256> NewPath;
-  path::append(NewPath, style, NewPrefix);
-  path::append(NewPath, style, RelPath);
-  Path.swap(NewPath);
-}
-
-void native(const Twine &path, SmallVectorImpl<char> &result, Style style) {
-  assert((!path.isSingleStringRef() ||
-          path.getSingleStringRef().data() != result.data()) &&
-         "path and result are not allowed to overlap!");
-  // Clear result.
-  result.clear();
-  path.toVector(result);
-  native(result, style);
-}
-
-void native(SmallVectorImpl<char> &Path, Style style) {
-  if (Path.empty())
-    return;
-  if (real_style(style) == Style::windows) {
-    std::replace(Path.begin(), Path.end(), '/', '\\');
-    if (Path[0] == '~' && (Path.size() == 1 || is_separator(Path[1], style))) {
-      SmallString<128> PathHome;
-      home_directory(PathHome);
-      PathHome.append(Path.begin() + 1, Path.end());
-      Path = PathHome;
-    }
-  } else {
-    for (auto PI = Path.begin(), PE = Path.end(); PI < PE; ++PI) {
-      if (*PI == '\\') {
-        auto PN = PI + 1;
-        if (PN < PE && *PN == '\\')
-          ++PI; // increment once, the for loop will move over the escaped slash
-        else
-          *PI = '/';
-      }
-    }
-  }
-}
-
-std::string convert_to_slash(StringRef path, Style style) {
-  if (real_style(style) != Style::windows)
-    return path;
-
-  std::string s = path.str();
-  std::replace(s.begin(), s.end(), '\\', '/');
-  return s;
-}
-
-StringRef filename(StringRef path, Style style) { return *rbegin(path, style); }
-
-StringRef stem(StringRef path, Style style) {
-  StringRef fname = filename(path, style);
-  size_t pos = fname.find_last_of('.');
-  if (pos == StringRef::npos)
-    return fname;
-  else
-    if ((fname.size() == 1 && fname == ".") ||
-        (fname.size() == 2 && fname == ".."))
-      return fname;
-    else
-      return fname.substr(0, pos);
-}
-
-StringRef extension(StringRef path, Style style) {
-  StringRef fname = filename(path, style);
-  size_t pos = fname.find_last_of('.');
-  if (pos == StringRef::npos)
-    return StringRef();
-  else
-    if ((fname.size() == 1 && fname == ".") ||
-        (fname.size() == 2 && fname == ".."))
-      return StringRef();
-    else
-      return fname.substr(pos);
-}
-
-bool is_separator(char value, Style style) {
-  if (value == '/')
-    return true;
-  if (real_style(style) == Style::windows)
-    return value == '\\';
-  return false;
-}
-
-StringRef get_separator(Style style) {
-  if (real_style(style) == Style::windows)
-    return "\\";
-  return "/";
-}
-
-bool has_root_name(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !root_name(p, style).empty();
-}
-
-bool has_root_directory(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !root_directory(p, style).empty();
-}
-
-bool has_root_path(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !root_path(p, style).empty();
-}
-
-bool has_relative_path(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !relative_path(p, style).empty();
-}
-
-bool has_filename(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !filename(p, style).empty();
-}
-
-bool has_parent_path(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !parent_path(p, style).empty();
-}
-
-bool has_stem(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !stem(p, style).empty();
-}
-
-bool has_extension(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  return !extension(p, style).empty();
-}
-
-bool is_absolute(const Twine &path, Style style) {
-  SmallString<128> path_storage;
-  StringRef p = path.toStringRef(path_storage);
-
-  bool rootDir = has_root_directory(p, style);
-  bool rootName =
-      (real_style(style) != Style::windows) || has_root_name(p, style);
-
-  return rootDir && rootName;
-}
-
-bool is_relative(const Twine &path, Style style) {
-  return !is_absolute(path, style);
-}
-
-StringRef remove_leading_dotslash(StringRef Path, Style style) {
-  // Remove leading "./" (or ".//" or "././" etc.)
-  while (Path.size() > 2 && Path[0] == '.' && is_separator(Path[1], style)) {
-    Path = Path.substr(2);
-    while (Path.size() > 0 && is_separator(Path[0], style))
-      Path = Path.substr(1);
-  }
-  return Path;
-}
-
-static SmallString<256> remove_dots(StringRef path, bool remove_dot_dot,
-                                    Style style) {
-  SmallVector<StringRef, 16> components;
-
-  // Skip the root path, then look for traversal in the components.
-  StringRef rel = path::relative_path(path, style);
-  for (StringRef C :
-       wpi::make_range(path::begin(rel, style), path::end(rel))) {
-    if (C == ".")
-      continue;
-    // Leading ".." will remain in the path unless it's at the root.
-    if (remove_dot_dot && C == "..") {
-      if (!components.empty() && components.back() != "..") {
-        components.pop_back();
-        continue;
-      }
-      if (path::is_absolute(path, style))
-        continue;
-    }
-    components.push_back(C);
-  }
-
-  SmallString<256> buffer = path::root_path(path, style);
-  for (StringRef C : components)
-    path::append(buffer, style, C);
-  return buffer;
-}
-
-bool remove_dots(SmallVectorImpl<char> &path, bool remove_dot_dot,
-                 Style style) {
-  StringRef p(path.data(), path.size());
-
-  SmallString<256> result = remove_dots(p, remove_dot_dot, style);
-  if (result == path)
-    return false;
-
-  path.swap(result);
-  return true;
-}
-
-} // end namespace path
-
-namespace fs {
-
-std::error_code getUniqueID(const Twine Path, UniqueID &Result) {
-  file_status Status;
-  std::error_code EC = status(Path, Status);
-  if (EC)
-    return EC;
-  Result = Status.getUniqueID();
-  return std::error_code();
-}
-
-void make_absolute(const Twine &current_directory,
-                   SmallVectorImpl<char> &path) {
-  StringRef p(path.data(), path.size());
-
-  bool rootDirectory = path::has_root_directory(p);
-  bool rootName =
-      (real_style(Style::native) != Style::windows) || path::has_root_name(p);
-
-  // Already absolute.
-  if (rootName && rootDirectory)
-    return;
-
-  // All of the following conditions will need the current directory.
-  SmallString<128> current_dir;
-  current_directory.toVector(current_dir);
-
-  // Relative path. Prepend the current directory.
-  if (!rootName && !rootDirectory) {
-    // Append path to the current directory.
-    path::append(current_dir, p);
-    // Set path to the result.
-    path.swap(current_dir);
-    return;
-  }
-
-  if (!rootName && rootDirectory) {
-    StringRef cdrn = path::root_name(current_dir);
-    SmallString<128> curDirRootName(cdrn.begin(), cdrn.end());
-    path::append(curDirRootName, p);
-    // Set path to the result.
-    path.swap(curDirRootName);
-    return;
-  }
-
-  if (rootName && !rootDirectory) {
-    StringRef pRootName      = path::root_name(p);
-    StringRef bRootDirectory = path::root_directory(current_dir);
-    StringRef bRelativePath  = path::relative_path(current_dir);
-    StringRef pRelativePath  = path::relative_path(p);
-
-    SmallString<128> res;
-    path::append(res, pRootName, bRootDirectory, bRelativePath, pRelativePath);
-    path.swap(res);
-    return;
-  }
-
-  wpi_unreachable("All rootName and rootDirectory combinations should have "
-                   "occurred above!");
-}
-
-std::error_code make_absolute(SmallVectorImpl<char> &path) {
-  if (path::is_absolute(path))
-    return {};
-
-  SmallString<128> current_dir;
-  if (std::error_code ec = current_path(current_dir))
-    return ec;
-
-  make_absolute(current_dir, path);
-  return {};
-}
-
-bool exists(const basic_file_status &status) {
-  return status_known(status) && status.type() != file_type::file_not_found;
-}
-
-bool status_known(const basic_file_status &s) {
-  return s.type() != file_type::status_error;
-}
-
-file_type get_file_type(const Twine &Path, bool Follow) {
-  file_status st;
-  if (status(Path, st, Follow))
-    return file_type::status_error;
-  return st.type();
-}
-
-bool is_directory(const basic_file_status &status) {
-  return status.type() == file_type::directory_file;
-}
-
-std::error_code is_directory(const Twine &path, bool &result) {
-  file_status st;
-  if (std::error_code ec = status(path, st))
-    return ec;
-  result = is_directory(st);
-  return std::error_code();
-}
-
-bool is_regular_file(const basic_file_status &status) {
-  return status.type() == file_type::regular_file;
-}
-
-std::error_code is_regular_file(const Twine &path, bool &result) {
-  file_status st;
-  if (std::error_code ec = status(path, st))
-    return ec;
-  result = is_regular_file(st);
-  return std::error_code();
-}
-
-bool is_symlink_file(const basic_file_status &status) {
-  return status.type() == file_type::symlink_file;
-}
-
-std::error_code is_symlink_file(const Twine &path, bool &result) {
-  file_status st;
-  if (std::error_code ec = status(path, st, false))
-    return ec;
-  result = is_symlink_file(st);
-  return std::error_code();
-}
-
-bool is_other(const basic_file_status &status) {
-  return exists(status) &&
-         !is_regular_file(status) &&
-         !is_directory(status);
-}
-
-std::error_code is_other(const Twine &Path, bool &Result) {
-  file_status FileStatus;
-  if (std::error_code EC = status(Path, FileStatus))
-    return EC;
-  Result = is_other(FileStatus);
-  return std::error_code();
-}
-
-void directory_entry::replace_filename(const Twine &Filename, file_type Type,
-                                       basic_file_status Status) {
-  SmallString<128> PathStr = path::parent_path(Path);
-  path::append(PathStr, Filename);
-  this->Path = PathStr.str();
-  this->Type = Type;
-  this->Status = Status;
-}
-
-ErrorOr<perms> getPermissions(const Twine &Path) {
-  file_status Status;
-  if (std::error_code EC = status(Path, Status))
-    return EC;
-
-  return Status.permissions();
-}
-
-} // end namespace fs
-} // end namespace sys
-} // end namespace wpi
-
-// Include the truly platform-specific parts.
-#ifdef _WIN32
-#include "Windows/Path.inc"
-#else
-#include "Unix/Path.inc"
-#endif
diff --git a/wpiutil/src/main/native/cpp/llvm/SmallPtrSet.cpp b/wpiutil/src/main/native/cpp/llvm/SmallPtrSet.cpp
index 1dab1fc..1b33f4c 100644
--- a/wpiutil/src/main/native/cpp/llvm/SmallPtrSet.cpp
+++ b/wpiutil/src/main/native/cpp/llvm/SmallPtrSet.cpp
@@ -15,6 +15,7 @@
 #include "wpi/SmallPtrSet.h"
 #include "wpi/DenseMapInfo.h"
 #include "wpi/MathExtras.h"
+#include "wpi/MemAlloc.h"
 #include "wpi/ErrorHandling.h"
 #include <algorithm>
 #include <cassert>
diff --git a/wpiutil/src/main/native/cpp/llvm/StringExtras.cpp b/wpiutil/src/main/native/cpp/llvm/StringExtras.cpp
deleted file mode 100644
index e4bfe8a..0000000
--- a/wpiutil/src/main/native/cpp/llvm/StringExtras.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-//===-- StringExtras.cpp - Implement the StringExtras header --------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the StringExtras.h header
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/StringExtras.h"
-#include "wpi/SmallVector.h"
-#include "wpi/raw_ostream.h"
-using namespace wpi;
-
-/// StrInStrNoCase - Portable version of strcasestr.  Locates the first
-/// occurrence of string 's1' in string 's2', ignoring case.  Returns
-/// the offset of s2 in s1 or npos if s2 cannot be found.
-StringRef::size_type wpi::StrInStrNoCase(StringRef s1, StringRef s2) {
-  size_t N = s2.size(), M = s1.size();
-  if (N > M)
-    return StringRef::npos;
-  for (size_t i = 0, e = M - N + 1; i != e; ++i)
-    if (s1.substr(i, N).equals_lower(s2))
-      return i;
-  return StringRef::npos;
-}
-
-/// getToken - This function extracts one token from source, ignoring any
-/// leading characters that appear in the Delimiters string, and ending the
-/// token at any of the characters that appear in the Delimiters string.  If
-/// there are no tokens in the source string, an empty string is returned.
-/// The function returns a pair containing the extracted token and the
-/// remaining tail string.
-std::pair<StringRef, StringRef> wpi::getToken(StringRef Source,
-                                               StringRef Delimiters) {
-  // Figure out where the token starts.
-  StringRef::size_type Start = Source.find_first_not_of(Delimiters);
-
-  // Find the next occurrence of the delimiter.
-  StringRef::size_type End = Source.find_first_of(Delimiters, Start);
-
-  return std::make_pair(Source.slice(Start, End), Source.substr(End));
-}
-
-/// SplitString - Split up the specified string according to the specified
-/// delimiters, appending the result fragments to the output list.
-void wpi::SplitString(StringRef Source,
-                       SmallVectorImpl<StringRef> &OutFragments,
-                       StringRef Delimiters) {
-  std::pair<StringRef, StringRef> S = getToken(Source, Delimiters);
-  while (!S.first.empty()) {
-    OutFragments.push_back(S.first);
-    S = getToken(S.second, Delimiters);
-  }
-}
-
-void wpi::printEscapedString(StringRef Name, raw_ostream &Out) {
-  for (unsigned i = 0, e = Name.size(); i != e; ++i) {
-    unsigned char C = Name[i];
-    if (isPrint(C) && C != '\\' && C != '"')
-      Out << C;
-    else
-      Out << '\\' << hexdigit(C >> 4) << hexdigit(C & 0x0F);
-  }
-}
-
-void wpi::printHTMLEscaped(StringRef String, raw_ostream &Out) {
-  for (char C : String) {
-    if (C == '&')
-      Out << "&amp;";
-    else if (C == '<')
-      Out << "&lt;";
-    else if (C == '>')
-      Out << "&gt;";
-    else if (C == '\"')
-      Out << "&quot;";
-    else if (C == '\'')
-      Out << "&apos;";
-    else
-      Out << C;
-  }
-}
-
-void wpi::printLowerCase(StringRef String, raw_ostream &Out) {
-  for (const char C : String)
-    Out << toLower(C);
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/StringMap.cpp b/wpiutil/src/main/native/cpp/llvm/StringMap.cpp
index 5c625c7..768801d 100644
--- a/wpiutil/src/main/native/cpp/llvm/StringMap.cpp
+++ b/wpiutil/src/main/native/cpp/llvm/StringMap.cpp
@@ -19,6 +19,21 @@
 
 using namespace wpi;
 
+/// HashString - Hash function for strings.
+///
+/// This is the Bernstein hash function.
+//
+// FIXME: Investigate whether a modified bernstein hash function performs
+// better: http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
+//   X*33+c -> X*33^c
+static inline unsigned HashString(std::string_view str,
+                                  unsigned result = 0) noexcept {
+  for (std::string_view::size_type i = 0, e = str.size(); i != e; ++i) {
+    result = result * 33 + static_cast<unsigned char>(str[i]);
+  }
+  return result;
+}
+
 /// Returns the number of buckets to allocate to ensure that the DenseMap can
 /// accommodate \p NumEntries without need to grow().
 static unsigned getMinBucketToReserveForEntries(unsigned NumEntries) {
@@ -74,7 +89,7 @@
 /// specified bucket will be non-null.  Otherwise, it will be null.  In either
 /// case, the FullHashValue field of the bucket will be set to the hash value
 /// of the string.
-unsigned StringMapImpl::LookupBucketFor(StringRef Name) {
+unsigned StringMapImpl::LookupBucketFor(std::string_view Name) {
   unsigned HTSize = NumBuckets;
   if (HTSize == 0) {  // Hash table unallocated so far?
     init(16);
@@ -113,7 +128,7 @@
       // Do the comparison like this because Name isn't necessarily
       // null-terminated!
       char *ItemStr = (char*)BucketItem+ItemSize;
-      if (Name == StringRef(ItemStr, BucketItem->getKeyLength())) {
+      if (Name == std::string_view(ItemStr, BucketItem->getKeyLength())) {
         // We found a match!
         return BucketNo;
       }
@@ -131,7 +146,7 @@
 /// FindKey - Look up the bucket that contains the specified key. If it exists
 /// in the map, return the bucket number of the key.  Otherwise return -1.
 /// This does not modify the map.
-int StringMapImpl::FindKey(StringRef Key) const {
+int StringMapImpl::FindKey(std::string_view Key) const {
   unsigned HTSize = NumBuckets;
   if (HTSize == 0) return -1;  // Really empty table?
   unsigned FullHashValue = HashString(Key);
@@ -156,7 +171,7 @@
       // Do the comparison like this because NameStart isn't necessarily
       // null-terminated!
       char *ItemStr = (char*)BucketItem+ItemSize;
-      if (Key == StringRef(ItemStr, BucketItem->getKeyLength())) {
+      if (Key == std::string_view(ItemStr, BucketItem->getKeyLength())) {
         // We found a match!
         return BucketNo;
       }
@@ -175,14 +190,14 @@
 /// delete it.  This aborts if the value isn't in the table.
 void StringMapImpl::RemoveKey(StringMapEntryBase *V) {
   const char *VStr = (char*)V + ItemSize;
-  StringMapEntryBase *V2 = RemoveKey(StringRef(VStr, V->getKeyLength()));
+  StringMapEntryBase *V2 = RemoveKey(std::string_view(VStr, V->getKeyLength()));
   (void)V2;
   assert(V == V2 && "Didn't find key?");
 }
 
 /// RemoveKey - Remove the StringMapEntry for the specified key from the
 /// table, returning it.  If the key is not in the table, this returns null.
-StringMapEntryBase *StringMapImpl::RemoveKey(StringRef Key) {
+StringMapEntryBase *StringMapImpl::RemoveKey(std::string_view Key) {
   int Bucket = FindKey(Key);
   if (Bucket == -1) return nullptr;
 
diff --git a/wpiutil/src/main/native/cpp/llvm/StringRef.cpp b/wpiutil/src/main/native/cpp/llvm/StringRef.cpp
deleted file mode 100644
index ea44bea..0000000
--- a/wpiutil/src/main/native/cpp/llvm/StringRef.cpp
+++ /dev/null
@@ -1,507 +0,0 @@
-//===-- StringRef.cpp - Lightweight String References ---------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/StringRef.h"
-#include "wpi/Hashing.h"
-#include "wpi/StringExtras.h"
-#include "wpi/SmallVector.h"
-#include <bitset>
-#include <climits>
-#include <ostream>
-
-using namespace wpi;
-
-// MSVC emits references to this into the translation units which reference it.
-#ifndef _MSC_VER
-const size_t StringRef::npos;
-#endif
-
-// strncasecmp() is not available on non-POSIX systems, so define an
-// alternative function here.
-static int ascii_strncasecmp(const char *LHS, const char *RHS, size_t Length) noexcept {
-  for (size_t I = 0; I < Length; ++I) {
-    unsigned char LHC = toLower(LHS[I]);
-    unsigned char RHC = toLower(RHS[I]);
-    if (LHC != RHC)
-      return LHC < RHC ? -1 : 1;
-  }
-  return 0;
-}
-
-/// compare_lower - Compare strings, ignoring case.
-int StringRef::compare_lower(StringRef RHS) const noexcept {
-  if (int Res = ascii_strncasecmp(Data, RHS.Data, std::min(Length, RHS.Length)))
-    return Res;
-  if (Length == RHS.Length)
-    return 0;
-  return Length < RHS.Length ? -1 : 1;
-}
-
-/// Check if this string starts with the given \p Prefix, ignoring case.
-bool StringRef::startswith_lower(StringRef Prefix) const noexcept {
-  return Length >= Prefix.Length &&
-      ascii_strncasecmp(Data, Prefix.Data, Prefix.Length) == 0;
-}
-
-/// Check if this string ends with the given \p Suffix, ignoring case.
-bool StringRef::endswith_lower(StringRef Suffix) const noexcept {
-  return Length >= Suffix.Length &&
-      ascii_strncasecmp(end() - Suffix.Length, Suffix.Data, Suffix.Length) == 0;
-}
-
-size_t StringRef::find_lower(char C, size_t From) const noexcept {
-  char L = toLower(C);
-  return find_if([L](char D) { return toLower(D) == L; }, From);
-}
-
-/// compare_numeric - Compare strings, handle embedded numbers.
-int StringRef::compare_numeric(StringRef RHS) const noexcept {
-  for (size_t I = 0, E = std::min(Length, RHS.Length); I != E; ++I) {
-    // Check for sequences of digits.
-    if (isDigit(Data[I]) && isDigit(RHS.Data[I])) {
-      // The longer sequence of numbers is considered larger.
-      // This doesn't really handle prefixed zeros well.
-      size_t J;
-      for (J = I + 1; J != E + 1; ++J) {
-        bool ld = J < Length && isDigit(Data[J]);
-        bool rd = J < RHS.Length && isDigit(RHS.Data[J]);
-        if (ld != rd)
-          return rd ? -1 : 1;
-        if (!rd)
-          break;
-      }
-      // The two number sequences have the same length (J-I), just memcmp them.
-      if (int Res = compareMemory(Data + I, RHS.Data + I, J - I))
-        return Res < 0 ? -1 : 1;
-      // Identical number sequences, continue search after the numbers.
-      I = J - 1;
-      continue;
-    }
-    if (Data[I] != RHS.Data[I])
-      return (unsigned char)Data[I] < (unsigned char)RHS.Data[I] ? -1 : 1;
-  }
-  if (Length == RHS.Length)
-    return 0;
-  return Length < RHS.Length ? -1 : 1;
-}
-
-//===----------------------------------------------------------------------===//
-// String Operations
-//===----------------------------------------------------------------------===//
-
-std::string StringRef::lower() const {
-  std::string Result(size(), char());
-  for (size_type i = 0, e = size(); i != e; ++i) {
-    Result[i] = toLower(Data[i]);
-  }
-  return Result;
-}
-
-std::string StringRef::upper() const {
-  std::string Result(size(), char());
-  for (size_type i = 0, e = size(); i != e; ++i) {
-    Result[i] = toUpper(Data[i]);
-  }
-  return Result;
-}
-
-//===----------------------------------------------------------------------===//
-// String Searching
-//===----------------------------------------------------------------------===//
-
-
-/// find - Search for the first string \arg Str in the string.
-///
-/// \return - The index of the first occurrence of \arg Str, or npos if not
-/// found.
-size_t StringRef::find(StringRef Str, size_t From) const noexcept {
-  if (From > Length)
-    return npos;
-
-  const char *Start = Data + From;
-  size_t Size = Length - From;
-
-  const char *Needle = Str.data();
-  size_t N = Str.size();
-  if (N == 0)
-    return From;
-  if (Size < N)
-    return npos;
-  if (N == 1) {
-    const char *Ptr = (const char *)::memchr(Start, Needle[0], Size);
-    return Ptr == nullptr ? npos : Ptr - Data;
-  }
-
-  const char *Stop = Start + (Size - N + 1);
-
-  // For short haystacks or unsupported needles fall back to the naive algorithm
-  if (Size < 16 || N > 255) {
-    do {
-      if (std::memcmp(Start, Needle, N) == 0)
-        return Start - Data;
-      ++Start;
-    } while (Start < Stop);
-    return npos;
-  }
-
-  // Build the bad char heuristic table, with uint8_t to reduce cache thrashing.
-  uint8_t BadCharSkip[256];
-  std::memset(BadCharSkip, N, 256);
-  for (unsigned i = 0; i != N-1; ++i)
-    BadCharSkip[(uint8_t)Str[i]] = N-1-i;
-
-  do {
-    uint8_t Last = Start[N - 1];
-    if (LLVM_UNLIKELY(Last == (uint8_t)Needle[N - 1]))
-      if (std::memcmp(Start, Needle, N - 1) == 0)
-        return Start - Data;
-
-    // Otherwise skip the appropriate number of bytes.
-    Start += BadCharSkip[Last];
-  } while (Start < Stop);
-
-  return npos;
-}
-
-size_t StringRef::find_lower(StringRef Str, size_t From) const noexcept {
-  StringRef This = substr(From);
-  while (This.size() >= Str.size()) {
-    if (This.startswith_lower(Str))
-      return From;
-    This = This.drop_front();
-    ++From;
-  }
-  return npos;
-}
-
-size_t StringRef::rfind_lower(char C, size_t From) const noexcept {
-  From = std::min(From, Length);
-  size_t i = From;
-  while (i != 0) {
-    --i;
-    if (toLower(Data[i]) == toLower(C))
-      return i;
-  }
-  return npos;
-}
-
-/// rfind - Search for the last string \arg Str in the string.
-///
-/// \return - The index of the last occurrence of \arg Str, or npos if not
-/// found.
-size_t StringRef::rfind(StringRef Str) const noexcept {
-  size_t N = Str.size();
-  if (N > Length)
-    return npos;
-  for (size_t i = Length - N + 1, e = 0; i != e;) {
-    --i;
-    if (substr(i, N).equals(Str))
-      return i;
-  }
-  return npos;
-}
-
-size_t StringRef::rfind_lower(StringRef Str) const noexcept {
-  size_t N = Str.size();
-  if (N > Length)
-    return npos;
-  for (size_t i = Length - N + 1, e = 0; i != e;) {
-    --i;
-    if (substr(i, N).equals_lower(Str))
-      return i;
-  }
-  return npos;
-}
-
-/// find_first_of - Find the first character in the string that is in \arg
-/// Chars, or npos if not found.
-///
-/// Note: O(size() + Chars.size())
-StringRef::size_type StringRef::find_first_of(StringRef Chars,
-                                              size_t From) const noexcept {
-  std::bitset<1 << CHAR_BIT> CharBits;
-  for (size_type i = 0; i != Chars.size(); ++i)
-    CharBits.set((unsigned char)Chars[i]);
-
-  for (size_type i = std::min(From, Length), e = Length; i != e; ++i)
-    if (CharBits.test((unsigned char)Data[i]))
-      return i;
-  return npos;
-}
-
-/// find_first_not_of - Find the first character in the string that is not
-/// \arg C or npos if not found.
-StringRef::size_type StringRef::find_first_not_of(char C, size_t From) const noexcept {
-  for (size_type i = std::min(From, Length), e = Length; i != e; ++i)
-    if (Data[i] != C)
-      return i;
-  return npos;
-}
-
-/// find_first_not_of - Find the first character in the string that is not
-/// in the string \arg Chars, or npos if not found.
-///
-/// Note: O(size() + Chars.size())
-StringRef::size_type StringRef::find_first_not_of(StringRef Chars,
-                                                  size_t From) const noexcept {
-  std::bitset<1 << CHAR_BIT> CharBits;
-  for (size_type i = 0; i != Chars.size(); ++i)
-    CharBits.set((unsigned char)Chars[i]);
-
-  for (size_type i = std::min(From, Length), e = Length; i != e; ++i)
-    if (!CharBits.test((unsigned char)Data[i]))
-      return i;
-  return npos;
-}
-
-/// find_last_of - Find the last character in the string that is in \arg C,
-/// or npos if not found.
-///
-/// Note: O(size() + Chars.size())
-StringRef::size_type StringRef::find_last_of(StringRef Chars,
-                                             size_t From) const noexcept {
-  std::bitset<1 << CHAR_BIT> CharBits;
-  for (size_type i = 0; i != Chars.size(); ++i)
-    CharBits.set((unsigned char)Chars[i]);
-
-  for (size_type i = std::min(From, Length) - 1, e = -1; i != e; --i)
-    if (CharBits.test((unsigned char)Data[i]))
-      return i;
-  return npos;
-}
-
-/// find_last_not_of - Find the last character in the string that is not
-/// \arg C, or npos if not found.
-StringRef::size_type StringRef::find_last_not_of(char C, size_t From) const noexcept {
-  for (size_type i = std::min(From, Length) - 1, e = -1; i != e; --i)
-    if (Data[i] != C)
-      return i;
-  return npos;
-}
-
-/// find_last_not_of - Find the last character in the string that is not in
-/// \arg Chars, or npos if not found.
-///
-/// Note: O(size() + Chars.size())
-StringRef::size_type StringRef::find_last_not_of(StringRef Chars,
-                                                 size_t From) const noexcept {
-  std::bitset<1 << CHAR_BIT> CharBits;
-  for (size_type i = 0, e = Chars.size(); i != e; ++i)
-    CharBits.set((unsigned char)Chars[i]);
-
-  for (size_type i = std::min(From, Length) - 1, e = -1; i != e; --i)
-    if (!CharBits.test((unsigned char)Data[i]))
-      return i;
-  return npos;
-}
-
-void StringRef::split(SmallVectorImpl<StringRef> &A,
-                      StringRef Separator, int MaxSplit,
-                      bool KeepEmpty) const {
-  StringRef S = *this;
-
-  // Count down from MaxSplit. When MaxSplit is -1, this will just split
-  // "forever". This doesn't support splitting more than 2^31 times
-  // intentionally; if we ever want that we can make MaxSplit a 64-bit integer
-  // but that seems unlikely to be useful.
-  while (MaxSplit-- != 0) {
-    size_t Idx = S.find(Separator);
-    if (Idx == npos)
-      break;
-
-    // Push this split.
-    if (KeepEmpty || Idx > 0)
-      A.push_back(S.slice(0, Idx));
-
-    // Jump forward.
-    S = S.slice(Idx + Separator.size(), npos);
-  }
-
-  // Push the tail.
-  if (KeepEmpty || !S.empty())
-    A.push_back(S);
-}
-
-void StringRef::split(SmallVectorImpl<StringRef> &A, char Separator,
-                      int MaxSplit, bool KeepEmpty) const {
-  StringRef S = *this;
-
-  // Count down from MaxSplit. When MaxSplit is -1, this will just split
-  // "forever". This doesn't support splitting more than 2^31 times
-  // intentionally; if we ever want that we can make MaxSplit a 64-bit integer
-  // but that seems unlikely to be useful.
-  while (MaxSplit-- != 0) {
-    size_t Idx = S.find(Separator);
-    if (Idx == npos)
-      break;
-
-    // Push this split.
-    if (KeepEmpty || Idx > 0)
-      A.push_back(S.slice(0, Idx));
-
-    // Jump forward.
-    S = S.slice(Idx + 1, npos);
-  }
-
-  // Push the tail.
-  if (KeepEmpty || !S.empty())
-    A.push_back(S);
-}
-
-//===----------------------------------------------------------------------===//
-// Helpful Algorithms
-//===----------------------------------------------------------------------===//
-
-/// count - Return the number of non-overlapped occurrences of \arg Str in
-/// the string.
-size_t StringRef::count(StringRef Str) const noexcept {
-  size_t Count = 0;
-  size_t N = Str.size();
-  if (N > Length)
-    return 0;
-  for (size_t i = 0, e = Length - N + 1; i != e; ++i)
-    if (substr(i, N).equals(Str))
-      ++Count;
-  return Count;
-}
-
-static unsigned GetAutoSenseRadix(StringRef &Str) noexcept {
-  if (Str.empty())
-    return 10;
-
-  if (Str.startswith("0x") || Str.startswith("0X")) {
-    Str = Str.substr(2);
-    return 16;
-  }
-
-  if (Str.startswith("0b") || Str.startswith("0B")) {
-    Str = Str.substr(2);
-    return 2;
-  }
-
-  if (Str.startswith("0o")) {
-    Str = Str.substr(2);
-    return 8;
-  }
-
-  if (Str[0] == '0' && Str.size() > 1 && isDigit(Str[1])) {
-    Str = Str.substr(1);
-    return 8;
-  }
-
-  return 10;
-}
-
-bool wpi::consumeUnsignedInteger(StringRef &Str, unsigned Radix,
-                                  unsigned long long &Result) noexcept {
-  // Autosense radix if not specified.
-  if (Radix == 0)
-    Radix = GetAutoSenseRadix(Str);
-
-  // Empty strings (after the radix autosense) are invalid.
-  if (Str.empty()) return true;
-
-  // Parse all the bytes of the string given this radix.  Watch for overflow.
-  StringRef Str2 = Str;
-  Result = 0;
-  while (!Str2.empty()) {
-    unsigned CharVal;
-    if (Str2[0] >= '0' && Str2[0] <= '9')
-      CharVal = Str2[0] - '0';
-    else if (Str2[0] >= 'a' && Str2[0] <= 'z')
-      CharVal = Str2[0] - 'a' + 10;
-    else if (Str2[0] >= 'A' && Str2[0] <= 'Z')
-      CharVal = Str2[0] - 'A' + 10;
-    else
-      break;
-
-    // If the parsed value is larger than the integer radix, we cannot
-    // consume any more characters.
-    if (CharVal >= Radix)
-      break;
-
-    // Add in this character.
-    unsigned long long PrevResult = Result;
-    Result = Result * Radix + CharVal;
-
-    // Check for overflow by shifting back and seeing if bits were lost.
-    if (Result / Radix < PrevResult)
-      return true;
-
-    Str2 = Str2.substr(1);
-  }
-
-  // We consider the operation a failure if no characters were consumed
-  // successfully.
-  if (Str.size() == Str2.size())
-    return true;
-
-  Str = Str2;
-  return false;
-}
-
-bool wpi::consumeSignedInteger(StringRef &Str, unsigned Radix,
-                                long long &Result) noexcept {
-  unsigned long long ULLVal;
-
-  // Handle positive strings first.
-  if (Str.empty() || Str.front() != '-') {
-    if (consumeUnsignedInteger(Str, Radix, ULLVal) ||
-        // Check for value so large it overflows a signed value.
-        (long long)ULLVal < 0)
-      return true;
-    Result = ULLVal;
-    return false;
-  }
-
-  // Get the positive part of the value.
-  StringRef Str2 = Str.drop_front(1);
-  if (consumeUnsignedInteger(Str2, Radix, ULLVal) ||
-      // Reject values so large they'd overflow as negative signed, but allow
-      // "-0".  This negates the unsigned so that the negative isn't undefined
-      // on signed overflow.
-      (long long)-ULLVal > 0)
-    return true;
-
-  Str = Str2;
-  Result = -ULLVal;
-  return false;
-}
-
-/// GetAsUnsignedInteger - Workhorse method that converts a integer character
-/// sequence of radix up to 36 to an unsigned long long value.
-bool wpi::getAsUnsignedInteger(StringRef Str, unsigned Radix,
-                                unsigned long long &Result) noexcept {
-  if (consumeUnsignedInteger(Str, Radix, Result))
-    return true;
-
-  // For getAsUnsignedInteger, we require the whole string to be consumed or
-  // else we consider it a failure.
-  return !Str.empty();
-}
-
-bool wpi::getAsSignedInteger(StringRef Str, unsigned Radix,
-                              long long &Result) noexcept {
-  if (consumeSignedInteger(Str, Radix, Result))
-    return true;
-
-  // For getAsSignedInteger, we require the whole string to be consumed or else
-  // we consider it a failure.
-  return !Str.empty();
-}
-
-std::ostream &wpi::operator<<(std::ostream &os, StringRef string) {
-  os.write(string.data(), string.size());
-  return os;
-}
-
-// Implementation of StringRef hashing.
-hash_code wpi::hash_value(StringRef S) {
-  return hash_combine_range(S.begin(), S.end());
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/Twine.cpp b/wpiutil/src/main/native/cpp/llvm/Twine.cpp
deleted file mode 100644
index ac705ff..0000000
--- a/wpiutil/src/main/native/cpp/llvm/Twine.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-//===-- Twine.cpp - Fast Temporary String Concatenation -------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/Twine.h"
-#include "wpi/SmallString.h"
-#include "wpi/raw_ostream.h"
-using namespace wpi;
-
-std::string Twine::str() const {
-  // If we're storing only a std::string, just return it.
-  if (LHSKind == StdStringKind && RHSKind == EmptyKind)
-    return *LHS.stdString;
-
-  // Otherwise, flatten and copy the contents first.
-  SmallString<256> Vec;
-  return toStringRef(Vec).str();
-}
-
-void Twine::toVector(SmallVectorImpl<char> &Out) const {
-  raw_svector_ostream OS(Out);
-  print(OS);
-}
-
-StringRef Twine::toNullTerminatedStringRef(SmallVectorImpl<char> &Out) const {
-  if (isUnary()) {
-    switch (getLHSKind()) {
-    case CStringKind:
-      // Already null terminated, yay!
-      return StringRef(LHS.cString);
-    case StdStringKind: {
-      const std::string *str = LHS.stdString;
-      return StringRef(str->c_str(), str->size());
-    }
-    default:
-      break;
-    }
-  }
-  toVector(Out);
-  Out.push_back(0);
-  Out.pop_back();
-  return StringRef(Out.data(), Out.size());
-}
-
-void Twine::printOneChild(raw_ostream &OS, Child Ptr,
-                          NodeKind Kind) const {
-  switch (Kind) {
-  case Twine::NullKind: break;
-  case Twine::EmptyKind: break;
-  case Twine::TwineKind:
-    Ptr.twine->print(OS);
-    break;
-  case Twine::CStringKind:
-    OS << Ptr.cString;
-    break;
-  case Twine::StdStringKind:
-    OS << *Ptr.stdString;
-    break;
-  case Twine::StringRefKind:
-    OS << *Ptr.stringRef;
-    break;
-  case Twine::SmallStringKind:
-    OS << *Ptr.smallString;
-    break;
-  case Twine::CharKind:
-    OS << Ptr.character;
-    break;
-  case Twine::DecUIKind:
-    OS << Ptr.decUI;
-    break;
-  case Twine::DecIKind:
-    OS << Ptr.decI;
-    break;
-  case Twine::DecULKind:
-    OS << *Ptr.decUL;
-    break;
-  case Twine::DecLKind:
-    OS << *Ptr.decL;
-    break;
-  case Twine::DecULLKind:
-    OS << *Ptr.decULL;
-    break;
-  case Twine::DecLLKind:
-    OS << *Ptr.decLL;
-    break;
-  case Twine::UHexKind:
-    OS.write_hex(*Ptr.uHex);
-    break;
-  }
-}
-
-void Twine::printOneChildRepr(raw_ostream &OS, Child Ptr,
-                              NodeKind Kind) const {
-  switch (Kind) {
-  case Twine::NullKind:
-    OS << "null"; break;
-  case Twine::EmptyKind:
-    OS << "empty"; break;
-  case Twine::TwineKind:
-    OS << "rope:";
-    Ptr.twine->printRepr(OS);
-    break;
-  case Twine::CStringKind:
-    OS << "cstring:\""
-       << Ptr.cString << "\"";
-    break;
-  case Twine::StdStringKind:
-    OS << "std::string:\""
-       << Ptr.stdString << "\"";
-    break;
-  case Twine::StringRefKind:
-    OS << "stringref:\""
-       << Ptr.stringRef << "\"";
-    break;
-  case Twine::SmallStringKind:
-    OS << "smallstring:\"" << *Ptr.smallString << "\"";
-    break;
-  case Twine::CharKind:
-    OS << "char:\"" << Ptr.character << "\"";
-    break;
-  case Twine::DecUIKind:
-    OS << "decUI:\"" << Ptr.decUI << "\"";
-    break;
-  case Twine::DecIKind:
-    OS << "decI:\"" << Ptr.decI << "\"";
-    break;
-  case Twine::DecULKind:
-    OS << "decUL:\"" << *Ptr.decUL << "\"";
-    break;
-  case Twine::DecLKind:
-    OS << "decL:\"" << *Ptr.decL << "\"";
-    break;
-  case Twine::DecULLKind:
-    OS << "decULL:\"" << *Ptr.decULL << "\"";
-    break;
-  case Twine::DecLLKind:
-    OS << "decLL:\"" << *Ptr.decLL << "\"";
-    break;
-  case Twine::UHexKind:
-    OS << "uhex:\"" << Ptr.uHex << "\"";
-    break;
-  }
-}
-
-void Twine::print(raw_ostream &OS) const {
-  printOneChild(OS, LHS, getLHSKind());
-  printOneChild(OS, RHS, getRHSKind());
-}
-
-void Twine::printRepr(raw_ostream &OS) const {
-  OS << "(Twine ";
-  printOneChildRepr(OS, LHS, getLHSKind());
-  OS << " ";
-  printOneChildRepr(OS, RHS, getRHSKind());
-  OS << ")";
-}
-
-LLVM_DUMP_METHOD void Twine::dump() const {
-  print(errs());
-}
-
-LLVM_DUMP_METHOD void Twine::dumpRepr() const {
-  printRepr(errs());
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/Unix/Path.inc b/wpiutil/src/main/native/cpp/llvm/Unix/Path.inc
deleted file mode 100644
index 4ec9e86..0000000
--- a/wpiutil/src/main/native/cpp/llvm/Unix/Path.inc
+++ /dev/null
@@ -1,539 +0,0 @@
-//===- llvm/Support/Unix/Path.inc - Unix Path Implementation ----*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the Unix specific implementation of the Path API.
-//
-//===----------------------------------------------------------------------===//
-
-//===----------------------------------------------------------------------===//
-//=== WARNING: Implementation here must contain only generic UNIX code that
-//===          is guaranteed to work on *all* UNIX variants.
-//===----------------------------------------------------------------------===//
-
-#include "wpi/Errno.h"
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <dirent.h>
-#include <pwd.h>
-#include <sys/param.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-using namespace wpi;
-
-namespace wpi {
-namespace sys  {
-namespace fs {
-
-const file_t kInvalidFile = -1;
-
-TimePoint<> basic_file_status::getLastAccessedTime() const {
-  return toTimePoint(fs_st_atime, fs_st_atime_nsec);
-}
-
-TimePoint<> basic_file_status::getLastModificationTime() const {
-  return toTimePoint(fs_st_mtime, fs_st_mtime_nsec);
-}
-
-UniqueID file_status::getUniqueID() const {
-  return UniqueID(fs_st_dev, fs_st_ino);
-}
-
-uint32_t file_status::getLinkCount() const {
-  return fs_st_nlinks;
-}
-
-std::error_code current_path(SmallVectorImpl<char> &result) {
-  result.clear();
-
-  const char *pwd = ::getenv("PWD");
-  wpi::sys::fs::file_status PWDStatus, DotStatus;
-  if (pwd && wpi::sys::path::is_absolute(pwd) &&
-      !wpi::sys::fs::status(pwd, PWDStatus) &&
-      !wpi::sys::fs::status(".", DotStatus) &&
-      PWDStatus.getUniqueID() == DotStatus.getUniqueID()) {
-    result.append(pwd, pwd + strlen(pwd));
-    return std::error_code();
-  }
-
-#ifdef MAXPATHLEN
-  result.reserve(MAXPATHLEN);
-#else
-  result.reserve(1024);
-#endif
-
-  while (true) {
-    if (::getcwd(result.data(), result.capacity()) == nullptr) {
-      // See if there was a real error.
-      if (errno != ENOMEM)
-        return std::error_code(errno, std::generic_category());
-      // Otherwise there just wasn't enough space.
-      result.reserve(result.capacity() * 2);
-    } else
-      break;
-  }
-
-  result.set_size(strlen(result.data()));
-  return std::error_code();
-}
-
-static int convertAccessMode(AccessMode Mode) {
-  switch (Mode) {
-  case AccessMode::Exist:
-    return F_OK;
-  case AccessMode::Write:
-    return W_OK;
-  case AccessMode::Execute:
-    return R_OK | X_OK; // scripts also need R_OK.
-  default:
-    return F_OK;
-  }
-}
-
-std::error_code access(const Twine &Path, AccessMode Mode) {
-  SmallString<128> PathStorage;
-  StringRef P = Path.toNullTerminatedStringRef(PathStorage);
-
-  if (::access(P.begin(), convertAccessMode(Mode)) == -1)
-    return std::error_code(errno, std::generic_category());
-
-  if (Mode == AccessMode::Execute) {
-    // Don't say that directories are executable.
-    struct stat buf;
-    if (0 != stat(P.begin(), &buf))
-      return errc::permission_denied;
-    if (!S_ISREG(buf.st_mode))
-      return errc::permission_denied;
-  }
-
-  return std::error_code();
-}
-
-bool equivalent(file_status A, file_status B) {
-  assert(status_known(A) && status_known(B));
-  return A.fs_st_dev == B.fs_st_dev &&
-         A.fs_st_ino == B.fs_st_ino;
-}
-
-std::error_code equivalent(const Twine &A, const Twine &B, bool &result) {
-  file_status fsA, fsB;
-  if (std::error_code ec = status(A, fsA))
-    return ec;
-  if (std::error_code ec = status(B, fsB))
-    return ec;
-  result = equivalent(fsA, fsB);
-  return std::error_code();
-}
-
-static file_type typeForMode(mode_t Mode) {
-  if (S_ISDIR(Mode))
-    return file_type::directory_file;
-  else if (S_ISREG(Mode))
-    return file_type::regular_file;
-  else if (S_ISBLK(Mode))
-    return file_type::block_file;
-  else if (S_ISCHR(Mode))
-    return file_type::character_file;
-  else if (S_ISFIFO(Mode))
-    return file_type::fifo_file;
-  else if (S_ISSOCK(Mode))
-    return file_type::socket_file;
-  else if (S_ISLNK(Mode))
-    return file_type::symlink_file;
-  return file_type::type_unknown;
-}
-
-static std::error_code fillStatus(int StatRet, const struct stat &Status,
-                                  file_status &Result) {
-  if (StatRet != 0) {
-    std::error_code EC(errno, std::generic_category());
-    if (EC == errc::no_such_file_or_directory)
-      Result = file_status(file_type::file_not_found);
-    else
-      Result = file_status(file_type::status_error);
-    return EC;
-  }
-
-  uint32_t atime_nsec, mtime_nsec;
-#if defined(__APPLE__)
-  atime_nsec = Status.st_atimespec.tv_nsec;
-  mtime_nsec = Status.st_mtimespec.tv_nsec;
-#else
-  atime_nsec = Status.st_atim.tv_nsec;
-  mtime_nsec = Status.st_mtim.tv_nsec;
-#endif
-
-  perms Perms = static_cast<perms>(Status.st_mode) & all_perms;
-  Result = file_status(typeForMode(Status.st_mode), Perms, Status.st_dev,
-                       Status.st_nlink, Status.st_ino,
-                       Status.st_atime, atime_nsec, Status.st_mtime, mtime_nsec,
-                       Status.st_uid, Status.st_gid, Status.st_size);
-
-  return std::error_code();
-}
-
-std::error_code status(const Twine &Path, file_status &Result, bool Follow) {
-  SmallString<128> PathStorage;
-  StringRef P = Path.toNullTerminatedStringRef(PathStorage);
-
-  struct stat Status;
-  int StatRet = (Follow ? ::stat : ::lstat)(P.begin(), &Status);
-  return fillStatus(StatRet, Status, Result);
-}
-
-std::error_code status(int FD, file_status &Result) {
-  struct stat Status;
-  int StatRet = ::fstat(FD, &Status);
-  return fillStatus(StatRet, Status, Result);
-}
-
-std::error_code mapped_file_region::init(int FD, uint64_t Offset,
-                                         mapmode Mode) {
-  assert(Size != 0);
-
-  int flags = (Mode == readwrite) ? MAP_SHARED : MAP_PRIVATE;
-  int prot = (Mode == readonly) ? PROT_READ : (PROT_READ | PROT_WRITE);
-#if defined(__APPLE__)
-  //----------------------------------------------------------------------
-  // Newer versions of MacOSX have a flag that will allow us to read from
-  // binaries whose code signature is invalid without crashing by using
-  // the MAP_RESILIENT_CODESIGN flag. Also if a file from removable media
-  // is mapped we can avoid crashing and return zeroes to any pages we try
-  // to read if the media becomes unavailable by using the
-  // MAP_RESILIENT_MEDIA flag.  These flags are only usable when mapping
-  // with PROT_READ, so take care not to specify them otherwise.
-  //----------------------------------------------------------------------
-  if (Mode == readonly) {
-#if defined(MAP_RESILIENT_CODESIGN)
-    flags |= MAP_RESILIENT_CODESIGN;
-#endif
-#if defined(MAP_RESILIENT_MEDIA)
-    flags |= MAP_RESILIENT_MEDIA;
-#endif
-  }
-#endif // #if defined (__APPLE__)
-
-  Mapping = ::mmap(nullptr, Size, prot, flags, FD, Offset);
-  if (Mapping == MAP_FAILED)
-    return std::error_code(errno, std::generic_category());
-  return std::error_code();
-}
-
-mapped_file_region::mapped_file_region(int fd, mapmode mode, size_t length,
-                                       uint64_t offset, std::error_code &ec)
-    : Size(length), Mapping(), Mode(mode) {
-  (void)Mode;
-  ec = init(fd, offset, mode);
-  if (ec)
-    Mapping = nullptr;
-}
-
-mapped_file_region::~mapped_file_region() {
-  if (Mapping)
-    ::munmap(Mapping, Size);
-}
-
-size_t mapped_file_region::size() const {
-  assert(Mapping && "Mapping failed but used anyway!");
-  return Size;
-}
-
-char *mapped_file_region::data() const {
-  assert(Mapping && "Mapping failed but used anyway!");
-  return reinterpret_cast<char*>(Mapping);
-}
-
-const char *mapped_file_region::const_data() const {
-  assert(Mapping && "Mapping failed but used anyway!");
-  return reinterpret_cast<const char*>(Mapping);
-}
-
-int mapped_file_region::alignment() {
-  return ::sysconf(_SC_PAGE_SIZE);
-}
-
-std::error_code detail::directory_iterator_construct(detail::DirIterState &it,
-                                                     StringRef path,
-                                                     bool follow_symlinks) {
-  SmallString<128> path_null(path);
-  DIR *directory = ::opendir(path_null.c_str());
-  if (!directory)
-    return std::error_code(errno, std::generic_category());
-
-  it.IterationHandle = reinterpret_cast<intptr_t>(directory);
-  // Add something for replace_filename to replace.
-  path::append(path_null, ".");
-  it.CurrentEntry = directory_entry(path_null.str(), follow_symlinks);
-  return directory_iterator_increment(it);
-}
-
-std::error_code detail::directory_iterator_destruct(detail::DirIterState &it) {
-  if (it.IterationHandle)
-    ::closedir(reinterpret_cast<DIR *>(it.IterationHandle));
-  it.IterationHandle = 0;
-  it.CurrentEntry = directory_entry();
-  return std::error_code();
-}
-
-static file_type direntType(dirent* Entry) {
-  // Most platforms provide the file type in the dirent: Linux/BSD/Mac.
-  // The DTTOIF macro lets us reuse our status -> type conversion.
-  return typeForMode(DTTOIF(Entry->d_type));
-}
-
-std::error_code detail::directory_iterator_increment(detail::DirIterState &It) {
-  errno = 0;
-  dirent *CurDir = ::readdir(reinterpret_cast<DIR *>(It.IterationHandle));
-  if (CurDir == nullptr && errno != 0) {
-    return std::error_code(errno, std::generic_category());
-  } else if (CurDir != nullptr) {
-    StringRef Name(CurDir->d_name);
-    if ((Name.size() == 1 && Name[0] == '.') ||
-        (Name.size() == 2 && Name[0] == '.' && Name[1] == '.'))
-      return directory_iterator_increment(It);
-    It.CurrentEntry.replace_filename(Name, direntType(CurDir));
-  } else
-    return directory_iterator_destruct(It);
-
-  return std::error_code();
-}
-
-ErrorOr<basic_file_status> directory_entry::status() const {
-  file_status s;
-  if (auto EC = fs::status(Path, s, FollowSymlinks))
-    return EC;
-  return s;
-}
-
-#if !defined(F_GETPATH)
-static bool hasProcSelfFD() {
-  // If we have a /proc filesystem mounted, we can quickly establish the
-  // real name of the file with readlink
-  static const bool Result = (::access("/proc/self/fd", R_OK) == 0);
-  return Result;
-}
-#endif
-
-static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags,
-                           FileAccess Access) {
-  int Result = 0;
-  if (Access == FA_Read)
-    Result |= O_RDONLY;
-  else if (Access == FA_Write)
-    Result |= O_WRONLY;
-  else if (Access == (FA_Read | FA_Write))
-    Result |= O_RDWR;
-
-  // This is for compatibility with old code that assumed F_Append implied
-  // would open an existing file.  See Windows/Path.inc for a longer comment.
-  if (Flags & F_Append)
-    Disp = CD_OpenAlways;
-
-  if (Disp == CD_CreateNew) {
-    Result |= O_CREAT; // Create if it doesn't exist.
-    Result |= O_EXCL;  // Fail if it does.
-  } else if (Disp == CD_CreateAlways) {
-    Result |= O_CREAT; // Create if it doesn't exist.
-    Result |= O_TRUNC; // Truncate if it does.
-  } else if (Disp == CD_OpenAlways) {
-    Result |= O_CREAT; // Create if it doesn't exist.
-  } else if (Disp == CD_OpenExisting) {
-    // Nothing special, just don't add O_CREAT and we get these semantics.
-  }
-
-  if (Flags & F_Append)
-    Result |= O_APPEND;
-
-#ifdef O_CLOEXEC
-  if (!(Flags & OF_ChildInherit))
-    Result |= O_CLOEXEC;
-#endif
-
-  return Result;
-}
-
-std::error_code openFile(const Twine &Name, int &ResultFD,
-                         CreationDisposition Disp, FileAccess Access,
-                         OpenFlags Flags, unsigned Mode) {
-  int OpenFlags = nativeOpenFlags(Disp, Flags, Access);
-
-  SmallString<128> Storage;
-  StringRef P = Name.toNullTerminatedStringRef(Storage);
-  // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal
-  // when open is overloaded, such as in Bionic.
-  auto Open = [&]() { return ::open(P.begin(), OpenFlags, Mode); };
-  if ((ResultFD = sys::RetryAfterSignal(-1, Open)) < 0)
-    return std::error_code(errno, std::generic_category());
-#ifndef O_CLOEXEC
-  if (!(Flags & OF_ChildInherit)) {
-    int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC);
-    (void)r;
-    assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed");
-  }
-#endif
-  return std::error_code();
-}
-
-Expected<int> openNativeFile(const Twine &Name, CreationDisposition Disp,
-                             FileAccess Access, OpenFlags Flags,
-                             unsigned Mode) {
-
-  int FD;
-  std::error_code EC = openFile(Name, FD, Disp, Access, Flags, Mode);
-  if (EC)
-    return errorCodeToError(EC);
-  return FD;
-}
-
-std::error_code openFileForRead(const Twine &Name, int &ResultFD,
-                                OpenFlags Flags,
-                                SmallVectorImpl<char> *RealPath) {
-  std::error_code EC =
-      openFile(Name, ResultFD, CD_OpenExisting, FA_Read, Flags, 0666);
-  if (EC)
-    return EC;
-
-  // Attempt to get the real name of the file, if the user asked
-  if(!RealPath)
-    return std::error_code();
-  RealPath->clear();
-#if defined(F_GETPATH)
-  // When F_GETPATH is available, it is the quickest way to get
-  // the real path name.
-  char Buffer[MAXPATHLEN];
-  if (::fcntl(ResultFD, F_GETPATH, Buffer) != -1)
-    RealPath->append(Buffer, Buffer + strlen(Buffer));
-#else
-  char Buffer[PATH_MAX];
-  if (hasProcSelfFD()) {
-    char ProcPath[64];
-    snprintf(ProcPath, sizeof(ProcPath), "/proc/self/fd/%d", ResultFD);
-    ssize_t CharCount = ::readlink(ProcPath, Buffer, sizeof(Buffer));
-    if (CharCount > 0)
-      RealPath->append(Buffer, Buffer + CharCount);
-  } else {
-    SmallString<128> Storage;
-    StringRef P = Name.toNullTerminatedStringRef(Storage);
-
-    // Use ::realpath to get the real path name
-    if (::realpath(P.begin(), Buffer) != nullptr)
-      RealPath->append(Buffer, Buffer + strlen(Buffer));
-  }
-#endif
-  return std::error_code();
-}
-
-Expected<file_t> openNativeFileForRead(const Twine &Name, OpenFlags Flags,
-                                       SmallVectorImpl<char> *RealPath) {
-  file_t ResultFD;
-  std::error_code EC = openFileForRead(Name, ResultFD, Flags, RealPath);
-  if (EC)
-    return errorCodeToError(EC);
-  return ResultFD;
-}
-
-void closeFile(file_t &F) {
-  ::close(F);
-  F = kInvalidFile;
-}
-
-} // end namespace fs
-
-namespace path {
-
-bool home_directory(SmallVectorImpl<char> &result) {
-  char *RequestedDir = getenv("HOME");
-  if (!RequestedDir) {
-    struct passwd *pw = getpwuid(getuid());
-    if (pw && pw->pw_dir)
-      RequestedDir = pw->pw_dir;
-  }
-  if (!RequestedDir)
-    return false;
-
-  result.clear();
-  result.append(RequestedDir, RequestedDir + strlen(RequestedDir));
-  return true;
-}
-
-static bool getDarwinConfDir(bool TempDir, SmallVectorImpl<char> &Result) {
-  #if defined(_CS_DARWIN_USER_TEMP_DIR) && defined(_CS_DARWIN_USER_CACHE_DIR)
-  // On Darwin, use DARWIN_USER_TEMP_DIR or DARWIN_USER_CACHE_DIR.
-  // macros defined in <unistd.h> on darwin >= 9
-  int ConfName = TempDir ? _CS_DARWIN_USER_TEMP_DIR
-                         : _CS_DARWIN_USER_CACHE_DIR;
-  size_t ConfLen = confstr(ConfName, nullptr, 0);
-  if (ConfLen > 0) {
-    do {
-      Result.resize(ConfLen);
-      ConfLen = confstr(ConfName, Result.data(), Result.size());
-    } while (ConfLen > 0 && ConfLen != Result.size());
-
-    if (ConfLen > 0) {
-      assert(Result.back() == 0);
-      Result.pop_back();
-      return true;
-    }
-
-    Result.clear();
-  }
-  #endif
-  return false;
-}
-
-static const char *getEnvTempDir() {
-  // Check whether the temporary directory is specified by an environment
-  // variable.
-  const char *EnvironmentVariables[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"};
-  for (const char *Env : EnvironmentVariables) {
-    if (const char *Dir = std::getenv(Env))
-      return Dir;
-  }
-
-  return nullptr;
-}
-
-static const char *getDefaultTempDir(bool ErasedOnReboot) {
-#ifdef P_tmpdir
-  if ((bool)P_tmpdir)
-    return P_tmpdir;
-#endif
-
-  if (ErasedOnReboot)
-    return "/tmp";
-  return "/var/tmp";
-}
-
-void system_temp_directory(bool ErasedOnReboot, SmallVectorImpl<char> &Result) {
-  Result.clear();
-
-  if (ErasedOnReboot) {
-    // There is no env variable for the cache directory.
-    if (const char *RequestedDir = getEnvTempDir()) {
-      Result.append(RequestedDir, RequestedDir + strlen(RequestedDir));
-      return;
-    }
-  }
-
-  if (getDarwinConfDir(ErasedOnReboot, Result))
-    return;
-
-  const char *RequestedDir = getDefaultTempDir(ErasedOnReboot);
-  Result.append(RequestedDir, RequestedDir + strlen(RequestedDir));
-}
-
-} // end namespace path
-} // end namespace sys
-} // end namespace wpi
diff --git a/wpiutil/src/main/native/cpp/llvm/Windows/Path.inc b/wpiutil/src/main/native/cpp/llvm/Windows/Path.inc
deleted file mode 100644
index e9b5280..0000000
--- a/wpiutil/src/main/native/cpp/llvm/Windows/Path.inc
+++ /dev/null
@@ -1,971 +0,0 @@
-//===- llvm/Support/Windows/Path.inc - Windows Path Impl --------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the Windows specific implementation of the Path API.
-//
-//===----------------------------------------------------------------------===//
-
-//===----------------------------------------------------------------------===//
-//=== WARNING: Implementation here must contain only generic Windows code that
-//===          is guaranteed to work on *all* Windows variants.
-//===----------------------------------------------------------------------===//
-
-#include "wpi/STLExtras.h"
-#include "wpi/ConvertUTF.h"
-#include "wpi/WindowsError.h"
-#include <fcntl.h>
-#include <io.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-// These two headers must be included last, and make sure shlobj is required
-// after Windows.h to make sure it picks up our definition of _WIN32_WINNT
-#include "WindowsSupport.h"
-#include <shellapi.h>
-#include <shlobj.h>
-
-#undef max
-
-// MinGW doesn't define this.
-#ifndef _ERRNO_T_DEFINED
-#define _ERRNO_T_DEFINED
-typedef int errno_t;
-#endif
-
-#ifdef _MSC_VER
-# pragma comment(lib, "shell32.lib")
-# pragma comment(lib, "ole32.lib")
-#pragma warning(push)
-#pragma warning(disable: 4244 4267 4146)
-#endif
-
-using namespace wpi;
-
-using wpi::sys::windows::UTF8ToUTF16;
-using wpi::sys::windows::CurCPToUTF16;
-using wpi::sys::windows::UTF16ToUTF8;
-using wpi::sys::path::widenPath;
-
-static bool is_separator(const wchar_t value) {
-  switch (value) {
-  case L'\\':
-  case L'/':
-    return true;
-  default:
-    return false;
-  }
-}
-
-namespace wpi {
-namespace sys  {
-namespace path {
-
-// Convert a UTF-8 path to UTF-16.  Also, if the absolute equivalent of the
-// path is longer than CreateDirectory can tolerate, make it absolute and
-// prefixed by '\\?\'.
-std::error_code widenPath(const Twine &Path8,
-                          SmallVectorImpl<wchar_t> &Path16) {
-  const size_t MaxDirLen = MAX_PATH - 12; // Must leave room for 8.3 filename.
-
-  // Several operations would convert Path8 to SmallString; more efficient to
-  // do it once up front.
-  SmallString<128> Path8Str;
-  Path8.toVector(Path8Str);
-
-  // If we made this path absolute, how much longer would it get?
-  size_t CurPathLen;
-  if (wpi::sys::path::is_absolute(Twine(Path8Str)))
-    CurPathLen = 0; // No contribution from current_path needed.
-  else {
-    CurPathLen = ::GetCurrentDirectoryW(0, NULL);
-    if (CurPathLen == 0)
-      return mapWindowsError(::GetLastError());
-  }
-
-  // Would the absolute path be longer than our limit?
-  if ((Path8Str.size() + CurPathLen) >= MaxDirLen &&
-      !Path8Str.startswith("\\\\?\\")) {
-    SmallString<2*MAX_PATH> FullPath("\\\\?\\");
-    if (CurPathLen) {
-      SmallString<80> CurPath;
-      if (std::error_code EC = wpi::sys::fs::current_path(CurPath))
-        return EC;
-      FullPath.append(CurPath);
-    }
-    // Traverse the requested path, canonicalizing . and .. (because the \\?\
-    // prefix is documented to treat them as real components).  Ignore
-    // separators, which can be returned from the iterator if the path has a
-    // drive name.  We don't need to call native() on the result since append()
-    // always attaches preferred_separator.
-    for (wpi::sys::path::const_iterator I = wpi::sys::path::begin(Path8Str),
-                                         E = wpi::sys::path::end(Path8Str);
-                                         I != E; ++I) {
-      if (I->size() == 1 && is_separator((*I)[0]))
-        continue;
-      if (I->size() == 1 && *I == ".")
-        continue;
-      if (I->size() == 2 && *I == "..")
-        wpi::sys::path::remove_filename(FullPath);
-      else
-        wpi::sys::path::append(FullPath, *I);
-    }
-    return UTF8ToUTF16(FullPath, Path16);
-  }
-
-  // Just use the caller's original path.
-  return UTF8ToUTF16(Path8Str, Path16);
-}
-} // end namespace path
-
-namespace fs {
-
-const file_t kInvalidFile = INVALID_HANDLE_VALUE;
-
-UniqueID file_status::getUniqueID() const {
-  // The file is uniquely identified by the volume serial number along
-  // with the 64-bit file identifier.
-  uint64_t FileID = (static_cast<uint64_t>(FileIndexHigh) << 32ULL) |
-                    static_cast<uint64_t>(FileIndexLow);
-
-  return UniqueID(VolumeSerialNumber, FileID);
-}
-
-TimePoint<> basic_file_status::getLastAccessedTime() const {
-  FILETIME Time;
-  Time.dwLowDateTime = LastAccessedTimeLow;
-  Time.dwHighDateTime = LastAccessedTimeHigh;
-  return toTimePoint(Time);
-}
-
-TimePoint<> basic_file_status::getLastModificationTime() const {
-  FILETIME Time;
-  Time.dwLowDateTime = LastWriteTimeLow;
-  Time.dwHighDateTime = LastWriteTimeHigh;
-  return toTimePoint(Time);
-}
-
-uint32_t file_status::getLinkCount() const {
-  return NumLinks;
-}
-
-std::error_code current_path(SmallVectorImpl<char> &result) {
-  SmallVector<wchar_t, MAX_PATH> cur_path;
-  DWORD len = MAX_PATH;
-
-  do {
-    cur_path.reserve(len);
-    len = ::GetCurrentDirectoryW(cur_path.capacity(), cur_path.data());
-
-    // A zero return value indicates a failure other than insufficient space.
-    if (len == 0)
-      return mapWindowsError(::GetLastError());
-
-    // If there's insufficient space, the len returned is larger than the len
-    // given.
-  } while (len > cur_path.capacity());
-
-  // On success, GetCurrentDirectoryW returns the number of characters not
-  // including the null-terminator.
-  cur_path.set_size(len);
-  return UTF16ToUTF8(cur_path.begin(), cur_path.size(), result);
-}
-
-static std::error_code realPathFromHandle(HANDLE H,
-                                          SmallVectorImpl<wchar_t> &Buffer) {
-  DWORD CountChars = ::GetFinalPathNameByHandleW(
-      H, Buffer.begin(), Buffer.capacity() - 1, FILE_NAME_NORMALIZED);
-  if (CountChars > Buffer.capacity()) {
-    // The buffer wasn't big enough, try again.  In this case the return value
-    // *does* indicate the size of the null terminator.
-    Buffer.reserve(CountChars);
-    CountChars = ::GetFinalPathNameByHandleW(
-        H, Buffer.data(), Buffer.capacity() - 1, FILE_NAME_NORMALIZED);
-  }
-  if (CountChars == 0)
-    return mapWindowsError(GetLastError());
-  Buffer.set_size(CountChars);
-  return std::error_code();
-}
-
-static std::error_code realPathFromHandle(HANDLE H,
-                                          SmallVectorImpl<char> &RealPath) {
-  RealPath.clear();
-  SmallVector<wchar_t, MAX_PATH> Buffer;
-  if (std::error_code EC = realPathFromHandle(H, Buffer))
-    return EC;
-
-  const wchar_t *Data = Buffer.data();
-  DWORD CountChars = Buffer.size();
-  if (CountChars >= 4) {
-    if (0 == ::memcmp(Data, L"\\\\?\\", 8)) {
-      CountChars -= 4;
-      Data += 4;
-    }
-  }
-
-  // Convert the result from UTF-16 to UTF-8.
-  return UTF16ToUTF8(Data, CountChars, RealPath);
-}
-
-static std::error_code setDeleteDisposition(HANDLE Handle, bool Delete) {
-  FILE_DISPOSITION_INFO Disposition;
-  Disposition.DeleteFile = Delete;
-  if (!SetFileInformationByHandle(Handle, FileDispositionInfo, &Disposition,
-                                  sizeof(Disposition)))
-    return mapWindowsError(::GetLastError());
-  return std::error_code();
-}
-
-std::error_code access(const Twine &Path, AccessMode Mode) {
-  SmallVector<wchar_t, 128> PathUtf16;
-
-  if (std::error_code EC = widenPath(Path, PathUtf16))
-    return EC;
-
-  DWORD Attributes = ::GetFileAttributesW(PathUtf16.begin());
-
-  if (Attributes == INVALID_FILE_ATTRIBUTES) {
-    // See if the file didn't actually exist.
-    DWORD LastError = ::GetLastError();
-    if (LastError != ERROR_FILE_NOT_FOUND &&
-        LastError != ERROR_PATH_NOT_FOUND)
-      return mapWindowsError(LastError);
-    return errc::no_such_file_or_directory;
-  }
-
-  if (Mode == AccessMode::Write && (Attributes & FILE_ATTRIBUTE_READONLY))
-    return errc::permission_denied;
-
-  return std::error_code();
-}
-
-bool equivalent(file_status A, file_status B) {
-  assert(status_known(A) && status_known(B));
-  return A.FileIndexHigh         == B.FileIndexHigh &&
-         A.FileIndexLow          == B.FileIndexLow &&
-         A.FileSizeHigh          == B.FileSizeHigh &&
-         A.FileSizeLow           == B.FileSizeLow &&
-         A.LastAccessedTimeHigh  == B.LastAccessedTimeHigh &&
-         A.LastAccessedTimeLow   == B.LastAccessedTimeLow &&
-         A.LastWriteTimeHigh     == B.LastWriteTimeHigh &&
-         A.LastWriteTimeLow      == B.LastWriteTimeLow &&
-         A.VolumeSerialNumber    == B.VolumeSerialNumber;
-}
-
-std::error_code equivalent(const Twine &A, const Twine &B, bool &result) {
-  file_status fsA, fsB;
-  if (std::error_code ec = status(A, fsA))
-    return ec;
-  if (std::error_code ec = status(B, fsB))
-    return ec;
-  result = equivalent(fsA, fsB);
-  return std::error_code();
-}
-
-static bool isReservedName(StringRef path) {
-  // This list of reserved names comes from MSDN, at:
-  // http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
-  static const char *const sReservedNames[] = { "nul", "con", "prn", "aux",
-                                                "com1", "com2", "com3", "com4",
-                                                "com5", "com6", "com7", "com8",
-                                                "com9", "lpt1", "lpt2", "lpt3",
-                                                "lpt4", "lpt5", "lpt6", "lpt7",
-                                                "lpt8", "lpt9" };
-
-  // First, check to see if this is a device namespace, which always
-  // starts with \\.\, since device namespaces are not legal file paths.
-  if (path.startswith("\\\\.\\"))
-    return true;
-
-  // Then compare against the list of ancient reserved names.
-  for (size_t i = 0; i < array_lengthof(sReservedNames); ++i) {
-    if (path.equals_lower(sReservedNames[i]))
-      return true;
-  }
-
-  // The path isn't what we consider reserved.
-  return false;
-}
-
-static file_type file_type_from_attrs(DWORD Attrs) {
-  return (Attrs & FILE_ATTRIBUTE_DIRECTORY) ? file_type::directory_file
-                                            : file_type::regular_file;
-}
-
-static perms perms_from_attrs(DWORD Attrs) {
-  return (Attrs & FILE_ATTRIBUTE_READONLY) ? (all_read | all_exe) : all_all;
-}
-
-static std::error_code getStatus(HANDLE FileHandle, file_status &Result) {
-  if (FileHandle == INVALID_HANDLE_VALUE)
-    goto handle_status_error;
-
-  switch (::GetFileType(FileHandle)) {
-  default:
-    Result = file_status(file_type::type_unknown);
-    return std::error_code();
-  case FILE_TYPE_UNKNOWN: {
-    DWORD Err = ::GetLastError();
-    if (Err != NO_ERROR)
-      return mapWindowsError(Err);
-    Result = file_status(file_type::type_unknown);
-    return std::error_code();
-  }
-  case FILE_TYPE_DISK:
-    break;
-  case FILE_TYPE_CHAR:
-    Result = file_status(file_type::character_file);
-    return std::error_code();
-  case FILE_TYPE_PIPE:
-    Result = file_status(file_type::fifo_file);
-    return std::error_code();
-  }
-
-  BY_HANDLE_FILE_INFORMATION Info;
-  if (!::GetFileInformationByHandle(FileHandle, &Info))
-    goto handle_status_error;
-
-  Result = file_status(
-      file_type_from_attrs(Info.dwFileAttributes),
-      perms_from_attrs(Info.dwFileAttributes), Info.nNumberOfLinks,
-      Info.ftLastAccessTime.dwHighDateTime, Info.ftLastAccessTime.dwLowDateTime,
-      Info.ftLastWriteTime.dwHighDateTime, Info.ftLastWriteTime.dwLowDateTime,
-      Info.dwVolumeSerialNumber, Info.nFileSizeHigh, Info.nFileSizeLow,
-      Info.nFileIndexHigh, Info.nFileIndexLow);
-  return std::error_code();
-
-handle_status_error:
-  DWORD LastError = ::GetLastError();
-  if (LastError == ERROR_FILE_NOT_FOUND ||
-      LastError == ERROR_PATH_NOT_FOUND)
-    Result = file_status(file_type::file_not_found);
-  else if (LastError == ERROR_SHARING_VIOLATION)
-    Result = file_status(file_type::type_unknown);
-  else
-    Result = file_status(file_type::status_error);
-  return mapWindowsError(LastError);
-}
-
-std::error_code status(const Twine &path, file_status &result, bool Follow) {
-  SmallString<128> path_storage;
-  SmallVector<wchar_t, 128> path_utf16;
-
-  StringRef path8 = path.toStringRef(path_storage);
-  if (isReservedName(path8)) {
-    result = file_status(file_type::character_file);
-    return std::error_code();
-  }
-
-  if (std::error_code ec = widenPath(path8, path_utf16))
-    return ec;
-
-  DWORD attr = ::GetFileAttributesW(path_utf16.begin());
-  if (attr == INVALID_FILE_ATTRIBUTES)
-    return getStatus(INVALID_HANDLE_VALUE, result);
-
-  DWORD Flags = FILE_FLAG_BACKUP_SEMANTICS;
-  // Handle reparse points.
-  if (!Follow && (attr & FILE_ATTRIBUTE_REPARSE_POINT))
-    Flags |= FILE_FLAG_OPEN_REPARSE_POINT;
-
-  ScopedFileHandle h(
-      ::CreateFileW(path_utf16.begin(), 0, // Attributes only.
-                    FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
-                    NULL, OPEN_EXISTING, Flags, 0));
-  if (!h)
-    return getStatus(INVALID_HANDLE_VALUE, result);
-
-  return getStatus(h, result);
-}
-
-std::error_code status(int FD, file_status &Result) {
-  HANDLE FileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(FD));
-  return getStatus(FileHandle, Result);
-}
-
-std::error_code mapped_file_region::init(int FD, uint64_t Offset,
-                                         mapmode Mode) {
-  this->Mode = Mode;
-  HANDLE OrigFileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(FD));
-  if (OrigFileHandle == INVALID_HANDLE_VALUE)
-    return make_error_code(errc::bad_file_descriptor);
-
-  DWORD flprotect;
-  switch (Mode) {
-  case readonly:  flprotect = PAGE_READONLY; break;
-  case readwrite: flprotect = PAGE_READWRITE; break;
-  case priv:      flprotect = PAGE_WRITECOPY; break;
-  }
-
-  HANDLE FileMappingHandle =
-      ::CreateFileMappingW(OrigFileHandle, 0, flprotect,
-                           Hi_32(Size),
-                           Lo_32(Size),
-                           0);
-  if (FileMappingHandle == NULL) {
-    std::error_code ec = mapWindowsError(GetLastError());
-    return ec;
-  }
-
-  DWORD dwDesiredAccess;
-  switch (Mode) {
-  case readonly:  dwDesiredAccess = FILE_MAP_READ; break;
-  case readwrite: dwDesiredAccess = FILE_MAP_WRITE; break;
-  case priv:      dwDesiredAccess = FILE_MAP_COPY; break;
-  }
-  Mapping = ::MapViewOfFile(FileMappingHandle,
-                            dwDesiredAccess,
-                            Offset >> 32,
-                            Offset & 0xffffffff,
-                            Size);
-  if (Mapping == NULL) {
-    std::error_code ec = mapWindowsError(GetLastError());
-    ::CloseHandle(FileMappingHandle);
-    return ec;
-  }
-
-  if (Size == 0) {
-    MEMORY_BASIC_INFORMATION mbi;
-    SIZE_T Result = VirtualQuery(Mapping, &mbi, sizeof(mbi));
-    if (Result == 0) {
-      std::error_code ec = mapWindowsError(GetLastError());
-      ::UnmapViewOfFile(Mapping);
-      ::CloseHandle(FileMappingHandle);
-      return ec;
-    }
-    Size = mbi.RegionSize;
-  }
-
-  // Close the file mapping handle, as it's kept alive by the file mapping. But
-  // neither the file mapping nor the file mapping handle keep the file handle
-  // alive, so we need to keep a reference to the file in case all other handles
-  // are closed and the file is deleted, which may cause invalid data to be read
-  // from the file.
-  ::CloseHandle(FileMappingHandle);
-  if (!::DuplicateHandle(::GetCurrentProcess(), OrigFileHandle,
-                         ::GetCurrentProcess(), &FileHandle, 0, 0,
-                         DUPLICATE_SAME_ACCESS)) {
-    std::error_code ec = mapWindowsError(GetLastError());
-    ::UnmapViewOfFile(Mapping);
-    return ec;
-  }
-
-  return std::error_code();
-}
-
-mapped_file_region::mapped_file_region(int fd, mapmode mode, size_t length,
-                                       uint64_t offset, std::error_code &ec)
-    : Size(length), Mapping() {
-  ec = init(fd, offset, mode);
-  if (ec)
-    Mapping = 0;
-}
-
-static bool hasFlushBufferKernelBug() {
-  static bool Ret{GetWindowsOSVersion() < wpi::VersionTuple(10, 0, 0, 17763)};
-  return Ret;
-}
-
-static bool isEXE(StringRef Magic) {
-  static const char PEMagic[] = {'P', 'E', '\0', '\0'};
-  if (Magic.startswith(StringRef("MZ")) && Magic.size() >= 0x3c + 4) {
-    uint32_t off = read32le(Magic.data() + 0x3c);
-    // PE/COFF file, either EXE or DLL.
-    if (Magic.substr(off).startswith(StringRef(PEMagic, sizeof(PEMagic))))
-      return true;
-  }
-  return false;
-}
-
-mapped_file_region::~mapped_file_region() {
-  if (Mapping) {
-
-    bool Exe = isEXE(StringRef((char *)Mapping, Size));
-
-    ::UnmapViewOfFile(Mapping);
-
-    if (Mode == mapmode::readwrite && Exe && hasFlushBufferKernelBug()) {
-      // There is a Windows kernel bug, the exact trigger conditions of which
-      // are not well understood.  When triggered, dirty pages are not properly
-      // flushed and subsequent process's attempts to read a file can return
-      // invalid data.  Calling FlushFileBuffers on the write handle is
-      // sufficient to ensure that this bug is not triggered.
-      // The bug only occurs when writing an executable and executing it right
-      // after, under high I/O pressure.
-      ::FlushFileBuffers(FileHandle);
-    }
-
-    ::CloseHandle(FileHandle);
-  }
-}
-
-size_t mapped_file_region::size() const {
-  assert(Mapping && "Mapping failed but used anyway!");
-  return Size;
-}
-
-char *mapped_file_region::data() const {
-  assert(Mapping && "Mapping failed but used anyway!");
-  return reinterpret_cast<char*>(Mapping);
-}
-
-const char *mapped_file_region::const_data() const {
-  assert(Mapping && "Mapping failed but used anyway!");
-  return reinterpret_cast<const char*>(Mapping);
-}
-
-int mapped_file_region::alignment() {
-  SYSTEM_INFO SysInfo;
-  ::GetSystemInfo(&SysInfo);
-  return SysInfo.dwAllocationGranularity;
-}
-
-static basic_file_status status_from_find_data(WIN32_FIND_DATAW *FindData) {
-  return basic_file_status(file_type_from_attrs(FindData->dwFileAttributes),
-                           perms_from_attrs(FindData->dwFileAttributes),
-                           FindData->ftLastAccessTime.dwHighDateTime,
-                           FindData->ftLastAccessTime.dwLowDateTime,
-                           FindData->ftLastWriteTime.dwHighDateTime,
-                           FindData->ftLastWriteTime.dwLowDateTime,
-                           FindData->nFileSizeHigh, FindData->nFileSizeLow);
-}
-
-std::error_code detail::directory_iterator_construct(detail::DirIterState &IT,
-                                                     StringRef Path,
-                                                     bool FollowSymlinks) {
-  SmallVector<wchar_t, 128> PathUTF16;
-
-  if (std::error_code EC = widenPath(Path, PathUTF16))
-    return EC;
-
-  // Convert path to the format that Windows is happy with.
-  if (PathUTF16.size() > 0 &&
-      !is_separator(PathUTF16[Path.size() - 1]) &&
-      PathUTF16[Path.size() - 1] != L':') {
-    PathUTF16.push_back(L'\\');
-    PathUTF16.push_back(L'*');
-  } else {
-    PathUTF16.push_back(L'*');
-  }
-
-  //  Get the first directory entry.
-  WIN32_FIND_DATAW FirstFind;
-  ScopedFindHandle FindHandle(::FindFirstFileExW(
-      c_str(PathUTF16), FindExInfoBasic, &FirstFind, FindExSearchNameMatch,
-      NULL, FIND_FIRST_EX_LARGE_FETCH));
-  if (!FindHandle)
-    return mapWindowsError(::GetLastError());
-
-  size_t FilenameLen = ::wcslen(FirstFind.cFileName);
-  while ((FilenameLen == 1 && FirstFind.cFileName[0] == L'.') ||
-         (FilenameLen == 2 && FirstFind.cFileName[0] == L'.' &&
-                              FirstFind.cFileName[1] == L'.'))
-    if (!::FindNextFileW(FindHandle, &FirstFind)) {
-      DWORD LastError = ::GetLastError();
-      // Check for end.
-      if (LastError == ERROR_NO_MORE_FILES)
-        return detail::directory_iterator_destruct(IT);
-      return mapWindowsError(LastError);
-    } else
-      FilenameLen = ::wcslen(FirstFind.cFileName);
-
-  // Construct the current directory entry.
-  SmallString<128> DirectoryEntryNameUTF8;
-  if (std::error_code EC =
-          UTF16ToUTF8(FirstFind.cFileName, ::wcslen(FirstFind.cFileName),
-                      DirectoryEntryNameUTF8))
-    return EC;
-
-  IT.IterationHandle = intptr_t(FindHandle.take());
-  SmallString<128> DirectoryEntryPath(Path);
-  path::append(DirectoryEntryPath, DirectoryEntryNameUTF8);
-  IT.CurrentEntry =
-      directory_entry(DirectoryEntryPath, FollowSymlinks,
-                      file_type_from_attrs(FirstFind.dwFileAttributes),
-                      status_from_find_data(&FirstFind));
-
-  return std::error_code();
-}
-
-std::error_code detail::directory_iterator_destruct(detail::DirIterState &IT) {
-  if (IT.IterationHandle != 0)
-    // Closes the handle if it's valid.
-    ScopedFindHandle close(HANDLE(IT.IterationHandle));
-  IT.IterationHandle = 0;
-  IT.CurrentEntry = directory_entry();
-  return std::error_code();
-}
-
-std::error_code detail::directory_iterator_increment(detail::DirIterState &IT) {
-  WIN32_FIND_DATAW FindData;
-  if (!::FindNextFileW(HANDLE(IT.IterationHandle), &FindData)) {
-    DWORD LastError = ::GetLastError();
-    // Check for end.
-    if (LastError == ERROR_NO_MORE_FILES)
-      return detail::directory_iterator_destruct(IT);
-    return mapWindowsError(LastError);
-  }
-
-  size_t FilenameLen = ::wcslen(FindData.cFileName);
-  if ((FilenameLen == 1 && FindData.cFileName[0] == L'.') ||
-      (FilenameLen == 2 && FindData.cFileName[0] == L'.' &&
-                           FindData.cFileName[1] == L'.'))
-    return directory_iterator_increment(IT);
-
-  SmallString<128> DirectoryEntryPathUTF8;
-  if (std::error_code EC =
-          UTF16ToUTF8(FindData.cFileName, ::wcslen(FindData.cFileName),
-                      DirectoryEntryPathUTF8))
-    return EC;
-
-  IT.CurrentEntry.replace_filename(
-      Twine(DirectoryEntryPathUTF8),
-      file_type_from_attrs(FindData.dwFileAttributes),
-      status_from_find_data(&FindData));
-  return std::error_code();
-}
-
-ErrorOr<basic_file_status> directory_entry::status() const {
-  return Status;
-}
-
-static std::error_code nativeFileToFd(Expected<HANDLE> H, int &ResultFD,
-                                      OpenFlags Flags) {
-  int CrtOpenFlags = 0;
-  if (Flags & OF_Append)
-    CrtOpenFlags |= _O_APPEND;
-
-  if (Flags & OF_Text)
-    CrtOpenFlags |= _O_TEXT;
-
-  ResultFD = -1;
-  if (!H)
-    return errorToErrorCode(H.takeError());
-
-  ResultFD = ::_open_osfhandle(intptr_t(*H), CrtOpenFlags);
-  if (ResultFD == -1) {
-    ::CloseHandle(*H);
-    return mapWindowsError(ERROR_INVALID_HANDLE);
-  }
-  return std::error_code();
-}
-
-static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) {
-  // This is a compatibility hack.  Really we should respect the creation
-  // disposition, but a lot of old code relied on the implicit assumption that
-  // OF_Append implied it would open an existing file.  Since the disposition is
-  // now explicit and defaults to CD_CreateAlways, this assumption would cause
-  // any usage of OF_Append to append to a new file, even if the file already
-  // existed.  A better solution might have two new creation dispositions:
-  // CD_AppendAlways and CD_AppendNew.  This would also address the problem of
-  // OF_Append being used on a read-only descriptor, which doesn't make sense.
-  if (Flags & OF_Append)
-    return OPEN_ALWAYS;
-
-  switch (Disp) {
-  case CD_CreateAlways:
-    return CREATE_ALWAYS;
-  case CD_CreateNew:
-    return CREATE_NEW;
-  case CD_OpenAlways:
-    return OPEN_ALWAYS;
-  case CD_OpenExisting:
-    return OPEN_EXISTING;
-  }
-  wpi_unreachable("unreachable!");
-}
-
-static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) {
-  DWORD Result = 0;
-  if (Access & FA_Read)
-    Result |= GENERIC_READ;
-  if (Access & FA_Write)
-    Result |= GENERIC_WRITE;
-  if (Flags & OF_Delete)
-    Result |= DELETE;
-  if (Flags & OF_UpdateAtime)
-    Result |= FILE_WRITE_ATTRIBUTES;
-  return Result;
-}
-
-static std::error_code openNativeFileInternal(const Twine &Name,
-                                              file_t &ResultFile, DWORD Disp,
-                                              DWORD Access, DWORD Flags,
-                                              bool Inherit = false) {
-  SmallVector<wchar_t, 128> PathUTF16;
-  if (std::error_code EC = widenPath(Name, PathUTF16))
-    return EC;
-
-  SECURITY_ATTRIBUTES SA;
-  SA.nLength = sizeof(SA);
-  SA.lpSecurityDescriptor = nullptr;
-  SA.bInheritHandle = Inherit;
-
-  HANDLE H =
-      ::CreateFileW(PathUTF16.begin(), Access,
-                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA,
-                    Disp, Flags, NULL);
-  if (H == INVALID_HANDLE_VALUE) {
-    DWORD LastError = ::GetLastError();
-    std::error_code EC = mapWindowsError(LastError);
-    // Provide a better error message when trying to open directories.
-    // This only runs if we failed to open the file, so there is probably
-    // no performances issues.
-    if (LastError != ERROR_ACCESS_DENIED)
-      return EC;
-    if (is_directory(Name))
-      return make_error_code(errc::is_a_directory);
-    return EC;
-  }
-  ResultFile = H;
-  return std::error_code();
-}
-
-Expected<file_t> openNativeFile(const Twine &Name, CreationDisposition Disp,
-                                FileAccess Access, OpenFlags Flags,
-                                unsigned Mode) {
-  // Verify that we don't have both "append" and "excl".
-  assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) &&
-         "Cannot specify both 'CreateNew' and 'Append' file creation flags!");
-
-  DWORD NativeDisp = nativeDisposition(Disp, Flags);
-  DWORD NativeAccess = nativeAccess(Access, Flags);
-
-  bool Inherit = false;
-  if (Flags & OF_ChildInherit)
-    Inherit = true;
-
-  file_t Result;
-  std::error_code EC = openNativeFileInternal(
-      Name, Result, NativeDisp, NativeAccess, FILE_ATTRIBUTE_NORMAL, Inherit);
-  if (EC)
-    return errorCodeToError(EC);
-
-  if (Flags & OF_UpdateAtime) {
-    FILETIME FileTime;
-    SYSTEMTIME SystemTime;
-    GetSystemTime(&SystemTime);
-    if (SystemTimeToFileTime(&SystemTime, &FileTime) == 0 ||
-        SetFileTime(Result, NULL, &FileTime, NULL) == 0) {
-      DWORD LastError = ::GetLastError();
-      ::CloseHandle(Result);
-      return errorCodeToError(mapWindowsError(LastError));
-    }
-  }
-
-  if (Flags & OF_Delete) {
-    if ((EC = setDeleteDisposition(Result, true))) {
-      ::CloseHandle(Result);
-      return errorCodeToError(EC);
-    }
-  }
-  return Result;
-}
-
-std::error_code openFile(const Twine &Name, int &ResultFD,
-                         CreationDisposition Disp, FileAccess Access,
-                         OpenFlags Flags, unsigned int Mode) {
-  Expected<file_t> Result = openNativeFile(Name, Disp, Access, Flags);
-  if (!Result)
-    return errorToErrorCode(Result.takeError());
-
-  return nativeFileToFd(*Result, ResultFD, Flags);
-}
-
-static std::error_code directoryRealPath(const Twine &Name,
-                                         SmallVectorImpl<char> &RealPath) {
-  file_t File;
-  std::error_code EC = openNativeFileInternal(
-      Name, File, OPEN_EXISTING, GENERIC_READ, FILE_FLAG_BACKUP_SEMANTICS);
-  if (EC)
-    return EC;
-
-  EC = realPathFromHandle(File, RealPath);
-  ::CloseHandle(File);
-  return EC;
-}
-
-std::error_code openFileForRead(const Twine &Name, int &ResultFD,
-                                OpenFlags Flags,
-                                SmallVectorImpl<char> *RealPath) {
-  Expected<HANDLE> NativeFile = openNativeFileForRead(Name, Flags, RealPath);
-  return nativeFileToFd(std::move(NativeFile), ResultFD, OF_None);
-}
-
-Expected<file_t> openNativeFileForRead(const Twine &Name, OpenFlags Flags,
-                                       SmallVectorImpl<char> *RealPath) {
-  Expected<file_t> Result =
-      openNativeFile(Name, CD_OpenExisting, FA_Read, Flags);
-
-  // Fetch the real name of the file, if the user asked
-  if (Result && RealPath)
-    realPathFromHandle(*Result, *RealPath);
-
-  return Result;
-}
-
-void closeFile(file_t &F) {
-  ::CloseHandle(F);
-  F = kInvalidFile;
-}
-
-
-} // end namespace fs
-
-namespace path {
-static bool getKnownFolderPath(KNOWNFOLDERID folderId,
-                               SmallVectorImpl<char> &result) {
-  wchar_t *path = nullptr;
-  if (::SHGetKnownFolderPath(folderId, KF_FLAG_CREATE, nullptr, &path) != S_OK)
-    return false;
-
-  bool ok = !UTF16ToUTF8(path, ::wcslen(path), result);
-  ::CoTaskMemFree(path);
-  return ok;
-}
-
-bool home_directory(SmallVectorImpl<char> &result) {
-  return getKnownFolderPath(FOLDERID_Profile, result);
-}
-
-static bool getTempDirEnvVar(const wchar_t *Var, SmallVectorImpl<char> &Res) {
-  SmallVector<wchar_t, 1024> Buf;
-  size_t Size = 1024;
-  do {
-    Buf.reserve(Size);
-    Size = GetEnvironmentVariableW(Var, Buf.data(), Buf.capacity());
-    if (Size == 0)
-      return false;
-
-    // Try again with larger buffer.
-  } while (Size > Buf.capacity());
-  Buf.set_size(Size);
-
-  return !windows::UTF16ToUTF8(Buf.data(), Size, Res);
-}
-
-static bool getTempDirEnvVar(SmallVectorImpl<char> &Res) {
-  const wchar_t *EnvironmentVariables[] = {L"TMP", L"TEMP", L"USERPROFILE"};
-  for (auto *Env : EnvironmentVariables) {
-    if (getTempDirEnvVar(Env, Res))
-      return true;
-  }
-  return false;
-}
-
-void system_temp_directory(bool ErasedOnReboot, SmallVectorImpl<char> &Result) {
-  (void)ErasedOnReboot;
-  Result.clear();
-
-  // Check whether the temporary directory is specified by an environment var.
-  // This matches GetTempPath logic to some degree. GetTempPath is not used
-  // directly as it cannot handle evn var longer than 130 chars on Windows 7
-  // (fixed on Windows 8).
-  if (getTempDirEnvVar(Result)) {
-    assert(!Result.empty() && "Unexpected empty path");
-    native(Result); // Some Unix-like shells use Unix path separator in $TMP.
-    fs::make_absolute(Result); // Make it absolute if not already.
-    return;
-  }
-
-  // Fall back to a system default.
-  const char *DefaultResult = "C:\\Temp";
-  Result.append(DefaultResult, DefaultResult + strlen(DefaultResult));
-}
-} // end namespace path
-
-namespace windows {
-std::error_code CodePageToUTF16(unsigned codepage,
-                                wpi::StringRef original,
-                                wpi::SmallVectorImpl<wchar_t> &utf16) {
-  if (!original.empty()) {
-    int len = ::MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, original.begin(),
-                                    original.size(), utf16.begin(), 0);
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-
-    utf16.reserve(len + 1);
-    utf16.set_size(len);
-
-    len = ::MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, original.begin(),
-                                original.size(), utf16.begin(), utf16.size());
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-  }
-
-  // Make utf16 null terminated.
-  utf16.push_back(0);
-  utf16.pop_back();
-
-  return std::error_code();
-}
-
-std::error_code UTF8ToUTF16(wpi::StringRef utf8,
-                            wpi::SmallVectorImpl<wchar_t> &utf16) {
-  return CodePageToUTF16(CP_UTF8, utf8, utf16);
-}
-
-std::error_code CurCPToUTF16(wpi::StringRef curcp,
-                            wpi::SmallVectorImpl<wchar_t> &utf16) {
-  return CodePageToUTF16(CP_ACP, curcp, utf16);
-}
-
-static
-std::error_code UTF16ToCodePage(unsigned codepage, const wchar_t *utf16,
-                                size_t utf16_len,
-                                wpi::SmallVectorImpl<char> &converted) {
-  if (utf16_len) {
-    // Get length.
-    int len = ::WideCharToMultiByte(codepage, 0, utf16, utf16_len, converted.begin(),
-                                    0, NULL, NULL);
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-
-    converted.reserve(len);
-    converted.set_size(len);
-
-    // Now do the actual conversion.
-    len = ::WideCharToMultiByte(codepage, 0, utf16, utf16_len, converted.data(),
-                                converted.size(), NULL, NULL);
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-  }
-
-  // Make the new string null terminated.
-  converted.push_back(0);
-  converted.pop_back();
-
-  return std::error_code();
-}
-
-std::error_code UTF16ToUTF8(const wchar_t *utf16, size_t utf16_len,
-                            wpi::SmallVectorImpl<char> &utf8) {
-  return UTF16ToCodePage(CP_UTF8, utf16, utf16_len, utf8);
-}
-
-std::error_code UTF16ToCurCP(const wchar_t *utf16, size_t utf16_len,
-                             wpi::SmallVectorImpl<char> &curcp) {
-  return UTF16ToCodePage(CP_ACP, utf16, utf16_len, curcp);
-}
-
-} // end namespace windows
-} // end namespace sys
-} // end namespace wpi
-
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
diff --git a/wpiutil/src/main/native/cpp/llvm/Windows/WindowsSupport.h b/wpiutil/src/main/native/cpp/llvm/Windows/WindowsSupport.h
index d830e33..7307337 100644
--- a/wpiutil/src/main/native/cpp/llvm/Windows/WindowsSupport.h
+++ b/wpiutil/src/main/native/cpp/llvm/Windows/WindowsSupport.h
@@ -36,13 +36,12 @@
 
 #include "wpi/SmallVector.h"
 #include "wpi/StringExtras.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
 #include "wpi/Chrono.h"
 #include "wpi/Compiler.h"
 #include "wpi/VersionTuple.h"
 #include <cassert>
 #include <string>
+#include <string_view>
 #include <system_error>
 #define WIN32_NO_STATUS
 #include <windows.h>
diff --git a/wpiutil/src/main/native/cpp/llvm/raw_ostream.cpp b/wpiutil/src/main/native/cpp/llvm/raw_ostream.cpp
index 9f2942c..622e0bf 100644
--- a/wpiutil/src/main/native/cpp/llvm/raw_ostream.cpp
+++ b/wpiutil/src/main/native/cpp/llvm/raw_ostream.cpp
@@ -16,17 +16,14 @@
 #endif
 
 #include "wpi/raw_ostream.h"
-#include "wpi/STLExtras.h"
 #include "wpi/SmallString.h"
 #include "wpi/SmallVector.h"
 #include "wpi/StringExtras.h"
 #include "wpi/Compiler.h"
 #include "wpi/ErrorHandling.h"
-#include "wpi/FileSystem.h"
-#include "wpi/Format.h"
 #include "wpi/MathExtras.h"
-#include "wpi/NativeFormatting.h"
 #include "wpi/WindowsError.h"
+#include "wpi/fs.h"
 #include <algorithm>
 #include <cctype>
 #include <cerrno>
@@ -67,6 +64,14 @@
 
 using namespace wpi;
 
+namespace {
+// Find the length of an array.
+template <class T, std::size_t N>
+constexpr inline size_t array_lengthof(T (&)[N]) {
+  return N;
+}
+}  // namespace
+
 raw_ostream::~raw_ostream() {
   // raw_ostream's subclasses should take care to flush the buffer
   // in their destructors.
@@ -113,32 +118,7 @@
   assert(OutBufStart <= OutBufEnd && "Invalid size!");
 }
 
-raw_ostream &raw_ostream::operator<<(unsigned long N) {
-  write_integer(*this, static_cast<uint64_t>(N), 0, IntegerStyle::Integer);
-  return *this;
-}
-
-raw_ostream &raw_ostream::operator<<(long N) {
-  write_integer(*this, static_cast<int64_t>(N), 0, IntegerStyle::Integer);
-  return *this;
-}
-
-raw_ostream &raw_ostream::operator<<(unsigned long long N) {
-  write_integer(*this, static_cast<uint64_t>(N), 0, IntegerStyle::Integer);
-  return *this;
-}
-
-raw_ostream &raw_ostream::operator<<(long long N) {
-  write_integer(*this, static_cast<int64_t>(N), 0, IntegerStyle::Integer);
-  return *this;
-}
-
-raw_ostream &raw_ostream::write_hex(unsigned long long N) {
-  wpi::write_hex(*this, N, HexPrintStyle::Lower);
-  return *this;
-}
-
-raw_ostream &raw_ostream::write_escaped(StringRef Str,
+raw_ostream &raw_ostream::write_escaped(std::string_view Str,
                                         bool UseHexEscapes) {
   for (unsigned char c : Str) {
     switch (c) {
@@ -178,16 +158,6 @@
   return *this;
 }
 
-raw_ostream &raw_ostream::operator<<(const void *P) {
-  wpi::write_hex(*this, (uintptr_t)P, HexPrintStyle::PrefixLower);
-  return *this;
-}
-
-raw_ostream &raw_ostream::operator<<(double N) {
-  wpi::write_double(*this, N, FloatStyle::Exponent);
-  return *this;
-}
-
 void raw_ostream::flush_nonempty() {
   assert(OutBufCur > OutBufStart && "Invalid call to flush_nonempty.");
   size_t Length = OutBufCur - OutBufStart;
@@ -277,170 +247,6 @@
   OutBufCur += Size;
 }
 
-// Formatted output.
-raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
-  // If we have more than a few bytes left in our output buffer, try
-  // formatting directly onto its end.
-  size_t NextBufferSize = 127;
-  size_t BufferBytesLeft = OutBufEnd - OutBufCur;
-  if (BufferBytesLeft > 3) {
-    size_t BytesUsed = Fmt.print(OutBufCur, BufferBytesLeft);
-
-    // Common case is that we have plenty of space.
-    if (BytesUsed <= BufferBytesLeft) {
-      OutBufCur += BytesUsed;
-      return *this;
-    }
-
-    // Otherwise, we overflowed and the return value tells us the size to try
-    // again with.
-    NextBufferSize = BytesUsed;
-  }
-
-  // If we got here, we didn't have enough space in the output buffer for the
-  // string.  Try printing into a SmallVector that is resized to have enough
-  // space.  Iterate until we win.
-  SmallVector<char, 128> V;
-
-  while (true) {
-    V.resize(NextBufferSize);
-
-    // Try formatting into the SmallVector.
-    size_t BytesUsed = Fmt.print(V.data(), NextBufferSize);
-
-    // If BytesUsed fit into the vector, we win.
-    if (BytesUsed <= NextBufferSize)
-      return write(V.data(), BytesUsed);
-
-    // Otherwise, try again with a new size.
-    assert(BytesUsed > NextBufferSize && "Didn't grow buffer!?");
-    NextBufferSize = BytesUsed;
-  }
-}
-
-raw_ostream &raw_ostream::operator<<(const FormattedString &FS) {
-  if (FS.Str.size() >= FS.Width || FS.Justify == FormattedString::JustifyNone) {
-    this->operator<<(FS.Str);
-    return *this;
-  }
-  const size_t Difference = FS.Width - FS.Str.size();
-  switch (FS.Justify) {
-  case FormattedString::JustifyLeft:
-    this->operator<<(FS.Str);
-    this->indent(Difference);
-    break;
-  case FormattedString::JustifyRight:
-    this->indent(Difference);
-    this->operator<<(FS.Str);
-    break;
-  case FormattedString::JustifyCenter: {
-    int PadAmount = Difference / 2;
-    this->indent(PadAmount);
-    this->operator<<(FS.Str);
-    this->indent(Difference - PadAmount);
-    break;
-  }
-  default:
-    wpi_unreachable("Bad Justification");
-  }
-  return *this;
-}
-
-raw_ostream &raw_ostream::operator<<(const FormattedNumber &FN) {
-  if (FN.Hex) {
-    HexPrintStyle Style;
-    if (FN.Upper && FN.HexPrefix)
-      Style = HexPrintStyle::PrefixUpper;
-    else if (FN.Upper && !FN.HexPrefix)
-      Style = HexPrintStyle::Upper;
-    else if (!FN.Upper && FN.HexPrefix)
-      Style = HexPrintStyle::PrefixLower;
-    else
-      Style = HexPrintStyle::Lower;
-    wpi::write_hex(*this, FN.HexValue, Style, FN.Width);
-  } else {
-    wpi::SmallString<16> Buffer;
-    wpi::raw_svector_ostream Stream(Buffer);
-    wpi::write_integer(Stream, FN.DecValue, 0, IntegerStyle::Integer);
-    if (Buffer.size() < FN.Width)
-      indent(FN.Width - Buffer.size());
-    (*this) << Buffer;
-  }
-  return *this;
-}
-
-raw_ostream &raw_ostream::operator<<(const FormattedBytes &FB) {
-  if (FB.Bytes.empty())
-    return *this;
-
-  size_t LineIndex = 0;
-  auto Bytes = FB.Bytes;
-  const size_t Size = Bytes.size();
-  HexPrintStyle HPS = FB.Upper ? HexPrintStyle::Upper : HexPrintStyle::Lower;
-  uint64_t OffsetWidth = 0;
-  if (FB.FirstByteOffset.has_value()) {
-    // Figure out how many nibbles are needed to print the largest offset
-    // represented by this data set, so that we can align the offset field
-    // to the right width.
-    size_t Lines = Size / FB.NumPerLine;
-    uint64_t MaxOffset = *FB.FirstByteOffset + Lines * FB.NumPerLine;
-    unsigned Power = 0;
-    if (MaxOffset > 0)
-      Power = wpi::Log2_64_Ceil(MaxOffset);
-    OffsetWidth = std::max<uint64_t>(4, wpi::alignTo(Power, 4) / 4);
-  }
-
-  // The width of a block of data including all spaces for group separators.
-  unsigned NumByteGroups =
-      alignTo(FB.NumPerLine, FB.ByteGroupSize) / FB.ByteGroupSize;
-  unsigned BlockCharWidth = FB.NumPerLine * 2 + NumByteGroups - 1;
-
-  while (!Bytes.empty()) {
-    indent(FB.IndentLevel);
-
-    if (FB.FirstByteOffset.has_value()) {
-      uint64_t Offset = FB.FirstByteOffset.value();
-      wpi::write_hex(*this, Offset + LineIndex, HPS, OffsetWidth);
-      *this << ": ";
-    }
-
-    auto Line = Bytes.take_front(FB.NumPerLine);
-
-    size_t CharsPrinted = 0;
-    // Print the hex bytes for this line in groups
-    for (size_t I = 0; I < Line.size(); ++I, CharsPrinted += 2) {
-      if (I && (I % FB.ByteGroupSize) == 0) {
-        ++CharsPrinted;
-        *this << " ";
-      }
-      wpi::write_hex(*this, Line[I], HPS, 2);
-    }
-
-    if (FB.ASCII) {
-      // Print any spaces needed for any bytes that we didn't print on this
-      // line so that the ASCII bytes are correctly aligned.
-      assert(BlockCharWidth >= CharsPrinted);
-      indent(BlockCharWidth - CharsPrinted + 2);
-      *this << "|";
-
-      // Print the ASCII char values for each byte on this line
-      for (uint8_t Byte : Line) {
-        if (isPrint(Byte))
-          *this << static_cast<char>(Byte);
-        else
-          *this << '.';
-      }
-      *this << '|';
-    }
-
-    Bytes = Bytes.drop_front(Line.size());
-    LineIndex += Line.size();
-    if (LineIndex < Size)
-      *this << '\n';
-  }
-  return *this;
-}
-
 template <char C>
 static raw_ostream &write_padding(raw_ostream &OS, unsigned NumChars) {
   static const char Chars[] = {C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C,
@@ -475,21 +281,13 @@
 void raw_ostream::anchor() {}
 
 //===----------------------------------------------------------------------===//
-//  Formatted Output
-//===----------------------------------------------------------------------===//
-
-// Out of line virtual method.
-void format_object_base::home() {
-}
-
-//===----------------------------------------------------------------------===//
 //  raw_fd_ostream
 //===----------------------------------------------------------------------===//
 
-static int getFD(StringRef Filename, std::error_code &EC,
-                 sys::fs::CreationDisposition Disp, sys::fs::FileAccess Access,
-                 sys::fs::OpenFlags Flags) {
-  assert((Access & sys::fs::FA_Write) &&
+static int getFD(std::string_view Filename, std::error_code &EC,
+                 fs::CreationDisposition Disp, fs::FileAccess Access,
+                 fs::OpenFlags Flags) {
+  assert((Access & fs::FA_Write) &&
          "Cannot make a raw_ostream from a read-only descriptor!");
 
   // Handle "-" as stdout. Note that when we do this, we consider ourself
@@ -498,7 +296,7 @@
     EC = std::error_code();
     // If user requested binary then put stdout into binary mode if
     // possible.
-    if (!(Flags & sys::fs::OF_Text)) {
+    if (!(Flags & fs::OF_Text)) {
 #if defined(_WIN32)
       _setmode(_fileno(stdout), _O_BINARY);
 #endif
@@ -506,39 +304,43 @@
     return STDOUT_FILENO;
   }
 
-  int FD;
-  if (Access & sys::fs::FA_Read)
-    EC = sys::fs::openFileForReadWrite(Filename, FD, Disp, Flags);
-  else
-    EC = sys::fs::openFileForWrite(Filename, FD, Disp, Flags);
+  fs::file_t F;
+  if (Access & fs::FA_Read) {
+    F = fs::OpenFileForReadWrite(fs::path{std::string_view{Filename.data(), Filename.size()}}, EC, Disp, Flags);
+  } else {
+    F = fs::OpenFileForWrite(fs::path{std::string_view{Filename.data(), Filename.size()}}, EC, Disp, Flags);
+  }
+  if (EC)
+    return -1;
+  int FD = fs::FileToFd(F, EC, Flags);
   if (EC)
     return -1;
 
   return FD;
 }
 
-raw_fd_ostream::raw_fd_ostream(StringRef Filename, std::error_code &EC)
-    : raw_fd_ostream(Filename, EC, sys::fs::CD_CreateAlways, sys::fs::FA_Write,
-                     sys::fs::OF_None) {}
+raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC)
+    : raw_fd_ostream(Filename, EC, fs::CD_CreateAlways, fs::FA_Write,
+                     fs::OF_None) {}
 
-raw_fd_ostream::raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                               sys::fs::CreationDisposition Disp)
-    : raw_fd_ostream(Filename, EC, Disp, sys::fs::FA_Write, sys::fs::OF_None) {}
+raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                               fs::CreationDisposition Disp)
+    : raw_fd_ostream(Filename, EC, Disp, fs::FA_Write, fs::OF_None) {}
 
-raw_fd_ostream::raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                               sys::fs::FileAccess Access)
-    : raw_fd_ostream(Filename, EC, sys::fs::CD_CreateAlways, Access,
-                     sys::fs::OF_None) {}
+raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                               fs::FileAccess Access)
+    : raw_fd_ostream(Filename, EC, fs::CD_CreateAlways, Access,
+                     fs::OF_None) {}
 
-raw_fd_ostream::raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                               sys::fs::OpenFlags Flags)
-    : raw_fd_ostream(Filename, EC, sys::fs::CD_CreateAlways, sys::fs::FA_Write,
+raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                               fs::OpenFlags Flags)
+    : raw_fd_ostream(Filename, EC, fs::CD_CreateAlways, fs::FA_Write,
                      Flags) {}
 
-raw_fd_ostream::raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                               sys::fs::CreationDisposition Disp,
-                               sys::fs::FileAccess Access,
-                               sys::fs::OpenFlags Flags)
+raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                               fs::CreationDisposition Disp,
+                               fs::FileAccess Access,
+                               fs::OpenFlags Flags)
     : raw_fd_ostream(getFD(Filename, EC, Disp, Access, Flags), true) {}
 
 /// FD is the file descriptor that this writes to.  If ShouldClose is true, this
@@ -618,7 +420,7 @@
 // the input is UTF-8 or transcode from the local codepage to UTF-8 before
 // quoting it. If they don't, this may mess up the encoding, but this is still
 // probably the best compromise we can make.
-static bool write_console_impl(int FD, StringRef Data) {
+static bool write_console_impl(int FD, std::string_view Data) {
   SmallVector<wchar_t, 256> WideText;
 
   // Fall back to ::write if it wasn't valid UTF-8.
@@ -661,7 +463,7 @@
   // If this is a Windows console device, try re-encoding from UTF-8 to UTF-16
   // and using WriteConsoleW. If that fails, fall back to plain write().
   if (IsWindowsConsole)
-    if (write_console_impl(FD, StringRef(Ptr, Size)))
+    if (write_console_impl(FD, std::string_view(Ptr, Size)))
       return;
 #endif
 
@@ -783,7 +585,7 @@
 raw_ostream &wpi::outs() {
   // Set buffer settings to model stdout behavior.
   std::error_code EC;
-  static raw_fd_ostream* S = new raw_fd_ostream("-", EC, sys::fs::F_None);
+  static raw_fd_ostream* S = new raw_fd_ostream("-", EC, fs::F_None);
   assert(!EC);
   return *S;
 }
diff --git a/wpiutil/src/main/native/cpp/mpack.cpp b/wpiutil/src/main/native/cpp/mpack.cpp
new file mode 100644
index 0000000..fbcb6a4
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/mpack.cpp
@@ -0,0 +1,7251 @@
+/**
+ * The MIT License (MIT)
+ * 
+ * Copyright (c) 2015-2021 Nicholas Fraser and the MPack authors
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ */
+
+/*
+ * This is the MPack 1.1 amalgamation package.
+ *
+ * http://github.com/ludocode/mpack
+ */
+
+#define MPACK_INTERNAL 1
+#define MPACK_EMIT_INLINE_DEFS 0
+
+#include "wpi/mpack.h"
+
+
+/* mpack/mpack-platform.c.c */
+
+
+// We define MPACK_EMIT_INLINE_DEFS and include mpack.h to emit
+// standalone definitions of all (non-static) inline functions in MPack.
+
+#define MPACK_INTERNAL 1
+#define MPACK_EMIT_INLINE_DEFS 0
+
+/* #include "mpack-platform.h" */
+/* #include "mpack.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+namespace mpack {
+
+#if MPACK_DEBUG
+
+#if MPACK_STDIO
+void mpack_assert_fail_format(const char* format, ...) {
+    char buffer[512];
+    va_list args;
+    va_start(args, format);
+    vsnprintf(buffer, sizeof(buffer), format, args);
+    va_end(args);
+    buffer[sizeof(buffer) - 1] = 0;
+    mpack_assert_fail_wrapper(buffer);
+}
+
+void mpack_break_hit_format(const char* format, ...) {
+    char buffer[512];
+    va_list args;
+    va_start(args, format);
+    vsnprintf(buffer, sizeof(buffer), format, args);
+    va_end(args);
+    buffer[sizeof(buffer) - 1] = 0;
+    mpack_break_hit(buffer);
+}
+#endif
+
+#if !MPACK_CUSTOM_ASSERT
+void mpack_assert_fail(const char* message) {
+    MPACK_UNUSED(message);
+
+    #if MPACK_STDIO
+    fprintf(stderr, "%s\n", message);
+    #endif
+}
+#endif
+
+// We split the assert failure from the wrapper so that a
+// custom assert function can return.
+void mpack_assert_fail_wrapper(const char* message) {
+
+    #ifdef MPACK_GCOV
+    // gcov marks even __builtin_unreachable() as an uncovered line. this
+    // silences it.
+    (mpack_assert_fail(message), __builtin_unreachable());
+
+    #else
+    mpack_assert_fail(message);
+
+    // mpack_assert_fail() is not supposed to return. in case it does, we
+    // abort.
+
+    #if !MPACK_NO_BUILTINS
+    #if defined(__GNUC__) || defined(__clang__)
+    __builtin_trap();
+    #elif defined(WIN32)
+    __debugbreak();
+    #endif
+    #endif
+
+    #if (defined(__GNUC__) || defined(__clang__)) && !MPACK_NO_BUILTINS
+    __builtin_abort();
+    #elif MPACK_STDLIB
+    abort();
+    #endif
+
+    MPACK_UNREACHABLE;
+    #endif
+}
+
+#if !MPACK_CUSTOM_BREAK
+
+// If we have a custom assert handler, break wraps it by default.
+// This allows users of MPack to only implement mpack_assert_fail() without
+// having to worry about the difference between assert and break.
+//
+// MPACK_CUSTOM_BREAK is available to define a separate break handler
+// (which is needed by the unit test suite), but this is not offered in
+// mpack-config.h for simplicity.
+
+#if MPACK_CUSTOM_ASSERT
+void mpack_break_hit(const char* message) {
+    mpack_assert_fail_wrapper(message);
+}
+#else
+void mpack_break_hit(const char* message) {
+    MPACK_UNUSED(message);
+
+    #if MPACK_STDIO
+    fprintf(stderr, "%s\n", message);
+    #endif
+
+    #if defined(__GNUC__) || defined(__clang__) && !MPACK_NO_BUILTINS
+    __builtin_trap();
+    #elif defined(WIN32) && !MPACK_NO_BUILTINS
+    __debugbreak();
+    #elif MPACK_STDLIB
+    abort();
+    #endif
+}
+#endif
+
+#endif
+
+#endif
+
+
+
+// The below are adapted from the C wikibook:
+//     https://en.wikibooks.org/wiki/C_Programming/Strings
+
+#ifndef mpack_memcmp
+int mpack_memcmp(const void* s1, const void* s2, size_t n) {
+     const unsigned char *us1 = (const unsigned char *) s1;
+     const unsigned char *us2 = (const unsigned char *) s2;
+     while (n-- != 0) {
+         if (*us1 != *us2)
+             return (*us1 < *us2) ? -1 : +1;
+         us1++;
+         us2++;
+     }
+     return 0;
+}
+#endif
+
+#ifndef mpack_memcpy
+void* mpack_memcpy(void* MPACK_RESTRICT s1, const void* MPACK_RESTRICT s2, size_t n) {
+    char* MPACK_RESTRICT dst = (char *)s1;
+    const char* MPACK_RESTRICT src = (const char *)s2;
+    while (n-- != 0)
+        *dst++ = *src++;
+    return s1;
+}
+#endif
+
+#ifndef mpack_memmove
+void* mpack_memmove(void* s1, const void* s2, size_t n) {
+    char *p1 = (char *)s1;
+    const char *p2 = (const char *)s2;
+    if (p2 < p1 && p1 < p2 + n) {
+        p2 += n;
+        p1 += n;
+        while (n-- != 0)
+            *--p1 = *--p2;
+    } else
+        while (n-- != 0)
+            *p1++ = *p2++;
+    return s1;
+}
+#endif
+
+#ifndef mpack_memset
+void* mpack_memset(void* s, int c, size_t n) {
+    unsigned char *us = (unsigned char *)s;
+    unsigned char uc = (unsigned char)c;
+    while (n-- != 0)
+        *us++ = uc;
+    return s;
+}
+#endif
+
+#ifndef mpack_strlen
+size_t mpack_strlen(const char* s) {
+    const char* p = s;
+    while (*p != '\0')
+        p++;
+    return (size_t)(p - s);
+}
+#endif
+
+
+
+#if defined(MPACK_MALLOC) && !defined(MPACK_REALLOC)
+void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) {
+    if (new_size == 0) {
+        if (old_ptr)
+            MPACK_FREE(old_ptr);
+        return NULL;
+    }
+
+    void* new_ptr = MPACK_MALLOC(new_size);
+    if (new_ptr == NULL)
+        return NULL;
+
+    mpack_memcpy(new_ptr, old_ptr, used_size);
+    MPACK_FREE(old_ptr);
+    return new_ptr;
+}
+#endif
+
+MPACK_SILENCE_WARNINGS_END
+
+/* mpack/mpack-common.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-common.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+
+const char* mpack_error_to_string(mpack_error_t error) {
+    #if MPACK_STRINGS
+    switch (error) {
+        #define MPACK_ERROR_STRING_CASE(e) case e: return #e
+        MPACK_ERROR_STRING_CASE(mpack_ok);
+        MPACK_ERROR_STRING_CASE(mpack_error_io);
+        MPACK_ERROR_STRING_CASE(mpack_error_invalid);
+        MPACK_ERROR_STRING_CASE(mpack_error_unsupported);
+        MPACK_ERROR_STRING_CASE(mpack_error_type);
+        MPACK_ERROR_STRING_CASE(mpack_error_too_big);
+        MPACK_ERROR_STRING_CASE(mpack_error_memory);
+        MPACK_ERROR_STRING_CASE(mpack_error_bug);
+        MPACK_ERROR_STRING_CASE(mpack_error_data);
+        MPACK_ERROR_STRING_CASE(mpack_error_eof);
+        #undef MPACK_ERROR_STRING_CASE
+    }
+    mpack_assert(0, "unrecognized error %i", (int)error);
+    return "(unknown mpack_error_t)";
+    #else
+    MPACK_UNUSED(error);
+    return "";
+    #endif
+}
+
+const char* mpack_type_to_string(mpack_type_t type) {
+    #if MPACK_STRINGS
+    switch (type) {
+        #define MPACK_TYPE_STRING_CASE(e) case e: return #e
+        MPACK_TYPE_STRING_CASE(mpack_type_missing);
+        MPACK_TYPE_STRING_CASE(mpack_type_nil);
+        MPACK_TYPE_STRING_CASE(mpack_type_bool);
+        MPACK_TYPE_STRING_CASE(mpack_type_float);
+        MPACK_TYPE_STRING_CASE(mpack_type_double);
+        MPACK_TYPE_STRING_CASE(mpack_type_int);
+        MPACK_TYPE_STRING_CASE(mpack_type_uint);
+        MPACK_TYPE_STRING_CASE(mpack_type_str);
+        MPACK_TYPE_STRING_CASE(mpack_type_bin);
+        MPACK_TYPE_STRING_CASE(mpack_type_array);
+        MPACK_TYPE_STRING_CASE(mpack_type_map);
+        #if MPACK_EXTENSIONS
+        MPACK_TYPE_STRING_CASE(mpack_type_ext);
+        #endif
+        #undef MPACK_TYPE_STRING_CASE
+    }
+    mpack_assert(0, "unrecognized type %i", (int)type);
+    return "(unknown mpack_type_t)";
+    #else
+    MPACK_UNUSED(type);
+    return "";
+    #endif
+}
+
+int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right) {
+
+    // positive numbers may be stored as int; convert to uint
+    if (left.type == mpack_type_int && left.v.i >= 0) {
+        left.type = mpack_type_uint;
+        left.v.u = (uint64_t)left.v.i;
+    }
+    if (right.type == mpack_type_int && right.v.i >= 0) {
+        right.type = mpack_type_uint;
+        right.v.u = (uint64_t)right.v.i;
+    }
+
+    if (left.type != right.type)
+        return ((int)left.type < (int)right.type) ? -1 : 1;
+
+    switch (left.type) {
+        case mpack_type_missing: // fallthrough
+        case mpack_type_nil:
+            return 0;
+
+        case mpack_type_bool:
+            return (int)left.v.b - (int)right.v.b;
+
+        case mpack_type_int:
+            if (left.v.i == right.v.i)
+                return 0;
+            return (left.v.i < right.v.i) ? -1 : 1;
+
+        case mpack_type_uint:
+            if (left.v.u == right.v.u)
+                return 0;
+            return (left.v.u < right.v.u) ? -1 : 1;
+
+        case mpack_type_array:
+        case mpack_type_map:
+            if (left.v.n == right.v.n)
+                return 0;
+            return (left.v.n < right.v.n) ? -1 : 1;
+
+        case mpack_type_str:
+        case mpack_type_bin:
+            if (left.v.l == right.v.l)
+                return 0;
+            return (left.v.l < right.v.l) ? -1 : 1;
+
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+            if (left.exttype == right.exttype) {
+                if (left.v.l == right.v.l)
+                    return 0;
+                return (left.v.l < right.v.l) ? -1 : 1;
+            }
+            return (int)left.exttype - (int)right.exttype;
+        #endif
+
+        // floats should not normally be compared for equality. we compare
+        // with memcmp() to silence compiler warnings, but this will return
+        // equal if both are NaNs with the same representation (though we may
+        // want this, for instance if you are for some bizarre reason using
+        // floats as map keys.) i'm not sure what the right thing to
+        // do is here. check for NaN first? always return false if the type
+        // is float? use operator== and pragmas to silence compiler warning?
+        // please send me your suggestions.
+        // note also that we don't convert floats to doubles, so when this is
+        // used for ordering purposes, all floats are ordered before all
+        // doubles.
+        case mpack_type_float:
+            return mpack_memcmp(&left.v.f, &right.v.f, sizeof(left.v.f));
+        case mpack_type_double:
+            return mpack_memcmp(&left.v.d, &right.v.d, sizeof(left.v.d));
+    }
+
+    mpack_assert(0, "unrecognized type %i", (int)left.type);
+    return false;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+static char mpack_hex_char(uint8_t hex_value) {
+    // Older compilers (e.g. GCC 4.4.7) promote the result of this ternary to
+    // int and warn under -Wconversion, so we have to cast it back to char.
+    return (char)((hex_value < 10) ? (char)('0' + hex_value) : (char)('a' + (hex_value - 10)));
+}
+
+static void mpack_tag_debug_complete_bin_ext(mpack_tag_t tag, size_t string_length, char* buffer, size_t buffer_size,
+        const char* prefix, size_t prefix_size)
+{
+    // If at any point in this function we run out of space in the buffer, we
+    // bail out. The outer tag print wrapper will make sure we have a
+    // null-terminator.
+
+    if (string_length == 0 || string_length >= buffer_size)
+        return;
+    buffer += string_length;
+    buffer_size -= string_length;
+
+    size_t total = mpack_tag_bytes(&tag);
+    if (total == 0) {
+        strncpy(buffer, ">", buffer_size);
+        return;
+    }
+
+    strncpy(buffer, ": ", buffer_size);
+    if (buffer_size < 2)
+        return;
+    buffer += 2;
+    buffer_size -= 2;
+
+    size_t hex_bytes = 0;
+    size_t i;
+    for (i = 0; i < MPACK_PRINT_BYTE_COUNT && i < prefix_size && buffer_size > 2; ++i) {
+        uint8_t byte = (uint8_t)prefix[i];
+        buffer[0] = mpack_hex_char((uint8_t)(byte >> 4));
+        buffer[1] = mpack_hex_char((uint8_t)(byte & 0xfu));
+        buffer += 2;
+        buffer_size -= 2;
+        ++hex_bytes;
+    }
+
+    if (buffer_size != 0)
+        mpack_snprintf(buffer, buffer_size, "%s>", (total > hex_bytes) ? "..." : "");
+}
+
+static void mpack_tag_debug_pseudo_json_bin(mpack_tag_t tag, char* buffer, size_t buffer_size,
+        const char* prefix, size_t prefix_size)
+{
+    mpack_assert(mpack_tag_type(&tag) == mpack_type_bin);
+    size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<binary data of length %u", tag.v.l);
+    mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size);
+}
+
+#if MPACK_EXTENSIONS
+static void mpack_tag_debug_pseudo_json_ext(mpack_tag_t tag, char* buffer, size_t buffer_size,
+        const char* prefix, size_t prefix_size)
+{
+    mpack_assert(mpack_tag_type(&tag) == mpack_type_ext);
+    size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<ext data of type %i and length %u",
+            mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag));
+    mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size);
+}
+#endif
+
+static void mpack_tag_debug_pseudo_json_impl(mpack_tag_t tag, char* buffer, size_t buffer_size,
+        const char* prefix, size_t prefix_size)
+{
+    switch (tag.type) {
+        case mpack_type_missing:
+            mpack_snprintf(buffer, buffer_size, "<missing!>");
+            return;
+        case mpack_type_nil:
+            mpack_snprintf(buffer, buffer_size, "null");
+            return;
+        case mpack_type_bool:
+            mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false");
+            return;
+        case mpack_type_int:
+            mpack_snprintf(buffer, buffer_size, "%" PRIi64, tag.v.i);
+            return;
+        case mpack_type_uint:
+            mpack_snprintf(buffer, buffer_size, "%" PRIu64, tag.v.u);
+            return;
+        case mpack_type_float:
+            #if MPACK_FLOAT
+            mpack_snprintf(buffer, buffer_size, "%f", tag.v.f);
+            #else
+            mpack_snprintf(buffer, buffer_size, "<float>");
+            #endif
+            return;
+        case mpack_type_double:
+            #if MPACK_DOUBLE
+            mpack_snprintf(buffer, buffer_size, "%f", tag.v.d);
+            #else
+            mpack_snprintf(buffer, buffer_size, "<double>");
+            #endif
+            return;
+
+        case mpack_type_str:
+            mpack_snprintf(buffer, buffer_size, "<string of %u bytes>", tag.v.l);
+            return;
+        case mpack_type_bin:
+            mpack_tag_debug_pseudo_json_bin(tag, buffer, buffer_size, prefix, prefix_size);
+            return;
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+            mpack_tag_debug_pseudo_json_ext(tag, buffer, buffer_size, prefix, prefix_size);
+            return;
+        #endif
+
+        case mpack_type_array:
+            mpack_snprintf(buffer, buffer_size, "<array of %u elements>", tag.v.n);
+            return;
+        case mpack_type_map:
+            mpack_snprintf(buffer, buffer_size, "<map of %u key-value pairs>", tag.v.n);
+            return;
+    }
+
+    mpack_snprintf(buffer, buffer_size, "<unknown!>");
+}
+
+void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_size,
+        const char* prefix, size_t prefix_size)
+{
+    mpack_assert(buffer_size > 0, "buffer size cannot be zero!");
+    buffer[0] = 0;
+
+    mpack_tag_debug_pseudo_json_impl(tag, buffer, buffer_size, prefix, prefix_size);
+
+    // We always null-terminate the buffer manually just in case the snprintf()
+    // function doesn't null-terminate when the string doesn't fit.
+    buffer[buffer_size - 1] = 0;
+}
+
+static void mpack_tag_debug_describe_impl(mpack_tag_t tag, char* buffer, size_t buffer_size) {
+    switch (tag.type) {
+        case mpack_type_missing:
+            mpack_snprintf(buffer, buffer_size, "missing");
+            return;
+        case mpack_type_nil:
+            mpack_snprintf(buffer, buffer_size, "nil");
+            return;
+        case mpack_type_bool:
+            mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false");
+            return;
+        case mpack_type_int:
+            mpack_snprintf(buffer, buffer_size, "int %" PRIi64, tag.v.i);
+            return;
+        case mpack_type_uint:
+            mpack_snprintf(buffer, buffer_size, "uint %" PRIu64, tag.v.u);
+            return;
+        case mpack_type_float:
+            #if MPACK_FLOAT
+            mpack_snprintf(buffer, buffer_size, "float %f", tag.v.f);
+            #else
+            mpack_snprintf(buffer, buffer_size, "float");
+            #endif
+            return;
+        case mpack_type_double:
+            #if MPACK_DOUBLE
+            mpack_snprintf(buffer, buffer_size, "double %f", tag.v.d);
+            #else
+            mpack_snprintf(buffer, buffer_size, "double");
+            #endif
+            return;
+        case mpack_type_str:
+            mpack_snprintf(buffer, buffer_size, "str of %u bytes", tag.v.l);
+            return;
+        case mpack_type_bin:
+            mpack_snprintf(buffer, buffer_size, "bin of %u bytes", tag.v.l);
+            return;
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+            mpack_snprintf(buffer, buffer_size, "ext of type %i, %u bytes",
+                    mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag));
+            return;
+        #endif
+        case mpack_type_array:
+            mpack_snprintf(buffer, buffer_size, "array of %u elements", tag.v.n);
+            return;
+        case mpack_type_map:
+            mpack_snprintf(buffer, buffer_size, "map of %u key-value pairs", tag.v.n);
+            return;
+    }
+
+    mpack_snprintf(buffer, buffer_size, "unknown!");
+}
+
+void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size) {
+    mpack_assert(buffer_size > 0, "buffer size cannot be zero!");
+    buffer[0] = 0;
+
+    mpack_tag_debug_describe_impl(tag, buffer, buffer_size);
+
+    // We always null-terminate the buffer manually just in case the snprintf()
+    // function doesn't null-terminate when the string doesn't fit.
+    buffer[buffer_size - 1] = 0;
+}
+#endif
+
+
+
+#if MPACK_READ_TRACKING || MPACK_WRITE_TRACKING
+
+#ifndef MPACK_TRACKING_INITIAL_CAPACITY
+// seems like a reasonable number. we grow by doubling, and it only
+// needs to be as long as the maximum depth of the message.
+#define MPACK_TRACKING_INITIAL_CAPACITY 8
+#endif
+
+mpack_error_t mpack_track_init(mpack_track_t* track) {
+    track->count = 0;
+    track->capacity = MPACK_TRACKING_INITIAL_CAPACITY;
+    track->elements = (mpack_track_element_t*)MPACK_MALLOC(sizeof(mpack_track_element_t) * track->capacity);
+    if (track->elements == NULL)
+        return mpack_error_memory;
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_grow(mpack_track_t* track) {
+    mpack_assert(track->elements, "null track elements!");
+    mpack_assert(track->count == track->capacity, "incorrect growing?");
+
+    size_t new_capacity = track->capacity * 2;
+
+    mpack_track_element_t* new_elements = (mpack_track_element_t*)mpack_realloc(track->elements,
+            sizeof(mpack_track_element_t) * track->count, sizeof(mpack_track_element_t) * new_capacity);
+    if (new_elements == NULL)
+        return mpack_error_memory;
+
+    track->elements = new_elements;
+    track->capacity = new_capacity;
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint32_t count) {
+    mpack_assert(track->elements, "null track elements!");
+    mpack_log("track pushing %s count %i\n", mpack_type_to_string(type), (int)count);
+
+    // grow if needed
+    if (track->count == track->capacity) {
+        mpack_error_t error = mpack_track_grow(track);
+        if (error != mpack_ok)
+            return error;
+    }
+
+    // insert new track
+    track->elements[track->count].type = type;
+    track->elements[track->count].left = count;
+    track->elements[track->count].builder = false;
+    track->elements[track->count].key_needs_value = false;
+    ++track->count;
+    return mpack_ok;
+}
+
+// TODO dedupe this
+mpack_error_t mpack_track_push_builder(mpack_track_t* track, mpack_type_t type) {
+    mpack_assert(track->elements, "null track elements!");
+    mpack_log("track pushing %s builder\n", mpack_type_to_string(type));
+
+    // grow if needed
+    if (track->count == track->capacity) {
+        mpack_error_t error = mpack_track_grow(track);
+        if (error != mpack_ok)
+            return error;
+    }
+
+    // insert new track
+    track->elements[track->count].type = type;
+    track->elements[track->count].left = 0;
+    track->elements[track->count].builder = true;
+    track->elements[track->count].key_needs_value = false;
+    ++track->count;
+    return mpack_ok;
+}
+
+static mpack_error_t mpack_track_pop_impl(mpack_track_t* track, mpack_type_t type, bool builder) {
+    mpack_assert(track->elements, "null track elements!");
+    mpack_log("track popping %s\n", mpack_type_to_string(type));
+
+    if (track->count == 0) {
+        mpack_break("attempting to close a %s but nothing was opened!", mpack_type_to_string(type));
+        return mpack_error_bug;
+    }
+
+    mpack_track_element_t* element = &track->elements[track->count - 1];
+
+    if (element->type != type) {
+        mpack_break("attempting to close a %s but the open element is a %s!",
+                mpack_type_to_string(type), mpack_type_to_string(element->type));
+        return mpack_error_bug;
+    }
+
+    if (element->key_needs_value) {
+        mpack_assert(type == mpack_type_map, "key_needs_value can only be true for maps!");
+        mpack_break("attempting to close a %s but an odd number of elements were written",
+                mpack_type_to_string(type));
+        return mpack_error_bug;
+    }
+
+    if (element->left != 0) {
+        mpack_break("attempting to close a %s but there are %i %s left",
+                mpack_type_to_string(type), element->left,
+                (type == mpack_type_map || type == mpack_type_array) ? "elements" : "bytes");
+        return mpack_error_bug;
+    }
+
+    if (element->builder != builder) {
+        mpack_break("attempting to pop a %sbuilder but the open element is %sa builder",
+                builder ? "" : "non-",
+                element->builder ? "" : "not ");
+        return mpack_error_bug;
+    }
+
+    --track->count;
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type) {
+    return mpack_track_pop_impl(track, type, false);
+}
+
+mpack_error_t mpack_track_pop_builder(mpack_track_t* track, mpack_type_t type) {
+    return mpack_track_pop_impl(track, type, true);
+}
+
+mpack_error_t mpack_track_peek_element(mpack_track_t* track, bool read) {
+    MPACK_UNUSED(read);
+    mpack_assert(track->elements, "null track elements!");
+
+    // if there are no open elements, that's fine, we can read/write elements at will
+    if (track->count == 0)
+        return mpack_ok;
+
+    mpack_track_element_t* element = &track->elements[track->count - 1];
+
+    if (element->type != mpack_type_map && element->type != mpack_type_array) {
+        mpack_break("elements cannot be %s within an %s", read ? "read" : "written",
+                mpack_type_to_string(element->type));
+        return mpack_error_bug;
+    }
+
+    if (!element->builder && element->left == 0 && !element->key_needs_value) {
+        mpack_break("too many elements %s for %s", read ? "read" : "written",
+                mpack_type_to_string(element->type));
+        return mpack_error_bug;
+    }
+
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_element(mpack_track_t* track, bool read) {
+    mpack_error_t error = mpack_track_peek_element(track, read);
+    if (track->count == 0 || error != mpack_ok)
+        return error;
+
+    mpack_track_element_t* element = &track->elements[track->count - 1];
+
+    if (element->type == mpack_type_map) {
+        if (!element->key_needs_value) {
+            element->key_needs_value = true;
+            return mpack_ok; // don't decrement
+        }
+        element->key_needs_value = false;
+    }
+
+    if (!element->builder)
+        --element->left;
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, size_t count) {
+    MPACK_UNUSED(read);
+    mpack_assert(track->elements, "null track elements!");
+
+    if (count > MPACK_UINT32_MAX) {
+        mpack_break("%s more bytes than could possibly fit in a str/bin/ext!",
+                read ? "reading" : "writing");
+        return mpack_error_bug;
+    }
+
+    if (track->count == 0) {
+        mpack_break("bytes cannot be %s with no open bin, str or ext", read ? "read" : "written");
+        return mpack_error_bug;
+    }
+
+    mpack_track_element_t* element = &track->elements[track->count - 1];
+
+    if (element->type == mpack_type_map || element->type == mpack_type_array) {
+        mpack_break("bytes cannot be %s within an %s", read ? "read" : "written",
+                mpack_type_to_string(element->type));
+        return mpack_error_bug;
+    }
+
+    if (element->left < count) {
+        mpack_break("too many bytes %s for %s", read ? "read" : "written",
+                mpack_type_to_string(element->type));
+        return mpack_error_bug;
+    }
+
+    element->left -= (uint32_t)count;
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, size_t count) {
+    mpack_error_t error = mpack_track_bytes(track, read, count);
+    if (error != mpack_ok)
+        return error;
+
+    mpack_track_element_t* element = &track->elements[track->count - 1];
+
+    if (element->type != mpack_type_str) {
+        mpack_break("the open type must be a string, not a %s", mpack_type_to_string(element->type));
+        return mpack_error_bug;
+    }
+
+    if (element->left != 0) {
+        mpack_break("not all bytes were read; the wrong byte count was requested for a string read.");
+        return mpack_error_bug;
+    }
+
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_check_empty(mpack_track_t* track) {
+    if (track->count != 0) {
+        mpack_break("unclosed %s", mpack_type_to_string(track->elements[0].type));
+        return mpack_error_bug;
+    }
+    return mpack_ok;
+}
+
+mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel) {
+    mpack_error_t error = cancel ? mpack_ok : mpack_track_check_empty(track);
+    if (track->elements) {
+        MPACK_FREE(track->elements);
+        track->elements = NULL;
+    }
+    return error;
+}
+#endif
+
+
+
+static bool mpack_utf8_check_impl(const uint8_t* str, size_t count, bool allow_null) {
+    while (count > 0) {
+        uint8_t lead = str[0];
+
+        // NUL
+        if (!allow_null && lead == '\0') // we don't allow NUL bytes in MPack C-strings
+            return false;
+
+        // ASCII
+        if (lead <= 0x7F) {
+            ++str;
+            --count;
+
+        // 2-byte sequence
+        } else if ((lead & 0xE0) == 0xC0) {
+            if (count < 2) // truncated sequence
+                return false;
+
+            uint8_t cont = str[1];
+            if ((cont & 0xC0) != 0x80) // not a continuation byte
+                return false;
+
+            str += 2;
+            count -= 2;
+
+            uint32_t z = ((uint32_t)(lead & ~0xE0) << 6) |
+                          (uint32_t)(cont & ~0xC0);
+
+            if (z < 0x80) // overlong sequence
+                return false;
+
+        // 3-byte sequence
+        } else if ((lead & 0xF0) == 0xE0) {
+            if (count < 3) // truncated sequence
+                return false;
+
+            uint8_t cont1 = str[1];
+            if ((cont1 & 0xC0) != 0x80) // not a continuation byte
+                return false;
+            uint8_t cont2 = str[2];
+            if ((cont2 & 0xC0) != 0x80) // not a continuation byte
+                return false;
+
+            str += 3;
+            count -= 3;
+
+            uint32_t z = ((uint32_t)(lead  & ~0xF0) << 12) |
+                         ((uint32_t)(cont1 & ~0xC0) <<  6) |
+                          (uint32_t)(cont2 & ~0xC0);
+
+            if (z < 0x800) // overlong sequence
+                return false;
+            if (z >= 0xD800 && z <= 0xDFFF) // surrogate
+                return false;
+
+        // 4-byte sequence
+        } else if ((lead & 0xF8) == 0xF0) {
+            if (count < 4) // truncated sequence
+                return false;
+
+            uint8_t cont1 = str[1];
+            if ((cont1 & 0xC0) != 0x80) // not a continuation byte
+                return false;
+            uint8_t cont2 = str[2];
+            if ((cont2 & 0xC0) != 0x80) // not a continuation byte
+                return false;
+            uint8_t cont3 = str[3];
+            if ((cont3 & 0xC0) != 0x80) // not a continuation byte
+                return false;
+
+            str += 4;
+            count -= 4;
+
+            uint32_t z = ((uint32_t)(lead  & ~0xF8) << 18) |
+                         ((uint32_t)(cont1 & ~0xC0) << 12) |
+                         ((uint32_t)(cont2 & ~0xC0) <<  6) |
+                          (uint32_t)(cont3 & ~0xC0);
+
+            if (z < 0x10000) // overlong sequence
+                return false;
+            if (z > 0x10FFFF) // codepoint limit
+                return false;
+
+        } else {
+            return false; // continuation byte without a lead, or lead for a 5-byte sequence or longer
+        }
+    }
+    return true;
+}
+
+bool mpack_utf8_check(const char* str, size_t bytes) {
+    return mpack_utf8_check_impl((const uint8_t*)str, bytes, true);
+}
+
+bool mpack_utf8_check_no_null(const char* str, size_t bytes) {
+    return mpack_utf8_check_impl((const uint8_t*)str, bytes, false);
+}
+
+bool mpack_str_check_no_null(const char* str, size_t bytes) {
+    size_t i;
+    for (i = 0; i < bytes; ++i)
+        if (str[i] == '\0')
+            return false;
+    return true;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+void mpack_print_append(mpack_print_t* print, const char* data, size_t count) {
+
+    // copy whatever fits into the buffer
+    size_t copy = print->size - print->count;
+    if (copy > count)
+        copy = count;
+    mpack_memcpy(print->buffer + print->count, data, copy);
+    print->count += copy;
+    data += copy;
+    count -= copy;
+
+    // if we don't need to flush or can't flush there's nothing else to do
+    if (count == 0 || print->callback == NULL)
+        return;
+
+    // flush the buffer
+    print->callback(print->context, print->buffer, print->count);
+
+    if (count > print->size / 2) {
+        // flush the rest of the data
+        print->count = 0;
+        print->callback(print->context, data, count);
+    } else {
+        // copy the rest of the data into the buffer
+        mpack_memcpy(print->buffer, data, count);
+        print->count = count;
+    }
+
+}
+
+void mpack_print_flush(mpack_print_t* print) {
+    if (print->count > 0 && print->callback != NULL) {
+        print->callback(print->context, print->buffer, print->count);
+        print->count = 0;
+    }
+}
+
+void mpack_print_file_callback(void* context, const char* data, size_t count) {
+    FILE* file = (FILE*)context;
+    fwrite(data, 1, count, file);
+}
+#endif
+
+MPACK_SILENCE_WARNINGS_END
+
+/* mpack/mpack-writer.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-writer.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+
+#if MPACK_WRITER
+
+#if MPACK_BUILDER
+static void mpack_builder_flush(mpack_writer_t* writer);
+#endif
+
+#if MPACK_WRITE_TRACKING
+static void mpack_writer_flag_if_error(mpack_writer_t* writer, mpack_error_t error) {
+    if (error != mpack_ok)
+        mpack_writer_flag_error(writer, error);
+}
+
+void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint32_t count) {
+    if (writer->error == mpack_ok)
+        mpack_writer_flag_if_error(writer, mpack_track_push(&writer->track, type, count));
+}
+
+void mpack_writer_track_push_builder(mpack_writer_t* writer, mpack_type_t type) {
+    if (writer->error == mpack_ok)
+        mpack_writer_flag_if_error(writer, mpack_track_push_builder(&writer->track, type));
+}
+
+void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type) {
+    if (writer->error == mpack_ok)
+        mpack_writer_flag_if_error(writer, mpack_track_pop(&writer->track, type));
+}
+
+void mpack_writer_track_pop_builder(mpack_writer_t* writer, mpack_type_t type) {
+    if (writer->error == mpack_ok)
+        mpack_writer_flag_if_error(writer, mpack_track_pop_builder(&writer->track, type));
+}
+
+void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count) {
+    if (writer->error == mpack_ok)
+        mpack_writer_flag_if_error(writer, mpack_track_bytes(&writer->track, false, count));
+}
+#endif
+
+// This should probably be renamed. It's not solely used for tracking.
+static inline void mpack_writer_track_element(mpack_writer_t* writer) {
+    (void)writer;
+
+    #if MPACK_WRITE_TRACKING
+    if (writer->error == mpack_ok)
+        mpack_writer_flag_if_error(writer, mpack_track_element(&writer->track, false));
+    #endif
+
+    #if MPACK_BUILDER
+    if (writer->builder.current_build != NULL) {
+        mpack_build_t* build = writer->builder.current_build;
+        // We only track this write if it's not nested within another non-build
+        // map or array.
+        if (build->nested_compound_elements == 0) {
+            if (build->type != mpack_type_map) {
+                ++build->count;
+                mpack_log("adding element to build %p, now %u elements\n", (void*)build, build->count);
+            } else if (build->key_needs_value) {
+                build->key_needs_value = false;
+                ++build->count;
+            } else {
+                build->key_needs_value = true;
+            }
+        }
+    }
+    #endif
+}
+
+static void mpack_writer_clear(mpack_writer_t* writer) {
+    #if MPACK_COMPATIBILITY
+    writer->version = mpack_version_current;
+    #endif
+    writer->flush = NULL;
+    writer->error_fn = NULL;
+    writer->teardown = NULL;
+    writer->context = NULL;
+
+    writer->buffer = NULL;
+    writer->position = NULL;
+    writer->end = NULL;
+    writer->error = mpack_ok;
+
+    #if MPACK_WRITE_TRACKING
+    mpack_memset(&writer->track, 0, sizeof(writer->track));
+    #endif
+
+    #if MPACK_BUILDER
+    writer->builder.current_build = NULL;
+    writer->builder.latest_build = NULL;
+    writer->builder.current_page = NULL;
+    writer->builder.pages = NULL;
+    writer->builder.stash_buffer = NULL;
+    writer->builder.stash_position = NULL;
+    writer->builder.stash_end = NULL;
+    #endif
+}
+
+void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size) {
+    mpack_assert(buffer != NULL, "cannot initialize writer with empty buffer");
+    mpack_writer_clear(writer);
+    writer->buffer = buffer;
+    writer->position = buffer;
+    writer->end = writer->buffer + size;
+
+    #if MPACK_WRITE_TRACKING
+    mpack_writer_flag_if_error(writer, mpack_track_init(&writer->track));
+    #endif
+
+    mpack_log("===========================\n");
+    mpack_log("initializing writer with buffer size %i\n", (int)size);
+}
+
+void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error) {
+    mpack_writer_clear(writer);
+    writer->error = error;
+
+    mpack_log("===========================\n");
+    mpack_log("initializing writer in error state %i\n", (int)error);
+}
+
+void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush) {
+    MPACK_STATIC_ASSERT(MPACK_WRITER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE,
+            "minimum buffer size must fit any tag!");
+    MPACK_STATIC_ASSERT(31 + MPACK_TAG_SIZE_FIXSTR >= MPACK_WRITER_MINIMUM_BUFFER_SIZE,
+            "minimum buffer size must fit the largest possible fixstr!");
+
+    if (mpack_writer_buffer_size(writer) < MPACK_WRITER_MINIMUM_BUFFER_SIZE) {
+        mpack_break("buffer size is %i, but minimum buffer size for flush is %i",
+                (int)mpack_writer_buffer_size(writer), MPACK_WRITER_MINIMUM_BUFFER_SIZE);
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+
+    writer->flush = flush;
+}
+
+#ifdef MPACK_MALLOC
+typedef struct mpack_growable_writer_t {
+    char** target_data;
+    size_t* target_size;
+} mpack_growable_writer_t;
+
+static char* mpack_writer_get_reserved(mpack_writer_t* writer) {
+    // This is in a separate function in order to avoid false strict aliasing
+    // warnings. We aren't actually violating strict aliasing (the reserved
+    // space is only ever dereferenced as an mpack_growable_writer_t.)
+    return (char*)writer->reserved;
+}
+
+static void mpack_growable_writer_flush(mpack_writer_t* writer, const char* data, size_t count) {
+
+    // This is an intrusive flush function which modifies the writer's buffer
+    // in response to a flush instead of emptying it in order to add more
+    // capacity for data. This removes the need to copy data from a fixed buffer
+    // into a growable one, improving performance.
+    //
+    // There are three ways flush can be called:
+    //   - flushing the buffer during writing (used is zero, count is all data, data is buffer)
+    //   - flushing extra data during writing (used is all flushed data, count is extra data, data is not buffer)
+    //   - flushing during teardown (used and count are both all flushed data, data is buffer)
+    //
+    // In the first two cases, we grow the buffer by at least double, enough
+    // to ensure that new data will fit. We ignore the teardown flush.
+
+    if (data == writer->buffer) {
+
+        // teardown, do nothing
+        if (mpack_writer_buffer_used(writer) == count)
+            return;
+
+        // otherwise leave the data in the buffer and just grow
+        writer->position = writer->buffer + count;
+        count = 0;
+    }
+
+    size_t used = mpack_writer_buffer_used(writer);
+    size_t size = mpack_writer_buffer_size(writer);
+
+    mpack_log("flush size %i used %i data %p buffer %p\n",
+            (int)count, (int)used, data, writer->buffer);
+
+    mpack_assert(data == writer->buffer || used + count > size,
+            "extra flush for %i but there is %i space left in the buffer! (%i/%i)",
+            (int)count, (int)mpack_writer_buffer_left(writer), (int)used, (int)size);
+
+    // grow to fit the data
+    // TODO: this really needs to correctly test for overflow
+    size_t new_size = size * 2;
+    while (new_size < used + count)
+        new_size *= 2;
+
+    mpack_log("flush growing buffer size from %i to %i\n", (int)size, (int)new_size);
+
+    // grow the buffer
+    char* new_buffer = (char*)mpack_realloc(writer->buffer, used, new_size);
+    if (new_buffer == NULL) {
+        mpack_writer_flag_error(writer, mpack_error_memory);
+        return;
+    }
+    writer->position = new_buffer + used;
+    writer->buffer = new_buffer;
+    writer->end = writer->buffer + new_size;
+
+    // append the extra data
+    if (count > 0) {
+        mpack_memcpy(writer->position, data, count);
+        writer->position += count;
+    }
+
+    mpack_log("new buffer %p, used %i\n", new_buffer, (int)mpack_writer_buffer_used(writer));
+}
+
+static void mpack_growable_writer_teardown(mpack_writer_t* writer) {
+    mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer);
+
+    if (mpack_writer_error(writer) == mpack_ok) {
+
+        // shrink the buffer to an appropriate size if the data is
+        // much smaller than the buffer
+        if (mpack_writer_buffer_used(writer) < mpack_writer_buffer_size(writer) / 2) {
+            size_t used = mpack_writer_buffer_used(writer);
+
+            // We always return a non-null pointer that must be freed, even if
+            // nothing was written. malloc() and realloc() do not necessarily
+            // do this so we enforce it ourselves.
+            size_t size = (used != 0) ? used : 1;
+
+            char* buffer = (char*)mpack_realloc(writer->buffer, used, size);
+            if (!buffer) {
+                MPACK_FREE(writer->buffer);
+                mpack_writer_flag_error(writer, mpack_error_memory);
+                return;
+            }
+            writer->buffer = buffer;
+            writer->end = (writer->position = writer->buffer + used);
+        }
+
+        *growable_writer->target_data = writer->buffer;
+        *growable_writer->target_size = mpack_writer_buffer_used(writer);
+        writer->buffer = NULL;
+
+    } else if (writer->buffer) {
+        MPACK_FREE(writer->buffer);
+        writer->buffer = NULL;
+    }
+
+    writer->context = NULL;
+}
+
+void mpack_writer_init_growable(mpack_writer_t* writer, char** target_data, size_t* target_size) {
+    mpack_assert(target_data != NULL, "cannot initialize writer without a destination for the data");
+    mpack_assert(target_size != NULL, "cannot initialize writer without a destination for the size");
+
+    *target_data = NULL;
+    *target_size = 0;
+
+    MPACK_STATIC_ASSERT(sizeof(mpack_growable_writer_t) <= sizeof(writer->reserved),
+            "not enough reserved space for growable writer!");
+    mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer);
+
+    growable_writer->target_data = target_data;
+    growable_writer->target_size = target_size;
+
+    size_t capacity = MPACK_BUFFER_SIZE;
+    char* buffer = (char*)MPACK_MALLOC(capacity);
+    if (buffer == NULL) {
+        mpack_writer_init_error(writer, mpack_error_memory);
+        return;
+    }
+
+    mpack_writer_init(writer, buffer, capacity);
+    mpack_writer_set_flush(writer, mpack_growable_writer_flush);
+    mpack_writer_set_teardown(writer, mpack_growable_writer_teardown);
+}
+#endif
+
+#if MPACK_STDIO
+static void mpack_file_writer_flush(mpack_writer_t* writer, const char* buffer, size_t count) {
+    FILE* file = (FILE*)writer->context;
+    size_t written = fwrite((const void*)buffer, 1, count, file);
+    if (written != count)
+        mpack_writer_flag_error(writer, mpack_error_io);
+}
+
+static void mpack_file_writer_teardown(mpack_writer_t* writer) {
+    MPACK_FREE(writer->buffer);
+    writer->buffer = NULL;
+    writer->context = NULL;
+}
+
+static void mpack_file_writer_teardown_close(mpack_writer_t* writer) {
+    FILE* file = (FILE*)writer->context;
+
+    if (file) {
+        int ret = fclose(file);
+        if (ret != 0)
+            mpack_writer_flag_error(writer, mpack_error_io);
+    }
+
+    mpack_file_writer_teardown(writer);
+}
+
+void mpack_writer_init_stdfile(mpack_writer_t* writer, FILE* file, bool close_when_done) {
+    mpack_assert(file != NULL, "file is NULL");
+
+    size_t capacity = MPACK_BUFFER_SIZE;
+    char* buffer = (char*)MPACK_MALLOC(capacity);
+    if (buffer == NULL) {
+        mpack_writer_init_error(writer, mpack_error_memory);
+        if (close_when_done) {
+            fclose(file);
+        }
+        return;
+    }
+
+    mpack_writer_init(writer, buffer, capacity);
+    mpack_writer_set_context(writer, file);
+    mpack_writer_set_flush(writer, mpack_file_writer_flush);
+    mpack_writer_set_teardown(writer, close_when_done ?
+            mpack_file_writer_teardown_close :
+            mpack_file_writer_teardown);
+}
+
+void mpack_writer_init_filename(mpack_writer_t* writer, const char* filename) {
+    mpack_assert(filename != NULL, "filename is NULL");
+
+    FILE* file = fopen(filename, "wb");
+    if (file == NULL) {
+        mpack_writer_init_error(writer, mpack_error_io);
+        return;
+    }
+
+    mpack_writer_init_stdfile(writer, file, true);
+}
+#endif
+
+void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error) {
+    mpack_log("writer %p setting error %i: %s\n", (void*)writer, (int)error, mpack_error_to_string(error));
+
+    if (writer->error == mpack_ok) {
+        writer->error = error;
+        if (writer->error_fn)
+            writer->error_fn(writer, writer->error);
+    }
+}
+
+MPACK_STATIC_INLINE void mpack_writer_flush_unchecked(mpack_writer_t* writer) {
+    // This is a bit ugly; we reset used before calling flush so that
+    // a flush function can distinguish between flushing the buffer
+    // versus flushing external data. see mpack_growable_writer_flush()
+    size_t used = mpack_writer_buffer_used(writer);
+    writer->position = writer->buffer;
+    writer->flush(writer, writer->buffer, used);
+}
+
+void mpack_writer_flush_message(mpack_writer_t* writer) {
+    if (writer->error != mpack_ok)
+        return;
+
+    #if MPACK_WRITE_TRACKING
+    // You cannot flush while there are elements open.
+    mpack_writer_flag_if_error(writer, mpack_track_check_empty(&writer->track));
+    if (writer->error != mpack_ok)
+        return;
+    #endif
+
+    #if MPACK_BUILDER
+    if (writer->builder.current_build != NULL) {
+        mpack_break("cannot call mpack_writer_flush_message() while there are elements open!");
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+    #endif
+
+    if (writer->flush == NULL) {
+        mpack_break("cannot call mpack_writer_flush_message() without a flush function!");
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+
+    if (mpack_writer_buffer_used(writer) > 0)
+        mpack_writer_flush_unchecked(writer);
+}
+
+// Ensures there are at least count bytes free in the buffer. This
+// will flag an error if the flush function fails to make enough
+// room in the buffer.
+MPACK_NOINLINE static bool mpack_writer_ensure(mpack_writer_t* writer, size_t count) {
+    mpack_assert(count != 0, "cannot ensure zero bytes!");
+    mpack_assert(count <= MPACK_WRITER_MINIMUM_BUFFER_SIZE,
+            "cannot ensure %i bytes, this is more than the minimum buffer size %i!",
+            (int)count, (int)MPACK_WRITER_MINIMUM_BUFFER_SIZE);
+    mpack_assert(count > mpack_writer_buffer_left(writer),
+            "request to ensure %i bytes but there are already %i left in the buffer!",
+            (int)count, (int)mpack_writer_buffer_left(writer));
+
+    mpack_log("ensuring %i bytes, %i left\n", (int)count, (int)mpack_writer_buffer_left(writer));
+
+    if (mpack_writer_error(writer) != mpack_ok)
+        return false;
+
+    #if MPACK_BUILDER
+    // if we have a build in progress, we just ask the builder for a page.
+    // either it will have space for a tag, or it will flag a memory error.
+    if (writer->builder.current_build != NULL) {
+        mpack_builder_flush(writer);
+        return mpack_writer_error(writer) == mpack_ok;
+    }
+    #endif
+
+    if (writer->flush == NULL) {
+        mpack_writer_flag_error(writer, mpack_error_too_big);
+        return false;
+    }
+
+    mpack_writer_flush_unchecked(writer);
+    if (mpack_writer_error(writer) != mpack_ok)
+        return false;
+
+    if (mpack_writer_buffer_left(writer) >= count)
+        return true;
+
+    mpack_writer_flag_error(writer, mpack_error_io);
+    return false;
+}
+
+// Writes encoded bytes to the buffer when we already know the data
+// does not fit in the buffer (i.e. it straddles the edge of the
+// buffer.) If there is a flush function, it is guaranteed to be
+// called; otherwise mpack_error_too_big is raised.
+MPACK_NOINLINE static void mpack_write_native_straddle(mpack_writer_t* writer, const char* p, size_t count) {
+    mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+    if (mpack_writer_error(writer) != mpack_ok)
+        return;
+    mpack_log("big write for %i bytes from %p, %i space left in buffer\n",
+            (int)count, p, (int)mpack_writer_buffer_left(writer));
+    mpack_assert(count > mpack_writer_buffer_left(writer),
+            "big write requested for %i bytes, but there is %i available "
+            "space in buffer. should have called mpack_write_native() instead",
+            (int)count, (int)(mpack_writer_buffer_left(writer)));
+
+    #if MPACK_BUILDER
+    // if we have a build in progress, we can't flush. we need to copy all
+    // bytes into as many build buffer pages as it takes.
+    if (writer->builder.current_build != NULL) {
+        while (true) {
+            size_t step = (size_t)(writer->end - writer->position);
+            if (step > count)
+                step = count;
+            mpack_memcpy(writer->position, p, step);
+            writer->position += step;
+            p += step;
+            count -= step;
+
+            if (count == 0)
+                return;
+
+            mpack_builder_flush(writer);
+            if (mpack_writer_error(writer) != mpack_ok)
+                return;
+            mpack_assert(writer->position != writer->end);
+        }
+    }
+    #endif
+
+    // we'll need a flush function
+    if (!writer->flush) {
+        mpack_writer_flag_error(writer, mpack_error_too_big);
+        return;
+    }
+
+    // flush the buffer
+    mpack_writer_flush_unchecked(writer);
+    if (mpack_writer_error(writer) != mpack_ok)
+        return;
+
+    // note that an intrusive flush function (such as mpack_growable_writer_flush())
+    // may have changed size and/or reset used to a non-zero value. we treat both as
+    // though they may have changed, and there may still be data in the buffer.
+
+    // flush the extra data directly if it doesn't fit in the buffer
+    if (count > mpack_writer_buffer_left(writer)) {
+        writer->flush(writer, p, count);
+        if (mpack_writer_error(writer) != mpack_ok)
+            return;
+    } else {
+        mpack_memcpy(writer->position, p, count);
+        writer->position += count;
+    }
+}
+
+// Writes encoded bytes to the buffer, flushing if necessary.
+MPACK_STATIC_INLINE void mpack_write_native(mpack_writer_t* writer, const char* p, size_t count) {
+    mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+    if (mpack_writer_buffer_left(writer) < count) {
+        mpack_write_native_straddle(writer, p, count);
+    } else {
+        mpack_memcpy(writer->position, p, count);
+        writer->position += count;
+    }
+}
+
+mpack_error_t mpack_writer_destroy(mpack_writer_t* writer) {
+
+    // clean up tracking, asserting if we're not already in an error state
+    #if MPACK_WRITE_TRACKING
+    mpack_track_destroy(&writer->track, writer->error != mpack_ok);
+    #endif
+
+    // flush any outstanding data
+    if (mpack_writer_error(writer) == mpack_ok && mpack_writer_buffer_used(writer) != 0 && writer->flush != NULL) {
+        writer->flush(writer, writer->buffer, mpack_writer_buffer_used(writer));
+        writer->flush = NULL;
+    }
+
+    if (writer->teardown) {
+        writer->teardown(writer);
+        writer->teardown = NULL;
+    }
+
+    return writer->error;
+}
+
+void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t value) {
+    switch (value.type) {
+        case mpack_type_missing:
+            mpack_break("cannot write a missing value!");
+            mpack_writer_flag_error(writer, mpack_error_bug);
+            return;
+
+        case mpack_type_nil:    mpack_write_nil   (writer);            return;
+        case mpack_type_bool:   mpack_write_bool  (writer, value.v.b); return;
+        case mpack_type_int:    mpack_write_int   (writer, value.v.i); return;
+        case mpack_type_uint:   mpack_write_uint  (writer, value.v.u); return;
+
+        case mpack_type_float:
+            #if MPACK_FLOAT
+            mpack_write_float
+            #else
+            mpack_write_raw_float
+            #endif
+                (writer, value.v.f);
+            return;
+        case mpack_type_double:
+            #if MPACK_DOUBLE
+            mpack_write_double
+            #else
+            mpack_write_raw_double
+            #endif
+                (writer, value.v.d);
+            return;
+
+        case mpack_type_str: mpack_start_str(writer, value.v.l); return;
+        case mpack_type_bin: mpack_start_bin(writer, value.v.l); return;
+
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+            mpack_start_ext(writer, mpack_tag_ext_exttype(&value), mpack_tag_ext_length(&value));
+            return;
+        #endif
+
+        case mpack_type_array: mpack_start_array(writer, value.v.n); return;
+        case mpack_type_map:   mpack_start_map(writer, value.v.n);   return;
+    }
+
+    mpack_break("unrecognized type %i", (int)value.type);
+    mpack_writer_flag_error(writer, mpack_error_bug);
+}
+
+MPACK_STATIC_INLINE void mpack_write_byte_element(mpack_writer_t* writer, char value) {
+    mpack_writer_track_element(writer);
+    if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= 1) || mpack_writer_ensure(writer, 1))
+        *(writer->position++) = value;
+}
+
+void mpack_write_nil(mpack_writer_t* writer) {
+    mpack_write_byte_element(writer, (char)0xc0);
+}
+
+void mpack_write_bool(mpack_writer_t* writer, bool value) {
+    mpack_write_byte_element(writer, (char)(0xc2 | (value ? 1 : 0)));
+}
+
+void mpack_write_true(mpack_writer_t* writer) {
+    mpack_write_byte_element(writer, (char)0xc3);
+}
+
+void mpack_write_false(mpack_writer_t* writer) {
+    mpack_write_byte_element(writer, (char)0xc2);
+}
+
+void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes) {
+    mpack_writer_track_element(writer);
+    mpack_write_native(writer, data, bytes);
+}
+
+/*
+ * Encode functions
+ */
+
+MPACK_STATIC_INLINE void mpack_encode_fixuint(char* p, uint8_t value) {
+    mpack_assert(value <= 127);
+    mpack_store_u8(p, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u8(char* p, uint8_t value) {
+    mpack_assert(value > 127);
+    mpack_store_u8(p, 0xcc);
+    mpack_store_u8(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u16(char* p, uint16_t value) {
+    mpack_assert(value > MPACK_UINT8_MAX);
+    mpack_store_u8(p, 0xcd);
+    mpack_store_u16(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u32(char* p, uint32_t value) {
+    mpack_assert(value > MPACK_UINT16_MAX);
+    mpack_store_u8(p, 0xce);
+    mpack_store_u32(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u64(char* p, uint64_t value) {
+    mpack_assert(value > MPACK_UINT32_MAX);
+    mpack_store_u8(p, 0xcf);
+    mpack_store_u64(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixint(char* p, int8_t value) {
+    // this can encode positive or negative fixints
+    mpack_assert(value >= -32);
+    mpack_store_i8(p, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i8(char* p, int8_t value) {
+    mpack_assert(value < -32);
+    mpack_store_u8(p, 0xd0);
+    mpack_store_i8(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i16(char* p, int16_t value) {
+    mpack_assert(value < MPACK_INT8_MIN);
+    mpack_store_u8(p, 0xd1);
+    mpack_store_i16(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i32(char* p, int32_t value) {
+    mpack_assert(value < MPACK_INT16_MIN);
+    mpack_store_u8(p, 0xd2);
+    mpack_store_i32(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i64(char* p, int64_t value) {
+    mpack_assert(value < MPACK_INT32_MIN);
+    mpack_store_u8(p, 0xd3);
+    mpack_store_i64(p + 1, value);
+}
+
+#if MPACK_FLOAT
+MPACK_STATIC_INLINE void mpack_encode_float(char* p, float value) {
+    mpack_store_u8(p, 0xca);
+    mpack_store_float(p + 1, value);
+}
+#else
+MPACK_STATIC_INLINE void mpack_encode_raw_float(char* p, uint32_t value) {
+    mpack_store_u8(p, 0xca);
+    mpack_store_u32(p + 1, value);
+}
+#endif
+
+#if MPACK_DOUBLE
+MPACK_STATIC_INLINE void mpack_encode_double(char* p, double value) {
+    mpack_store_u8(p, 0xcb);
+    mpack_store_double(p + 1, value);
+}
+#else
+MPACK_STATIC_INLINE void mpack_encode_raw_double(char* p, uint64_t value) {
+    mpack_store_u8(p, 0xcb);
+    mpack_store_u64(p + 1, value);
+}
+#endif
+
+MPACK_STATIC_INLINE void mpack_encode_fixarray(char* p, uint8_t count) {
+    mpack_assert(count <= 15);
+    mpack_store_u8(p, (uint8_t)(0x90 | count));
+}
+
+MPACK_STATIC_INLINE void mpack_encode_array16(char* p, uint16_t count) {
+    mpack_assert(count > 15);
+    mpack_store_u8(p, 0xdc);
+    mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_array32(char* p, uint32_t count) {
+    mpack_assert(count > MPACK_UINT16_MAX);
+    mpack_store_u8(p, 0xdd);
+    mpack_store_u32(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixmap(char* p, uint8_t count) {
+    mpack_assert(count <= 15);
+    mpack_store_u8(p, (uint8_t)(0x80 | count));
+}
+
+MPACK_STATIC_INLINE void mpack_encode_map16(char* p, uint16_t count) {
+    mpack_assert(count > 15);
+    mpack_store_u8(p, 0xde);
+    mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_map32(char* p, uint32_t count) {
+    mpack_assert(count > MPACK_UINT16_MAX);
+    mpack_store_u8(p, 0xdf);
+    mpack_store_u32(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixstr(char* p, uint8_t count) {
+    mpack_assert(count <= 31);
+    mpack_store_u8(p, (uint8_t)(0xa0 | count));
+}
+
+MPACK_STATIC_INLINE void mpack_encode_str8(char* p, uint8_t count) {
+    mpack_assert(count > 31);
+    mpack_store_u8(p, 0xd9);
+    mpack_store_u8(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_str16(char* p, uint16_t count) {
+    // we might be encoding a raw in compatibility mode, so we
+    // allow count to be in the range [32, MPACK_UINT8_MAX].
+    mpack_assert(count > 31);
+    mpack_store_u8(p, 0xda);
+    mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_str32(char* p, uint32_t count) {
+    mpack_assert(count > MPACK_UINT16_MAX);
+    mpack_store_u8(p, 0xdb);
+    mpack_store_u32(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_bin8(char* p, uint8_t count) {
+    mpack_store_u8(p, 0xc4);
+    mpack_store_u8(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_bin16(char* p, uint16_t count) {
+    mpack_assert(count > MPACK_UINT8_MAX);
+    mpack_store_u8(p, 0xc5);
+    mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_bin32(char* p, uint32_t count) {
+    mpack_assert(count > MPACK_UINT16_MAX);
+    mpack_store_u8(p, 0xc6);
+    mpack_store_u32(p + 1, count);
+}
+
+#if MPACK_EXTENSIONS
+MPACK_STATIC_INLINE void mpack_encode_fixext1(char* p, int8_t exttype) {
+    mpack_store_u8(p, 0xd4);
+    mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext2(char* p, int8_t exttype) {
+    mpack_store_u8(p, 0xd5);
+    mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext4(char* p, int8_t exttype) {
+    mpack_store_u8(p, 0xd6);
+    mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext8(char* p, int8_t exttype) {
+    mpack_store_u8(p, 0xd7);
+    mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext16(char* p, int8_t exttype) {
+    mpack_store_u8(p, 0xd8);
+    mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_ext8(char* p, int8_t exttype, uint8_t count) {
+    mpack_assert(count != 1 && count != 2 && count != 4 && count != 8 && count != 16);
+    mpack_store_u8(p, 0xc7);
+    mpack_store_u8(p + 1, count);
+    mpack_store_i8(p + 2, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_ext16(char* p, int8_t exttype, uint16_t count) {
+    mpack_assert(count > MPACK_UINT8_MAX);
+    mpack_store_u8(p, 0xc8);
+    mpack_store_u16(p + 1, count);
+    mpack_store_i8(p + 3, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_ext32(char* p, int8_t exttype, uint32_t count) {
+    mpack_assert(count > MPACK_UINT16_MAX);
+    mpack_store_u8(p, 0xc9);
+    mpack_store_u32(p + 1, count);
+    mpack_store_i8(p + 5, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_timestamp_4(char* p, uint32_t seconds) {
+    mpack_encode_fixext4(p, MPACK_EXTTYPE_TIMESTAMP);
+    mpack_store_u32(p + MPACK_TAG_SIZE_FIXEXT4, seconds);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_timestamp_8(char* p, int64_t seconds, uint32_t nanoseconds) {
+    mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX);
+    mpack_encode_fixext8(p, MPACK_EXTTYPE_TIMESTAMP);
+    uint64_t encoded = ((uint64_t)nanoseconds << 34) | (uint64_t)seconds;
+    mpack_store_u64(p + MPACK_TAG_SIZE_FIXEXT8, encoded);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_timestamp_12(char* p, int64_t seconds, uint32_t nanoseconds) {
+    mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX);
+    mpack_encode_ext8(p, MPACK_EXTTYPE_TIMESTAMP, 12);
+    mpack_store_u32(p + MPACK_TAG_SIZE_EXT8, nanoseconds);
+    mpack_store_i64(p + MPACK_TAG_SIZE_EXT8 + 4, seconds);
+}
+#endif
+
+
+
+/*
+ * Write functions
+ */
+
+// This is a macro wrapper to the encode functions to encode
+// directly into the buffer. If mpack_writer_ensure() fails
+// it will flag an error so we don't have to do anything.
+#define MPACK_WRITE_ENCODED(encode_fn, size, ...) do {                                                 \
+    if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) { \
+        MPACK_EXPAND(encode_fn(writer->position, __VA_ARGS__));                                        \
+        writer->position += size;                                                                      \
+    }                                                                                                  \
+} while (0)
+
+void mpack_write_u8(mpack_writer_t* writer, uint8_t value) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_write_u64(writer, value);
+    #else
+    mpack_writer_track_element(writer);
+    if (value <= 127) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, value);
+    }
+    #endif
+}
+
+void mpack_write_u16(mpack_writer_t* writer, uint16_t value) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_write_u64(writer, value);
+    #else
+    mpack_writer_track_element(writer);
+    if (value <= 127) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
+    } else if (value <= MPACK_UINT8_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, value);
+    }
+    #endif
+}
+
+void mpack_write_u32(mpack_writer_t* writer, uint32_t value) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_write_u64(writer, value);
+    #else
+    mpack_writer_track_element(writer);
+    if (value <= 127) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
+    } else if (value <= MPACK_UINT8_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+    } else if (value <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, value);
+    }
+    #endif
+}
+
+void mpack_write_u64(mpack_writer_t* writer, uint64_t value) {
+    mpack_writer_track_element(writer);
+
+    if (value <= 127) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
+    } else if (value <= MPACK_UINT8_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+    } else if (value <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+    } else if (value <= MPACK_UINT32_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, value);
+    }
+}
+
+void mpack_write_i8(mpack_writer_t* writer, int8_t value) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_write_i64(writer, value);
+    #else
+    mpack_writer_track_element(writer);
+    if (value >= -32) {
+        // we encode positive and negative fixints together
+        MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+    }
+    #endif
+}
+
+void mpack_write_i16(mpack_writer_t* writer, int16_t value) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_write_i64(writer, value);
+    #else
+    mpack_writer_track_element(writer);
+    if (value >= -32) {
+        if (value <= 127) {
+            // we encode positive and negative fixints together
+            MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+        } else if (value <= MPACK_UINT8_MAX) {
+            MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+        } else {
+            MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+        }
+    } else if (value >= MPACK_INT8_MIN) {
+        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
+    }
+    #endif
+}
+
+void mpack_write_i32(mpack_writer_t* writer, int32_t value) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_write_i64(writer, value);
+    #else
+    mpack_writer_track_element(writer);
+    if (value >= -32) {
+        if (value <= 127) {
+            // we encode positive and negative fixints together
+            MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+        } else if (value <= MPACK_UINT8_MAX) {
+            MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+        } else if (value <= MPACK_UINT16_MAX) {
+            MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+        } else {
+            MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
+        }
+    } else if (value >= MPACK_INT8_MIN) {
+        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+    } else if (value >= MPACK_INT16_MIN) {
+        MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, value);
+    }
+    #endif
+}
+
+void mpack_write_i64(mpack_writer_t* writer, int64_t value) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    if (value > 127) {
+        // for non-fix positive ints we call the u64 writer to save space
+        mpack_write_u64(writer, (uint64_t)value);
+        return;
+    }
+    #endif
+
+    mpack_writer_track_element(writer);
+    if (value >= -32) {
+        #if MPACK_OPTIMIZE_FOR_SIZE
+        MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+        #else
+        if (value <= 127) {
+            MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+        } else if (value <= MPACK_UINT8_MAX) {
+            MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+        } else if (value <= MPACK_UINT16_MAX) {
+            MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+        } else if (value <= MPACK_UINT32_MAX) {
+            MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
+        } else {
+            MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, (uint64_t)value);
+        }
+        #endif
+    } else if (value >= MPACK_INT8_MIN) {
+        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+    } else if (value >= MPACK_INT16_MIN) {
+        MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
+    } else if (value >= MPACK_INT32_MIN) {
+        MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, (int32_t)value);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_i64, MPACK_TAG_SIZE_I64, value);
+    }
+}
+
+#if MPACK_FLOAT
+void mpack_write_float(mpack_writer_t* writer, float value) {
+    mpack_writer_track_element(writer);
+    MPACK_WRITE_ENCODED(mpack_encode_float, MPACK_TAG_SIZE_FLOAT, value);
+}
+#else
+void mpack_write_raw_float(mpack_writer_t* writer, uint32_t value) {
+    mpack_writer_track_element(writer);
+    MPACK_WRITE_ENCODED(mpack_encode_raw_float, MPACK_TAG_SIZE_FLOAT, value);
+}
+#endif
+
+#if MPACK_DOUBLE
+void mpack_write_double(mpack_writer_t* writer, double value) {
+    mpack_writer_track_element(writer);
+    MPACK_WRITE_ENCODED(mpack_encode_double, MPACK_TAG_SIZE_DOUBLE, value);
+}
+#else
+void mpack_write_raw_double(mpack_writer_t* writer, uint64_t value) {
+    mpack_writer_track_element(writer);
+    MPACK_WRITE_ENCODED(mpack_encode_raw_double, MPACK_TAG_SIZE_DOUBLE, value);
+}
+#endif
+
+#if MPACK_EXTENSIONS
+void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds) {
+    #if MPACK_COMPATIBILITY
+    if (writer->version <= mpack_version_v4) {
+        mpack_break("Timestamps require spec version v5 or later. This writer is in v%i mode.", (int)writer->version);
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+    #endif
+
+    if (nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
+        mpack_break("timestamp nanoseconds out of bounds: %u", nanoseconds);
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+
+    mpack_writer_track_element(writer);
+
+    if (seconds < 0 || seconds >= (MPACK_INT64_C(1) << 34)) {
+        MPACK_WRITE_ENCODED(mpack_encode_timestamp_12, MPACK_EXT_SIZE_TIMESTAMP12, seconds, nanoseconds);
+    } else if (seconds > MPACK_UINT32_MAX || nanoseconds > 0) {
+        MPACK_WRITE_ENCODED(mpack_encode_timestamp_8, MPACK_EXT_SIZE_TIMESTAMP8, seconds, nanoseconds);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_timestamp_4, MPACK_EXT_SIZE_TIMESTAMP4, (uint32_t)seconds);
+    }
+}
+#endif
+
+static void mpack_write_array_notrack(mpack_writer_t* writer, uint32_t count) {
+    if (count <= 15) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixarray, MPACK_TAG_SIZE_FIXARRAY, (uint8_t)count);
+    } else if (count <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_array16, MPACK_TAG_SIZE_ARRAY16, (uint16_t)count);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_array32, MPACK_TAG_SIZE_ARRAY32, (uint32_t)count);
+    }
+}
+
+static void mpack_write_map_notrack(mpack_writer_t* writer, uint32_t count) {
+    if (count <= 15) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixmap, MPACK_TAG_SIZE_FIXMAP, (uint8_t)count);
+    } else if (count <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_map16, MPACK_TAG_SIZE_MAP16, (uint16_t)count);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_map32, MPACK_TAG_SIZE_MAP32, (uint32_t)count);
+    }
+}
+
+void mpack_start_array(mpack_writer_t* writer, uint32_t count) {
+    mpack_writer_track_element(writer);
+    mpack_write_array_notrack(writer, count);
+    mpack_writer_track_push(writer, mpack_type_array, count);
+    mpack_builder_compound_push(writer);
+}
+
+void mpack_start_map(mpack_writer_t* writer, uint32_t count) {
+    mpack_writer_track_element(writer);
+    mpack_write_map_notrack(writer, count);
+    mpack_writer_track_push(writer, mpack_type_map, count);
+    mpack_builder_compound_push(writer);
+}
+
+static void mpack_start_str_notrack(mpack_writer_t* writer, uint32_t count) {
+    if (count <= 31) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixstr, MPACK_TAG_SIZE_FIXSTR, (uint8_t)count);
+
+    // str8 is only supported in v5 or later.
+    } else if (count <= MPACK_UINT8_MAX
+            #if MPACK_COMPATIBILITY
+            && writer->version >= mpack_version_v5
+            #endif
+            ) {
+        MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count);
+
+    } else if (count <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count);
+    }
+}
+
+static void mpack_start_bin_notrack(mpack_writer_t* writer, uint32_t count) {
+    #if MPACK_COMPATIBILITY
+    // In the v4 spec, there was only the raw type for any kind of
+    // variable-length data. In v4 mode, we support the bin functions,
+    // but we produce an old-style raw.
+    if (writer->version <= mpack_version_v4) {
+        mpack_start_str_notrack(writer, count);
+        return;
+    }
+    #endif
+
+    if (count <= MPACK_UINT8_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_bin8, MPACK_TAG_SIZE_BIN8, (uint8_t)count);
+    } else if (count <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_bin16, MPACK_TAG_SIZE_BIN16, (uint16_t)count);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_bin32, MPACK_TAG_SIZE_BIN32, (uint32_t)count);
+    }
+}
+
+void mpack_start_str(mpack_writer_t* writer, uint32_t count) {
+    mpack_writer_track_element(writer);
+    mpack_start_str_notrack(writer, count);
+    mpack_writer_track_push(writer, mpack_type_str, count);
+}
+
+void mpack_start_bin(mpack_writer_t* writer, uint32_t count) {
+    mpack_writer_track_element(writer);
+    mpack_start_bin_notrack(writer, count);
+    mpack_writer_track_push(writer, mpack_type_bin, count);
+}
+
+#if MPACK_EXTENSIONS
+void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count) {
+    #if MPACK_COMPATIBILITY
+    if (writer->version <= mpack_version_v4) {
+        mpack_break("Ext types require spec version v5 or later. This writer is in v%i mode.", (int)writer->version);
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+    #endif
+
+    mpack_writer_track_element(writer);
+
+    if (count == 1) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixext1, MPACK_TAG_SIZE_FIXEXT1, exttype);
+    } else if (count == 2) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixext2, MPACK_TAG_SIZE_FIXEXT2, exttype);
+    } else if (count == 4) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixext4, MPACK_TAG_SIZE_FIXEXT4, exttype);
+    } else if (count == 8) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixext8, MPACK_TAG_SIZE_FIXEXT8, exttype);
+    } else if (count == 16) {
+        MPACK_WRITE_ENCODED(mpack_encode_fixext16, MPACK_TAG_SIZE_FIXEXT16, exttype);
+    } else if (count <= MPACK_UINT8_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_ext8, MPACK_TAG_SIZE_EXT8, exttype, (uint8_t)count);
+    } else if (count <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_ext16, MPACK_TAG_SIZE_EXT16, exttype, (uint16_t)count);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_ext32, MPACK_TAG_SIZE_EXT32, exttype, (uint32_t)count);
+    }
+
+    mpack_writer_track_push(writer, mpack_type_ext, count);
+}
+#endif
+
+
+
+/*
+ * Compound helpers and other functions
+ */
+
+void mpack_write_str(mpack_writer_t* writer, const char* data, uint32_t count) {
+    mpack_assert(data != NULL, "data for string of length %i is NULL", (int)count);
+
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_writer_track_element(writer);
+    mpack_start_str_notrack(writer, count);
+    mpack_write_native(writer, data, count);
+    #else
+
+    mpack_writer_track_element(writer);
+
+    if (count <= 31) {
+        // The minimum buffer size when using a flush function is guaranteed to
+        // fit the largest possible fixstr.
+        size_t size = count + MPACK_TAG_SIZE_FIXSTR;
+        if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) {
+            char* MPACK_RESTRICT p = writer->position;
+            mpack_encode_fixstr(p, (uint8_t)count);
+            mpack_memcpy(p + MPACK_TAG_SIZE_FIXSTR, data, count);
+            writer->position += count + MPACK_TAG_SIZE_FIXSTR;
+        }
+        return;
+    }
+
+    if (count <= MPACK_UINT8_MAX
+            #if MPACK_COMPATIBILITY
+            && writer->version >= mpack_version_v5
+            #endif
+            ) {
+        if (count + MPACK_TAG_SIZE_STR8 <= mpack_writer_buffer_left(writer)) {
+            char* MPACK_RESTRICT p = writer->position;
+            mpack_encode_str8(p, (uint8_t)count);
+            mpack_memcpy(p + MPACK_TAG_SIZE_STR8, data, count);
+            writer->position += count + MPACK_TAG_SIZE_STR8;
+        } else {
+            MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count);
+            mpack_write_native(writer, data, count);
+        }
+        return;
+    }
+
+    // str16 and str32 are likely to be a significant fraction of the buffer
+    // size, so we don't bother with a combined space check in order to
+    // minimize code size.
+    if (count <= MPACK_UINT16_MAX) {
+        MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count);
+        mpack_write_native(writer, data, count);
+    } else {
+        MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count);
+        mpack_write_native(writer, data, count);
+    }
+
+    #endif
+}
+
+void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count) {
+    mpack_assert(data != NULL, "data pointer for bin of %i bytes is NULL", (int)count);
+    mpack_start_bin(writer, count);
+    mpack_write_bytes(writer, data, count);
+    mpack_finish_bin(writer);
+}
+
+#if MPACK_EXTENSIONS
+void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count) {
+    mpack_assert(data != NULL, "data pointer for ext of type %i and %i bytes is NULL", exttype, (int)count);
+    mpack_start_ext(writer, exttype, count);
+    mpack_write_bytes(writer, data, count);
+    mpack_finish_ext(writer);
+}
+#endif
+
+void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count) {
+    mpack_assert(data != NULL, "data pointer for %i bytes is NULL", (int)count);
+    mpack_writer_track_bytes(writer, count);
+    mpack_write_native(writer, data, count);
+}
+
+void mpack_write_cstr(mpack_writer_t* writer, const char* cstr) {
+    mpack_assert(cstr != NULL, "cstr pointer is NULL");
+    size_t length = mpack_strlen(cstr);
+    if (length > MPACK_UINT32_MAX)
+        mpack_writer_flag_error(writer, mpack_error_invalid);
+    mpack_write_str(writer, cstr, (uint32_t)length);
+}
+
+void mpack_write_cstr_or_nil(mpack_writer_t* writer, const char* cstr) {
+    if (cstr)
+        mpack_write_cstr(writer, cstr);
+    else
+        mpack_write_nil(writer);
+}
+
+void mpack_write_utf8(mpack_writer_t* writer, const char* str, uint32_t length) {
+    mpack_assert(str != NULL, "data for string of length %i is NULL", (int)length);
+    if (!mpack_utf8_check(str, length)) {
+        mpack_writer_flag_error(writer, mpack_error_invalid);
+        return;
+    }
+    mpack_write_str(writer, str, length);
+}
+
+void mpack_write_utf8_cstr(mpack_writer_t* writer, const char* cstr) {
+    mpack_assert(cstr != NULL, "cstr pointer is NULL");
+    size_t length = mpack_strlen(cstr);
+    if (length > MPACK_UINT32_MAX) {
+        mpack_writer_flag_error(writer, mpack_error_invalid);
+        return;
+    }
+    mpack_write_utf8(writer, cstr, (uint32_t)length);
+}
+
+void mpack_write_utf8_cstr_or_nil(mpack_writer_t* writer, const char* cstr) {
+    if (cstr)
+        mpack_write_utf8_cstr(writer, cstr);
+    else
+        mpack_write_nil(writer);
+}
+
+/*
+ * Builder implementation
+ *
+ * When a writer is in build mode, it diverts writes to an internal growable
+ * buffer. All elements other than builder start tags are encoded as normal
+ * into the builder buffer (even nested maps and arrays of known size, e.g.
+ * `mpack_start_array()`.) But for compound elements of unknown size, an
+ * mpack_build_t is written to the buffer instead.
+ *
+ * The mpack_build_t tracks everything needed to re-constitute the final
+ * message once all sizes are known. When the last build element is completed,
+ * the builder resolves the build by walking through the builds, outputting the
+ * final encoded tag, and copying everything in between to the writer's true
+ * buffer.
+ *
+ * To make things extra complicated, the builder buffer is not contiguous. It's
+ * allocated in pages, where the first page may be an internal page in the
+ * writer. But, each mpack_build_t must itself be contiguous and aligned
+ * properly within the buffer. This means bytes can be skipped (and wasted)
+ * before the builds or at the end of pages.
+ *
+ * To keep track of this, builds store both their element count and the number
+ * of encoded bytes that follow, and pages store the number of bytes used. As
+ * elements are written, each element adds to the count in the current open
+ * build, and the number of bytes written adds to the current page and the byte
+ * count in the last started build (whether or not it is completed.)
+ */
+
+#if MPACK_BUILDER
+
+#ifdef MPACK_ALIGNOF
+    #define MPACK_BUILD_ALIGNMENT MPACK_ALIGNOF(mpack_build_t)
+#else
+    // without alignof, we just align to the greater of size_t, void* and uint64_t.
+    // (we do this even though we don't have uint64_t in it in case we add it later.)
+    #define MPACK_BUILD_ALIGNMENT_MAX(x, y) ((x) > (y) ? (x) : (y))
+    #define MPACK_BUILD_ALIGNMENT (MPACK_BUILD_ALIGNMENT_MAX(sizeof(void*), \
+                MPACK_BUILD_ALIGNMENT_MAX(sizeof(size_t), sizeof(uint64_t))))
+#endif
+
+static inline void mpack_builder_check_sizes(mpack_writer_t* writer) {
+
+    // We check internal and page sizes here so that we don't have to check
+    // them again. A new page with a build in it will have a page header,
+    // build, and minimum space for a tag. This will perform horribly and waste
+    // tons of memory if the page size is small, so you're best off just
+    // sticking with the defaults.
+    //
+    // These are all known at compile time, so if they are large
+    // enough this function should trivially optimize to a no-op.
+
+    #if MPACK_BUILDER_INTERNAL_STORAGE
+    // make sure the internal storage is big enough to be useful
+    MPACK_STATIC_ASSERT(MPACK_BUILDER_INTERNAL_STORAGE_SIZE >= (sizeof(mpack_builder_page_t) +
+            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE),
+            "MPACK_BUILDER_INTERNAL_STORAGE_SIZE is too small to be useful!");
+    if (MPACK_BUILDER_INTERNAL_STORAGE_SIZE < (sizeof(mpack_builder_page_t) +
+            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE))
+    {
+        mpack_break("MPACK_BUILDER_INTERNAL_STORAGE_SIZE is too small to be useful!");
+        mpack_writer_flag_error(writer, mpack_error_bug);
+    }
+    #endif
+
+    // make sure the builder page size is big enough to be useful
+    MPACK_STATIC_ASSERT(MPACK_BUILDER_PAGE_SIZE >= (sizeof(mpack_builder_page_t) +
+            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE),
+            "MPACK_BUILDER_PAGE_SIZE is too small to be useful!");
+    if (MPACK_BUILDER_PAGE_SIZE < (sizeof(mpack_builder_page_t) +
+            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE))
+    {
+        mpack_break("MPACK_BUILDER_PAGE_SIZE is too small to be useful!");
+        mpack_writer_flag_error(writer, mpack_error_bug);
+    }
+}
+
+static inline size_t mpack_builder_page_size(mpack_writer_t* writer, mpack_builder_page_t* page) {
+    #if MPACK_BUILDER_INTERNAL_STORAGE
+    if ((char*)page == writer->builder.internal)
+        return sizeof(writer->builder.internal);
+    #else
+    (void)writer;
+    (void)page;
+    #endif
+    return MPACK_BUILDER_PAGE_SIZE;
+}
+
+static inline size_t mpack_builder_align_build(size_t bytes_used) {
+    size_t offset = bytes_used;
+    offset += MPACK_BUILD_ALIGNMENT - 1;
+    offset -= offset % MPACK_BUILD_ALIGNMENT;
+    mpack_log("aligned %zi to %zi\n", bytes_used, offset);
+    return offset;
+}
+
+static inline void mpack_builder_free_page(mpack_writer_t* writer, mpack_builder_page_t* page) {
+    mpack_log("freeing page %p\n", (void*)page);
+    #if MPACK_BUILDER_INTERNAL_STORAGE
+    if ((char*)page == writer->builder.internal)
+        return;
+    #else
+    (void)writer;
+    #endif
+    MPACK_FREE(page);
+}
+
+static inline size_t mpack_builder_page_remaining(mpack_writer_t* writer, mpack_builder_page_t* page) {
+    return mpack_builder_page_size(writer, page) - page->bytes_used;
+}
+
+static void mpack_builder_configure_buffer(mpack_writer_t* writer) {
+    if (mpack_writer_error(writer) != mpack_ok)
+        return;
+    mpack_builder_t* builder = &writer->builder;
+
+    mpack_builder_page_t* page = builder->current_page;
+    mpack_assert(page != NULL, "page is null??");
+
+    // This diverts the writer into the remainder of the current page of our
+    // build buffer.
+    writer->buffer = (char*)page + page->bytes_used;
+    writer->position = (char*)page + page->bytes_used;
+    writer->end = (char*)page + mpack_builder_page_size(writer, page);
+    mpack_log("configuring buffer from %p to %p\n", (void*)writer->position, (void*)writer->end);
+}
+
+static void mpack_builder_add_page(mpack_writer_t* writer) {
+    mpack_builder_t* builder = &writer->builder;
+    mpack_assert(writer->error == mpack_ok);
+
+    mpack_log("adding a page.\n");
+    mpack_builder_page_t* page = (mpack_builder_page_t*)MPACK_MALLOC(MPACK_BUILDER_PAGE_SIZE);
+    if (page == NULL) {
+        mpack_writer_flag_error(writer, mpack_error_memory);
+        return;
+    }
+
+    page->next = NULL;
+    page->bytes_used = sizeof(mpack_builder_page_t);
+    builder->current_page->next = page;
+    builder->current_page = page;
+}
+
+// Checks how many bytes the writer wrote to the page, adding it to the page's
+// bytes_used. This must be followed up with mpack_builder_configure_buffer()
+// (after adding a new page, build, etc) to reset the writer's buffer pointers.
+static void mpack_builder_apply_writes(mpack_writer_t* writer) {
+    mpack_assert(writer->error == mpack_ok);
+    mpack_builder_t* builder = &writer->builder;
+    mpack_log("latest build is %p\n", (void*)builder->latest_build);
+
+    // The difference between buffer and current is the number of bytes that
+    // were written to the page.
+    size_t bytes_written = (size_t)(writer->position - writer->buffer);
+    mpack_log("applying write of %zi bytes to build %p\n", bytes_written, (void*)builder->latest_build);
+
+    mpack_assert(builder->current_page != NULL);
+    mpack_assert(builder->latest_build != NULL);
+    builder->current_page->bytes_used += bytes_written;
+    builder->latest_build->bytes += bytes_written;
+    mpack_log("latest build %p now has %zi bytes\n", (void*)builder->latest_build, builder->latest_build->bytes);
+}
+
+static void mpack_builder_flush(mpack_writer_t* writer) {
+    mpack_assert(writer->error == mpack_ok);
+    mpack_builder_apply_writes(writer);
+    mpack_builder_add_page(writer);
+    mpack_builder_configure_buffer(writer);
+}
+
+MPACK_NOINLINE static void mpack_builder_begin(mpack_writer_t* writer) {
+    mpack_builder_t* builder = &writer->builder;
+    mpack_assert(writer->error == mpack_ok);
+    mpack_assert(builder->current_build == NULL);
+    mpack_assert(builder->latest_build == NULL);
+    mpack_assert(builder->pages == NULL);
+
+    // If this is the first build, we need to stash the real buffer backing our
+    // writer. We'll be diverting the writer to our build buffer.
+    builder->stash_buffer = writer->buffer;
+    builder->stash_position = writer->position;
+    builder->stash_end = writer->end;
+
+    mpack_builder_page_t* page;
+
+    // we've checked that both these sizes are large enough above.
+    #if MPACK_BUILDER_INTERNAL_STORAGE
+    page = (mpack_builder_page_t*)builder->internal;
+    mpack_log("beginning builder with internal storage %p\n", (void*)page);
+    #else
+    page = (mpack_builder_page_t*)MPACK_MALLOC(MPACK_BUILDER_PAGE_SIZE);
+    if (page == NULL) {
+        mpack_writer_flag_error(writer, mpack_error_memory);
+        return;
+    }
+    mpack_log("beginning builder with allocated page %p\n", (void*)page);
+    #endif
+
+    page->next = NULL;
+    page->bytes_used = sizeof(mpack_builder_page_t);
+    builder->pages = page;
+    builder->current_page = page;
+}
+
+static void mpack_builder_build(mpack_writer_t* writer, mpack_type_t type) {
+    mpack_builder_check_sizes(writer);
+    if (mpack_writer_error(writer) != mpack_ok)
+        return;
+
+    mpack_writer_track_element(writer);
+    mpack_writer_track_push_builder(writer, type);
+
+    mpack_builder_t* builder = &writer->builder;
+
+    if (builder->current_build == NULL) {
+        mpack_builder_begin(writer);
+    } else {
+        mpack_builder_apply_writes(writer);
+    }
+    if (mpack_writer_error(writer) != mpack_ok)
+        return;
+
+    // find aligned space for a new build. if there isn't enough space in the
+    // current page, we discard the remaining space in it and allocate a new
+    // page.
+    size_t offset = mpack_builder_align_build(builder->current_page->bytes_used);
+    if (offset + sizeof(mpack_build_t) > mpack_builder_page_size(writer, builder->current_page)) {
+        mpack_log("not enough space for a build. %zi bytes used of %zi in this page\n",
+                builder->current_page->bytes_used, mpack_builder_page_size(writer, builder->current_page));
+        mpack_builder_add_page(writer);
+        // there is always enough space in a fresh page.
+        offset = mpack_builder_align_build(builder->current_page->bytes_used);
+    }
+
+    // allocate the build within the page. note that we don't keep track of the
+    // space wasted due to the offset. instead the previous build has stored
+    // how many bytes follow it, and we'll redo this offset calculation to find
+    // this build after it.
+    mpack_builder_page_t* page = builder->current_page;
+    page->bytes_used = offset + sizeof(mpack_build_t);
+    mpack_assert(page->bytes_used <= mpack_builder_page_size(writer, page));
+    mpack_build_t* build = (mpack_build_t*)((char*)page + offset);
+    mpack_log("created new build %p within page %p, which now has %zi bytes used\n",
+            (void*)build, (void*)page, page->bytes_used);
+
+    // configure the new build
+    build->parent = builder->current_build;
+    build->bytes = 0;
+    build->count = 0;
+    build->type = type;
+    build->key_needs_value = false;
+    build->nested_compound_elements = 0;
+
+    mpack_log("setting current and latest build to new build %p\n", (void*)build);
+    builder->current_build = build;
+    builder->latest_build = build;
+
+    // we always need to provide a buffer that meets the minimum buffer size.
+    // if there isn't enough space, we discard the remaining space in the
+    // current page and allocate a new one.
+    if (mpack_builder_page_remaining(writer, page) < MPACK_WRITER_MINIMUM_BUFFER_SIZE) {
+        mpack_log("less than minimum buffer size in current page. %zi bytes used of %zi in this page\n",
+                builder->current_page->bytes_used, mpack_builder_page_size(writer, builder->current_page));
+        mpack_builder_add_page(writer);
+        if (mpack_writer_error(writer) != mpack_ok)
+            return;
+    }
+    mpack_assert(mpack_builder_page_remaining(writer, builder->current_page) >= MPACK_WRITER_MINIMUM_BUFFER_SIZE);
+    mpack_builder_configure_buffer(writer);
+}
+
+MPACK_NOINLINE
+static void mpack_builder_resolve(mpack_writer_t* writer) {
+    mpack_builder_t* builder = &writer->builder;
+
+    // The starting page is the internal storage (if we have it), otherwise
+    // it's the first page in the array
+    mpack_builder_page_t* page =
+        #if MPACK_BUILDER_INTERNAL_STORAGE
+        (mpack_builder_page_t*)builder->internal
+        #else
+        builder->pages
+        #endif
+        ;
+
+    // We start by restoring the writer's original buffer so we can write the
+    // data for real.
+    writer->buffer = builder->stash_buffer;
+    writer->position = builder->stash_position;
+    writer->end = builder->stash_end;
+
+    // We can also close out the build now.
+    builder->current_build = NULL;
+    builder->latest_build = NULL;
+    builder->current_page = NULL;
+    builder->pages = NULL;
+
+    // the starting page always starts with the first build
+    size_t offset = mpack_builder_align_build(sizeof(mpack_builder_page_t));
+    mpack_build_t* build = (mpack_build_t*)((char*)page + offset);
+    mpack_log("starting resolve with build %p in page %p\n", (void*)build, (void*)page);
+
+    // encoded data immediately follows the build
+    offset += sizeof(mpack_build_t);
+
+    // Walk the list of builds, writing everything out in the buffer. Note that
+    // we don't check for errors anywhere. The lower-level write functions will
+    // all check for errors. We need to walk all pages anyway to free them, so
+    // there's not much point in optimizing an error path at the expense of the
+    // normal path.
+    while (true) {
+
+        // write out the container tag
+        mpack_log("writing out an %s with count %u followed by %zi bytes\n",
+                mpack_type_to_string(build->type), build->count, build->bytes);
+        switch (build->type) {
+            case mpack_type_map:
+                mpack_write_map_notrack(writer, build->count);
+                break;
+            case mpack_type_array:
+                mpack_write_array_notrack(writer, build->count);
+                break;
+            default:
+                mpack_break("invalid type in builder?");
+                mpack_writer_flag_error(writer, mpack_error_bug);
+                return;
+        }
+
+        // figure out how many bytes follow this container. we're going to be
+        // freeing pages as we write, so we need to be done with this build.
+        size_t left = build->bytes;
+        build = NULL;
+
+        // write out all bytes following this container
+        while (left > 0) {
+            size_t bytes_used = page->bytes_used;
+            if (offset < bytes_used) {
+                size_t step = bytes_used - offset;
+                if (step > left)
+                    step = left;
+                mpack_log("writing out %zi bytes starting at %p in page %p\n",
+                        step, (void*)((char*)page + offset), (void*)page);
+                mpack_write_native(writer, (char*)page + offset, step);
+                offset += step;
+                left -= step;
+            }
+
+            if (left == 0) {
+                mpack_log("done writing bytes for this build\n");
+                break;
+            }
+
+            // still need to write more bytes. free this page and jump to the
+            // next one.
+            mpack_builder_page_t* next_page = page->next;
+            mpack_builder_free_page(writer, page);
+            page = next_page;
+            // bytes on the next page immediately follow the header.
+            offset = sizeof(mpack_builder_page_t);
+        }
+
+        // now see if we can find another build.
+        offset = mpack_builder_align_build(offset);
+        if (offset + sizeof(mpack_build_t) >= mpack_builder_page_size(writer, page)) {
+            mpack_log("not enough room in this page for another build\n");
+            mpack_builder_page_t* next_page = page->next;
+            mpack_builder_free_page(writer, page);
+            page = next_page;
+            if (page == NULL) {
+                mpack_log("no more pages\n");
+                // there are no more pages. we're done.
+                break;
+            }
+            offset = mpack_builder_align_build(sizeof(mpack_builder_page_t));
+        }
+        if (offset + sizeof(mpack_build_t) > page->bytes_used) {
+            // there is no more data. we're done.
+            mpack_log("no more data\n");
+            mpack_builder_free_page(writer, page);
+            break;
+        }
+
+        // we've found another build. loop around!
+        build = (mpack_build_t*)((char*)page + offset);
+        offset += sizeof(mpack_build_t);
+        mpack_log("found build %p\n", (void*)build);
+    }
+
+    mpack_log("done resolve.\n");
+}
+
+static void mpack_builder_complete(mpack_writer_t* writer, mpack_type_t type) {
+    if (mpack_writer_error(writer) != mpack_ok)
+        return;
+
+    mpack_writer_track_pop_builder(writer, type);
+    mpack_builder_t* builder = &writer->builder;
+    mpack_assert(builder->current_build != NULL, "no build in progress!");
+    mpack_assert(builder->latest_build != NULL, "missing latest build!");
+    mpack_assert(builder->current_build->type == type, "completing wrong type!");
+    mpack_log("completing build %p\n", (void*)builder->current_build);
+
+    if (builder->current_build->key_needs_value) {
+        mpack_break("an odd number of elements were written in a map!");
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+
+    if (builder->current_build->nested_compound_elements != 0) {
+        mpack_break("there is a nested unfinished non-build map or array in this build.");
+        mpack_writer_flag_error(writer, mpack_error_bug);
+        return;
+    }
+
+    // We need to apply whatever writes have been made to the current build
+    // before popping it.
+    mpack_builder_apply_writes(writer);
+
+    // For a nested build, we just switch the current build back to its parent.
+    if (builder->current_build->parent != NULL) {
+        mpack_log("setting current build to parent build %p. latest is still %p.\n",
+                (void*)builder->current_build->parent, (void*)builder->latest_build);
+        builder->current_build = builder->current_build->parent;
+        mpack_builder_configure_buffer(writer);
+    } else {
+        // We're completing the final build.
+        mpack_builder_resolve(writer);
+    }
+}
+
+void mpack_build_map(mpack_writer_t* writer) {
+    mpack_builder_build(writer, mpack_type_map);
+}
+
+void mpack_build_array(mpack_writer_t* writer) {
+    mpack_builder_build(writer, mpack_type_array);
+}
+
+void mpack_complete_map(mpack_writer_t* writer) {
+    mpack_builder_complete(writer, mpack_type_map);
+}
+
+void mpack_complete_array(mpack_writer_t* writer) {
+    mpack_builder_complete(writer, mpack_type_array);
+}
+
+#endif // MPACK_BUILDER
+#endif // MPACK_WRITER
+
+MPACK_SILENCE_WARNINGS_END
+
+/* mpack/mpack-reader.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-reader.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+
+#if MPACK_READER
+
+static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count);
+
+void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count) {
+    mpack_assert(buffer != NULL, "buffer is NULL");
+
+    mpack_memset(reader, 0, sizeof(*reader));
+    reader->buffer = buffer;
+    reader->size = size;
+    reader->data = buffer;
+    reader->end = buffer + count;
+
+    #if MPACK_READ_TRACKING
+    mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track));
+    #endif
+
+    mpack_log("===========================\n");
+    mpack_log("initializing reader with buffer size %i\n", (int)size);
+}
+
+void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error) {
+    mpack_memset(reader, 0, sizeof(*reader));
+    reader->error = error;
+
+    mpack_log("===========================\n");
+    mpack_log("initializing reader error state %i\n", (int)error);
+}
+
+void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count) {
+    mpack_assert(data != NULL, "data is NULL");
+
+    mpack_memset(reader, 0, sizeof(*reader));
+    reader->data = data;
+    reader->end = data + count;
+
+    #if MPACK_READ_TRACKING
+    mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track));
+    #endif
+
+    mpack_log("===========================\n");
+    mpack_log("initializing reader with data size %i\n", (int)count);
+}
+
+void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill) {
+    MPACK_STATIC_ASSERT(MPACK_READER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE,
+            "minimum buffer size must fit any tag!");
+
+    if (reader->size == 0) {
+        mpack_break("cannot use fill function without a writeable buffer!");
+        mpack_reader_flag_error(reader, mpack_error_bug);
+        return;
+    }
+
+    if (reader->size < MPACK_READER_MINIMUM_BUFFER_SIZE) {
+        mpack_break("buffer size is %i, but minimum buffer size for fill is %i",
+                (int)reader->size, MPACK_READER_MINIMUM_BUFFER_SIZE);
+        mpack_reader_flag_error(reader, mpack_error_bug);
+        return;
+    }
+
+    reader->fill = fill;
+}
+
+void mpack_reader_set_skip(mpack_reader_t* reader, mpack_reader_skip_t skip) {
+    mpack_assert(reader->size != 0, "cannot use skip function without a writeable buffer!");
+    reader->skip = skip;
+}
+
+#if MPACK_STDIO
+static size_t mpack_file_reader_fill(mpack_reader_t* reader, char* buffer, size_t count) {
+    if (feof((FILE *)reader->context)) {
+       mpack_reader_flag_error(reader, mpack_error_eof);
+       return 0;
+    }
+    return fread((void*)buffer, 1, count, (FILE*)reader->context);
+}
+
+static void mpack_file_reader_skip(mpack_reader_t* reader, size_t count) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return;
+    FILE* file = (FILE*)reader->context;
+
+    // We call ftell() to test whether the stream is seekable
+    // without causing a file error.
+    if (ftell(file) >= 0) {
+        mpack_log("seeking forward %i bytes\n", (int)count);
+        if (fseek(file, (long int)count, SEEK_CUR) == 0)
+            return;
+        mpack_log("fseek() didn't return zero!\n");
+        if (ferror(file)) {
+            mpack_reader_flag_error(reader, mpack_error_io);
+            return;
+        }
+    }
+
+    // If the stream is not seekable, fall back to the fill function.
+    mpack_reader_skip_using_fill(reader, count);
+}
+
+static void mpack_file_reader_teardown(mpack_reader_t* reader) {
+    MPACK_FREE(reader->buffer);
+    reader->buffer = NULL;
+    reader->context = NULL;
+    reader->size = 0;
+    reader->fill = NULL;
+    reader->skip = NULL;
+    reader->teardown = NULL;
+}
+
+static void mpack_file_reader_teardown_close(mpack_reader_t* reader) {
+    FILE* file = (FILE*)reader->context;
+
+    if (file) {
+        int ret = fclose(file);
+        if (ret != 0)
+            mpack_reader_flag_error(reader, mpack_error_io);
+    }
+
+    mpack_file_reader_teardown(reader);
+}
+
+void mpack_reader_init_stdfile(mpack_reader_t* reader, FILE* file, bool close_when_done) {
+    mpack_assert(file != NULL, "file is NULL");
+
+    size_t capacity = MPACK_BUFFER_SIZE;
+    char* buffer = (char*)MPACK_MALLOC(capacity);
+    if (buffer == NULL) {
+        mpack_reader_init_error(reader, mpack_error_memory);
+        if (close_when_done) {
+            fclose(file);
+        }
+        return;
+    }
+
+    mpack_reader_init(reader, buffer, capacity, 0);
+    mpack_reader_set_context(reader, file);
+    mpack_reader_set_fill(reader, mpack_file_reader_fill);
+    mpack_reader_set_skip(reader, mpack_file_reader_skip);
+    mpack_reader_set_teardown(reader, close_when_done ?
+            mpack_file_reader_teardown_close :
+            mpack_file_reader_teardown);
+}
+
+void mpack_reader_init_filename(mpack_reader_t* reader, const char* filename) {
+    mpack_assert(filename != NULL, "filename is NULL");
+
+    FILE* file = fopen(filename, "rb");
+    if (file == NULL) {
+        mpack_reader_init_error(reader, mpack_error_io);
+        return;
+    }
+
+    mpack_reader_init_stdfile(reader, file, true);
+}
+#endif
+
+mpack_error_t mpack_reader_destroy(mpack_reader_t* reader) {
+
+    // clean up tracking, asserting if we're not already in an error state
+    #if MPACK_READ_TRACKING
+    mpack_reader_flag_if_error(reader, mpack_track_destroy(&reader->track, mpack_reader_error(reader) != mpack_ok));
+    #endif
+
+    if (reader->teardown)
+        reader->teardown(reader);
+    reader->teardown = NULL;
+
+    return reader->error;
+}
+
+size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return 0;
+
+    #if MPACK_READ_TRACKING
+    if (mpack_reader_flag_if_error(reader, mpack_track_check_empty(&reader->track)) != mpack_ok)
+        return 0;
+    #endif
+
+    if (data)
+        *data = reader->data;
+    return (size_t)(reader->end - reader->data);
+}
+
+void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error) {
+    mpack_log("reader %p setting error %i: %s\n", (void*)reader, (int)error, mpack_error_to_string(error));
+
+    if (reader->error == mpack_ok) {
+        reader->error = error;
+        reader->end = reader->data;
+        if (reader->error_fn)
+            reader->error_fn(reader, error);
+    }
+}
+
+// Loops on the fill function, reading between the minimum and
+// maximum number of bytes and flagging an error if it fails.
+MPACK_NOINLINE static size_t mpack_fill_range(mpack_reader_t* reader, char* p, size_t min_bytes, size_t max_bytes) {
+    mpack_assert(reader->fill != NULL, "mpack_fill_range() called with no fill function?");
+    mpack_assert(min_bytes > 0, "cannot fill zero bytes!");
+    mpack_assert(max_bytes >= min_bytes, "min_bytes %i cannot be larger than max_bytes %i!",
+            (int)min_bytes, (int)max_bytes);
+
+    size_t count = 0;
+    while (count < min_bytes) {
+        size_t read = reader->fill(reader, p + count, max_bytes - count);
+
+        // Reader fill functions can flag an error or return 0 on failure. We
+        // also guard against functions that return -1 just in case.
+        if (mpack_reader_error(reader) != mpack_ok)
+            return 0;
+        if (read == 0 || read == ((size_t)(-1))) {
+            mpack_reader_flag_error(reader, mpack_error_io);
+            return 0;
+        }
+
+        count += read;
+    }
+    return count;
+}
+
+MPACK_NOINLINE bool mpack_reader_ensure_straddle(mpack_reader_t* reader, size_t count) {
+    mpack_assert(count != 0, "cannot ensure zero bytes!");
+    mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
+
+    mpack_assert(count > (size_t)(reader->end - reader->data),
+            "straddling ensure requested for %i bytes, but there are %i bytes "
+            "left in buffer. call mpack_reader_ensure() instead",
+            (int)count, (int)(reader->end - reader->data));
+
+    // we'll need a fill function to get more data. if there's no
+    // fill function, the buffer should contain an entire MessagePack
+    // object, so we raise mpack_error_invalid instead of mpack_error_io
+    // on truncated data.
+    if (reader->fill == NULL) {
+        mpack_reader_flag_error(reader, mpack_error_invalid);
+        return false;
+    }
+
+    // we need enough space in the buffer. if the buffer is not
+    // big enough, we return mpack_error_too_big (since this is
+    // for an in-place read larger than the buffer size.)
+    if (count > reader->size) {
+        mpack_reader_flag_error(reader, mpack_error_too_big);
+        return false;
+    }
+
+    // move the existing data to the start of the buffer
+    size_t left = (size_t)(reader->end - reader->data);
+    mpack_memmove(reader->buffer, reader->data, left);
+    reader->end -= reader->data - reader->buffer;
+    reader->data = reader->buffer;
+
+    // read at least the necessary number of bytes, accepting up to the
+    // buffer size
+    size_t read = mpack_fill_range(reader, reader->buffer + left,
+            count - left, reader->size - left);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return false;
+    reader->end += read;
+    return true;
+}
+
+// Reads count bytes into p. Used when there are not enough bytes
+// left in the buffer to satisfy a read.
+MPACK_NOINLINE void mpack_read_native_straddle(mpack_reader_t* reader, char* p, size_t count) {
+    mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+    if (mpack_reader_error(reader) != mpack_ok) {
+        mpack_memset(p, 0, count);
+        return;
+    }
+
+    size_t left = (size_t)(reader->end - reader->data);
+    mpack_log("big read for %i bytes into %p, %i left in buffer, buffer size %i\n",
+            (int)count, p, (int)left, (int)reader->size);
+
+    if (count <= left) {
+        mpack_assert(0,
+                "big read requested for %i bytes, but there are %i bytes "
+                "left in buffer. call mpack_read_native() instead",
+                (int)count, (int)left);
+        mpack_reader_flag_error(reader, mpack_error_bug);
+        mpack_memset(p, 0, count);
+        return;
+    }
+
+    // we'll need a fill function to get more data. if there's no
+    // fill function, the buffer should contain an entire MessagePack
+    // object, so we raise mpack_error_invalid instead of mpack_error_io
+    // on truncated data.
+    if (reader->fill == NULL) {
+        mpack_reader_flag_error(reader, mpack_error_invalid);
+        mpack_memset(p, 0, count);
+        return;
+    }
+
+    if (reader->size == 0) {
+        // somewhat debatable what error should be returned here. when
+        // initializing a reader with an in-memory buffer it's not
+        // necessarily a bug if the data is blank; it might just have
+        // been truncated to zero. for this reason we return the same
+        // error as if the data was truncated.
+        mpack_reader_flag_error(reader, mpack_error_io);
+        mpack_memset(p, 0, count);
+        return;
+    }
+
+    // flush what's left of the buffer
+    if (left > 0) {
+        mpack_log("flushing %i bytes remaining in buffer\n", (int)left);
+        mpack_memcpy(p, reader->data, left);
+        count -= left;
+        p += left;
+        reader->data += left;
+    }
+
+    // if the remaining data needed is some small fraction of the
+    // buffer size, we'll try to fill the buffer as much as possible
+    // and copy the needed data out.
+    if (count <= reader->size / MPACK_READER_SMALL_FRACTION_DENOMINATOR) {
+        size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size);
+        if (mpack_reader_error(reader) != mpack_ok)
+            return;
+        mpack_memcpy(p, reader->buffer, count);
+        reader->data = reader->buffer + count;
+        reader->end = reader->buffer + read;
+
+    // otherwise we read the remaining data directly into the target.
+    } else {
+        mpack_log("reading %i additional bytes\n", (int)count);
+        mpack_fill_range(reader, p, count, count);
+    }
+}
+
+MPACK_NOINLINE static void mpack_skip_bytes_straddle(mpack_reader_t* reader, size_t count) {
+
+    // we'll need at least a fill function to skip more data. if there's
+    // no fill function, the buffer should contain an entire MessagePack
+    // object, so we raise mpack_error_invalid instead of mpack_error_io
+    // on truncated data. (see mpack_read_native_straddle())
+    if (reader->fill == NULL) {
+        mpack_log("reader has no fill function!\n");
+        mpack_reader_flag_error(reader, mpack_error_invalid);
+        return;
+    }
+
+    // discard whatever's left in the buffer
+    size_t left = (size_t)(reader->end - reader->data);
+    mpack_log("discarding %i bytes still in buffer\n", (int)left);
+    count -= left;
+    reader->data = reader->end;
+
+    // use the skip function if we've got one, and if we're trying
+    // to skip a lot of data. if we only need to skip some tiny
+    // fraction of the buffer size, it's probably better to just
+    // fill the buffer and skip from it instead of trying to seek.
+    if (reader->skip && count > reader->size / 16) {
+        mpack_log("calling skip function for %i bytes\n", (int)count);
+        reader->skip(reader, count);
+        return;
+    }
+
+    mpack_reader_skip_using_fill(reader, count);
+}
+
+void mpack_skip_bytes(mpack_reader_t* reader, size_t count) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return;
+    mpack_log("skip requested for %i bytes\n", (int)count);
+
+    mpack_reader_track_bytes(reader, count);
+
+    // check if we have enough in the buffer already
+    size_t left = (size_t)(reader->end - reader->data);
+    if (left >= count) {
+        mpack_log("skipping %u bytes still in buffer\n", (uint32_t)count);
+        reader->data += count;
+        return;
+    }
+
+    mpack_skip_bytes_straddle(reader, count);
+}
+
+MPACK_NOINLINE static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count) {
+    mpack_assert(reader->fill != NULL, "missing fill function!");
+    mpack_assert(reader->data == reader->end, "there are bytes left in the buffer!");
+    mpack_assert(reader->error == mpack_ok, "should not have called this in an error state (%i)", reader->error);
+    mpack_log("skip using fill for %i bytes\n", (int)count);
+
+    // fill and discard multiples of the buffer size
+    while (count > reader->size) {
+        mpack_log("filling and discarding buffer of %i bytes\n", (int)reader->size);
+        if (mpack_fill_range(reader, reader->buffer, reader->size, reader->size) < reader->size) {
+            mpack_reader_flag_error(reader, mpack_error_io);
+            return;
+        }
+        count -= reader->size;
+    }
+
+    // fill the buffer as much as possible
+    reader->data = reader->buffer;
+    size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size);
+    if (read < count) {
+        mpack_reader_flag_error(reader, mpack_error_io);
+        return;
+    }
+    reader->end = reader->data + read;
+    mpack_log("filled %i bytes into buffer; discarding %i bytes\n", (int)read, (int)count);
+    reader->data += count;
+}
+
+void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count) {
+    mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)count);
+    mpack_reader_track_bytes(reader, count);
+    mpack_read_native(reader, p, count);
+}
+
+void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count) {
+    mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)byte_count);
+    mpack_reader_track_str_bytes_all(reader, byte_count);
+    mpack_read_native(reader, p, byte_count);
+
+    if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(p, byte_count))
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+static void mpack_read_cstr_unchecked(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
+    mpack_assert(buf != NULL, "destination for read of %i bytes is NULL", (int)byte_count);
+    mpack_assert(buffer_size >= 1, "buffer size is zero; you must have room for at least a null-terminator");
+
+    if (mpack_reader_error(reader)) {
+        buf[0] = 0;
+        return;
+    }
+
+    if (byte_count > buffer_size - 1) {
+        mpack_reader_flag_error(reader, mpack_error_too_big);
+        buf[0] = 0;
+        return;
+    }
+
+    mpack_reader_track_str_bytes_all(reader, byte_count);
+    mpack_read_native(reader, buf, byte_count);
+    buf[byte_count] = 0;
+}
+
+void mpack_read_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
+    mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count);
+
+    // check for null bytes
+    if (mpack_reader_error(reader) == mpack_ok && !mpack_str_check_no_null(buf, byte_count)) {
+        buf[0] = 0;
+        mpack_reader_flag_error(reader, mpack_error_type);
+    }
+}
+
+void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
+    mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count);
+
+    // check encoding
+    if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check_no_null(buf, byte_count)) {
+        buf[0] = 0;
+        mpack_reader_flag_error(reader, mpack_error_type);
+    }
+}
+
+#ifdef MPACK_MALLOC
+// Reads native bytes with error callback disabled. This allows MPack reader functions
+// to hold an allocated buffer and read native data into it without leaking it in
+// case of a non-local jump (longjmp, throw) out of an error handler.
+static void mpack_read_native_noerrorfn(mpack_reader_t* reader, char* p, size_t count) {
+    mpack_assert(reader->error == mpack_ok, "cannot call if an error is already flagged!");
+    mpack_reader_error_t error_fn = reader->error_fn;
+    reader->error_fn = NULL;
+    mpack_read_native(reader, p, count);
+    reader->error_fn = error_fn;
+}
+
+char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated) {
+
+    // track the bytes first in case it jumps
+    mpack_reader_track_bytes(reader, count);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return NULL;
+
+    // cannot allocate zero bytes. this is not an error.
+    if (count == 0 && null_terminated == false)
+        return NULL;
+
+    // allocate data
+    char* data = (char*)MPACK_MALLOC(count + (null_terminated ? 1 : 0)); // TODO: can this overflow?
+    if (data == NULL) {
+        mpack_reader_flag_error(reader, mpack_error_memory);
+        return NULL;
+    }
+
+    // read with error callback disabled so we don't leak our buffer
+    mpack_read_native_noerrorfn(reader, data, count);
+
+    // report flagged errors
+    if (mpack_reader_error(reader) != mpack_ok) {
+        MPACK_FREE(data);
+        if (reader->error_fn)
+            reader->error_fn(reader, mpack_reader_error(reader));
+        return NULL;
+    }
+
+    if (null_terminated)
+        data[count] = '\0';
+    return data;
+}
+#endif
+
+// read inplace without tracking (since there are different
+// tracking modes for different inplace readers)
+static const char* mpack_read_bytes_inplace_notrack(mpack_reader_t* reader, size_t count) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return NULL;
+
+    // if we have enough bytes already in the buffer, we can return it directly.
+    if ((size_t)(reader->end - reader->data) >= count) {
+        const char* bytes = reader->data;
+        reader->data += count;
+        return bytes;
+    }
+
+    if (!mpack_reader_ensure(reader, count))
+        return NULL;
+
+    const char* bytes = reader->data;
+    reader->data += count;
+    return bytes;
+}
+
+const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count) {
+    mpack_reader_track_bytes(reader, count);
+    return mpack_read_bytes_inplace_notrack(reader, count);
+}
+
+const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count) {
+    mpack_reader_track_str_bytes_all(reader, count);
+    const char* str = mpack_read_bytes_inplace_notrack(reader, count);
+
+    if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(str, count)) {
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return NULL;
+    }
+
+    return str;
+}
+
+static size_t mpack_parse_tag(mpack_reader_t* reader, mpack_tag_t* tag) {
+    mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
+
+    if (!mpack_reader_ensure(reader, 1))
+        return 0;
+    uint8_t type = mpack_load_u8(reader->data);
+
+    // unfortunately, by far the fastest way to parse a tag is to switch
+    // on the first byte, and to explicitly list every possible byte. so for
+    // infix types, the list of cases is quite large.
+    //
+    // in size-optimized builds, we switch on the top four bits first to
+    // handle most infix types with a smaller jump table to save space.
+
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    switch (type >> 4) {
+
+        // positive fixnum
+        case 0x0: case 0x1: case 0x2: case 0x3:
+        case 0x4: case 0x5: case 0x6: case 0x7:
+            *tag = mpack_tag_make_uint(type);
+            return 1;
+
+        // negative fixnum
+        case 0xe: case 0xf:
+            *tag = mpack_tag_make_int((int8_t)type);
+            return 1;
+
+        // fixmap
+        case 0x8:
+            *tag = mpack_tag_make_map(type & ~0xf0u);
+            return 1;
+
+        // fixarray
+        case 0x9:
+            *tag = mpack_tag_make_array(type & ~0xf0u);
+            return 1;
+
+        // fixstr
+        case 0xa: case 0xb:
+            *tag = mpack_tag_make_str(type & ~0xe0u);
+            return 1;
+
+        // not one of the common infix types
+        default:
+            break;
+
+    }
+    #endif
+
+    // handle individual type tags
+    switch (type) {
+
+        #if !MPACK_OPTIMIZE_FOR_SIZE
+        // positive fixnum
+        case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
+        case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+        case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+        case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
+        case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
+        case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
+        case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
+        case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
+        case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
+        case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
+        case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
+        case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
+        case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
+        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
+        case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
+            *tag = mpack_tag_make_uint(type);
+            return 1;
+
+        // negative fixnum
+        case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7:
+        case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef:
+        case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7:
+        case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff:
+            *tag = mpack_tag_make_int((int8_t)type);
+            return 1;
+
+        // fixmap
+        case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
+        case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
+            *tag = mpack_tag_make_map(type & ~0xf0u);
+            return 1;
+
+        // fixarray
+        case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
+        case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
+            *tag = mpack_tag_make_array(type & ~0xf0u);
+            return 1;
+
+        // fixstr
+        case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7:
+        case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
+        case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
+        case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
+            *tag = mpack_tag_make_str(type & ~0xe0u);
+            return 1;
+        #endif
+
+        // nil
+        case 0xc0:
+            *tag = mpack_tag_make_nil();
+            return 1;
+
+        // bool
+        case 0xc2: case 0xc3:
+            *tag = mpack_tag_make_bool((bool)(type & 1));
+            return 1;
+
+        // bin8
+        case 0xc4:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN8))
+                return 0;
+            *tag = mpack_tag_make_bin(mpack_load_u8(reader->data + 1));
+            return MPACK_TAG_SIZE_BIN8;
+
+        // bin16
+        case 0xc5:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN16))
+                return 0;
+            *tag = mpack_tag_make_bin(mpack_load_u16(reader->data + 1));
+            return MPACK_TAG_SIZE_BIN16;
+
+        // bin32
+        case 0xc6:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN32))
+                return 0;
+            *tag = mpack_tag_make_bin(mpack_load_u32(reader->data + 1));
+            return MPACK_TAG_SIZE_BIN32;
+
+        #if MPACK_EXTENSIONS
+        // ext8
+        case 0xc7:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT8))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 2), mpack_load_u8(reader->data + 1));
+            return MPACK_TAG_SIZE_EXT8;
+
+        // ext16
+        case 0xc8:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT16))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 3), mpack_load_u16(reader->data + 1));
+            return MPACK_TAG_SIZE_EXT16;
+
+        // ext32
+        case 0xc9:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT32))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 5), mpack_load_u32(reader->data + 1));
+            return MPACK_TAG_SIZE_EXT32;
+        #endif
+
+        // float
+        case 0xca:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FLOAT))
+                return 0;
+            #if MPACK_FLOAT
+            *tag = mpack_tag_make_float(mpack_load_float(reader->data + 1));
+            #else
+            *tag = mpack_tag_make_raw_float(mpack_load_u32(reader->data + 1));
+            #endif
+            return MPACK_TAG_SIZE_FLOAT;
+
+        // double
+        case 0xcb:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_DOUBLE))
+                return 0;
+            #if MPACK_DOUBLE
+            *tag = mpack_tag_make_double(mpack_load_double(reader->data + 1));
+            #else
+            *tag = mpack_tag_make_raw_double(mpack_load_u64(reader->data + 1));
+            #endif
+            return MPACK_TAG_SIZE_DOUBLE;
+
+        // uint8
+        case 0xcc:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U8))
+                return 0;
+            *tag = mpack_tag_make_uint(mpack_load_u8(reader->data + 1));
+            return MPACK_TAG_SIZE_U8;
+
+        // uint16
+        case 0xcd:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U16))
+                return 0;
+            *tag = mpack_tag_make_uint(mpack_load_u16(reader->data + 1));
+            return MPACK_TAG_SIZE_U16;
+
+        // uint32
+        case 0xce:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U32))
+                return 0;
+            *tag = mpack_tag_make_uint(mpack_load_u32(reader->data + 1));
+            return MPACK_TAG_SIZE_U32;
+
+        // uint64
+        case 0xcf:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U64))
+                return 0;
+            *tag = mpack_tag_make_uint(mpack_load_u64(reader->data + 1));
+            return MPACK_TAG_SIZE_U64;
+
+        // int8
+        case 0xd0:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I8))
+                return 0;
+            *tag = mpack_tag_make_int(mpack_load_i8(reader->data + 1));
+            return MPACK_TAG_SIZE_I8;
+
+        // int16
+        case 0xd1:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I16))
+                return 0;
+            *tag = mpack_tag_make_int(mpack_load_i16(reader->data + 1));
+            return MPACK_TAG_SIZE_I16;
+
+        // int32
+        case 0xd2:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I32))
+                return 0;
+            *tag = mpack_tag_make_int(mpack_load_i32(reader->data + 1));
+            return MPACK_TAG_SIZE_I32;
+
+        // int64
+        case 0xd3:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I64))
+                return 0;
+            *tag = mpack_tag_make_int(mpack_load_i64(reader->data + 1));
+            return MPACK_TAG_SIZE_I64;
+
+        #if MPACK_EXTENSIONS
+        // fixext1
+        case 0xd4:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT1))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 1);
+            return MPACK_TAG_SIZE_FIXEXT1;
+
+        // fixext2
+        case 0xd5:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT2))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 2);
+            return MPACK_TAG_SIZE_FIXEXT2;
+
+        // fixext4
+        case 0xd6:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT4))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 4);
+            return 2;
+
+        // fixext8
+        case 0xd7:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT8))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 8);
+            return MPACK_TAG_SIZE_FIXEXT8;
+
+        // fixext16
+        case 0xd8:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT16))
+                return 0;
+            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 16);
+            return MPACK_TAG_SIZE_FIXEXT16;
+        #endif
+
+        // str8
+        case 0xd9:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR8))
+                return 0;
+            *tag = mpack_tag_make_str(mpack_load_u8(reader->data + 1));
+            return MPACK_TAG_SIZE_STR8;
+
+        // str16
+        case 0xda:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR16))
+                return 0;
+            *tag = mpack_tag_make_str(mpack_load_u16(reader->data + 1));
+            return MPACK_TAG_SIZE_STR16;
+
+        // str32
+        case 0xdb:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR32))
+                return 0;
+            *tag = mpack_tag_make_str(mpack_load_u32(reader->data + 1));
+            return MPACK_TAG_SIZE_STR32;
+
+        // array16
+        case 0xdc:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY16))
+                return 0;
+            *tag = mpack_tag_make_array(mpack_load_u16(reader->data + 1));
+            return MPACK_TAG_SIZE_ARRAY16;
+
+        // array32
+        case 0xdd:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY32))
+                return 0;
+            *tag = mpack_tag_make_array(mpack_load_u32(reader->data + 1));
+            return MPACK_TAG_SIZE_ARRAY32;
+
+        // map16
+        case 0xde:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP16))
+                return 0;
+            *tag = mpack_tag_make_map(mpack_load_u16(reader->data + 1));
+            return MPACK_TAG_SIZE_MAP16;
+
+        // map32
+        case 0xdf:
+            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP32))
+                return 0;
+            *tag = mpack_tag_make_map(mpack_load_u32(reader->data + 1));
+            return MPACK_TAG_SIZE_MAP32;
+
+        // reserved
+        case 0xc1:
+            mpack_reader_flag_error(reader, mpack_error_invalid);
+            return 0;
+
+        #if !MPACK_EXTENSIONS
+        // ext
+        case 0xc7: // fallthrough
+        case 0xc8: // fallthrough
+        case 0xc9: // fallthrough
+        // fixext
+        case 0xd4: // fallthrough
+        case 0xd5: // fallthrough
+        case 0xd6: // fallthrough
+        case 0xd7: // fallthrough
+        case 0xd8:
+            mpack_reader_flag_error(reader, mpack_error_unsupported);
+            return 0;
+        #endif
+
+        #if MPACK_OPTIMIZE_FOR_SIZE
+        // any other bytes should have been handled by the infix switch
+        default:
+            break;
+        #endif
+    }
+
+    mpack_assert(0, "unreachable");
+    return 0;
+}
+
+mpack_tag_t mpack_read_tag(mpack_reader_t* reader) {
+    mpack_log("reading tag\n");
+
+    // make sure we can read a tag
+    if (mpack_reader_error(reader) != mpack_ok)
+        return mpack_tag_nil();
+    if (mpack_reader_track_element(reader) != mpack_ok)
+        return mpack_tag_nil();
+
+    mpack_tag_t tag = MPACK_TAG_ZERO;
+    size_t count = mpack_parse_tag(reader, &tag);
+    if (count == 0)
+        return mpack_tag_nil();
+
+    #if MPACK_READ_TRACKING
+    mpack_error_t track_error = mpack_ok;
+
+    switch (tag.type) {
+        case mpack_type_map:
+        case mpack_type_array:
+            track_error = mpack_track_push(&reader->track, tag.type, tag.v.n);
+            break;
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+        #endif
+        case mpack_type_str:
+        case mpack_type_bin:
+            track_error = mpack_track_push(&reader->track, tag.type, tag.v.l);
+            break;
+        default:
+            break;
+    }
+
+    if (track_error != mpack_ok) {
+        mpack_reader_flag_error(reader, track_error);
+        return mpack_tag_nil();
+    }
+    #endif
+
+    reader->data += count;
+    return tag;
+}
+
+mpack_tag_t mpack_peek_tag(mpack_reader_t* reader) {
+    mpack_log("peeking tag\n");
+
+    // make sure we can peek a tag
+    if (mpack_reader_error(reader) != mpack_ok)
+        return mpack_tag_nil();
+    if (mpack_reader_track_peek_element(reader) != mpack_ok)
+        return mpack_tag_nil();
+
+    mpack_tag_t tag = MPACK_TAG_ZERO;
+    if (mpack_parse_tag(reader, &tag) == 0)
+        return mpack_tag_nil();
+    return tag;
+}
+
+void mpack_discard(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (mpack_reader_error(reader))
+        return;
+    switch (var.type) {
+        case mpack_type_str:
+            mpack_skip_bytes(reader, var.v.l);
+            mpack_done_str(reader);
+            break;
+        case mpack_type_bin:
+            mpack_skip_bytes(reader, var.v.l);
+            mpack_done_bin(reader);
+            break;
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+            mpack_skip_bytes(reader, var.v.l);
+            mpack_done_ext(reader);
+            break;
+        #endif
+        case mpack_type_array: {
+            for (; var.v.n > 0; --var.v.n) {
+                mpack_discard(reader);
+                if (mpack_reader_error(reader))
+                    break;
+            }
+            mpack_done_array(reader);
+            break;
+        }
+        case mpack_type_map: {
+            for (; var.v.n > 0; --var.v.n) {
+                mpack_discard(reader);
+                mpack_discard(reader);
+                if (mpack_reader_error(reader))
+                    break;
+            }
+            mpack_done_map(reader);
+            break;
+        }
+        default:
+            break;
+    }
+}
+
+#if MPACK_EXTENSIONS
+mpack_timestamp_t mpack_read_timestamp(mpack_reader_t* reader, size_t size) {
+    mpack_timestamp_t timestamp = {0, 0};
+
+    if (size != 4 && size != 8 && size != 12) {
+        mpack_reader_flag_error(reader, mpack_error_invalid);
+        return timestamp;
+    }
+
+    char buf[12];
+    mpack_read_bytes(reader, buf, size);
+    mpack_done_ext(reader);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return timestamp;
+
+    switch (size) {
+        case 4:
+            timestamp.seconds = (int64_t)(uint64_t)mpack_load_u32(buf);
+            break;
+
+        case 8: {
+            uint64_t packed = mpack_load_u64(buf);
+            timestamp.seconds = (int64_t)(packed & ((MPACK_UINT64_C(1) << 34) - 1));
+            timestamp.nanoseconds = (uint32_t)(packed >> 34);
+            break;
+        }
+
+        case 12:
+            timestamp.nanoseconds = mpack_load_u32(buf);
+            timestamp.seconds = mpack_load_i64(buf + 4);
+            break;
+
+        default:
+            mpack_assert(false, "unreachable");
+            break;
+    }
+
+    if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
+        mpack_reader_flag_error(reader, mpack_error_invalid);
+        mpack_timestamp_t zero = {0, 0};
+        return zero;
+    }
+
+    return timestamp;
+}
+#endif
+
+#if MPACK_READ_TRACKING
+void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) {
+    if (mpack_reader_error(reader) == mpack_ok)
+        mpack_reader_flag_if_error(reader, mpack_track_pop(&reader->track, type));
+}
+#endif
+
+#if MPACK_DEBUG && MPACK_STDIO
+static size_t mpack_print_read_prefix(mpack_reader_t* reader, size_t length, char* buffer, size_t buffer_size) {
+    if (length == 0)
+        return 0;
+
+    size_t read = (length < buffer_size) ? length : buffer_size;
+    mpack_read_bytes(reader, buffer, read);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return 0;
+
+    mpack_skip_bytes(reader, length - read);
+    return read;
+}
+
+static void mpack_print_element(mpack_reader_t* reader, mpack_print_t* print, size_t depth) {
+    mpack_tag_t val = mpack_read_tag(reader);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return;
+
+    // We read some bytes from bin and ext so we can print its prefix in hex.
+    char buffer[MPACK_PRINT_BYTE_COUNT];
+    size_t count = 0;
+    size_t i, j;
+
+    switch (val.type) {
+        case mpack_type_str:
+            mpack_print_append_cstr(print, "\"");
+            for (i = 0; i < val.v.l; ++i) {
+                char c;
+                mpack_read_bytes(reader, &c, 1);
+                if (mpack_reader_error(reader) != mpack_ok)
+                    return;
+                switch (c) {
+                    case '\n': mpack_print_append_cstr(print, "\\n"); break;
+                    case '\\': mpack_print_append_cstr(print, "\\\\"); break;
+                    case '"': mpack_print_append_cstr(print, "\\\""); break;
+                    default: mpack_print_append(print, &c, 1); break;
+                }
+            }
+            mpack_print_append_cstr(print, "\"");
+            mpack_done_str(reader);
+            return;
+
+        case mpack_type_array:
+            mpack_print_append_cstr(print, "[\n");
+            for (i = 0; i < val.v.n; ++i) {
+                for (j = 0; j < depth + 1; ++j)
+                    mpack_print_append_cstr(print, "    ");
+                mpack_print_element(reader, print, depth + 1);
+                if (mpack_reader_error(reader) != mpack_ok)
+                    return;
+                if (i != val.v.n - 1)
+                    mpack_print_append_cstr(print, ",");
+                mpack_print_append_cstr(print, "\n");
+            }
+            for (i = 0; i < depth; ++i)
+                mpack_print_append_cstr(print, "    ");
+            mpack_print_append_cstr(print, "]");
+            mpack_done_array(reader);
+            return;
+
+        case mpack_type_map:
+            mpack_print_append_cstr(print, "{\n");
+            for (i = 0; i < val.v.n; ++i) {
+                for (j = 0; j < depth + 1; ++j)
+                    mpack_print_append_cstr(print, "    ");
+                mpack_print_element(reader, print, depth + 1);
+                if (mpack_reader_error(reader) != mpack_ok)
+                    return;
+                mpack_print_append_cstr(print, ": ");
+                mpack_print_element(reader, print, depth + 1);
+                if (mpack_reader_error(reader) != mpack_ok)
+                    return;
+                if (i != val.v.n - 1)
+                    mpack_print_append_cstr(print, ",");
+                mpack_print_append_cstr(print, "\n");
+            }
+            for (i = 0; i < depth; ++i)
+                mpack_print_append_cstr(print, "    ");
+            mpack_print_append_cstr(print, "}");
+            mpack_done_map(reader);
+            return;
+
+        // The above cases return so as not to print a pseudo-json value. The
+        // below cases break and print pseudo-json.
+
+        case mpack_type_bin:
+            count = mpack_print_read_prefix(reader, mpack_tag_bin_length(&val), buffer, sizeof(buffer));
+            mpack_done_bin(reader);
+            break;
+
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+            count = mpack_print_read_prefix(reader, mpack_tag_ext_length(&val), buffer, sizeof(buffer));
+            mpack_done_ext(reader);
+            break;
+        #endif
+
+        default:
+            break;
+    }
+
+    char buf[256];
+    mpack_tag_debug_pseudo_json(val, buf, sizeof(buf), buffer, count);
+    mpack_print_append_cstr(print, buf);
+}
+
+static void mpack_print_and_destroy(mpack_reader_t* reader, mpack_print_t* print, size_t depth) {
+    size_t i;
+    for (i = 0; i < depth; ++i)
+        mpack_print_append_cstr(print, "    ");
+    mpack_print_element(reader, print, depth);
+
+    size_t remaining = mpack_reader_remaining(reader, NULL);
+
+    char buf[256];
+    if (mpack_reader_destroy(reader) != mpack_ok) {
+        mpack_snprintf(buf, sizeof(buf), "\n<mpack parsing error %s>", mpack_error_to_string(mpack_reader_error(reader)));
+        buf[sizeof(buf) - 1] = '\0';
+        mpack_print_append_cstr(print, buf);
+    } else if (remaining > 0) {
+        mpack_snprintf(buf, sizeof(buf), "\n<%i extra bytes at end of message>", (int)remaining);
+        buf[sizeof(buf) - 1] = '\0';
+        mpack_print_append_cstr(print, buf);
+    }
+}
+
+static void mpack_print_data(const char* data, size_t len, mpack_print_t* print, size_t depth) {
+    mpack_reader_t reader;
+    mpack_reader_init_data(&reader, data, len);
+    mpack_print_and_destroy(&reader, print, depth);
+}
+
+void mpack_print_data_to_buffer(const char* data, size_t data_size, char* buffer, size_t buffer_size) {
+    if (buffer_size == 0) {
+        mpack_assert(false, "buffer size is zero!");
+        return;
+    }
+
+    mpack_print_t print;
+    mpack_memset(&print, 0, sizeof(print));
+    print.buffer = buffer;
+    print.size = buffer_size;
+    mpack_print_data(data, data_size, &print, 0);
+    mpack_print_append(&print, "",  1); // null-terminator
+    mpack_print_flush(&print);
+
+    // we always make sure there's a null-terminator at the end of the buffer
+    // in case we ran out of space.
+    print.buffer[print.size - 1] = '\0';
+}
+
+void mpack_print_data_to_callback(const char* data, size_t size, mpack_print_callback_t callback, void* context) {
+    char buffer[1024];
+    mpack_print_t print;
+    mpack_memset(&print, 0, sizeof(print));
+    print.buffer = buffer;
+    print.size = sizeof(buffer);
+    print.callback = callback;
+    print.context = context;
+    mpack_print_data(data, size, &print, 0);
+    mpack_print_flush(&print);
+}
+
+void mpack_print_data_to_file(const char* data, size_t len, FILE* file) {
+    mpack_assert(data != NULL, "data is NULL");
+    mpack_assert(file != NULL, "file is NULL");
+
+    char buffer[1024];
+    mpack_print_t print;
+    mpack_memset(&print, 0, sizeof(print));
+    print.buffer = buffer;
+    print.size = sizeof(buffer);
+    print.callback = &mpack_print_file_callback;
+    print.context = file;
+
+    mpack_print_data(data, len, &print, 2);
+    mpack_print_append_cstr(&print, "\n");
+    mpack_print_flush(&print);
+}
+
+void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback, void* context) {
+    char buffer[1024];
+    mpack_print_t print;
+    mpack_memset(&print, 0, sizeof(print));
+    print.buffer = buffer;
+    print.size = sizeof(buffer);
+    print.callback = callback;
+    print.context = context;
+
+    mpack_reader_t reader;
+    mpack_reader_init_stdfile(&reader, file, false);
+    mpack_print_and_destroy(&reader, &print, 0);
+    mpack_print_flush(&print);
+}
+#endif
+
+#endif
+
+MPACK_SILENCE_WARNINGS_END
+
+/* mpack/mpack-expect.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-expect.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+
+#if MPACK_EXPECT
+
+
+// Helpers
+
+MPACK_STATIC_INLINE uint8_t mpack_expect_native_u8(mpack_reader_t* reader) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return 0;
+    uint8_t type;
+    if (!mpack_reader_ensure(reader, sizeof(type)))
+        return 0;
+    type = mpack_load_u8(reader->data);
+    reader->data += sizeof(type);
+    return type;
+}
+
+#if !MPACK_OPTIMIZE_FOR_SIZE
+MPACK_STATIC_INLINE uint16_t mpack_expect_native_u16(mpack_reader_t* reader) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return 0;
+    uint16_t type;
+    if (!mpack_reader_ensure(reader, sizeof(type)))
+        return 0;
+    type = mpack_load_u16(reader->data);
+    reader->data += sizeof(type);
+    return type;
+}
+
+MPACK_STATIC_INLINE uint32_t mpack_expect_native_u32(mpack_reader_t* reader) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return 0;
+    uint32_t type;
+    if (!mpack_reader_ensure(reader, sizeof(type)))
+        return 0;
+    type = mpack_load_u32(reader->data);
+    reader->data += sizeof(type);
+    return type;
+}
+#endif
+
+MPACK_STATIC_INLINE uint8_t mpack_expect_type_byte(mpack_reader_t* reader) {
+    mpack_reader_track_element(reader);
+    return mpack_expect_native_u8(reader);
+}
+
+
+// Basic Number Functions
+
+uint8_t mpack_expect_u8(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        if (var.v.u <= MPACK_UINT8_MAX)
+            return (uint8_t)var.v.u;
+    } else if (var.type == mpack_type_int) {
+        if (var.v.i >= 0 && var.v.i <= MPACK_UINT8_MAX)
+            return (uint8_t)var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+uint16_t mpack_expect_u16(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        if (var.v.u <= MPACK_UINT16_MAX)
+            return (uint16_t)var.v.u;
+    } else if (var.type == mpack_type_int) {
+        if (var.v.i >= 0 && var.v.i <= MPACK_UINT16_MAX)
+            return (uint16_t)var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+uint32_t mpack_expect_u32(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        if (var.v.u <= MPACK_UINT32_MAX)
+            return (uint32_t)var.v.u;
+    } else if (var.type == mpack_type_int) {
+        if (var.v.i >= 0 && var.v.i <= MPACK_UINT32_MAX)
+            return (uint32_t)var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+uint64_t mpack_expect_u64(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        return var.v.u;
+    } else if (var.type == mpack_type_int) {
+        if (var.v.i >= 0)
+            return (uint64_t)var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+int8_t mpack_expect_i8(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        if (var.v.u <= MPACK_INT8_MAX)
+            return (int8_t)var.v.u;
+    } else if (var.type == mpack_type_int) {
+        if (var.v.i >= MPACK_INT8_MIN && var.v.i <= MPACK_INT8_MAX)
+            return (int8_t)var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+int16_t mpack_expect_i16(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        if (var.v.u <= MPACK_INT16_MAX)
+            return (int16_t)var.v.u;
+    } else if (var.type == mpack_type_int) {
+        if (var.v.i >= MPACK_INT16_MIN && var.v.i <= MPACK_INT16_MAX)
+            return (int16_t)var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+int32_t mpack_expect_i32(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        if (var.v.u <= MPACK_INT32_MAX)
+            return (int32_t)var.v.u;
+    } else if (var.type == mpack_type_int) {
+        if (var.v.i >= MPACK_INT32_MIN && var.v.i <= MPACK_INT32_MAX)
+            return (int32_t)var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+int64_t mpack_expect_i64(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint) {
+        if (var.v.u <= MPACK_INT64_MAX)
+            return (int64_t)var.v.u;
+    } else if (var.type == mpack_type_int) {
+        return var.v.i;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+#if MPACK_FLOAT
+float mpack_expect_float(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint)
+        return (float)var.v.u;
+    if (var.type == mpack_type_int)
+        return (float)var.v.i;
+    if (var.type == mpack_type_float)
+        return var.v.f;
+
+    if (var.type == mpack_type_double) {
+        #if MPACK_DOUBLE
+        return (float)var.v.d;
+        #else
+        return mpack_shorten_raw_double_to_float(var.v.d);
+        #endif
+    }
+
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0.0f;
+}
+#endif
+
+#if MPACK_DOUBLE
+double mpack_expect_double(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_uint)
+        return (double)var.v.u;
+    else if (var.type == mpack_type_int)
+        return (double)var.v.i;
+    else if (var.type == mpack_type_float)
+        return (double)var.v.f;
+    else if (var.type == mpack_type_double)
+        return var.v.d;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0.0;
+}
+#endif
+
+#if MPACK_FLOAT
+float mpack_expect_float_strict(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_float)
+        return var.v.f;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0.0f;
+}
+#endif
+
+#if MPACK_DOUBLE
+double mpack_expect_double_strict(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_float)
+        return (double)var.v.f;
+    else if (var.type == mpack_type_double)
+        return var.v.d;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0.0;
+}
+#endif
+
+#if !MPACK_FLOAT
+uint32_t mpack_expect_raw_float(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_float)
+        return var.v.f;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+#endif
+
+#if !MPACK_DOUBLE
+uint64_t mpack_expect_raw_double(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_double)
+        return var.v.d;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+#endif
+
+
+// Ranged Number Functions
+//
+// All ranged functions are identical other than the type, so we
+// define their content with a macro. The prototypes are still written
+// out in full to support ctags/IDE tools.
+
+#define MPACK_EXPECT_RANGE_IMPL(name, type_t)                           \
+                                                                        \
+    /* make sure the range is sensible */                               \
+    mpack_assert(min_value <= max_value,                                \
+            "min_value %i must be less than or equal to max_value %i",  \
+            min_value, max_value);                                      \
+                                                                        \
+    /* read the value */                                                \
+    type_t val = mpack_expect_##name(reader);                           \
+    if (mpack_reader_error(reader) != mpack_ok)                         \
+        return min_value;                                               \
+                                                                        \
+    /* make sure it fits */                                             \
+    if (val < min_value || val > max_value) {                           \
+        mpack_reader_flag_error(reader, mpack_error_type);              \
+        return min_value;                                               \
+    }                                                                   \
+                                                                        \
+    return val;
+
+uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value) {MPACK_EXPECT_RANGE_IMPL(u8, uint8_t)}
+uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value) {MPACK_EXPECT_RANGE_IMPL(u16, uint16_t)}
+uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(u32, uint32_t)}
+uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value) {MPACK_EXPECT_RANGE_IMPL(u64, uint64_t)}
+
+int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value) {MPACK_EXPECT_RANGE_IMPL(i8, int8_t)}
+int16_t mpack_expect_i16_range(mpack_reader_t* reader, int16_t min_value, int16_t max_value) {MPACK_EXPECT_RANGE_IMPL(i16, int16_t)}
+int32_t mpack_expect_i32_range(mpack_reader_t* reader, int32_t min_value, int32_t max_value) {MPACK_EXPECT_RANGE_IMPL(i32, int32_t)}
+int64_t mpack_expect_i64_range(mpack_reader_t* reader, int64_t min_value, int64_t max_value) {MPACK_EXPECT_RANGE_IMPL(i64, int64_t)}
+
+#if MPACK_FLOAT
+float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value) {MPACK_EXPECT_RANGE_IMPL(float, float)}
+#endif
+#if MPACK_DOUBLE
+double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value) {MPACK_EXPECT_RANGE_IMPL(double, double)}
+#endif
+
+uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(map, uint32_t)}
+uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(array, uint32_t)}
+
+
+// Matching Number Functions
+
+void mpack_expect_uint_match(mpack_reader_t* reader, uint64_t value) {
+    if (mpack_expect_u64(reader) != value)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+void mpack_expect_int_match(mpack_reader_t* reader, int64_t value) {
+    if (mpack_expect_i64(reader) != value)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+
+// Other Basic Types
+
+void mpack_expect_nil(mpack_reader_t* reader) {
+    if (mpack_expect_type_byte(reader) != 0xc0)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+bool mpack_expect_bool(mpack_reader_t* reader) {
+    uint8_t type = mpack_expect_type_byte(reader);
+    if ((type & ~1) != 0xc2)
+        mpack_reader_flag_error(reader, mpack_error_type);
+    return (bool)(type & 1);
+}
+
+void mpack_expect_true(mpack_reader_t* reader) {
+    if (mpack_expect_bool(reader) != true)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+void mpack_expect_false(mpack_reader_t* reader) {
+    if (mpack_expect_bool(reader) != false)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+#if MPACK_EXTENSIONS
+mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader) {
+    mpack_timestamp_t zero = {0, 0};
+
+    mpack_tag_t tag = mpack_read_tag(reader);
+    if (tag.type != mpack_type_ext) {
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return zero;
+    }
+    if (mpack_tag_ext_exttype(&tag) != MPACK_EXTTYPE_TIMESTAMP) {
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return zero;
+    }
+
+    return mpack_read_timestamp(reader, mpack_tag_ext_length(&tag));
+}
+
+int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader) {
+    return mpack_expect_timestamp(reader).seconds;
+}
+#endif
+
+
+// Compound Types
+
+uint32_t mpack_expect_map(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_map)
+        return var.v.n;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count) {
+    if (mpack_expect_map(reader) != count)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+bool mpack_expect_map_or_nil(mpack_reader_t* reader, uint32_t* count) {
+    mpack_assert(count != NULL, "count cannot be NULL");
+
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_nil) {
+        *count = 0;
+        return false;
+    }
+    if (var.type == mpack_type_map) {
+        *count = var.v.n;
+        return true;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    *count = 0;
+    return false;
+}
+
+bool mpack_expect_map_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) {
+    mpack_assert(count != NULL, "count cannot be NULL");
+
+    bool has_map = mpack_expect_map_or_nil(reader, count);
+    if (has_map && *count > max_count) {
+        *count = 0;
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return false;
+    }
+    return has_map;
+}
+
+uint32_t mpack_expect_array(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_array)
+        return var.v.n;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count) {
+    if (mpack_expect_array(reader) != count)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+bool mpack_expect_array_or_nil(mpack_reader_t* reader, uint32_t* count) {
+    mpack_assert(count != NULL, "count cannot be NULL");
+
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_nil) {
+        *count = 0;
+        return false;
+    }
+    if (var.type == mpack_type_array) {
+        *count = var.v.n;
+        return true;
+    }
+    mpack_reader_flag_error(reader, mpack_error_type);
+    *count = 0;
+    return false;
+}
+
+bool mpack_expect_array_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) {
+    mpack_assert(count != NULL, "count cannot be NULL");
+
+    bool has_array = mpack_expect_array_or_nil(reader, count);
+    if (has_array && *count > max_count) {
+        *count = 0;
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return false;
+    }
+    return has_array;
+}
+
+#ifdef MPACK_MALLOC
+void* mpack_expect_array_alloc_impl(mpack_reader_t* reader, size_t element_size, uint32_t max_count, uint32_t* out_count, bool allow_nil) {
+    mpack_assert(out_count != NULL, "out_count cannot be NULL");
+    *out_count = 0;
+
+    uint32_t count;
+    bool has_array = true;
+    if (allow_nil)
+        has_array = mpack_expect_array_max_or_nil(reader, max_count, &count);
+    else
+        count = mpack_expect_array_max(reader, max_count);
+    if (mpack_reader_error(reader))
+        return NULL;
+
+    // size 0 is not an error; we return NULL for no elements.
+    if (count == 0) {
+        // we call mpack_done_array() automatically ONLY if we are using
+        // the _or_nil variant. this is the only way to allow nil and empty
+        // to work the same way.
+        if (allow_nil && has_array)
+            mpack_done_array(reader);
+        return NULL;
+    }
+
+    void* p = MPACK_MALLOC(element_size * count);
+    if (p == NULL) {
+        mpack_reader_flag_error(reader, mpack_error_memory);
+        return NULL;
+    }
+
+    *out_count = count;
+    return p;
+}
+#endif
+
+
+// Str, Bin and Ext Functions
+
+uint32_t mpack_expect_str(mpack_reader_t* reader) {
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_str)
+        return var.v.l;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+    #else
+    uint8_t type = mpack_expect_type_byte(reader);
+    uint32_t count;
+
+    if ((type >> 5) == 5) {
+        count = type & (uint8_t)~0xe0;
+    } else if (type == 0xd9) {
+        count = mpack_expect_native_u8(reader);
+    } else if (type == 0xda) {
+        count = mpack_expect_native_u16(reader);
+    } else if (type == 0xdb) {
+        count = mpack_expect_native_u32(reader);
+    } else {
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return 0;
+    }
+
+    #if MPACK_READ_TRACKING
+    mpack_reader_flag_if_error(reader, mpack_track_push(&reader->track, mpack_type_str, count));
+    #endif
+    return count;
+    #endif
+}
+
+size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize) {
+    mpack_assert(buf != NULL, "buf cannot be NULL");
+
+    size_t length = mpack_expect_str(reader);
+    if (mpack_reader_error(reader))
+        return 0;
+
+    if (length > bufsize) {
+        mpack_reader_flag_error(reader, mpack_error_too_big);
+        return 0;
+    }
+
+    mpack_read_bytes(reader, buf, length);
+    if (mpack_reader_error(reader))
+        return 0;
+
+    mpack_done_str(reader);
+    return length;
+}
+
+size_t mpack_expect_utf8(mpack_reader_t* reader, char* buf, size_t size) {
+    mpack_assert(buf != NULL, "buf cannot be NULL");
+
+    size_t length = mpack_expect_str_buf(reader, buf, size);
+
+    if (!mpack_utf8_check(buf, length)) {
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return 0;
+    }
+
+    return length;
+}
+
+uint32_t mpack_expect_bin(mpack_reader_t* reader) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_bin)
+        return var.v.l;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t bufsize) {
+    mpack_assert(buf != NULL, "buf cannot be NULL");
+
+    size_t binsize = mpack_expect_bin(reader);
+    if (mpack_reader_error(reader))
+        return 0;
+    if (binsize > bufsize) {
+        mpack_reader_flag_error(reader, mpack_error_too_big);
+        return 0;
+    }
+    mpack_read_bytes(reader, buf, binsize);
+    if (mpack_reader_error(reader))
+        return 0;
+    mpack_done_bin(reader);
+    return binsize;
+}
+
+void mpack_expect_bin_size_buf(mpack_reader_t* reader, char* buf, uint32_t size) {
+    mpack_assert(buf != NULL, "buf cannot be NULL");
+    mpack_expect_bin_size(reader, size);
+    mpack_read_bytes(reader, buf, size);
+    mpack_done_bin(reader);
+}
+
+#if MPACK_EXTENSIONS
+uint32_t mpack_expect_ext(mpack_reader_t* reader, int8_t* type) {
+    mpack_tag_t var = mpack_read_tag(reader);
+    if (var.type == mpack_type_ext) {
+        *type = mpack_tag_ext_exttype(&var);
+        return mpack_tag_ext_length(&var);
+    }
+    *type = 0;
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return 0;
+}
+
+size_t mpack_expect_ext_buf(mpack_reader_t* reader, int8_t* type, char* buf, size_t bufsize) {
+    mpack_assert(buf != NULL, "buf cannot be NULL");
+
+    size_t extsize = mpack_expect_ext(reader, type);
+    if (mpack_reader_error(reader))
+        return 0;
+    if (extsize > bufsize) {
+        *type = 0;
+        mpack_reader_flag_error(reader, mpack_error_too_big);
+        return 0;
+    }
+    mpack_read_bytes(reader, buf, extsize);
+    if (mpack_reader_error(reader)) {
+        *type = 0;
+        return 0;
+    }
+    mpack_done_ext(reader);
+    return extsize;
+}
+#endif
+
+void mpack_expect_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) {
+    uint32_t length = mpack_expect_str(reader);
+    mpack_read_cstr(reader, buf, bufsize, length);
+    mpack_done_str(reader);
+}
+
+void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) {
+    uint32_t length = mpack_expect_str(reader);
+    mpack_read_utf8_cstr(reader, buf, bufsize, length);
+    mpack_done_str(reader);
+}
+
+#ifdef MPACK_MALLOC
+static char* mpack_expect_cstr_alloc_unchecked(mpack_reader_t* reader, size_t maxsize, size_t* out_length) {
+    mpack_assert(out_length != NULL, "out_length cannot be NULL");
+    *out_length = 0;
+
+    // make sure argument makes sense
+    if (maxsize < 1) {
+        mpack_break("maxsize is zero; you must have room for at least a null-terminator");
+        mpack_reader_flag_error(reader, mpack_error_bug);
+        return NULL;
+    }
+
+    if (SIZE_MAX < MPACK_UINT32_MAX) {
+        if (maxsize > SIZE_MAX)
+            maxsize = SIZE_MAX;
+    } else {
+        if (maxsize > (size_t)MPACK_UINT32_MAX)
+            maxsize = (size_t)MPACK_UINT32_MAX;
+    }
+
+    size_t length = mpack_expect_str_max(reader, (uint32_t)maxsize - 1);
+    char* str = mpack_read_bytes_alloc_impl(reader, length, true);
+    mpack_done_str(reader);
+
+    if (str)
+        *out_length = length;
+    return str;
+}
+
+char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize) {
+    size_t length;
+    char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length);
+
+    if (str && !mpack_str_check_no_null(str, length)) {
+        MPACK_FREE(str);
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return NULL;
+    }
+
+    return str;
+}
+
+char* mpack_expect_utf8_cstr_alloc(mpack_reader_t* reader, size_t maxsize) {
+    size_t length;
+    char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length);
+
+    if (str && !mpack_utf8_check_no_null(str, length)) {
+        MPACK_FREE(str);
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return NULL;
+    }
+
+    return str;
+}
+#endif
+
+void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t len) {
+    mpack_assert(str != NULL, "str cannot be NULL");
+
+    // expect a str the correct length
+    if (len > MPACK_UINT32_MAX)
+        mpack_reader_flag_error(reader, mpack_error_type);
+    mpack_expect_str_length(reader, (uint32_t)len);
+    if (mpack_reader_error(reader))
+        return;
+    mpack_reader_track_bytes(reader, (uint32_t)len);
+
+    // check each byte one by one (matched strings are likely to be very small)
+    for (; len > 0; --len) {
+        if (mpack_expect_native_u8(reader) != *str++) {
+            mpack_reader_flag_error(reader, mpack_error_type);
+            return;
+        }
+    }
+
+    mpack_done_str(reader);
+}
+
+void mpack_expect_tag(mpack_reader_t* reader, mpack_tag_t expected) {
+    mpack_tag_t actual = mpack_read_tag(reader);
+    if (!mpack_tag_equal(actual, expected))
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+#ifdef MPACK_MALLOC
+char* mpack_expect_bin_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size) {
+    mpack_assert(size != NULL, "size cannot be NULL");
+    *size = 0;
+
+    if (SIZE_MAX < MPACK_UINT32_MAX) {
+        if (maxsize > SIZE_MAX)
+            maxsize = SIZE_MAX;
+    } else {
+        if (maxsize > (size_t)MPACK_UINT32_MAX)
+            maxsize = (size_t)MPACK_UINT32_MAX;
+    }
+
+    size_t length = mpack_expect_bin_max(reader, (uint32_t)maxsize);
+    if (mpack_reader_error(reader))
+        return NULL;
+
+    char* data = mpack_read_bytes_alloc(reader, length);
+    mpack_done_bin(reader);
+
+    if (data)
+        *size = length;
+    return data;
+}
+#endif
+
+#if MPACK_EXTENSIONS && defined(MPACK_MALLOC)
+char* mpack_expect_ext_alloc(mpack_reader_t* reader, int8_t* type, size_t maxsize, size_t* size) {
+    mpack_assert(size != NULL, "size cannot be NULL");
+    *size = 0;
+
+    if (SIZE_MAX < MPACK_UINT32_MAX) {
+        if (maxsize > SIZE_MAX)
+            maxsize = SIZE_MAX;
+    } else {
+        if (maxsize > (size_t)MPACK_UINT32_MAX)
+            maxsize = (size_t)MPACK_UINT32_MAX;
+    }
+
+    size_t length = mpack_expect_ext_max(reader, type, (uint32_t)maxsize);
+    if (mpack_reader_error(reader))
+        return NULL;
+
+    char* data = mpack_read_bytes_alloc(reader, length);
+    mpack_done_ext(reader);
+
+    if (data) {
+        *size = length;
+    } else {
+        *type = 0;
+    }
+    return data;
+}
+#endif
+
+size_t mpack_expect_enum(mpack_reader_t* reader, const char* strings[], size_t count) {
+
+    // read the string in-place
+    size_t keylen = mpack_expect_str(reader);
+    const char* key = mpack_read_bytes_inplace(reader, keylen);
+    mpack_done_str(reader);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return count;
+
+    // find what key it matches
+    size_t i;
+    for (i = 0; i < count; ++i) {
+        const char* other = strings[i];
+        size_t otherlen = mpack_strlen(other);
+        if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
+            return i;
+    }
+
+    // no matches
+    mpack_reader_flag_error(reader, mpack_error_type);
+    return count;
+}
+
+size_t mpack_expect_enum_optional(mpack_reader_t* reader, const char* strings[], size_t count) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return count;
+
+    mpack_assert(count != 0, "count cannot be zero; no strings are valid!");
+    mpack_assert(strings != NULL, "strings cannot be NULL");
+
+    // the key is only recognized if it is a string
+    if (mpack_peek_tag(reader).type != mpack_type_str) {
+        mpack_discard(reader);
+        return count;
+    }
+
+    // read the string in-place
+    size_t keylen = mpack_expect_str(reader);
+    const char* key = mpack_read_bytes_inplace(reader, keylen);
+    mpack_done_str(reader);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return count;
+
+    // find what key it matches
+    size_t i;
+    for (i = 0; i < count; ++i) {
+        const char* other = strings[i];
+        size_t otherlen = mpack_strlen(other);
+        if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
+            return i;
+    }
+
+    // no matches
+    return count;
+}
+
+size_t mpack_expect_key_uint(mpack_reader_t* reader, bool found[], size_t count) {
+    if (mpack_reader_error(reader) != mpack_ok)
+        return count;
+
+    if (count == 0) {
+        mpack_break("count cannot be zero; no keys are valid!");
+        mpack_reader_flag_error(reader, mpack_error_bug);
+        return count;
+    }
+    mpack_assert(found != NULL, "found cannot be NULL");
+
+    // the key is only recognized if it is an unsigned int
+    if (mpack_peek_tag(reader).type != mpack_type_uint) {
+        mpack_discard(reader);
+        return count;
+    }
+
+    // read the key
+    uint64_t value = mpack_expect_u64(reader);
+    if (mpack_reader_error(reader) != mpack_ok)
+        return count;
+
+    // unrecognized keys are fine, we just return count
+    if (value >= count)
+        return count;
+
+    // check if this key is a duplicate
+    if (found[value]) {
+        mpack_reader_flag_error(reader, mpack_error_invalid);
+        return count;
+    }
+
+    found[value] = true;
+    return (size_t)value;
+}
+
+size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[], bool found[], size_t count) {
+    size_t i = mpack_expect_enum_optional(reader, keys, count);
+
+    // unrecognized keys are fine, we just return count
+    if (i == count)
+        return count;
+
+    // check if this key is a duplicate
+    mpack_assert(found != NULL, "found cannot be NULL");
+    if (found[i]) {
+        mpack_reader_flag_error(reader, mpack_error_invalid);
+        return count;
+    }
+
+    found[i] = true;
+    return i;
+}
+
+#endif
+
+MPACK_SILENCE_WARNINGS_END
+
+/* mpack/mpack-node.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-node.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+
+#if MPACK_NODE
+
+MPACK_STATIC_INLINE const char* mpack_node_data_unchecked(mpack_node_t node) {
+    mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!");
+
+    mpack_type_t type = node.data->type;
+    MPACK_UNUSED(type);
+    #if MPACK_EXTENSIONS
+    mpack_assert(type == mpack_type_str || type == mpack_type_bin || type == mpack_type_ext,
+            "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type));
+    #else
+    mpack_assert(type == mpack_type_str || type == mpack_type_bin,
+            "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type));
+    #endif
+
+    return node.tree->data + node.data->value.offset;
+}
+
+#if MPACK_EXTENSIONS
+MPACK_STATIC_INLINE int8_t mpack_node_exttype_unchecked(mpack_node_t node) {
+    mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!");
+
+    mpack_type_t type = node.data->type;
+    MPACK_UNUSED(type);
+    mpack_assert(type == mpack_type_ext, "node of type %i (%s) is not an ext type!",
+            type, mpack_type_to_string(type));
+
+    // the exttype of an ext node is stored in the byte preceding the data
+    return mpack_load_i8(mpack_node_data_unchecked(node) - 1);
+}
+#endif
+
+
+
+/*
+ * Tree Parsing
+ */
+
+#ifdef MPACK_MALLOC
+
+// fix up the alloc size to make sure it exactly fits the
+// maximum number of nodes it can contain (the allocator will
+// waste it back anyway, but we round it down just in case)
+
+#define MPACK_NODES_PER_PAGE \
+    ((MPACK_NODE_PAGE_SIZE - sizeof(mpack_tree_page_t)) / sizeof(mpack_node_data_t) + 1)
+
+#define MPACK_PAGE_ALLOC_SIZE \
+    (sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (MPACK_NODES_PER_PAGE - 1))
+
+#endif
+
+#ifdef MPACK_MALLOC
+/*
+ * Fills the tree until we have at least enough bytes for the current node.
+ */
+static bool mpack_tree_reserve_fill(mpack_tree_t* tree) {
+    mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
+
+    size_t bytes = tree->parser.current_node_reserved;
+    mpack_assert(bytes > tree->parser.possible_nodes_left,
+            "there are already enough bytes! call mpack_tree_ensure() instead.");
+    mpack_log("filling to reserve %i bytes\n", (int)bytes);
+
+    // if the necessary bytes would put us over the maximum tree
+    // size, fail right away.
+    // TODO: check for overflow?
+    if (tree->data_length + bytes > tree->max_size) {
+        mpack_tree_flag_error(tree, mpack_error_too_big);
+        return false;
+    }
+
+    // we'll need a read function to fetch more data. if there's
+    // no read function, the data should contain an entire message
+    // (or messages), so we flag it as invalid.
+    if (tree->read_fn == NULL) {
+        mpack_log("tree has no read function!\n");
+        mpack_tree_flag_error(tree, mpack_error_invalid);
+        return false;
+    }
+
+    // expand the buffer if needed
+    if (tree->data_length + bytes > tree->buffer_capacity) {
+
+        // TODO: check for overflow?
+        size_t new_capacity = (tree->buffer_capacity == 0) ? MPACK_BUFFER_SIZE : tree->buffer_capacity;
+        while (new_capacity < tree->data_length + bytes)
+            new_capacity *= 2;
+        if (new_capacity > tree->max_size)
+            new_capacity = tree->max_size;
+
+        mpack_log("expanding buffer from %i to %i\n", (int)tree->buffer_capacity, (int)new_capacity);
+
+        char* new_buffer;
+        if (tree->buffer == NULL)
+            new_buffer = (char*)MPACK_MALLOC(new_capacity);
+        else
+            new_buffer = (char*)mpack_realloc(tree->buffer, tree->data_length, new_capacity);
+
+        if (new_buffer == NULL) {
+            mpack_tree_flag_error(tree, mpack_error_memory);
+            return false;
+        }
+
+        tree->data = new_buffer;
+        tree->buffer = new_buffer;
+        tree->buffer_capacity = new_capacity;
+    }
+
+    // request as much data as possible, looping until we have
+    // all the data we need
+    do {
+        size_t read = tree->read_fn(tree, tree->buffer + tree->data_length, tree->buffer_capacity - tree->data_length);
+
+        // If the fill function encounters an error, it should flag an error on
+        // the tree.
+        if (mpack_tree_error(tree) != mpack_ok)
+            return false;
+
+        // We guard against fill functions that return -1 just in case.
+        if (read == (size_t)(-1)) {
+            mpack_tree_flag_error(tree, mpack_error_io);
+            return false;
+        }
+
+        // If the fill function returns 0, the data is not available yet. We
+        // return false to stop parsing the current node.
+        if (read == 0) {
+            mpack_log("not enough data.\n");
+            return false;
+        }
+
+        mpack_log("read %u more bytes\n", (uint32_t)read);
+        tree->data_length += read;
+        tree->parser.possible_nodes_left += read;
+    } while (tree->parser.possible_nodes_left < bytes);
+
+    return true;
+}
+#endif
+
+/*
+ * Ensures there are enough additional bytes in the tree for the current node
+ * (including reserved bytes for the children of this node, and in addition to
+ * the reserved bytes for children of previous compound nodes), reading more
+ * data if needed.
+ *
+ * extra_bytes is the number of additional bytes to reserve for the current
+ * node beyond the type byte (since one byte is already reserved for each node
+ * by its parent array or map.)
+ *
+ * This may reallocate the tree, which means the tree->data pointer may change!
+ *
+ * Returns false if not enough bytes could be read.
+ */
+MPACK_STATIC_INLINE bool mpack_tree_reserve_bytes(mpack_tree_t* tree, size_t extra_bytes) {
+    mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
+
+    // We guard against overflow here. A compound type could declare more than
+    // MPACK_UINT32_MAX contents which overflows SIZE_MAX on 32-bit platforms. We
+    // flag mpack_error_invalid instead of mpack_error_too_big since it's far
+    // more likely that the message is corrupt than that the data is valid but
+    // not parseable on this architecture (see test_read_node_possible() in
+    // test-node.c .)
+    if ((uint64_t)tree->parser.current_node_reserved + (uint64_t)extra_bytes > SIZE_MAX) {
+        mpack_tree_flag_error(tree, mpack_error_invalid);
+        return false;
+    }
+
+    tree->parser.current_node_reserved += extra_bytes;
+
+    // Note that possible_nodes_left already accounts for reserved bytes for
+    // children of previous compound nodes. So even if there are hundreds of
+    // bytes left in the buffer, we might need to read anyway.
+    if (tree->parser.current_node_reserved <= tree->parser.possible_nodes_left)
+        return true;
+
+    #ifdef MPACK_MALLOC
+    return mpack_tree_reserve_fill(tree);
+    #else
+    return false;
+    #endif
+}
+
+MPACK_STATIC_INLINE size_t mpack_tree_parser_stack_capacity(mpack_tree_t* tree) {
+    #ifdef MPACK_MALLOC
+    return tree->parser.stack_capacity;
+    #else
+    return sizeof(tree->parser.stack) / sizeof(tree->parser.stack[0]);
+    #endif
+}
+
+static bool mpack_tree_push_stack(mpack_tree_t* tree, mpack_node_data_t* first_child, size_t total) {
+    mpack_tree_parser_t* parser = &tree->parser;
+    mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
+
+    // No need to push empty containers
+    if (total == 0)
+        return true;
+
+    // Make sure we have enough room in the stack
+    if (parser->level + 1 == mpack_tree_parser_stack_capacity(tree)) {
+        #ifdef MPACK_MALLOC
+        size_t new_capacity = parser->stack_capacity * 2;
+        mpack_log("growing parse stack to capacity %i\n", (int)new_capacity);
+
+        // Replace the stack-allocated parsing stack
+        if (!parser->stack_owned) {
+            mpack_level_t* new_stack = (mpack_level_t*)MPACK_MALLOC(sizeof(mpack_level_t) * new_capacity);
+            if (!new_stack) {
+                mpack_tree_flag_error(tree, mpack_error_memory);
+                return false;
+            }
+            mpack_memcpy(new_stack, parser->stack, sizeof(mpack_level_t) * parser->stack_capacity);
+            parser->stack = new_stack;
+            parser->stack_owned = true;
+
+        // Realloc the allocated parsing stack
+        } else {
+            mpack_level_t* new_stack = (mpack_level_t*)mpack_realloc(parser->stack,
+                    sizeof(mpack_level_t) * parser->stack_capacity, sizeof(mpack_level_t) * new_capacity);
+            if (!new_stack) {
+                mpack_tree_flag_error(tree, mpack_error_memory);
+                return false;
+            }
+            parser->stack = new_stack;
+        }
+        parser->stack_capacity = new_capacity;
+        #else
+        mpack_tree_flag_error(tree, mpack_error_too_big);
+        return false;
+        #endif
+    }
+
+    // Push the contents of this node onto the parsing stack
+    ++parser->level;
+    parser->stack[parser->level].child = first_child;
+    parser->stack[parser->level].left = total;
+    return true;
+}
+
+static bool mpack_tree_parse_children(mpack_tree_t* tree, mpack_node_data_t* node) {
+    mpack_tree_parser_t* parser = &tree->parser;
+    mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
+
+    mpack_type_t type = node->type;
+    size_t total = node->len;
+
+    // Calculate total elements to read
+    if (type == mpack_type_map) {
+        if ((uint64_t)total * 2 > SIZE_MAX) {
+            mpack_tree_flag_error(tree, mpack_error_too_big);
+            return false;
+        }
+        total *= 2;
+    }
+
+    // Make sure we are under our total node limit (TODO can this overflow?)
+    tree->node_count += total;
+    if (tree->node_count > tree->max_nodes) {
+        mpack_tree_flag_error(tree, mpack_error_too_big);
+        return false;
+    }
+
+    // Each node is at least one byte. Count these bytes now to make
+    // sure there is enough data left.
+    if (!mpack_tree_reserve_bytes(tree, total))
+        return false;
+
+    // If there are enough nodes left in the current page, no need to grow
+    if (total <= parser->nodes_left) {
+        node->value.children = parser->nodes;
+        parser->nodes += total;
+        parser->nodes_left -= total;
+
+    } else {
+
+        #ifdef MPACK_MALLOC
+
+        // We can't grow if we're using a fixed pool (i.e. we didn't start with a page)
+        if (!tree->next) {
+            mpack_tree_flag_error(tree, mpack_error_too_big);
+            return false;
+        }
+
+        // Otherwise we need to grow, and the node's children need to be contiguous.
+        // This is a heuristic to decide whether we should waste the remaining space
+        // in the current page and start a new one, or give the children their
+        // own page. With a fraction of 1/8, this causes at most 12% additional
+        // waste. Note that reducing this too much causes less cache coherence and
+        // more malloc() overhead due to smaller allocations, so there's a tradeoff
+        // here. This heuristic could use some improvement, especially with custom
+        // page sizes.
+
+        mpack_tree_page_t* page;
+
+        if (total > MPACK_NODES_PER_PAGE || parser->nodes_left > MPACK_NODES_PER_PAGE / 8) {
+            // TODO: this should check for overflow
+            page = (mpack_tree_page_t*)MPACK_MALLOC(
+                    sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (total - 1));
+            if (page == NULL) {
+                mpack_tree_flag_error(tree, mpack_error_memory);
+                return false;
+            }
+            mpack_log("allocated seperate page %p for %i children, %i left in page of %i total\n",
+                    (void*)page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE);
+
+            node->value.children = page->nodes;
+
+        } else {
+            page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE);
+            if (page == NULL) {
+                mpack_tree_flag_error(tree, mpack_error_memory);
+                return false;
+            }
+            mpack_log("allocated new page %p for %i children, wasting %i in page of %i total\n",
+                    (void*)page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE);
+
+            node->value.children = page->nodes;
+            parser->nodes = page->nodes + total;
+            parser->nodes_left = MPACK_NODES_PER_PAGE - total;
+        }
+
+        page->next = tree->next;
+        tree->next = page;
+
+        #else
+        // We can't grow if we don't have an allocator
+        mpack_tree_flag_error(tree, mpack_error_too_big);
+        return false;
+        #endif
+    }
+
+    return mpack_tree_push_stack(tree, node->value.children, total);
+}
+
+static bool mpack_tree_parse_bytes(mpack_tree_t* tree, mpack_node_data_t* node) {
+    node->value.offset = tree->size + tree->parser.current_node_reserved + 1;
+    return mpack_tree_reserve_bytes(tree, node->len);
+}
+
+#if MPACK_EXTENSIONS
+static bool mpack_tree_parse_ext(mpack_tree_t* tree, mpack_node_data_t* node) {
+    // reserve space for exttype
+    tree->parser.current_node_reserved += sizeof(int8_t);
+    node->type = mpack_type_ext;
+    return mpack_tree_parse_bytes(tree, node);
+}
+#endif
+
+static bool mpack_tree_parse_node_contents(mpack_tree_t* tree, mpack_node_data_t* node) {
+    mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
+    mpack_assert(node != NULL, "null node?");
+
+    // read the type. we've already accounted for this byte in
+    // possible_nodes_left, so we already know it is in bounds, and we don't
+    // need to reserve it for this node.
+    mpack_assert(tree->data_length > tree->size);
+    uint8_t type = mpack_load_u8(tree->data + tree->size);
+    mpack_log("node type %x\n", type);
+    tree->parser.current_node_reserved = 0;
+
+    // as with mpack_read_tag(), the fastest way to parse a node is to switch
+    // on the first byte, and to explicitly list every possible byte. we switch
+    // on the first four bits in size-optimized builds.
+
+    #if MPACK_OPTIMIZE_FOR_SIZE
+    switch (type >> 4) {
+
+        // positive fixnum
+        case 0x0: case 0x1: case 0x2: case 0x3:
+        case 0x4: case 0x5: case 0x6: case 0x7:
+            node->type = mpack_type_uint;
+            node->value.u = type;
+            return true;
+
+        // negative fixnum
+        case 0xe: case 0xf:
+            node->type = mpack_type_int;
+            node->value.i = (int8_t)type;
+            return true;
+
+        // fixmap
+        case 0x8:
+            node->type = mpack_type_map;
+            node->len = (uint32_t)(type & ~0xf0);
+            return mpack_tree_parse_children(tree, node);
+
+        // fixarray
+        case 0x9:
+            node->type = mpack_type_array;
+            node->len = (uint32_t)(type & ~0xf0);
+            return mpack_tree_parse_children(tree, node);
+
+        // fixstr
+        case 0xa: case 0xb:
+            node->type = mpack_type_str;
+            node->len = (uint32_t)(type & ~0xe0);
+            return mpack_tree_parse_bytes(tree, node);
+
+        // not one of the common infix types
+        default:
+            break;
+    }
+    #endif
+
+    switch (type) {
+
+        #if !MPACK_OPTIMIZE_FOR_SIZE
+        // positive fixnum
+        case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
+        case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+        case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+        case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
+        case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
+        case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
+        case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
+        case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
+        case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
+        case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
+        case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
+        case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
+        case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
+        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
+        case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
+            node->type = mpack_type_uint;
+            node->value.u = type;
+            return true;
+
+        // negative fixnum
+        case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7:
+        case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef:
+        case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7:
+        case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff:
+            node->type = mpack_type_int;
+            node->value.i = (int8_t)type;
+            return true;
+
+        // fixmap
+        case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
+        case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
+            node->type = mpack_type_map;
+            node->len = (uint32_t)(type & ~0xf0);
+            return mpack_tree_parse_children(tree, node);
+
+        // fixarray
+        case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
+        case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
+            node->type = mpack_type_array;
+            node->len = (uint32_t)(type & ~0xf0);
+            return mpack_tree_parse_children(tree, node);
+
+        // fixstr
+        case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7:
+        case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
+        case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
+        case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
+            node->type = mpack_type_str;
+            node->len = (uint32_t)(type & ~0xe0);
+            return mpack_tree_parse_bytes(tree, node);
+        #endif
+
+        // nil
+        case 0xc0:
+            node->type = mpack_type_nil;
+            return true;
+
+        // bool
+        case 0xc2: case 0xc3:
+            node->type = mpack_type_bool;
+            node->value.b = type & 1;
+            return true;
+
+        // bin8
+        case 0xc4:
+            node->type = mpack_type_bin;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+                return false;
+            node->len = mpack_load_u8(tree->data + tree->size + 1);
+            return mpack_tree_parse_bytes(tree, node);
+
+        // bin16
+        case 0xc5:
+            node->type = mpack_type_bin;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+                return false;
+            node->len = mpack_load_u16(tree->data + tree->size + 1);
+            return mpack_tree_parse_bytes(tree, node);
+
+        // bin32
+        case 0xc6:
+            node->type = mpack_type_bin;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+                return false;
+            node->len = mpack_load_u32(tree->data + tree->size + 1);
+            return mpack_tree_parse_bytes(tree, node);
+
+        #if MPACK_EXTENSIONS
+        // ext8
+        case 0xc7:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+                return false;
+            node->len = mpack_load_u8(tree->data + tree->size + 1);
+            return mpack_tree_parse_ext(tree, node);
+
+        // ext16
+        case 0xc8:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+                return false;
+            node->len = mpack_load_u16(tree->data + tree->size + 1);
+            return mpack_tree_parse_ext(tree, node);
+
+        // ext32
+        case 0xc9:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+                return false;
+            node->len = mpack_load_u32(tree->data + tree->size + 1);
+            return mpack_tree_parse_ext(tree, node);
+        #endif
+
+        // float
+        case 0xca:
+            #if MPACK_FLOAT
+            if (!mpack_tree_reserve_bytes(tree, sizeof(float)))
+                return false;
+            node->value.f = mpack_load_float(tree->data + tree->size + 1);
+            #else
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+                return false;
+            node->value.f = mpack_load_u32(tree->data + tree->size + 1);
+            #endif
+            node->type = mpack_type_float;
+            return true;
+
+        // double
+        case 0xcb:
+            #if MPACK_DOUBLE
+            if (!mpack_tree_reserve_bytes(tree, sizeof(double)))
+                return false;
+            node->value.d = mpack_load_double(tree->data + tree->size + 1);
+            #else
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t)))
+                return false;
+            node->value.d = mpack_load_u64(tree->data + tree->size + 1);
+            #endif
+            node->type = mpack_type_double;
+            return true;
+
+        // uint8
+        case 0xcc:
+            node->type = mpack_type_uint;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+                return false;
+            node->value.u = mpack_load_u8(tree->data + tree->size + 1);
+            return true;
+
+        // uint16
+        case 0xcd:
+            node->type = mpack_type_uint;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+                return false;
+            node->value.u = mpack_load_u16(tree->data + tree->size + 1);
+            return true;
+
+        // uint32
+        case 0xce:
+            node->type = mpack_type_uint;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+                return false;
+            node->value.u = mpack_load_u32(tree->data + tree->size + 1);
+            return true;
+
+        // uint64
+        case 0xcf:
+            node->type = mpack_type_uint;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t)))
+                return false;
+            node->value.u = mpack_load_u64(tree->data + tree->size + 1);
+            return true;
+
+        // int8
+        case 0xd0:
+            node->type = mpack_type_int;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(int8_t)))
+                return false;
+            node->value.i = mpack_load_i8(tree->data + tree->size + 1);
+            return true;
+
+        // int16
+        case 0xd1:
+            node->type = mpack_type_int;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(int16_t)))
+                return false;
+            node->value.i = mpack_load_i16(tree->data + tree->size + 1);
+            return true;
+
+        // int32
+        case 0xd2:
+            node->type = mpack_type_int;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(int32_t)))
+                return false;
+            node->value.i = mpack_load_i32(tree->data + tree->size + 1);
+            return true;
+
+        // int64
+        case 0xd3:
+            node->type = mpack_type_int;
+            if (!mpack_tree_reserve_bytes(tree, sizeof(int64_t)))
+                return false;
+            node->value.i = mpack_load_i64(tree->data + tree->size + 1);
+            return true;
+
+        #if MPACK_EXTENSIONS
+        // fixext1
+        case 0xd4:
+            node->len = 1;
+            return mpack_tree_parse_ext(tree, node);
+
+        // fixext2
+        case 0xd5:
+            node->len = 2;
+            return mpack_tree_parse_ext(tree, node);
+
+        // fixext4
+        case 0xd6:
+            node->len = 4;
+            return mpack_tree_parse_ext(tree, node);
+
+        // fixext8
+        case 0xd7:
+            node->len = 8;
+            return mpack_tree_parse_ext(tree, node);
+
+        // fixext16
+        case 0xd8:
+            node->len = 16;
+            return mpack_tree_parse_ext(tree, node);
+        #endif
+
+        // str8
+        case 0xd9:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+                return false;
+            node->len = mpack_load_u8(tree->data + tree->size + 1);
+            node->type = mpack_type_str;
+            return mpack_tree_parse_bytes(tree, node);
+
+        // str16
+        case 0xda:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+                return false;
+            node->len = mpack_load_u16(tree->data + tree->size + 1);
+            node->type = mpack_type_str;
+            return mpack_tree_parse_bytes(tree, node);
+
+        // str32
+        case 0xdb:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+                return false;
+            node->len = mpack_load_u32(tree->data + tree->size + 1);
+            node->type = mpack_type_str;
+            return mpack_tree_parse_bytes(tree, node);
+
+        // array16
+        case 0xdc:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+                return false;
+            node->len = mpack_load_u16(tree->data + tree->size + 1);
+            node->type = mpack_type_array;
+            return mpack_tree_parse_children(tree, node);
+
+        // array32
+        case 0xdd:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+                return false;
+            node->len = mpack_load_u32(tree->data + tree->size + 1);
+            node->type = mpack_type_array;
+            return mpack_tree_parse_children(tree, node);
+
+        // map16
+        case 0xde:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+                return false;
+            node->len = mpack_load_u16(tree->data + tree->size + 1);
+            node->type = mpack_type_map;
+            return mpack_tree_parse_children(tree, node);
+
+        // map32
+        case 0xdf:
+            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+                return false;
+            node->len = mpack_load_u32(tree->data + tree->size + 1);
+            node->type = mpack_type_map;
+            return mpack_tree_parse_children(tree, node);
+
+        // reserved
+        case 0xc1:
+            mpack_tree_flag_error(tree, mpack_error_invalid);
+            return false;
+
+        #if !MPACK_EXTENSIONS
+        // ext
+        case 0xc7: // fallthrough
+        case 0xc8: // fallthrough
+        case 0xc9: // fallthrough
+        // fixext
+        case 0xd4: // fallthrough
+        case 0xd5: // fallthrough
+        case 0xd6: // fallthrough
+        case 0xd7: // fallthrough
+        case 0xd8:
+            mpack_tree_flag_error(tree, mpack_error_unsupported);
+            return false;
+        #endif
+
+        #if MPACK_OPTIMIZE_FOR_SIZE
+        // any other bytes should have been handled by the infix switch
+        default:
+            break;
+        #endif
+    }
+
+    mpack_assert(0, "unreachable");
+    return false;
+}
+
+static bool mpack_tree_parse_node(mpack_tree_t* tree, mpack_node_data_t* node) {
+    mpack_log("parsing a node at position %i in level %i\n",
+            (int)tree->size, (int)tree->parser.level);
+
+    if (!mpack_tree_parse_node_contents(tree, node)) {
+        mpack_log("node parsing returned false\n");
+        return false;
+    }
+
+    tree->parser.possible_nodes_left -= tree->parser.current_node_reserved;
+
+    // The reserve for the current node does not include the initial byte
+    // previously reserved as part of its parent.
+    size_t node_size = tree->parser.current_node_reserved + 1;
+
+    // If the parsed type is a map or array, the reserve includes one byte for
+    // each child. We want to subtract these out of possible_nodes_left, but
+    // not out of the current size of the tree.
+    if (node->type == mpack_type_array)
+        node_size -= node->len;
+    else if (node->type == mpack_type_map)
+        node_size -= node->len * 2;
+    tree->size += node_size;
+
+    mpack_log("parsed a node of type %s of %i bytes and "
+            "%i additional bytes reserved for children.\n",
+            mpack_type_to_string(node->type), (int)node_size,
+            (int)tree->parser.current_node_reserved + 1 - (int)node_size);
+
+    return true;
+}
+
+/*
+ * We read nodes in a loop instead of recursively for maximum performance. The
+ * stack holds the amount of children left to read in each level of the tree.
+ * Parsing can pause and resume when more data becomes available.
+ */
+static bool mpack_tree_continue_parsing(mpack_tree_t* tree) {
+    if (mpack_tree_error(tree) != mpack_ok)
+        return false;
+
+    mpack_tree_parser_t* parser = &tree->parser;
+    mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
+    mpack_log("parsing tree elements, %i bytes in buffer\n", (int)tree->data_length);
+
+    // we loop parsing nodes until the parse stack is empty. we break
+    // by returning out of the function.
+    while (true) {
+        mpack_node_data_t* node = parser->stack[parser->level].child;
+        size_t level = parser->level;
+        if (!mpack_tree_parse_node(tree, node))
+            return false;
+        --parser->stack[level].left;
+        ++parser->stack[level].child;
+
+        mpack_assert(mpack_tree_error(tree) == mpack_ok,
+                "mpack_tree_parse_node() should have returned false due to error!");
+
+        // pop empty stack levels, exiting the outer loop when the stack is empty.
+        // (we could tail-optimize containers by pre-emptively popping empty
+        // stack levels before reading the new element, this way we wouldn't
+        // have to loop. but we eventually want to use the parse stack to give
+        // better error messages that contain the location of the error, so
+        // it needs to be complete.)
+        while (parser->stack[parser->level].left == 0) {
+            if (parser->level == 0)
+                return true;
+            --parser->level;
+        }
+    }
+}
+
+static void mpack_tree_cleanup(mpack_tree_t* tree) {
+    MPACK_UNUSED(tree);
+
+    #ifdef MPACK_MALLOC
+    if (tree->parser.stack_owned) {
+        MPACK_FREE(tree->parser.stack);
+        tree->parser.stack = NULL;
+        tree->parser.stack_owned = false;
+    }
+
+    mpack_tree_page_t* page = tree->next;
+    while (page != NULL) {
+        mpack_tree_page_t* next = page->next;
+        mpack_log("freeing page %p\n", (void*)page);
+        MPACK_FREE(page);
+        page = next;
+    }
+    tree->next = NULL;
+    #endif
+}
+
+static bool mpack_tree_parse_start(mpack_tree_t* tree) {
+    if (mpack_tree_error(tree) != mpack_ok)
+        return false;
+
+    mpack_tree_parser_t* parser = &tree->parser;
+    mpack_assert(parser->state != mpack_tree_parse_state_in_progress,
+            "previous parsing was not finished!");
+
+    if (parser->state == mpack_tree_parse_state_parsed)
+        mpack_tree_cleanup(tree);
+
+    mpack_log("starting parse\n");
+    tree->parser.state = mpack_tree_parse_state_in_progress;
+    tree->parser.current_node_reserved = 0;
+
+    // check if we previously parsed a tree
+    if (tree->size > 0) {
+        #ifdef MPACK_MALLOC
+        // if we're buffered, move the remaining data back to the
+        // start of the buffer
+        // TODO: This is not ideal performance-wise. We should only move data
+        // when we need to call the fill function.
+        // TODO: We could consider shrinking the buffer here, especially if we
+        // determine that the fill function is providing less than a quarter of
+        // the buffer size or if messages take up less than a quarter of the
+        // buffer size. Maybe this should be configurable.
+        if (tree->buffer != NULL) {
+            mpack_memmove(tree->buffer, tree->buffer + tree->size, tree->data_length - tree->size);
+        }
+        else
+        #endif
+        // otherwise advance past the parsed data
+        {
+            tree->data += tree->size;
+        }
+        tree->data_length -= tree->size;
+        tree->size = 0;
+        tree->node_count = 0;
+    }
+
+    // make sure we have at least one byte available before allocating anything
+    parser->possible_nodes_left = tree->data_length;
+    if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) {
+        tree->parser.state = mpack_tree_parse_state_not_started;
+        return false;
+    }
+    mpack_log("parsing tree at %p starting with byte %x\n", tree->data, (uint8_t)tree->data[0]);
+    parser->possible_nodes_left -= 1;
+    tree->node_count = 1;
+
+    #ifdef MPACK_MALLOC
+    parser->stack = parser->stack_local;
+    parser->stack_owned = false;
+    parser->stack_capacity = sizeof(parser->stack_local) / sizeof(*parser->stack_local);
+
+    if (tree->pool == NULL) {
+
+        // allocate first page
+        mpack_tree_page_t* page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE);
+        mpack_log("allocated initial page %p of size %i count %i\n",
+                (void*)page, (int)MPACK_PAGE_ALLOC_SIZE, (int)MPACK_NODES_PER_PAGE);
+        if (page == NULL) {
+            tree->error = mpack_error_memory;
+            return false;
+        }
+        page->next = NULL;
+        tree->next = page;
+
+        parser->nodes = page->nodes;
+        parser->nodes_left = MPACK_NODES_PER_PAGE;
+    }
+    else
+    #endif
+    {
+        // otherwise use the provided pool
+        mpack_assert(tree->pool != NULL, "no pool provided?");
+        parser->nodes = tree->pool;
+        parser->nodes_left = tree->pool_count;
+    }
+
+    tree->root = parser->nodes;
+    ++parser->nodes;
+    --parser->nodes_left;
+
+    parser->level = 0;
+    parser->stack[0].child = tree->root;
+    parser->stack[0].left = 1;
+
+    return true;
+}
+
+void mpack_tree_parse(mpack_tree_t* tree) {
+    if (mpack_tree_error(tree) != mpack_ok)
+        return;
+
+    if (tree->parser.state != mpack_tree_parse_state_in_progress) {
+        if (!mpack_tree_parse_start(tree)) {
+            mpack_tree_flag_error(tree, (tree->read_fn == NULL) ?
+                    mpack_error_invalid : mpack_error_io);
+            return;
+        }
+    }
+
+    if (!mpack_tree_continue_parsing(tree)) {
+        if (mpack_tree_error(tree) != mpack_ok)
+            return;
+
+        // We're parsing synchronously on a blocking fill function. If we
+        // didn't completely finish parsing the tree, it's an error.
+        mpack_log("tree parsing incomplete. flagging error.\n");
+        mpack_tree_flag_error(tree, (tree->read_fn == NULL) ?
+                mpack_error_invalid : mpack_error_io);
+        return;
+    }
+
+    mpack_assert(mpack_tree_error(tree) == mpack_ok);
+    mpack_assert(tree->parser.level == 0);
+    tree->parser.state = mpack_tree_parse_state_parsed;
+    mpack_log("parsed tree of %i bytes, %i bytes left\n", (int)tree->size, (int)tree->parser.possible_nodes_left);
+    mpack_log("%i nodes in final page\n", (int)tree->parser.nodes_left);
+}
+
+bool mpack_tree_try_parse(mpack_tree_t* tree) {
+    if (mpack_tree_error(tree) != mpack_ok)
+        return false;
+
+    if (tree->parser.state != mpack_tree_parse_state_in_progress)
+        if (!mpack_tree_parse_start(tree))
+            return false;
+
+    if (!mpack_tree_continue_parsing(tree))
+        return false;
+
+    mpack_assert(mpack_tree_error(tree) == mpack_ok);
+    mpack_assert(tree->parser.level == 0);
+    tree->parser.state = mpack_tree_parse_state_parsed;
+    return true;
+}
+
+
+
+/*
+ * Tree functions
+ */
+
+mpack_node_t mpack_tree_root(mpack_tree_t* tree) {
+    if (mpack_tree_error(tree) != mpack_ok)
+        return mpack_tree_nil_node(tree);
+
+    // We check that a tree was parsed successfully and assert if not. You must
+    // call mpack_tree_parse() (or mpack_tree_try_parse() with a success
+    // result) in order to access the root node.
+    if (tree->parser.state != mpack_tree_parse_state_parsed) {
+        mpack_break("Tree has not been parsed! "
+                "Did you call mpack_tree_parse() or mpack_tree_try_parse()?");
+        mpack_tree_flag_error(tree, mpack_error_bug);
+        return mpack_tree_nil_node(tree);
+    }
+
+    return mpack_node(tree, tree->root);
+}
+
+static void mpack_tree_init_clear(mpack_tree_t* tree) {
+    mpack_memset(tree, 0, sizeof(*tree));
+    tree->nil_node.type = mpack_type_nil;
+    tree->missing_node.type = mpack_type_missing;
+    tree->max_size = SIZE_MAX;
+    tree->max_nodes = SIZE_MAX;
+}
+
+#ifdef MPACK_MALLOC
+void mpack_tree_init_data(mpack_tree_t* tree, const char* data, size_t length) {
+    mpack_tree_init_clear(tree);
+
+    MPACK_STATIC_ASSERT(MPACK_NODE_PAGE_SIZE >= sizeof(mpack_tree_page_t),
+            "MPACK_NODE_PAGE_SIZE is too small");
+
+    MPACK_STATIC_ASSERT(MPACK_PAGE_ALLOC_SIZE <= MPACK_NODE_PAGE_SIZE,
+            "incorrect page rounding?");
+
+    tree->data = data;
+    tree->data_length = length;
+    tree->pool = NULL;
+    tree->pool_count = 0;
+    tree->next = NULL;
+
+    mpack_log("===========================\n");
+    mpack_log("initializing tree with data of size %i\n", (int)length);
+}
+#endif
+
+void mpack_tree_init_pool(mpack_tree_t* tree, const char* data, size_t length,
+        mpack_node_data_t* node_pool, size_t node_pool_count)
+{
+    mpack_tree_init_clear(tree);
+    #ifdef MPACK_MALLOC
+    tree->next = NULL;
+    #endif
+
+    if (node_pool_count == 0) {
+        mpack_break("initial page has no nodes!");
+        mpack_tree_flag_error(tree, mpack_error_bug);
+        return;
+    }
+
+    tree->data = data;
+    tree->data_length = length;
+    tree->pool = node_pool;
+    tree->pool_count = node_pool_count;
+
+    mpack_log("===========================\n");
+    mpack_log("initializing tree with data of size %i and pool of count %i\n",
+            (int)length, (int)node_pool_count);
+}
+
+void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error) {
+    mpack_tree_init_clear(tree);
+    tree->error = error;
+
+    mpack_log("===========================\n");
+    mpack_log("initializing tree error state %i\n", (int)error);
+}
+
+#ifdef MPACK_MALLOC
+void mpack_tree_init_stream(mpack_tree_t* tree, mpack_tree_read_t read_fn, void* context,
+        size_t max_message_size, size_t max_message_nodes) {
+    mpack_tree_init_clear(tree);
+
+    tree->read_fn = read_fn;
+    tree->context = context;
+
+    mpack_tree_set_limits(tree, max_message_size, max_message_nodes);
+    tree->max_size = max_message_size;
+    tree->max_nodes = max_message_nodes;
+
+    mpack_log("===========================\n");
+    mpack_log("initializing tree with stream, max size %i max nodes %i\n",
+            (int)max_message_size, (int)max_message_nodes);
+}
+#endif
+
+void mpack_tree_set_limits(mpack_tree_t* tree, size_t max_message_size, size_t max_message_nodes) {
+    mpack_assert(max_message_size > 0);
+    mpack_assert(max_message_nodes > 0);
+    tree->max_size = max_message_size;
+    tree->max_nodes = max_message_nodes;
+}
+
+#if MPACK_STDIO
+typedef struct mpack_file_tree_t {
+    char* data;
+    size_t size;
+    char buffer[MPACK_BUFFER_SIZE];
+} mpack_file_tree_t;
+
+static void mpack_file_tree_teardown(mpack_tree_t* tree) {
+    mpack_file_tree_t* file_tree = (mpack_file_tree_t*)tree->context;
+    MPACK_FREE(file_tree->data);
+    MPACK_FREE(file_tree);
+}
+
+static bool mpack_file_tree_read(mpack_tree_t* tree, mpack_file_tree_t* file_tree, FILE* file, size_t max_bytes) {
+
+    // get the file size
+    errno = 0;
+    int error = 0;
+    fseek(file, 0, SEEK_END);
+    error |= errno;
+    long size = ftell(file);
+    error |= errno;
+    fseek(file, 0, SEEK_SET);
+    error |= errno;
+
+    // check for errors
+    if (error != 0 || size < 0) {
+        mpack_tree_init_error(tree, mpack_error_io);
+        return false;
+    }
+    if (size == 0) {
+        mpack_tree_init_error(tree, mpack_error_invalid);
+        return false;
+    }
+
+    // make sure the size is less than max_bytes
+    // (this mess exists to safely convert between long and size_t regardless of their widths)
+    if (max_bytes != 0 && (((uint64_t)LONG_MAX > (uint64_t)SIZE_MAX && size > (long)SIZE_MAX) || (size_t)size > max_bytes)) {
+        mpack_tree_init_error(tree, mpack_error_too_big);
+        return false;
+    }
+
+    // allocate data
+    file_tree->data = (char*)MPACK_MALLOC((size_t)size);
+    if (file_tree->data == NULL) {
+        mpack_tree_init_error(tree, mpack_error_memory);
+        return false;
+    }
+
+    // read the file
+    long total = 0;
+    while (total < size) {
+        size_t read = fread(file_tree->data + total, 1, (size_t)(size - total), file);
+        if (read <= 0) {
+            mpack_tree_init_error(tree, mpack_error_io);
+            MPACK_FREE(file_tree->data);
+            return false;
+        }
+        total += (long)read;
+    }
+
+    file_tree->size = (size_t)size;
+    return true;
+}
+
+static bool mpack_tree_file_check_max_bytes(mpack_tree_t* tree, size_t max_bytes) {
+
+    // the C STDIO family of file functions use long (e.g. ftell)
+    if (max_bytes > LONG_MAX) {
+        mpack_break("max_bytes of %" PRIu64 " is invalid, maximum is LONG_MAX", (uint64_t)max_bytes);
+        mpack_tree_init_error(tree, mpack_error_bug);
+        return false;
+    }
+
+    return true;
+}
+
+static void mpack_tree_init_stdfile_noclose(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes) {
+
+    // allocate file tree
+    mpack_file_tree_t* file_tree = (mpack_file_tree_t*) MPACK_MALLOC(sizeof(mpack_file_tree_t));
+    if (file_tree == NULL) {
+        mpack_tree_init_error(tree, mpack_error_memory);
+        return;
+    }
+
+    // read all data
+    if (!mpack_file_tree_read(tree, file_tree, stdfile, max_bytes)) {
+        MPACK_FREE(file_tree);
+        return;
+    }
+
+    mpack_tree_init_data(tree, file_tree->data, file_tree->size);
+    mpack_tree_set_context(tree, file_tree);
+    mpack_tree_set_teardown(tree, mpack_file_tree_teardown);
+}
+
+void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes, bool close_when_done) {
+    if (!mpack_tree_file_check_max_bytes(tree, max_bytes))
+        return;
+
+    mpack_tree_init_stdfile_noclose(tree, stdfile, max_bytes);
+
+    if (close_when_done)
+        fclose(stdfile);
+}
+
+void mpack_tree_init_filename(mpack_tree_t* tree, const char* filename, size_t max_bytes) {
+    if (!mpack_tree_file_check_max_bytes(tree, max_bytes))
+        return;
+
+    // open the file
+    FILE* file = fopen(filename, "rb");
+    if (!file) {
+        mpack_tree_init_error(tree, mpack_error_io);
+        return;
+    }
+
+    mpack_tree_init_stdfile(tree, file, max_bytes, true);
+}
+#endif
+
+mpack_error_t mpack_tree_destroy(mpack_tree_t* tree) {
+    mpack_tree_cleanup(tree);
+
+    #ifdef MPACK_MALLOC
+    if (tree->buffer)
+        MPACK_FREE(tree->buffer);
+    #endif
+
+    if (tree->teardown)
+        tree->teardown(tree);
+    tree->teardown = NULL;
+
+    return tree->error;
+}
+
+void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error) {
+    if (tree->error == mpack_ok) {
+        mpack_log("tree %p setting error %i: %s\n", (void*)tree, (int)error, mpack_error_to_string(error));
+        tree->error = error;
+        if (tree->error_fn)
+            tree->error_fn(tree, error);
+    }
+
+}
+
+
+
+/*
+ * Node misc functions
+ */
+
+void mpack_node_flag_error(mpack_node_t node, mpack_error_t error) {
+    mpack_tree_flag_error(node.tree, error);
+}
+
+mpack_tag_t mpack_node_tag(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return mpack_tag_nil();
+
+    mpack_tag_t tag = MPACK_TAG_ZERO;
+
+    tag.type = node.data->type;
+    switch (node.data->type) {
+        case mpack_type_missing:
+            // If a node is missing, I don't know if it makes sense to ask for
+            // a tag for it. We'll return a missing tag to match the missing
+            // node I guess, but attempting to use the tag for anything (like
+            // writing it for example) will flag mpack_error_bug.
+            break;
+        case mpack_type_nil:                                            break;
+        case mpack_type_bool:    tag.v.b = node.data->value.b;          break;
+        case mpack_type_float:   tag.v.f = node.data->value.f;          break;
+        case mpack_type_double:  tag.v.d = node.data->value.d;          break;
+        case mpack_type_int:     tag.v.i = node.data->value.i;          break;
+        case mpack_type_uint:    tag.v.u = node.data->value.u;          break;
+
+        case mpack_type_str:     tag.v.l = node.data->len;     break;
+        case mpack_type_bin:     tag.v.l = node.data->len;     break;
+
+        #if MPACK_EXTENSIONS
+        case mpack_type_ext:
+            tag.v.l = node.data->len;
+            tag.exttype = mpack_node_exttype_unchecked(node);
+            break;
+        #endif
+
+        case mpack_type_array:   tag.v.n = node.data->len;  break;
+        case mpack_type_map:     tag.v.n = node.data->len;  break;
+
+        default:
+            mpack_assert(0, "unrecognized type %i", (int)node.data->type);
+            break;
+    }
+    return tag;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+static void mpack_node_print_element(mpack_node_t node, mpack_print_t* print, size_t depth) {
+    mpack_node_data_t* data = node.data;
+    size_t i,j;
+    switch (data->type) {
+        case mpack_type_str:
+            {
+                mpack_print_append_cstr(print, "\"");
+                const char* bytes = mpack_node_data_unchecked(node);
+                for (i = 0; i < data->len; ++i) {
+                    char c = bytes[i];
+                    switch (c) {
+                        case '\n': mpack_print_append_cstr(print, "\\n"); break;
+                        case '\\': mpack_print_append_cstr(print, "\\\\"); break;
+                        case '"': mpack_print_append_cstr(print, "\\\""); break;
+                        default: mpack_print_append(print, &c, 1); break;
+                    }
+                }
+                mpack_print_append_cstr(print, "\"");
+            }
+            break;
+
+        case mpack_type_array:
+            mpack_print_append_cstr(print, "[\n");
+            for (i = 0; i < data->len; ++i) {
+                for (j = 0; j < depth + 1; ++j)
+                    mpack_print_append_cstr(print, "    ");
+                mpack_node_print_element(mpack_node_array_at(node, i), print, depth + 1);
+                if (i != data->len - 1)
+                    mpack_print_append_cstr(print, ",");
+                mpack_print_append_cstr(print, "\n");
+            }
+            for (i = 0; i < depth; ++i)
+                mpack_print_append_cstr(print, "    ");
+            mpack_print_append_cstr(print, "]");
+            break;
+
+        case mpack_type_map:
+            mpack_print_append_cstr(print, "{\n");
+            for (i = 0; i < data->len; ++i) {
+                for (j = 0; j < depth + 1; ++j)
+                    mpack_print_append_cstr(print, "    ");
+                mpack_node_print_element(mpack_node_map_key_at(node, i), print, depth + 1);
+                mpack_print_append_cstr(print, ": ");
+                mpack_node_print_element(mpack_node_map_value_at(node, i), print, depth + 1);
+                if (i != data->len - 1)
+                    mpack_print_append_cstr(print, ",");
+                mpack_print_append_cstr(print, "\n");
+            }
+            for (i = 0; i < depth; ++i)
+                mpack_print_append_cstr(print, "    ");
+            mpack_print_append_cstr(print, "}");
+            break;
+
+        default:
+            {
+                const char* prefix = NULL;
+                size_t prefix_length = 0;
+                if (mpack_node_type(node) == mpack_type_bin
+                        #if MPACK_EXTENSIONS
+                        || mpack_node_type(node) == mpack_type_ext
+                        #endif
+                ) {
+                    prefix = mpack_node_data(node);
+                    prefix_length = mpack_node_data_len(node);
+                }
+
+                char buf[256];
+                mpack_tag_t tag = mpack_node_tag(node);
+                mpack_tag_debug_pseudo_json(tag, buf, sizeof(buf), prefix, prefix_length);
+                mpack_print_append_cstr(print, buf);
+            }
+            break;
+    }
+}
+
+void mpack_node_print_to_buffer(mpack_node_t node, char* buffer, size_t buffer_size) {
+    if (buffer_size == 0) {
+        mpack_assert(false, "buffer size is zero!");
+        return;
+    }
+
+    mpack_print_t print;
+    mpack_memset(&print, 0, sizeof(print));
+    print.buffer = buffer;
+    print.size = buffer_size;
+    mpack_node_print_element(node, &print, 0);
+    mpack_print_append(&print, "",  1); // null-terminator
+    mpack_print_flush(&print);
+
+    // we always make sure there's a null-terminator at the end of the buffer
+    // in case we ran out of space.
+    print.buffer[print.size - 1] = '\0';
+}
+
+void mpack_node_print_to_callback(mpack_node_t node, mpack_print_callback_t callback, void* context) {
+    char buffer[1024];
+    mpack_print_t print;
+    mpack_memset(&print, 0, sizeof(print));
+    print.buffer = buffer;
+    print.size = sizeof(buffer);
+    print.callback = callback;
+    print.context = context;
+    mpack_node_print_element(node, &print, 0);
+    mpack_print_flush(&print);
+}
+
+void mpack_node_print_to_file(mpack_node_t node, FILE* file) {
+    mpack_assert(file != NULL, "file is NULL");
+
+    char buffer[1024];
+    mpack_print_t print;
+    mpack_memset(&print, 0, sizeof(print));
+    print.buffer = buffer;
+    print.size = sizeof(buffer);
+    print.callback = &mpack_print_file_callback;
+    print.context = file;
+
+    size_t depth = 2;
+    size_t i;
+    for (i = 0; i < depth; ++i)
+        mpack_print_append_cstr(&print, "    ");
+    mpack_node_print_element(node, &print, depth);
+    mpack_print_append_cstr(&print, "\n");
+    mpack_print_flush(&print);
+}
+#endif
+
+
+
+/*
+ * Node Value Functions
+ */
+
+#if MPACK_EXTENSIONS
+mpack_timestamp_t mpack_node_timestamp(mpack_node_t node) {
+    mpack_timestamp_t timestamp = {0, 0};
+
+    // we'll let mpack_node_exttype() do most checks
+    if (mpack_node_exttype(node) != MPACK_EXTTYPE_TIMESTAMP) {
+        mpack_log("exttype %i\n", mpack_node_exttype(node));
+        mpack_node_flag_error(node, mpack_error_type);
+        return timestamp;
+    }
+
+    const char* p = mpack_node_data_unchecked(node);
+
+    switch (node.data->len) {
+        case 4:
+            timestamp.nanoseconds = 0;
+            timestamp.seconds = mpack_load_u32(p);
+            break;
+
+        case 8: {
+            uint64_t value = mpack_load_u64(p);
+            timestamp.nanoseconds = (uint32_t)(value >> 34);
+            timestamp.seconds = value & ((MPACK_UINT64_C(1) << 34) - 1);
+            break;
+        }
+
+        case 12:
+            timestamp.nanoseconds = mpack_load_u32(p);
+            timestamp.seconds = mpack_load_i64(p + 4);
+            break;
+
+        default:
+            mpack_tree_flag_error(node.tree, mpack_error_invalid);
+            return timestamp;
+    }
+
+    if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
+        mpack_tree_flag_error(node.tree, mpack_error_invalid);
+        mpack_timestamp_t zero = {0, 0};
+        return zero;
+    }
+
+    return timestamp;
+}
+
+int64_t mpack_node_timestamp_seconds(mpack_node_t node) {
+    return mpack_node_timestamp(node).seconds;
+}
+
+uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node) {
+    return mpack_node_timestamp(node).nanoseconds;
+}
+#endif
+
+
+
+/*
+ * Node Data Functions
+ */
+
+void mpack_node_check_utf8(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return;
+    mpack_node_data_t* data = node.data;
+    if (data->type != mpack_type_str || !mpack_utf8_check(mpack_node_data_unchecked(node), data->len))
+        mpack_node_flag_error(node, mpack_error_type);
+}
+
+void mpack_node_check_utf8_cstr(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return;
+    mpack_node_data_t* data = node.data;
+    if (data->type != mpack_type_str || !mpack_utf8_check_no_null(mpack_node_data_unchecked(node), data->len))
+        mpack_node_flag_error(node, mpack_error_type);
+}
+
+size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t bufsize) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize);
+
+    mpack_type_t type = node.data->type;
+    if (type != mpack_type_str && type != mpack_type_bin
+            #if MPACK_EXTENSIONS
+            && type != mpack_type_ext
+            #endif
+    ) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return 0;
+    }
+
+    if (node.data->len > bufsize) {
+        mpack_node_flag_error(node, mpack_error_too_big);
+        return 0;
+    }
+
+    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+    return (size_t)node.data->len;
+}
+
+size_t mpack_node_copy_utf8(mpack_node_t node, char* buffer, size_t bufsize) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize);
+
+    mpack_type_t type = node.data->type;
+    if (type != mpack_type_str) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return 0;
+    }
+
+    if (node.data->len > bufsize) {
+        mpack_node_flag_error(node, mpack_error_too_big);
+        return 0;
+    }
+
+    if (!mpack_utf8_check(mpack_node_data_unchecked(node), node.data->len)) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return 0;
+    }
+
+    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+    return (size_t)node.data->len;
+}
+
+void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t bufsize) {
+
+    // we can't break here because the error isn't recoverable; we
+    // have to add a null-terminator.
+    mpack_assert(buffer != NULL, "buffer is NULL");
+    mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator");
+
+    if (mpack_node_error(node) != mpack_ok) {
+        buffer[0] = '\0';
+        return;
+    }
+
+    if (node.data->type != mpack_type_str) {
+        buffer[0] = '\0';
+        mpack_node_flag_error(node, mpack_error_type);
+        return;
+    }
+
+    if (node.data->len > bufsize - 1) {
+        buffer[0] = '\0';
+        mpack_node_flag_error(node, mpack_error_too_big);
+        return;
+    }
+
+    if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+        buffer[0] = '\0';
+        mpack_node_flag_error(node, mpack_error_type);
+        return;
+    }
+
+    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+    buffer[node.data->len] = '\0';
+}
+
+void mpack_node_copy_utf8_cstr(mpack_node_t node, char* buffer, size_t bufsize) {
+
+    // we can't break here because the error isn't recoverable; we
+    // have to add a null-terminator.
+    mpack_assert(buffer != NULL, "buffer is NULL");
+    mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator");
+
+    if (mpack_node_error(node) != mpack_ok) {
+        buffer[0] = '\0';
+        return;
+    }
+
+    if (node.data->type != mpack_type_str) {
+        buffer[0] = '\0';
+        mpack_node_flag_error(node, mpack_error_type);
+        return;
+    }
+
+    if (node.data->len > bufsize - 1) {
+        buffer[0] = '\0';
+        mpack_node_flag_error(node, mpack_error_too_big);
+        return;
+    }
+
+    if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+        buffer[0] = '\0';
+        mpack_node_flag_error(node, mpack_error_type);
+        return;
+    }
+
+    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+    buffer[node.data->len] = '\0';
+}
+
+#ifdef MPACK_MALLOC
+char* mpack_node_data_alloc(mpack_node_t node, size_t maxlen) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    // make sure this is a valid data type
+    mpack_type_t type = node.data->type;
+    if (type != mpack_type_str && type != mpack_type_bin
+            #if MPACK_EXTENSIONS
+            && type != mpack_type_ext
+            #endif
+    ) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    if (node.data->len > maxlen) {
+        mpack_node_flag_error(node, mpack_error_too_big);
+        return NULL;
+    }
+
+    char* ret = (char*) MPACK_MALLOC((size_t)node.data->len);
+    if (ret == NULL) {
+        mpack_node_flag_error(node, mpack_error_memory);
+        return NULL;
+    }
+
+    mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
+    return ret;
+}
+
+char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxlen) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    // make sure maxlen makes sense
+    if (maxlen < 1) {
+        mpack_break("maxlen is zero; you must have room for at least a null-terminator");
+        mpack_node_flag_error(node, mpack_error_bug);
+        return NULL;
+    }
+
+    if (node.data->type != mpack_type_str) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    if (node.data->len > maxlen - 1) {
+        mpack_node_flag_error(node, mpack_error_too_big);
+        return NULL;
+    }
+
+    if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1));
+    if (ret == NULL) {
+        mpack_node_flag_error(node, mpack_error_memory);
+        return NULL;
+    }
+
+    mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
+    ret[node.data->len] = '\0';
+    return ret;
+}
+
+char* mpack_node_utf8_cstr_alloc(mpack_node_t node, size_t maxlen) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    // make sure maxlen makes sense
+    if (maxlen < 1) {
+        mpack_break("maxlen is zero; you must have room for at least a null-terminator");
+        mpack_node_flag_error(node, mpack_error_bug);
+        return NULL;
+    }
+
+    if (node.data->type != mpack_type_str) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    if (node.data->len > maxlen - 1) {
+        mpack_node_flag_error(node, mpack_error_too_big);
+        return NULL;
+    }
+
+    if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1));
+    if (ret == NULL) {
+        mpack_node_flag_error(node, mpack_error_memory);
+        return NULL;
+    }
+
+    mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
+    ret[node.data->len] = '\0';
+    return ret;
+}
+#endif
+
+
+/*
+ * Compound Node Functions
+ */
+
+static mpack_node_data_t* mpack_node_map_int_impl(mpack_node_t node, int64_t num) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    if (node.data->type != mpack_type_map) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    mpack_node_data_t* found = NULL;
+
+    size_t i;
+    for (i = 0; i < node.data->len; ++i) {
+        mpack_node_data_t* key = mpack_node_child(node, i * 2);
+
+        if ((key->type == mpack_type_int && key->value.i == num) ||
+            (key->type == mpack_type_uint && num >= 0 && key->value.u == (uint64_t)num))
+        {
+            if (found) {
+                mpack_node_flag_error(node, mpack_error_data);
+                return NULL;
+            }
+            found = mpack_node_child(node, i * 2 + 1);
+        }
+    }
+
+    if (found)
+        return found;
+
+    return NULL;
+}
+
+static mpack_node_data_t* mpack_node_map_uint_impl(mpack_node_t node, uint64_t num) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    if (node.data->type != mpack_type_map) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    mpack_node_data_t* found = NULL;
+
+    size_t i;
+    for (i = 0; i < node.data->len; ++i) {
+        mpack_node_data_t* key = mpack_node_child(node, i * 2);
+
+        if ((key->type == mpack_type_uint && key->value.u == num) ||
+            (key->type == mpack_type_int && key->value.i >= 0 && (uint64_t)key->value.i == num))
+        {
+            if (found) {
+                mpack_node_flag_error(node, mpack_error_data);
+                return NULL;
+            }
+            found = mpack_node_child(node, i * 2 + 1);
+        }
+    }
+
+    if (found)
+        return found;
+
+    return NULL;
+}
+
+static mpack_node_data_t* mpack_node_map_str_impl(mpack_node_t node, const char* str, size_t length) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    mpack_assert(length == 0 || str != NULL, "str of length %i is NULL", (int)length);
+
+    if (node.data->type != mpack_type_map) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return NULL;
+    }
+
+    mpack_tree_t* tree = node.tree;
+    mpack_node_data_t* found = NULL;
+
+    size_t i;
+    for (i = 0; i < node.data->len; ++i) {
+        mpack_node_data_t* key = mpack_node_child(node, i * 2);
+
+        if (key->type == mpack_type_str && key->len == length &&
+                mpack_memcmp(str, mpack_node_data_unchecked(mpack_node(tree, key)), length) == 0) {
+            if (found) {
+                mpack_node_flag_error(node, mpack_error_data);
+                return NULL;
+            }
+            found = mpack_node_child(node, i * 2 + 1);
+        }
+    }
+
+    if (found)
+        return found;
+
+    return NULL;
+}
+
+static mpack_node_t mpack_node_wrap_lookup(mpack_tree_t* tree, mpack_node_data_t* data) {
+    if (!data) {
+        if (tree->error == mpack_ok)
+            mpack_tree_flag_error(tree, mpack_error_data);
+        return mpack_tree_nil_node(tree);
+    }
+    return mpack_node(tree, data);
+}
+
+static mpack_node_t mpack_node_wrap_lookup_optional(mpack_tree_t* tree, mpack_node_data_t* data) {
+    if (!data) {
+        if (tree->error == mpack_ok)
+            return mpack_tree_missing_node(tree);
+        return mpack_tree_nil_node(tree);
+    }
+    return mpack_node(tree, data);
+}
+
+mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num) {
+    return mpack_node_wrap_lookup(node.tree, mpack_node_map_int_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num) {
+    return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_int_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num) {
+    return mpack_node_wrap_lookup(node.tree, mpack_node_map_uint_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num) {
+    return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_uint_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length) {
+    return mpack_node_wrap_lookup(node.tree, mpack_node_map_str_impl(node, str, length));
+}
+
+mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length) {
+    return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_str_impl(node, str, length));
+}
+
+mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr) {
+    mpack_assert(cstr != NULL, "cstr is NULL");
+    return mpack_node_map_str(node, cstr, mpack_strlen(cstr));
+}
+
+mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr) {
+    mpack_assert(cstr != NULL, "cstr is NULL");
+    return mpack_node_map_str_optional(node, cstr, mpack_strlen(cstr));
+}
+
+bool mpack_node_map_contains_int(mpack_node_t node, int64_t num) {
+    return mpack_node_map_int_impl(node, num) != NULL;
+}
+
+bool mpack_node_map_contains_uint(mpack_node_t node, uint64_t num) {
+    return mpack_node_map_uint_impl(node, num) != NULL;
+}
+
+bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length) {
+    return mpack_node_map_str_impl(node, str, length) != NULL;
+}
+
+bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr) {
+    mpack_assert(cstr != NULL, "cstr is NULL");
+    return mpack_node_map_contains_str(node, cstr, mpack_strlen(cstr));
+}
+
+size_t mpack_node_enum_optional(mpack_node_t node, const char* strings[], size_t count) {
+    if (mpack_node_error(node) != mpack_ok)
+        return count;
+
+    // the value is only recognized if it is a string
+    if (mpack_node_type(node) != mpack_type_str)
+        return count;
+
+    // fetch the string
+    const char* key = mpack_node_str(node);
+    size_t keylen = mpack_node_strlen(node);
+    mpack_assert(mpack_node_error(node) == mpack_ok, "these should not fail");
+
+    // find what key it matches
+    size_t i;
+    for (i = 0; i < count; ++i) {
+        const char* other = strings[i];
+        size_t otherlen = mpack_strlen(other);
+        if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
+            return i;
+    }
+
+    // no matches
+    return count;
+}
+
+size_t mpack_node_enum(mpack_node_t node, const char* strings[], size_t count) {
+    size_t value = mpack_node_enum_optional(node, strings, count);
+    if (value == count)
+        mpack_node_flag_error(node, mpack_error_type);
+    return value;
+}
+
+mpack_type_t mpack_node_type(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return mpack_type_nil;
+    return node.data->type;
+}
+
+bool mpack_node_is_nil(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok) {
+        // All nodes are treated as nil nodes when we are in error.
+        return true;
+    }
+    return node.data->type == mpack_type_nil;
+}
+
+bool mpack_node_is_missing(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok) {
+        // errors still return nil nodes, not missing nodes.
+        return false;
+    }
+    return node.data->type == mpack_type_missing;
+}
+
+void mpack_node_nil(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return;
+    if (node.data->type != mpack_type_nil)
+        mpack_node_flag_error(node, mpack_error_type);
+}
+
+void mpack_node_missing(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return;
+    if (node.data->type != mpack_type_missing)
+        mpack_node_flag_error(node, mpack_error_type);
+}
+
+bool mpack_node_bool(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return false;
+
+    if (node.data->type == mpack_type_bool)
+        return node.data->value.b;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return false;
+}
+
+void mpack_node_true(mpack_node_t node) {
+    if (mpack_node_bool(node) != true)
+        mpack_node_flag_error(node, mpack_error_type);
+}
+
+void mpack_node_false(mpack_node_t node) {
+    if (mpack_node_bool(node) != false)
+        mpack_node_flag_error(node, mpack_error_type);
+}
+
+uint8_t mpack_node_u8(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        if (node.data->value.u <= MPACK_UINT8_MAX)
+            return (uint8_t)node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        if (node.data->value.i >= 0 && node.data->value.i <= MPACK_UINT8_MAX)
+            return (uint8_t)node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+int8_t mpack_node_i8(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        if (node.data->value.u <= MPACK_INT8_MAX)
+            return (int8_t)node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        if (node.data->value.i >= MPACK_INT8_MIN && node.data->value.i <= MPACK_INT8_MAX)
+            return (int8_t)node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+uint16_t mpack_node_u16(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        if (node.data->value.u <= MPACK_UINT16_MAX)
+            return (uint16_t)node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        if (node.data->value.i >= 0 && node.data->value.i <= MPACK_UINT16_MAX)
+            return (uint16_t)node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+int16_t mpack_node_i16(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        if (node.data->value.u <= MPACK_INT16_MAX)
+            return (int16_t)node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        if (node.data->value.i >= MPACK_INT16_MIN && node.data->value.i <= MPACK_INT16_MAX)
+            return (int16_t)node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+uint32_t mpack_node_u32(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        if (node.data->value.u <= MPACK_UINT32_MAX)
+            return (uint32_t)node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        if (node.data->value.i >= 0 && node.data->value.i <= MPACK_UINT32_MAX)
+            return (uint32_t)node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+int32_t mpack_node_i32(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        if (node.data->value.u <= MPACK_INT32_MAX)
+            return (int32_t)node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        if (node.data->value.i >= MPACK_INT32_MIN && node.data->value.i <= MPACK_INT32_MAX)
+            return (int32_t)node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+uint64_t mpack_node_u64(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        return node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        if (node.data->value.i >= 0)
+            return (uint64_t)node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+int64_t mpack_node_i64(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_uint) {
+        if (node.data->value.u <= (uint64_t)MPACK_INT64_MAX)
+            return (int64_t)node.data->value.u;
+    } else if (node.data->type == mpack_type_int) {
+        return node.data->value.i;
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+unsigned int mpack_node_uint(mpack_node_t node) {
+
+    // This should be true at compile-time, so this just wraps the 32-bit function.
+    if (sizeof(unsigned int) == 4)
+        return (unsigned int)mpack_node_u32(node);
+
+    // Otherwise we use u64 and check the range.
+    uint64_t val = mpack_node_u64(node);
+    if (val <= MPACK_UINT_MAX)
+        return (unsigned int)val;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+int mpack_node_int(mpack_node_t node) {
+
+    // This should be true at compile-time, so this just wraps the 32-bit function.
+    if (sizeof(int) == 4)
+        return (int)mpack_node_i32(node);
+
+    // Otherwise we use i64 and check the range.
+    int64_t val = mpack_node_i64(node);
+    if (val >= MPACK_INT_MIN && val <= MPACK_INT_MAX)
+        return (int)val;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+#if MPACK_FLOAT
+float mpack_node_float(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0.0f;
+
+    if (node.data->type == mpack_type_uint)
+        return (float)node.data->value.u;
+    if (node.data->type == mpack_type_int)
+        return (float)node.data->value.i;
+    if (node.data->type == mpack_type_float)
+        return node.data->value.f;
+
+    if (node.data->type == mpack_type_double) {
+        #if MPACK_DOUBLE
+        return (float)node.data->value.d;
+        #else
+        return mpack_shorten_raw_double_to_float(node.data->value.d);
+        #endif
+    }
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0.0f;
+}
+#endif
+
+#if MPACK_DOUBLE
+double mpack_node_double(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0.0;
+
+    if (node.data->type == mpack_type_uint)
+        return (double)node.data->value.u;
+    else if (node.data->type == mpack_type_int)
+        return (double)node.data->value.i;
+    else if (node.data->type == mpack_type_float)
+        return (double)node.data->value.f;
+    else if (node.data->type == mpack_type_double)
+        return node.data->value.d;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0.0;
+}
+#endif
+
+#if MPACK_FLOAT
+float mpack_node_float_strict(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0.0f;
+
+    if (node.data->type == mpack_type_float)
+        return node.data->value.f;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0.0f;
+}
+#endif
+
+#if MPACK_DOUBLE
+double mpack_node_double_strict(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0.0;
+
+    if (node.data->type == mpack_type_float)
+        return (double)node.data->value.f;
+    else if (node.data->type == mpack_type_double)
+        return node.data->value.d;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0.0;
+}
+#endif
+
+#if !MPACK_FLOAT
+uint32_t mpack_node_raw_float(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_float)
+        return node.data->value.f;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+#endif
+
+#if !MPACK_DOUBLE
+uint64_t mpack_node_raw_double(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_double)
+        return node.data->value.d;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+#endif
+
+#if MPACK_EXTENSIONS
+int8_t mpack_node_exttype(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_ext)
+        return mpack_node_exttype_unchecked(node);
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+#endif
+
+uint32_t mpack_node_data_len(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    mpack_type_t type = node.data->type;
+    if (type == mpack_type_str || type == mpack_type_bin
+            #if MPACK_EXTENSIONS
+            || type == mpack_type_ext
+            #endif
+            )
+        return (uint32_t)node.data->len;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+size_t mpack_node_strlen(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_str)
+        return (size_t)node.data->len;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+const char* mpack_node_str(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    mpack_type_t type = node.data->type;
+    if (type == mpack_type_str)
+        return mpack_node_data_unchecked(node);
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return NULL;
+}
+
+const char* mpack_node_data(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    mpack_type_t type = node.data->type;
+    if (type == mpack_type_str || type == mpack_type_bin
+            #if MPACK_EXTENSIONS
+            || type == mpack_type_ext
+            #endif
+            )
+        return mpack_node_data_unchecked(node);
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return NULL;
+}
+
+const char* mpack_node_bin_data(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return NULL;
+
+    if (node.data->type == mpack_type_bin)
+        return mpack_node_data_unchecked(node);
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return NULL;
+}
+
+size_t mpack_node_bin_size(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type == mpack_type_bin)
+        return (size_t)node.data->len;
+
+    mpack_node_flag_error(node, mpack_error_type);
+    return 0;
+}
+
+size_t mpack_node_array_length(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type != mpack_type_array) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return 0;
+    }
+
+    return (size_t)node.data->len;
+}
+
+mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index) {
+    if (mpack_node_error(node) != mpack_ok)
+        return mpack_tree_nil_node(node.tree);
+
+    if (node.data->type != mpack_type_array) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return mpack_tree_nil_node(node.tree);
+    }
+
+    if (index >= node.data->len) {
+        mpack_node_flag_error(node, mpack_error_data);
+        return mpack_tree_nil_node(node.tree);
+    }
+
+    return mpack_node(node.tree, mpack_node_child(node, index));
+}
+
+size_t mpack_node_map_count(mpack_node_t node) {
+    if (mpack_node_error(node) != mpack_ok)
+        return 0;
+
+    if (node.data->type != mpack_type_map) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return 0;
+    }
+
+    return node.data->len;
+}
+
+// internal node map lookup
+static mpack_node_t mpack_node_map_at(mpack_node_t node, size_t index, size_t offset) {
+    if (mpack_node_error(node) != mpack_ok)
+        return mpack_tree_nil_node(node.tree);
+
+    if (node.data->type != mpack_type_map) {
+        mpack_node_flag_error(node, mpack_error_type);
+        return mpack_tree_nil_node(node.tree);
+    }
+
+    if (index >= node.data->len) {
+        mpack_node_flag_error(node, mpack_error_data);
+        return mpack_tree_nil_node(node.tree);
+    }
+
+    return mpack_node(node.tree, mpack_node_child(node, index * 2 + offset));
+}
+
+mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index) {
+    return mpack_node_map_at(node, index, 0);
+}
+
+mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index) {
+    return mpack_node_map_at(node, index, 1);
+}
+
+#endif
+
+}  // namespace wpi
+MPACK_SILENCE_WARNINGS_END
diff --git a/wpiutil/src/main/native/cpp/raw_istream.cpp b/wpiutil/src/main/native/cpp/raw_istream.cpp
index f63c217..a179b98 100644
--- a/wpiutil/src/main/native/cpp/raw_istream.cpp
+++ b/wpiutil/src/main/native/cpp/raw_istream.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #define _CRT_NONSTDC_NO_WARNINGS
 
@@ -17,10 +14,10 @@
 
 #include <cstdlib>
 #include <cstring>
+#include <string_view>
 
-#include "wpi/FileSystem.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
+#include "wpi/fs.h"
 
 #if defined(_MSC_VER)
 #ifndef STDIN_FILENO
@@ -36,22 +33,30 @@
 
 using namespace wpi;
 
-StringRef raw_istream::getline(SmallVectorImpl<char>& buf, int maxLen) {
+std::string_view raw_istream::getline(SmallVectorImpl<char>& buf, int maxLen) {
   buf.clear();
   for (int i = 0; i < maxLen; ++i) {
     char c;
     read(c);
-    if (has_error()) return StringRef{buf.data(), buf.size()};
-    if (c == '\r') continue;
+    if (has_error()) {
+      return {buf.data(), buf.size()};
+    }
+    if (c == '\r') {
+      continue;
+    }
     buf.push_back(c);
-    if (c == '\n') break;
+    if (c == '\n') {
+      break;
+    }
   }
-  return StringRef{buf.data(), buf.size()};
+  return {buf.data(), buf.size()};
 }
 
 void raw_mem_istream::close() {}
 
-size_t raw_mem_istream::in_avail() const { return m_left; }
+size_t raw_mem_istream::in_avail() const {
+  return m_left;
+}
 
 void raw_mem_istream::read_impl(void* data, size_t len) {
   if (len > m_left) {
@@ -64,25 +69,27 @@
   set_read_count(len);
 }
 
-static int getFD(const Twine& Filename, std::error_code& EC) {
+static int getFD(std::string_view Filename, std::error_code& EC) {
   // Handle "-" as stdin. Note that when we do this, we consider ourself
   // the owner of stdin. This means that we can do things like close the
   // file descriptor when we're done and set the "binary" flag globally.
-  if (Filename.isSingleStringRef() && Filename.getSingleStringRef() == "-") {
+  if (Filename == "-") {
     EC = std::error_code();
     return STDIN_FILENO;
   }
 
-  int FD;
-
-  EC = sys::fs::openFileForRead(Filename, FD);
-  if (EC) return -1;
-
-  EC = std::error_code();
+  fs::file_t F = fs::OpenFileForRead(Filename, EC);
+  if (EC) {
+    return -1;
+  }
+  int FD = fs::FileToFd(F, EC, fs::OF_None);
+  if (EC) {
+    return -1;
+  }
   return FD;
 }
 
-raw_fd_istream::raw_fd_istream(const Twine& filename, std::error_code& ec,
+raw_fd_istream::raw_fd_istream(std::string_view filename, std::error_code& ec,
                                size_t bufSize)
     : raw_fd_istream(getFD(filename, ec), true, bufSize) {}
 
@@ -92,7 +99,9 @@
 }
 
 raw_fd_istream::~raw_fd_istream() {
-  if (m_shouldClose) close();
+  if (m_shouldClose) {
+    close();
+  }
   std::free(m_buf);
 }
 
@@ -103,7 +112,9 @@
   }
 }
 
-size_t raw_fd_istream::in_avail() const { return m_end - m_cur; }
+size_t raw_fd_istream::in_avail() const {
+  return m_end - m_cur;
+}
 
 void raw_fd_istream::read_impl(void* data, size_t len) {
   char* cdata = static_cast<char*>(data);
diff --git a/wpiutil/src/main/native/cpp/raw_socket_istream.cpp b/wpiutil/src/main/native/cpp/raw_socket_istream.cpp
index 68c4dd6..c6b759e 100644
--- a/wpiutil/src/main/native/cpp/raw_socket_istream.cpp
+++ b/wpiutil/src/main/native/cpp/raw_socket_istream.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/raw_socket_istream.h"
 
@@ -27,6 +24,10 @@
   set_read_count(pos);
 }
 
-void raw_socket_istream::close() { m_stream.close(); }
+void raw_socket_istream::close() {
+  m_stream.close();
+}
 
-size_t raw_socket_istream::in_avail() const { return 0; }
+size_t raw_socket_istream::in_avail() const {
+  return 0;
+}
diff --git a/wpiutil/src/main/native/cpp/raw_socket_ostream.cpp b/wpiutil/src/main/native/cpp/raw_socket_ostream.cpp
index fb8d94d..af01c84 100644
--- a/wpiutil/src/main/native/cpp/raw_socket_ostream.cpp
+++ b/wpiutil/src/main/native/cpp/raw_socket_ostream.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/raw_socket_ostream.h"
 
@@ -13,7 +10,9 @@
 
 raw_socket_ostream::~raw_socket_ostream() {
   flush();
-  if (m_shouldClose) close();
+  if (m_shouldClose) {
+    close();
+  }
 }
 
 void raw_socket_ostream::write_impl(const char* data, size_t len) {
@@ -30,10 +29,14 @@
   }
 }
 
-uint64_t raw_socket_ostream::current_pos() const { return 0; }
+uint64_t raw_socket_ostream::current_pos() const {
+  return 0;
+}
 
 void raw_socket_ostream::close() {
-  if (!m_shouldClose) return;
+  if (!m_shouldClose) {
+    return;
+  }
   flush();
   m_stream.close();
 }
diff --git a/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp b/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp
index 8fc103d..f055a2a 100644
--- a/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp
+++ b/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/raw_uv_ostream.h"
 
@@ -35,6 +32,8 @@
 
 uint64_t raw_uv_ostream::current_pos() const {
   uint64_t size = 0;
-  for (auto&& buf : m_bufs) size += buf.len;
+  for (auto&& buf : m_bufs) {
+    size += buf.len;
+  }
   return size;
 }
diff --git a/wpiutil/src/main/native/cpp/sendable/SendableRegistry.cpp b/wpiutil/src/main/native/cpp/sendable/SendableRegistry.cpp
new file mode 100644
index 0000000..3ad39e5
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/sendable/SendableRegistry.cpp
@@ -0,0 +1,434 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/sendable/SendableRegistry.h"
+
+#include <memory>
+
+#include "fmt/format.h"
+#include "wpi/DenseMap.h"
+#include "wpi/SmallVector.h"
+#include "wpi/UidVector.h"
+#include "wpi/mutex.h"
+#include "wpi/sendable/Sendable.h"
+#include "wpi/sendable/SendableBuilder.h"
+
+using namespace wpi;
+
+namespace {
+struct Component {
+  Sendable* sendable = nullptr;
+  std::unique_ptr<SendableBuilder> builder;
+  std::string name;
+  std::string subsystem = "Ungrouped";
+  Sendable* parent = nullptr;
+  bool liveWindow = false;
+  wpi::SmallVector<std::shared_ptr<void>, 2> data;
+
+  void SetName(std::string_view moduleType, int channel) {
+    name = fmt::format("{}[{}]", moduleType, channel);
+  }
+
+  void SetName(std::string_view moduleType, int moduleNumber, int channel) {
+    name = fmt::format("{}[{},{}]", moduleType, moduleNumber, channel);
+  }
+};
+
+struct SendableRegistryInst {
+  wpi::recursive_mutex mutex;
+
+  std::function<std::unique_ptr<SendableBuilder>()> liveWindowFactory;
+  wpi::UidVector<std::unique_ptr<Component>, 32> components;
+  wpi::DenseMap<void*, SendableRegistry::UID> componentMap;
+  int nextDataHandle = 0;
+
+  Component& GetOrAdd(void* sendable, SendableRegistry::UID* uid = nullptr);
+};
+}  // namespace
+
+Component& SendableRegistryInst::GetOrAdd(void* sendable,
+                                          SendableRegistry::UID* uid) {
+  SendableRegistry::UID& compUid = componentMap[sendable];
+  if (compUid == 0) {
+    compUid = components.emplace_back(std::make_unique<Component>()) + 1;
+  }
+  if (uid) {
+    *uid = compUid;
+  }
+
+  return *components[compUid - 1];
+}
+
+static SendableRegistryInst& GetInstance() {
+  static SendableRegistryInst instance;
+  return instance;
+}
+
+void SendableRegistry::SetLiveWindowBuilderFactory(
+    std::function<std::unique_ptr<SendableBuilder>()> factory) {
+  GetInstance().liveWindowFactory = std::move(factory);
+}
+
+void SendableRegistry::Add(Sendable* sendable, std::string_view name) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  comp.name = name;
+}
+
+void SendableRegistry::Add(Sendable* sendable, std::string_view moduleType,
+                           int channel) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  comp.SetName(moduleType, channel);
+}
+
+void SendableRegistry::Add(Sendable* sendable, std::string_view moduleType,
+                           int moduleNumber, int channel) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  comp.SetName(moduleType, moduleNumber, channel);
+}
+
+void SendableRegistry::Add(Sendable* sendable, std::string_view subsystem,
+                           std::string_view name) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  comp.name = name;
+  comp.subsystem = subsystem;
+}
+
+void SendableRegistry::AddLW(Sendable* sendable, std::string_view name) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  if (inst.liveWindowFactory) {
+    comp.builder = inst.liveWindowFactory();
+  }
+  comp.liveWindow = true;
+  comp.name = name;
+}
+
+void SendableRegistry::AddLW(Sendable* sendable, std::string_view moduleType,
+                             int channel) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  if (inst.liveWindowFactory) {
+    comp.builder = inst.liveWindowFactory();
+  }
+  comp.liveWindow = true;
+  comp.SetName(moduleType, channel);
+}
+
+void SendableRegistry::AddLW(Sendable* sendable, std::string_view moduleType,
+                             int moduleNumber, int channel) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  if (inst.liveWindowFactory) {
+    comp.builder = inst.liveWindowFactory();
+  }
+  comp.liveWindow = true;
+  comp.SetName(moduleType, moduleNumber, channel);
+}
+
+void SendableRegistry::AddLW(Sendable* sendable, std::string_view subsystem,
+                             std::string_view name) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(sendable);
+  comp.sendable = sendable;
+  if (inst.liveWindowFactory) {
+    comp.builder = inst.liveWindowFactory();
+  }
+  comp.liveWindow = true;
+  comp.name = name;
+  comp.subsystem = subsystem;
+}
+
+void SendableRegistry::AddChild(Sendable* parent, Sendable* child) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(child);
+  comp.parent = parent;
+}
+
+void SendableRegistry::AddChild(Sendable* parent, void* child) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto& comp = inst.GetOrAdd(child);
+  comp.parent = parent;
+}
+
+bool SendableRegistry::Remove(Sendable* sendable) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end()) {
+    return false;
+  }
+  UID compUid = it->getSecond();
+  inst.components.erase(compUid - 1);
+  inst.componentMap.erase(it);
+  // update any parent pointers
+  for (auto&& comp : inst.components) {
+    if (comp->parent == sendable) {
+      comp->parent = nullptr;
+    }
+  }
+  return true;
+}
+
+void SendableRegistry::Move(Sendable* to, Sendable* from) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(from);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  UID compUid = it->getSecond();
+  inst.componentMap.erase(it);
+  inst.componentMap[to] = compUid;
+  auto& comp = *inst.components[compUid - 1];
+  comp.sendable = to;
+  if (comp.builder && comp.builder->IsPublished()) {
+    // rebuild builder, as lambda captures can point to "from"
+    comp.builder->ClearProperties();
+    to->InitSendable(*comp.builder);
+  }
+  // update any parent pointers
+  for (auto&& comp : inst.components) {
+    if (comp->parent == from) {
+      comp->parent = to;
+    }
+  }
+}
+
+bool SendableRegistry::Contains(const Sendable* sendable) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  return inst.componentMap.count(sendable) != 0;
+}
+
+std::string SendableRegistry::GetName(const Sendable* sendable) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return {};
+  }
+  return inst.components[it->getSecond() - 1]->name;
+}
+
+void SendableRegistry::SetName(Sendable* sendable, std::string_view name) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  inst.components[it->getSecond() - 1]->name = name;
+}
+
+void SendableRegistry::SetName(Sendable* sendable, std::string_view moduleType,
+                               int channel) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  inst.components[it->getSecond() - 1]->SetName(moduleType, channel);
+}
+
+void SendableRegistry::SetName(Sendable* sendable, std::string_view moduleType,
+                               int moduleNumber, int channel) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  inst.components[it->getSecond() - 1]->SetName(moduleType, moduleNumber,
+                                                channel);
+}
+
+void SendableRegistry::SetName(Sendable* sendable, std::string_view subsystem,
+                               std::string_view name) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  auto& comp = *inst.components[it->getSecond() - 1];
+  comp.name = name;
+  comp.subsystem = subsystem;
+}
+
+std::string SendableRegistry::GetSubsystem(const Sendable* sendable) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return {};
+  }
+  return inst.components[it->getSecond() - 1]->subsystem;
+}
+
+void SendableRegistry::SetSubsystem(Sendable* sendable,
+                                    std::string_view subsystem) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  inst.components[it->getSecond() - 1]->subsystem = subsystem;
+}
+
+int SendableRegistry::GetDataHandle() {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  return inst.nextDataHandle++;
+}
+
+std::shared_ptr<void> SendableRegistry::SetData(Sendable* sendable, int handle,
+                                                std::shared_ptr<void> data) {
+  auto& inst = GetInstance();
+  assert(handle >= 0);
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return nullptr;
+  }
+  auto& comp = *inst.components[it->getSecond() - 1];
+  std::shared_ptr<void> rv;
+  if (static_cast<size_t>(handle) < comp.data.size()) {
+    rv = std::move(comp.data[handle]);
+  } else {
+    comp.data.resize(handle + 1);
+  }
+  comp.data[handle] = std::move(data);
+  return rv;
+}
+
+std::shared_ptr<void> SendableRegistry::GetData(Sendable* sendable,
+                                                int handle) {
+  auto& inst = GetInstance();
+  assert(handle >= 0);
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return nullptr;
+  }
+  auto& comp = *inst.components[it->getSecond() - 1];
+  if (static_cast<size_t>(handle) >= comp.data.size()) {
+    return nullptr;
+  }
+  return comp.data[handle];
+}
+
+void SendableRegistry::EnableLiveWindow(Sendable* sendable) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  inst.components[it->getSecond() - 1]->liveWindow = true;
+}
+
+void SendableRegistry::DisableLiveWindow(Sendable* sendable) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  auto it = inst.componentMap.find(sendable);
+  if (it == inst.componentMap.end() || !inst.components[it->getSecond() - 1]) {
+    return;
+  }
+  inst.components[it->getSecond() - 1]->liveWindow = false;
+}
+
+SendableRegistry::UID SendableRegistry::GetUniqueId(Sendable* sendable) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  UID uid;
+  auto& comp = inst.GetOrAdd(sendable, &uid);
+  comp.sendable = sendable;
+  return uid;
+}
+
+Sendable* SendableRegistry::GetSendable(UID uid) {
+  auto& inst = GetInstance();
+  if (uid == 0) {
+    return nullptr;
+  }
+  std::scoped_lock lock(inst.mutex);
+  if ((uid - 1) >= inst.components.size() || !inst.components[uid - 1]) {
+    return nullptr;
+  }
+  return inst.components[uid - 1]->sendable;
+}
+
+void SendableRegistry::Publish(UID sendableUid,
+                               std::unique_ptr<SendableBuilder> builder) {
+  auto& inst = GetInstance();
+  std::scoped_lock lock(inst.mutex);
+  if (sendableUid == 0 || (sendableUid - 1) >= inst.components.size() ||
+      !inst.components[sendableUid - 1]) {
+    return;
+  }
+  auto& comp = *inst.components[sendableUid - 1];
+  comp.builder = std::move(builder);  // clear any current builder
+  comp.sendable->InitSendable(*comp.builder);
+  comp.builder->Update();
+}
+
+void SendableRegistry::Update(UID sendableUid) {
+  auto& inst = GetInstance();
+  if (sendableUid == 0) {
+    return;
+  }
+  std::scoped_lock lock(inst.mutex);
+  if ((sendableUid - 1) >= inst.components.size() ||
+      !inst.components[sendableUid - 1]) {
+    return;
+  }
+  if (inst.components[sendableUid - 1]->builder) {
+    inst.components[sendableUid - 1]->builder->Update();
+  }
+}
+
+void SendableRegistry::ForeachLiveWindow(
+    int dataHandle, wpi::function_ref<void(CallbackData& data)> callback) {
+  auto& inst = GetInstance();
+  assert(dataHandle >= 0);
+  std::scoped_lock lock(inst.mutex);
+  wpi::SmallVector<Component*, 128> components;
+  for (auto&& comp : inst.components) {
+    components.emplace_back(comp.get());
+  }
+  for (auto comp : components) {
+    if (comp && comp->builder && comp->sendable && comp->liveWindow) {
+      if (static_cast<size_t>(dataHandle) >= comp->data.size()) {
+        comp->data.resize(dataHandle + 1);
+      }
+      CallbackData cbdata{comp->sendable,         comp->name,
+                          comp->subsystem,        comp->parent,
+                          comp->data[dataHandle], *comp->builder};
+      callback(cbdata);
+    }
+  }
+}
diff --git a/wpiutil/src/main/native/cpp/sha1.cpp b/wpiutil/src/main/native/cpp/sha1.cpp
index e1346f4..8ec7cb1 100644
--- a/wpiutil/src/main/native/cpp/sha1.cpp
+++ b/wpiutil/src/main/native/cpp/sha1.cpp
@@ -212,10 +212,12 @@
   }
 }
 
-SHA1::SHA1() { reset(digest, buf_size, transforms); }
+SHA1::SHA1() {
+  reset(digest, buf_size, transforms);
+}
 
-void SHA1::Update(StringRef s) {
-  raw_mem_istream is(makeArrayRef(s.data(), s.size()));
+void SHA1::Update(std::string_view s) {
+  raw_mem_istream is(span<const char>(s.data(), s.size()));
   Update(is);
 }
 
@@ -291,7 +293,7 @@
   return os.str();
 }
 
-StringRef SHA1::Final(SmallVectorImpl<char>& buf) {
+std::string_view SHA1::Final(SmallVectorImpl<char>& buf) {
   raw_svector_ostream os(buf);
 
   finalize(digest, buffer, buf_size, transforms, os, true);
@@ -299,7 +301,7 @@
   return os.str();
 }
 
-StringRef SHA1::RawFinal(SmallVectorImpl<char>& buf) {
+std::string_view SHA1::RawFinal(SmallVectorImpl<char>& buf) {
   raw_svector_ostream os(buf);
 
   finalize(digest, buffer, buf_size, transforms, os, false);
@@ -307,7 +309,7 @@
   return os.str();
 }
 
-std::string SHA1::FromFile(StringRef filename) {
+std::string SHA1::FromFile(std::string_view filename) {
   std::error_code ec;
   raw_fd_istream stream(filename, ec);
   SHA1 checksum;
diff --git a/wpiutil/src/main/native/cpp/timestamp.cpp b/wpiutil/src/main/native/cpp/timestamp.cpp
index 7f3b0cf..9c32bf3 100644
--- a/wpiutil/src/main/native/cpp/timestamp.cpp
+++ b/wpiutil/src/main/native/cpp/timestamp.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/timestamp.h"
 
@@ -19,7 +16,7 @@
 #endif
 
 // offset in microseconds
-static uint64_t zerotime() {
+static uint64_t zerotime() noexcept {
 #ifdef _WIN32
   FILETIME ft;
   uint64_t tmpres = 0;
@@ -43,7 +40,7 @@
 #endif
 }
 
-static uint64_t timestamp() {
+static uint64_t timestamp() noexcept {
 #ifdef _WIN32
   LARGE_INTEGER li;
   QueryPerformanceCounter(&li);
@@ -95,14 +92,22 @@
   now_impl = func ? func : NowDefault;
 }
 
-uint64_t wpi::Now() { return (now_impl.load())(); }
+uint64_t wpi::Now() {
+  return (now_impl.load())();
+}
 
 extern "C" {
 
-uint64_t WPI_NowDefault(void) { return wpi::NowDefault(); }
+uint64_t WPI_NowDefault(void) {
+  return wpi::NowDefault();
+}
 
-void WPI_SetNowImpl(uint64_t (*func)(void)) { wpi::SetNowImpl(func); }
+void WPI_SetNowImpl(uint64_t (*func)(void)) {
+  wpi::SetNowImpl(func);
+}
 
-uint64_t WPI_Now(void) { return wpi::Now(); }
+uint64_t WPI_Now(void) {
+  return wpi::Now();
+}
 
 }  // extern "C"
diff --git a/wpiutil/src/main/native/cpp/uv/Async.cpp b/wpiutil/src/main/native/cpp/uv/Async.cpp
index 5479f49..c9d698e 100644
--- a/wpiutil/src/main/native/cpp/uv/Async.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Async.cpp
@@ -1,22 +1,19 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Async.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 Async<>::~Async() noexcept {
-  if (auto loop = m_loop.lock())
+  if (auto loop = m_loop.lock()) {
     Close();
-  else
+  } else {
     ForceClosed();
+  }
 }
 
 std::shared_ptr<Async<>> Async<>::Create(const std::shared_ptr<Loop>& loop) {
@@ -33,5 +30,4 @@
   return h;
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Check.cpp b/wpiutil/src/main/native/cpp/uv/Check.cpp
index 0f4cccf..97265d4 100644
--- a/wpiutil/src/main/native/cpp/uv/Check.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Check.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Check.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Check> Check::Create(Loop& loop) {
   auto h = std::make_shared<Check>(private_init{});
@@ -30,5 +26,4 @@
   });
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/FsEvent.cpp b/wpiutil/src/main/native/cpp/uv/FsEvent.cpp
index 54ba31f..3c83d1d 100644
--- a/wpiutil/src/main/native/cpp/uv/FsEvent.cpp
+++ b/wpiutil/src/main/native/cpp/uv/FsEvent.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/FsEvent.h"
 
@@ -12,8 +9,7 @@
 #include "wpi/SmallString.h"
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<FsEvent> FsEvent::Create(Loop& loop) {
   auto h = std::make_shared<FsEvent>(private_init{});
@@ -26,18 +22,19 @@
   return h;
 }
 
-void FsEvent::Start(const Twine& path, unsigned int flags) {
-  SmallString<128> pathBuf;
+void FsEvent::Start(std::string_view path, unsigned int flags) {
+  SmallString<128> pathBuf{path};
   Invoke(
       &uv_fs_event_start, GetRaw(),
       [](uv_fs_event_t* handle, const char* filename, int events, int status) {
         FsEvent& h = *static_cast<FsEvent*>(handle->data);
-        if (status < 0)
+        if (status < 0) {
           h.ReportError(status);
-        else
+        } else {
           h.fsEvent(filename, events);
+        }
       },
-      path.toNullTerminatedStringRef(pathBuf).data(), flags);
+      pathBuf.c_str(), flags);
 }
 
 std::string FsEvent::GetPath() {
@@ -63,5 +60,4 @@
   return std::string{};
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp b/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp
index 21f6404..2e6e38f 100644
--- a/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp
+++ b/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp
@@ -1,55 +1,51 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/GetAddrInfo.h"
 
+#include "wpi/SmallString.h"
 #include "wpi/uv/Loop.h"
 #include "wpi/uv/util.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 GetAddrInfoReq::GetAddrInfoReq() {
   error = [this](Error err) { GetLoop().error(err); };
 }
 
 void GetAddrInfo(Loop& loop, const std::shared_ptr<GetAddrInfoReq>& req,
-                 const Twine& node, const Twine& service,
+                 std::string_view node, std::string_view service,
                  const addrinfo* hints) {
-  SmallVector<char, 128> nodeStr;
-  SmallVector<char, 128> serviceStr;
+  SmallString<128> nodeStr{node};
+  SmallString<128> serviceStr{service};
   int err = uv_getaddrinfo(
       loop.GetRaw(), req->GetRaw(),
       [](uv_getaddrinfo_t* req, int status, addrinfo* res) {
         auto& h = *static_cast<GetAddrInfoReq*>(req->data);
-        if (status < 0)
+        if (status < 0) {
           h.ReportError(status);
-        else
+        } else {
           h.resolved(*res);
+        }
         uv_freeaddrinfo(res);
         h.Release();  // this is always a one-shot
       },
-      node.isNull() ? nullptr : node.toNullTerminatedStringRef(nodeStr).data(),
-      service.isNull() ? nullptr
-                       : service.toNullTerminatedStringRef(serviceStr).data(),
-      hints);
-  if (err < 0)
+      node.empty() ? nullptr : nodeStr.c_str(),
+      service.empty() ? nullptr : serviceStr.c_str(), hints);
+  if (err < 0) {
     loop.ReportError(err);
-  else
+  } else {
     req->Keep();
+  }
 }
 
 void GetAddrInfo(Loop& loop, std::function<void(const addrinfo&)> callback,
-                 const Twine& node, const Twine& service,
+                 std::string_view node, std::string_view service,
                  const addrinfo* hints) {
   auto req = std::make_shared<GetAddrInfoReq>();
-  req->resolved.connect(callback);
+  req->resolved.connect(std::move(callback));
   GetAddrInfo(loop, req, node, service, hints);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp b/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp
index 6a4446f..4e662f3 100644
--- a/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp
+++ b/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp
@@ -1,17 +1,13 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/GetNameInfo.h"
 
 #include "wpi/uv/Loop.h"
 #include "wpi/uv/util.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 GetNameInfoReq::GetNameInfoReq() {
   error = [this](Error err) { GetLoop().error(err); };
@@ -24,68 +20,75 @@
       [](uv_getnameinfo_t* req, int status, const char* hostname,
          const char* service) {
         auto& h = *static_cast<GetNameInfoReq*>(req->data);
-        if (status < 0)
+        if (status < 0) {
           h.ReportError(status);
-        else
+        } else {
           h.resolved(hostname, service);
+        }
         h.Release();  // this is always a one-shot
       },
       &addr, flags);
-  if (err < 0)
+  if (err < 0) {
     loop.ReportError(err);
-  else
+  } else {
     req->Keep();
+  }
 }
 
 void GetNameInfo(Loop& loop,
                  std::function<void(const char*, const char*)> callback,
                  const sockaddr& addr, int flags) {
   auto req = std::make_shared<GetNameInfoReq>();
-  req->resolved.connect(callback);
+  req->resolved.connect(std::move(callback));
   GetNameInfo(loop, req, addr, flags);
 }
 
 void GetNameInfo4(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
-                  const Twine& ip, unsigned int port, int flags) {
+                  std::string_view ip, unsigned int port, int flags) {
   sockaddr_in addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     loop.ReportError(err);
-  else
+  } else {
     GetNameInfo(loop, req, reinterpret_cast<const sockaddr&>(addr), flags);
+  }
 }
 
 void GetNameInfo4(Loop& loop,
                   std::function<void(const char*, const char*)> callback,
-                  const Twine& ip, unsigned int port, int flags) {
+                  std::string_view ip, unsigned int port, int flags) {
   sockaddr_in addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     loop.ReportError(err);
-  else
-    GetNameInfo(loop, callback, reinterpret_cast<const sockaddr&>(addr), flags);
+  } else {
+    GetNameInfo(loop, std::move(callback),
+                reinterpret_cast<const sockaddr&>(addr), flags);
+  }
 }
 
 void GetNameInfo6(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
-                  const Twine& ip, unsigned int port, int flags) {
+                  std::string_view ip, unsigned int port, int flags) {
   sockaddr_in6 addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     loop.ReportError(err);
-  else
+  } else {
     GetNameInfo(loop, req, reinterpret_cast<const sockaddr&>(addr), flags);
+  }
 }
 
 void GetNameInfo6(Loop& loop,
                   std::function<void(const char*, const char*)> callback,
-                  const Twine& ip, unsigned int port, int flags) {
+                  std::string_view ip, unsigned int port, int flags) {
   sockaddr_in6 addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     loop.ReportError(err);
-  else
-    GetNameInfo(loop, callback, reinterpret_cast<const sockaddr&>(addr), flags);
+  } else {
+    GetNameInfo(loop, std::move(callback),
+                reinterpret_cast<const sockaddr&>(addr), flags);
+  }
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Handle.cpp b/wpiutil/src/main/native/cpp/uv/Handle.cpp
index 93ef423..74c4c60 100644
--- a/wpiutil/src/main/native/cpp/uv/Handle.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Handle.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Handle.h"
 
@@ -11,9 +8,9 @@
 
 Handle::~Handle() noexcept {
   if (!m_closed && m_uv_handle->type != UV_UNKNOWN_HANDLE) {
-    uv_close(m_uv_handle, [](uv_handle_t* uv_handle) { delete uv_handle; });
+    uv_close(m_uv_handle, [](uv_handle_t* uv_handle) { std::free(uv_handle); });
   } else {
-    delete m_uv_handle;
+    std::free(m_uv_handle);
   }
 }
 
@@ -33,4 +30,6 @@
   *buf = h.m_allocBuf(size);
 }
 
-void Handle::DefaultFreeBuf(Buffer& buf) { buf.Deallocate(); }
+void Handle::DefaultFreeBuf(Buffer& buf) {
+  buf.Deallocate();
+}
diff --git a/wpiutil/src/main/native/cpp/uv/Idle.cpp b/wpiutil/src/main/native/cpp/uv/Idle.cpp
index 9eae218..6bf8602 100644
--- a/wpiutil/src/main/native/cpp/uv/Idle.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Idle.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Idle.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Idle> Idle::Create(Loop& loop) {
   auto h = std::make_shared<Idle>(private_init{});
@@ -30,5 +26,4 @@
   });
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Loop.cpp b/wpiutil/src/main/native/cpp/uv/Loop.cpp
index a31ad09..c5b7163 100644
--- a/wpiutil/src/main/native/cpp/uv/Loop.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Loop.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Loop.h"
 
@@ -29,7 +26,9 @@
 
 std::shared_ptr<Loop> Loop::Create() {
   auto loop = std::make_shared<Loop>(private_init{});
-  if (uv_loop_init(&loop->m_loopStruct) < 0) return nullptr;
+  if (uv_loop_init(&loop->m_loopStruct) < 0) {
+    return nullptr;
+  }
   loop->m_loop = &loop->m_loopStruct;
   loop->m_loop->data = loop.get();
   return loop;
@@ -38,22 +37,26 @@
 std::shared_ptr<Loop> Loop::GetDefault() {
   static std::shared_ptr<Loop> loop = std::make_shared<Loop>(private_init{});
   loop->m_loop = uv_default_loop();
-  if (!loop->m_loop) return nullptr;
+  if (!loop->m_loop) {
+    return nullptr;
+  }
   loop->m_loop->data = loop.get();
   return loop;
 }
 
 void Loop::Close() {
   int err = uv_loop_close(m_loop);
-  if (err < 0) ReportError(err);
+  if (err < 0) {
+    ReportError(err);
+  }
 }
 
-void Loop::Walk(std::function<void(Handle&)> callback) {
+void Loop::Walk(function_ref<void(Handle&)> callback) {
   uv_walk(
       m_loop,
       [](uv_handle_t* handle, void* func) {
         auto& h = *static_cast<Handle*>(handle->data);
-        auto& f = *static_cast<std::function<void(Handle&)>*>(func);
+        auto& f = *static_cast<function_ref<void(Handle&)>*>(func);
         f(h);
       },
       &callback);
@@ -61,5 +64,7 @@
 
 void Loop::Fork() {
   int err = uv_loop_fork(m_loop);
-  if (err < 0) ReportError(err);
+  if (err < 0) {
+    ReportError(err);
+  }
 }
diff --git a/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp b/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp
index b407c2b..23ec6da 100644
--- a/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp
+++ b/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/util.h"  // NOLINT(build/include_order)
 
@@ -11,58 +8,52 @@
 
 #include "wpi/SmallString.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in* addr) {
-  SmallString<128> tmp;
-  StringRef ipStr = ip.toNullTerminatedStringRef(tmp);
-  if (ipStr.empty()) {
+int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in* addr) {
+  if (ip.empty()) {
     std::memset(addr, 0, sizeof(sockaddr_in));
     addr->sin_family = PF_INET;
     addr->sin_addr.s_addr = INADDR_ANY;
     addr->sin_port = htons(port);
     return 0;
   } else {
-    return uv_ip4_addr(ipStr.data(), port, addr);
+    SmallString<128> ipBuf{ip};
+    return uv_ip4_addr(ipBuf.c_str(), port, addr);
   }
 }
 
-int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in6* addr) {
-  SmallString<128> tmp;
-  StringRef ipStr = ip.toNullTerminatedStringRef(tmp);
-  if (ipStr.empty()) {
+int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in6* addr) {
+  if (ip.empty()) {
     std::memset(addr, 0, sizeof(sockaddr_in6));
     addr->sin6_family = PF_INET6;
     addr->sin6_addr = in6addr_any;
     addr->sin6_port = htons(port);
     return 0;
   } else {
-    return uv_ip6_addr(ipStr.data(), port, addr);
+    SmallString<128> ipBuf{ip};
+    return uv_ip6_addr(ipBuf.c_str(), port, addr);
   }
 }
 
-int NameToAddr(const Twine& ip, in_addr* addr) {
-  SmallString<128> tmp;
-  StringRef ipStr = ip.toNullTerminatedStringRef(tmp);
-  if (ipStr.empty()) {
+int NameToAddr(std::string_view ip, in_addr* addr) {
+  if (ip.empty()) {
     addr->s_addr = INADDR_ANY;
     return 0;
   } else {
-    return uv_inet_pton(AF_INET, ipStr.data(), addr);
+    SmallString<128> ipBuf{ip};
+    return uv_inet_pton(AF_INET, ipBuf.c_str(), addr);
   }
 }
 
-int NameToAddr(const Twine& ip, in6_addr* addr) {
-  SmallString<128> tmp;
-  StringRef ipStr = ip.toNullTerminatedStringRef(tmp);
-  if (ipStr.empty()) {
+int NameToAddr(std::string_view ip, in6_addr* addr) {
+  if (ip.empty()) {
     *addr = in6addr_any;
     return 0;
   } else {
-    return uv_inet_pton(AF_INET6, ipStr.data(), addr);
+    SmallString<128> ipBuf{ip};
+    return uv_inet_pton(AF_INET6, ipBuf.c_str(), addr);
   }
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp b/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp
index 6e327a7..0bc3337 100644
--- a/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp
+++ b/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp
@@ -1,14 +1,10 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/NetworkStream.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 ConnectReq::ConnectReq() {
   error = [this](Error err) { GetStream().error(err); };
@@ -18,17 +14,17 @@
   Invoke(&uv_listen, GetRawStream(), backlog,
          [](uv_stream_t* handle, int status) {
            auto& h = *static_cast<NetworkStream*>(handle->data);
-           if (status < 0)
+           if (status < 0) {
              h.ReportError(status);
-           else
+           } else {
              h.connection();
+           }
          });
 }
 
 void NetworkStream::Listen(std::function<void()> callback, int backlog) {
-  connection.connect(callback);
+  connection.connect(std::move(callback));
   Listen(backlog);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Pipe.cpp b/wpiutil/src/main/native/cpp/uv/Pipe.cpp
index 9db879a..b5ca673 100644
--- a/wpiutil/src/main/native/cpp/uv/Pipe.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Pipe.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Pipe.h"
 
@@ -11,8 +8,7 @@
 
 #include "wpi/SmallString.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Pipe> Pipe::Create(Loop& loop, bool ipc) {
   auto h = std::make_shared<Pipe>(private_init{});
@@ -26,13 +22,19 @@
 }
 
 void Pipe::Reuse(std::function<void()> callback, bool ipc) {
-  if (IsClosing()) return;
-  if (!m_reuseData) m_reuseData = std::make_unique<ReuseData>();
-  m_reuseData->callback = callback;
+  if (IsClosing()) {
+    return;
+  }
+  if (!m_reuseData) {
+    m_reuseData = std::make_unique<ReuseData>();
+  }
+  m_reuseData->callback = std::move(callback);
   m_reuseData->ipc = ipc;
   uv_close(GetRawHandle(), [](uv_handle_t* handle) {
     Pipe& h = *static_cast<Pipe*>(handle->data);
-    if (!h.m_reuseData) return;
+    if (!h.m_reuseData) {
+      return;
+    }
     auto data = std::move(h.m_reuseData);
     auto err =
         uv_pipe_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->ipc ? 1 : 0);
@@ -46,7 +48,9 @@
 
 std::shared_ptr<Pipe> Pipe::Accept() {
   auto client = Create(GetLoopRef(), GetRaw()->ipc);
-  if (!client) return nullptr;
+  if (!client) {
+    return nullptr;
+  }
   if (!Accept(client)) {
     client->Release();
     return nullptr;
@@ -54,33 +58,34 @@
   return client;
 }
 
-Pipe* Pipe::DoAccept() { return Accept().get(); }
-
-void Pipe::Bind(const Twine& name) {
-  SmallString<128> nameBuf;
-  Invoke(&uv_pipe_bind, GetRaw(),
-         name.toNullTerminatedStringRef(nameBuf).data());
+Pipe* Pipe::DoAccept() {
+  return Accept().get();
 }
 
-void Pipe::Connect(const Twine& name,
+void Pipe::Bind(std::string_view name) {
+  SmallString<128> nameBuf{name};
+  Invoke(&uv_pipe_bind, GetRaw(), nameBuf.c_str());
+}
+
+void Pipe::Connect(std::string_view name,
                    const std::shared_ptr<PipeConnectReq>& req) {
-  SmallString<128> nameBuf;
-  uv_pipe_connect(req->GetRaw(), GetRaw(),
-                  name.toNullTerminatedStringRef(nameBuf).data(),
+  SmallString<128> nameBuf{name};
+  uv_pipe_connect(req->GetRaw(), GetRaw(), nameBuf.c_str(),
                   [](uv_connect_t* req, int status) {
                     auto& h = *static_cast<PipeConnectReq*>(req->data);
-                    if (status < 0)
+                    if (status < 0) {
                       h.ReportError(status);
-                    else
+                    } else {
                       h.connected();
+                    }
                     h.Release();  // this is always a one-shot
                   });
   req->Keep();
 }
 
-void Pipe::Connect(const Twine& name, std::function<void()> callback) {
+void Pipe::Connect(std::string_view name, std::function<void()> callback) {
   auto req = std::make_shared<PipeConnectReq>();
-  req->connected.connect(callback);
+  req->connected.connect(std::move(callback));
   Connect(name, req);
 }
 
@@ -130,5 +135,4 @@
   return std::string{};
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Poll.cpp b/wpiutil/src/main/native/cpp/uv/Poll.cpp
index 8b608cb..090a40b 100644
--- a/wpiutil/src/main/native/cpp/uv/Poll.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Poll.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Poll.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Poll> Poll::Create(Loop& loop, int fd) {
   auto h = std::make_shared<Poll>(private_init{});
@@ -35,14 +31,20 @@
 }
 
 void Poll::Reuse(int fd, std::function<void()> callback) {
-  if (IsClosing()) return;
-  if (!m_reuseData) m_reuseData = std::make_unique<ReuseData>();
-  m_reuseData->callback = callback;
+  if (IsClosing()) {
+    return;
+  }
+  if (!m_reuseData) {
+    m_reuseData = std::make_unique<ReuseData>();
+  }
+  m_reuseData->callback = std::move(callback);
   m_reuseData->isSocket = false;
   m_reuseData->fd = fd;
   uv_close(GetRawHandle(), [](uv_handle_t* handle) {
     Poll& h = *static_cast<Poll*>(handle->data);
-    if (!h.m_reuseData || h.m_reuseData->isSocket) return;  // just in case
+    if (!h.m_reuseData || h.m_reuseData->isSocket) {
+      return;  // just in case
+    }
     auto data = std::move(h.m_reuseData);
     int err = uv_poll_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->fd);
     if (err < 0) {
@@ -54,14 +56,20 @@
 }
 
 void Poll::ReuseSocket(uv_os_sock_t sock, std::function<void()> callback) {
-  if (IsClosing()) return;
-  if (!m_reuseData) m_reuseData = std::make_unique<ReuseData>();
-  m_reuseData->callback = callback;
+  if (IsClosing()) {
+    return;
+  }
+  if (!m_reuseData) {
+    m_reuseData = std::make_unique<ReuseData>();
+  }
+  m_reuseData->callback = std::move(callback);
   m_reuseData->isSocket = true;
   m_reuseData->sock = sock;
   uv_close(GetRawHandle(), [](uv_handle_t* handle) {
     Poll& h = *static_cast<Poll*>(handle->data);
-    if (!h.m_reuseData || !h.m_reuseData->isSocket) return;  // just in case
+    if (!h.m_reuseData || !h.m_reuseData->isSocket) {
+      return;  // just in case
+    }
     auto data = std::move(h.m_reuseData);
     int err = uv_poll_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->sock);
     if (err < 0) {
@@ -76,12 +84,12 @@
   Invoke(&uv_poll_start, GetRaw(), events,
          [](uv_poll_t* handle, int status, int events) {
            Poll& h = *static_cast<Poll*>(handle->data);
-           if (status < 0)
+           if (status < 0) {
              h.ReportError(status);
-           else
+           } else {
              h.pollEvent(events);
+           }
          });
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Prepare.cpp b/wpiutil/src/main/native/cpp/uv/Prepare.cpp
index f27f477..048fd08 100644
--- a/wpiutil/src/main/native/cpp/uv/Prepare.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Prepare.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Prepare.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Prepare> Prepare::Create(Loop& loop) {
   auto h = std::make_shared<Prepare>(private_init{});
@@ -30,5 +26,4 @@
   });
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Process.cpp b/wpiutil/src/main/native/cpp/uv/Process.cpp
index 5778965..c8d5229 100644
--- a/wpiutil/src/main/native/cpp/uv/Process.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Process.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Process.h"
 
@@ -11,11 +8,10 @@
 #include "wpi/uv/Loop.h"
 #include "wpi/uv/Pipe.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-std::shared_ptr<Process> Process::SpawnArray(Loop& loop, const Twine& file,
-                                             ArrayRef<Option> options) {
+std::shared_ptr<Process> Process::SpawnArray(Loop& loop, std::string_view file,
+                                             span<const Option> options) {
   // convert Option array to libuv structure
   uv_process_options_t coptions;
 
@@ -24,8 +20,8 @@
     h.exited(status, signal);
   };
 
-  SmallString<128> fileBuf;
-  coptions.file = file.toNullTerminatedStringRef(fileBuf).data();
+  SmallString<128> fileBuf{file};
+  coptions.file = fileBuf.c_str();
   coptions.cwd = nullptr;
   coptions.flags = 0;
   coptions.uid = 0;
@@ -68,28 +64,36 @@
         break;
       case Option::kStdioIgnore: {
         size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) stdioBuf.resize(index + 1);
+        if (index >= stdioBuf.size()) {
+          stdioBuf.resize(index + 1);
+        }
         stdioBuf[index].flags = UV_IGNORE;
         stdioBuf[index].data.fd = 0;
         break;
       }
       case Option::kStdioInheritFd: {
         size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) stdioBuf.resize(index + 1);
+        if (index >= stdioBuf.size()) {
+          stdioBuf.resize(index + 1);
+        }
         stdioBuf[index].flags = UV_INHERIT_FD;
         stdioBuf[index].data.fd = o.m_data.stdio.fd;
         break;
       }
       case Option::kStdioInheritPipe: {
         size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) stdioBuf.resize(index + 1);
+        if (index >= stdioBuf.size()) {
+          stdioBuf.resize(index + 1);
+        }
         stdioBuf[index].flags = UV_INHERIT_STREAM;
         stdioBuf[index].data.stream = o.m_data.stdio.pipe->GetRawStream();
         break;
       }
       case Option::kStdioCreatePipe: {
         size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) stdioBuf.resize(index + 1);
+        if (index >= stdioBuf.size()) {
+          stdioBuf.resize(index + 1);
+        }
         stdioBuf[index].flags =
             static_cast<uv_stdio_flags>(UV_CREATE_PIPE | o.m_data.stdio.flags);
         stdioBuf[index].data.stream = o.m_data.stdio.pipe->GetRawStream();
@@ -100,7 +104,9 @@
     }
   }
 
-  if (argsBuf.empty()) argsBuf.push_back(const_cast<char*>(coptions.file));
+  if (argsBuf.empty()) {
+    argsBuf.push_back(const_cast<char*>(coptions.file));
+  }
   argsBuf.push_back(nullptr);
   coptions.args = argsBuf.data();
 
@@ -124,5 +130,4 @@
   return h;
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Signal.cpp b/wpiutil/src/main/native/cpp/uv/Signal.cpp
index 4569ac2..81d7c3e 100644
--- a/wpiutil/src/main/native/cpp/uv/Signal.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Signal.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Signal.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Signal> Signal::Create(Loop& loop) {
   auto h = std::make_shared<Signal>(private_init{});
@@ -33,5 +29,4 @@
       signum);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Stream.cpp b/wpiutil/src/main/native/cpp/uv/Stream.cpp
index b1fd294..a37750b 100644
--- a/wpiutil/src/main/native/cpp/uv/Stream.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Stream.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Stream.h"
 
@@ -15,10 +12,11 @@
 namespace {
 class CallbackWriteReq : public WriteReq {
  public:
-  CallbackWriteReq(ArrayRef<Buffer> bufs,
-                   std::function<void(MutableArrayRef<Buffer>, Error)> callback)
+  CallbackWriteReq(span<const Buffer> bufs,
+                   std::function<void(span<Buffer>, Error)> callback)
       : m_bufs{bufs.begin(), bufs.end()} {
-    finish.connect([=](Error err) { callback(m_bufs, err); });
+    finish.connect(
+        [this, f = std::move(callback)](Error err) { f(m_bufs, err); });
   }
 
  private:
@@ -26,8 +24,7 @@
 };
 }  // namespace
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 ShutdownReq::ShutdownReq() {
   error = [this](Error err) { GetStream().error(err); };
@@ -41,18 +38,22 @@
   if (Invoke(&uv_shutdown, req->GetRaw(), GetRawStream(),
              [](uv_shutdown_t* req, int status) {
                auto& h = *static_cast<ShutdownReq*>(req->data);
-               if (status < 0)
+               if (status < 0) {
                  h.ReportError(status);
-               else
+               } else {
                  h.complete();
+               }
                h.Release();  // this is always a one-shot
-             }))
+             })) {
     req->Keep();
+  }
 }
 
 void Stream::Shutdown(std::function<void()> callback) {
   auto req = std::make_shared<ShutdownReq>();
-  if (callback) req->complete.connect(callback);
+  if (callback) {
+    req->complete.connect(std::move(callback));
+  }
   Shutdown(req);
 }
 
@@ -63,37 +64,40 @@
            Buffer data = *buf;
 
            // nread=0 is simply ignored
-           if (nread == UV_EOF)
+           if (nread == UV_EOF) {
              h.end();
-           else if (nread > 0)
+           } else if (nread > 0) {
              h.data(data, static_cast<size_t>(nread));
-           else if (nread < 0)
+           } else if (nread < 0) {
              h.ReportError(nread);
+           }
 
            // free the buffer
            h.FreeBuf(data);
          });
 }
 
-void Stream::Write(ArrayRef<Buffer> bufs,
+void Stream::Write(span<const Buffer> bufs,
                    const std::shared_ptr<WriteReq>& req) {
   if (Invoke(&uv_write, req->GetRaw(), GetRawStream(), bufs.data(), bufs.size(),
              [](uv_write_t* r, int status) {
                auto& h = *static_cast<WriteReq*>(r->data);
-               if (status < 0) h.ReportError(status);
+               if (status < 0) {
+                 h.ReportError(status);
+               }
                h.finish(Error(status));
                h.Release();  // this is always a one-shot
-             }))
+             })) {
     req->Keep();
+  }
 }
 
-void Stream::Write(
-    ArrayRef<Buffer> bufs,
-    std::function<void(MutableArrayRef<Buffer>, Error)> callback) {
-  Write(bufs, std::make_shared<CallbackWriteReq>(bufs, callback));
+void Stream::Write(span<const Buffer> bufs,
+                   std::function<void(span<Buffer>, Error)> callback) {
+  Write(bufs, std::make_shared<CallbackWriteReq>(bufs, std::move(callback)));
 }
 
-int Stream::TryWrite(ArrayRef<Buffer> bufs) {
+int Stream::TryWrite(span<const Buffer> bufs) {
   int val = uv_try_write(GetRawStream(), bufs.data(), bufs.size());
   if (val < 0) {
     this->ReportError(val);
@@ -102,5 +106,4 @@
   return val;
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Tcp.cpp b/wpiutil/src/main/native/cpp/uv/Tcp.cpp
index f71e055..6f92557 100644
--- a/wpiutil/src/main/native/cpp/uv/Tcp.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Tcp.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Tcp.h"
 
@@ -11,8 +8,7 @@
 
 #include "wpi/uv/util.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Tcp> Tcp::Create(Loop& loop, unsigned int flags) {
   auto h = std::make_shared<Tcp>(private_init{});
@@ -26,13 +22,19 @@
 }
 
 void Tcp::Reuse(std::function<void()> callback, unsigned int flags) {
-  if (IsClosing()) return;
-  if (!m_reuseData) m_reuseData = std::make_unique<ReuseData>();
-  m_reuseData->callback = callback;
+  if (IsClosing()) {
+    return;
+  }
+  if (!m_reuseData) {
+    m_reuseData = std::make_unique<ReuseData>();
+  }
+  m_reuseData->callback = std::move(callback);
   m_reuseData->flags = flags;
   uv_close(GetRawHandle(), [](uv_handle_t* handle) {
     Tcp& h = *static_cast<Tcp*>(handle->data);
-    if (!h.m_reuseData) return;  // just in case
+    if (!h.m_reuseData) {
+      return;  // just in case
+    }
     auto data = std::move(h.m_reuseData);
     int err = uv_tcp_init_ex(h.GetLoopRef().GetRaw(), h.GetRaw(), data->flags);
     if (err < 0) {
@@ -45,7 +47,9 @@
 
 std::shared_ptr<Tcp> Tcp::Accept() {
   auto client = Create(GetLoopRef());
-  if (!client) return nullptr;
+  if (!client) {
+    return nullptr;
+  }
   if (!Accept(client)) {
     client->Release();
     return nullptr;
@@ -53,32 +57,37 @@
   return client;
 }
 
-Tcp* Tcp::DoAccept() { return Accept().get(); }
-
-void Tcp::Bind(const Twine& ip, unsigned int port, unsigned int flags) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
-    ReportError(err);
-  else
-    Bind(reinterpret_cast<const sockaddr&>(addr), flags);
+Tcp* Tcp::DoAccept() {
+  return Accept().get();
 }
 
-void Tcp::Bind6(const Twine& ip, unsigned int port, unsigned int flags) {
+void Tcp::Bind(std::string_view ip, unsigned int port, unsigned int flags) {
+  sockaddr_in addr;
+  int err = NameToAddr(ip, port, &addr);
+  if (err < 0) {
+    ReportError(err);
+  } else {
+    Bind(reinterpret_cast<const sockaddr&>(addr), flags);
+  }
+}
+
+void Tcp::Bind6(std::string_view ip, unsigned int port, unsigned int flags) {
   sockaddr_in6 addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
+  } else {
     Bind(reinterpret_cast<const sockaddr&>(addr), flags);
+  }
 }
 
 sockaddr_storage Tcp::GetSock() {
   sockaddr_storage name;
   int len = sizeof(name);
   if (!Invoke(&uv_tcp_getsockname, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len))
+              &len)) {
     std::memset(&name, 0, sizeof(name));
+  }
   return name;
 }
 
@@ -86,8 +95,9 @@
   sockaddr_storage name;
   int len = sizeof(name);
   if (!Invoke(&uv_tcp_getpeername, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len))
+              &len)) {
     std::memset(&name, 0, sizeof(name));
+  }
   return name;
 }
 
@@ -96,60 +106,65 @@
   if (Invoke(&uv_tcp_connect, req->GetRaw(), GetRaw(), &addr,
              [](uv_connect_t* req, int status) {
                auto& h = *static_cast<TcpConnectReq*>(req->data);
-               if (status < 0)
+               if (status < 0) {
                  h.ReportError(status);
-               else
+               } else {
                  h.connected();
+               }
                h.Release();  // this is always a one-shot
-             }))
+             })) {
     req->Keep();
+  }
 }
 
 void Tcp::Connect(const sockaddr& addr, std::function<void()> callback) {
   auto req = std::make_shared<TcpConnectReq>();
-  req->connected.connect(callback);
+  req->connected.connect(std::move(callback));
   Connect(addr, req);
 }
 
-void Tcp::Connect(const Twine& ip, unsigned int port,
+void Tcp::Connect(std::string_view ip, unsigned int port,
                   const std::shared_ptr<TcpConnectReq>& req) {
   sockaddr_in addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
+  } else {
     Connect(reinterpret_cast<const sockaddr&>(addr), req);
+  }
 }
 
-void Tcp::Connect(const Twine& ip, unsigned int port,
+void Tcp::Connect(std::string_view ip, unsigned int port,
                   std::function<void()> callback) {
   sockaddr_in addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
-    Connect(reinterpret_cast<const sockaddr&>(addr), callback);
+  } else {
+    Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
+  }
 }
 
-void Tcp::Connect6(const Twine& ip, unsigned int port,
+void Tcp::Connect6(std::string_view ip, unsigned int port,
                    const std::shared_ptr<TcpConnectReq>& req) {
   sockaddr_in6 addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
+  } else {
     Connect(reinterpret_cast<const sockaddr&>(addr), req);
+  }
 }
 
-void Tcp::Connect6(const Twine& ip, unsigned int port,
+void Tcp::Connect6(std::string_view ip, unsigned int port,
                    std::function<void()> callback) {
   sockaddr_in6 addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
-    Connect(reinterpret_cast<const sockaddr&>(addr), callback);
+  } else {
+    Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
+  }
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Timer.cpp b/wpiutil/src/main/native/cpp/uv/Timer.cpp
index 0fdd3be..33fd851 100644
--- a/wpiutil/src/main/native/cpp/uv/Timer.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Timer.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Timer.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Timer> Timer::Create(Loop& loop) {
   auto h = std::make_shared<Timer>(private_init{});
@@ -25,9 +21,11 @@
 
 void Timer::SingleShot(Loop& loop, Time timeout, std::function<void()> func) {
   auto h = Create(loop);
-  if (!h) return;
-  h->timeout.connect([theTimer = h.get(), func]() {
-    func();
+  if (!h) {
+    return;
+  }
+  h->timeout.connect([theTimer = h.get(), f = std::move(func)]() {
+    f();
     theTimer->Close();
   });
   h->Start(timeout);
@@ -43,5 +41,4 @@
       timeout.count(), repeat.count());
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Tty.cpp b/wpiutil/src/main/native/cpp/uv/Tty.cpp
index cdd6fd5..4531ded 100644
--- a/wpiutil/src/main/native/cpp/uv/Tty.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Tty.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Tty.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 std::shared_ptr<Tty> Tty::Create(Loop& loop, uv_file fd, bool readable) {
   auto h = std::make_shared<Tty>(private_init{});
@@ -23,5 +19,4 @@
   return h;
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Udp.cpp b/wpiutil/src/main/native/cpp/uv/Udp.cpp
index 2c0d29f..bea2b57 100644
--- a/wpiutil/src/main/native/cpp/uv/Udp.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Udp.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Udp.h"
 
@@ -20,11 +17,11 @@
 
 class CallbackUdpSendReq : public UdpSendReq {
  public:
-  CallbackUdpSendReq(
-      ArrayRef<Buffer> bufs,
-      std::function<void(MutableArrayRef<Buffer>, Error)> callback)
+  CallbackUdpSendReq(span<const Buffer> bufs,
+                     std::function<void(span<Buffer>, Error)> callback)
       : m_bufs{bufs.begin(), bufs.end()} {
-    complete.connect([=](Error err) { callback(m_bufs, err); });
+    complete.connect(
+        [this, f = std::move(callback)](Error err) { f(m_bufs, err); });
   }
 
  private:
@@ -33,8 +30,7 @@
 
 }  // namespace
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 UdpSendReq::UdpSendReq() {
   error = [this](Error err) { GetUdp().error(err); };
@@ -51,48 +47,53 @@
   return h;
 }
 
-void Udp::Bind(const Twine& ip, unsigned int port, unsigned int flags) {
+void Udp::Bind(std::string_view ip, unsigned int port, unsigned int flags) {
   sockaddr_in addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
+  } else {
     Bind(reinterpret_cast<const sockaddr&>(addr), flags);
+  }
 }
 
-void Udp::Bind6(const Twine& ip, unsigned int port, unsigned int flags) {
+void Udp::Bind6(std::string_view ip, unsigned int port, unsigned int flags) {
   sockaddr_in6 addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
+  } else {
     Bind(reinterpret_cast<const sockaddr&>(addr), flags);
+  }
 }
 
-void Udp::Connect(const Twine& ip, unsigned int port) {
+void Udp::Connect(std::string_view ip, unsigned int port) {
   sockaddr_in addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
+  } else {
     Connect(reinterpret_cast<const sockaddr&>(addr));
+  }
 }
 
-void Udp::Connect6(const Twine& ip, unsigned int port) {
+void Udp::Connect6(std::string_view ip, unsigned int port) {
   sockaddr_in6 addr;
   int err = NameToAddr(ip, port, &addr);
-  if (err < 0)
+  if (err < 0) {
     ReportError(err);
-  else
+  } else {
     Connect(reinterpret_cast<const sockaddr&>(addr));
+  }
 }
 
 sockaddr_storage Udp::GetPeer() {
   sockaddr_storage name;
   int len = sizeof(name);
   if (!Invoke(&uv_udp_getpeername, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len))
+              &len)) {
     std::memset(&name, 0, sizeof(name));
+  }
   return name;
 }
 
@@ -100,58 +101,65 @@
   sockaddr_storage name;
   int len = sizeof(name);
   if (!Invoke(&uv_udp_getsockname, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len))
+              &len)) {
     std::memset(&name, 0, sizeof(name));
+  }
   return name;
 }
 
-void Udp::SetMembership(const Twine& multicastAddr, const Twine& interfaceAddr,
+void Udp::SetMembership(std::string_view multicastAddr,
+                        std::string_view interfaceAddr,
                         uv_membership membership) {
-  SmallString<128> multicastAddrBuf;
-  SmallString<128> interfaceAddrBuf;
-  Invoke(&uv_udp_set_membership, GetRaw(),
-         multicastAddr.toNullTerminatedStringRef(multicastAddrBuf).data(),
-         interfaceAddr.toNullTerminatedStringRef(interfaceAddrBuf).data(),
-         membership);
+  SmallString<128> multicastAddrBuf{multicastAddr};
+  SmallString<128> interfaceAddrBuf{interfaceAddr};
+  Invoke(&uv_udp_set_membership, GetRaw(), multicastAddrBuf.c_str(),
+         interfaceAddrBuf.c_str(), membership);
 }
 
-void Udp::SetMulticastInterface(const Twine& interfaceAddr) {
-  SmallString<128> interfaceAddrBuf;
-  Invoke(&uv_udp_set_multicast_interface, GetRaw(),
-         interfaceAddr.toNullTerminatedStringRef(interfaceAddrBuf).data());
+void Udp::SetMulticastInterface(std::string_view interfaceAddr) {
+  SmallString<128> interfaceAddrBuf{interfaceAddr};
+  Invoke(&uv_udp_set_multicast_interface, GetRaw(), interfaceAddrBuf.c_str());
 }
 
-void Udp::Send(const sockaddr& addr, ArrayRef<Buffer> bufs,
+void Udp::Send(const sockaddr& addr, span<const Buffer> bufs,
                const std::shared_ptr<UdpSendReq>& req) {
   if (Invoke(&uv_udp_send, req->GetRaw(), GetRaw(), bufs.data(), bufs.size(),
              &addr, [](uv_udp_send_t* r, int status) {
                auto& h = *static_cast<UdpSendReq*>(r->data);
-               if (status < 0) h.ReportError(status);
+               if (status < 0) {
+                 h.ReportError(status);
+               }
                h.complete(Error(status));
                h.Release();  // this is always a one-shot
-             }))
+             })) {
     req->Keep();
+  }
 }
 
-void Udp::Send(const sockaddr& addr, ArrayRef<Buffer> bufs,
-               std::function<void(MutableArrayRef<Buffer>, Error)> callback) {
-  Send(addr, bufs, std::make_shared<CallbackUdpSendReq>(bufs, callback));
+void Udp::Send(const sockaddr& addr, span<const Buffer> bufs,
+               std::function<void(span<Buffer>, Error)> callback) {
+  Send(addr, bufs,
+       std::make_shared<CallbackUdpSendReq>(bufs, std::move(callback)));
 }
 
-void Udp::Send(ArrayRef<Buffer> bufs, const std::shared_ptr<UdpSendReq>& req) {
+void Udp::Send(span<const Buffer> bufs,
+               const std::shared_ptr<UdpSendReq>& req) {
   if (Invoke(&uv_udp_send, req->GetRaw(), GetRaw(), bufs.data(), bufs.size(),
              nullptr, [](uv_udp_send_t* r, int status) {
                auto& h = *static_cast<UdpSendReq*>(r->data);
-               if (status < 0) h.ReportError(status);
+               if (status < 0) {
+                 h.ReportError(status);
+               }
                h.complete(Error(status));
                h.Release();  // this is always a one-shot
-             }))
+             })) {
     req->Keep();
+  }
 }
 
-void Udp::Send(ArrayRef<Buffer> bufs,
-               std::function<void(MutableArrayRef<Buffer>, Error)> callback) {
-  Send(bufs, std::make_shared<CallbackUdpSendReq>(bufs, callback));
+void Udp::Send(span<const Buffer> bufs,
+               std::function<void(span<Buffer>, Error)> callback) {
+  Send(bufs, std::make_shared<CallbackUdpSendReq>(bufs, std::move(callback)));
 }
 
 void Udp::StartRecv() {
@@ -162,15 +170,15 @@
            Buffer data = *buf;
 
            // nread=0 is simply ignored
-           if (nread > 0)
+           if (nread > 0) {
              h.received(data, static_cast<size_t>(nread), *addr, flags);
-           else if (nread < 0)
+           } else if (nread < 0) {
              h.ReportError(nread);
+           }
 
            // free the buffer
            h.FreeBuf(data);
          });
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Work.cpp b/wpiutil/src/main/native/cpp/uv/Work.cpp
index e5d7bb6..0fc254e 100644
--- a/wpiutil/src/main/native/cpp/uv/Work.cpp
+++ b/wpiutil/src/main/native/cpp/uv/Work.cpp
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Work.h"
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 WorkReq::WorkReq() {
   error = [this](Error err) { GetLoop().error(err); };
@@ -25,25 +21,30 @@
       },
       [](uv_work_t* req, int status) {
         auto& h = *static_cast<WorkReq*>(req->data);
-        if (status < 0)
+        if (status < 0) {
           h.ReportError(status);
-        else
+        } else {
           h.afterWork();
+        }
         h.Release();  // this is always a one-shot
       });
-  if (err < 0)
+  if (err < 0) {
     loop.ReportError(err);
-  else
+  } else {
     req->Keep();
+  }
 }
 
 void QueueWork(Loop& loop, std::function<void()> work,
                std::function<void()> afterWork) {
   auto req = std::make_shared<WorkReq>();
-  if (work) req->work.connect(work);
-  if (afterWork) req->afterWork.connect(afterWork);
+  if (work) {
+    req->work.connect(std::move(work));
+  }
+  if (afterWork) {
+    req->afterWork.connect(std::move(afterWork));
+  }
   QueueWork(loop, req);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/args.h b/wpiutil/src/main/native/fmtlib/include/fmt/args.h
new file mode 100644
index 0000000..562e8ab
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/args.h
@@ -0,0 +1,232 @@
+// Formatting library for C++ - dynamic format arguments
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_ARGS_H_
+#define FMT_ARGS_H_
+
+#include <functional>  // std::reference_wrapper
+#include <memory>      // std::unique_ptr
+#include <vector>
+
+#include "core.h"
+
+FMT_BEGIN_NAMESPACE
+
+namespace detail {
+
+template <typename T> struct is_reference_wrapper : std::false_type {};
+template <typename T>
+struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
+
+template <typename T> const T& unwrap(const T& v) { return v; }
+template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
+  return static_cast<const T&>(v);
+}
+
+class dynamic_arg_list {
+  // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
+  // templates it doesn't complain about inability to deduce single translation
+  // unit for placing vtable. So storage_node_base is made a fake template.
+  template <typename = void> struct node {
+    virtual ~node() = default;
+    std::unique_ptr<node<>> next;
+  };
+
+  template <typename T> struct typed_node : node<> {
+    T value;
+
+    template <typename Arg>
+    FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
+
+    template <typename Char>
+    FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
+        : value(arg.data(), arg.size()) {}
+  };
+
+  std::unique_ptr<node<>> head_;
+
+ public:
+  template <typename T, typename Arg> const T& push(const Arg& arg) {
+    auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
+    auto& value = new_node->value;
+    new_node->next = std::move(head_);
+    head_ = std::move(new_node);
+    return value;
+  }
+};
+}  // namespace detail
+
+/**
+  \rst
+  A dynamic version of `fmt::format_arg_store`.
+  It's equipped with a storage to potentially temporary objects which lifetimes
+  could be shorter than the format arguments object.
+
+  It can be implicitly converted into `~fmt::basic_format_args` for passing
+  into type-erased formatting functions such as `~fmt::vformat`.
+  \endrst
+ */
+template <typename Context>
+class dynamic_format_arg_store
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+    // Workaround a GCC template argument substitution bug.
+    : public basic_format_args<Context>
+#endif
+{
+ private:
+  using char_type = typename Context::char_type;
+
+  template <typename T> struct need_copy {
+    static constexpr detail::type mapped_type =
+        detail::mapped_type_constant<T, Context>::value;
+
+    enum {
+      value = !(detail::is_reference_wrapper<T>::value ||
+                std::is_same<T, basic_string_view<char_type>>::value ||
+                std::is_same<T, detail::std_string_view<char_type>>::value ||
+                (mapped_type != detail::type::cstring_type &&
+                 mapped_type != detail::type::string_type &&
+                 mapped_type != detail::type::custom_type))
+    };
+  };
+
+  template <typename T>
+  using stored_type = conditional_t<detail::is_string<T>::value &&
+                                        !has_formatter<T, Context>::value &&
+                                        !detail::is_reference_wrapper<T>::value,
+                                    std::basic_string<char_type>, T>;
+
+  // Storage of basic_format_arg must be contiguous.
+  std::vector<basic_format_arg<Context>> data_;
+  std::vector<detail::named_arg_info<char_type>> named_info_;
+
+  // Storage of arguments not fitting into basic_format_arg must grow
+  // without relocation because items in data_ refer to it.
+  detail::dynamic_arg_list dynamic_args_;
+
+  friend class basic_format_args<Context>;
+
+  unsigned long long get_types() const {
+    return detail::is_unpacked_bit | data_.size() |
+           (named_info_.empty()
+                ? 0ULL
+                : static_cast<unsigned long long>(detail::has_named_args_bit));
+  }
+
+  const basic_format_arg<Context>* data() const {
+    return named_info_.empty() ? data_.data() : data_.data() + 1;
+  }
+
+  template <typename T> void emplace_arg(const T& arg) {
+    data_.emplace_back(detail::make_arg<Context>(arg));
+  }
+
+  template <typename T>
+  void emplace_arg(const detail::named_arg<char_type, T>& arg) {
+    if (named_info_.empty()) {
+      constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
+      data_.insert(data_.begin(), {zero_ptr, 0});
+    }
+    data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
+    auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
+      data->pop_back();
+    };
+    std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
+        guard{&data_, pop_one};
+    named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
+    data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
+    guard.release();
+  }
+
+ public:
+  /**
+    \rst
+    Adds an argument into the dynamic store for later passing to a formatting
+    function.
+
+    Note that custom types and string types (but not string views) are copied
+    into the store dynamically allocating memory if necessary.
+
+    **Example**::
+
+      fmt::dynamic_format_arg_store<fmt::format_context> store;
+      store.push_back(42);
+      store.push_back("abc");
+      store.push_back(1.5f);
+      std::string result = fmt::vformat("{} and {} and {}", store);
+    \endrst
+  */
+  template <typename T> void push_back(const T& arg) {
+    if (detail::const_check(need_copy<T>::value))
+      emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
+    else
+      emplace_arg(detail::unwrap(arg));
+  }
+
+  /**
+    \rst
+    Adds a reference to the argument into the dynamic store for later passing to
+    a formatting function.
+
+    **Example**::
+
+      fmt::dynamic_format_arg_store<fmt::format_context> store;
+      char band[] = "Rolling Stones";
+      store.push_back(std::cref(band));
+      band[9] = 'c'; // Changing str affects the output.
+      std::string result = fmt::vformat("{}", store);
+      // result == "Rolling Scones"
+    \endrst
+  */
+  template <typename T> void push_back(std::reference_wrapper<T> arg) {
+    static_assert(
+        need_copy<T>::value,
+        "objects of built-in types and string views are always copied");
+    emplace_arg(arg.get());
+  }
+
+  /**
+    Adds named argument into the dynamic store for later passing to a formatting
+    function. ``std::reference_wrapper`` is supported to avoid copying of the
+    argument. The name is always copied into the store.
+  */
+  template <typename T>
+  void push_back(const detail::named_arg<char_type, T>& arg) {
+    const char_type* arg_name =
+        dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
+    if (detail::const_check(need_copy<T>::value)) {
+      emplace_arg(
+          fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
+    } else {
+      emplace_arg(fmt::arg(arg_name, arg.value));
+    }
+  }
+
+  /** Erase all elements from the store */
+  void clear() {
+    data_.clear();
+    named_info_.clear();
+    dynamic_args_ = detail::dynamic_arg_list();
+  }
+
+  /**
+    \rst
+    Reserves space to store at least *new_cap* arguments including
+    *new_cap_named* named arguments.
+    \endrst
+  */
+  void reserve(size_t new_cap, size_t new_cap_named) {
+    FMT_ASSERT(new_cap >= new_cap_named,
+               "Set of arguments includes set of named arguments");
+    data_.reserve(new_cap);
+    named_info_.reserve(new_cap_named);
+  }
+};
+
+FMT_END_NAMESPACE
+
+#endif  // FMT_ARGS_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/chrono.h b/wpiutil/src/main/native/fmtlib/include/fmt/chrono.h
new file mode 100644
index 0000000..c024fd7
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/chrono.h
@@ -0,0 +1,1308 @@
+// Formatting library for C++ - chrono support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_CHRONO_H_
+#define FMT_CHRONO_H_
+
+#include <algorithm>
+#include <chrono>
+#include <ctime>
+#include <locale>
+#include <sstream>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+
+// Enable safe chrono durations, unless explicitly disabled.
+#ifndef FMT_SAFE_DURATION_CAST
+#  define FMT_SAFE_DURATION_CAST 1
+#endif
+#if FMT_SAFE_DURATION_CAST
+
+// For conversion between std::chrono::durations without undefined
+// behaviour or erroneous results.
+// This is a stripped down version of duration_cast, for inclusion in fmt.
+// See https://github.com/pauldreik/safe_duration_cast
+//
+// Copyright Paul Dreik 2019
+namespace safe_duration_cast {
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
+                        std::numeric_limits<From>::is_signed ==
+                            std::numeric_limits<To>::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  using F = std::numeric_limits<From>;
+  using T = std::numeric_limits<To>;
+  static_assert(F::is_integer, "From must be integral");
+  static_assert(T::is_integer, "To must be integral");
+
+  // A and B are both signed, or both unsigned.
+  if (F::digits <= T::digits) {
+    // From fits in To without any problem.
+  } else {
+    // From does not always fit in To, resort to a dynamic check.
+    if (from < (T::min)() || from > (T::max)()) {
+      // outside range.
+      ec = 1;
+      return {};
+    }
+  }
+  return static_cast<To>(from);
+}
+
+/**
+ * converts From to To, without loss. If the dynamic value of from
+ * can't be converted to To without loss, ec is set.
+ */
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
+                        std::numeric_limits<From>::is_signed !=
+                            std::numeric_limits<To>::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  using F = std::numeric_limits<From>;
+  using T = std::numeric_limits<To>;
+  static_assert(F::is_integer, "From must be integral");
+  static_assert(T::is_integer, "To must be integral");
+
+  if (detail::const_check(F::is_signed && !T::is_signed)) {
+    // From may be negative, not allowed!
+    if (fmt::detail::is_negative(from)) {
+      ec = 1;
+      return {};
+    }
+    // From is positive. Can it always fit in To?
+    if (F::digits > T::digits &&
+        from > static_cast<From>(detail::max_value<To>())) {
+      ec = 1;
+      return {};
+    }
+  }
+
+  if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
+      from > static_cast<From>(detail::max_value<To>())) {
+    ec = 1;
+    return {};
+  }
+  return static_cast<To>(from);  // Lossless conversion.
+}
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(std::is_same<From, To>::value)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  return from;
+}  // function
+
+// clang-format off
+/**
+ * converts From to To if possible, otherwise ec is set.
+ *
+ * input                            |    output
+ * ---------------------------------|---------------
+ * NaN                              | NaN
+ * Inf                              | Inf
+ * normal, fits in output           | converted (possibly lossy)
+ * normal, does not fit in output   | ec is set
+ * subnormal                        | best effort
+ * -Inf                             | -Inf
+ */
+// clang-format on
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+  ec = 0;
+  using T = std::numeric_limits<To>;
+  static_assert(std::is_floating_point<From>::value, "From must be floating");
+  static_assert(std::is_floating_point<To>::value, "To must be floating");
+
+  // catch the only happy case
+  if (std::isfinite(from)) {
+    if (from >= T::lowest() && from <= (T::max)()) {
+      return static_cast<To>(from);
+    }
+    // not within range.
+    ec = 1;
+    return {};
+  }
+
+  // nan and inf will be preserved
+  return static_cast<To>(from);
+}  // function
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(std::is_same<From, To>::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+  ec = 0;
+  static_assert(std::is_floating_point<From>::value, "From must be floating");
+  return from;
+}
+
+/**
+ * safe duration cast between integral durations
+ */
+template <typename To, typename FromRep, typename FromPeriod,
+          FMT_ENABLE_IF(std::is_integral<FromRep>::value),
+          FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
+To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                      int& ec) {
+  using From = std::chrono::duration<FromRep, FromPeriod>;
+  ec = 0;
+  // the basic idea is that we need to convert from count() in the from type
+  // to count() in the To type, by multiplying it with this:
+  struct Factor
+      : std::ratio_divide<typename From::period, typename To::period> {};
+
+  static_assert(Factor::num > 0, "num must be positive");
+  static_assert(Factor::den > 0, "den must be positive");
+
+  // the conversion is like this: multiply from.count() with Factor::num
+  // /Factor::den and convert it to To::rep, all this without
+  // overflow/underflow. let's start by finding a suitable type that can hold
+  // both To, From and Factor::num
+  using IntermediateRep =
+      typename std::common_type<typename From::rep, typename To::rep,
+                                decltype(Factor::num)>::type;
+
+  // safe conversion to IntermediateRep
+  IntermediateRep count =
+      lossless_integral_conversion<IntermediateRep>(from.count(), ec);
+  if (ec) return {};
+  // multiply with Factor::num without overflow or underflow
+  if (detail::const_check(Factor::num != 1)) {
+    const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
+    if (count > max1) {
+      ec = 1;
+      return {};
+    }
+    const auto min1 =
+        (std::numeric_limits<IntermediateRep>::min)() / Factor::num;
+    if (count < min1) {
+      ec = 1;
+      return {};
+    }
+    count *= Factor::num;
+  }
+
+  if (detail::const_check(Factor::den != 1)) count /= Factor::den;
+  auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
+  return ec ? To() : To(tocount);
+}
+
+/**
+ * safe duration_cast between floating point durations
+ */
+template <typename To, typename FromRep, typename FromPeriod,
+          FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
+          FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
+To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                      int& ec) {
+  using From = std::chrono::duration<FromRep, FromPeriod>;
+  ec = 0;
+  if (std::isnan(from.count())) {
+    // nan in, gives nan out. easy.
+    return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
+  }
+  // maybe we should also check if from is denormal, and decide what to do about
+  // it.
+
+  // +-inf should be preserved.
+  if (std::isinf(from.count())) {
+    return To{from.count()};
+  }
+
+  // the basic idea is that we need to convert from count() in the from type
+  // to count() in the To type, by multiplying it with this:
+  struct Factor
+      : std::ratio_divide<typename From::period, typename To::period> {};
+
+  static_assert(Factor::num > 0, "num must be positive");
+  static_assert(Factor::den > 0, "den must be positive");
+
+  // the conversion is like this: multiply from.count() with Factor::num
+  // /Factor::den and convert it to To::rep, all this without
+  // overflow/underflow. let's start by finding a suitable type that can hold
+  // both To, From and Factor::num
+  using IntermediateRep =
+      typename std::common_type<typename From::rep, typename To::rep,
+                                decltype(Factor::num)>::type;
+
+  // force conversion of From::rep -> IntermediateRep to be safe,
+  // even if it will never happen be narrowing in this context.
+  IntermediateRep count =
+      safe_float_conversion<IntermediateRep>(from.count(), ec);
+  if (ec) {
+    return {};
+  }
+
+  // multiply with Factor::num without overflow or underflow
+  if (Factor::num != 1) {
+    constexpr auto max1 = detail::max_value<IntermediateRep>() /
+                          static_cast<IntermediateRep>(Factor::num);
+    if (count > max1) {
+      ec = 1;
+      return {};
+    }
+    constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
+                          static_cast<IntermediateRep>(Factor::num);
+    if (count < min1) {
+      ec = 1;
+      return {};
+    }
+    count *= static_cast<IntermediateRep>(Factor::num);
+  }
+
+  // this can't go wrong, right? den>0 is checked earlier.
+  if (Factor::den != 1) {
+    using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
+    count /= static_cast<common_t>(Factor::den);
+  }
+
+  // convert to the to type, safely
+  using ToRep = typename To::rep;
+
+  const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
+  if (ec) {
+    return {};
+  }
+  return To{tocount};
+}
+}  // namespace safe_duration_cast
+#endif
+
+// Prevents expansion of a preceding token as a function-style macro.
+// Usage: f FMT_NOMACRO()
+#define FMT_NOMACRO
+
+namespace detail {
+template <typename T = void> struct null {};
+inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
+inline null<> localtime_s(...) { return null<>(); }
+inline null<> gmtime_r(...) { return null<>(); }
+inline null<> gmtime_s(...) { return null<>(); }
+
+inline auto do_write(const std::tm& time, const std::locale& loc, char format,
+                     char modifier) -> std::string {
+  auto&& os = std::ostringstream();
+  os.imbue(loc);
+  using iterator = std::ostreambuf_iterator<char>;
+  const auto& facet = std::use_facet<std::time_put<char, iterator>>(loc);
+  auto end = facet.put(os, os, ' ', &time, format, modifier);
+  if (end.failed()) FMT_THROW(format_error("failed to format time"));
+  auto str = os.str();
+  if (!detail::is_utf8() || loc == std::locale::classic()) return str;
+    // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
+    // gcc-4.
+#if FMT_MSC_VER != 0 || \
+    (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
+  // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
+  // and newer.
+  using code_unit = wchar_t;
+#else
+  using code_unit = char32_t;
+#endif
+  auto& f = std::use_facet<std::codecvt<code_unit, char, std::mbstate_t>>(loc);
+  auto mb = std::mbstate_t();
+  const char* from_next = nullptr;
+  code_unit* to_next = nullptr;
+  constexpr size_t buf_size = 32;
+  code_unit buf[buf_size] = {};
+  auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
+                     buf + buf_size, to_next);
+  if (result != std::codecvt_base::ok)
+    FMT_THROW(format_error("failed to format time"));
+  str.clear();
+  for (code_unit* p = buf; p != to_next; ++p) {
+    uint32_t c = static_cast<uint32_t>(*p);
+    if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
+      // surrogate pair
+      ++p;
+      if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
+        FMT_THROW(format_error("failed to format time"));
+      }
+      c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
+    }
+    if (c < 0x80) {
+      str.push_back(static_cast<char>(c));
+    } else if (c < 0x800) {
+      str.push_back(static_cast<char>(0xc0 | (c >> 6)));
+      str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
+    } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
+      str.push_back(static_cast<char>(0xe0 | (c >> 12)));
+      str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
+      str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
+    } else if (c >= 0x10000 && c <= 0x10ffff) {
+      str.push_back(static_cast<char>(0xf0 | (c >> 18)));
+      str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
+      str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
+      str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
+    } else {
+      FMT_THROW(format_error("failed to format time"));
+    }
+  }
+  return str;
+}
+
+template <typename OutputIt>
+auto write(OutputIt out, const std::tm& time, const std::locale& loc,
+           char format, char modifier = 0) -> OutputIt {
+  auto str = do_write(time, loc, format, modifier);
+  return std::copy(str.begin(), str.end(), out);
+}
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+/**
+  Converts given time since epoch as ``std::time_t`` value into calendar time,
+  expressed in local time. Unlike ``std::localtime``, this function is
+  thread-safe on most platforms.
+ */
+inline std::tm localtime(std::time_t time) {
+  struct dispatcher {
+    std::time_t time_;
+    std::tm tm_;
+
+    dispatcher(std::time_t t) : time_(t) {}
+
+    bool run() {
+      using namespace fmt::detail;
+      return handle(localtime_r(&time_, &tm_));
+    }
+
+    bool handle(std::tm* tm) { return tm != nullptr; }
+
+    bool handle(detail::null<>) {
+      using namespace fmt::detail;
+      return fallback(localtime_s(&tm_, &time_));
+    }
+
+    bool fallback(int res) { return res == 0; }
+
+#if !FMT_MSC_VER
+    bool fallback(detail::null<>) {
+      using namespace fmt::detail;
+      std::tm* tm = std::localtime(&time_);
+      if (tm) tm_ = *tm;
+      return tm != nullptr;
+    }
+#endif
+  };
+  dispatcher lt(time);
+  // Too big time values may be unsupported.
+  if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
+  return lt.tm_;
+}
+
+inline std::tm localtime(
+    std::chrono::time_point<std::chrono::system_clock> time_point) {
+  return localtime(std::chrono::system_clock::to_time_t(time_point));
+}
+
+/**
+  Converts given time since epoch as ``std::time_t`` value into calendar time,
+  expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
+  function is thread-safe on most platforms.
+ */
+inline std::tm gmtime(std::time_t time) {
+  struct dispatcher {
+    std::time_t time_;
+    std::tm tm_;
+
+    dispatcher(std::time_t t) : time_(t) {}
+
+    bool run() {
+      using namespace fmt::detail;
+      return handle(gmtime_r(&time_, &tm_));
+    }
+
+    bool handle(std::tm* tm) { return tm != nullptr; }
+
+    bool handle(detail::null<>) {
+      using namespace fmt::detail;
+      return fallback(gmtime_s(&tm_, &time_));
+    }
+
+    bool fallback(int res) { return res == 0; }
+
+#if !FMT_MSC_VER
+    bool fallback(detail::null<>) {
+      std::tm* tm = std::gmtime(&time_);
+      if (tm) tm_ = *tm;
+      return tm != nullptr;
+    }
+#endif
+  };
+  dispatcher gt(time);
+  // Too big time values may be unsupported.
+  if (!gt.run()) FMT_THROW(format_error("time_t value out of range"));
+  return gt.tm_;
+}
+
+inline std::tm gmtime(
+    std::chrono::time_point<std::chrono::system_clock> time_point) {
+  return gmtime(std::chrono::system_clock::to_time_t(time_point));
+}
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+inline size_t strftime(char* str, size_t count, const char* format,
+                       const std::tm* time) {
+  // Assign to a pointer to suppress GCCs -Wformat-nonliteral
+  // First assign the nullptr to suppress -Wsuggest-attribute=format
+  std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
+      nullptr;
+  strftime = std::strftime;
+  return strftime(str, count, format, time);
+}
+
+inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
+                       const std::tm* time) {
+  // See above
+  std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
+                          const std::tm*) = nullptr;
+  wcsftime = std::wcsftime;
+  return wcsftime(str, count, format, time);
+}
+
+FMT_END_DETAIL_NAMESPACE
+
+template <typename Char, typename Duration>
+struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
+                 Char> : formatter<std::tm, Char> {
+  FMT_CONSTEXPR formatter() {
+    this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)};
+  }
+
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    auto it = ctx.begin();
+    if (it != ctx.end() && *it == ':') ++it;
+    auto end = it;
+    while (end != ctx.end() && *end != '}') ++end;
+    if (end != it) this->specs = {it, detail::to_unsigned(end - it)};
+    return end;
+  }
+
+  template <typename FormatContext>
+  auto format(std::chrono::time_point<std::chrono::system_clock> val,
+              FormatContext& ctx) -> decltype(ctx.out()) {
+    std::tm time = localtime(val);
+    return formatter<std::tm, Char>::format(time, ctx);
+  }
+
+  static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-',
+                                           '%', 'd', ' ', '%', 'H', ':',
+                                           '%', 'M', ':', '%', 'S'};
+};
+
+template <typename Char, typename Duration>
+constexpr Char
+    formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
+              Char>::default_specs[];
+
+template <typename Char> struct formatter<std::tm, Char> {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    auto it = ctx.begin();
+    if (it != ctx.end() && *it == ':') ++it;
+    auto end = it;
+    while (end != ctx.end() && *end != '}') ++end;
+    specs = {it, detail::to_unsigned(end - it)};
+    return end;
+  }
+
+  template <typename FormatContext>
+  auto format(const std::tm& tm, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    basic_memory_buffer<Char> tm_format;
+    tm_format.append(specs.begin(), specs.end());
+    // By appending an extra space we can distinguish an empty result that
+    // indicates insufficient buffer size from a guaranteed non-empty result
+    // https://github.com/fmtlib/fmt/issues/2238
+    tm_format.push_back(' ');
+    tm_format.push_back('\0');
+    basic_memory_buffer<Char> buf;
+    size_t start = buf.size();
+    for (;;) {
+      size_t size = buf.capacity() - start;
+      size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm);
+      if (count != 0) {
+        buf.resize(start + count);
+        break;
+      }
+      const size_t MIN_GROWTH = 10;
+      buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
+    }
+    // Remove the extra space.
+    return std::copy(buf.begin(), buf.end() - 1, ctx.out());
+  }
+
+  basic_string_view<Char> specs;
+};
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
+  if (std::is_same<Period, std::atto>::value) return "as";
+  if (std::is_same<Period, std::femto>::value) return "fs";
+  if (std::is_same<Period, std::pico>::value) return "ps";
+  if (std::is_same<Period, std::nano>::value) return "ns";
+  if (std::is_same<Period, std::micro>::value) return "µs";
+  if (std::is_same<Period, std::milli>::value) return "ms";
+  if (std::is_same<Period, std::centi>::value) return "cs";
+  if (std::is_same<Period, std::deci>::value) return "ds";
+  if (std::is_same<Period, std::ratio<1>>::value) return "s";
+  if (std::is_same<Period, std::deca>::value) return "das";
+  if (std::is_same<Period, std::hecto>::value) return "hs";
+  if (std::is_same<Period, std::kilo>::value) return "ks";
+  if (std::is_same<Period, std::mega>::value) return "Ms";
+  if (std::is_same<Period, std::giga>::value) return "Gs";
+  if (std::is_same<Period, std::tera>::value) return "Ts";
+  if (std::is_same<Period, std::peta>::value) return "Ps";
+  if (std::is_same<Period, std::exa>::value) return "Es";
+  if (std::is_same<Period, std::ratio<60>>::value) return "m";
+  if (std::is_same<Period, std::ratio<3600>>::value) return "h";
+  return nullptr;
+}
+
+enum class numeric_system {
+  standard,
+  // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
+  alternative
+};
+
+// Parses a put_time-like format string and invokes handler actions.
+template <typename Char, typename Handler>
+FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
+                                              const Char* end,
+                                              Handler&& handler) {
+  auto ptr = begin;
+  while (ptr != end) {
+    auto c = *ptr;
+    if (c == '}') break;
+    if (c != '%') {
+      ++ptr;
+      continue;
+    }
+    if (begin != ptr) handler.on_text(begin, ptr);
+    ++ptr;  // consume '%'
+    if (ptr == end) FMT_THROW(format_error("invalid format"));
+    c = *ptr++;
+    switch (c) {
+    case '%':
+      handler.on_text(ptr - 1, ptr);
+      break;
+    case 'n': {
+      const Char newline[] = {'\n'};
+      handler.on_text(newline, newline + 1);
+      break;
+    }
+    case 't': {
+      const Char tab[] = {'\t'};
+      handler.on_text(tab, tab + 1);
+      break;
+    }
+    // Day of the week:
+    case 'a':
+      handler.on_abbr_weekday();
+      break;
+    case 'A':
+      handler.on_full_weekday();
+      break;
+    case 'w':
+      handler.on_dec0_weekday(numeric_system::standard);
+      break;
+    case 'u':
+      handler.on_dec1_weekday(numeric_system::standard);
+      break;
+    // Month:
+    case 'b':
+      handler.on_abbr_month();
+      break;
+    case 'B':
+      handler.on_full_month();
+      break;
+    // Hour, minute, second:
+    case 'H':
+      handler.on_24_hour(numeric_system::standard);
+      break;
+    case 'I':
+      handler.on_12_hour(numeric_system::standard);
+      break;
+    case 'M':
+      handler.on_minute(numeric_system::standard);
+      break;
+    case 'S':
+      handler.on_second(numeric_system::standard);
+      break;
+    // Other:
+    case 'c':
+      handler.on_datetime(numeric_system::standard);
+      break;
+    case 'x':
+      handler.on_loc_date(numeric_system::standard);
+      break;
+    case 'X':
+      handler.on_loc_time(numeric_system::standard);
+      break;
+    case 'D':
+      handler.on_us_date();
+      break;
+    case 'F':
+      handler.on_iso_date();
+      break;
+    case 'r':
+      handler.on_12_hour_time();
+      break;
+    case 'R':
+      handler.on_24_hour_time();
+      break;
+    case 'T':
+      handler.on_iso_time();
+      break;
+    case 'p':
+      handler.on_am_pm();
+      break;
+    case 'Q':
+      handler.on_duration_value();
+      break;
+    case 'q':
+      handler.on_duration_unit();
+      break;
+    case 'z':
+      handler.on_utc_offset();
+      break;
+    case 'Z':
+      handler.on_tz_name();
+      break;
+    // Alternative representation:
+    case 'E': {
+      if (ptr == end) FMT_THROW(format_error("invalid format"));
+      c = *ptr++;
+      switch (c) {
+      case 'c':
+        handler.on_datetime(numeric_system::alternative);
+        break;
+      case 'x':
+        handler.on_loc_date(numeric_system::alternative);
+        break;
+      case 'X':
+        handler.on_loc_time(numeric_system::alternative);
+        break;
+      default:
+        FMT_THROW(format_error("invalid format"));
+      }
+      break;
+    }
+    case 'O':
+      if (ptr == end) FMT_THROW(format_error("invalid format"));
+      c = *ptr++;
+      switch (c) {
+      case 'w':
+        handler.on_dec0_weekday(numeric_system::alternative);
+        break;
+      case 'u':
+        handler.on_dec1_weekday(numeric_system::alternative);
+        break;
+      case 'H':
+        handler.on_24_hour(numeric_system::alternative);
+        break;
+      case 'I':
+        handler.on_12_hour(numeric_system::alternative);
+        break;
+      case 'M':
+        handler.on_minute(numeric_system::alternative);
+        break;
+      case 'S':
+        handler.on_second(numeric_system::alternative);
+        break;
+      default:
+        FMT_THROW(format_error("invalid format"));
+      }
+      break;
+    default:
+      FMT_THROW(format_error("invalid format"));
+    }
+    begin = ptr;
+  }
+  if (begin != ptr) handler.on_text(begin, ptr);
+  return ptr;
+}
+
+template <typename Derived> struct null_chrono_spec_handler {
+  FMT_CONSTEXPR void unsupported() {
+    static_cast<Derived*>(this)->unsupported();
+  }
+  FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
+  FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
+  FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
+  FMT_CONSTEXPR void on_full_month() { unsupported(); }
+  FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_us_date() { unsupported(); }
+  FMT_CONSTEXPR void on_iso_date() { unsupported(); }
+  FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
+  FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
+  FMT_CONSTEXPR void on_iso_time() { unsupported(); }
+  FMT_CONSTEXPR void on_am_pm() { unsupported(); }
+  FMT_CONSTEXPR void on_duration_value() { unsupported(); }
+  FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
+  FMT_CONSTEXPR void on_utc_offset() { unsupported(); }
+  FMT_CONSTEXPR void on_tz_name() { unsupported(); }
+};
+
+struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
+  FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
+
+  template <typename Char>
+  FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+  FMT_CONSTEXPR void on_24_hour(numeric_system) {}
+  FMT_CONSTEXPR void on_12_hour(numeric_system) {}
+  FMT_CONSTEXPR void on_minute(numeric_system) {}
+  FMT_CONSTEXPR void on_second(numeric_system) {}
+  FMT_CONSTEXPR void on_12_hour_time() {}
+  FMT_CONSTEXPR void on_24_hour_time() {}
+  FMT_CONSTEXPR void on_iso_time() {}
+  FMT_CONSTEXPR void on_am_pm() {}
+  FMT_CONSTEXPR void on_duration_value() {}
+  FMT_CONSTEXPR void on_duration_unit() {}
+};
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline bool isnan(T) {
+  return false;
+}
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+inline bool isnan(T value) {
+  return std::isnan(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline bool isfinite(T) {
+  return true;
+}
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+inline bool isfinite(T value) {
+  return std::isfinite(value);
+}
+
+// Converts value to int and checks that it's in the range [0, upper).
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline int to_nonnegative_int(T value, int upper) {
+  FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
+             "invalid value");
+  (void)upper;
+  return static_cast<int>(value);
+}
+template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+inline int to_nonnegative_int(T value, int upper) {
+  FMT_ASSERT(
+      std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)),
+      "invalid value");
+  (void)upper;
+  return static_cast<int>(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline T mod(T x, int y) {
+  return x % static_cast<T>(y);
+}
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+inline T mod(T x, int y) {
+  return std::fmod(x, static_cast<T>(y));
+}
+
+// If T is an integral type, maps T to its unsigned counterpart, otherwise
+// leaves it unchanged (unlike std::make_unsigned).
+template <typename T, bool INTEGRAL = std::is_integral<T>::value>
+struct make_unsigned_or_unchanged {
+  using type = T;
+};
+
+template <typename T> struct make_unsigned_or_unchanged<T, true> {
+  using type = typename std::make_unsigned<T>::type;
+};
+
+#if FMT_SAFE_DURATION_CAST
+// throwing version of safe_duration_cast
+template <typename To, typename FromRep, typename FromPeriod>
+To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
+  int ec;
+  To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
+  if (ec) FMT_THROW(format_error("cannot format duration"));
+  return to;
+}
+#endif
+
+template <typename Rep, typename Period,
+          FMT_ENABLE_IF(std::is_integral<Rep>::value)>
+inline std::chrono::duration<Rep, std::milli> get_milliseconds(
+    std::chrono::duration<Rep, Period> d) {
+  // this may overflow and/or the result may not fit in the
+  // target type.
+#if FMT_SAFE_DURATION_CAST
+  using CommonSecondsType =
+      typename std::common_type<decltype(d), std::chrono::seconds>::type;
+  const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
+  const auto d_as_whole_seconds =
+      fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
+  // this conversion should be nonproblematic
+  const auto diff = d_as_common - d_as_whole_seconds;
+  const auto ms =
+      fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
+  return ms;
+#else
+  auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
+  return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
+#endif
+}
+
+template <typename Rep, typename Period,
+          FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
+inline std::chrono::duration<Rep, std::milli> get_milliseconds(
+    std::chrono::duration<Rep, Period> d) {
+  using common_type = typename std::common_type<Rep, std::intmax_t>::type;
+  auto ms = mod(d.count() * static_cast<common_type>(Period::num) /
+                    static_cast<common_type>(Period::den) * 1000,
+                1000);
+  return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
+}
+
+template <typename Char, typename Rep, typename OutputIt,
+          FMT_ENABLE_IF(std::is_integral<Rep>::value)>
+OutputIt format_duration_value(OutputIt out, Rep val, int) {
+  return write<Char>(out, val);
+}
+
+template <typename Char, typename Rep, typename OutputIt,
+          FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
+OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
+  auto specs = basic_format_specs<Char>();
+  specs.precision = precision;
+  specs.type = precision > 0 ? 'f' : 'g';
+  return write<Char>(out, val, specs);
+}
+
+template <typename Char, typename OutputIt>
+OutputIt copy_unit(string_view unit, OutputIt out, Char) {
+  return std::copy(unit.begin(), unit.end(), out);
+}
+
+template <typename OutputIt>
+OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
+  // This works when wchar_t is UTF-32 because units only contain characters
+  // that have the same representation in UTF-16 and UTF-32.
+  utf8_to_utf16 u(unit);
+  return std::copy(u.c_str(), u.c_str() + u.size(), out);
+}
+
+template <typename Char, typename Period, typename OutputIt>
+OutputIt format_duration_unit(OutputIt out) {
+  if (const char* unit = get_units<Period>())
+    return copy_unit(string_view(unit), out, Char());
+  *out++ = '[';
+  out = write<Char>(out, Period::num);
+  if (const_check(Period::den != 1)) {
+    *out++ = '/';
+    out = write<Char>(out, Period::den);
+  }
+  *out++ = ']';
+  *out++ = 's';
+  return out;
+}
+
+template <typename FormatContext, typename OutputIt, typename Rep,
+          typename Period>
+struct chrono_formatter {
+  FormatContext& context;
+  OutputIt out;
+  int precision;
+  bool localized = false;
+  // rep is unsigned to avoid overflow.
+  using rep =
+      conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
+                    unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
+  rep val;
+  using seconds = std::chrono::duration<rep>;
+  seconds s;
+  using milliseconds = std::chrono::duration<rep, std::milli>;
+  bool negative;
+
+  using char_type = typename FormatContext::char_type;
+
+  explicit chrono_formatter(FormatContext& ctx, OutputIt o,
+                            std::chrono::duration<Rep, Period> d)
+      : context(ctx),
+        out(o),
+        val(static_cast<rep>(d.count())),
+        negative(false) {
+    if (d.count() < 0) {
+      val = 0 - val;
+      negative = true;
+    }
+
+    // this may overflow and/or the result may not fit in the
+    // target type.
+#if FMT_SAFE_DURATION_CAST
+    // might need checked conversion (rep!=Rep)
+    auto tmpval = std::chrono::duration<rep, Period>(val);
+    s = fmt_safe_duration_cast<seconds>(tmpval);
+#else
+    s = std::chrono::duration_cast<seconds>(
+        std::chrono::duration<rep, Period>(val));
+#endif
+  }
+
+  // returns true if nan or inf, writes to out.
+  bool handle_nan_inf() {
+    if (isfinite(val)) {
+      return false;
+    }
+    if (isnan(val)) {
+      write_nan();
+      return true;
+    }
+    // must be +-inf
+    if (val > 0) {
+      write_pinf();
+    } else {
+      write_ninf();
+    }
+    return true;
+  }
+
+  Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); }
+
+  Rep hour12() const {
+    Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12));
+    return hour <= 0 ? 12 : hour;
+  }
+
+  Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); }
+  Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); }
+
+  std::tm time() const {
+    auto time = std::tm();
+    time.tm_hour = to_nonnegative_int(hour(), 24);
+    time.tm_min = to_nonnegative_int(minute(), 60);
+    time.tm_sec = to_nonnegative_int(second(), 60);
+    return time;
+  }
+
+  void write_sign() {
+    if (negative) {
+      *out++ = '-';
+      negative = false;
+    }
+  }
+
+  void write(Rep value, int width) {
+    write_sign();
+    if (isnan(value)) return write_nan();
+    uint32_or_64_or_128_t<int> n =
+        to_unsigned(to_nonnegative_int(value, max_value<int>()));
+    int num_digits = detail::count_digits(n);
+    if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
+    out = format_decimal<char_type>(out, n, num_digits).end;
+  }
+
+  void write_nan() { std::copy_n("nan", 3, out); }
+  void write_pinf() { std::copy_n("inf", 3, out); }
+  void write_ninf() { std::copy_n("-inf", 4, out); }
+
+  void format_localized(const tm& time, char format, char modifier = 0) {
+    if (isnan(val)) return write_nan();
+    const auto& loc = localized ? context.locale().template get<std::locale>()
+                                : std::locale::classic();
+    out = detail::write(out, time, loc, format, modifier);
+  }
+
+  void on_text(const char_type* begin, const char_type* end) {
+    std::copy(begin, end, out);
+  }
+
+  // These are not implemented because durations don't have date information.
+  void on_abbr_weekday() {}
+  void on_full_weekday() {}
+  void on_dec0_weekday(numeric_system) {}
+  void on_dec1_weekday(numeric_system) {}
+  void on_abbr_month() {}
+  void on_full_month() {}
+  void on_datetime(numeric_system) {}
+  void on_loc_date(numeric_system) {}
+  void on_loc_time(numeric_system) {}
+  void on_us_date() {}
+  void on_iso_date() {}
+  void on_utc_offset() {}
+  void on_tz_name() {}
+
+  void on_24_hour(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) return write(hour(), 2);
+    auto time = tm();
+    time.tm_hour = to_nonnegative_int(hour(), 24);
+    format_localized(time, 'H', 'O');
+  }
+
+  void on_12_hour(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) return write(hour12(), 2);
+    auto time = tm();
+    time.tm_hour = to_nonnegative_int(hour12(), 12);
+    format_localized(time, 'I', 'O');
+  }
+
+  void on_minute(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) return write(minute(), 2);
+    auto time = tm();
+    time.tm_min = to_nonnegative_int(minute(), 60);
+    format_localized(time, 'M', 'O');
+  }
+
+  void on_second(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) {
+      write(second(), 2);
+#if FMT_SAFE_DURATION_CAST
+      // convert rep->Rep
+      using duration_rep = std::chrono::duration<rep, Period>;
+      using duration_Rep = std::chrono::duration<Rep, Period>;
+      auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val});
+#else
+      auto tmpval = std::chrono::duration<Rep, Period>(val);
+#endif
+      auto ms = get_milliseconds(tmpval);
+      if (ms != std::chrono::milliseconds(0)) {
+        *out++ = '.';
+        write(ms.count(), 3);
+      }
+      return;
+    }
+    auto time = tm();
+    time.tm_sec = to_nonnegative_int(second(), 60);
+    format_localized(time, 'S', 'O');
+  }
+
+  void on_12_hour_time() {
+    if (handle_nan_inf()) return;
+    format_localized(time(), 'r');
+  }
+
+  void on_24_hour_time() {
+    if (handle_nan_inf()) {
+      *out++ = ':';
+      handle_nan_inf();
+      return;
+    }
+
+    write(hour(), 2);
+    *out++ = ':';
+    write(minute(), 2);
+  }
+
+  void on_iso_time() {
+    on_24_hour_time();
+    *out++ = ':';
+    if (handle_nan_inf()) return;
+    write(second(), 2);
+  }
+
+  void on_am_pm() {
+    if (handle_nan_inf()) return;
+    format_localized(time(), 'p');
+  }
+
+  void on_duration_value() {
+    if (handle_nan_inf()) return;
+    write_sign();
+    out = format_duration_value<char_type>(out, val, precision);
+  }
+
+  void on_duration_unit() {
+    out = format_duration_unit<char_type, Period>(out);
+  }
+};
+
+FMT_END_DETAIL_NAMESPACE
+
+#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
+using weekday = std::chrono::weekday;
+#else
+// A fallback version of weekday.
+class weekday {
+ private:
+  unsigned char value;
+
+ public:
+  weekday() = default;
+  explicit constexpr weekday(unsigned wd) noexcept
+      : value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
+  constexpr unsigned c_encoding() const noexcept { return value; }
+};
+#endif
+
+// A rudimentary weekday formatter.
+template <> struct formatter<weekday> {
+ private:
+  bool localized = false;
+
+ public:
+  FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+    auto begin = ctx.begin(), end = ctx.end();
+    if (begin != end && *begin == 'L') {
+      ++begin;
+      localized = true;
+    }
+    return begin;
+  }
+
+  auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
+    auto time = std::tm();
+    time.tm_wday = static_cast<int>(wd.c_encoding());
+    const auto& loc = localized ? ctx.locale().template get<std::locale>()
+                                : std::locale::classic();
+    return detail::write(ctx.out(), time, loc, 'a');
+  }
+};
+
+template <typename Rep, typename Period, typename Char>
+struct formatter<std::chrono::duration<Rep, Period>, Char> {
+ private:
+  basic_format_specs<Char> specs;
+  int precision = -1;
+  using arg_ref_type = detail::arg_ref<Char>;
+  arg_ref_type width_ref;
+  arg_ref_type precision_ref;
+  bool localized = false;
+  basic_string_view<Char> format_str;
+  using duration = std::chrono::duration<Rep, Period>;
+
+  struct spec_handler {
+    formatter& f;
+    basic_format_parse_context<Char>& context;
+    basic_string_view<Char> format_str;
+
+    template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
+      context.check_arg_id(arg_id);
+      return arg_ref_type(arg_id);
+    }
+
+    FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) {
+      context.check_arg_id(arg_id);
+      return arg_ref_type(arg_id);
+    }
+
+    FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) {
+      return arg_ref_type(context.next_arg_id());
+    }
+
+    void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
+    FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
+      f.specs.fill = fill;
+    }
+    FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; }
+    FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; }
+    FMT_CONSTEXPR void on_precision(int _precision) {
+      f.precision = _precision;
+    }
+    FMT_CONSTEXPR void end_precision() {}
+
+    template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
+      f.width_ref = make_arg_ref(arg_id);
+    }
+
+    template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
+      f.precision_ref = make_arg_ref(arg_id);
+    }
+  };
+
+  using iterator = typename basic_format_parse_context<Char>::iterator;
+  struct parse_range {
+    iterator begin;
+    iterator end;
+  };
+
+  FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context<Char>& ctx) {
+    auto begin = ctx.begin(), end = ctx.end();
+    if (begin == end || *begin == '}') return {begin, begin};
+    spec_handler handler{*this, ctx, format_str};
+    begin = detail::parse_align(begin, end, handler);
+    if (begin == end) return {begin, begin};
+    begin = detail::parse_width(begin, end, handler);
+    if (begin == end) return {begin, begin};
+    if (*begin == '.') {
+      if (std::is_floating_point<Rep>::value)
+        begin = detail::parse_precision(begin, end, handler);
+      else
+        handler.on_error("precision not allowed for this argument type");
+    }
+    if (begin != end && *begin == 'L') {
+      ++begin;
+      localized = true;
+    }
+    end = parse_chrono_format(begin, end, detail::chrono_format_checker());
+    return {begin, end};
+  }
+
+ public:
+  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
+      -> decltype(ctx.begin()) {
+    auto range = do_parse(ctx);
+    format_str = basic_string_view<Char>(
+        &*range.begin, detail::to_unsigned(range.end - range.begin));
+    return range.end;
+  }
+
+  template <typename FormatContext>
+  auto format(const duration& d, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    auto specs_copy = specs;
+    auto precision_copy = precision;
+    auto begin = format_str.begin(), end = format_str.end();
+    // As a possible future optimization, we could avoid extra copying if width
+    // is not specified.
+    basic_memory_buffer<Char> buf;
+    auto out = std::back_inserter(buf);
+    detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
+                                                       width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(precision_copy,
+                                                           precision_ref, ctx);
+    if (begin == end || *begin == '}') {
+      out = detail::format_duration_value<Char>(out, d.count(), precision_copy);
+      detail::format_duration_unit<Char, Period>(out);
+    } else {
+      detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
+          ctx, out, d);
+      f.precision = precision_copy;
+      f.localized = localized;
+      detail::parse_chrono_format(begin, end, f);
+    }
+    return detail::write(
+        ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
+  }
+};
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_CHRONO_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/color.h b/wpiutil/src/main/native/fmtlib/include/fmt/color.h
new file mode 100644
index 0000000..3d5490e
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/color.h
@@ -0,0 +1,627 @@
+// Formatting library for C++ - color support
+//
+// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_COLOR_H_
+#define FMT_COLOR_H_
+
+#include "format.h"
+
+// __declspec(deprecated) is broken in some MSVC versions.
+#if FMT_MSC_VER
+#  define FMT_DEPRECATED_NONMSVC
+#else
+#  define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
+#endif
+
+FMT_BEGIN_NAMESPACE
+FMT_MODULE_EXPORT_BEGIN
+
+enum class color : uint32_t {
+  alice_blue = 0xF0F8FF,               // rgb(240,248,255)
+  antique_white = 0xFAEBD7,            // rgb(250,235,215)
+  aqua = 0x00FFFF,                     // rgb(0,255,255)
+  aquamarine = 0x7FFFD4,               // rgb(127,255,212)
+  azure = 0xF0FFFF,                    // rgb(240,255,255)
+  beige = 0xF5F5DC,                    // rgb(245,245,220)
+  bisque = 0xFFE4C4,                   // rgb(255,228,196)
+  black = 0x000000,                    // rgb(0,0,0)
+  blanched_almond = 0xFFEBCD,          // rgb(255,235,205)
+  blue = 0x0000FF,                     // rgb(0,0,255)
+  blue_violet = 0x8A2BE2,              // rgb(138,43,226)
+  brown = 0xA52A2A,                    // rgb(165,42,42)
+  burly_wood = 0xDEB887,               // rgb(222,184,135)
+  cadet_blue = 0x5F9EA0,               // rgb(95,158,160)
+  chartreuse = 0x7FFF00,               // rgb(127,255,0)
+  chocolate = 0xD2691E,                // rgb(210,105,30)
+  coral = 0xFF7F50,                    // rgb(255,127,80)
+  cornflower_blue = 0x6495ED,          // rgb(100,149,237)
+  cornsilk = 0xFFF8DC,                 // rgb(255,248,220)
+  crimson = 0xDC143C,                  // rgb(220,20,60)
+  cyan = 0x00FFFF,                     // rgb(0,255,255)
+  dark_blue = 0x00008B,                // rgb(0,0,139)
+  dark_cyan = 0x008B8B,                // rgb(0,139,139)
+  dark_golden_rod = 0xB8860B,          // rgb(184,134,11)
+  dark_gray = 0xA9A9A9,                // rgb(169,169,169)
+  dark_green = 0x006400,               // rgb(0,100,0)
+  dark_khaki = 0xBDB76B,               // rgb(189,183,107)
+  dark_magenta = 0x8B008B,             // rgb(139,0,139)
+  dark_olive_green = 0x556B2F,         // rgb(85,107,47)
+  dark_orange = 0xFF8C00,              // rgb(255,140,0)
+  dark_orchid = 0x9932CC,              // rgb(153,50,204)
+  dark_red = 0x8B0000,                 // rgb(139,0,0)
+  dark_salmon = 0xE9967A,              // rgb(233,150,122)
+  dark_sea_green = 0x8FBC8F,           // rgb(143,188,143)
+  dark_slate_blue = 0x483D8B,          // rgb(72,61,139)
+  dark_slate_gray = 0x2F4F4F,          // rgb(47,79,79)
+  dark_turquoise = 0x00CED1,           // rgb(0,206,209)
+  dark_violet = 0x9400D3,              // rgb(148,0,211)
+  deep_pink = 0xFF1493,                // rgb(255,20,147)
+  deep_sky_blue = 0x00BFFF,            // rgb(0,191,255)
+  dim_gray = 0x696969,                 // rgb(105,105,105)
+  dodger_blue = 0x1E90FF,              // rgb(30,144,255)
+  fire_brick = 0xB22222,               // rgb(178,34,34)
+  floral_white = 0xFFFAF0,             // rgb(255,250,240)
+  forest_green = 0x228B22,             // rgb(34,139,34)
+  fuchsia = 0xFF00FF,                  // rgb(255,0,255)
+  gainsboro = 0xDCDCDC,                // rgb(220,220,220)
+  ghost_white = 0xF8F8FF,              // rgb(248,248,255)
+  gold = 0xFFD700,                     // rgb(255,215,0)
+  golden_rod = 0xDAA520,               // rgb(218,165,32)
+  gray = 0x808080,                     // rgb(128,128,128)
+  green = 0x008000,                    // rgb(0,128,0)
+  green_yellow = 0xADFF2F,             // rgb(173,255,47)
+  honey_dew = 0xF0FFF0,                // rgb(240,255,240)
+  hot_pink = 0xFF69B4,                 // rgb(255,105,180)
+  indian_red = 0xCD5C5C,               // rgb(205,92,92)
+  indigo = 0x4B0082,                   // rgb(75,0,130)
+  ivory = 0xFFFFF0,                    // rgb(255,255,240)
+  khaki = 0xF0E68C,                    // rgb(240,230,140)
+  lavender = 0xE6E6FA,                 // rgb(230,230,250)
+  lavender_blush = 0xFFF0F5,           // rgb(255,240,245)
+  lawn_green = 0x7CFC00,               // rgb(124,252,0)
+  lemon_chiffon = 0xFFFACD,            // rgb(255,250,205)
+  light_blue = 0xADD8E6,               // rgb(173,216,230)
+  light_coral = 0xF08080,              // rgb(240,128,128)
+  light_cyan = 0xE0FFFF,               // rgb(224,255,255)
+  light_golden_rod_yellow = 0xFAFAD2,  // rgb(250,250,210)
+  light_gray = 0xD3D3D3,               // rgb(211,211,211)
+  light_green = 0x90EE90,              // rgb(144,238,144)
+  light_pink = 0xFFB6C1,               // rgb(255,182,193)
+  light_salmon = 0xFFA07A,             // rgb(255,160,122)
+  light_sea_green = 0x20B2AA,          // rgb(32,178,170)
+  light_sky_blue = 0x87CEFA,           // rgb(135,206,250)
+  light_slate_gray = 0x778899,         // rgb(119,136,153)
+  light_steel_blue = 0xB0C4DE,         // rgb(176,196,222)
+  light_yellow = 0xFFFFE0,             // rgb(255,255,224)
+  lime = 0x00FF00,                     // rgb(0,255,0)
+  lime_green = 0x32CD32,               // rgb(50,205,50)
+  linen = 0xFAF0E6,                    // rgb(250,240,230)
+  magenta = 0xFF00FF,                  // rgb(255,0,255)
+  maroon = 0x800000,                   // rgb(128,0,0)
+  medium_aquamarine = 0x66CDAA,        // rgb(102,205,170)
+  medium_blue = 0x0000CD,              // rgb(0,0,205)
+  medium_orchid = 0xBA55D3,            // rgb(186,85,211)
+  medium_purple = 0x9370DB,            // rgb(147,112,219)
+  medium_sea_green = 0x3CB371,         // rgb(60,179,113)
+  medium_slate_blue = 0x7B68EE,        // rgb(123,104,238)
+  medium_spring_green = 0x00FA9A,      // rgb(0,250,154)
+  medium_turquoise = 0x48D1CC,         // rgb(72,209,204)
+  medium_violet_red = 0xC71585,        // rgb(199,21,133)
+  midnight_blue = 0x191970,            // rgb(25,25,112)
+  mint_cream = 0xF5FFFA,               // rgb(245,255,250)
+  misty_rose = 0xFFE4E1,               // rgb(255,228,225)
+  moccasin = 0xFFE4B5,                 // rgb(255,228,181)
+  navajo_white = 0xFFDEAD,             // rgb(255,222,173)
+  navy = 0x000080,                     // rgb(0,0,128)
+  old_lace = 0xFDF5E6,                 // rgb(253,245,230)
+  olive = 0x808000,                    // rgb(128,128,0)
+  olive_drab = 0x6B8E23,               // rgb(107,142,35)
+  orange = 0xFFA500,                   // rgb(255,165,0)
+  orange_red = 0xFF4500,               // rgb(255,69,0)
+  orchid = 0xDA70D6,                   // rgb(218,112,214)
+  pale_golden_rod = 0xEEE8AA,          // rgb(238,232,170)
+  pale_green = 0x98FB98,               // rgb(152,251,152)
+  pale_turquoise = 0xAFEEEE,           // rgb(175,238,238)
+  pale_violet_red = 0xDB7093,          // rgb(219,112,147)
+  papaya_whip = 0xFFEFD5,              // rgb(255,239,213)
+  peach_puff = 0xFFDAB9,               // rgb(255,218,185)
+  peru = 0xCD853F,                     // rgb(205,133,63)
+  pink = 0xFFC0CB,                     // rgb(255,192,203)
+  plum = 0xDDA0DD,                     // rgb(221,160,221)
+  powder_blue = 0xB0E0E6,              // rgb(176,224,230)
+  purple = 0x800080,                   // rgb(128,0,128)
+  rebecca_purple = 0x663399,           // rgb(102,51,153)
+  red = 0xFF0000,                      // rgb(255,0,0)
+  rosy_brown = 0xBC8F8F,               // rgb(188,143,143)
+  royal_blue = 0x4169E1,               // rgb(65,105,225)
+  saddle_brown = 0x8B4513,             // rgb(139,69,19)
+  salmon = 0xFA8072,                   // rgb(250,128,114)
+  sandy_brown = 0xF4A460,              // rgb(244,164,96)
+  sea_green = 0x2E8B57,                // rgb(46,139,87)
+  sea_shell = 0xFFF5EE,                // rgb(255,245,238)
+  sienna = 0xA0522D,                   // rgb(160,82,45)
+  silver = 0xC0C0C0,                   // rgb(192,192,192)
+  sky_blue = 0x87CEEB,                 // rgb(135,206,235)
+  slate_blue = 0x6A5ACD,               // rgb(106,90,205)
+  slate_gray = 0x708090,               // rgb(112,128,144)
+  snow = 0xFFFAFA,                     // rgb(255,250,250)
+  spring_green = 0x00FF7F,             // rgb(0,255,127)
+  steel_blue = 0x4682B4,               // rgb(70,130,180)
+  tan = 0xD2B48C,                      // rgb(210,180,140)
+  teal = 0x008080,                     // rgb(0,128,128)
+  thistle = 0xD8BFD8,                  // rgb(216,191,216)
+  tomato = 0xFF6347,                   // rgb(255,99,71)
+  turquoise = 0x40E0D0,                // rgb(64,224,208)
+  violet = 0xEE82EE,                   // rgb(238,130,238)
+  wheat = 0xF5DEB3,                    // rgb(245,222,179)
+  white = 0xFFFFFF,                    // rgb(255,255,255)
+  white_smoke = 0xF5F5F5,              // rgb(245,245,245)
+  yellow = 0xFFFF00,                   // rgb(255,255,0)
+  yellow_green = 0x9ACD32              // rgb(154,205,50)
+};                                     // enum class color
+
+enum class terminal_color : uint8_t {
+  black = 30,
+  red,
+  green,
+  yellow,
+  blue,
+  magenta,
+  cyan,
+  white,
+  bright_black = 90,
+  bright_red,
+  bright_green,
+  bright_yellow,
+  bright_blue,
+  bright_magenta,
+  bright_cyan,
+  bright_white
+};
+
+enum class emphasis : uint8_t {
+  bold = 1,
+  italic = 1 << 1,
+  underline = 1 << 2,
+  strikethrough = 1 << 3
+};
+
+// rgb is a struct for red, green and blue colors.
+// Using the name "rgb" makes some editors show the color in a tooltip.
+struct rgb {
+  FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
+  FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
+  FMT_CONSTEXPR rgb(uint32_t hex)
+      : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
+  FMT_CONSTEXPR rgb(color hex)
+      : r((uint32_t(hex) >> 16) & 0xFF),
+        g((uint32_t(hex) >> 8) & 0xFF),
+        b(uint32_t(hex) & 0xFF) {}
+  uint8_t r;
+  uint8_t g;
+  uint8_t b;
+};
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+// color is a struct of either a rgb color or a terminal color.
+struct color_type {
+  FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
+  FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
+                                                           value{} {
+    value.rgb_color = static_cast<uint32_t>(rgb_color);
+  }
+  FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
+    value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
+                      (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
+  }
+  FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
+                                                                     value{} {
+    value.term_color = static_cast<uint8_t>(term_color);
+  }
+  bool is_rgb;
+  union color_union {
+    uint8_t term_color;
+    uint32_t rgb_color;
+  } value;
+};
+
+FMT_END_DETAIL_NAMESPACE
+
+/** A text style consisting of foreground and background colors and emphasis. */
+class text_style {
+ public:
+  FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
+      : set_foreground_color(),
+        set_background_color(),
+        ems(em) {}
+
+  FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
+    if (!set_foreground_color) {
+      set_foreground_color = rhs.set_foreground_color;
+      foreground_color = rhs.foreground_color;
+    } else if (rhs.set_foreground_color) {
+      if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
+        FMT_THROW(format_error("can't OR a terminal color"));
+      foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
+    }
+
+    if (!set_background_color) {
+      set_background_color = rhs.set_background_color;
+      background_color = rhs.background_color;
+    } else if (rhs.set_background_color) {
+      if (!background_color.is_rgb || !rhs.background_color.is_rgb)
+        FMT_THROW(format_error("can't OR a terminal color"));
+      background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
+    }
+
+    ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
+                                static_cast<uint8_t>(rhs.ems));
+    return *this;
+  }
+
+  friend FMT_CONSTEXPR text_style operator|(text_style lhs,
+                                            const text_style& rhs) {
+    return lhs |= rhs;
+  }
+
+  FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
+      const text_style& rhs) {
+    return and_assign(rhs);
+  }
+
+  FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
+  operator&(text_style lhs, const text_style& rhs) {
+    return lhs.and_assign(rhs);
+  }
+
+  FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
+    return set_foreground_color;
+  }
+  FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
+    return set_background_color;
+  }
+  FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
+    return static_cast<uint8_t>(ems) != 0;
+  }
+  FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
+    FMT_ASSERT(has_foreground(), "no foreground specified for this style");
+    return foreground_color;
+  }
+  FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
+    FMT_ASSERT(has_background(), "no background specified for this style");
+    return background_color;
+  }
+  FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
+    FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
+    return ems;
+  }
+
+ private:
+  FMT_CONSTEXPR text_style(bool is_foreground,
+                           detail::color_type text_color) FMT_NOEXCEPT
+      : set_foreground_color(),
+        set_background_color(),
+        ems() {
+    if (is_foreground) {
+      foreground_color = text_color;
+      set_foreground_color = true;
+    } else {
+      background_color = text_color;
+      set_background_color = true;
+    }
+  }
+
+  // DEPRECATED!
+  FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
+    if (!set_foreground_color) {
+      set_foreground_color = rhs.set_foreground_color;
+      foreground_color = rhs.foreground_color;
+    } else if (rhs.set_foreground_color) {
+      if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
+        FMT_THROW(format_error("can't AND a terminal color"));
+      foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
+    }
+
+    if (!set_background_color) {
+      set_background_color = rhs.set_background_color;
+      background_color = rhs.background_color;
+    } else if (rhs.set_background_color) {
+      if (!background_color.is_rgb || !rhs.background_color.is_rgb)
+        FMT_THROW(format_error("can't AND a terminal color"));
+      background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
+    }
+
+    ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
+                                static_cast<uint8_t>(rhs.ems));
+    return *this;
+  }
+
+  friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
+      FMT_NOEXCEPT;
+
+  friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
+      FMT_NOEXCEPT;
+
+  detail::color_type foreground_color;
+  detail::color_type background_color;
+  bool set_foreground_color;
+  bool set_background_color;
+  emphasis ems;
+};
+
+/** Creates a text style from the foreground (text) color. */
+FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
+  return text_style(true, foreground);
+}
+
+/** Creates a text style from the background color. */
+FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
+  return text_style(false, background);
+}
+
+FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
+                                          emphasis rhs) FMT_NOEXCEPT {
+  return text_style(lhs) | rhs;
+}
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+template <typename Char> struct ansi_color_escape {
+  FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
+                                  const char* esc) FMT_NOEXCEPT {
+    // If we have a terminal color, we need to output another escape code
+    // sequence.
+    if (!text_color.is_rgb) {
+      bool is_background = esc == string_view("\x1b[48;2;");
+      uint32_t value = text_color.value.term_color;
+      // Background ASCII codes are the same as the foreground ones but with
+      // 10 more.
+      if (is_background) value += 10u;
+
+      size_t index = 0;
+      buffer[index++] = static_cast<Char>('\x1b');
+      buffer[index++] = static_cast<Char>('[');
+
+      if (value >= 100u) {
+        buffer[index++] = static_cast<Char>('1');
+        value %= 100u;
+      }
+      buffer[index++] = static_cast<Char>('0' + value / 10u);
+      buffer[index++] = static_cast<Char>('0' + value % 10u);
+
+      buffer[index++] = static_cast<Char>('m');
+      buffer[index++] = static_cast<Char>('\0');
+      return;
+    }
+
+    for (int i = 0; i < 7; i++) {
+      buffer[i] = static_cast<Char>(esc[i]);
+    }
+    rgb color(text_color.value.rgb_color);
+    to_esc(color.r, buffer + 7, ';');
+    to_esc(color.g, buffer + 11, ';');
+    to_esc(color.b, buffer + 15, 'm');
+    buffer[19] = static_cast<Char>(0);
+  }
+  FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
+    uint8_t em_codes[4] = {};
+    uint8_t em_bits = static_cast<uint8_t>(em);
+    if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
+    if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
+    if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
+    if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
+      em_codes[3] = 9;
+
+    size_t index = 0;
+    for (int i = 0; i < 4; ++i) {
+      if (!em_codes[i]) continue;
+      buffer[index++] = static_cast<Char>('\x1b');
+      buffer[index++] = static_cast<Char>('[');
+      buffer[index++] = static_cast<Char>('0' + em_codes[i]);
+      buffer[index++] = static_cast<Char>('m');
+    }
+    buffer[index++] = static_cast<Char>(0);
+  }
+  FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
+
+  FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
+  FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
+    return buffer + std::char_traits<Char>::length(buffer);
+  }
+
+ private:
+  Char buffer[7u + 3u * 4u + 1u];
+
+  static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
+                                   char delimiter) FMT_NOEXCEPT {
+    out[0] = static_cast<Char>('0' + c / 100);
+    out[1] = static_cast<Char>('0' + c / 10 % 10);
+    out[2] = static_cast<Char>('0' + c % 10);
+    out[3] = static_cast<Char>(delimiter);
+  }
+};
+
+template <typename Char>
+FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
+    detail::color_type foreground) FMT_NOEXCEPT {
+  return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
+}
+
+template <typename Char>
+FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
+    detail::color_type background) FMT_NOEXCEPT {
+  return ansi_color_escape<Char>(background, "\x1b[48;2;");
+}
+
+template <typename Char>
+FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
+  return ansi_color_escape<Char>(em);
+}
+
+template <typename Char>
+inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
+  std::fputs(chars, stream);
+}
+
+template <>
+inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
+  std::fputws(chars, stream);
+}
+
+template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
+  fputs("\x1b[0m", stream);
+}
+
+template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
+  fputs(L"\x1b[0m", stream);
+}
+
+template <typename Char>
+inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
+  auto reset_color = string_view("\x1b[0m");
+  buffer.append(reset_color.begin(), reset_color.end());
+}
+
+template <typename Char>
+void vformat_to(buffer<Char>& buf, const text_style& ts,
+                basic_string_view<Char> format_str,
+                basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  bool has_style = false;
+  if (ts.has_emphasis()) {
+    has_style = true;
+    auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
+    buf.append(emphasis.begin(), emphasis.end());
+  }
+  if (ts.has_foreground()) {
+    has_style = true;
+    auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
+    buf.append(foreground.begin(), foreground.end());
+  }
+  if (ts.has_background()) {
+    has_style = true;
+    auto background = detail::make_background_color<Char>(ts.get_background());
+    buf.append(background.begin(), background.end());
+  }
+  detail::vformat_to(buf, format_str, args, {});
+  if (has_style) detail::reset_color<Char>(buf);
+}
+
+FMT_END_DETAIL_NAMESPACE
+
+template <typename S, typename Char = char_t<S>>
+void vprint(std::FILE* f, const text_style& ts, const S& format,
+            basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buf;
+  detail::vformat_to(buf, ts, to_string_view(format), args);
+  buf.push_back(Char(0));
+  detail::fputs(buf.data(), f);
+}
+
+/**
+  \rst
+  Formats a string and prints it to the specified file stream using ANSI
+  escape sequences to specify text formatting.
+
+  **Example**::
+
+    fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
+               "Elapsed time: {0:.2f} seconds", 1.23);
+  \endrst
+ */
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_string<S>::value)>
+void print(std::FILE* f, const text_style& ts, const S& format_str,
+           const Args&... args) {
+  vprint(f, ts, format_str,
+         fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+/**
+  \rst
+  Formats a string and prints it to stdout using ANSI escape sequences to
+  specify text formatting.
+
+  **Example**::
+
+    fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
+               "Elapsed time: {0:.2f} seconds", 1.23);
+  \endrst
+ */
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_string<S>::value)>
+void print(const text_style& ts, const S& format_str, const Args&... args) {
+  return print(stdout, ts, format_str, args...);
+}
+
+template <typename S, typename Char = char_t<S>>
+inline std::basic_string<Char> vformat(
+    const text_style& ts, const S& format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buf;
+  detail::vformat_to(buf, ts, to_string_view(format_str), args);
+  return fmt::to_string(buf);
+}
+
+/**
+  \rst
+  Formats arguments and returns the result as a string using ANSI
+  escape sequences to specify text formatting.
+
+  **Example**::
+
+    #include <fmt/color.h>
+    std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
+                                      "The answer is {}", 42);
+  \endrst
+*/
+template <typename S, typename... Args, typename Char = char_t<S>>
+inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
+                                      const Args&... args) {
+  return fmt::vformat(ts, to_string_view(format_str),
+                      fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+/**
+  Formats a string with the given text_style and writes the output to ``out``.
+ */
+template <typename OutputIt, typename Char,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
+OutputIt vformat_to(
+    OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  auto&& buf = detail::get_buffer<Char>(out);
+  detail::vformat_to(buf, ts, format_str, args);
+  return detail::get_iterator(buf);
+}
+
+/**
+  \rst
+  Formats arguments with the given text_style, writes the result to the output
+  iterator ``out`` and returns the iterator past the end of the output range.
+
+  **Example**::
+
+    std::vector<char> out;
+    fmt::format_to(std::back_inserter(out),
+                   fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
+  \endrst
+*/
+template <typename OutputIt, typename S, typename... Args,
+          bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
+              detail::is_string<S>::value>
+inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
+                      Args&&... args) ->
+    typename std::enable_if<enable, OutputIt>::type {
+  return vformat_to(out, ts, to_string_view(format_str),
+                    fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_COLOR_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/compile.h b/wpiutil/src/main/native/fmtlib/include/fmt/compile.h
new file mode 100644
index 0000000..00000c9
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/compile.h
@@ -0,0 +1,639 @@
+// Formatting library for C++ - experimental format string compilation
+//
+// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_COMPILE_H_
+#define FMT_COMPILE_H_
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+// An output iterator that counts the number of objects written to it and
+// discards them.
+class counting_iterator {
+ private:
+  size_t count_;
+
+ public:
+  using iterator_category = std::output_iterator_tag;
+  using difference_type = std::ptrdiff_t;
+  using pointer = void;
+  using reference = void;
+  using _Unchecked_type = counting_iterator;  // Mark iterator as checked.
+
+  struct value_type {
+    template <typename T> void operator=(const T&) {}
+  };
+
+  counting_iterator() : count_(0) {}
+
+  size_t count() const { return count_; }
+
+  counting_iterator& operator++() {
+    ++count_;
+    return *this;
+  }
+  counting_iterator operator++(int) {
+    auto it = *this;
+    ++*this;
+    return it;
+  }
+
+  friend counting_iterator operator+(counting_iterator it, difference_type n) {
+    it.count_ += static_cast<size_t>(n);
+    return it;
+  }
+
+  value_type operator*() const { return {}; }
+};
+
+template <typename Char, typename InputIt>
+inline counting_iterator copy_str(InputIt begin, InputIt end,
+                                  counting_iterator it) {
+  return it + (end - begin);
+}
+
+template <typename OutputIt> class truncating_iterator_base {
+ protected:
+  OutputIt out_;
+  size_t limit_;
+  size_t count_ = 0;
+
+  truncating_iterator_base() : out_(), limit_(0) {}
+
+  truncating_iterator_base(OutputIt out, size_t limit)
+      : out_(out), limit_(limit) {}
+
+ public:
+  using iterator_category = std::output_iterator_tag;
+  using value_type = typename std::iterator_traits<OutputIt>::value_type;
+  using difference_type = std::ptrdiff_t;
+  using pointer = void;
+  using reference = void;
+  using _Unchecked_type =
+      truncating_iterator_base;  // Mark iterator as checked.
+
+  OutputIt base() const { return out_; }
+  size_t count() const { return count_; }
+};
+
+// An output iterator that truncates the output and counts the number of objects
+// written to it.
+template <typename OutputIt,
+          typename Enable = typename std::is_void<
+              typename std::iterator_traits<OutputIt>::value_type>::type>
+class truncating_iterator;
+
+template <typename OutputIt>
+class truncating_iterator<OutputIt, std::false_type>
+    : public truncating_iterator_base<OutputIt> {
+  mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
+
+ public:
+  using value_type = typename truncating_iterator_base<OutputIt>::value_type;
+
+  truncating_iterator() = default;
+
+  truncating_iterator(OutputIt out, size_t limit)
+      : truncating_iterator_base<OutputIt>(out, limit) {}
+
+  truncating_iterator& operator++() {
+    if (this->count_++ < this->limit_) ++this->out_;
+    return *this;
+  }
+
+  truncating_iterator operator++(int) {
+    auto it = *this;
+    ++*this;
+    return it;
+  }
+
+  value_type& operator*() const {
+    return this->count_ < this->limit_ ? *this->out_ : blackhole_;
+  }
+};
+
+template <typename OutputIt>
+class truncating_iterator<OutputIt, std::true_type>
+    : public truncating_iterator_base<OutputIt> {
+ public:
+  truncating_iterator() = default;
+
+  truncating_iterator(OutputIt out, size_t limit)
+      : truncating_iterator_base<OutputIt>(out, limit) {}
+
+  template <typename T> truncating_iterator& operator=(T val) {
+    if (this->count_++ < this->limit_) *this->out_++ = val;
+    return *this;
+  }
+
+  truncating_iterator& operator++() { return *this; }
+  truncating_iterator& operator++(int) { return *this; }
+  truncating_iterator& operator*() { return *this; }
+};
+
+// A compile-time string which is compiled into fast formatting code.
+class compiled_string {};
+
+template <typename S>
+struct is_compiled_string : std::is_base_of<compiled_string, S> {};
+
+/**
+  \rst
+  Converts a string literal *s* into a format string that will be parsed at
+  compile time and converted into efficient formatting code. Requires C++17
+  ``constexpr if`` compiler support.
+
+  **Example**::
+
+    // Converts 42 into std::string using the most efficient method and no
+    // runtime format string processing.
+    std::string s = fmt::format(FMT_COMPILE("{}"), 42);
+  \endrst
+ */
+#ifdef __cpp_if_constexpr
+#  define FMT_COMPILE(s) \
+    FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
+#else
+#  define FMT_COMPILE(s) FMT_STRING(s)
+#endif
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <typename Char, size_t N,
+          fmt::detail_exported::fixed_string<Char, N> Str>
+struct udl_compiled_string : compiled_string {
+  using char_type = Char;
+  constexpr operator basic_string_view<char_type>() const {
+    return {Str.data, N - 1};
+  }
+};
+#endif
+
+template <typename T, typename... Tail>
+const T& first(const T& value, const Tail&...) {
+  return value;
+}
+
+#ifdef __cpp_if_constexpr
+template <typename... Args> struct type_list {};
+
+// Returns a reference to the argument at index N from [first, rest...].
+template <int N, typename T, typename... Args>
+constexpr const auto& get([[maybe_unused]] const T& first,
+                          [[maybe_unused]] const Args&... rest) {
+  static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
+  if constexpr (N == 0)
+    return first;
+  else
+    return get<N - 1>(rest...);
+}
+
+template <typename Char, typename... Args>
+constexpr int get_arg_index_by_name(basic_string_view<Char> name,
+                                    type_list<Args...>) {
+  return get_arg_index_by_name<Args...>(name);
+}
+
+template <int N, typename> struct get_type_impl;
+
+template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
+  using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
+};
+
+template <int N, typename T>
+using get_type = typename get_type_impl<N, T>::type;
+
+template <typename T> struct is_compiled_format : std::false_type {};
+
+template <typename Char> struct text {
+  basic_string_view<Char> data;
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&...) const {
+    return write<Char>(out, data);
+  }
+};
+
+template <typename Char>
+struct is_compiled_format<text<Char>> : std::true_type {};
+
+template <typename Char>
+constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
+                               size_t size) {
+  return {{&s[pos], size}};
+}
+
+template <typename Char> struct code_unit {
+  Char value;
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&...) const {
+    return write<Char>(out, value);
+  }
+};
+
+// This ensures that the argument type is convertible to `const T&`.
+template <typename T, int N, typename... Args>
+constexpr const T& get_arg_checked(const Args&... args) {
+  const auto& arg = get<N>(args...);
+  if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
+    return arg.value;
+  } else {
+    return arg;
+  }
+}
+
+template <typename Char>
+struct is_compiled_format<code_unit<Char>> : std::true_type {};
+
+// A replacement field that refers to argument N.
+template <typename Char, typename T, int N> struct field {
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&... args) const {
+    return write<Char>(out, get_arg_checked<T, N>(args...));
+  }
+};
+
+template <typename Char, typename T, int N>
+struct is_compiled_format<field<Char, T, N>> : std::true_type {};
+
+// A replacement field that refers to argument with name.
+template <typename Char> struct runtime_named_field {
+  using char_type = Char;
+  basic_string_view<Char> name;
+
+  template <typename OutputIt, typename T>
+  constexpr static bool try_format_argument(
+      OutputIt& out,
+      // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
+      [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
+    if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
+      if (arg_name == arg.name) {
+        out = write<Char>(out, arg.value);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&... args) const {
+    bool found = (try_format_argument(out, name, args) || ...);
+    if (!found) {
+      throw format_error("argument with specified name is not found");
+    }
+    return out;
+  }
+};
+
+template <typename Char>
+struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
+
+// A replacement field that refers to argument N and has format specifiers.
+template <typename Char, typename T, int N> struct spec_field {
+  using char_type = Char;
+  formatter<T, Char> fmt;
+
+  template <typename OutputIt, typename... Args>
+  constexpr FMT_INLINE OutputIt format(OutputIt out,
+                                       const Args&... args) const {
+    const auto& vargs =
+        fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
+    basic_format_context<OutputIt, Char> ctx(out, vargs);
+    return fmt.format(get_arg_checked<T, N>(args...), ctx);
+  }
+};
+
+template <typename Char, typename T, int N>
+struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
+
+template <typename L, typename R> struct concat {
+  L lhs;
+  R rhs;
+  using char_type = typename L::char_type;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&... args) const {
+    out = lhs.format(out, args...);
+    return rhs.format(out, args...);
+  }
+};
+
+template <typename L, typename R>
+struct is_compiled_format<concat<L, R>> : std::true_type {};
+
+template <typename L, typename R>
+constexpr concat<L, R> make_concat(L lhs, R rhs) {
+  return {lhs, rhs};
+}
+
+struct unknown_format {};
+
+template <typename Char>
+constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
+  for (size_t size = str.size(); pos != size; ++pos) {
+    if (str[pos] == '{' || str[pos] == '}') break;
+  }
+  return pos;
+}
+
+template <typename Args, size_t POS, int ID, typename S>
+constexpr auto compile_format_string(S format_str);
+
+template <typename Args, size_t POS, int ID, typename T, typename S>
+constexpr auto parse_tail(T head, S format_str) {
+  if constexpr (POS !=
+                basic_string_view<typename S::char_type>(format_str).size()) {
+    constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
+    if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
+                               unknown_format>())
+      return tail;
+    else
+      return make_concat(head, tail);
+  } else {
+    return head;
+  }
+}
+
+template <typename T, typename Char> struct parse_specs_result {
+  formatter<T, Char> fmt;
+  size_t end;
+  int next_arg_id;
+};
+
+constexpr int manual_indexing_id = -1;
+
+template <typename T, typename Char>
+constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
+                                                  size_t pos, int next_arg_id) {
+  str.remove_prefix(pos);
+  auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
+  auto f = formatter<T, Char>();
+  auto end = f.parse(ctx);
+  return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
+          next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
+}
+
+template <typename Char> struct arg_id_handler {
+  arg_ref<Char> arg_id;
+
+  constexpr int operator()() {
+    FMT_ASSERT(false, "handler cannot be used with automatic indexing");
+    return 0;
+  }
+  constexpr int operator()(int id) {
+    arg_id = arg_ref<Char>(id);
+    return 0;
+  }
+  constexpr int operator()(basic_string_view<Char> id) {
+    arg_id = arg_ref<Char>(id);
+    return 0;
+  }
+
+  constexpr void on_error(const char* message) { throw format_error(message); }
+};
+
+template <typename Char> struct parse_arg_id_result {
+  arg_ref<Char> arg_id;
+  const Char* arg_id_end;
+};
+
+template <int ID, typename Char>
+constexpr auto parse_arg_id(const Char* begin, const Char* end) {
+  auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
+  auto arg_id_end = parse_arg_id(begin, end, handler);
+  return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
+}
+
+template <typename T, typename Enable = void> struct field_type {
+  using type = remove_cvref_t<T>;
+};
+
+template <typename T>
+struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
+  using type = remove_cvref_t<decltype(T::value)>;
+};
+
+template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
+          typename S>
+constexpr auto parse_replacement_field_then_tail(S format_str) {
+  using char_type = typename S::char_type;
+  constexpr auto str = basic_string_view<char_type>(format_str);
+  constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
+  if constexpr (c == '}') {
+    return parse_tail<Args, END_POS + 1, NEXT_ID>(
+        field<char_type, typename field_type<T>::type, ARG_INDEX>(),
+        format_str);
+  } else if constexpr (c == ':') {
+    constexpr auto result = parse_specs<typename field_type<T>::type>(
+        str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
+    return parse_tail<Args, result.end, result.next_arg_id>(
+        spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
+            result.fmt},
+        format_str);
+  }
+}
+
+// Compiles a non-empty format string and returns the compiled representation
+// or unknown_format() on unrecognized input.
+template <typename Args, size_t POS, int ID, typename S>
+constexpr auto compile_format_string(S format_str) {
+  using char_type = typename S::char_type;
+  constexpr auto str = basic_string_view<char_type>(format_str);
+  if constexpr (str[POS] == '{') {
+    if constexpr (POS + 1 == str.size())
+      throw format_error("unmatched '{' in format string");
+    if constexpr (str[POS + 1] == '{') {
+      return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
+    } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
+      static_assert(ID != manual_indexing_id,
+                    "cannot switch from manual to automatic argument indexing");
+      constexpr auto next_id =
+          ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
+      return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
+                                               POS + 1, ID, next_id>(
+          format_str);
+    } else {
+      constexpr auto arg_id_result =
+          parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
+      constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
+      constexpr char_type c =
+          arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
+      static_assert(c == '}' || c == ':', "missing '}' in format string");
+      if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
+        static_assert(
+            ID == manual_indexing_id || ID == 0,
+            "cannot switch from automatic to manual argument indexing");
+        constexpr auto arg_index = arg_id_result.arg_id.val.index;
+        return parse_replacement_field_then_tail<get_type<arg_index, Args>,
+                                                 Args, arg_id_end_pos,
+                                                 arg_index, manual_indexing_id>(
+            format_str);
+      } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
+        constexpr auto arg_index =
+            get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
+        if constexpr (arg_index != invalid_arg_index) {
+          constexpr auto next_id =
+              ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
+          return parse_replacement_field_then_tail<
+              decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
+              arg_index, next_id>(format_str);
+        } else {
+          if constexpr (c == '}') {
+            return parse_tail<Args, arg_id_end_pos + 1, ID>(
+                runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
+                format_str);
+          } else if constexpr (c == ':') {
+            return unknown_format();  // no type info for specs parsing
+          }
+        }
+      }
+    }
+  } else if constexpr (str[POS] == '}') {
+    if constexpr (POS + 1 == str.size())
+      throw format_error("unmatched '}' in format string");
+    return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
+  } else {
+    constexpr auto end = parse_text(str, POS + 1);
+    if constexpr (end - POS > 1) {
+      return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
+                                       format_str);
+    } else {
+      return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
+                                       format_str);
+    }
+  }
+}
+
+template <typename... Args, typename S,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+constexpr auto compile(S format_str) {
+  constexpr auto str = basic_string_view<typename S::char_type>(format_str);
+  if constexpr (str.size() == 0) {
+    return detail::make_text(str, 0, 0);
+  } else {
+    constexpr auto result =
+        detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
+            format_str);
+    return result;
+  }
+}
+#endif  // __cpp_if_constexpr
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+#ifdef __cpp_if_constexpr
+
+template <typename CompiledFormat, typename... Args,
+          typename Char = typename CompiledFormat::char_type,
+          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
+FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
+                                          const Args&... args) {
+  auto s = std::basic_string<Char>();
+  cf.format(std::back_inserter(s), args...);
+  return s;
+}
+
+template <typename OutputIt, typename CompiledFormat, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
+constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
+                                        const Args&... args) {
+  return cf.format(out, args...);
+}
+
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
+                                                           Args&&... args) {
+  if constexpr (std::is_same<typename S::char_type, char>::value) {
+    constexpr auto str = basic_string_view<typename S::char_type>(S());
+    if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
+      const auto& first = detail::first(args...);
+      if constexpr (detail::is_named_arg<
+                        remove_cvref_t<decltype(first)>>::value) {
+        return fmt::to_string(first.value);
+      } else {
+        return fmt::to_string(first);
+      }
+    }
+  }
+  constexpr auto compiled = detail::compile<Args...>(S());
+  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
+                             detail::unknown_format>()) {
+    return format(static_cast<basic_string_view<typename S::char_type>>(S()),
+                  std::forward<Args>(args)...);
+  } else {
+    return format(compiled, std::forward<Args>(args)...);
+  }
+}
+
+template <typename OutputIt, typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
+  constexpr auto compiled = detail::compile<Args...>(S());
+  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
+                             detail::unknown_format>()) {
+    return format_to(out,
+                     static_cast<basic_string_view<typename S::char_type>>(S()),
+                     std::forward<Args>(args)...);
+  } else {
+    return format_to(out, compiled, std::forward<Args>(args)...);
+  }
+}
+#endif
+
+template <typename OutputIt, typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
+                                         const S& format_str, Args&&... args) {
+  auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
+                      std::forward<Args>(args)...);
+  return {it.base(), it.count()};
+}
+
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+size_t formatted_size(const S& format_str, const Args&... args) {
+  return format_to(detail::counting_iterator(), format_str, args...).count();
+}
+
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+void print(std::FILE* f, const S& format_str, const Args&... args) {
+  memory_buffer buffer;
+  format_to(std::back_inserter(buffer), format_str, args...);
+  detail::print(f, {buffer.data(), buffer.size()});
+}
+
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+void print(const S& format_str, const Args&... args) {
+  print(stdout, format_str, args...);
+}
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+inline namespace literals {
+template <detail_exported::fixed_string Str>
+constexpr detail::udl_compiled_string<
+    remove_cvref_t<decltype(Str.data[0])>,
+    sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
+operator""_cf() {
+  return {};
+}
+}  // namespace literals
+#endif
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_COMPILE_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/core.h b/wpiutil/src/main/native/fmtlib/include/fmt/core.h
new file mode 100644
index 0000000..d058398
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/core.h
@@ -0,0 +1,3002 @@
+// Formatting library for C++ - the core API for char/UTF-8
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_CORE_H_
+#define FMT_CORE_H_
+
+#include <cstdio>  // std::FILE
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <string>
+#include <type_traits>
+
+// The fmt library version in the form major * 10000 + minor * 100 + patch.
+#define FMT_VERSION 80001
+
+#ifdef __clang__
+#  define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
+#else
+#  define FMT_CLANG_VERSION 0
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
+#  define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+#  define FMT_GCC_PRAGMA(arg) _Pragma(arg)
+#else
+#  define FMT_GCC_VERSION 0
+#  define FMT_GCC_PRAGMA(arg)
+#endif
+
+#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
+#  define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION
+#else
+#  define FMT_HAS_GXX_CXX11 0
+#endif
+
+#if defined(__INTEL_COMPILER)
+#  define FMT_ICC_VERSION __INTEL_COMPILER
+#else
+#  define FMT_ICC_VERSION 0
+#endif
+
+#ifdef __NVCC__
+#  define FMT_NVCC __NVCC__
+#else
+#  define FMT_NVCC 0
+#endif
+
+#ifdef _MSC_VER
+#  define FMT_MSC_VER _MSC_VER
+#  define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__))
+#else
+#  define FMT_MSC_VER 0
+#  define FMT_MSC_WARNING(...)
+#endif
+
+#ifdef __has_feature
+#  define FMT_HAS_FEATURE(x) __has_feature(x)
+#else
+#  define FMT_HAS_FEATURE(x) 0
+#endif
+
+#if defined(__has_include) &&                             \
+    (!defined(__INTELLISENSE__) || FMT_MSC_VER > 1900) && \
+    (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600)
+#  define FMT_HAS_INCLUDE(x) __has_include(x)
+#else
+#  define FMT_HAS_INCLUDE(x) 0
+#endif
+
+#ifdef __has_cpp_attribute
+#  define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+#  define FMT_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+
+#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \
+  (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+
+#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \
+  (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+
+// Check if relaxed C++14 constexpr is supported.
+// GCC doesn't allow throw in constexpr until version 6 (bug 67371).
+#ifndef FMT_USE_CONSTEXPR
+#  define FMT_USE_CONSTEXPR                                           \
+    (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \
+     (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) &&           \
+        !FMT_NVCC && !FMT_ICC_VERSION
+#endif
+#if FMT_USE_CONSTEXPR
+#  define FMT_CONSTEXPR constexpr
+#  define FMT_CONSTEXPR_DECL constexpr
+#else
+#  define FMT_CONSTEXPR
+#  define FMT_CONSTEXPR_DECL
+#endif
+
+// Check if constexpr std::char_traits<>::compare,length is supported.
+#if defined(__GLIBCXX__)
+#  if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \
+      _GLIBCXX_RELEASE >= 7  // GCC 7+ libstdc++ has _GLIBCXX_RELEASE.
+#    define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#  endif
+#elif defined(_LIBCPP_VERSION) && __cplusplus >= 201703L && \
+    _LIBCPP_VERSION >= 4000
+#  define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#elif FMT_MSC_VER >= 1914 && _MSVC_LANG >= 201703L
+#  define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#endif
+#ifndef FMT_CONSTEXPR_CHAR_TRAITS
+#  define FMT_CONSTEXPR_CHAR_TRAITS
+#endif
+
+#ifndef FMT_OVERRIDE
+#  if FMT_HAS_FEATURE(cxx_override_control) || \
+      (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
+#    define FMT_OVERRIDE override
+#  else
+#    define FMT_OVERRIDE
+#  endif
+#endif
+
+// Check if exceptions are disabled.
+#ifndef FMT_EXCEPTIONS
+#  if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \
+      FMT_MSC_VER && !_HAS_EXCEPTIONS
+#    define FMT_EXCEPTIONS 0
+#  else
+#    define FMT_EXCEPTIONS 1
+#  endif
+#endif
+
+// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature).
+#ifndef FMT_USE_NOEXCEPT
+#  define FMT_USE_NOEXCEPT 0
+#endif
+
+#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \
+    (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
+#  define FMT_DETECTED_NOEXCEPT noexcept
+#  define FMT_HAS_CXX11_NOEXCEPT 1
+#else
+#  define FMT_DETECTED_NOEXCEPT throw()
+#  define FMT_HAS_CXX11_NOEXCEPT 0
+#endif
+
+#ifndef FMT_NOEXCEPT
+#  if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT
+#    define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT
+#  else
+#    define FMT_NOEXCEPT
+#  endif
+#endif
+
+// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code
+// warnings.
+#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VER && \
+    !FMT_NVCC
+#  define FMT_NORETURN [[noreturn]]
+#else
+#  define FMT_NORETURN
+#endif
+
+#ifndef FMT_MAYBE_UNUSED
+#  if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused)
+#    define FMT_MAYBE_UNUSED [[maybe_unused]]
+#  else
+#    define FMT_MAYBE_UNUSED
+#  endif
+#endif
+
+#if __cplusplus == 201103L || __cplusplus == 201402L
+#  if defined(__INTEL_COMPILER) || defined(__PGI)
+#    define FMT_FALLTHROUGH
+#  elif defined(__clang__)
+#    define FMT_FALLTHROUGH [[clang::fallthrough]]
+#  elif FMT_GCC_VERSION >= 700 && \
+      (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520)
+#    define FMT_FALLTHROUGH [[gnu::fallthrough]]
+#  else
+#    define FMT_FALLTHROUGH
+#  endif
+#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \
+    (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#  define FMT_FALLTHROUGH [[fallthrough]]
+#else
+#  define FMT_FALLTHROUGH
+#endif
+
+#ifndef FMT_USE_FLOAT
+#  define FMT_USE_FLOAT 1
+#endif
+#ifndef FMT_USE_DOUBLE
+#  define FMT_USE_DOUBLE 1
+#endif
+#ifndef FMT_USE_LONG_DOUBLE
+#  define FMT_USE_LONG_DOUBLE 1
+#endif
+
+#ifndef FMT_INLINE
+#  if FMT_GCC_VERSION || FMT_CLANG_VERSION
+#    define FMT_INLINE inline __attribute__((always_inline))
+#  else
+#    define FMT_INLINE inline
+#  endif
+#endif
+
+#ifndef FMT_USE_INLINE_NAMESPACES
+#  if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
+      (FMT_MSC_VER >= 1900 && (!defined(_MANAGED) || !_MANAGED))
+#    define FMT_USE_INLINE_NAMESPACES 1
+#  else
+#    define FMT_USE_INLINE_NAMESPACES 0
+#  endif
+#endif
+
+#ifndef FMT_BEGIN_NAMESPACE
+#  if FMT_USE_INLINE_NAMESPACES
+#    define FMT_INLINE_NAMESPACE inline namespace
+#    define FMT_END_NAMESPACE \
+      }                       \
+      }
+#  else
+#    define FMT_INLINE_NAMESPACE namespace
+#    define FMT_END_NAMESPACE \
+      }                       \
+      using namespace v8;     \
+      }
+#  endif
+#  define FMT_BEGIN_NAMESPACE \
+    namespace fmt {           \
+    FMT_INLINE_NAMESPACE v8 {
+#endif
+
+#ifndef FMT_MODULE_EXPORT
+#  define FMT_MODULE_EXPORT
+#  define FMT_MODULE_EXPORT_BEGIN
+#  define FMT_MODULE_EXPORT_END
+#  define FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
+#  define FMT_END_DETAIL_NAMESPACE }
+#endif
+
+#if !defined(FMT_HEADER_ONLY) && defined(_WIN32)
+#  define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275)
+#  ifdef FMT_EXPORT
+#    define FMT_API __declspec(dllexport)
+#  elif defined(FMT_SHARED)
+#    define FMT_API __declspec(dllimport)
+#  endif
+#else
+#  define FMT_CLASS_API
+#  if defined(FMT_EXPORT) || defined(FMT_SHARED)
+#    if defined(__GNUC__) || defined(__clang__)
+#      define FMT_API __attribute__((visibility("default")))
+#    endif
+#  endif
+#endif
+#ifndef FMT_API
+#  define FMT_API
+#endif
+
+#if FMT_GCC_VERSION
+#  define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden")))
+#else
+#  define FMT_GCC_VISIBILITY_HIDDEN
+#endif
+
+// libc++ supports string_view in pre-c++17.
+#if (FMT_HAS_INCLUDE(<string_view>) &&                       \
+     (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \
+    (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)
+#  include <string_view>
+#  define FMT_USE_STRING_VIEW
+#elif FMT_HAS_INCLUDE("experimental/string_view") && __cplusplus >= 201402L
+#  include <experimental/string_view>
+#  define FMT_USE_EXPERIMENTAL_STRING_VIEW
+#endif
+
+#ifndef FMT_UNICODE
+#  define FMT_UNICODE !FMT_MSC_VER
+#endif
+
+#ifndef FMT_CONSTEVAL
+#  if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \
+       __cplusplus > 201703L) ||                                 \
+      (defined(__cpp_consteval) &&                               \
+       !FMT_MSC_VER)  // consteval is broken in MSVC.
+#    define FMT_CONSTEVAL consteval
+#    define FMT_HAS_CONSTEVAL
+#  else
+#    define FMT_CONSTEVAL
+#  endif
+#endif
+
+#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+#  if defined(__cpp_nontype_template_args) &&                \
+      ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \
+       __cpp_nontype_template_args >= 201911L)
+#    define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1
+#  else
+#    define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0
+#  endif
+#endif
+
+// Enable minimal optimizations for more compact code in debug mode.
+FMT_GCC_PRAGMA("GCC push_options")
+#ifndef __OPTIMIZE__
+FMT_GCC_PRAGMA("GCC optimize(\"Og\")")
+#endif
+
+FMT_BEGIN_NAMESPACE
+FMT_MODULE_EXPORT_BEGIN
+
+// Implementations of enable_if_t and other metafunctions for older systems.
+template <bool B, typename T = void>
+using enable_if_t = typename std::enable_if<B, T>::type;
+template <bool B, typename T, typename F>
+using conditional_t = typename std::conditional<B, T, F>::type;
+template <bool B> using bool_constant = std::integral_constant<bool, B>;
+template <typename T>
+using remove_reference_t = typename std::remove_reference<T>::type;
+template <typename T>
+using remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type;
+template <typename T> struct type_identity { using type = T; };
+template <typename T> using type_identity_t = typename type_identity<T>::type;
+
+struct monostate {
+  constexpr monostate() {}
+};
+
+// Suppress "unused variable" warnings with the method described in
+// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
+// (void)var does not work on many Intel compilers.
+template <typename... T> FMT_CONSTEXPR void ignore_unused(const T&...) {}
+
+// An enable_if helper to be used in template parameters which results in much
+// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed
+// to workaround a bug in MSVC 2019 (see #1140 and #1186).
+#ifdef FMT_DOC
+#  define FMT_ENABLE_IF(...)
+#else
+#  define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0
+#endif
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+constexpr FMT_INLINE auto is_constant_evaluated() FMT_NOEXCEPT -> bool {
+#ifdef __cpp_lib_is_constant_evaluated
+  return std::is_constant_evaluated();
+#else
+  return false;
+#endif
+}
+
+// A function to suppress "conditional expression is constant" warnings.
+template <typename T> constexpr auto const_check(T value) -> T { return value; }
+
+FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
+                                      const char* message);
+
+#ifndef FMT_ASSERT
+#  ifdef NDEBUG
+// FMT_ASSERT is not empty to avoid -Werror=empty-body.
+#    define FMT_ASSERT(condition, message) \
+      ::fmt::ignore_unused((condition), (message))
+#  else
+#    define FMT_ASSERT(condition, message)                                    \
+      ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
+           ? (void)0                                                          \
+           : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message)))
+#  endif
+#endif
+
+#if defined(FMT_USE_STRING_VIEW)
+template <typename Char> using std_string_view = std::basic_string_view<Char>;
+#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW)
+template <typename Char>
+using std_string_view = std::experimental::basic_string_view<Char>;
+#else
+template <typename T> struct std_string_view {};
+#endif
+
+#ifdef FMT_USE_INT128
+// Do nothing.
+#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \
+    !(FMT_CLANG_VERSION && FMT_MSC_VER)
+#  define FMT_USE_INT128 1
+using int128_t = __int128_t;
+using uint128_t = __uint128_t;
+template <typename T> inline auto convert_for_visit(T value) -> T {
+  return value;
+}
+#else
+#  define FMT_USE_INT128 0
+#endif
+#if !FMT_USE_INT128
+enum class int128_t {};
+enum class uint128_t {};
+// Reduce template instantiations.
+template <typename T> inline auto convert_for_visit(T) -> monostate {
+  return {};
+}
+#endif
+
+// Casts a nonnegative integer to unsigned.
+template <typename Int>
+FMT_CONSTEXPR auto to_unsigned(Int value) ->
+    typename std::make_unsigned<Int>::type {
+  FMT_ASSERT(value >= 0, "negative value");
+  return static_cast<typename std::make_unsigned<Int>::type>(value);
+}
+
+FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5";
+
+constexpr auto is_utf8() -> bool {
+  // Avoid buggy sign extensions in MSVC's constant evaluation mode.
+  // https://developercommunity.visualstudio.com/t/C-difference-in-behavior-for-unsigned/1233612
+  using uchar = unsigned char;
+  return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 &&
+                         uchar(micro[1]) == 0xB5);
+}
+FMT_END_DETAIL_NAMESPACE
+
+/**
+  An implementation of ``std::basic_string_view`` for pre-C++17. It provides a
+  subset of the API. ``fmt::basic_string_view`` is used for format strings even
+  if ``std::string_view`` is available to prevent issues when a library is
+  compiled with a different ``-std`` option than the client code (which is not
+  recommended).
+ */
+template <typename Char> class basic_string_view {
+ private:
+  const Char* data_;
+  size_t size_;
+
+ public:
+  using value_type = Char;
+  using iterator = const Char*;
+
+  constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {}
+
+  /** Constructs a string reference object from a C string and a size. */
+  constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT
+      : data_(s),
+        size_(count) {}
+
+  /**
+    \rst
+    Constructs a string reference object from a C string computing
+    the size with ``std::char_traits<Char>::length``.
+    \endrst
+   */
+  FMT_CONSTEXPR_CHAR_TRAITS
+  FMT_INLINE
+  basic_string_view(const Char* s) : data_(s) {
+    if (detail::const_check(std::is_same<Char, char>::value &&
+                            !detail::is_constant_evaluated()))
+      size_ = std::strlen(reinterpret_cast<const char*>(s));
+    else
+      size_ = std::char_traits<Char>::length(s);
+  }
+
+  /** Constructs a string reference from a ``std::basic_string`` object. */
+  template <typename Traits, typename Alloc>
+  FMT_CONSTEXPR basic_string_view(
+      const std::basic_string<Char, Traits, Alloc>& s) FMT_NOEXCEPT
+      : data_(s.data()),
+        size_(s.size()) {}
+
+  template <typename S, FMT_ENABLE_IF(std::is_same<
+                                      S, detail::std_string_view<Char>>::value)>
+  FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()),
+                                                      size_(s.size()) {}
+
+  /** Returns a pointer to the string data. */
+  constexpr auto data() const -> const Char* { return data_; }
+
+  /** Returns the string size. */
+  constexpr auto size() const -> size_t { return size_; }
+
+  constexpr auto begin() const -> iterator { return data_; }
+  constexpr auto end() const -> iterator { return data_ + size_; }
+
+  constexpr auto operator[](size_t pos) const -> const Char& {
+    return data_[pos];
+  }
+
+  FMT_CONSTEXPR void remove_prefix(size_t n) {
+    data_ += n;
+    size_ -= n;
+  }
+
+  // Lexicographically compare this string reference to other.
+  FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int {
+    size_t str_size = size_ < other.size_ ? size_ : other.size_;
+    int result = std::char_traits<Char>::compare(data_, other.data_, str_size);
+    if (result == 0)
+      result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1);
+    return result;
+  }
+
+  FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs,
+                                                   basic_string_view rhs)
+      -> bool {
+    return lhs.compare(rhs) == 0;
+  }
+  friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool {
+    return lhs.compare(rhs) != 0;
+  }
+  friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool {
+    return lhs.compare(rhs) < 0;
+  }
+  friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool {
+    return lhs.compare(rhs) <= 0;
+  }
+  friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool {
+    return lhs.compare(rhs) > 0;
+  }
+  friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool {
+    return lhs.compare(rhs) >= 0;
+  }
+};
+
+using string_view = basic_string_view<char>;
+
+/** Specifies if ``T`` is a character type. Can be specialized by users. */
+template <typename T> struct is_char : std::false_type {};
+template <> struct is_char<char> : std::true_type {};
+
+// Returns a string view of `s`.
+template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)>
+FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view<Char> {
+  return s;
+}
+template <typename Char, typename Traits, typename Alloc>
+inline auto to_string_view(const std::basic_string<Char, Traits, Alloc>& s)
+    -> basic_string_view<Char> {
+  return s;
+}
+template <typename Char>
+constexpr auto to_string_view(basic_string_view<Char> s)
+    -> basic_string_view<Char> {
+  return s;
+}
+template <typename Char,
+          FMT_ENABLE_IF(!std::is_empty<detail::std_string_view<Char>>::value)>
+inline auto to_string_view(detail::std_string_view<Char> s)
+    -> basic_string_view<Char> {
+  return s;
+}
+
+// A base class for compile-time strings. It is defined in the fmt namespace to
+// make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42).
+struct compile_string {};
+
+template <typename S>
+struct is_compile_string : std::is_base_of<compile_string, S> {};
+
+template <typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
+constexpr auto to_string_view(const S& s)
+    -> basic_string_view<typename S::char_type> {
+  return basic_string_view<typename S::char_type>(s);
+}
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+void to_string_view(...);
+using fmt::v8::to_string_view;
+
+// Specifies whether S is a string type convertible to fmt::basic_string_view.
+// It should be a constexpr function but MSVC 2017 fails to compile it in
+// enable_if and MSVC 2015 fails to compile it as an alias template.
+template <typename S>
+struct is_string : std::is_class<decltype(to_string_view(std::declval<S>()))> {
+};
+
+template <typename S, typename = void> struct char_t_impl {};
+template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> {
+  using result = decltype(to_string_view(std::declval<S>()));
+  using type = typename result::value_type;
+};
+
+// Reports a compile-time error if S is not a valid format string.
+template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
+FMT_INLINE void check_format_string(const S&) {
+#ifdef FMT_ENFORCE_COMPILE_STRING
+  static_assert(is_compile_string<S>::value,
+                "FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
+                "FMT_STRING.");
+#endif
+}
+template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
+void check_format_string(S);
+
+struct error_handler {
+  constexpr error_handler() = default;
+  constexpr error_handler(const error_handler&) = default;
+
+  // This function is intentionally not constexpr to give a compile-time error.
+  FMT_NORETURN FMT_API void on_error(const char* message);
+};
+FMT_END_DETAIL_NAMESPACE
+
+/** String's character type. */
+template <typename S> using char_t = typename detail::char_t_impl<S>::type;
+
+/**
+  \rst
+  Parsing context consisting of a format string range being parsed and an
+  argument counter for automatic indexing.
+  You can use the ``format_parse_context`` type alias for ``char`` instead.
+  \endrst
+ */
+template <typename Char, typename ErrorHandler = detail::error_handler>
+class basic_format_parse_context : private ErrorHandler {
+ private:
+  basic_string_view<Char> format_str_;
+  int next_arg_id_;
+
+ public:
+  using char_type = Char;
+  using iterator = typename basic_string_view<Char>::iterator;
+
+  explicit constexpr basic_format_parse_context(
+      basic_string_view<Char> format_str, ErrorHandler eh = {},
+      int next_arg_id = 0)
+      : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {}
+
+  /**
+    Returns an iterator to the beginning of the format string range being
+    parsed.
+   */
+  constexpr auto begin() const FMT_NOEXCEPT -> iterator {
+    return format_str_.begin();
+  }
+
+  /**
+    Returns an iterator past the end of the format string range being parsed.
+   */
+  constexpr auto end() const FMT_NOEXCEPT -> iterator {
+    return format_str_.end();
+  }
+
+  /** Advances the begin iterator to ``it``. */
+  FMT_CONSTEXPR void advance_to(iterator it) {
+    format_str_.remove_prefix(detail::to_unsigned(it - begin()));
+  }
+
+  /**
+    Reports an error if using the manual argument indexing; otherwise returns
+    the next argument index and switches to the automatic indexing.
+   */
+  FMT_CONSTEXPR auto next_arg_id() -> int {
+    // Don't check if the argument id is valid to avoid overhead and because it
+    // will be checked during formatting anyway.
+    if (next_arg_id_ >= 0) return next_arg_id_++;
+    on_error("cannot switch from manual to automatic argument indexing");
+    return 0;
+  }
+
+  /**
+    Reports an error if using the automatic argument indexing; otherwise
+    switches to the manual indexing.
+   */
+  FMT_CONSTEXPR void check_arg_id(int) {
+    if (next_arg_id_ > 0)
+      on_error("cannot switch from automatic to manual argument indexing");
+    else
+      next_arg_id_ = -1;
+  }
+
+  FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {}
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    ErrorHandler::on_error(message);
+  }
+
+  constexpr auto error_handler() const -> ErrorHandler { return *this; }
+};
+
+using format_parse_context = basic_format_parse_context<char>;
+
+template <typename Context> class basic_format_arg;
+template <typename Context> class basic_format_args;
+template <typename Context> class dynamic_format_arg_store;
+
+// A formatter for objects of type T.
+template <typename T, typename Char = char, typename Enable = void>
+struct formatter {
+  // A deleted default constructor indicates a disabled formatter.
+  formatter() = delete;
+};
+
+// Specifies if T has an enabled formatter specialization. A type can be
+// formattable even if it doesn't have a formatter e.g. via a conversion.
+template <typename T, typename Context>
+using has_formatter =
+    std::is_constructible<typename Context::template formatter_type<T>>;
+
+// Checks whether T is a container with contiguous storage.
+template <typename T> struct is_contiguous : std::false_type {};
+template <typename Char>
+struct is_contiguous<std::basic_string<Char>> : std::true_type {};
+
+class appender;
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+// Extracts a reference to the container from back_insert_iterator.
+template <typename Container>
+inline auto get_container(std::back_insert_iterator<Container> it)
+    -> Container& {
+  using bi_iterator = std::back_insert_iterator<Container>;
+  struct accessor : bi_iterator {
+    accessor(bi_iterator iter) : bi_iterator(iter) {}
+    using bi_iterator::container;
+  };
+  return *accessor(it).container;
+}
+
+template <typename Char, typename InputIt, typename OutputIt>
+FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out)
+    -> OutputIt {
+  while (begin != end) *out++ = static_cast<Char>(*begin++);
+  return out;
+}
+
+template <typename Char, FMT_ENABLE_IF(std::is_same<Char, char>::value)>
+FMT_CONSTEXPR auto copy_str(const Char* begin, const Char* end, Char* out)
+    -> Char* {
+  if (is_constant_evaluated())
+    return copy_str<Char, const Char*, Char*>(begin, end, out);
+  auto size = to_unsigned(end - begin);
+  memcpy(out, begin, size);
+  return out + size;
+}
+
+/**
+  \rst
+  A contiguous memory buffer with an optional growing ability. It is an internal
+  class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`.
+  \endrst
+ */
+template <typename T> class buffer {
+ private:
+  T* ptr_;
+  size_t size_;
+  size_t capacity_;
+
+ protected:
+  // Don't initialize ptr_ since it is not accessed to save a few cycles.
+  FMT_MSC_WARNING(suppress : 26495)
+  buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {}
+
+  buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT
+      : ptr_(p),
+        size_(sz),
+        capacity_(cap) {}
+
+  ~buffer() = default;
+  buffer(buffer&&) = default;
+
+  /** Sets the buffer data and capacity. */
+  void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
+    ptr_ = buf_data;
+    capacity_ = buf_capacity;
+  }
+
+  /** Increases the buffer capacity to hold at least *capacity* elements. */
+  virtual void grow(size_t capacity) = 0;
+
+ public:
+  using value_type = T;
+  using const_reference = const T&;
+
+  buffer(const buffer&) = delete;
+  void operator=(const buffer&) = delete;
+
+  auto begin() FMT_NOEXCEPT -> T* { return ptr_; }
+  auto end() FMT_NOEXCEPT -> T* { return ptr_ + size_; }
+
+  auto begin() const FMT_NOEXCEPT -> const T* { return ptr_; }
+  auto end() const FMT_NOEXCEPT -> const T* { return ptr_ + size_; }
+
+  /** Returns the size of this buffer. */
+  auto size() const FMT_NOEXCEPT -> size_t { return size_; }
+
+  /** Returns the capacity of this buffer. */
+  auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; }
+
+  /** Returns a pointer to the buffer data. */
+  auto data() FMT_NOEXCEPT -> T* { return ptr_; }
+
+  /** Returns a pointer to the buffer data. */
+  auto data() const FMT_NOEXCEPT -> const T* { return ptr_; }
+
+  /** Clears this buffer. */
+  void clear() { size_ = 0; }
+
+  // Tries resizing the buffer to contain *count* elements. If T is a POD type
+  // the new elements may not be initialized.
+  void try_resize(size_t count) {
+    try_reserve(count);
+    size_ = count <= capacity_ ? count : capacity_;
+  }
+
+  // Tries increasing the buffer capacity to *new_capacity*. It can increase the
+  // capacity by a smaller amount than requested but guarantees there is space
+  // for at least one additional element either by increasing the capacity or by
+  // flushing the buffer if it is full.
+  void try_reserve(size_t new_capacity) {
+    if (new_capacity > capacity_) grow(new_capacity);
+  }
+
+  void push_back(const T& value) {
+    try_reserve(size_ + 1);
+    ptr_[size_++] = value;
+  }
+
+  /** Appends data to the end of the buffer. */
+  template <typename U> void append(const U* begin, const U* end);
+
+  template <typename I> auto operator[](I index) -> T& { return ptr_[index]; }
+  template <typename I> auto operator[](I index) const -> const T& {
+    return ptr_[index];
+  }
+};
+
+struct buffer_traits {
+  explicit buffer_traits(size_t) {}
+  auto count() const -> size_t { return 0; }
+  auto limit(size_t size) -> size_t { return size; }
+};
+
+class fixed_buffer_traits {
+ private:
+  size_t count_ = 0;
+  size_t limit_;
+
+ public:
+  explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
+  auto count() const -> size_t { return count_; }
+  auto limit(size_t size) -> size_t {
+    size_t n = limit_ > count_ ? limit_ - count_ : 0;
+    count_ += size;
+    return size < n ? size : n;
+  }
+};
+
+// A buffer that writes to an output iterator when flushed.
+template <typename OutputIt, typename T, typename Traits = buffer_traits>
+class iterator_buffer final : public Traits, public buffer<T> {
+ private:
+  OutputIt out_;
+  enum { buffer_size = 256 };
+  T data_[buffer_size];
+
+ protected:
+  void grow(size_t) final FMT_OVERRIDE {
+    if (this->size() == buffer_size) flush();
+  }
+
+  void flush() {
+    auto size = this->size();
+    this->clear();
+    out_ = copy_str<T>(data_, data_ + this->limit(size), out_);
+  }
+
+ public:
+  explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
+      : Traits(n), buffer<T>(data_, 0, buffer_size), out_(out) {}
+  iterator_buffer(iterator_buffer&& other)
+      : Traits(other), buffer<T>(data_, 0, buffer_size), out_(other.out_) {}
+  ~iterator_buffer() { flush(); }
+
+  auto out() -> OutputIt {
+    flush();
+    return out_;
+  }
+  auto count() const -> size_t { return Traits::count() + this->size(); }
+};
+
+template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
+ protected:
+  void grow(size_t) final FMT_OVERRIDE {}
+
+ public:
+  explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {}
+
+  auto out() -> T* { return &*this->end(); }
+};
+
+// A buffer that writes to a container with the contiguous storage.
+template <typename Container>
+class iterator_buffer<std::back_insert_iterator<Container>,
+                      enable_if_t<is_contiguous<Container>::value,
+                                  typename Container::value_type>>
+    final : public buffer<typename Container::value_type> {
+ private:
+  Container& container_;
+
+ protected:
+  void grow(size_t capacity) final FMT_OVERRIDE {
+    container_.resize(capacity);
+    this->set(&container_[0], capacity);
+  }
+
+ public:
+  explicit iterator_buffer(Container& c)
+      : buffer<typename Container::value_type>(c.size()), container_(c) {}
+  explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0)
+      : iterator_buffer(get_container(out)) {}
+  auto out() -> std::back_insert_iterator<Container> {
+    return std::back_inserter(container_);
+  }
+};
+
+// A buffer that counts the number of code units written discarding the output.
+template <typename T = char> class counting_buffer final : public buffer<T> {
+ private:
+  enum { buffer_size = 256 };
+  T data_[buffer_size];
+  size_t count_ = 0;
+
+ protected:
+  void grow(size_t) final FMT_OVERRIDE {
+    if (this->size() != buffer_size) return;
+    count_ += this->size();
+    this->clear();
+  }
+
+ public:
+  counting_buffer() : buffer<T>(data_, 0, buffer_size) {}
+
+  auto count() -> size_t { return count_ + this->size(); }
+};
+
+template <typename T>
+using buffer_appender = conditional_t<std::is_same<T, char>::value, appender,
+                                      std::back_insert_iterator<buffer<T>>>;
+
+// Maps an output iterator to a buffer.
+template <typename T, typename OutputIt>
+auto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> {
+  return iterator_buffer<OutputIt, T>(out);
+}
+
+template <typename Buffer>
+auto get_iterator(Buffer& buf) -> decltype(buf.out()) {
+  return buf.out();
+}
+template <typename T> auto get_iterator(buffer<T>& buf) -> buffer_appender<T> {
+  return buffer_appender<T>(buf);
+}
+
+template <typename T, typename Char = char, typename Enable = void>
+struct fallback_formatter {
+  fallback_formatter() = delete;
+};
+
+// Specifies if T has an enabled fallback_formatter specialization.
+template <typename T, typename Char>
+using has_fallback_formatter =
+    std::is_constructible<fallback_formatter<T, Char>>;
+
+struct view {};
+
+template <typename Char, typename T> struct named_arg : view {
+  const Char* name;
+  const T& value;
+  named_arg(const Char* n, const T& v) : name(n), value(v) {}
+};
+
+template <typename Char> struct named_arg_info {
+  const Char* name;
+  int id;
+};
+
+template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
+struct arg_data {
+  // args_[0].named_args points to named_args_ to avoid bloating format_args.
+  // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
+  T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)];
+  named_arg_info<Char> named_args_[NUM_NAMED_ARGS];
+
+  template <typename... U>
+  arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {}
+  arg_data(const arg_data& other) = delete;
+  auto args() const -> const T* { return args_ + 1; }
+  auto named_args() -> named_arg_info<Char>* { return named_args_; }
+};
+
+template <typename T, typename Char, size_t NUM_ARGS>
+struct arg_data<T, Char, NUM_ARGS, 0> {
+  // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
+  T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];
+
+  template <typename... U>
+  FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {}
+  FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; }
+  FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t {
+    return nullptr;
+  }
+};
+
+template <typename Char>
+inline void init_named_args(named_arg_info<Char>*, int, int) {}
+
+template <typename T> struct is_named_arg : std::false_type {};
+template <typename T> struct is_statically_named_arg : std::false_type {};
+
+template <typename T, typename Char>
+struct is_named_arg<named_arg<Char, T>> : std::true_type {};
+
+template <typename Char, typename T, typename... Tail,
+          FMT_ENABLE_IF(!is_named_arg<T>::value)>
+void init_named_args(named_arg_info<Char>* named_args, int arg_count,
+                     int named_arg_count, const T&, const Tail&... args) {
+  init_named_args(named_args, arg_count + 1, named_arg_count, args...);
+}
+
+template <typename Char, typename T, typename... Tail,
+          FMT_ENABLE_IF(is_named_arg<T>::value)>
+void init_named_args(named_arg_info<Char>* named_args, int arg_count,
+                     int named_arg_count, const T& arg, const Tail&... args) {
+  named_args[named_arg_count++] = {arg.name, arg_count};
+  init_named_args(named_args, arg_count + 1, named_arg_count, args...);
+}
+
+template <typename... Args>
+FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int,
+                                              const Args&...) {}
+
+template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; }
+template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t {
+  return (B1 ? 1 : 0) + count<B2, Tail...>();
+}
+
+template <typename... Args> constexpr auto count_named_args() -> size_t {
+  return count<is_named_arg<Args>::value...>();
+}
+
+enum class type {
+  none_type,
+  // Integer types should go first,
+  int_type,
+  uint_type,
+  long_long_type,
+  ulong_long_type,
+  int128_type,
+  uint128_type,
+  bool_type,
+  char_type,
+  last_integer_type = char_type,
+  // followed by floating-point types.
+  float_type,
+  double_type,
+  long_double_type,
+  last_numeric_type = long_double_type,
+  cstring_type,
+  string_type,
+  pointer_type,
+  custom_type
+};
+
+// Maps core type T to the corresponding type enum constant.
+template <typename T, typename Char>
+struct type_constant : std::integral_constant<type, type::custom_type> {};
+
+#define FMT_TYPE_CONSTANT(Type, constant) \
+  template <typename Char>                \
+  struct type_constant<Type, Char>        \
+      : std::integral_constant<type, type::constant> {}
+
+FMT_TYPE_CONSTANT(int, int_type);
+FMT_TYPE_CONSTANT(unsigned, uint_type);
+FMT_TYPE_CONSTANT(long long, long_long_type);
+FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type);
+FMT_TYPE_CONSTANT(int128_t, int128_type);
+FMT_TYPE_CONSTANT(uint128_t, uint128_type);
+FMT_TYPE_CONSTANT(bool, bool_type);
+FMT_TYPE_CONSTANT(Char, char_type);
+FMT_TYPE_CONSTANT(float, float_type);
+FMT_TYPE_CONSTANT(double, double_type);
+FMT_TYPE_CONSTANT(long double, long_double_type);
+FMT_TYPE_CONSTANT(const Char*, cstring_type);
+FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type);
+FMT_TYPE_CONSTANT(const void*, pointer_type);
+
+constexpr bool is_integral_type(type t) {
+  return t > type::none_type && t <= type::last_integer_type;
+}
+
+constexpr bool is_arithmetic_type(type t) {
+  return t > type::none_type && t <= type::last_numeric_type;
+}
+
+template <typename Char> struct string_value {
+  const Char* data;
+  size_t size;
+};
+
+template <typename Char> struct named_arg_value {
+  const named_arg_info<Char>* data;
+  size_t size;
+};
+
+template <typename Context> struct custom_value {
+  using parse_context = typename Context::parse_context_type;
+  const void* value;
+  void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx);
+};
+
+// A formatting argument value.
+template <typename Context> class value {
+ public:
+  using char_type = typename Context::char_type;
+
+  union {
+    monostate no_value;
+    int int_value;
+    unsigned uint_value;
+    long long long_long_value;
+    unsigned long long ulong_long_value;
+    int128_t int128_value;
+    uint128_t uint128_value;
+    bool bool_value;
+    char_type char_value;
+    float float_value;
+    double double_value;
+    long double long_double_value;
+    const void* pointer;
+    string_value<char_type> string;
+    custom_value<Context> custom;
+    named_arg_value<char_type> named_args;
+  };
+
+  constexpr FMT_INLINE value() : no_value() {}
+  constexpr FMT_INLINE value(int val) : int_value(val) {}
+  constexpr FMT_INLINE value(unsigned val) : uint_value(val) {}
+  constexpr FMT_INLINE value(long long val) : long_long_value(val) {}
+  constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
+  FMT_INLINE value(int128_t val) : int128_value(val) {}
+  FMT_INLINE value(uint128_t val) : uint128_value(val) {}
+  FMT_INLINE value(float val) : float_value(val) {}
+  FMT_INLINE value(double val) : double_value(val) {}
+  FMT_INLINE value(long double val) : long_double_value(val) {}
+  constexpr FMT_INLINE value(bool val) : bool_value(val) {}
+  constexpr FMT_INLINE value(char_type val) : char_value(val) {}
+  FMT_CONSTEXPR FMT_INLINE value(const char_type* val) {
+    string.data = val;
+    if (is_constant_evaluated()) string.size = {};
+  }
+  FMT_CONSTEXPR FMT_INLINE value(basic_string_view<char_type> val) {
+    string.data = val.data();
+    string.size = val.size();
+  }
+  FMT_INLINE value(const void* val) : pointer(val) {}
+  FMT_INLINE value(const named_arg_info<char_type>* args, size_t size)
+      : named_args{args, size} {}
+
+  template <typename T> FMT_CONSTEXPR FMT_INLINE value(const T& val) {
+    custom.value = &val;
+    // Get the formatter type through the context to allow different contexts
+    // have different extension points, e.g. `formatter<T>` for `format` and
+    // `printf_formatter<T>` for `printf`.
+    custom.format = format_custom_arg<
+        T, conditional_t<has_formatter<T, Context>::value,
+                         typename Context::template formatter_type<T>,
+                         fallback_formatter<T, char_type>>>;
+  }
+
+ private:
+  // Formats an argument of a custom type, such as a user-defined class.
+  template <typename T, typename Formatter>
+  static void format_custom_arg(const void* arg,
+                                typename Context::parse_context_type& parse_ctx,
+                                Context& ctx) {
+    Formatter f;
+    parse_ctx.advance_to(f.parse(parse_ctx));
+    ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx));
+  }
+};
+
+template <typename Context, typename T>
+FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg<Context>;
+
+// To minimize the number of types we need to deal with, long is translated
+// either to int or to long long depending on its size.
+enum { long_short = sizeof(long) == sizeof(int) };
+using long_type = conditional_t<long_short, int, long long>;
+using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
+
+struct unformattable {};
+
+// Maps formatting arguments to core types.
+template <typename Context> struct arg_mapper {
+  using char_type = typename Context::char_type;
+
+  FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned {
+    return val;
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned {
+    return val;
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type {
+    return val;
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val)
+      -> unsigned long long {
+    return val;
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(int128_t val) -> int128_t { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(uint128_t val) -> uint128_t { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; }
+
+  template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type {
+    static_assert(
+        std::is_same<T, char>::value || std::is_same<T, char_type>::value,
+        "mixing character types is disallowed");
+    return val;
+  }
+
+  FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double {
+    return val;
+  }
+
+  FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* {
+    return val;
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* {
+    return val;
+  }
+  template <typename T, FMT_ENABLE_IF(is_string<T>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+      -> basic_string_view<char_type> {
+    static_assert(std::is_same<char_type, char_t<T>>::value,
+                  "mixing character types is disallowed");
+    return to_string_view(val);
+  }
+  template <typename T,
+            FMT_ENABLE_IF(
+                std::is_constructible<basic_string_view<char_type>, T>::value &&
+                !is_string<T>::value && !has_formatter<T, Context>::value &&
+                !has_fallback_formatter<T, char_type>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+      -> basic_string_view<char_type> {
+    return basic_string_view<char_type>(val);
+  }
+  template <
+      typename T,
+      FMT_ENABLE_IF(
+          std::is_constructible<std_string_view<char_type>, T>::value &&
+          !std::is_constructible<basic_string_view<char_type>, T>::value &&
+          !is_string<T>::value && !has_formatter<T, Context>::value &&
+          !has_fallback_formatter<T, char_type>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+      -> basic_string_view<char_type> {
+    return std_string_view<char_type>(val);
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) -> const char* {
+    static_assert(std::is_same<char_type, char>::value, "invalid string type");
+    return reinterpret_cast<const char*>(val);
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) -> const char* {
+    static_assert(std::is_same<char_type, char>::value, "invalid string type");
+    return reinterpret_cast<const char*>(val);
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) -> const char* {
+    const auto* const_val = val;
+    return map(const_val);
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) -> const char* {
+    const auto* const_val = val;
+    return map(const_val);
+  }
+
+  FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; }
+  FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* {
+    return val;
+  }
+  FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* {
+    return val;
+  }
+
+  // We use SFINAE instead of a const T* parameter to avoid conflicting with
+  // the C array overload.
+  template <typename T>
+  FMT_CONSTEXPR auto map(T) -> enable_if_t<std::is_pointer<T>::value, int> {
+    // Formatting of arbitrary pointers is disallowed. If you want to output
+    // a pointer cast it to "void *" or "const void *". In particular, this
+    // forbids formatting of "[const] volatile char *" which is printed as bool
+    // by iostreams.
+    static_assert(!sizeof(T), "formatting of non-void pointers is disallowed");
+    return 0;
+  }
+
+  template <typename T, std::size_t N>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] {
+    return values;
+  }
+
+  template <typename T,
+            FMT_ENABLE_IF(std::is_enum<T>::value &&
+                          !has_formatter<T, Context>::value &&
+                          !has_fallback_formatter<T, char_type>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+      -> decltype(std::declval<arg_mapper>().map(
+          static_cast<typename std::underlying_type<T>::type>(val))) {
+    return map(static_cast<typename std::underlying_type<T>::type>(val));
+  }
+  template <typename T,
+            FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
+                          (has_formatter<T, Context>::value ||
+                           has_fallback_formatter<T, char_type>::value))>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> const T& {
+    return val;
+  }
+
+  template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg)
+      -> decltype(std::declval<arg_mapper>().map(named_arg.value)) {
+    return map(named_arg.value);
+  }
+
+  auto map(...) -> unformattable { return {}; }
+};
+
+// A type constant after applying arg_mapper<Context>.
+template <typename T, typename Context>
+using mapped_type_constant =
+    type_constant<decltype(arg_mapper<Context>().map(std::declval<const T&>())),
+                  typename Context::char_type>;
+
+enum { packed_arg_bits = 4 };
+// Maximum number of arguments with packed types.
+enum { max_packed_args = 62 / packed_arg_bits };
+enum : unsigned long long { is_unpacked_bit = 1ULL << 63 };
+enum : unsigned long long { has_named_args_bit = 1ULL << 62 };
+
+FMT_END_DETAIL_NAMESPACE
+
+// An output iterator that appends to a buffer.
+// It is used to reduce symbol sizes for the common case.
+class appender : public std::back_insert_iterator<detail::buffer<char>> {
+  using base = std::back_insert_iterator<detail::buffer<char>>;
+
+  template <typename T>
+  friend auto get_buffer(appender out) -> detail::buffer<char>& {
+    return detail::get_container(out);
+  }
+
+ public:
+  using std::back_insert_iterator<detail::buffer<char>>::back_insert_iterator;
+  appender(base it) : base(it) {}
+  using _Unchecked_type = appender;  // Mark iterator as checked.
+
+  auto operator++() -> appender& {
+    base::operator++();
+    return *this;
+  }
+
+  auto operator++(int) -> appender {
+    auto tmp = *this;
+    ++*this;
+    return tmp;
+  }
+};
+
+// A formatting argument. It is a trivially copyable/constructible type to
+// allow storage in basic_memory_buffer.
+template <typename Context> class basic_format_arg {
+ private:
+  detail::value<Context> value_;
+  detail::type type_;
+
+  template <typename ContextType, typename T>
+  friend FMT_CONSTEXPR auto detail::make_arg(const T& value)
+      -> basic_format_arg<ContextType>;
+
+  template <typename Visitor, typename Ctx>
+  friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
+                                             const basic_format_arg<Ctx>& arg)
+      -> decltype(vis(0));
+
+  friend class basic_format_args<Context>;
+  friend class dynamic_format_arg_store<Context>;
+
+  using char_type = typename Context::char_type;
+
+  template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
+  friend struct detail::arg_data;
+
+  basic_format_arg(const detail::named_arg_info<char_type>* args, size_t size)
+      : value_(args, size) {}
+
+ public:
+  class handle {
+   public:
+    explicit handle(detail::custom_value<Context> custom) : custom_(custom) {}
+
+    void format(typename Context::parse_context_type& parse_ctx,
+                Context& ctx) const {
+      custom_.format(custom_.value, parse_ctx, ctx);
+    }
+
+   private:
+    detail::custom_value<Context> custom_;
+  };
+
+  constexpr basic_format_arg() : type_(detail::type::none_type) {}
+
+  constexpr explicit operator bool() const FMT_NOEXCEPT {
+    return type_ != detail::type::none_type;
+  }
+
+  auto type() const -> detail::type { return type_; }
+
+  auto is_integral() const -> bool { return detail::is_integral_type(type_); }
+  auto is_arithmetic() const -> bool {
+    return detail::is_arithmetic_type(type_);
+  }
+};
+
+/**
+  \rst
+  Visits an argument dispatching to the appropriate visit method based on
+  the argument type. For example, if the argument type is ``double`` then
+  ``vis(value)`` will be called with the value of type ``double``.
+  \endrst
+ */
+template <typename Visitor, typename Context>
+FMT_CONSTEXPR FMT_INLINE auto visit_format_arg(
+    Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) {
+  switch (arg.type_) {
+  case detail::type::none_type:
+    break;
+  case detail::type::int_type:
+    return vis(arg.value_.int_value);
+  case detail::type::uint_type:
+    return vis(arg.value_.uint_value);
+  case detail::type::long_long_type:
+    return vis(arg.value_.long_long_value);
+  case detail::type::ulong_long_type:
+    return vis(arg.value_.ulong_long_value);
+  case detail::type::int128_type:
+    return vis(detail::convert_for_visit(arg.value_.int128_value));
+  case detail::type::uint128_type:
+    return vis(detail::convert_for_visit(arg.value_.uint128_value));
+  case detail::type::bool_type:
+    return vis(arg.value_.bool_value);
+  case detail::type::char_type:
+    return vis(arg.value_.char_value);
+  case detail::type::float_type:
+    return vis(arg.value_.float_value);
+  case detail::type::double_type:
+    return vis(arg.value_.double_value);
+  case detail::type::long_double_type:
+    return vis(arg.value_.long_double_value);
+  case detail::type::cstring_type:
+    return vis(arg.value_.string.data);
+  case detail::type::string_type:
+    using sv = basic_string_view<typename Context::char_type>;
+    return vis(sv(arg.value_.string.data, arg.value_.string.size));
+  case detail::type::pointer_type:
+    return vis(arg.value_.pointer);
+  case detail::type::custom_type:
+    return vis(typename basic_format_arg<Context>::handle(arg.value_.custom));
+  }
+  return vis(monostate());
+}
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+template <typename Char, typename InputIt>
+auto copy_str(InputIt begin, InputIt end, appender out) -> appender {
+  get_container(out).append(begin, end);
+  return out;
+}
+
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
+// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
+template <typename... Ts> struct void_t_impl { using type = void; };
+template <typename... Ts>
+using void_t = typename detail::void_t_impl<Ts...>::type;
+#else
+template <typename...> using void_t = void;
+#endif
+
+template <typename It, typename T, typename Enable = void>
+struct is_output_iterator : std::false_type {};
+
+template <typename It, typename T>
+struct is_output_iterator<
+    It, T,
+    void_t<typename std::iterator_traits<It>::iterator_category,
+           decltype(*std::declval<It>() = std::declval<T>())>>
+    : std::true_type {};
+
+template <typename OutputIt>
+struct is_back_insert_iterator : std::false_type {};
+template <typename Container>
+struct is_back_insert_iterator<std::back_insert_iterator<Container>>
+    : std::true_type {};
+
+template <typename OutputIt>
+struct is_contiguous_back_insert_iterator : std::false_type {};
+template <typename Container>
+struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
+    : is_contiguous<Container> {};
+template <>
+struct is_contiguous_back_insert_iterator<appender> : std::true_type {};
+
+// A type-erased reference to an std::locale to avoid heavy <locale> include.
+class locale_ref {
+ private:
+  const void* locale_;  // A type-erased pointer to std::locale.
+
+ public:
+  constexpr locale_ref() : locale_(nullptr) {}
+  template <typename Locale> explicit locale_ref(const Locale& loc);
+
+  explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; }
+
+  template <typename Locale> auto get() const -> Locale;
+};
+
+template <typename> constexpr auto encode_types() -> unsigned long long {
+  return 0;
+}
+
+template <typename Context, typename Arg, typename... Args>
+constexpr auto encode_types() -> unsigned long long {
+  return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) |
+         (encode_types<Context, Args...>() << packed_arg_bits);
+}
+
+template <typename Context, typename T>
+FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg<Context> {
+  basic_format_arg<Context> arg;
+  arg.type_ = mapped_type_constant<T, Context>::value;
+  arg.value_ = arg_mapper<Context>().map(value);
+  return arg;
+}
+
+// The type template parameter is there to avoid an ODR violation when using
+// a fallback formatter in one translation unit and an implicit conversion in
+// another (not recommended).
+template <bool IS_PACKED, typename Context, type, typename T,
+          FMT_ENABLE_IF(IS_PACKED)>
+FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value<Context> {
+  const auto& arg = arg_mapper<Context>().map(val);
+  static_assert(
+      !std::is_same<decltype(arg), const unformattable&>::value,
+      "Cannot format an argument. To make type T formattable provide a "
+      "formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
+  return {arg};
+}
+
+template <bool IS_PACKED, typename Context, type, typename T,
+          FMT_ENABLE_IF(!IS_PACKED)>
+inline auto make_arg(const T& value) -> basic_format_arg<Context> {
+  return make_arg<Context>(value);
+}
+FMT_END_DETAIL_NAMESPACE
+
+// Formatting context.
+template <typename OutputIt, typename Char> class basic_format_context {
+ public:
+  /** The character type for the output. */
+  using char_type = Char;
+
+ private:
+  OutputIt out_;
+  basic_format_args<basic_format_context> args_;
+  detail::locale_ref loc_;
+
+ public:
+  using iterator = OutputIt;
+  using format_arg = basic_format_arg<basic_format_context>;
+  using parse_context_type = basic_format_parse_context<Char>;
+  template <typename T> using formatter_type = formatter<T, char_type>;
+
+  basic_format_context(basic_format_context&&) = default;
+  basic_format_context(const basic_format_context&) = delete;
+  void operator=(const basic_format_context&) = delete;
+  /**
+   Constructs a ``basic_format_context`` object. References to the arguments are
+   stored in the object so make sure they have appropriate lifetimes.
+   */
+  constexpr basic_format_context(
+      OutputIt out, basic_format_args<basic_format_context> ctx_args,
+      detail::locale_ref loc = detail::locale_ref())
+      : out_(out), args_(ctx_args), loc_(loc) {}
+
+  constexpr auto arg(int id) const -> format_arg { return args_.get(id); }
+  FMT_CONSTEXPR auto arg(basic_string_view<char_type> name) -> format_arg {
+    return args_.get(name);
+  }
+  FMT_CONSTEXPR auto arg_id(basic_string_view<char_type> name) -> int {
+    return args_.get_id(name);
+  }
+  auto args() const -> const basic_format_args<basic_format_context>& {
+    return args_;
+  }
+
+  FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; }
+  void on_error(const char* message) { error_handler().on_error(message); }
+
+  // Returns an iterator to the beginning of the output range.
+  FMT_CONSTEXPR auto out() -> iterator { return out_; }
+
+  // Advances the begin iterator to ``it``.
+  void advance_to(iterator it) {
+    if (!detail::is_back_insert_iterator<iterator>()) out_ = it;
+  }
+
+  FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; }
+};
+
+template <typename Char>
+using buffer_context =
+    basic_format_context<detail::buffer_appender<Char>, Char>;
+using format_context = buffer_context<char>;
+
+// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164.
+#define FMT_BUFFER_CONTEXT(Char) \
+  basic_format_context<detail::buffer_appender<Char>, Char>
+
+template <typename T, typename Char = char>
+using is_formattable = bool_constant<
+    !std::is_same<decltype(detail::arg_mapper<buffer_context<Char>>().map(
+                      std::declval<T>())),
+                  detail::unformattable>::value &&
+    !detail::has_fallback_formatter<T, Char>::value>;
+
+/**
+  \rst
+  An array of references to arguments. It can be implicitly converted into
+  `~fmt::basic_format_args` for passing into type-erased formatting functions
+  such as `~fmt::vformat`.
+  \endrst
+ */
+template <typename Context, typename... Args>
+class format_arg_store
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+    // Workaround a GCC template argument substitution bug.
+    : public basic_format_args<Context>
+#endif
+{
+ private:
+  static const size_t num_args = sizeof...(Args);
+  static const size_t num_named_args = detail::count_named_args<Args...>();
+  static const bool is_packed = num_args <= detail::max_packed_args;
+
+  using value_type = conditional_t<is_packed, detail::value<Context>,
+                                   basic_format_arg<Context>>;
+
+  detail::arg_data<value_type, typename Context::char_type, num_args,
+                   num_named_args>
+      data_;
+
+  friend class basic_format_args<Context>;
+
+  static constexpr unsigned long long desc =
+      (is_packed ? detail::encode_types<Context, Args...>()
+                 : detail::is_unpacked_bit | num_args) |
+      (num_named_args != 0
+           ? static_cast<unsigned long long>(detail::has_named_args_bit)
+           : 0);
+
+ public:
+  FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args)
+      :
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+        basic_format_args<Context>(*this),
+#endif
+        data_{detail::make_arg<
+            is_packed, Context,
+            detail::mapped_type_constant<Args, Context>::value>(args)...} {
+    detail::init_named_args(data_.named_args(), 0, 0, args...);
+  }
+};
+
+/**
+  \rst
+  Constructs a `~fmt::format_arg_store` object that contains references to
+  arguments and can be implicitly converted to `~fmt::format_args`. `Context`
+  can be omitted in which case it defaults to `~fmt::context`.
+  See `~fmt::arg` for lifetime considerations.
+  \endrst
+ */
+template <typename Context = format_context, typename... Args>
+constexpr auto make_format_args(const Args&... args)
+    -> format_arg_store<Context, Args...> {
+  return {args...};
+}
+
+/**
+  \rst
+  Returns a named argument to be used in a formatting function.
+  It should only be used in a call to a formatting function or
+  `dynamic_format_arg_store::push_back`.
+
+  **Example**::
+
+    fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23));
+  \endrst
+ */
+template <typename Char, typename T>
+inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> {
+  static_assert(!detail::is_named_arg<T>(), "nested named arguments");
+  return {name, arg};
+}
+
+/**
+  \rst
+  A view of a collection of formatting arguments. To avoid lifetime issues it
+  should only be used as a parameter type in type-erased functions such as
+  ``vformat``::
+
+    void vlog(string_view format_str, format_args args);  // OK
+    format_args args = make_format_args(42);  // Error: dangling reference
+  \endrst
+ */
+template <typename Context> class basic_format_args {
+ public:
+  using size_type = int;
+  using format_arg = basic_format_arg<Context>;
+
+ private:
+  // A descriptor that contains information about formatting arguments.
+  // If the number of arguments is less or equal to max_packed_args then
+  // argument types are passed in the descriptor. This reduces binary code size
+  // per formatting function call.
+  unsigned long long desc_;
+  union {
+    // If is_packed() returns true then argument values are stored in values_;
+    // otherwise they are stored in args_. This is done to improve cache
+    // locality and reduce compiled code size since storing larger objects
+    // may require more code (at least on x86-64) even if the same amount of
+    // data is actually copied to stack. It saves ~10% on the bloat test.
+    const detail::value<Context>* values_;
+    const format_arg* args_;
+  };
+
+  constexpr auto is_packed() const -> bool {
+    return (desc_ & detail::is_unpacked_bit) == 0;
+  }
+  auto has_named_args() const -> bool {
+    return (desc_ & detail::has_named_args_bit) != 0;
+  }
+
+  FMT_CONSTEXPR auto type(int index) const -> detail::type {
+    int shift = index * detail::packed_arg_bits;
+    unsigned int mask = (1 << detail::packed_arg_bits) - 1;
+    return static_cast<detail::type>((desc_ >> shift) & mask);
+  }
+
+  constexpr FMT_INLINE basic_format_args(unsigned long long desc,
+                                         const detail::value<Context>* values)
+      : desc_(desc), values_(values) {}
+  constexpr basic_format_args(unsigned long long desc, const format_arg* args)
+      : desc_(desc), args_(args) {}
+
+ public:
+  constexpr basic_format_args() : desc_(0), args_(nullptr) {}
+
+  /**
+   \rst
+   Constructs a `basic_format_args` object from `~fmt::format_arg_store`.
+   \endrst
+   */
+  template <typename... Args>
+  constexpr FMT_INLINE basic_format_args(
+      const format_arg_store<Context, Args...>& store)
+      : basic_format_args(format_arg_store<Context, Args...>::desc,
+                          store.data_.args()) {}
+
+  /**
+   \rst
+   Constructs a `basic_format_args` object from
+   `~fmt::dynamic_format_arg_store`.
+   \endrst
+   */
+  constexpr FMT_INLINE basic_format_args(
+      const dynamic_format_arg_store<Context>& store)
+      : basic_format_args(store.get_types(), store.data()) {}
+
+  /**
+   \rst
+   Constructs a `basic_format_args` object from a dynamic set of arguments.
+   \endrst
+   */
+  constexpr basic_format_args(const format_arg* args, int count)
+      : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count),
+                          args) {}
+
+  /** Returns the argument with the specified id. */
+  FMT_CONSTEXPR auto get(int id) const -> format_arg {
+    format_arg arg;
+    if (!is_packed()) {
+      if (id < max_size()) arg = args_[id];
+      return arg;
+    }
+    if (id >= detail::max_packed_args) return arg;
+    arg.type_ = type(id);
+    if (arg.type_ == detail::type::none_type) return arg;
+    arg.value_ = values_[id];
+    return arg;
+  }
+
+  template <typename Char>
+  auto get(basic_string_view<Char> name) const -> format_arg {
+    int id = get_id(name);
+    return id >= 0 ? get(id) : format_arg();
+  }
+
+  template <typename Char>
+  auto get_id(basic_string_view<Char> name) const -> int {
+    if (!has_named_args()) return -1;
+    const auto& named_args =
+        (is_packed() ? values_[-1] : args_[-1].value_).named_args;
+    for (size_t i = 0; i < named_args.size; ++i) {
+      if (named_args.data[i].name == name) return named_args.data[i].id;
+    }
+    return -1;
+  }
+
+  auto max_size() const -> int {
+    unsigned long long max_packed = detail::max_packed_args;
+    return static_cast<int>(is_packed() ? max_packed
+                                        : desc_ & ~detail::is_unpacked_bit);
+  }
+};
+
+/** An alias to ``basic_format_args<format_context>``. */
+// A separate type would result in shorter symbols but break ABI compatibility
+// between clang and gcc on ARM (#1919).
+using format_args = basic_format_args<format_context>;
+
+// We cannot use enum classes as bit fields because of a gcc bug
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414.
+namespace align {
+enum type { none, left, right, center, numeric };
+}
+using align_t = align::type;
+namespace sign {
+enum type { none, minus, plus, space };
+}
+using sign_t = sign::type;
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+void throw_format_error(const char* message);
+
+// Workaround an array initialization issue in gcc 4.8.
+template <typename Char> struct fill_t {
+ private:
+  enum { max_size = 4 };
+  Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)};
+  unsigned char size_ = 1;
+
+ public:
+  FMT_CONSTEXPR void operator=(basic_string_view<Char> s) {
+    auto size = s.size();
+    if (size > max_size) return throw_format_error("invalid fill");
+    for (size_t i = 0; i < size; ++i) data_[i] = s[i];
+    size_ = static_cast<unsigned char>(size);
+  }
+
+  constexpr auto size() const -> size_t { return size_; }
+  constexpr auto data() const -> const Char* { return data_; }
+
+  FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; }
+  FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& {
+    return data_[index];
+  }
+};
+FMT_END_DETAIL_NAMESPACE
+
+// Format specifiers for built-in and string types.
+template <typename Char> struct basic_format_specs {
+  int width;
+  int precision;
+  char type;
+  align_t align : 4;
+  sign_t sign : 3;
+  bool alt : 1;  // Alternate form ('#').
+  bool localized : 1;
+  detail::fill_t<Char> fill;
+
+  constexpr basic_format_specs()
+      : width(0),
+        precision(-1),
+        type(0),
+        align(align::none),
+        sign(sign::none),
+        alt(false),
+        localized(false) {}
+};
+
+using format_specs = basic_format_specs<char>;
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+enum class arg_id_kind { none, index, name };
+
+// An argument reference.
+template <typename Char> struct arg_ref {
+  FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
+
+  FMT_CONSTEXPR explicit arg_ref(int index)
+      : kind(arg_id_kind::index), val(index) {}
+  FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
+      : kind(arg_id_kind::name), val(name) {}
+
+  FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& {
+    kind = arg_id_kind::index;
+    val.index = idx;
+    return *this;
+  }
+
+  arg_id_kind kind;
+  union value {
+    FMT_CONSTEXPR value(int id = 0) : index{id} {}
+    FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {}
+
+    int index;
+    basic_string_view<Char> name;
+  } val;
+};
+
+// Format specifiers with width and precision resolved at formatting rather
+// than parsing time to allow re-using the same parsed specifiers with
+// different sets of arguments (precompilation of format strings).
+template <typename Char>
+struct dynamic_format_specs : basic_format_specs<Char> {
+  arg_ref<Char> width_ref;
+  arg_ref<Char> precision_ref;
+};
+
+struct auto_id {};
+
+// A format specifier handler that sets fields in basic_format_specs.
+template <typename Char> class specs_setter {
+ protected:
+  basic_format_specs<Char>& specs_;
+
+ public:
+  explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char>& specs)
+      : specs_(specs) {}
+
+  FMT_CONSTEXPR specs_setter(const specs_setter& other)
+      : specs_(other.specs_) {}
+
+  FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; }
+  FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
+    specs_.fill = fill;
+  }
+  FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; }
+  FMT_CONSTEXPR void on_hash() { specs_.alt = true; }
+  FMT_CONSTEXPR void on_localized() { specs_.localized = true; }
+
+  FMT_CONSTEXPR void on_zero() {
+    if (specs_.align == align::none) specs_.align = align::numeric;
+    specs_.fill[0] = Char('0');
+  }
+
+  FMT_CONSTEXPR void on_width(int width) { specs_.width = width; }
+  FMT_CONSTEXPR void on_precision(int precision) {
+    specs_.precision = precision;
+  }
+  FMT_CONSTEXPR void end_precision() {}
+
+  FMT_CONSTEXPR void on_type(Char type) {
+    specs_.type = static_cast<char>(type);
+  }
+};
+
+// Format spec handler that saves references to arguments representing dynamic
+// width and precision to be resolved at formatting time.
+template <typename ParseContext>
+class dynamic_specs_handler
+    : public specs_setter<typename ParseContext::char_type> {
+ public:
+  using char_type = typename ParseContext::char_type;
+
+  FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs<char_type>& specs,
+                                      ParseContext& ctx)
+      : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {}
+
+  FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other)
+      : specs_setter<char_type>(other),
+        specs_(other.specs_),
+        context_(other.context_) {}
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
+    specs_.width_ref = make_arg_ref(arg_id);
+  }
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
+    specs_.precision_ref = make_arg_ref(arg_id);
+  }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    context_.on_error(message);
+  }
+
+ private:
+  dynamic_format_specs<char_type>& specs_;
+  ParseContext& context_;
+
+  using arg_ref_type = arg_ref<char_type>;
+
+  FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type {
+    context_.check_arg_id(arg_id);
+    return arg_ref_type(arg_id);
+  }
+
+  FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type {
+    return arg_ref_type(context_.next_arg_id());
+  }
+
+  FMT_CONSTEXPR auto make_arg_ref(basic_string_view<char_type> arg_id)
+      -> arg_ref_type {
+    context_.check_arg_id(arg_id);
+    basic_string_view<char_type> format_str(
+        context_.begin(), to_unsigned(context_.end() - context_.begin()));
+    return arg_ref_type(arg_id);
+  }
+};
+
+template <typename Char> constexpr bool is_ascii_letter(Char c) {
+  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+// Converts a character to ASCII. Returns a number > 127 on conversion failure.
+template <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)>
+constexpr auto to_ascii(Char value) -> Char {
+  return value;
+}
+template <typename Char, FMT_ENABLE_IF(std::is_enum<Char>::value)>
+constexpr auto to_ascii(Char value) ->
+    typename std::underlying_type<Char>::type {
+  return value;
+}
+
+template <typename Char>
+FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int {
+  if (const_check(sizeof(Char) != 1)) return 1;
+  constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                              0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0};
+  int len = lengths[static_cast<unsigned char>(*begin) >> 3];
+
+  // Compute the pointer to the next character early so that the next
+  // iteration can start working on the next character. Neither Clang
+  // nor GCC figure out this reordering on their own.
+  return len + !len;
+}
+
+// Return the result via the out param to workaround gcc bug 77539.
+template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
+FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
+  for (out = first; out != last; ++out) {
+    if (*out == value) return true;
+  }
+  return false;
+}
+
+template <>
+inline auto find<false, char>(const char* first, const char* last, char value,
+                              const char*& out) -> bool {
+  out = static_cast<const char*>(
+      std::memchr(first, value, to_unsigned(last - first)));
+  return out != nullptr;
+}
+
+// Parses the range [begin, end) as an unsigned integer. This function assumes
+// that the range is non-empty and the first character is a digit.
+template <typename Char>
+FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end,
+                                         int error_value) noexcept -> int {
+  FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', "");
+  unsigned value = 0, prev = 0;
+  auto p = begin;
+  do {
+    prev = value;
+    value = value * 10 + unsigned(*p - '0');
+    ++p;
+  } while (p != end && '0' <= *p && *p <= '9');
+  auto num_digits = p - begin;
+  begin = p;
+  if (num_digits <= std::numeric_limits<int>::digits10)
+    return static_cast<int>(value);
+  // Check for overflow.
+  const unsigned max = to_unsigned((std::numeric_limits<int>::max)());
+  return num_digits == std::numeric_limits<int>::digits10 + 1 &&
+                 prev * 10ull + unsigned(p[-1] - '0') <= max
+             ? static_cast<int>(value)
+             : error_value;
+}
+
+// Parses fill and alignment.
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end,
+                               Handler&& handler) -> const Char* {
+  FMT_ASSERT(begin != end, "");
+  auto align = align::none;
+  auto p = begin + code_point_length(begin);
+  if (p >= end) p = begin;
+  for (;;) {
+    switch (to_ascii(*p)) {
+    case '<':
+      align = align::left;
+      break;
+    case '>':
+      align = align::right;
+      break;
+    case '^':
+      align = align::center;
+      break;
+    default:
+      break;
+    }
+    if (align != align::none) {
+      if (p != begin) {
+        auto c = *begin;
+        if (c == '{')
+          return handler.on_error("invalid fill character '{'"), begin;
+        handler.on_fill(basic_string_view<Char>(begin, to_unsigned(p - begin)));
+        begin = p + 1;
+      } else
+        ++begin;
+      handler.on_align(align);
+      break;
+    } else if (p == begin) {
+      break;
+    }
+    p = begin;
+  }
+  return begin;
+}
+
+template <typename Char> FMT_CONSTEXPR bool is_name_start(Char c) {
+  return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c;
+}
+
+template <typename Char, typename IDHandler>
+FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end,
+                                   IDHandler&& handler) -> const Char* {
+  FMT_ASSERT(begin != end, "");
+  Char c = *begin;
+  if (c >= '0' && c <= '9') {
+    int index = 0;
+    if (c != '0')
+      index =
+          parse_nonnegative_int(begin, end, (std::numeric_limits<int>::max)());
+    else
+      ++begin;
+    if (begin == end || (*begin != '}' && *begin != ':'))
+      handler.on_error("invalid format string");
+    else
+      handler(index);
+    return begin;
+  }
+  if (!is_name_start(c)) {
+    handler.on_error("invalid format string");
+    return begin;
+  }
+  auto it = begin;
+  do {
+    ++it;
+  } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9')));
+  handler(basic_string_view<Char>(begin, to_unsigned(it - begin)));
+  return it;
+}
+
+template <typename Char, typename IDHandler>
+FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end,
+                                           IDHandler&& handler) -> const Char* {
+  Char c = *begin;
+  if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler);
+  handler();
+  return begin;
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end,
+                               Handler&& handler) -> const Char* {
+  using detail::auto_id;
+  struct width_adapter {
+    Handler& handler;
+
+    FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); }
+    FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); }
+    FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+      handler.on_dynamic_width(id);
+    }
+    FMT_CONSTEXPR void on_error(const char* message) {
+      if (message) handler.on_error(message);
+    }
+  };
+
+  FMT_ASSERT(begin != end, "");
+  if ('0' <= *begin && *begin <= '9') {
+    int width = parse_nonnegative_int(begin, end, -1);
+    if (width != -1)
+      handler.on_width(width);
+    else
+      handler.on_error("number is too big");
+  } else if (*begin == '{') {
+    ++begin;
+    if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler});
+    if (begin == end || *begin != '}')
+      return handler.on_error("invalid format string"), begin;
+    ++begin;
+  }
+  return begin;
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end,
+                                   Handler&& handler) -> const Char* {
+  using detail::auto_id;
+  struct precision_adapter {
+    Handler& handler;
+
+    FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); }
+    FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); }
+    FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+      handler.on_dynamic_precision(id);
+    }
+    FMT_CONSTEXPR void on_error(const char* message) {
+      if (message) handler.on_error(message);
+    }
+  };
+
+  ++begin;
+  auto c = begin != end ? *begin : Char();
+  if ('0' <= c && c <= '9') {
+    auto precision = parse_nonnegative_int(begin, end, -1);
+    if (precision != -1)
+      handler.on_precision(precision);
+    else
+      handler.on_error("number is too big");
+  } else if (c == '{') {
+    ++begin;
+    if (begin != end)
+      begin = parse_arg_id(begin, end, precision_adapter{handler});
+    if (begin == end || *begin++ != '}')
+      return handler.on_error("invalid format string"), begin;
+  } else {
+    return handler.on_error("missing precision specifier"), begin;
+  }
+  handler.end_precision();
+  return begin;
+}
+
+// Parses standard format specifiers and sends notifications about parsed
+// components to handler.
+template <typename Char, typename SpecHandler>
+FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin,
+                                                 const Char* end,
+                                                 SpecHandler&& handler)
+    -> const Char* {
+  if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) &&
+      *begin != 'L') {
+    handler.on_type(*begin++);
+    return begin;
+  }
+
+  if (begin == end) return begin;
+
+  begin = parse_align(begin, end, handler);
+  if (begin == end) return begin;
+
+  // Parse sign.
+  switch (to_ascii(*begin)) {
+  case '+':
+    handler.on_sign(sign::plus);
+    ++begin;
+    break;
+  case '-':
+    handler.on_sign(sign::minus);
+    ++begin;
+    break;
+  case ' ':
+    handler.on_sign(sign::space);
+    ++begin;
+    break;
+  default:
+    break;
+  }
+  if (begin == end) return begin;
+
+  if (*begin == '#') {
+    handler.on_hash();
+    if (++begin == end) return begin;
+  }
+
+  // Parse zero flag.
+  if (*begin == '0') {
+    handler.on_zero();
+    if (++begin == end) return begin;
+  }
+
+  begin = parse_width(begin, end, handler);
+  if (begin == end) return begin;
+
+  // Parse precision.
+  if (*begin == '.') {
+    begin = parse_precision(begin, end, handler);
+    if (begin == end) return begin;
+  }
+
+  if (*begin == 'L') {
+    handler.on_localized();
+    ++begin;
+  }
+
+  // Parse type.
+  if (begin != end && *begin != '}') handler.on_type(*begin++);
+  return begin;
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end,
+                                           Handler&& handler) -> const Char* {
+  struct id_adapter {
+    Handler& handler;
+    int arg_id;
+
+    FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); }
+    FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); }
+    FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+      arg_id = handler.on_arg_id(id);
+    }
+    FMT_CONSTEXPR void on_error(const char* message) {
+      if (message) handler.on_error(message);
+    }
+  };
+
+  ++begin;
+  if (begin == end) return handler.on_error("invalid format string"), end;
+  if (*begin == '}') {
+    handler.on_replacement_field(handler.on_arg_id(), begin);
+  } else if (*begin == '{') {
+    handler.on_text(begin, begin + 1);
+  } else {
+    auto adapter = id_adapter{handler, 0};
+    begin = parse_arg_id(begin, end, adapter);
+    Char c = begin != end ? *begin : Char();
+    if (c == '}') {
+      handler.on_replacement_field(adapter.arg_id, begin);
+    } else if (c == ':') {
+      begin = handler.on_format_specs(adapter.arg_id, begin + 1, end);
+      if (begin == end || *begin != '}')
+        return handler.on_error("unknown format specifier"), end;
+    } else {
+      return handler.on_error("missing '}' in format string"), end;
+    }
+  }
+  return begin + 1;
+}
+
+template <bool IS_CONSTEXPR, typename Char, typename Handler>
+FMT_CONSTEXPR FMT_INLINE void parse_format_string(
+    basic_string_view<Char> format_str, Handler&& handler) {
+  // this is most likely a name-lookup defect in msvc's modules implementation
+  using detail::find;
+
+  auto begin = format_str.data();
+  auto end = begin + format_str.size();
+  if (end - begin < 32) {
+    // Use a simple loop instead of memchr for small strings.
+    const Char* p = begin;
+    while (p != end) {
+      auto c = *p++;
+      if (c == '{') {
+        handler.on_text(begin, p - 1);
+        begin = p = parse_replacement_field(p - 1, end, handler);
+      } else if (c == '}') {
+        if (p == end || *p != '}')
+          return handler.on_error("unmatched '}' in format string");
+        handler.on_text(begin, p);
+        begin = ++p;
+      }
+    }
+    handler.on_text(begin, end);
+    return;
+  }
+  struct writer {
+    FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) {
+      if (pbegin == pend) return;
+      for (;;) {
+        const Char* p = nullptr;
+        if (!find<IS_CONSTEXPR>(pbegin, pend, Char('}'), p))
+          return handler_.on_text(pbegin, pend);
+        ++p;
+        if (p == pend || *p != '}')
+          return handler_.on_error("unmatched '}' in format string");
+        handler_.on_text(pbegin, p);
+        pbegin = p + 1;
+      }
+    }
+    Handler& handler_;
+  } write{handler};
+  while (begin != end) {
+    // Doing two passes with memchr (one for '{' and another for '}') is up to
+    // 2.5x faster than the naive one-pass implementation on big format strings.
+    const Char* p = begin;
+    if (*begin != '{' && !find<IS_CONSTEXPR>(begin + 1, end, Char('{'), p))
+      return write(begin, end);
+    write(begin, p);
+    begin = parse_replacement_field(p, end, handler);
+  }
+}
+
+template <typename T, typename ParseContext>
+FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx)
+    -> decltype(ctx.begin()) {
+  using char_type = typename ParseContext::char_type;
+  using context = buffer_context<char_type>;
+  using mapped_type = conditional_t<
+      mapped_type_constant<T, context>::value != type::custom_type,
+      decltype(arg_mapper<context>().map(std::declval<const T&>())), T>;
+  auto f = conditional_t<has_formatter<mapped_type, context>::value,
+                         formatter<mapped_type, char_type>,
+                         fallback_formatter<T, char_type>>();
+  return f.parse(ctx);
+}
+
+// A parse context with extra argument id checks. It is only used at compile
+// time because adding checks at runtime would introduce substantial overhead
+// and would be redundant since argument ids are checked when arguments are
+// retrieved anyway.
+template <typename Char, typename ErrorHandler = error_handler>
+class compile_parse_context
+    : public basic_format_parse_context<Char, ErrorHandler> {
+ private:
+  int num_args_;
+  using base = basic_format_parse_context<Char, ErrorHandler>;
+
+ public:
+  explicit FMT_CONSTEXPR compile_parse_context(
+      basic_string_view<Char> format_str,
+      int num_args = (std::numeric_limits<int>::max)(), ErrorHandler eh = {})
+      : base(format_str, eh), num_args_(num_args) {}
+
+  FMT_CONSTEXPR auto next_arg_id() -> int {
+    int id = base::next_arg_id();
+    if (id >= num_args_) this->on_error("argument not found");
+    return id;
+  }
+
+  FMT_CONSTEXPR void check_arg_id(int id) {
+    base::check_arg_id(id);
+    if (id >= num_args_) this->on_error("argument not found");
+  }
+  using base::check_arg_id;
+};
+
+template <typename ErrorHandler>
+FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) {
+  switch (spec) {
+  case 0:
+  case 'd':
+  case 'x':
+  case 'X':
+  case 'b':
+  case 'B':
+  case 'o':
+  case 'c':
+    break;
+  default:
+    eh.on_error("invalid type specifier");
+    break;
+  }
+}
+
+// Checks char specs and returns true if the type spec is char (and not int).
+template <typename Char, typename ErrorHandler = error_handler>
+FMT_CONSTEXPR auto check_char_specs(const basic_format_specs<Char>& specs,
+                                    ErrorHandler&& eh = {}) -> bool {
+  if (specs.type && specs.type != 'c') {
+    check_int_type_spec(specs.type, eh);
+    return false;
+  }
+  if (specs.align == align::numeric || specs.sign != sign::none || specs.alt)
+    eh.on_error("invalid format specifier for char");
+  return true;
+}
+
+// A floating-point presentation format.
+enum class float_format : unsigned char {
+  general,  // General: exponent notation or fixed point based on magnitude.
+  exp,      // Exponent notation with the default precision of 6, e.g. 1.2e-3.
+  fixed,    // Fixed point with the default precision of 6, e.g. 0.0012.
+  hex
+};
+
+struct float_specs {
+  int precision;
+  float_format format : 8;
+  sign_t sign : 8;
+  bool upper : 1;
+  bool locale : 1;
+  bool binary32 : 1;
+  bool use_grisu : 1;
+  bool showpoint : 1;
+};
+
+template <typename ErrorHandler = error_handler, typename Char>
+FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs<Char>& specs,
+                                         ErrorHandler&& eh = {})
+    -> float_specs {
+  auto result = float_specs();
+  result.showpoint = specs.alt;
+  result.locale = specs.localized;
+  switch (specs.type) {
+  case 0:
+    result.format = float_format::general;
+    break;
+  case 'G':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'g':
+    result.format = float_format::general;
+    break;
+  case 'E':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'e':
+    result.format = float_format::exp;
+    result.showpoint |= specs.precision != 0;
+    break;
+  case 'F':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'f':
+    result.format = float_format::fixed;
+    result.showpoint |= specs.precision != 0;
+    break;
+  case 'A':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'a':
+    result.format = float_format::hex;
+    break;
+  default:
+    eh.on_error("invalid type specifier");
+    break;
+  }
+  return result;
+}
+
+template <typename Char, typename ErrorHandler = error_handler>
+FMT_CONSTEXPR auto check_cstring_type_spec(Char spec, ErrorHandler&& eh = {})
+    -> bool {
+  if (spec == 0 || spec == 's') return true;
+  if (spec != 'p') eh.on_error("invalid type specifier");
+  return false;
+}
+
+template <typename Char, typename ErrorHandler = error_handler>
+FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh = {}) {
+  if (spec != 0 && spec != 's') eh.on_error("invalid type specifier");
+}
+
+template <typename Char, typename ErrorHandler>
+FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) {
+  if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier");
+}
+
+// A parse_format_specs handler that checks if specifiers are consistent with
+// the argument type.
+template <typename Handler> class specs_checker : public Handler {
+ private:
+  detail::type arg_type_;
+
+  FMT_CONSTEXPR void require_numeric_argument() {
+    if (!is_arithmetic_type(arg_type_))
+      this->on_error("format specifier requires numeric argument");
+  }
+
+ public:
+  FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type)
+      : Handler(handler), arg_type_(arg_type) {}
+
+  FMT_CONSTEXPR void on_align(align_t align) {
+    if (align == align::numeric) require_numeric_argument();
+    Handler::on_align(align);
+  }
+
+  FMT_CONSTEXPR void on_sign(sign_t s) {
+    require_numeric_argument();
+    if (is_integral_type(arg_type_) && arg_type_ != type::int_type &&
+        arg_type_ != type::long_long_type && arg_type_ != type::char_type) {
+      this->on_error("format specifier requires signed argument");
+    }
+    Handler::on_sign(s);
+  }
+
+  FMT_CONSTEXPR void on_hash() {
+    require_numeric_argument();
+    Handler::on_hash();
+  }
+
+  FMT_CONSTEXPR void on_localized() {
+    require_numeric_argument();
+    Handler::on_localized();
+  }
+
+  FMT_CONSTEXPR void on_zero() {
+    require_numeric_argument();
+    Handler::on_zero();
+  }
+
+  FMT_CONSTEXPR void end_precision() {
+    if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type)
+      this->on_error("precision not allowed for this argument type");
+  }
+};
+
+constexpr int invalid_arg_index = -1;
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <int N, typename T, typename... Args, typename Char>
+constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
+  if constexpr (detail::is_statically_named_arg<T>()) {
+    if (name == T::name) return N;
+  }
+  if constexpr (sizeof...(Args) > 0) {
+    return get_arg_index_by_name<N + 1, Args...>(name);
+  } else {
+    (void)name;  // Workaround an MSVC bug about "unused" parameter.
+    return invalid_arg_index;
+  }
+}
+#endif
+
+template <typename... Args, typename Char>
+FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+  if constexpr (sizeof...(Args) > 0) {
+    return get_arg_index_by_name<0, Args...>(name);
+  } else {
+    (void)name;
+    return invalid_arg_index;
+  }
+#else
+  (void)name;
+  return invalid_arg_index;
+#endif
+}
+
+template <typename Char, typename ErrorHandler, typename... Args>
+class format_string_checker {
+ private:
+  using parse_context_type = compile_parse_context<Char, ErrorHandler>;
+  enum { num_args = sizeof...(Args) };
+
+  // Format specifier parsing function.
+  using parse_func = const Char* (*)(parse_context_type&);
+
+  parse_context_type context_;
+  parse_func parse_funcs_[num_args > 0 ? num_args : 1];
+
+ public:
+  explicit FMT_CONSTEXPR format_string_checker(
+      basic_string_view<Char> format_str, ErrorHandler eh)
+      : context_(format_str, num_args, eh),
+        parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
+
+  FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+
+  FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); }
+  FMT_CONSTEXPR auto on_arg_id(int id) -> int {
+    return context_.check_arg_id(id), id;
+  }
+  FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int {
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+    auto index = get_arg_index_by_name<Args...>(id);
+    if (index == invalid_arg_index) on_error("named argument is not found");
+    return context_.check_arg_id(index), index;
+#else
+    (void)id;
+    on_error("compile-time checks for named arguments require C++20 support");
+    return 0;
+#endif
+  }
+
+  FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
+
+  FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*)
+      -> const Char* {
+    context_.advance_to(context_.begin() + (begin - &*context_.begin()));
+    // id >= 0 check is a workaround for gcc 10 bug (#2065).
+    return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin;
+  }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    context_.on_error(message);
+  }
+};
+
+template <typename... Args, typename S,
+          enable_if_t<(is_compile_string<S>::value), int>>
+void check_format_string(S format_str) {
+  FMT_CONSTEXPR auto s = to_string_view(format_str);
+  using checker = format_string_checker<typename S::char_type, error_handler,
+                                        remove_cvref_t<Args>...>;
+  FMT_CONSTEXPR bool invalid_format =
+      (parse_format_string<true>(s, checker(s, {})), true);
+  ignore_unused(invalid_format);
+}
+
+template <typename Char>
+void vformat_to(
+    buffer<Char>& buf, basic_string_view<Char> fmt,
+    basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
+    locale_ref loc = {});
+
+FMT_API void vprint_mojibake(std::FILE*, string_view, format_args);
+#ifndef _WIN32
+inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
+#endif
+FMT_END_DETAIL_NAMESPACE
+
+// A formatter specialization for the core types corresponding to detail::type
+// constants.
+template <typename T, typename Char>
+struct formatter<T, Char,
+                 enable_if_t<detail::type_constant<T, Char>::value !=
+                             detail::type::custom_type>> {
+ private:
+  detail::dynamic_format_specs<Char> specs_;
+
+ public:
+  // Parses format specifiers stopping either at the end of the range or at the
+  // terminating '}'.
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    auto begin = ctx.begin(), end = ctx.end();
+    if (begin == end) return begin;
+    using handler_type = detail::dynamic_specs_handler<ParseContext>;
+    auto type = detail::type_constant<T, Char>::value;
+    auto checker =
+        detail::specs_checker<handler_type>(handler_type(specs_, ctx), type);
+    auto it = detail::parse_format_specs(begin, end, checker);
+    auto eh = ctx.error_handler();
+    switch (type) {
+    case detail::type::none_type:
+      FMT_ASSERT(false, "invalid argument type");
+      break;
+    case detail::type::bool_type:
+      if (!specs_.type || specs_.type == 's') break;
+      FMT_FALLTHROUGH;
+    case detail::type::int_type:
+    case detail::type::uint_type:
+    case detail::type::long_long_type:
+    case detail::type::ulong_long_type:
+    case detail::type::int128_type:
+    case detail::type::uint128_type:
+      detail::check_int_type_spec(specs_.type, eh);
+      break;
+    case detail::type::char_type:
+      detail::check_char_specs(specs_, eh);
+      break;
+    case detail::type::float_type:
+      if (detail::const_check(FMT_USE_FLOAT))
+        detail::parse_float_type_spec(specs_, eh);
+      else
+        FMT_ASSERT(false, "float support disabled");
+      break;
+    case detail::type::double_type:
+      if (detail::const_check(FMT_USE_DOUBLE))
+        detail::parse_float_type_spec(specs_, eh);
+      else
+        FMT_ASSERT(false, "double support disabled");
+      break;
+    case detail::type::long_double_type:
+      if (detail::const_check(FMT_USE_LONG_DOUBLE))
+        detail::parse_float_type_spec(specs_, eh);
+      else
+        FMT_ASSERT(false, "long double support disabled");
+      break;
+    case detail::type::cstring_type:
+      detail::check_cstring_type_spec(specs_.type, eh);
+      break;
+    case detail::type::string_type:
+      detail::check_string_type_spec(specs_.type, eh);
+      break;
+    case detail::type::pointer_type:
+      detail::check_pointer_type_spec(specs_.type, eh);
+      break;
+    case detail::type::custom_type:
+      // Custom format specifiers are checked in parse functions of
+      // formatter specializations.
+      break;
+    }
+    return it;
+  }
+
+  template <typename FormatContext>
+  FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const
+      -> decltype(ctx.out());
+};
+
+template <typename Char> struct basic_runtime { basic_string_view<Char> str; };
+
+template <typename Char, typename... Args> class basic_format_string {
+ private:
+  basic_string_view<Char> str_;
+
+ public:
+  template <typename S,
+            FMT_ENABLE_IF(
+                std::is_convertible<const S&, basic_string_view<Char>>::value)>
+  FMT_CONSTEVAL basic_format_string(const S& s) : str_(s) {
+    static_assert(
+        detail::count<
+            (std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
+             std::is_reference<Args>::value)...>() == 0,
+        "passing views as lvalues is disallowed");
+#ifdef FMT_HAS_CONSTEVAL
+    if constexpr (detail::count_named_args<Args...>() == 0) {
+      using checker = detail::format_string_checker<Char, detail::error_handler,
+                                                    remove_cvref_t<Args>...>;
+      detail::parse_format_string<true>(str_, checker(s, {}));
+    }
+#else
+    detail::check_format_string<Args...>(s);
+#endif
+  }
+  basic_format_string(basic_runtime<Char> r) : str_(r.str) {}
+
+  FMT_INLINE operator basic_string_view<Char>() const { return str_; }
+};
+
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+// Workaround broken conversion on older gcc.
+template <typename... Args> using format_string = string_view;
+template <typename S> auto runtime(const S& s) -> basic_string_view<char_t<S>> {
+  return s;
+}
+#else
+template <typename... Args>
+using format_string = basic_format_string<char, type_identity_t<Args>...>;
+// Creates a runtime format string.
+template <typename S> auto runtime(const S& s) -> basic_runtime<char_t<S>> {
+  return {{s}};
+}
+#endif
+
+FMT_API auto vformat(string_view fmt, format_args args) -> std::string;
+
+/**
+  \rst
+  Formats ``args`` according to specifications in ``fmt`` and returns the result
+  as a string.
+
+  **Example**::
+
+    #include <fmt/core.h>
+    std::string message = fmt::format("The answer is {}", 42);
+  \endrst
+*/
+template <typename... T>
+FMT_INLINE auto format(format_string<T...> fmt, T&&... args) -> std::string {
+  return vformat(fmt, fmt::make_format_args(args...));
+}
+
+/** Formats a string and writes the output to ``out``. */
+template <typename OutputIt,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt {
+  using detail::get_buffer;
+  auto&& buf = get_buffer<char>(out);
+  detail::vformat_to(buf, string_view(fmt), args, {});
+  return detail::get_iterator(buf);
+}
+
+/**
+ \rst
+ Formats ``args`` according to specifications in ``fmt``, writes the result to
+ the output iterator ``out`` and returns the iterator past the end of the output
+ range.
+
+ **Example**::
+
+   auto out = std::vector<char>();
+   fmt::format_to(std::back_inserter(out), "{}", 42);
+ \endrst
+ */
+template <typename OutputIt, typename... T,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+FMT_INLINE auto format_to(OutputIt out, format_string<T...> fmt, T&&... args)
+    -> OutputIt {
+  return vformat_to(out, fmt, fmt::make_format_args(args...));
+}
+
+template <typename OutputIt> struct format_to_n_result {
+  /** Iterator past the end of the output range. */
+  OutputIt out;
+  /** Total (not truncated) output size. */
+  size_t size;
+};
+
+template <typename OutputIt, typename... T,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args)
+    -> format_to_n_result<OutputIt> {
+  using buffer =
+      detail::iterator_buffer<OutputIt, char, detail::fixed_buffer_traits>;
+  auto buf = buffer(out, n);
+  detail::vformat_to(buf, fmt, args, {});
+  return {buf.out(), buf.count()};
+}
+
+/**
+  \rst
+  Formats ``args`` according to specifications in ``fmt``, writes up to ``n``
+  characters of the result to the output iterator ``out`` and returns the total
+  (not truncated) output size and the iterator past the end of the output range.
+  \endrst
+ */
+template <typename OutputIt, typename... T,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string<T...> fmt,
+                            const T&... args) -> format_to_n_result<OutputIt> {
+  return vformat_to_n(out, n, fmt, fmt::make_format_args(args...));
+}
+
+/** Returns the number of chars in the output of ``format(fmt, args...)``. */
+template <typename... T>
+FMT_INLINE auto formatted_size(format_string<T...> fmt, T&&... args) -> size_t {
+  auto buf = detail::counting_buffer<>();
+  detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {});
+  return buf.count();
+}
+
+FMT_API void vprint(string_view fmt, format_args args);
+FMT_API void vprint(std::FILE* f, string_view fmt, format_args args);
+
+/**
+  \rst
+  Formats ``args`` according to specifications in ``fmt`` and writes the output
+  to ``stdout``.
+
+  **Example**::
+
+    fmt::print("Elapsed time: {0:.2f} seconds", 1.23);
+  \endrst
+ */
+template <typename... T>
+FMT_INLINE void print(format_string<T...> fmt, T&&... args) {
+  const auto& vargs = fmt::make_format_args(args...);
+  return detail::is_utf8() ? vprint(fmt, vargs)
+                           : detail::vprint_mojibake(stdout, fmt, vargs);
+}
+
+/**
+  \rst
+  Formats ``args`` according to specifications in ``fmt`` and writes the
+  output to the file ``f``.
+
+  **Example**::
+
+    fmt::print(stderr, "Don't {}!", "panic");
+  \endrst
+ */
+template <typename... T>
+FMT_INLINE void print(std::FILE* f, format_string<T...> fmt, T&&... args) {
+  const auto& vargs = fmt::make_format_args(args...);
+  return detail::is_utf8() ? vprint(f, fmt, vargs)
+                           : detail::vprint_mojibake(f, fmt, vargs);
+}
+
+FMT_MODULE_EXPORT_END
+FMT_GCC_PRAGMA("GCC pop_options")
+FMT_END_NAMESPACE
+
+#ifdef FMT_HEADER_ONLY
+#  include "format.h"
+#endif
+#endif  // FMT_CORE_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/format-inl.h b/wpiutil/src/main/native/fmtlib/include/fmt/format-inl.h
new file mode 100644
index 0000000..94a36d1
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/format-inl.h
@@ -0,0 +1,2620 @@
+// Formatting library for C++ - implementation
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_FORMAT_INL_H_
+#define FMT_FORMAT_INL_H_
+
+#include <algorithm>
+#include <cctype>
+#include <cerrno>  // errno
+#include <climits>
+#include <cmath>
+#include <cstdarg>
+#include <cstring>  // std::memmove
+#include <cwchar>
+#include <exception>
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+#  include <locale>
+#endif
+
+#ifdef _WIN32
+#  include <io.h>  // _isatty
+#endif
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
+  // Use unchecked std::fprintf to avoid triggering another assertion when
+  // writing to stderr fails
+  std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
+  // Chosen instead of std::abort to satisfy Clang in CUDA mode during device
+  // code pass.
+  std::terminate();
+}
+
+#ifndef _MSC_VER
+#  define FMT_SNPRINTF snprintf
+#else  // _MSC_VER
+inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) {
+  va_list args;
+  va_start(args, format);
+  int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
+  va_end(args);
+  return result;
+}
+#  define FMT_SNPRINTF fmt_snprintf
+#endif  // _MSC_VER
+
+FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
+                                string_view message) FMT_NOEXCEPT {
+  // Report error code making sure that the output fits into
+  // inline_buffer_size to avoid dynamic memory allocation and potential
+  // bad_alloc.
+  out.try_resize(0);
+  static const char SEP[] = ": ";
+  static const char ERROR_STR[] = "error ";
+  // Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
+  size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
+  auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);
+  if (detail::is_negative(error_code)) {
+    abs_value = 0 - abs_value;
+    ++error_code_size;
+  }
+  error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
+  auto it = buffer_appender<char>(out);
+  if (message.size() <= inline_buffer_size - error_code_size)
+    format_to(it, FMT_STRING("{}{}"), message, SEP);
+  format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
+  FMT_ASSERT(out.size() <= inline_buffer_size, "");
+}
+
+FMT_FUNC void report_error(format_func func, int error_code,
+                           const char* message) FMT_NOEXCEPT {
+  memory_buffer full_message;
+  func(full_message, error_code, message);
+  // Don't use fwrite_fully because the latter may throw.
+  if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0)
+    std::fputc('\n', stderr);
+}
+
+// A wrapper around fwrite that throws on error.
+inline void fwrite_fully(const void* ptr, size_t size, size_t count,
+                         FILE* stream) {
+  size_t written = std::fwrite(ptr, size, count, stream);
+  if (written < count) FMT_THROW(system_error(errno, "cannot write to file"));
+}
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+template <typename Locale>
+locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
+  static_assert(std::is_same<Locale, std::locale>::value, "");
+}
+
+template <typename Locale> Locale locale_ref::get() const {
+  static_assert(std::is_same<Locale, std::locale>::value, "");
+  return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
+}
+
+template <typename Char>
+FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
+  auto& facet = std::use_facet<std::numpunct<Char>>(loc.get<std::locale>());
+  auto grouping = facet.grouping();
+  auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
+  return {std::move(grouping), thousands_sep};
+}
+template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
+  return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
+      .decimal_point();
+}
+#else
+template <typename Char>
+FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result<Char> {
+  return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR};
+}
+template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
+  return '.';
+}
+#endif
+}  // namespace detail
+
+#if !FMT_MSC_VER
+FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default;
+#endif
+
+FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str,
+                                         format_args args) {
+  auto ec = std::error_code(error_code, std::generic_category());
+  return std::system_error(ec, vformat(format_str, args));
+}
+
+namespace detail {
+
+template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
+  // fallback_uintptr is always stored in little endian.
+  int i = static_cast<int>(sizeof(void*)) - 1;
+  while (i > 0 && n.value[i] == 0) --i;
+  auto char_digits = std::numeric_limits<unsigned char>::digits / 4;
+  return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
+}
+
+#if __cplusplus < 201703L
+template <typename T> constexpr const char basic_data<T>::digits[][2];
+template <typename T> constexpr const char basic_data<T>::hex_digits[];
+template <typename T> constexpr const char basic_data<T>::signs[];
+template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
+template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
+template <typename T>
+constexpr const char basic_data<T>::right_padding_shifts[];
+#endif
+
+template <typename T> struct bits {
+  static FMT_CONSTEXPR_DECL const int value =
+      static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
+};
+
+class fp;
+template <int SHIFT = 0> fp normalize(fp value);
+
+// Lower (upper) boundary is a value half way between a floating-point value
+// and its predecessor (successor). Boundaries have the same exponent as the
+// value so only significands are stored.
+struct boundaries {
+  uint64_t lower;
+  uint64_t upper;
+};
+
+// A handmade floating-point number f * pow(2, e).
+class fp {
+ private:
+  using significand_type = uint64_t;
+
+  template <typename Float>
+  using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
+                                           sizeof(Float) == sizeof(uint32_t)>;
+
+ public:
+  significand_type f;
+  int e;
+
+  // All sizes are in bits.
+  // Subtract 1 to account for an implicit most significant bit in the
+  // normalized form.
+  static FMT_CONSTEXPR_DECL const int double_significand_size =
+      std::numeric_limits<double>::digits - 1;
+  static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
+      1ULL << double_significand_size;
+  static FMT_CONSTEXPR_DECL const int significand_size =
+      bits<significand_type>::value;
+
+  fp() : f(0), e(0) {}
+  fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
+
+  // Constructs fp from an IEEE754 double. It is a template to prevent compile
+  // errors on platforms where double is not IEEE754.
+  template <typename Double> explicit fp(Double d) { assign(d); }
+
+  // Assigns d to this and return true iff predecessor is closer than successor.
+  template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)>
+  bool assign(Float d) {
+    // Assume float is in the format [sign][exponent][significand].
+    using limits = std::numeric_limits<Float>;
+    const int float_significand_size = limits::digits - 1;
+    const int exponent_size =
+        bits<Float>::value - float_significand_size - 1;  // -1 for sign
+    const uint64_t float_implicit_bit = 1ULL << float_significand_size;
+    const uint64_t significand_mask = float_implicit_bit - 1;
+    const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
+    const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
+    constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
+    auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d);
+    f = u & significand_mask;
+    int biased_e =
+        static_cast<int>((u & exponent_mask) >> float_significand_size);
+    // Predecessor is closer if d is a normalized power of 2 (f == 0) other than
+    // the smallest normalized number (biased_e > 1).
+    bool is_predecessor_closer = f == 0 && biased_e > 1;
+    if (biased_e != 0)
+      f += float_implicit_bit;
+    else
+      biased_e = 1;  // Subnormals use biased exponent 1 (min exponent).
+    e = biased_e - exponent_bias - float_significand_size;
+    return is_predecessor_closer;
+  }
+
+  template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)>
+  bool assign(Float) {
+    *this = fp();
+    return false;
+  }
+};
+
+// Normalizes the value converted from double and multiplied by (1 << SHIFT).
+template <int SHIFT> fp normalize(fp value) {
+  // Handle subnormals.
+  const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
+  while ((value.f & shifted_implicit_bit) == 0) {
+    value.f <<= 1;
+    --value.e;
+  }
+  // Subtract 1 to account for hidden bit.
+  const auto offset =
+      fp::significand_size - fp::double_significand_size - SHIFT - 1;
+  value.f <<= offset;
+  value.e -= offset;
+  return value;
+}
+
+inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
+
+// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
+inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
+#if FMT_USE_INT128
+  auto product = static_cast<__uint128_t>(lhs) * rhs;
+  auto f = static_cast<uint64_t>(product >> 64);
+  return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;
+#else
+  // Multiply 32-bit parts of significands.
+  uint64_t mask = (1ULL << 32) - 1;
+  uint64_t a = lhs >> 32, b = lhs & mask;
+  uint64_t c = rhs >> 32, d = rhs & mask;
+  uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
+  // Compute mid 64-bit of result and round.
+  uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
+  return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);
+#endif
+}
+
+inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; }
+
+// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
+// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
+inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
+  // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
+  // These are generated by support/compute-powers.py.
+  static constexpr const uint64_t pow10_significands[] = {
+      0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
+      0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
+      0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
+      0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
+      0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
+      0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
+      0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
+      0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
+      0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
+      0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
+      0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
+      0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
+      0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
+      0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
+      0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
+      0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
+      0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
+      0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
+      0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
+      0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
+      0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
+      0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
+      0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
+      0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
+      0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
+      0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
+      0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
+      0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
+      0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
+  };
+
+  // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
+  // to significands above.
+  static constexpr const int16_t pow10_exponents[] = {
+      -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
+      -927,  -901,  -874,  -847,  -821,  -794,  -768,  -741,  -715,  -688, -661,
+      -635,  -608,  -582,  -555,  -529,  -502,  -475,  -449,  -422,  -396, -369,
+      -343,  -316,  -289,  -263,  -236,  -210,  -183,  -157,  -130,  -103, -77,
+      -50,   -24,   3,     30,    56,    83,    109,   136,   162,   189,  216,
+      242,   269,   295,   322,   348,   375,   402,   428,   455,   481,  508,
+      534,   561,   588,   614,   641,   667,   694,   720,   747,   774,  800,
+      827,   853,   880,   907,   933,   960,   986,   1013,  1039,  1066};
+
+  const int shift = 32;
+  const auto significand = static_cast<int64_t>(data::log10_2_significand);
+  int index = static_cast<int>(
+      ((min_exponent + fp::significand_size - 1) * (significand >> shift) +
+       ((int64_t(1) << shift) - 1))  // ceil
+      >> 32                          // arithmetic shift
+  );
+  // Decimal exponent of the first (smallest) cached power of 10.
+  const int first_dec_exp = -348;
+  // Difference between 2 consecutive decimal exponents in cached powers of 10.
+  const int dec_exp_step = 8;
+  index = (index - first_dec_exp - 1) / dec_exp_step + 1;
+  pow10_exponent = first_dec_exp + index * dec_exp_step;
+  return {pow10_significands[index], pow10_exponents[index]};
+}
+
+// A simple accumulator to hold the sums of terms in bigint::square if uint128_t
+// is not available.
+struct accumulator {
+  uint64_t lower;
+  uint64_t upper;
+
+  accumulator() : lower(0), upper(0) {}
+  explicit operator uint32_t() const { return static_cast<uint32_t>(lower); }
+
+  void operator+=(uint64_t n) {
+    lower += n;
+    if (lower < n) ++upper;
+  }
+  void operator>>=(int shift) {
+    FMT_ASSERT(shift == 32, "");
+    (void)shift;
+    lower = (upper << 32) | (lower >> 32);
+    upper >>= 32;
+  }
+};
+
+class bigint {
+ private:
+  // A bigint is stored as an array of bigits (big digits), with bigit at index
+  // 0 being the least significant one.
+  using bigit = uint32_t;
+  using double_bigit = uint64_t;
+  enum { bigits_capacity = 32 };
+  basic_memory_buffer<bigit, bigits_capacity> bigits_;
+  int exp_;
+
+  bigit operator[](int index) const { return bigits_[to_unsigned(index)]; }
+  bigit& operator[](int index) { return bigits_[to_unsigned(index)]; }
+
+  static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
+
+  friend struct formatter<bigint>;
+
+  void subtract_bigits(int index, bigit other, bigit& borrow) {
+    auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
+    (*this)[index] = static_cast<bigit>(result);
+    borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
+  }
+
+  void remove_leading_zeros() {
+    int num_bigits = static_cast<int>(bigits_.size()) - 1;
+    while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
+    bigits_.resize(to_unsigned(num_bigits + 1));
+  }
+
+  // Computes *this -= other assuming aligned bigints and *this >= other.
+  void subtract_aligned(const bigint& other) {
+    FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
+    FMT_ASSERT(compare(*this, other) >= 0, "");
+    bigit borrow = 0;
+    int i = other.exp_ - exp_;
+    for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j)
+      subtract_bigits(i, other.bigits_[j], borrow);
+    while (borrow > 0) subtract_bigits(i, 0, borrow);
+    remove_leading_zeros();
+  }
+
+  void multiply(uint32_t value) {
+    const double_bigit wide_value = value;
+    bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      double_bigit result = bigits_[i] * wide_value + carry;
+      bigits_[i] = static_cast<bigit>(result);
+      carry = static_cast<bigit>(result >> bigit_bits);
+    }
+    if (carry != 0) bigits_.push_back(carry);
+  }
+
+  void multiply(uint64_t value) {
+    const bigit mask = ~bigit(0);
+    const double_bigit lower = value & mask;
+    const double_bigit upper = value >> bigit_bits;
+    double_bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      double_bigit result = bigits_[i] * lower + (carry & mask);
+      carry =
+          bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits);
+      bigits_[i] = static_cast<bigit>(result);
+    }
+    while (carry != 0) {
+      bigits_.push_back(carry & mask);
+      carry >>= bigit_bits;
+    }
+  }
+
+ public:
+  bigint() : exp_(0) {}
+  explicit bigint(uint64_t n) { assign(n); }
+  ~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); }
+
+  bigint(const bigint&) = delete;
+  void operator=(const bigint&) = delete;
+
+  void assign(const bigint& other) {
+    auto size = other.bigits_.size();
+    bigits_.resize(size);
+    auto data = other.bigits_.data();
+    std::copy(data, data + size, make_checked(bigits_.data(), size));
+    exp_ = other.exp_;
+  }
+
+  void assign(uint64_t n) {
+    size_t num_bigits = 0;
+    do {
+      bigits_[num_bigits++] = n & ~bigit(0);
+      n >>= bigit_bits;
+    } while (n != 0);
+    bigits_.resize(num_bigits);
+    exp_ = 0;
+  }
+
+  int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
+
+  FMT_NOINLINE bigint& operator<<=(int shift) {
+    FMT_ASSERT(shift >= 0, "");
+    exp_ += shift / bigit_bits;
+    shift %= bigit_bits;
+    if (shift == 0) return *this;
+    bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      bigit c = bigits_[i] >> (bigit_bits - shift);
+      bigits_[i] = (bigits_[i] << shift) + carry;
+      carry = c;
+    }
+    if (carry != 0) bigits_.push_back(carry);
+    return *this;
+  }
+
+  template <typename Int> bigint& operator*=(Int value) {
+    FMT_ASSERT(value > 0, "");
+    multiply(uint32_or_64_or_128_t<Int>(value));
+    return *this;
+  }
+
+  friend int compare(const bigint& lhs, const bigint& rhs) {
+    int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
+    if (num_lhs_bigits != num_rhs_bigits)
+      return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
+    int i = static_cast<int>(lhs.bigits_.size()) - 1;
+    int j = static_cast<int>(rhs.bigits_.size()) - 1;
+    int end = i - j;
+    if (end < 0) end = 0;
+    for (; i >= end; --i, --j) {
+      bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j];
+      if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1;
+    }
+    if (i != j) return i > j ? 1 : -1;
+    return 0;
+  }
+
+  // Returns compare(lhs1 + lhs2, rhs).
+  friend int add_compare(const bigint& lhs1, const bigint& lhs2,
+                         const bigint& rhs) {
+    int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
+    int num_rhs_bigits = rhs.num_bigits();
+    if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;
+    if (max_lhs_bigits > num_rhs_bigits) return 1;
+    auto get_bigit = [](const bigint& n, int i) -> bigit {
+      return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0;
+    };
+    double_bigit borrow = 0;
+    int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_);
+    for (int i = num_rhs_bigits - 1; i >= min_exp; --i) {
+      double_bigit sum =
+          static_cast<double_bigit>(get_bigit(lhs1, i)) + get_bigit(lhs2, i);
+      bigit rhs_bigit = get_bigit(rhs, i);
+      if (sum > rhs_bigit + borrow) return 1;
+      borrow = rhs_bigit + borrow - sum;
+      if (borrow > 1) return -1;
+      borrow <<= bigit_bits;
+    }
+    return borrow != 0 ? -1 : 0;
+  }
+
+  // Assigns pow(10, exp) to this bigint.
+  void assign_pow10(int exp) {
+    FMT_ASSERT(exp >= 0, "");
+    if (exp == 0) return assign(1);
+    // Find the top bit.
+    int bitmask = 1;
+    while (exp >= bitmask) bitmask <<= 1;
+    bitmask >>= 1;
+    // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by
+    // repeated squaring and multiplication.
+    assign(5);
+    bitmask >>= 1;
+    while (bitmask != 0) {
+      square();
+      if ((exp & bitmask) != 0) *this *= 5;
+      bitmask >>= 1;
+    }
+    *this <<= exp;  // Multiply by pow(2, exp) by shifting.
+  }
+
+  void square() {
+    int num_bigits = static_cast<int>(bigits_.size());
+    int num_result_bigits = 2 * num_bigits;
+    basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
+    bigits_.resize(to_unsigned(num_result_bigits));
+    using accumulator_t = conditional_t<FMT_USE_INT128, uint128_t, accumulator>;
+    auto sum = accumulator_t();
+    for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) {
+      // Compute bigit at position bigit_index of the result by adding
+      // cross-product terms n[i] * n[j] such that i + j == bigit_index.
+      for (int i = 0, j = bigit_index; j >= 0; ++i, --j) {
+        // Most terms are multiplied twice which can be optimized in the future.
+        sum += static_cast<double_bigit>(n[i]) * n[j];
+      }
+      (*this)[bigit_index] = static_cast<bigit>(sum);
+      sum >>= bits<bigit>::value;  // Compute the carry.
+    }
+    // Do the same for the top half.
+    for (int bigit_index = num_bigits; bigit_index < num_result_bigits;
+         ++bigit_index) {
+      for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;)
+        sum += static_cast<double_bigit>(n[i++]) * n[j--];
+      (*this)[bigit_index] = static_cast<bigit>(sum);
+      sum >>= bits<bigit>::value;
+    }
+    remove_leading_zeros();
+    exp_ *= 2;
+  }
+
+  // If this bigint has a bigger exponent than other, adds trailing zero to make
+  // exponents equal. This simplifies some operations such as subtraction.
+  void align(const bigint& other) {
+    int exp_difference = exp_ - other.exp_;
+    if (exp_difference <= 0) return;
+    int num_bigits = static_cast<int>(bigits_.size());
+    bigits_.resize(to_unsigned(num_bigits + exp_difference));
+    for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
+      bigits_[j] = bigits_[i];
+    std::uninitialized_fill_n(bigits_.data(), exp_difference, 0);
+    exp_ -= exp_difference;
+  }
+
+  // Divides this bignum by divisor, assigning the remainder to this and
+  // returning the quotient.
+  int divmod_assign(const bigint& divisor) {
+    FMT_ASSERT(this != &divisor, "");
+    if (compare(*this, divisor) < 0) return 0;
+    FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
+    align(divisor);
+    int quotient = 0;
+    do {
+      subtract_aligned(divisor);
+      ++quotient;
+    } while (compare(*this, divisor) >= 0);
+    return quotient;
+  }
+};
+
+enum class round_direction { unknown, up, down };
+
+// Given the divisor (normally a power of 10), the remainder = v % divisor for
+// some number v and the error, returns whether v should be rounded up, down, or
+// whether the rounding direction can't be determined due to error.
+// error should be less than divisor / 2.
+inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder,
+                                           uint64_t error) {
+  FMT_ASSERT(remainder < divisor, "");  // divisor - remainder won't overflow.
+  FMT_ASSERT(error < divisor, "");      // divisor - error won't overflow.
+  FMT_ASSERT(error < divisor - error, "");  // error * 2 won't overflow.
+  // Round down if (remainder + error) * 2 <= divisor.
+  if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2)
+    return round_direction::down;
+  // Round up if (remainder - error) * 2 >= divisor.
+  if (remainder >= error &&
+      remainder - error >= divisor - (remainder - error)) {
+    return round_direction::up;
+  }
+  return round_direction::unknown;
+}
+
+namespace digits {
+enum result {
+  more,  // Generate more digits.
+  done,  // Done generating digits.
+  error  // Digit generation cancelled due to an error.
+};
+}
+
+inline uint64_t power_of_10_64(int exp) {
+  static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1),
+                                            FMT_POWERS_OF_10(1000000000ULL),
+                                            10000000000000000000ULL};
+  return data[exp];
+}
+
+// Generates output using the Grisu digit-gen algorithm.
+// error: the size of the region (lower, upper) outside of which numbers
+// definitely do not round to value (Delta in Grisu3).
+template <typename Handler>
+FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
+                                           Handler& handler) {
+  const fp one(1ULL << -value.e, value.e);
+  // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
+  // zero because it contains a product of two 64-bit numbers with MSB set (due
+  // to normalization) - 1, shifted right by at most 60 bits.
+  auto integral = static_cast<uint32_t>(value.f >> -one.e);
+  FMT_ASSERT(integral != 0, "");
+  FMT_ASSERT(integral == value.f >> -one.e, "");
+  // The fractional part of scaled value (p2 in Grisu) c = value % one.
+  uint64_t fractional = value.f & (one.f - 1);
+  exp = count_digits(integral);  // kappa in Grisu.
+  // Divide by 10 to prevent overflow.
+  auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e,
+                                 value.f / 10, error * 10, exp);
+  if (result != digits::more) return result;
+  // Generate digits for the integral part. This can produce up to 10 digits.
+  do {
+    uint32_t digit = 0;
+    auto divmod_integral = [&](uint32_t divisor) {
+      digit = integral / divisor;
+      integral %= divisor;
+    };
+    // This optimization by Milo Yip reduces the number of integer divisions by
+    // one per iteration.
+    switch (exp) {
+    case 10:
+      divmod_integral(1000000000);
+      break;
+    case 9:
+      divmod_integral(100000000);
+      break;
+    case 8:
+      divmod_integral(10000000);
+      break;
+    case 7:
+      divmod_integral(1000000);
+      break;
+    case 6:
+      divmod_integral(100000);
+      break;
+    case 5:
+      divmod_integral(10000);
+      break;
+    case 4:
+      divmod_integral(1000);
+      break;
+    case 3:
+      divmod_integral(100);
+      break;
+    case 2:
+      divmod_integral(10);
+      break;
+    case 1:
+      digit = integral;
+      integral = 0;
+      break;
+    default:
+      FMT_ASSERT(false, "invalid number of digits");
+    }
+    --exp;
+    auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
+    result = handler.on_digit(static_cast<char>('0' + digit),
+                              power_of_10_64(exp) << -one.e, remainder, error,
+                              exp, true);
+    if (result != digits::more) return result;
+  } while (exp > 0);
+  // Generate digits for the fractional part.
+  for (;;) {
+    fractional *= 10;
+    error *= 10;
+    char digit = static_cast<char>('0' + (fractional >> -one.e));
+    fractional &= one.f - 1;
+    --exp;
+    result = handler.on_digit(digit, one.f, fractional, error, exp, false);
+    if (result != digits::more) return result;
+  }
+}
+
+// The fixed precision digit handler.
+struct fixed_handler {
+  char* buf;
+  int size;
+  int precision;
+  int exp10;
+  bool fixed;
+
+  digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
+                          int& exp) {
+    // Non-fixed formats require at least one digit and no precision adjustment.
+    if (!fixed) return digits::more;
+    // Adjust fixed precision by exponent because it is relative to decimal
+    // point.
+    precision += exp + exp10;
+    // Check if precision is satisfied just by leading zeros, e.g.
+    // format("{:.2f}", 0.001) gives "0.00" without generating any digits.
+    if (precision > 0) return digits::more;
+    if (precision < 0) return digits::done;
+    auto dir = get_round_direction(divisor, remainder, error);
+    if (dir == round_direction::unknown) return digits::error;
+    buf[size++] = dir == round_direction::up ? '1' : '0';
+    return digits::done;
+  }
+
+  digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
+                          uint64_t error, int, bool integral) {
+    FMT_ASSERT(remainder < divisor, "");
+    buf[size++] = digit;
+    if (!integral && error >= remainder) return digits::error;
+    if (size < precision) return digits::more;
+    if (!integral) {
+      // Check if error * 2 < divisor with overflow prevention.
+      // The check is not needed for the integral part because error = 1
+      // and divisor > (1 << 32) there.
+      if (error >= divisor || error >= divisor - error) return digits::error;
+    } else {
+      FMT_ASSERT(error == 1 && divisor > 2, "");
+    }
+    auto dir = get_round_direction(divisor, remainder, error);
+    if (dir != round_direction::up)
+      return dir == round_direction::down ? digits::done : digits::error;
+    ++buf[size - 1];
+    for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
+      buf[i] = '0';
+      ++buf[i - 1];
+    }
+    if (buf[0] > '9') {
+      buf[0] = '1';
+      if (fixed)
+        buf[size++] = '0';
+      else
+        ++exp10;
+    }
+    return digits::done;
+  }
+};
+
+// A 128-bit integer type used internally,
+struct uint128_wrapper {
+  uint128_wrapper() = default;
+
+#if FMT_USE_INT128
+  uint128_t internal_;
+
+  constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT
+      : internal_{static_cast<uint128_t>(low) |
+                  (static_cast<uint128_t>(high) << 64)} {}
+
+  constexpr uint128_wrapper(uint128_t u) : internal_{u} {}
+
+  constexpr uint64_t high() const FMT_NOEXCEPT {
+    return uint64_t(internal_ >> 64);
+  }
+  constexpr uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); }
+
+  uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
+    internal_ += n;
+    return *this;
+  }
+#else
+  uint64_t high_;
+  uint64_t low_;
+
+  constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT
+      : high_{high},
+        low_{low} {}
+
+  constexpr uint64_t high() const FMT_NOEXCEPT { return high_; }
+  constexpr uint64_t low() const FMT_NOEXCEPT { return low_; }
+
+  uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
+#  if defined(_MSC_VER) && defined(_M_X64)
+    unsigned char carry = _addcarry_u64(0, low_, n, &low_);
+    _addcarry_u64(carry, high_, 0, &high_);
+    return *this;
+#  else
+    uint64_t sum = low_ + n;
+    high_ += (sum < low_ ? 1 : 0);
+    low_ = sum;
+    return *this;
+#  endif
+  }
+#endif
+};
+
+// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
+namespace dragonbox {
+// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
+inline uint128_wrapper umul128(uint64_t x, uint64_t y) FMT_NOEXCEPT {
+#if FMT_USE_INT128
+  return static_cast<uint128_t>(x) * static_cast<uint128_t>(y);
+#elif defined(_MSC_VER) && defined(_M_X64)
+  uint128_wrapper result;
+  result.low_ = _umul128(x, y, &result.high_);
+  return result;
+#else
+  const uint64_t mask = (uint64_t(1) << 32) - uint64_t(1);
+
+  uint64_t a = x >> 32;
+  uint64_t b = x & mask;
+  uint64_t c = y >> 32;
+  uint64_t d = y & mask;
+
+  uint64_t ac = a * c;
+  uint64_t bc = b * c;
+  uint64_t ad = a * d;
+  uint64_t bd = b * d;
+
+  uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);
+
+  return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),
+          (intermediate << 32) + (bd & mask)};
+#endif
+}
+
+// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
+inline uint64_t umul128_upper64(uint64_t x, uint64_t y) FMT_NOEXCEPT {
+#if FMT_USE_INT128
+  auto p = static_cast<uint128_t>(x) * static_cast<uint128_t>(y);
+  return static_cast<uint64_t>(p >> 64);
+#elif defined(_MSC_VER) && defined(_M_X64)
+  return __umulh(x, y);
+#else
+  return umul128(x, y).high();
+#endif
+}
+
+// Computes upper 64 bits of multiplication of a 64-bit unsigned integer and a
+// 128-bit unsigned integer.
+inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT {
+  uint128_wrapper g0 = umul128(x, y.high());
+  g0 += umul128_upper64(x, y.low());
+  return g0.high();
+}
+
+// Computes upper 32 bits of multiplication of a 32-bit unsigned integer and a
+// 64-bit unsigned integer.
+inline uint32_t umul96_upper32(uint32_t x, uint64_t y) FMT_NOEXCEPT {
+  return static_cast<uint32_t>(umul128_upper64(x, y));
+}
+
+// Computes middle 64 bits of multiplication of a 64-bit unsigned integer and a
+// 128-bit unsigned integer.
+inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT {
+  uint64_t g01 = x * y.high();
+  uint64_t g10 = umul128_upper64(x, y.low());
+  return g01 + g10;
+}
+
+// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a
+// 64-bit unsigned integer.
+inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT {
+  return x * y;
+}
+
+// Computes floor(log10(pow(2, e))) for e in [-1700, 1700] using the method from
+// https://fmt.dev/papers/Grisu-Exact.pdf#page=5, section 3.4.
+inline int floor_log10_pow2(int e) FMT_NOEXCEPT {
+  FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
+  const int shift = 22;
+  return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >>
+         shift;
+}
+
+// Various fast log computations.
+inline int floor_log2_pow10(int e) FMT_NOEXCEPT {
+  FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
+  const uint64_t log2_10_integer_part = 3;
+  const uint64_t log2_10_fractional_digits = 0x5269e12f346e2bf9;
+  const int shift_amount = 19;
+  return (e * static_cast<int>(
+                  (log2_10_integer_part << shift_amount) |
+                  (log2_10_fractional_digits >> (64 - shift_amount)))) >>
+         shift_amount;
+}
+inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT {
+  FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
+  const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375;
+  const int shift_amount = 22;
+  return (e * static_cast<int>(data::log10_2_significand >>
+                               (64 - shift_amount)) -
+          static_cast<int>(log10_4_over_3_fractional_digits >>
+                           (64 - shift_amount))) >>
+         shift_amount;
+}
+
+// Returns true iff x is divisible by pow(2, exp).
+inline bool divisible_by_power_of_2(uint32_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp >= 1, "");
+  FMT_ASSERT(x != 0, "");
+#ifdef FMT_BUILTIN_CTZ
+  return FMT_BUILTIN_CTZ(x) >= exp;
+#else
+  return exp < num_bits<uint32_t>() && x == ((x >> exp) << exp);
+#endif
+}
+inline bool divisible_by_power_of_2(uint64_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp >= 1, "");
+  FMT_ASSERT(x != 0, "");
+#ifdef FMT_BUILTIN_CTZLL
+  return FMT_BUILTIN_CTZLL(x) >= exp;
+#else
+  return exp < num_bits<uint64_t>() && x == ((x >> exp) << exp);
+#endif
+}
+
+// Table entry type for divisibility test.
+template <typename T> struct divtest_table_entry {
+  T mod_inv;
+  T max_quotient;
+};
+
+// Returns true iff x is divisible by pow(5, exp).
+inline bool divisible_by_power_of_5(uint32_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp <= 10, "too large exponent");
+  static constexpr const divtest_table_entry<uint32_t> divtest_table[] = {
+      {0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333},
+      {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba},
+      {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5},
+      {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf},
+      {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897},
+      {0x3ed61f49, 0x000001b7}};
+  return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient;
+}
+inline bool divisible_by_power_of_5(uint64_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp <= 23, "too large exponent");
+  static constexpr const divtest_table_entry<uint64_t> divtest_table[] = {
+      {0x0000000000000001, 0xffffffffffffffff},
+      {0xcccccccccccccccd, 0x3333333333333333},
+      {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70},
+      {0x1cac083126e978d5, 0x020c49ba5e353f7c},
+      {0xd288ce703afb7e91, 0x0068db8bac710cb2},
+      {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0},
+      {0x790fb65668c26139, 0x000431bde82d7b63},
+      {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a},
+      {0xc767074b22e90e21, 0x00002af31dc46118},
+      {0x8e47ce423a2e9c6d, 0x0000089705f4136b},
+      {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b},
+      {0x0fee64690c913975, 0x00000057f5ff85e5},
+      {0x3662e0e1cf503eb1, 0x000000119799812d},
+      {0xa47a2cf9f6433fbd, 0x0000000384b84d09},
+      {0x54186f653140a659, 0x00000000b424dc35},
+      {0x7738164770402145, 0x0000000024075f3d},
+      {0xe4a4d1417cd9a041, 0x000000000734aca5},
+      {0xc75429d9e5c5200d, 0x000000000170ef54},
+      {0xc1773b91fac10669, 0x000000000049c977},
+      {0x26b172506559ce15, 0x00000000000ec1e4},
+      {0xd489e3a9addec2d1, 0x000000000002f394},
+      {0x90e860bb892c8d5d, 0x000000000000971d},
+      {0x502e79bf1b6f4f79, 0x0000000000001e39},
+      {0xdcd618596be30fe5, 0x000000000000060b}};
+  return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient;
+}
+
+// Replaces n by floor(n / pow(5, N)) returning true if and only if n is
+// divisible by pow(5, N).
+// Precondition: n <= 2 * pow(5, N + 1).
+template <int N>
+bool check_divisibility_and_divide_by_pow5(uint32_t& n) FMT_NOEXCEPT {
+  static constexpr struct {
+    uint32_t magic_number;
+    int bits_for_comparison;
+    uint32_t threshold;
+    int shift_amount;
+  } infos[] = {{0xcccd, 16, 0x3333, 18}, {0xa429, 8, 0x0a, 20}};
+  constexpr auto info = infos[N - 1];
+  n *= info.magic_number;
+  const uint32_t comparison_mask = (1u << info.bits_for_comparison) - 1;
+  bool result = (n & comparison_mask) <= info.threshold;
+  n >>= info.shift_amount;
+  return result;
+}
+
+// Computes floor(n / pow(10, N)) for small n and N.
+// Precondition: n <= pow(10, N + 1).
+template <int N> uint32_t small_division_by_pow10(uint32_t n) FMT_NOEXCEPT {
+  static constexpr struct {
+    uint32_t magic_number;
+    int shift_amount;
+    uint32_t divisor_times_10;
+  } infos[] = {{0xcccd, 19, 100}, {0xa3d8, 22, 1000}};
+  constexpr auto info = infos[N - 1];
+  FMT_ASSERT(n <= info.divisor_times_10, "n is too large");
+  return n * info.magic_number >> info.shift_amount;
+}
+
+// Computes floor(n / 10^(kappa + 1)) (float)
+inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) FMT_NOEXCEPT {
+  return n / float_info<float>::big_divisor;
+}
+// Computes floor(n / 10^(kappa + 1)) (double)
+inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) FMT_NOEXCEPT {
+  return umul128_upper64(n, 0x83126e978d4fdf3c) >> 9;
+}
+
+// Various subroutines using pow10 cache
+template <class T> struct cache_accessor;
+
+template <> struct cache_accessor<float> {
+  using carrier_uint = float_info<float>::carrier_uint;
+  using cache_entry_type = uint64_t;
+
+  static uint64_t get_cached_power(int k) FMT_NOEXCEPT {
+    FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
+               "k is out of range");
+    constexpr const uint64_t pow10_significands[] = {
+        0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
+        0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
+        0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
+        0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb,
+        0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a,
+        0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810,
+        0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff,
+        0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd,
+        0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424,
+        0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b,
+        0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000,
+        0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000,
+        0xc350000000000000, 0xf424000000000000, 0x9896800000000000,
+        0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000,
+        0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000,
+        0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000,
+        0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000,
+        0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000,
+        0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0,
+        0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984,
+        0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296,
+        0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6,
+        0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20,
+        0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd,
+        0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719,
+        0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e};
+    return pow10_significands[k - float_info<float>::min_k];
+  }
+
+  static carrier_uint compute_mul(carrier_uint u,
+                                  const cache_entry_type& cache) FMT_NOEXCEPT {
+    return umul96_upper32(u, cache);
+  }
+
+  static uint32_t compute_delta(const cache_entry_type& cache,
+                                int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<uint32_t>(cache >> (64 - 1 - beta_minus_1));
+  }
+
+  static bool compute_mul_parity(carrier_uint two_f,
+                                 const cache_entry_type& cache,
+                                 int beta_minus_1) FMT_NOEXCEPT {
+    FMT_ASSERT(beta_minus_1 >= 1, "");
+    FMT_ASSERT(beta_minus_1 < 64, "");
+
+    return ((umul96_lower64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0;
+  }
+
+  static carrier_uint compute_left_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<carrier_uint>(
+        (cache - (cache >> (float_info<float>::significand_bits + 2))) >>
+        (64 - float_info<float>::significand_bits - 1 - beta_minus_1));
+  }
+
+  static carrier_uint compute_right_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<carrier_uint>(
+        (cache + (cache >> (float_info<float>::significand_bits + 1))) >>
+        (64 - float_info<float>::significand_bits - 1 - beta_minus_1));
+  }
+
+  static carrier_uint compute_round_up_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return (static_cast<carrier_uint>(
+                cache >>
+                (64 - float_info<float>::significand_bits - 2 - beta_minus_1)) +
+            1) /
+           2;
+  }
+};
+
+template <> struct cache_accessor<double> {
+  using carrier_uint = float_info<double>::carrier_uint;
+  using cache_entry_type = uint128_wrapper;
+
+  static uint128_wrapper get_cached_power(int k) FMT_NOEXCEPT {
+    FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
+               "k is out of range");
+
+    static constexpr const uint128_wrapper pow10_significands[] = {
+#if FMT_USE_FULL_CACHE_DRAGONBOX
+      {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
+      {0x9faacf3df73609b1, 0x77b191618c54e9ad},
+      {0xc795830d75038c1d, 0xd59df5b9ef6a2418},
+      {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e},
+      {0x9becce62836ac577, 0x4ee367f9430aec33},
+      {0xc2e801fb244576d5, 0x229c41f793cda740},
+      {0xf3a20279ed56d48a, 0x6b43527578c11110},
+      {0x9845418c345644d6, 0x830a13896b78aaaa},
+      {0xbe5691ef416bd60c, 0x23cc986bc656d554},
+      {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9},
+      {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa},
+      {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54},
+      {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69},
+      {0x91376c36d99995be, 0x23100809b9c21fa2},
+      {0xb58547448ffffb2d, 0xabd40a0c2832a78b},
+      {0xe2e69915b3fff9f9, 0x16c90c8f323f516d},
+      {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4},
+      {0xb1442798f49ffb4a, 0x99cd11cfdf41779d},
+      {0xdd95317f31c7fa1d, 0x40405643d711d584},
+      {0x8a7d3eef7f1cfc52, 0x482835ea666b2573},
+      {0xad1c8eab5ee43b66, 0xda3243650005eed0},
+      {0xd863b256369d4a40, 0x90bed43e40076a83},
+      {0x873e4f75e2224e68, 0x5a7744a6e804a292},
+      {0xa90de3535aaae202, 0x711515d0a205cb37},
+      {0xd3515c2831559a83, 0x0d5a5b44ca873e04},
+      {0x8412d9991ed58091, 0xe858790afe9486c3},
+      {0xa5178fff668ae0b6, 0x626e974dbe39a873},
+      {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
+      {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a},
+      {0xa139029f6a239f72, 0x1c1fffc1ebc44e81},
+      {0xc987434744ac874e, 0xa327ffb266b56221},
+      {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9},
+      {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa},
+      {0xc4ce17b399107c22, 0xcb550fb4384d21d4},
+      {0xf6019da07f549b2b, 0x7e2a53a146606a49},
+      {0x99c102844f94e0fb, 0x2eda7444cbfc426e},
+      {0xc0314325637a1939, 0xfa911155fefb5309},
+      {0xf03d93eebc589f88, 0x793555ab7eba27cb},
+      {0x96267c7535b763b5, 0x4bc1558b2f3458df},
+      {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17},
+      {0xea9c227723ee8bcb, 0x465e15a979c1cadd},
+      {0x92a1958a7675175f, 0x0bfacd89ec191eca},
+      {0xb749faed14125d36, 0xcef980ec671f667c},
+      {0xe51c79a85916f484, 0x82b7e12780e7401b},
+      {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811},
+      {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16},
+      {0xdfbdcece67006ac9, 0x67a791e093e1d49b},
+      {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1},
+      {0xaecc49914078536d, 0x58fae9f773886e19},
+      {0xda7f5bf590966848, 0xaf39a475506a899f},
+      {0x888f99797a5e012d, 0x6d8406c952429604},
+      {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84},
+      {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65},
+      {0x855c3be0a17fcd26, 0x5cf2eea09a550680},
+      {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
+      {0xd0601d8efc57b08b, 0xf13b94daf124da27},
+      {0x823c12795db6ce57, 0x76c53d08d6b70859},
+      {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f},
+      {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a},
+      {0xfe5d54150b090b02, 0xd3f93b35435d7c4d},
+      {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0},
+      {0xc6b8e9b0709f109a, 0x359ab6419ca1091c},
+      {0xf867241c8cc6d4c0, 0xc30163d203c94b63},
+      {0x9b407691d7fc44f8, 0x79e0de63425dcf1e},
+      {0xc21094364dfb5636, 0x985915fc12f542e5},
+      {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e},
+      {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43},
+      {0xbd8430bd08277231, 0x50c6ff782a838354},
+      {0xece53cec4a314ebd, 0xa4f8bf5635246429},
+      {0x940f4613ae5ed136, 0x871b7795e136be9a},
+      {0xb913179899f68584, 0x28e2557b59846e40},
+      {0xe757dd7ec07426e5, 0x331aeada2fe589d0},
+      {0x9096ea6f3848984f, 0x3ff0d2c85def7622},
+      {0xb4bca50b065abe63, 0x0fed077a756b53aa},
+      {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895},
+      {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d},
+      {0xb080392cc4349dec, 0xbd8d794d96aacfb4},
+      {0xdca04777f541c567, 0xecf0d7a0fc5583a1},
+      {0x89e42caaf9491b60, 0xf41686c49db57245},
+      {0xac5d37d5b79b6239, 0x311c2875c522ced6},
+      {0xd77485cb25823ac7, 0x7d633293366b828c},
+      {0x86a8d39ef77164bc, 0xae5dff9c02033198},
+      {0xa8530886b54dbdeb, 0xd9f57f830283fdfd},
+      {0xd267caa862a12d66, 0xd072df63c324fd7c},
+      {0x8380dea93da4bc60, 0x4247cb9e59f71e6e},
+      {0xa46116538d0deb78, 0x52d9be85f074e609},
+      {0xcd795be870516656, 0x67902e276c921f8c},
+      {0x806bd9714632dff6, 0x00ba1cd8a3db53b7},
+      {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5},
+      {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce},
+      {0xfad2a4b13d1b5d6c, 0x796b805720085f82},
+      {0x9cc3a6eec6311a63, 0xcbe3303674053bb1},
+      {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d},
+      {0xf4f1b4d515acb93b, 0xee92fb5515482d45},
+      {0x991711052d8bf3c5, 0x751bdd152d4d1c4b},
+      {0xbf5cd54678eef0b6, 0xd262d45a78a0635e},
+      {0xef340a98172aace4, 0x86fb897116c87c35},
+      {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1},
+      {0xbae0a846d2195712, 0x8974836059cca10a},
+      {0xe998d258869facd7, 0x2bd1a438703fc94c},
+      {0x91ff83775423cc06, 0x7b6306a34627ddd0},
+      {0xb67f6455292cbf08, 0x1a3bc84c17b1d543},
+      {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94},
+      {0x8e938662882af53e, 0x547eb47b7282ee9d},
+      {0xb23867fb2a35b28d, 0xe99e619a4f23aa44},
+      {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5},
+      {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05},
+      {0xae0b158b4738705e, 0x9624ab50b148d446},
+      {0xd98ddaee19068c76, 0x3badd624dd9b0958},
+      {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7},
+      {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d},
+      {0xd47487cc8470652b, 0x7647c32000696720},
+      {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074},
+      {0xa5fb0a17c777cf09, 0xf468107100525891},
+      {0xcf79cc9db955c2cc, 0x7182148d4066eeb5},
+      {0x81ac1fe293d599bf, 0xc6f14cd848405531},
+      {0xa21727db38cb002f, 0xb8ada00e5a506a7d},
+      {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d},
+      {0xfd442e4688bd304a, 0x908f4a166d1da664},
+      {0x9e4a9cec15763e2e, 0x9a598e4e043287ff},
+      {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe},
+      {0xf7549530e188c128, 0xd12bee59e68ef47d},
+      {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf},
+      {0xc13a148e3032d6e7, 0xe36a52363c1faf02},
+      {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2},
+      {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba},
+      {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8},
+      {0xebdf661791d60f56, 0x111b495b3464ad22},
+      {0x936b9fcebb25c995, 0xcab10dd900beec35},
+      {0xb84687c269ef3bfb, 0x3d5d514f40eea743},
+      {0xe65829b3046b0afa, 0x0cb4a5a3112a5113},
+      {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac},
+      {0xb3f4e093db73a093, 0x59ed216765690f57},
+      {0xe0f218b8d25088b8, 0x306869c13ec3532d},
+      {0x8c974f7383725573, 0x1e414218c73a13fc},
+      {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
+      {0xdbac6c247d62a583, 0xdf45f746b74abf3a},
+      {0x894bc396ce5da772, 0x6b8bba8c328eb784},
+      {0xab9eb47c81f5114f, 0x066ea92f3f326565},
+      {0xd686619ba27255a2, 0xc80a537b0efefebe},
+      {0x8613fd0145877585, 0xbd06742ce95f5f37},
+      {0xa798fc4196e952e7, 0x2c48113823b73705},
+      {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6},
+      {0x82ef85133de648c4, 0x9a984d73dbe722fc},
+      {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb},
+      {0xcc963fee10b7d1b3, 0x318df905079926a9},
+      {0xffbbcfe994e5c61f, 0xfdf17746497f7053},
+      {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634},
+      {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1},
+      {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1},
+      {0x9c1661a651213e2d, 0x06bea10ca65c084f},
+      {0xc31bfa0fe5698db8, 0x486e494fcff30a63},
+      {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb},
+      {0x986ddb5c6b3a76b7, 0xf89629465a75e01d},
+      {0xbe89523386091465, 0xf6bbb397f1135824},
+      {0xee2ba6c0678b597f, 0x746aa07ded582e2d},
+      {0x94db483840b717ef, 0xa8c2a44eb4571cdd},
+      {0xba121a4650e4ddeb, 0x92f34d62616ce414},
+      {0xe896a0d7e51e1566, 0x77b020baf9c81d18},
+      {0x915e2486ef32cd60, 0x0ace1474dc1d122f},
+      {0xb5b5ada8aaff80b8, 0x0d819992132456bb},
+      {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a},
+      {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
+      {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3},
+      {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf},
+      {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c},
+      {0xad4ab7112eb3929d, 0x86c16c98d2c953c7},
+      {0xd89d64d57a607744, 0xe871c7bf077ba8b8},
+      {0x87625f056c7c4a8b, 0x11471cd764ad4973},
+      {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0},
+      {0xd389b47879823479, 0x4aff1d108d4ec2c4},
+      {0x843610cb4bf160cb, 0xcedf722a585139bb},
+      {0xa54394fe1eedb8fe, 0xc2974eb4ee658829},
+      {0xce947a3da6a9273e, 0x733d226229feea33},
+      {0x811ccc668829b887, 0x0806357d5a3f5260},
+      {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8},
+      {0xc9bcff6034c13052, 0xfc89b393dd02f0b6},
+      {0xfc2c3f3841f17c67, 0xbbac2078d443ace3},
+      {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e},
+      {0xc5029163f384a931, 0x0a9e795e65d4df12},
+      {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6},
+      {0x99ea0196163fa42e, 0x504bced1bf8e4e46},
+      {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7},
+      {0xf07da27a82c37088, 0x5d767327bb4e5a4d},
+      {0x964e858c91ba2655, 0x3a6a07f8d510f870},
+      {0xbbe226efb628afea, 0x890489f70a55368c},
+      {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f},
+      {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e},
+      {0xb77ada0617e3bbcb, 0x09ce6ebb40173745},
+      {0xe55990879ddcaabd, 0xcc420a6a101d0516},
+      {0x8f57fa54c2a9eab6, 0x9fa946824a12232e},
+      {0xb32df8e9f3546564, 0x47939822dc96abfa},
+      {0xdff9772470297ebd, 0x59787e2b93bc56f8},
+      {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b},
+      {0xaefae51477a06b03, 0xede622920b6b23f2},
+      {0xdab99e59958885c4, 0xe95fab368e45ecee},
+      {0x88b402f7fd75539b, 0x11dbcb0218ebb415},
+      {0xaae103b5fcd2a881, 0xd652bdc29f26a11a},
+      {0xd59944a37c0752a2, 0x4be76d3346f04960},
+      {0x857fcae62d8493a5, 0x6f70a4400c562ddc},
+      {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953},
+      {0xd097ad07a71f26b2, 0x7e2000a41346a7a8},
+      {0x825ecc24c873782f, 0x8ed400668c0c28c9},
+      {0xa2f67f2dfa90563b, 0x728900802f0f32fb},
+      {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba},
+      {0xfea126b7d78186bc, 0xe2f610c84987bfa9},
+      {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca},
+      {0xc6ede63fa05d3143, 0x91503d1c79720dbc},
+      {0xf8a95fcf88747d94, 0x75a44c6397ce912b},
+      {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb},
+      {0xc24452da229b021b, 0xfbe85badce996169},
+      {0xf2d56790ab41c2a2, 0xfae27299423fb9c4},
+      {0x97c560ba6b0919a5, 0xdccd879fc967d41b},
+      {0xbdb6b8e905cb600f, 0x5400e987bbc1c921},
+      {0xed246723473e3813, 0x290123e9aab23b69},
+      {0x9436c0760c86e30b, 0xf9a0b6720aaf6522},
+      {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
+      {0xe7958cb87392c2c2, 0xb60b1d1230b20e05},
+      {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3},
+      {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4},
+      {0xe2280b6c20dd5232, 0x25c6da63c38de1b1},
+      {0x8d590723948a535f, 0x579c487e5a38ad0f},
+      {0xb0af48ec79ace837, 0x2d835a9df0c6d852},
+      {0xdcdb1b2798182244, 0xf8e431456cf88e66},
+      {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900},
+      {0xac8b2d36eed2dac5, 0xe272467e3d222f40},
+      {0xd7adf884aa879177, 0x5b0ed81dcc6abb10},
+      {0x86ccbb52ea94baea, 0x98e947129fc2b4ea},
+      {0xa87fea27a539e9a5, 0x3f2398d747b36225},
+      {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae},
+      {0x83a3eeeef9153e89, 0x1953cf68300424ad},
+      {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8},
+      {0xcdb02555653131b6, 0x3792f412cb06794e},
+      {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1},
+      {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5},
+      {0xc8de047564d20a8b, 0xf245825a5a445276},
+      {0xfb158592be068d2e, 0xeed6e2f0f0d56713},
+      {0x9ced737bb6c4183d, 0x55464dd69685606c},
+      {0xc428d05aa4751e4c, 0xaa97e14c3c26b887},
+      {0xf53304714d9265df, 0xd53dd99f4b3066a9},
+      {0x993fe2c6d07b7fab, 0xe546a8038efe402a},
+      {0xbf8fdb78849a5f96, 0xde98520472bdd034},
+      {0xef73d256a5c0f77c, 0x963e66858f6d4441},
+      {0x95a8637627989aad, 0xdde7001379a44aa9},
+      {0xbb127c53b17ec159, 0x5560c018580d5d53},
+      {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7},
+      {0x9226712162ab070d, 0xcab3961304ca70e9},
+      {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23},
+      {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b},
+      {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243},
+      {0xb267ed1940f1c61c, 0x55f038b237591ed4},
+      {0xdf01e85f912e37a3, 0x6b6c46dec52f6689},
+      {0x8b61313bbabce2c6, 0x2323ac4b3b3da016},
+      {0xae397d8aa96c1b77, 0xabec975e0a0d081b},
+      {0xd9c7dced53c72255, 0x96e7bd358c904a22},
+      {0x881cea14545c7575, 0x7e50d64177da2e55},
+      {0xaa242499697392d2, 0xdde50bd1d5d0b9ea},
+      {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865},
+      {0x84ec3c97da624ab4, 0xbd5af13bef0b113f},
+      {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f},
+      {0xcfb11ead453994ba, 0x67de18eda5814af3},
+      {0x81ceb32c4b43fcf4, 0x80eacf948770ced8},
+      {0xa2425ff75e14fc31, 0xa1258379a94d028e},
+      {0xcad2f7f5359a3b3e, 0x096ee45813a04331},
+      {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd},
+      {0x9e74d1b791e07e48, 0x775ea264cf55347e},
+      {0xc612062576589dda, 0x95364afe032a819e},
+      {0xf79687aed3eec551, 0x3a83ddbd83f52205},
+      {0x9abe14cd44753b52, 0xc4926a9672793543},
+      {0xc16d9a0095928a27, 0x75b7053c0f178294},
+      {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
+      {0x971da05074da7bee, 0xd3f6fc16ebca5e04},
+      {0xbce5086492111aea, 0x88f4bb1ca6bcf585},
+      {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6},
+      {0x9392ee8e921d5d07, 0x3aff322e62439fd0},
+      {0xb877aa3236a4b449, 0x09befeb9fad487c3},
+      {0xe69594bec44de15b, 0x4c2ebe687989a9b4},
+      {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11},
+      {0xb424dc35095cd80f, 0x538484c19ef38c95},
+      {0xe12e13424bb40e13, 0x2865a5f206b06fba},
+      {0x8cbccc096f5088cb, 0xf93f87b7442e45d4},
+      {0xafebff0bcb24aafe, 0xf78f69a51539d749},
+      {0xdbe6fecebdedd5be, 0xb573440e5a884d1c},
+      {0x89705f4136b4a597, 0x31680a88f8953031},
+      {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e},
+      {0xd6bf94d5e57a42bc, 0x3d32907604691b4d},
+      {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110},
+      {0xa7c5ac471b478423, 0x0fcf80dc33721d54},
+      {0xd1b71758e219652b, 0xd3c36113404ea4a9},
+      {0x83126e978d4fdf3b, 0x645a1cac083126ea},
+      {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4},
+      {0xcccccccccccccccc, 0xcccccccccccccccd},
+      {0x8000000000000000, 0x0000000000000000},
+      {0xa000000000000000, 0x0000000000000000},
+      {0xc800000000000000, 0x0000000000000000},
+      {0xfa00000000000000, 0x0000000000000000},
+      {0x9c40000000000000, 0x0000000000000000},
+      {0xc350000000000000, 0x0000000000000000},
+      {0xf424000000000000, 0x0000000000000000},
+      {0x9896800000000000, 0x0000000000000000},
+      {0xbebc200000000000, 0x0000000000000000},
+      {0xee6b280000000000, 0x0000000000000000},
+      {0x9502f90000000000, 0x0000000000000000},
+      {0xba43b74000000000, 0x0000000000000000},
+      {0xe8d4a51000000000, 0x0000000000000000},
+      {0x9184e72a00000000, 0x0000000000000000},
+      {0xb5e620f480000000, 0x0000000000000000},
+      {0xe35fa931a0000000, 0x0000000000000000},
+      {0x8e1bc9bf04000000, 0x0000000000000000},
+      {0xb1a2bc2ec5000000, 0x0000000000000000},
+      {0xde0b6b3a76400000, 0x0000000000000000},
+      {0x8ac7230489e80000, 0x0000000000000000},
+      {0xad78ebc5ac620000, 0x0000000000000000},
+      {0xd8d726b7177a8000, 0x0000000000000000},
+      {0x878678326eac9000, 0x0000000000000000},
+      {0xa968163f0a57b400, 0x0000000000000000},
+      {0xd3c21bcecceda100, 0x0000000000000000},
+      {0x84595161401484a0, 0x0000000000000000},
+      {0xa56fa5b99019a5c8, 0x0000000000000000},
+      {0xcecb8f27f4200f3a, 0x0000000000000000},
+      {0x813f3978f8940984, 0x4000000000000000},
+      {0xa18f07d736b90be5, 0x5000000000000000},
+      {0xc9f2c9cd04674ede, 0xa400000000000000},
+      {0xfc6f7c4045812296, 0x4d00000000000000},
+      {0x9dc5ada82b70b59d, 0xf020000000000000},
+      {0xc5371912364ce305, 0x6c28000000000000},
+      {0xf684df56c3e01bc6, 0xc732000000000000},
+      {0x9a130b963a6c115c, 0x3c7f400000000000},
+      {0xc097ce7bc90715b3, 0x4b9f100000000000},
+      {0xf0bdc21abb48db20, 0x1e86d40000000000},
+      {0x96769950b50d88f4, 0x1314448000000000},
+      {0xbc143fa4e250eb31, 0x17d955a000000000},
+      {0xeb194f8e1ae525fd, 0x5dcfab0800000000},
+      {0x92efd1b8d0cf37be, 0x5aa1cae500000000},
+      {0xb7abc627050305ad, 0xf14a3d9e40000000},
+      {0xe596b7b0c643c719, 0x6d9ccd05d0000000},
+      {0x8f7e32ce7bea5c6f, 0xe4820023a2000000},
+      {0xb35dbf821ae4f38b, 0xdda2802c8a800000},
+      {0xe0352f62a19e306e, 0xd50b2037ad200000},
+      {0x8c213d9da502de45, 0x4526f422cc340000},
+      {0xaf298d050e4395d6, 0x9670b12b7f410000},
+      {0xdaf3f04651d47b4c, 0x3c0cdd765f114000},
+      {0x88d8762bf324cd0f, 0xa5880a69fb6ac800},
+      {0xab0e93b6efee0053, 0x8eea0d047a457a00},
+      {0xd5d238a4abe98068, 0x72a4904598d6d880},
+      {0x85a36366eb71f041, 0x47a6da2b7f864750},
+      {0xa70c3c40a64e6c51, 0x999090b65f67d924},
+      {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d},
+      {0x82818f1281ed449f, 0xbff8f10e7a8921a4},
+      {0xa321f2d7226895c7, 0xaff72d52192b6a0d},
+      {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490},
+      {0xfee50b7025c36a08, 0x02f236d04753d5b4},
+      {0x9f4f2726179a2245, 0x01d762422c946590},
+      {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5},
+      {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2},
+      {0x9b934c3b330c8577, 0x63cc55f49f88eb2f},
+      {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb},
+      {0xf316271c7fc3908a, 0x8bef464e3945ef7a},
+      {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac},
+      {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317},
+      {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd},
+      {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a},
+      {0xb975d6b6ee39e436, 0xb3e2fd538e122b44},
+      {0xe7d34c64a9c85d44, 0x60dbbca87196b616},
+      {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd},
+      {0xb51d13aea4a488dd, 0x6babab6398bdbe41},
+      {0xe264589a4dcdab14, 0xc696963c7eed2dd1},
+      {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2},
+      {0xb0de65388cc8ada8, 0x3b25a55f43294bcb},
+      {0xdd15fe86affad912, 0x49ef0eb713f39ebe},
+      {0x8a2dbf142dfcc7ab, 0x6e3569326c784337},
+      {0xacb92ed9397bf996, 0x49c2c37f07965404},
+      {0xd7e77a8f87daf7fb, 0xdc33745ec97be906},
+      {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3},
+      {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c},
+      {0xd2d80db02aabd62b, 0xf50a3fa490c30190},
+      {0x83c7088e1aab65db, 0x792667c6da79e0fa},
+      {0xa4b8cab1a1563f52, 0x577001b891185938},
+      {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
+      {0x80b05e5ac60b6178, 0x544f8158315b05b4},
+      {0xa0dc75f1778e39d6, 0x696361ae3db1c721},
+      {0xc913936dd571c84c, 0x03bc3a19cd1e38e9},
+      {0xfb5878494ace3a5f, 0x04ab48a04065c723},
+      {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76},
+      {0xc45d1df942711d9a, 0x3ba5d0bd324f8394},
+      {0xf5746577930d6500, 0xca8f44ec7ee36479},
+      {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb},
+      {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e},
+      {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e},
+      {0x95d04aee3b80ece5, 0xbba1f1d158724a12},
+      {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97},
+      {0xea1575143cf97226, 0xf52d09d71a3293bd},
+      {0x924d692ca61be758, 0x593c2626705f9c56},
+      {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c},
+      {0xe498f455c38b997a, 0x0b6dfb9c0f956447},
+      {0x8edf98b59a373fec, 0x4724bd4189bd5eac},
+      {0xb2977ee300c50fe7, 0x58edec91ec2cb657},
+      {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed},
+      {0x8b865b215899f46c, 0xbd79e0d20082ee74},
+      {0xae67f1e9aec07187, 0xecd8590680a3aa11},
+      {0xda01ee641a708de9, 0xe80e6f4820cc9495},
+      {0x884134fe908658b2, 0x3109058d147fdcdd},
+      {0xaa51823e34a7eede, 0xbd4b46f0599fd415},
+      {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a},
+      {0x850fadc09923329e, 0x03e2cf6bc604ddb0},
+      {0xa6539930bf6bff45, 0x84db8346b786151c},
+      {0xcfe87f7cef46ff16, 0xe612641865679a63},
+      {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e},
+      {0xa26da3999aef7749, 0xe3be5e330f38f09d},
+      {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5},
+      {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6},
+      {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa},
+      {0xc646d63501a1511d, 0xb281e1fd541501b8},
+      {0xf7d88bc24209a565, 0x1f225a7ca91a4226},
+      {0x9ae757596946075f, 0x3375788de9b06958},
+      {0xc1a12d2fc3978937, 0x0052d6b1641c83ae},
+      {0xf209787bb47d6b84, 0xc0678c5dbd23a49a},
+      {0x9745eb4d50ce6332, 0xf840b7ba963646e0},
+      {0xbd176620a501fbff, 0xb650e5a93bc3d898},
+      {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe},
+      {0x93ba47c980e98cdf, 0xc66f336c36b10137},
+      {0xb8a8d9bbe123f017, 0xb80b0047445d4184},
+      {0xe6d3102ad96cec1d, 0xa60dc059157491e5},
+      {0x9043ea1ac7e41392, 0x87c89837ad68db2f},
+      {0xb454e4a179dd1877, 0x29babe4598c311fb},
+      {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a},
+      {0x8ce2529e2734bb1d, 0x1899e4a65f58660c},
+      {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f},
+      {0xdc21a1171d42645d, 0x76707543f4fa1f73},
+      {0x899504ae72497eba, 0x6a06494a791c53a8},
+      {0xabfa45da0edbde69, 0x0487db9d17636892},
+      {0xd6f8d7509292d603, 0x45a9d2845d3c42b6},
+      {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
+      {0xa7f26836f282b732, 0x8e6cac7768d7141e},
+      {0xd1ef0244af2364ff, 0x3207d795430cd926},
+      {0x8335616aed761f1f, 0x7f44e6bd49e807b8},
+      {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6},
+      {0xcd036837130890a1, 0x36dba887c37a8c0f},
+      {0x802221226be55a64, 0xc2494954da2c9789},
+      {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c},
+      {0xc83553c5c8965d3d, 0x6f92829494e5acc7},
+      {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9},
+      {0x9c69a97284b578d7, 0xff2a760414536efb},
+      {0xc38413cf25e2d70d, 0xfef5138519684aba},
+      {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69},
+      {0x98bf2f79d5993802, 0xef2f773ffbd97a61},
+      {0xbeeefb584aff8603, 0xaafb550ffacfd8fa},
+      {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38},
+      {0x952ab45cfa97a0b2, 0xdd945a747bf26183},
+      {0xba756174393d88df, 0x94f971119aeef9e4},
+      {0xe912b9d1478ceb17, 0x7a37cd5601aab85d},
+      {0x91abb422ccb812ee, 0xac62e055c10ab33a},
+      {0xb616a12b7fe617aa, 0x577b986b314d6009},
+      {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b},
+      {0x8e41ade9fbebc27d, 0x14588f13be847307},
+      {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8},
+      {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb},
+      {0x8aec23d680043bee, 0x25de7bb9480d5854},
+      {0xada72ccc20054ae9, 0xaf561aa79a10ae6a},
+      {0xd910f7ff28069da4, 0x1b2ba1518094da04},
+      {0x87aa9aff79042286, 0x90fb44d2f05d0842},
+      {0xa99541bf57452b28, 0x353a1607ac744a53},
+      {0xd3fa922f2d1675f2, 0x42889b8997915ce8},
+      {0x847c9b5d7c2e09b7, 0x69956135febada11},
+      {0xa59bc234db398c25, 0x43fab9837e699095},
+      {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb},
+      {0x8161afb94b44f57d, 0x1d1be0eebac278f5},
+      {0xa1ba1ba79e1632dc, 0x6462d92a69731732},
+      {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe},
+      {0xfcb2cb35e702af78, 0x5cda735244c3d43e},
+      {0x9defbf01b061adab, 0x3a0888136afa64a7},
+      {0xc56baec21c7a1916, 0x088aaa1845b8fdd0},
+      {0xf6c69a72a3989f5b, 0x8aad549e57273d45},
+      {0x9a3c2087a63f6399, 0x36ac54e2f678864b},
+      {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd},
+      {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5},
+      {0x969eb7c47859e743, 0x9f644ae5a4b1b325},
+      {0xbc4665b596706114, 0x873d5d9f0dde1fee},
+      {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea},
+      {0x9316ff75dd87cbd8, 0x09a7f12442d588f2},
+      {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f},
+      {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa},
+      {0x8fa475791a569d10, 0xf96e017d694487bc},
+      {0xb38d92d760ec4455, 0x37c981dcc395a9ac},
+      {0xe070f78d3927556a, 0x85bbe253f47b1417},
+      {0x8c469ab843b89562, 0x93956d7478ccec8e},
+      {0xaf58416654a6babb, 0x387ac8d1970027b2},
+      {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e},
+      {0x88fcf317f22241e2, 0x441fece3bdf81f03},
+      {0xab3c2fddeeaad25a, 0xd527e81cad7626c3},
+      {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074},
+      {0x85c7056562757456, 0xf6872d5667844e49},
+      {0xa738c6bebb12d16c, 0xb428f8ac016561db},
+      {0xd106f86e69d785c7, 0xe13336d701beba52},
+      {0x82a45b450226b39c, 0xecc0024661173473},
+      {0xa34d721642b06084, 0x27f002d7f95d0190},
+      {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4},
+      {0xff290242c83396ce, 0x7e67047175a15271},
+      {0x9f79a169bd203e41, 0x0f0062c6e984d386},
+      {0xc75809c42c684dd1, 0x52c07b78a3e60868},
+      {0xf92e0c3537826145, 0xa7709a56ccdf8a82},
+      {0x9bbcc7a142b17ccb, 0x88a66076400bb691},
+      {0xc2abf989935ddbfe, 0x6acff893d00ea435},
+      {0xf356f7ebf83552fe, 0x0583f6b8c4124d43},
+      {0x98165af37b2153de, 0xc3727a337a8b704a},
+      {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c},
+      {0xeda2ee1c7064130c, 0x1162def06f79df73},
+      {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8},
+      {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692},
+      {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437},
+      {0x910ab1d4db9914a0, 0x1d9c9892400a22a2},
+      {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b},
+      {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d},
+      {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
+      {0xb10d8e1456105dad, 0x7425a83e872c5f47},
+      {0xdd50f1996b947518, 0xd12f124e28f77719},
+      {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f},
+      {0xace73cbfdc0bfb7b, 0x636cc64d1001550b},
+      {0xd8210befd30efa5a, 0x3c47f7e05401aa4e},
+      {0x8714a775e3e95c78, 0x65acfaec34810a71},
+      {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d},
+      {0xd31045a8341ca07c, 0x1ede48111209a050},
+      {0x83ea2b892091e44d, 0x934aed0aab460432},
+      {0xa4e4b66b68b65d60, 0xf81da84d5617853f},
+      {0xce1de40642e3f4b9, 0x36251260ab9d668e},
+      {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019},
+      {0xa1075a24e4421730, 0xb24cf65b8612f81f},
+      {0xc94930ae1d529cfc, 0xdee033f26797b627},
+      {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1},
+      {0x9d412e0806e88aa5, 0x8e1f289560ee864e},
+      {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2},
+      {0xf5b5d7ec8acb58a2, 0xae10af696774b1db},
+      {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29},
+      {0xbff610b0cc6edd3f, 0x17fd090a58d32af3},
+      {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0},
+      {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e},
+      {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1},
+      {0xea53df5fd18d5513, 0x84c86189216dc5ed},
+      {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4},
+      {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1},
+      {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
+      {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e},
+      {0xb2c71d5bca9023f8, 0x743e20e9ef511012},
+      {0xdf78e4b2bd342cf6, 0x914da9246b255416},
+      {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e},
+      {0xae9672aba3d0c320, 0xa184ac2473b529b1},
+      {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e},
+      {0x8865899617fb1871, 0x7e2fa67c7a658892},
+      {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7},
+      {0xd51ea6fa85785631, 0x552a74227f3ea565},
+      {0x8533285c936b35de, 0xd53a88958f87275f},
+      {0xa67ff273b8460356, 0x8a892abaf368f137},
+      {0xd01fef10a657842c, 0x2d2b7569b0432d85},
+      {0x8213f56a67f6b29b, 0x9c3b29620e29fc73},
+      {0xa298f2c501f45f42, 0x8349f3ba91b47b8f},
+      {0xcb3f2f7642717713, 0x241c70a936219a73},
+      {0xfe0efb53d30dd4d7, 0xed238cd383aa0110},
+      {0x9ec95d1463e8a506, 0xf4363804324a40aa},
+      {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5},
+      {0xf81aa16fdc1b81da, 0xdd94b7868e94050a},
+      {0x9b10a4e5e9913128, 0xca7cf2b4191c8326},
+      {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0},
+      {0xf24a01a73cf2dccf, 0xbc633b39673c8cec},
+      {0x976e41088617ca01, 0xd5be0503e085d813},
+      {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18},
+      {0xec9c459d51852ba2, 0xddf8e7d60ed1219e},
+      {0x93e1ab8252f33b45, 0xcabb90e5c942b503},
+      {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
+      {0xe7109bfba19c0c9d, 0x0cc512670a783ad4},
+      {0x906a617d450187e2, 0x27fb2b80668b24c5},
+      {0xb484f9dc9641e9da, 0xb1f9f660802dedf6},
+      {0xe1a63853bbd26451, 0x5e7873f8a0396973},
+      {0x8d07e33455637eb2, 0xdb0b487b6423e1e8},
+      {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62},
+      {0xdc5c5301c56b75f7, 0x7641a140cc7810fb},
+      {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d},
+      {0xac2820d9623bf429, 0x546345fa9fbdcd44},
+      {0xd732290fbacaf133, 0xa97c177947ad4095},
+      {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d},
+      {0xa81f301449ee8c70, 0x5c68f256bfff5a74},
+      {0xd226fc195c6a2f8c, 0x73832eec6fff3111},
+      {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab},
+      {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55},
+      {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb},
+      {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3},
+      {0xa0555e361951c366, 0xd7e105bcc332621f},
+      {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7},
+      {0xfa856334878fc150, 0xb14f98f6f0feb951},
+      {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3},
+      {0xc3b8358109e84f07, 0x0a862f80ec4700c8},
+      {0xf4a642e14c6262c8, 0xcd27bb612758c0fa},
+      {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c},
+      {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3},
+      {0xeeea5d5004981478, 0x1858ccfce06cac74},
+      {0x95527a5202df0ccb, 0x0f37801e0c43ebc8},
+      {0xbaa718e68396cffd, 0xd30560258f54e6ba},
+      {0xe950df20247c83fd, 0x47c6b82ef32a2069},
+      {0x91d28b7416cdd27e, 0x4cdc331d57fa5441},
+      {0xb6472e511c81471d, 0xe0133fe4adf8e952},
+      {0xe3d8f9e563a198e5, 0x58180fddd97723a6},
+      {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648},
+      {0xb201833b35d63f73, 0x2cd2cc6551e513da},
+      {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1},
+      {0x8b112e86420f6191, 0xfb04afaf27faf782},
+      {0xadd57a27d29339f6, 0x79c5db9af1f9b563},
+      {0xd94ad8b1c7380874, 0x18375281ae7822bc},
+      {0x87cec76f1c830548, 0x8f2293910d0b15b5},
+      {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22},
+      {0xd433179d9c8cb841, 0x5fa60692a46151eb},
+      {0x849feec281d7f328, 0xdbc7c41ba6bcd333},
+      {0xa5c7ea73224deff3, 0x12b9b522906c0800},
+      {0xcf39e50feae16bef, 0xd768226b34870a00},
+      {0x81842f29f2cce375, 0xe6a1158300d46640},
+      {0xa1e53af46f801c53, 0x60495ae3c1097fd0},
+      {0xca5e89b18b602368, 0x385bb19cb14bdfc4},
+      {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5},
+      {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1},
+      {0xc5a05277621be293, 0xc7098b7305241885},
+      { 0xf70867153aa2db38,
+        0xb8cbee4fc66d1ea7 }
+#else
+      {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
+      {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
+      {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
+      {0x86a8d39ef77164bc, 0xae5dff9c02033198},
+      {0xd98ddaee19068c76, 0x3badd624dd9b0958},
+      {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
+      {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
+      {0xe55990879ddcaabd, 0xcc420a6a101d0516},
+      {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
+      {0x95a8637627989aad, 0xdde7001379a44aa9},
+      {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
+      {0xc350000000000000, 0x0000000000000000},
+      {0x9dc5ada82b70b59d, 0xf020000000000000},
+      {0xfee50b7025c36a08, 0x02f236d04753d5b4},
+      {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
+      {0xa6539930bf6bff45, 0x84db8346b786151c},
+      {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
+      {0xd910f7ff28069da4, 0x1b2ba1518094da04},
+      {0xaf58416654a6babb, 0x387ac8d1970027b2},
+      {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
+      {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
+      {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
+      { 0x95527a5202df0ccb,
+        0x0f37801e0c43ebc8 }
+#endif
+    };
+
+#if FMT_USE_FULL_CACHE_DRAGONBOX
+    return pow10_significands[k - float_info<double>::min_k];
+#else
+    static constexpr const uint64_t powers_of_5_64[] = {
+        0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
+        0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
+        0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
+        0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd,
+        0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9,
+        0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5,
+        0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631,
+        0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed,
+        0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9};
+
+    static constexpr const uint32_t pow10_recovery_errors[] = {
+        0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001,
+        0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555,
+        0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015,
+        0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110,
+        0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454,
+        0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014,
+        0x69514555, 0x05151109, 0x00155555};
+
+    static const int compression_ratio = 27;
+
+    // Compute base index.
+    int cache_index = (k - float_info<double>::min_k) / compression_ratio;
+    int kb = cache_index * compression_ratio + float_info<double>::min_k;
+    int offset = k - kb;
+
+    // Get base cache.
+    uint128_wrapper base_cache = pow10_significands[cache_index];
+    if (offset == 0) return base_cache;
+
+    // Compute the required amount of bit-shift.
+    int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset;
+    FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected");
+
+    // Try to recover the real cache.
+    uint64_t pow5 = powers_of_5_64[offset];
+    uint128_wrapper recovered_cache = umul128(base_cache.high(), pow5);
+    uint128_wrapper middle_low =
+        umul128(base_cache.low() - (kb < 0 ? 1u : 0u), pow5);
+
+    recovered_cache += middle_low.high();
+
+    uint64_t high_to_middle = recovered_cache.high() << (64 - alpha);
+    uint64_t middle_to_low = recovered_cache.low() << (64 - alpha);
+
+    recovered_cache =
+        uint128_wrapper{(recovered_cache.low() >> alpha) | high_to_middle,
+                        ((middle_low.low() >> alpha) | middle_to_low)};
+
+    if (kb < 0) recovered_cache += 1;
+
+    // Get error.
+    int error_idx = (k - float_info<double>::min_k) / 16;
+    uint32_t error = (pow10_recovery_errors[error_idx] >>
+                      ((k - float_info<double>::min_k) % 16) * 2) &
+                     0x3;
+
+    // Add the error back.
+    FMT_ASSERT(recovered_cache.low() + error >= recovered_cache.low(), "");
+    return {recovered_cache.high(), recovered_cache.low() + error};
+#endif
+  }
+
+  static carrier_uint compute_mul(carrier_uint u,
+                                  const cache_entry_type& cache) FMT_NOEXCEPT {
+    return umul192_upper64(u, cache);
+  }
+
+  static uint32_t compute_delta(cache_entry_type const& cache,
+                                int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta_minus_1));
+  }
+
+  static bool compute_mul_parity(carrier_uint two_f,
+                                 const cache_entry_type& cache,
+                                 int beta_minus_1) FMT_NOEXCEPT {
+    FMT_ASSERT(beta_minus_1 >= 1, "");
+    FMT_ASSERT(beta_minus_1 < 64, "");
+
+    return ((umul192_middle64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0;
+  }
+
+  static carrier_uint compute_left_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return (cache.high() -
+            (cache.high() >> (float_info<double>::significand_bits + 2))) >>
+           (64 - float_info<double>::significand_bits - 1 - beta_minus_1);
+  }
+
+  static carrier_uint compute_right_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return (cache.high() +
+            (cache.high() >> (float_info<double>::significand_bits + 1))) >>
+           (64 - float_info<double>::significand_bits - 1 - beta_minus_1);
+  }
+
+  static carrier_uint compute_round_up_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return ((cache.high() >>
+             (64 - float_info<double>::significand_bits - 2 - beta_minus_1)) +
+            1) /
+           2;
+  }
+};
+
+// Various integer checks
+template <class T>
+bool is_left_endpoint_integer_shorter_interval(int exponent) FMT_NOEXCEPT {
+  return exponent >=
+             float_info<
+                 T>::case_shorter_interval_left_endpoint_lower_threshold &&
+         exponent <=
+             float_info<T>::case_shorter_interval_left_endpoint_upper_threshold;
+}
+template <class T>
+bool is_endpoint_integer(typename float_info<T>::carrier_uint two_f,
+                         int exponent, int minus_k) FMT_NOEXCEPT {
+  if (exponent < float_info<T>::case_fc_pm_half_lower_threshold) return false;
+  // For k >= 0.
+  if (exponent <= float_info<T>::case_fc_pm_half_upper_threshold) return true;
+  // For k < 0.
+  if (exponent > float_info<T>::divisibility_check_by_5_threshold) return false;
+  return divisible_by_power_of_5(two_f, minus_k);
+}
+
+template <class T>
+bool is_center_integer(typename float_info<T>::carrier_uint two_f, int exponent,
+                       int minus_k) FMT_NOEXCEPT {
+  // Exponent for 5 is negative.
+  if (exponent > float_info<T>::divisibility_check_by_5_threshold) return false;
+  if (exponent > float_info<T>::case_fc_upper_threshold)
+    return divisible_by_power_of_5(two_f, minus_k);
+  // Both exponents are nonnegative.
+  if (exponent >= float_info<T>::case_fc_lower_threshold) return true;
+  // Exponent for 2 is negative.
+  return divisible_by_power_of_2(two_f, minus_k - exponent + 1);
+}
+
+// Remove trailing zeros from n and return the number of zeros removed (float)
+FMT_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT {
+#ifdef FMT_BUILTIN_CTZ
+  int t = FMT_BUILTIN_CTZ(n);
+#else
+  int t = ctz(n);
+#endif
+  if (t > float_info<float>::max_trailing_zeros)
+    t = float_info<float>::max_trailing_zeros;
+
+  const uint32_t mod_inv1 = 0xcccccccd;
+  const uint32_t max_quotient1 = 0x33333333;
+  const uint32_t mod_inv2 = 0xc28f5c29;
+  const uint32_t max_quotient2 = 0x0a3d70a3;
+
+  int s = 0;
+  for (; s < t - 1; s += 2) {
+    if (n * mod_inv2 > max_quotient2) break;
+    n *= mod_inv2;
+  }
+  if (s < t && n * mod_inv1 <= max_quotient1) {
+    n *= mod_inv1;
+    ++s;
+  }
+  n >>= s;
+  return s;
+}
+
+// Removes trailing zeros and returns the number of zeros removed (double)
+FMT_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT {
+#ifdef FMT_BUILTIN_CTZLL
+  int t = FMT_BUILTIN_CTZLL(n);
+#else
+  int t = ctzll(n);
+#endif
+  if (t > float_info<double>::max_trailing_zeros)
+    t = float_info<double>::max_trailing_zeros;
+  // Divide by 10^8 and reduce to 32-bits
+  // Since ret_value.significand <= (2^64 - 1) / 1000 < 10^17,
+  // both of the quotient and the r should fit in 32-bits
+
+  const uint32_t mod_inv1 = 0xcccccccd;
+  const uint32_t max_quotient1 = 0x33333333;
+  const uint64_t mod_inv8 = 0xc767074b22e90e21;
+  const uint64_t max_quotient8 = 0x00002af31dc46118;
+
+  // If the number is divisible by 1'0000'0000, work with the quotient
+  if (t >= 8) {
+    auto quotient_candidate = n * mod_inv8;
+
+    if (quotient_candidate <= max_quotient8) {
+      auto quotient = static_cast<uint32_t>(quotient_candidate >> 8);
+
+      int s = 8;
+      for (; s < t; ++s) {
+        if (quotient * mod_inv1 > max_quotient1) break;
+        quotient *= mod_inv1;
+      }
+      quotient >>= (s - 8);
+      n = quotient;
+      return s;
+    }
+  }
+
+  // Otherwise, work with the remainder
+  auto quotient = static_cast<uint32_t>(n / 100000000);
+  auto remainder = static_cast<uint32_t>(n - 100000000 * quotient);
+
+  if (t == 0 || remainder * mod_inv1 > max_quotient1) {
+    return 0;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 1 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 1) + quotient * 10000000ull;
+    return 1;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 2 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 2) + quotient * 1000000ull;
+    return 2;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 3 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 3) + quotient * 100000ull;
+    return 3;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 4 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 4) + quotient * 10000ull;
+    return 4;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 5 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 5) + quotient * 1000ull;
+    return 5;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 6 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 6) + quotient * 100ull;
+    return 6;
+  }
+  remainder *= mod_inv1;
+
+  n = (remainder >> 7) + quotient * 10ull;
+  return 7;
+}
+
+// The main algorithm for shorter interval case
+template <class T>
+FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) FMT_NOEXCEPT {
+  decimal_fp<T> ret_value;
+  // Compute k and beta
+  const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);
+  const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k);
+
+  // Compute xi and zi
+  using cache_entry_type = typename cache_accessor<T>::cache_entry_type;
+  const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k);
+
+  auto xi = cache_accessor<T>::compute_left_endpoint_for_shorter_interval_case(
+      cache, beta_minus_1);
+  auto zi = cache_accessor<T>::compute_right_endpoint_for_shorter_interval_case(
+      cache, beta_minus_1);
+
+  // If the left endpoint is not an integer, increase it
+  if (!is_left_endpoint_integer_shorter_interval<T>(exponent)) ++xi;
+
+  // Try bigger divisor
+  ret_value.significand = zi / 10;
+
+  // If succeed, remove trailing zeros if necessary and return
+  if (ret_value.significand * 10 >= xi) {
+    ret_value.exponent = minus_k + 1;
+    ret_value.exponent += remove_trailing_zeros(ret_value.significand);
+    return ret_value;
+  }
+
+  // Otherwise, compute the round-up of y
+  ret_value.significand =
+      cache_accessor<T>::compute_round_up_for_shorter_interval_case(
+          cache, beta_minus_1);
+  ret_value.exponent = minus_k;
+
+  // When tie occurs, choose one of them according to the rule
+  if (exponent >= float_info<T>::shorter_interval_tie_lower_threshold &&
+      exponent <= float_info<T>::shorter_interval_tie_upper_threshold) {
+    ret_value.significand = ret_value.significand % 2 == 0
+                                ? ret_value.significand
+                                : ret_value.significand - 1;
+  } else if (ret_value.significand < xi) {
+    ++ret_value.significand;
+  }
+  return ret_value;
+}
+
+template <typename T> decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT {
+  // Step 1: integer promotion & Schubfach multiplier calculation.
+
+  using carrier_uint = typename float_info<T>::carrier_uint;
+  using cache_entry_type = typename cache_accessor<T>::cache_entry_type;
+  auto br = bit_cast<carrier_uint>(x);
+
+  // Extract significand bits and exponent bits.
+  const carrier_uint significand_mask =
+      (static_cast<carrier_uint>(1) << float_info<T>::significand_bits) - 1;
+  carrier_uint significand = (br & significand_mask);
+  int exponent = static_cast<int>((br & exponent_mask<T>()) >>
+                                  float_info<T>::significand_bits);
+
+  if (exponent != 0) {  // Check if normal.
+    exponent += float_info<T>::exponent_bias - float_info<T>::significand_bits;
+
+    // Shorter interval case; proceed like Schubfach.
+    if (significand == 0) return shorter_interval_case<T>(exponent);
+
+    significand |=
+        (static_cast<carrier_uint>(1) << float_info<T>::significand_bits);
+  } else {
+    // Subnormal case; the interval is always regular.
+    if (significand == 0) return {0, 0};
+    exponent = float_info<T>::min_exponent - float_info<T>::significand_bits;
+  }
+
+  const bool include_left_endpoint = (significand % 2 == 0);
+  const bool include_right_endpoint = include_left_endpoint;
+
+  // Compute k and beta.
+  const int minus_k = floor_log10_pow2(exponent) - float_info<T>::kappa;
+  const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k);
+  const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k);
+
+  // Compute zi and deltai
+  // 10^kappa <= deltai < 10^(kappa + 1)
+  const uint32_t deltai = cache_accessor<T>::compute_delta(cache, beta_minus_1);
+  const carrier_uint two_fc = significand << 1;
+  const carrier_uint two_fr = two_fc | 1;
+  const carrier_uint zi =
+      cache_accessor<T>::compute_mul(two_fr << beta_minus_1, cache);
+
+  // Step 2: Try larger divisor; remove trailing zeros if necessary
+
+  // Using an upper bound on zi, we might be able to optimize the division
+  // better than the compiler; we are computing zi / big_divisor here
+  decimal_fp<T> ret_value;
+  ret_value.significand = divide_by_10_to_kappa_plus_1(zi);
+  uint32_t r = static_cast<uint32_t>(zi - float_info<T>::big_divisor *
+                                              ret_value.significand);
+
+  if (r > deltai) {
+    goto small_divisor_case_label;
+  } else if (r < deltai) {
+    // Exclude the right endpoint if necessary
+    if (r == 0 && !include_right_endpoint &&
+        is_endpoint_integer<T>(two_fr, exponent, minus_k)) {
+      --ret_value.significand;
+      r = float_info<T>::big_divisor;
+      goto small_divisor_case_label;
+    }
+  } else {
+    // r == deltai; compare fractional parts
+    // Check conditions in the order different from the paper
+    // to take advantage of short-circuiting
+    const carrier_uint two_fl = two_fc - 1;
+    if ((!include_left_endpoint ||
+         !is_endpoint_integer<T>(two_fl, exponent, minus_k)) &&
+        !cache_accessor<T>::compute_mul_parity(two_fl, cache, beta_minus_1)) {
+      goto small_divisor_case_label;
+    }
+  }
+  ret_value.exponent = minus_k + float_info<T>::kappa + 1;
+
+  // We may need to remove trailing zeros
+  ret_value.exponent += remove_trailing_zeros(ret_value.significand);
+  return ret_value;
+
+  // Step 3: Find the significand with the smaller divisor
+
+small_divisor_case_label:
+  ret_value.significand *= 10;
+  ret_value.exponent = minus_k + float_info<T>::kappa;
+
+  const uint32_t mask = (1u << float_info<T>::kappa) - 1;
+  auto dist = r - (deltai / 2) + (float_info<T>::small_divisor / 2);
+
+  // Is dist divisible by 2^kappa?
+  if ((dist & mask) == 0) {
+    const bool approx_y_parity =
+        ((dist ^ (float_info<T>::small_divisor / 2)) & 1) != 0;
+    dist >>= float_info<T>::kappa;
+
+    // Is dist divisible by 5^kappa?
+    if (check_divisibility_and_divide_by_pow5<float_info<T>::kappa>(dist)) {
+      ret_value.significand += dist;
+
+      // Check z^(f) >= epsilon^(f)
+      // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1,
+      // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f)
+      // Since there are only 2 possibilities, we only need to care about the
+      // parity. Also, zi and r should have the same parity since the divisor
+      // is an even number
+      if (cache_accessor<T>::compute_mul_parity(two_fc, cache, beta_minus_1) !=
+          approx_y_parity) {
+        --ret_value.significand;
+      } else {
+        // If z^(f) >= epsilon^(f), we might have a tie
+        // when z^(f) == epsilon^(f), or equivalently, when y is an integer
+        if (is_center_integer<T>(two_fc, exponent, minus_k)) {
+          ret_value.significand = ret_value.significand % 2 == 0
+                                      ? ret_value.significand
+                                      : ret_value.significand - 1;
+        }
+      }
+    }
+    // Is dist not divisible by 5^kappa?
+    else {
+      ret_value.significand += dist;
+    }
+  }
+  // Is dist not divisible by 2^kappa?
+  else {
+    // Since we know dist is small, we might be able to optimize the division
+    // better than the compiler; we are computing dist / small_divisor here
+    ret_value.significand +=
+        small_division_by_pow10<float_info<T>::kappa>(dist);
+  }
+  return ret_value;
+}
+}  // namespace dragonbox
+
+// Formats value using a variation of the Fixed-Precision Positive
+// Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
+// https://fmt.dev/papers/p372-steele.pdf.
+template <typename Double>
+void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
+                     int& exp10) {
+  bigint numerator;    // 2 * R in (FPP)^2.
+  bigint denominator;  // 2 * S in (FPP)^2.
+  // lower and upper are differences between value and corresponding boundaries.
+  bigint lower;             // (M^- in (FPP)^2).
+  bigint upper_store;       // upper's value if different from lower.
+  bigint* upper = nullptr;  // (M^+ in (FPP)^2).
+  fp value;
+  // Shift numerator and denominator by an extra bit or two (if lower boundary
+  // is closer) to make lower and upper integers. This eliminates multiplication
+  // by 2 during later computations.
+  const bool is_predecessor_closer =
+      binary32 ? value.assign(static_cast<float>(d)) : value.assign(d);
+  int shift = is_predecessor_closer ? 2 : 1;
+  uint64_t significand = value.f << shift;
+  if (value.e >= 0) {
+    numerator.assign(significand);
+    numerator <<= value.e;
+    lower.assign(1);
+    lower <<= value.e;
+    if (shift != 1) {
+      upper_store.assign(1);
+      upper_store <<= value.e + 1;
+      upper = &upper_store;
+    }
+    denominator.assign_pow10(exp10);
+    denominator <<= shift;
+  } else if (exp10 < 0) {
+    numerator.assign_pow10(-exp10);
+    lower.assign(numerator);
+    if (shift != 1) {
+      upper_store.assign(numerator);
+      upper_store <<= 1;
+      upper = &upper_store;
+    }
+    numerator *= significand;
+    denominator.assign(1);
+    denominator <<= shift - value.e;
+  } else {
+    numerator.assign(significand);
+    denominator.assign_pow10(exp10);
+    denominator <<= shift - value.e;
+    lower.assign(1);
+    if (shift != 1) {
+      upper_store.assign(1ULL << 1);
+      upper = &upper_store;
+    }
+  }
+  // Invariant: value == (numerator / denominator) * pow(10, exp10).
+  if (num_digits < 0) {
+    // Generate the shortest representation.
+    if (!upper) upper = &lower;
+    bool even = (value.f & 1) == 0;
+    num_digits = 0;
+    char* data = buf.data();
+    for (;;) {
+      int digit = numerator.divmod_assign(denominator);
+      bool low = compare(numerator, lower) - even < 0;  // numerator <[=] lower.
+      // numerator + upper >[=] pow10:
+      bool high = add_compare(numerator, *upper, denominator) + even > 0;
+      data[num_digits++] = static_cast<char>('0' + digit);
+      if (low || high) {
+        if (!low) {
+          ++data[num_digits - 1];
+        } else if (high) {
+          int result = add_compare(numerator, numerator, denominator);
+          // Round half to even.
+          if (result > 0 || (result == 0 && (digit % 2) != 0))
+            ++data[num_digits - 1];
+        }
+        buf.try_resize(to_unsigned(num_digits));
+        exp10 -= num_digits - 1;
+        return;
+      }
+      numerator *= 10;
+      lower *= 10;
+      if (upper != &lower) *upper *= 10;
+    }
+  }
+  // Generate the given number of digits.
+  exp10 -= num_digits - 1;
+  if (num_digits == 0) {
+    buf.try_resize(1);
+    denominator *= 10;
+    buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
+    return;
+  }
+  buf.try_resize(to_unsigned(num_digits));
+  for (int i = 0; i < num_digits - 1; ++i) {
+    int digit = numerator.divmod_assign(denominator);
+    buf[i] = static_cast<char>('0' + digit);
+    numerator *= 10;
+  }
+  int digit = numerator.divmod_assign(denominator);
+  auto result = add_compare(numerator, numerator, denominator);
+  if (result > 0 || (result == 0 && (digit % 2) != 0)) {
+    if (digit == 9) {
+      const auto overflow = '0' + 10;
+      buf[num_digits - 1] = overflow;
+      // Propagate the carry.
+      for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) {
+        buf[i] = '0';
+        ++buf[i - 1];
+      }
+      if (buf[0] == overflow) {
+        buf[0] = '1';
+        ++exp10;
+      }
+      return;
+    }
+    ++digit;
+  }
+  buf[num_digits - 1] = static_cast<char>('0' + digit);
+}
+
+template <typename T>
+int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
+  static_assert(!std::is_same<T, float>::value, "");
+  FMT_ASSERT(value >= 0, "value is negative");
+
+  const bool fixed = specs.format == float_format::fixed;
+  if (value <= 0) {  // <= instead of == to silence a warning.
+    if (precision <= 0 || !fixed) {
+      buf.push_back('0');
+      return 0;
+    }
+    buf.try_resize(to_unsigned(precision));
+    std::uninitialized_fill_n(buf.data(), precision, '0');
+    return -precision;
+  }
+
+  if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf);
+
+  if (precision < 0) {
+    // Use Dragonbox for the shortest format.
+    if (specs.binary32) {
+      auto dec = dragonbox::to_decimal(static_cast<float>(value));
+      write<char>(buffer_appender<char>(buf), dec.significand);
+      return dec.exponent;
+    }
+    auto dec = dragonbox::to_decimal(static_cast<double>(value));
+    write<char>(buffer_appender<char>(buf), dec.significand);
+    return dec.exponent;
+  }
+
+  // Use Grisu + Dragon4 for the given precision:
+  // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
+  int exp = 0;
+  const int min_exp = -60;  // alpha in Grisu.
+  int cached_exp10 = 0;     // K in Grisu.
+  fp normalized = normalize(fp(value));
+  const auto cached_pow = get_cached_power(
+      min_exp - (normalized.e + fp::significand_size), cached_exp10);
+  normalized = normalized * cached_pow;
+  // Limit precision to the maximum possible number of significant digits in an
+  // IEEE754 double because we don't need to generate zeros.
+  const int max_double_digits = 767;
+  if (precision > max_double_digits) precision = max_double_digits;
+  fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
+  if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) {
+    exp += handler.size - cached_exp10 - 1;
+    fallback_format(value, handler.precision, specs.binary32, buf, exp);
+  } else {
+    exp += handler.exp10;
+    buf.try_resize(to_unsigned(handler.size));
+  }
+  if (!fixed && !specs.showpoint) {
+    // Remove trailing zeros.
+    auto num_digits = buf.size();
+    while (num_digits > 0 && buf[num_digits - 1] == '0') {
+      --num_digits;
+      ++exp;
+    }
+    buf.try_resize(num_digits);
+  }
+  return exp;
+}  // namespace detail
+
+template <typename T>
+int snprintf_float(T value, int precision, float_specs specs,
+                   buffer<char>& buf) {
+  // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
+  FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer");
+  static_assert(!std::is_same<T, float>::value, "");
+
+  // Subtract 1 to account for the difference in precision since we use %e for
+  // both general and exponent format.
+  if (specs.format == float_format::general ||
+      specs.format == float_format::exp)
+    precision = (precision >= 0 ? precision : 6) - 1;
+
+  // Build the format string.
+  enum { max_format_size = 7 };  // The longest format is "%#.*Le".
+  char format[max_format_size];
+  char* format_ptr = format;
+  *format_ptr++ = '%';
+  if (specs.showpoint && specs.format == float_format::hex) *format_ptr++ = '#';
+  if (precision >= 0) {
+    *format_ptr++ = '.';
+    *format_ptr++ = '*';
+  }
+  if (std::is_same<T, long double>()) *format_ptr++ = 'L';
+  *format_ptr++ = specs.format != float_format::hex
+                      ? (specs.format == float_format::fixed ? 'f' : 'e')
+                      : (specs.upper ? 'A' : 'a');
+  *format_ptr = '\0';
+
+  // Format using snprintf.
+  auto offset = buf.size();
+  for (;;) {
+    auto begin = buf.data() + offset;
+    auto capacity = buf.capacity() - offset;
+#ifdef FMT_FUZZ
+    if (precision > 100000)
+      throw std::runtime_error(
+          "fuzz mode - avoid large allocation inside snprintf");
+#endif
+    // Suppress the warning about a nonliteral format string.
+    // Cannot use auto because of a bug in MinGW (#1532).
+    int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
+    int result = precision >= 0
+                     ? snprintf_ptr(begin, capacity, format, precision, value)
+                     : snprintf_ptr(begin, capacity, format, value);
+    if (result < 0) {
+      // The buffer will grow exponentially.
+      buf.try_reserve(buf.capacity() + 1);
+      continue;
+    }
+    auto size = to_unsigned(result);
+    // Size equal to capacity means that the last character was truncated.
+    if (size >= capacity) {
+      buf.try_reserve(size + offset + 1);  // Add 1 for the terminating '\0'.
+      continue;
+    }
+    auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
+    if (specs.format == float_format::fixed) {
+      if (precision == 0) {
+        buf.try_resize(size);
+        return 0;
+      }
+      // Find and remove the decimal point.
+      auto end = begin + size, p = end;
+      do {
+        --p;
+      } while (is_digit(*p));
+      int fraction_size = static_cast<int>(end - p - 1);
+      std::memmove(p, p + 1, to_unsigned(fraction_size));
+      buf.try_resize(size - 1);
+      return -fraction_size;
+    }
+    if (specs.format == float_format::hex) {
+      buf.try_resize(size + offset);
+      return 0;
+    }
+    // Find and parse the exponent.
+    auto end = begin + size, exp_pos = end;
+    do {
+      --exp_pos;
+    } while (*exp_pos != 'e');
+    char sign = exp_pos[1];
+    FMT_ASSERT(sign == '+' || sign == '-', "");
+    int exp = 0;
+    auto p = exp_pos + 2;  // Skip 'e' and sign.
+    do {
+      FMT_ASSERT(is_digit(*p), "");
+      exp = exp * 10 + (*p++ - '0');
+    } while (p != end);
+    if (sign == '-') exp = -exp;
+    int fraction_size = 0;
+    if (exp_pos != begin + 1) {
+      // Remove trailing zeros.
+      auto fraction_end = exp_pos - 1;
+      while (*fraction_end == '0') --fraction_end;
+      // Move the fractional part left to get rid of the decimal point.
+      fraction_size = static_cast<int>(fraction_end - begin - 1);
+      std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size));
+    }
+    buf.try_resize(to_unsigned(fraction_size) + offset + 1);
+    return exp - fraction_size;
+  }
+}
+}  // namespace detail
+
+template <> struct formatter<detail::bigint> {
+  FMT_CONSTEXPR format_parse_context::iterator parse(
+      format_parse_context& ctx) {
+    return ctx.begin();
+  }
+
+  format_context::iterator format(const detail::bigint& n,
+                                  format_context& ctx) {
+    auto out = ctx.out();
+    bool first = true;
+    for (auto i = n.bigits_.size(); i > 0; --i) {
+      auto value = n.bigits_[i - 1u];
+      if (first) {
+        out = format_to(out, FMT_STRING("{:x}"), value);
+        first = false;
+        continue;
+      }
+      out = format_to(out, FMT_STRING("{:08x}"), value);
+    }
+    if (n.exp_ > 0)
+      out = format_to(out, FMT_STRING("p{}"),
+                      n.exp_ * detail::bigint::bigit_bits);
+    return out;
+  }
+};
+
+FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
+  for_each_codepoint(s, [this](uint32_t cp, int error) {
+    if (error != 0) FMT_THROW(std::runtime_error("invalid utf8"));
+    if (cp <= 0xFFFF) {
+      buffer_.push_back(static_cast<wchar_t>(cp));
+    } else {
+      cp -= 0x10000;
+      buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));
+      buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));
+    }
+  });
+  buffer_.push_back(0);
+}
+
+FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
+                                  const char* message) FMT_NOEXCEPT {
+  FMT_TRY {
+    auto ec = std::error_code(error_code, std::generic_category());
+    write(std::back_inserter(out), std::system_error(ec, message).what());
+    return;
+  }
+  FMT_CATCH(...) {}
+  format_error_code(out, error_code, message);
+}
+
+FMT_FUNC void detail::error_handler::on_error(const char* message) {
+  FMT_THROW(format_error(message));
+}
+
+FMT_FUNC void report_system_error(int error_code,
+                                  const char* message) FMT_NOEXCEPT {
+  report_error(format_system_error, error_code, message);
+}
+
+FMT_FUNC std::string vformat(string_view fmt, format_args args) {
+  // Don't optimize the "{}" case to keep the binary size small and because it
+  // can be better optimized in fmt::format anyway.
+  auto buffer = memory_buffer();
+  detail::vformat_to(buffer, fmt, args);
+  return to_string(buffer);
+}
+
+#ifdef _WIN32
+namespace detail {
+using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
+extern "C" __declspec(dllimport) int __stdcall WriteConsoleW(  //
+    void*, const void*, dword, dword*, void*);
+}  // namespace detail
+#endif
+
+namespace detail {
+FMT_FUNC void print(std::FILE* f, string_view text) {
+#ifdef _WIN32
+  auto fd = _fileno(f);
+  if (_isatty(fd)) {
+    detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
+    auto written = detail::dword();
+    if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
+                              u16.c_str(), static_cast<uint32_t>(u16.size()),
+                              &written, nullptr)) {
+      return;
+    }
+    // Fallback to fwrite on failure. It can happen if the output has been
+    // redirected to NUL.
+  }
+#endif
+  detail::fwrite_fully(text.data(), 1, text.size(), f);
+}
+}  // namespace detail
+
+FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
+  memory_buffer buffer;
+  detail::vformat_to(buffer, format_str, args);
+  detail::print(f, {buffer.data(), buffer.size()});
+}
+
+#ifdef _WIN32
+// Print assuming legacy (non-Unicode) encoding.
+FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
+                                      format_args args) {
+  memory_buffer buffer;
+  detail::vformat_to(buffer, format_str,
+                     basic_format_args<buffer_context<char>>(args));
+  fwrite_fully(buffer.data(), 1, buffer.size(), f);
+}
+#endif
+
+FMT_FUNC void vprint(string_view format_str, format_args args) {
+  vprint(stdout, format_str, args);
+}
+
+FMT_END_NAMESPACE
+
+#endif  // FMT_FORMAT_INL_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/format.h b/wpiutil/src/main/native/fmtlib/include/fmt/format.h
new file mode 100644
index 0000000..5398a23
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/format.h
@@ -0,0 +1,2830 @@
+/*
+ Formatting library for C++
+
+ Copyright (c) 2012 - present, Victor Zverovich
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ 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 AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ --- Optional exception to the license ---
+
+ As an exception, if, as a result of your compiling your source code, portions
+ of this Software are embedded into a machine-executable object form of such
+ source code, you may redistribute such embedded portions in such object form
+ without including the above copyright and permission notices.
+ */
+
+#ifndef FMT_FORMAT_H_
+#define FMT_FORMAT_H_
+
+#include <cmath>         // std::signbit
+#include <cstdint>       // uint32_t
+#include <limits>        // std::numeric_limits
+#include <memory>        // std::uninitialized_copy
+#include <stdexcept>     // std::runtime_error
+#include <system_error>  // std::system_error
+#include <utility>       // std::swap
+
+#include "core.h"
+
+#ifdef __INTEL_COMPILER
+#  define FMT_ICC_VERSION __INTEL_COMPILER
+#elif defined(__ICL)
+#  define FMT_ICC_VERSION __ICL
+#else
+#  define FMT_ICC_VERSION 0
+#endif
+
+#ifdef __NVCC__
+#  define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__)
+#else
+#  define FMT_CUDA_VERSION 0
+#endif
+
+#ifdef __has_builtin
+#  define FMT_HAS_BUILTIN(x) __has_builtin(x)
+#else
+#  define FMT_HAS_BUILTIN(x) 0
+#endif
+
+#if FMT_GCC_VERSION || FMT_CLANG_VERSION
+#  define FMT_NOINLINE __attribute__((noinline))
+#else
+#  define FMT_NOINLINE
+#endif
+
+#if FMT_MSC_VER
+#  define FMT_MSC_DEFAULT = default
+#else
+#  define FMT_MSC_DEFAULT
+#endif
+
+#ifndef FMT_THROW
+#  if FMT_EXCEPTIONS
+#    if FMT_MSC_VER || FMT_NVCC
+FMT_BEGIN_NAMESPACE
+namespace detail {
+template <typename Exception> inline void do_throw(const Exception& x) {
+  // Silence unreachable code warnings in MSVC and NVCC because these
+  // are nearly impossible to fix in a generic code.
+  volatile bool b = true;
+  if (b) throw x;
+}
+}  // namespace detail
+FMT_END_NAMESPACE
+#      define FMT_THROW(x) detail::do_throw(x)
+#    else
+#      define FMT_THROW(x) throw x
+#    endif
+#  else
+#    define FMT_THROW(x)               \
+      do {                             \
+        FMT_ASSERT(false, (x).what()); \
+      } while (false)
+#  endif
+#endif
+
+#if FMT_EXCEPTIONS
+#  define FMT_TRY try
+#  define FMT_CATCH(x) catch (x)
+#else
+#  define FMT_TRY if (true)
+#  define FMT_CATCH(x) if (false)
+#endif
+
+#ifndef FMT_DEPRECATED
+#  if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
+#    define FMT_DEPRECATED [[deprecated]]
+#  else
+#    if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
+#      define FMT_DEPRECATED __attribute__((deprecated))
+#    elif FMT_MSC_VER
+#      define FMT_DEPRECATED __declspec(deprecated)
+#    else
+#      define FMT_DEPRECATED /* deprecated */
+#    endif
+#  endif
+#endif
+
+// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers.
+#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC
+#  define FMT_DEPRECATED_ALIAS
+#else
+#  define FMT_DEPRECATED_ALIAS FMT_DEPRECATED
+#endif
+
+#ifndef FMT_USE_USER_DEFINED_LITERALS
+// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs.
+#  if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \
+       FMT_MSC_VER >= 1900) &&                                         \
+      (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480)
+#    define FMT_USE_USER_DEFINED_LITERALS 1
+#  else
+#    define FMT_USE_USER_DEFINED_LITERALS 0
+#  endif
+#endif
+
+// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of
+// integer formatter template instantiations to just one by only using the
+// largest integer type. This results in a reduction in binary size but will
+// cause a decrease in integer formatting performance.
+#if !defined(FMT_REDUCE_INT_INSTANTIATIONS)
+#  define FMT_REDUCE_INT_INSTANTIATIONS 0
+#endif
+
+// __builtin_clz is broken in clang with Microsoft CodeGen:
+// https://github.com/fmtlib/fmt/issues/519
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER
+#  define FMT_BUILTIN_CLZ(n) __builtin_clz(n)
+#endif
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER
+#  define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)
+#endif
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz))
+#  define FMT_BUILTIN_CTZ(n) __builtin_ctz(n)
+#endif
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll))
+#  define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n)
+#endif
+
+#if FMT_MSC_VER
+#  include <intrin.h>  // _BitScanReverse[64], _BitScanForward[64], _umul128
+#endif
+
+// Some compilers masquerade as both MSVC and GCC-likes or otherwise support
+// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the
+// MSVC intrinsics if the clz and clzll builtins are not available.
+#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL)
+FMT_BEGIN_NAMESPACE
+namespace detail {
+// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning.
+#  if !defined(__clang__)
+#    pragma managed(push, off)
+#    pragma intrinsic(_BitScanForward)
+#    pragma intrinsic(_BitScanReverse)
+#    if defined(_WIN64)
+#      pragma intrinsic(_BitScanForward64)
+#      pragma intrinsic(_BitScanReverse64)
+#    endif
+#  endif
+
+inline auto clz(uint32_t x) -> int {
+  unsigned long r = 0;
+  _BitScanReverse(&r, x);
+  FMT_ASSERT(x != 0, "");
+  // Static analysis complains about using uninitialized data
+  // "r", but the only way that can happen is if "x" is 0,
+  // which the callers guarantee to not happen.
+  FMT_MSC_WARNING(suppress : 6102)
+  return 31 ^ static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CLZ(n) detail::clz(n)
+
+inline auto clzll(uint64_t x) -> int {
+  unsigned long r = 0;
+#  ifdef _WIN64
+  _BitScanReverse64(&r, x);
+#  else
+  // Scan the high 32 bits.
+  if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) return 63 ^ (r + 32);
+  // Scan the low 32 bits.
+  _BitScanReverse(&r, static_cast<uint32_t>(x));
+#  endif
+  FMT_ASSERT(x != 0, "");
+  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.
+  return 63 ^ static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CLZLL(n) detail::clzll(n)
+
+inline auto ctz(uint32_t x) -> int {
+  unsigned long r = 0;
+  _BitScanForward(&r, x);
+  FMT_ASSERT(x != 0, "");
+  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.
+  return static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CTZ(n) detail::ctz(n)
+
+inline auto ctzll(uint64_t x) -> int {
+  unsigned long r = 0;
+  FMT_ASSERT(x != 0, "");
+  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.
+#  ifdef _WIN64
+  _BitScanForward64(&r, x);
+#  else
+  // Scan the low 32 bits.
+  if (_BitScanForward(&r, static_cast<uint32_t>(x))) return static_cast<int>(r);
+  // Scan the high 32 bits.
+  _BitScanForward(&r, static_cast<uint32_t>(x >> 32));
+  r += 32;
+#  endif
+  return static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CTZLL(n) detail::ctzll(n)
+#  if !defined(__clang__)
+#    pragma managed(pop)
+#  endif
+}  // namespace detail
+FMT_END_NAMESPACE
+#endif
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+#if __cplusplus >= 202002L || \
+    (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
+#  define FMT_CONSTEXPR20 constexpr
+#else
+#  define FMT_CONSTEXPR20
+#endif
+
+// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have
+// undefined behavior (e.g. due to type aliasing).
+// Example: uint64_t d = bit_cast<uint64_t>(2.718);
+template <typename Dest, typename Source>
+inline auto bit_cast(const Source& source) -> Dest {
+  static_assert(sizeof(Dest) == sizeof(Source), "size mismatch");
+  Dest dest;
+  std::memcpy(&dest, &source, sizeof(dest));
+  return dest;
+}
+
+inline auto is_big_endian() -> bool {
+  const auto u = 1u;
+  struct bytes {
+    char data[sizeof(u)];
+  };
+  return bit_cast<bytes>(u).data[0] == 0;
+}
+
+// A fallback implementation of uintptr_t for systems that lack it.
+struct fallback_uintptr {
+  unsigned char value[sizeof(void*)];
+
+  fallback_uintptr() = default;
+  explicit fallback_uintptr(const void* p) {
+    *this = bit_cast<fallback_uintptr>(p);
+    if (is_big_endian()) {
+      for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j)
+        std::swap(value[i], value[j]);
+    }
+  }
+};
+#ifdef UINTPTR_MAX
+using uintptr_t = ::uintptr_t;
+inline auto to_uintptr(const void* p) -> uintptr_t {
+  return bit_cast<uintptr_t>(p);
+}
+#else
+using uintptr_t = fallback_uintptr;
+inline auto to_uintptr(const void* p) -> fallback_uintptr {
+  return fallback_uintptr(p);
+}
+#endif
+
+// Returns the largest possible value for type T. Same as
+// std::numeric_limits<T>::max() but shorter and not affected by the max macro.
+template <typename T> constexpr auto max_value() -> T {
+  return (std::numeric_limits<T>::max)();
+}
+template <typename T> constexpr auto num_bits() -> int {
+  return std::numeric_limits<T>::digits;
+}
+// std::numeric_limits<T>::digits may return 0 for 128-bit ints.
+template <> constexpr auto num_bits<int128_t>() -> int { return 128; }
+template <> constexpr auto num_bits<uint128_t>() -> int { return 128; }
+template <> constexpr auto num_bits<fallback_uintptr>() -> int {
+  return static_cast<int>(sizeof(void*) *
+                          std::numeric_limits<unsigned char>::digits);
+}
+
+FMT_INLINE void assume(bool condition) {
+  (void)condition;
+#if FMT_HAS_BUILTIN(__builtin_assume)
+  __builtin_assume(condition);
+#endif
+}
+
+// An approximation of iterator_t for pre-C++20 systems.
+template <typename T>
+using iterator_t = decltype(std::begin(std::declval<T&>()));
+template <typename T> using sentinel_t = decltype(std::end(std::declval<T&>()));
+
+// A workaround for std::string not having mutable data() until C++17.
+template <typename Char>
+inline auto get_data(std::basic_string<Char>& s) -> Char* {
+  return &s[0];
+}
+template <typename Container>
+inline auto get_data(Container& c) -> typename Container::value_type* {
+  return c.data();
+}
+
+#if defined(_SECURE_SCL) && _SECURE_SCL
+// Make a checked iterator to avoid MSVC warnings.
+template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
+template <typename T> auto make_checked(T* p, size_t size) -> checked_ptr<T> {
+  return {p, size};
+}
+#else
+template <typename T> using checked_ptr = T*;
+template <typename T> inline auto make_checked(T* p, size_t) -> T* { return p; }
+#endif
+
+// Attempts to reserve space for n extra characters in the output range.
+// Returns a pointer to the reserved range or a reference to it.
+template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
+#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
+__attribute__((no_sanitize("undefined")))
+#endif
+inline auto
+reserve(std::back_insert_iterator<Container> it, size_t n)
+    -> checked_ptr<typename Container::value_type> {
+  Container& c = get_container(it);
+  size_t size = c.size();
+  c.resize(size + n);
+  return make_checked(get_data(c) + size, n);
+}
+
+template <typename T>
+inline auto reserve(buffer_appender<T> it, size_t n) -> buffer_appender<T> {
+  buffer<T>& buf = get_container(it);
+  buf.try_reserve(buf.size() + n);
+  return it;
+}
+
+template <typename Iterator>
+constexpr auto reserve(Iterator& it, size_t) -> Iterator& {
+  return it;
+}
+
+template <typename OutputIt>
+using reserve_iterator =
+    remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>;
+
+template <typename T, typename OutputIt>
+constexpr auto to_pointer(OutputIt, size_t) -> T* {
+  return nullptr;
+}
+template <typename T> auto to_pointer(buffer_appender<T> it, size_t n) -> T* {
+  buffer<T>& buf = get_container(it);
+  auto size = buf.size();
+  if (buf.capacity() < size + n) return nullptr;
+  buf.try_resize(size + n);
+  return buf.data() + size;
+}
+
+template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
+inline auto base_iterator(std::back_insert_iterator<Container>& it,
+                          checked_ptr<typename Container::value_type>)
+    -> std::back_insert_iterator<Container> {
+  return it;
+}
+
+template <typename Iterator>
+constexpr auto base_iterator(Iterator, Iterator it) -> Iterator {
+  return it;
+}
+
+// <algorithm> is spectacularly slow to compile in C++20 so use a simple fill_n
+// instead (#1998).
+template <typename OutputIt, typename Size, typename T>
+FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value)
+    -> OutputIt {
+  for (Size i = 0; i < count; ++i) *out++ = value;
+  return out;
+}
+template <typename T, typename Size>
+FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* {
+  if (is_constant_evaluated()) {
+    return fill_n<T*, Size, T>(out, count, value);
+  }
+  std::memset(out, value, to_unsigned(count));
+  return out + count;
+}
+
+#ifdef __cpp_char8_t
+using char8_type = char8_t;
+#else
+enum char8_type : unsigned char {};
+#endif
+
+template <typename OutChar, typename InputIt, typename OutputIt>
+FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end,
+                                                  OutputIt out) -> OutputIt {
+  return copy_str<OutChar>(begin, end, out);
+}
+
+// A public domain branchless UTF-8 decoder by Christopher Wellons:
+// https://github.com/skeeto/branchless-utf8
+/* Decode the next character, c, from s, reporting errors in e.
+ *
+ * Since this is a branchless decoder, four bytes will be read from the
+ * buffer regardless of the actual length of the next character. This
+ * means the buffer _must_ have at least three bytes of zero padding
+ * following the end of the data stream.
+ *
+ * Errors are reported in e, which will be non-zero if the parsed
+ * character was somehow invalid: invalid byte sequence, non-canonical
+ * encoding, or a surrogate half.
+ *
+ * The function returns a pointer to the next character. When an error
+ * occurs, this pointer will be a guess that depends on the particular
+ * error, but it will always advance at least one byte.
+ */
+FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e)
+    -> const char* {
+  constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
+  constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
+  constexpr const int shiftc[] = {0, 18, 12, 6, 0};
+  constexpr const int shifte[] = {0, 6, 4, 2, 0};
+
+  int len = code_point_length(s);
+  const char* next = s + len;
+
+  // Assume a four-byte character and load four bytes. Unused bits are
+  // shifted out.
+  *c = uint32_t(s[0] & masks[len]) << 18;
+  *c |= uint32_t(s[1] & 0x3f) << 12;
+  *c |= uint32_t(s[2] & 0x3f) << 6;
+  *c |= uint32_t(s[3] & 0x3f) << 0;
+  *c >>= shiftc[len];
+
+  // Accumulate the various error conditions.
+  using uchar = unsigned char;
+  *e = (*c < mins[len]) << 6;       // non-canonical encoding
+  *e |= ((*c >> 11) == 0x1b) << 7;  // surrogate half?
+  *e |= (*c > 0x10FFFF) << 8;       // out of range?
+  *e |= (uchar(s[1]) & 0xc0) >> 2;
+  *e |= (uchar(s[2]) & 0xc0) >> 4;
+  *e |= uchar(s[3]) >> 6;
+  *e ^= 0x2a;  // top two bits of each tail byte correct?
+  *e >>= shifte[len];
+
+  return next;
+}
+
+template <typename F>
+FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) {
+  auto decode = [f](const char* p) {
+    auto cp = uint32_t();
+    auto error = 0;
+    p = utf8_decode(p, &cp, &error);
+    f(cp, error);
+    return p;
+  };
+  auto p = s.data();
+  const size_t block_size = 4;  // utf8_decode always reads blocks of 4 chars.
+  if (s.size() >= block_size) {
+    for (auto end = p + s.size() - block_size + 1; p < end;) p = decode(p);
+  }
+  if (auto num_chars_left = s.data() + s.size() - p) {
+    char buf[2 * block_size - 1] = {};
+    copy_str<char>(p, p + num_chars_left, buf);
+    p = buf;
+    do {
+      p = decode(p);
+    } while (p - buf < num_chars_left);
+  }
+}
+
+template <typename Char>
+inline auto compute_width(basic_string_view<Char> s) -> size_t {
+  return s.size();
+}
+
+// Computes approximate display width of a UTF-8 string.
+FMT_CONSTEXPR inline size_t compute_width(string_view s) {
+  size_t num_code_points = 0;
+  // It is not a lambda for compatibility with C++14.
+  struct count_code_points {
+    size_t* count;
+    FMT_CONSTEXPR void operator()(uint32_t cp, int error) const {
+      *count += detail::to_unsigned(
+          1 +
+          (error == 0 && cp >= 0x1100 &&
+           (cp <= 0x115f ||  // Hangul Jamo init. consonants
+            cp == 0x2329 ||  // LEFT-POINTING ANGLE BRACKET
+            cp == 0x232a ||  // RIGHT-POINTING ANGLE BRACKET
+            // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE:
+            (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) ||
+            (cp >= 0xac00 && cp <= 0xd7a3) ||    // Hangul Syllables
+            (cp >= 0xf900 && cp <= 0xfaff) ||    // CJK Compatibility Ideographs
+            (cp >= 0xfe10 && cp <= 0xfe19) ||    // Vertical Forms
+            (cp >= 0xfe30 && cp <= 0xfe6f) ||    // CJK Compatibility Forms
+            (cp >= 0xff00 && cp <= 0xff60) ||    // Fullwidth Forms
+            (cp >= 0xffe0 && cp <= 0xffe6) ||    // Fullwidth Forms
+            (cp >= 0x20000 && cp <= 0x2fffd) ||  // CJK
+            (cp >= 0x30000 && cp <= 0x3fffd) ||
+            // Miscellaneous Symbols and Pictographs + Emoticons:
+            (cp >= 0x1f300 && cp <= 0x1f64f) ||
+            // Supplemental Symbols and Pictographs:
+            (cp >= 0x1f900 && cp <= 0x1f9ff))));
+    }
+  };
+  for_each_codepoint(s, count_code_points{&num_code_points});
+  return num_code_points;
+}
+
+inline auto compute_width(basic_string_view<char8_type> s) -> size_t {
+  return compute_width(basic_string_view<char>(
+      reinterpret_cast<const char*>(s.data()), s.size()));
+}
+
+template <typename Char>
+inline auto code_point_index(basic_string_view<Char> s, size_t n) -> size_t {
+  size_t size = s.size();
+  return n < size ? n : size;
+}
+
+// Calculates the index of the nth code point in a UTF-8 string.
+inline auto code_point_index(basic_string_view<char8_type> s, size_t n)
+    -> size_t {
+  const char8_type* data = s.data();
+  size_t num_code_points = 0;
+  for (size_t i = 0, size = s.size(); i != size; ++i) {
+    if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i;
+  }
+  return s.size();
+}
+
+template <typename T>
+using is_fast_float = bool_constant<std::numeric_limits<T>::is_iec559 &&
+                                    sizeof(T) <= sizeof(double)>;
+
+#ifndef FMT_USE_FULL_CACHE_DRAGONBOX
+#  define FMT_USE_FULL_CACHE_DRAGONBOX 0
+#endif
+
+template <typename T>
+template <typename U>
+void buffer<T>::append(const U* begin, const U* end) {
+  while (begin != end) {
+    auto count = to_unsigned(end - begin);
+    try_reserve(size_ + count);
+    auto free_cap = capacity_ - size_;
+    if (free_cap < count) count = free_cap;
+    std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count));
+    size_ += count;
+    begin += count;
+  }
+}
+
+template <typename T, typename Enable = void>
+struct is_locale : std::false_type {};
+template <typename T>
+struct is_locale<T, void_t<decltype(T::classic())>> : std::true_type {};
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+// The number of characters to store in the basic_memory_buffer object itself
+// to avoid dynamic memory allocation.
+enum { inline_buffer_size = 500 };
+
+/**
+  \rst
+  A dynamically growing memory buffer for trivially copyable/constructible types
+  with the first ``SIZE`` elements stored in the object itself.
+
+  You can use the ``memory_buffer`` type alias for ``char`` instead.
+
+  **Example**::
+
+     fmt::memory_buffer out;
+     format_to(out, "The answer is {}.", 42);
+
+  This will append the following output to the ``out`` object:
+
+  .. code-block:: none
+
+     The answer is 42.
+
+  The output can be converted to an ``std::string`` with ``to_string(out)``.
+  \endrst
+ */
+template <typename T, size_t SIZE = inline_buffer_size,
+          typename Allocator = std::allocator<T>>
+class basic_memory_buffer final : public detail::buffer<T> {
+ private:
+  T store_[SIZE];
+
+  // Don't inherit from Allocator avoid generating type_info for it.
+  Allocator alloc_;
+
+  // Deallocate memory allocated by the buffer.
+  void deallocate() {
+    T* data = this->data();
+    if (data != store_) alloc_.deallocate(data, this->capacity());
+  }
+
+ protected:
+  void grow(size_t size) final FMT_OVERRIDE;
+
+ public:
+  using value_type = T;
+  using const_reference = const T&;
+
+  explicit basic_memory_buffer(const Allocator& alloc = Allocator())
+      : alloc_(alloc) {
+    this->set(store_, SIZE);
+  }
+  ~basic_memory_buffer() { deallocate(); }
+
+ private:
+  // Move data from other to this buffer.
+  void move(basic_memory_buffer& other) {
+    alloc_ = std::move(other.alloc_);
+    T* data = other.data();
+    size_t size = other.size(), capacity = other.capacity();
+    if (data == other.store_) {
+      this->set(store_, capacity);
+      std::uninitialized_copy(other.store_, other.store_ + size,
+                              detail::make_checked(store_, capacity));
+    } else {
+      this->set(data, capacity);
+      // Set pointer to the inline array so that delete is not called
+      // when deallocating.
+      other.set(other.store_, 0);
+    }
+    this->resize(size);
+  }
+
+ public:
+  /**
+    \rst
+    Constructs a :class:`fmt::basic_memory_buffer` object moving the content
+    of the other object to it.
+    \endrst
+   */
+  basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); }
+
+  /**
+    \rst
+    Moves the content of the other ``basic_memory_buffer`` object to this one.
+    \endrst
+   */
+  auto operator=(basic_memory_buffer&& other) FMT_NOEXCEPT
+      -> basic_memory_buffer& {
+    FMT_ASSERT(this != &other, "");
+    deallocate();
+    move(other);
+    return *this;
+  }
+
+  // Returns a copy of the allocator associated with this buffer.
+  auto get_allocator() const -> Allocator { return alloc_; }
+
+  /**
+    Resizes the buffer to contain *count* elements. If T is a POD type new
+    elements may not be initialized.
+   */
+  void resize(size_t count) { this->try_resize(count); }
+
+  /** Increases the buffer capacity to *new_capacity*. */
+  void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }
+
+  // Directly append data into the buffer
+  using detail::buffer<T>::append;
+  template <typename ContiguousRange>
+  void append(const ContiguousRange& range) {
+    append(range.data(), range.data() + range.size());
+  }
+};
+
+template <typename T, size_t SIZE, typename Allocator>
+void basic_memory_buffer<T, SIZE, Allocator>::grow(size_t size) {
+#ifdef FMT_FUZZ
+  if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much");
+#endif
+  const size_t max_size = std::allocator_traits<Allocator>::max_size(alloc_);
+  size_t old_capacity = this->capacity();
+  size_t new_capacity = old_capacity + old_capacity / 2;
+  if (size > new_capacity)
+    new_capacity = size;
+  else if (new_capacity > max_size)
+    new_capacity = size > max_size ? size : max_size;
+  T* old_data = this->data();
+  T* new_data =
+      std::allocator_traits<Allocator>::allocate(alloc_, new_capacity);
+  // The following code doesn't throw, so the raw pointer above doesn't leak.
+  std::uninitialized_copy(old_data, old_data + this->size(),
+                          detail::make_checked(new_data, new_capacity));
+  this->set(new_data, new_capacity);
+  // deallocate must not throw according to the standard, but even if it does,
+  // the buffer already uses the new storage and will deallocate it in
+  // destructor.
+  if (old_data != store_) alloc_.deallocate(old_data, old_capacity);
+}
+
+using memory_buffer = basic_memory_buffer<char>;
+
+template <typename T, size_t SIZE, typename Allocator>
+struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type {
+};
+
+namespace detail {
+FMT_API void print(std::FILE*, string_view);
+}
+
+/** A formatting error such as invalid format string. */
+FMT_CLASS_API
+class FMT_API format_error : public std::runtime_error {
+ public:
+  explicit format_error(const char* message) : std::runtime_error(message) {}
+  explicit format_error(const std::string& message)
+      : std::runtime_error(message) {}
+  format_error(const format_error&) = default;
+  format_error& operator=(const format_error&) = default;
+  format_error(format_error&&) = default;
+  format_error& operator=(format_error&&) = default;
+  ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT;
+};
+
+/**
+  \rst
+  Constructs a `~fmt::format_arg_store` object that contains references
+  to arguments and can be implicitly converted to `~fmt::format_args`.
+  If ``fmt`` is a compile-time string then `make_args_checked` checks
+  its validity at compile time.
+  \endrst
+ */
+template <typename... Args, typename S, typename Char = char_t<S>>
+FMT_INLINE auto make_args_checked(const S& fmt,
+                                  const remove_reference_t<Args>&... args)
+    -> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
+  static_assert(
+      detail::count<(
+              std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
+              std::is_reference<Args>::value)...>() == 0,
+      "passing views as lvalues is disallowed");
+  detail::check_format_string<Args...>(fmt);
+  return {args...};
+}
+
+// compile-time support
+namespace detail_exported {
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <typename Char, size_t N> struct fixed_string {
+  constexpr fixed_string(const Char (&str)[N]) {
+    detail::copy_str<Char, const Char*, Char*>(static_cast<const Char*>(str),
+                                               str + N, data);
+  }
+  Char data[N]{};
+};
+#endif
+
+// Converts a compile-time string to basic_string_view.
+template <typename Char, size_t N>
+constexpr auto compile_string_to_view(const Char (&s)[N])
+    -> basic_string_view<Char> {
+  // Remove trailing NUL character if needed. Won't be present if this is used
+  // with a raw character array (i.e. not defined as a string).
+  return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
+}
+template <typename Char>
+constexpr auto compile_string_to_view(detail::std_string_view<Char> s)
+    -> basic_string_view<Char> {
+  return {s.data(), s.size()};
+}
+}  // namespace detail_exported
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+inline void throw_format_error(const char* message) {
+  FMT_THROW(format_error(message));
+}
+
+template <typename T> struct is_integral : std::is_integral<T> {};
+template <> struct is_integral<int128_t> : std::true_type {};
+template <> struct is_integral<uint128_t> : std::true_type {};
+
+template <typename T>
+using is_signed =
+    std::integral_constant<bool, std::numeric_limits<T>::is_signed ||
+                                     std::is_same<T, int128_t>::value>;
+
+// Returns true if value is negative, false otherwise.
+// Same as `value < 0` but doesn't produce warnings if T is an unsigned type.
+template <typename T, FMT_ENABLE_IF(is_signed<T>::value)>
+FMT_CONSTEXPR auto is_negative(T value) -> bool {
+  return value < 0;
+}
+template <typename T, FMT_ENABLE_IF(!is_signed<T>::value)>
+FMT_CONSTEXPR auto is_negative(T) -> bool {
+  return false;
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_CONSTEXPR auto is_supported_floating_point(T) -> uint16_t {
+  return (std::is_same<T, float>::value && FMT_USE_FLOAT) ||
+         (std::is_same<T, double>::value && FMT_USE_DOUBLE) ||
+         (std::is_same<T, long double>::value && FMT_USE_LONG_DOUBLE);
+}
+
+// Smallest of uint32_t, uint64_t, uint128_t that is large enough to
+// represent all values of an integral type T.
+template <typename T>
+using uint32_or_64_or_128_t =
+    conditional_t<num_bits<T>() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS,
+                  uint32_t,
+                  conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>>;
+template <typename T>
+using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;
+
+#define FMT_POWERS_OF_10(factor)                                             \
+  factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
+      (factor)*1000000, (factor)*10000000, (factor)*100000000,               \
+      (factor)*1000000000
+
+// Static data is placed in this class template for the header-only config.
+template <typename T = void> struct basic_data {
+  // log10(2) = 0x0.4d104d427de7fbcc...
+  static const uint64_t log10_2_significand = 0x4d104d427de7fbcc;
+
+  // GCC generates slightly better code for pairs than chars.
+  FMT_API static constexpr const char digits[100][2] = {
+      {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
+      {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
+      {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
+      {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
+      {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
+      {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
+      {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
+      {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
+      {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
+      {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
+      {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
+      {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
+      {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
+      {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
+      {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
+      {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
+      {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
+
+  FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
+  FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
+  FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
+                                                         0x1000000u | ' '};
+  FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1,
+                                                                0};
+  FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1,
+                                                                 0};
+};
+
+#ifdef FMT_SHARED
+// Required for -flto, -fivisibility=hidden and -shared to work
+extern template struct basic_data<void>;
+#endif
+
+// This is a struct rather than an alias to avoid shadowing warnings in gcc.
+struct data : basic_data<> {};
+
+template <typename T> FMT_CONSTEXPR auto count_digits_fallback(T n) -> int {
+  int count = 1;
+  for (;;) {
+    // Integer division is slow so do it for a group of four digits instead
+    // of for every digit. The idea comes from the talk by Alexandrescu
+    // "Three Optimization Tips for C++". See speed-test for a comparison.
+    if (n < 10) return count;
+    if (n < 100) return count + 1;
+    if (n < 1000) return count + 2;
+    if (n < 10000) return count + 3;
+    n /= 10000u;
+    count += 4;
+  }
+}
+#if FMT_USE_INT128
+FMT_CONSTEXPR inline auto count_digits(uint128_t n) -> int {
+  return count_digits_fallback(n);
+}
+#endif
+
+// Returns the number of decimal digits in n. Leading zeros are not counted
+// except for n == 0 in which case count_digits returns 1.
+FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int {
+#ifdef FMT_BUILTIN_CLZLL
+  if (!is_constant_evaluated()) {
+    // https://github.com/fmtlib/format-benchmark/blob/master/digits10
+    // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
+    constexpr uint16_t bsr2log10[] = {
+        1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4,  4,  4,  5,  5,  5,
+        6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9,  10, 10, 10,
+        10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
+        15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
+    auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63];
+    constexpr const uint64_t zero_or_powers_of_10[] = {
+        0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
+        10000000000000000000ULL};
+    return t - (n < zero_or_powers_of_10[t]);
+  }
+#endif
+  return count_digits_fallback(n);
+}
+
+// Counts the number of digits in n. BITS = log2(radix).
+template <int BITS, typename UInt>
+FMT_CONSTEXPR auto count_digits(UInt n) -> int {
+#ifdef FMT_BUILTIN_CLZ
+  if (num_bits<UInt>() == 32)
+    return (FMT_BUILTIN_CLZ(static_cast<uint32_t>(n) | 1) ^ 31) / BITS + 1;
+#endif
+  int num_digits = 0;
+  do {
+    ++num_digits;
+  } while ((n >>= BITS) != 0);
+  return num_digits;
+}
+
+template <> auto count_digits<4>(detail::fallback_uintptr n) -> int;
+
+// It is a separate function rather than a part of count_digits to workaround
+// the lack of static constexpr in constexpr functions.
+FMT_INLINE uint64_t count_digits_inc(int n) {
+  // An optimization by Kendall Willets from https://bit.ly/3uOIQrB.
+  // This increments the upper 32 bits (log10(T) - 1) when >= T is added.
+#define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T)
+  static constexpr uint64_t table[] = {
+      FMT_INC(0),          FMT_INC(0),          FMT_INC(0),           // 8
+      FMT_INC(10),         FMT_INC(10),         FMT_INC(10),          // 64
+      FMT_INC(100),        FMT_INC(100),        FMT_INC(100),         // 512
+      FMT_INC(1000),       FMT_INC(1000),       FMT_INC(1000),        // 4096
+      FMT_INC(10000),      FMT_INC(10000),      FMT_INC(10000),       // 32k
+      FMT_INC(100000),     FMT_INC(100000),     FMT_INC(100000),      // 256k
+      FMT_INC(1000000),    FMT_INC(1000000),    FMT_INC(1000000),     // 2048k
+      FMT_INC(10000000),   FMT_INC(10000000),   FMT_INC(10000000),    // 16M
+      FMT_INC(100000000),  FMT_INC(100000000),  FMT_INC(100000000),   // 128M
+      FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000),  // 1024M
+      FMT_INC(1000000000), FMT_INC(1000000000)                        // 4B
+  };
+  return table[n];
+}
+
+// Optional version of count_digits for better performance on 32-bit platforms.
+FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int {
+#ifdef FMT_BUILTIN_CLZ
+  if (!is_constant_evaluated()) {
+    auto inc = count_digits_inc(FMT_BUILTIN_CLZ(n | 1) ^ 31);
+    return static_cast<int>((n + inc) >> 32);
+  }
+#endif
+  return count_digits_fallback(n);
+}
+
+template <typename Int> constexpr auto digits10() FMT_NOEXCEPT -> int {
+  return std::numeric_limits<Int>::digits10;
+}
+template <> constexpr auto digits10<int128_t>() FMT_NOEXCEPT -> int {
+  return 38;
+}
+template <> constexpr auto digits10<uint128_t>() FMT_NOEXCEPT -> int {
+  return 38;
+}
+
+template <typename Char> struct thousands_sep_result {
+  std::string grouping;
+  Char thousands_sep;
+};
+
+template <typename Char>
+FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char>;
+template <typename Char>
+inline auto thousands_sep(locale_ref loc) -> thousands_sep_result<Char> {
+  auto result = thousands_sep_impl<char>(loc);
+  return {result.grouping, Char(result.thousands_sep)};
+}
+template <>
+inline auto thousands_sep(locale_ref loc) -> thousands_sep_result<wchar_t> {
+  return thousands_sep_impl<wchar_t>(loc);
+}
+
+template <typename Char>
+FMT_API auto decimal_point_impl(locale_ref loc) -> Char;
+template <typename Char> inline auto decimal_point(locale_ref loc) -> Char {
+  return Char(decimal_point_impl<char>(loc));
+}
+template <> inline auto decimal_point(locale_ref loc) -> wchar_t {
+  return decimal_point_impl<wchar_t>(loc);
+}
+
+// Compares two characters for equality.
+template <typename Char> auto equal2(const Char* lhs, const char* rhs) -> bool {
+  return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]);
+}
+inline auto equal2(const char* lhs, const char* rhs) -> bool {
+  return memcmp(lhs, rhs, 2) == 0;
+}
+
+// Copies two characters from src to dst.
+template <typename Char> void copy2(Char* dst, const char* src) {
+  *dst++ = static_cast<Char>(*src++);
+  *dst = static_cast<Char>(*src);
+}
+FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); }
+
+template <typename Iterator> struct format_decimal_result {
+  Iterator begin;
+  Iterator end;
+};
+
+// Formats a decimal unsigned integer value writing into out pointing to a
+// buffer of specified size. The caller must ensure that the buffer is large
+// enough.
+template <typename Char, typename UInt>
+FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size)
+    -> format_decimal_result<Char*> {
+  FMT_ASSERT(size >= count_digits(value), "invalid digit count");
+  out += size;
+  Char* end = out;
+  if (is_constant_evaluated()) {
+    while (value >= 10) {
+      *--out = static_cast<Char>('0' + value % 10);
+      value /= 10;
+    }
+    *--out = static_cast<Char>('0' + value);
+    return {out, end};
+  }
+  while (value >= 100) {
+    // Integer division is slow so do it for a group of two digits instead
+    // of for every digit. The idea comes from the talk by Alexandrescu
+    // "Three Optimization Tips for C++". See speed-test for a comparison.
+    out -= 2;
+    copy2(out, data::digits[value % 100]);
+    value /= 100;
+  }
+  if (value < 10) {
+    *--out = static_cast<Char>('0' + value);
+    return {out, end};
+  }
+  out -= 2;
+  copy2(out, data::digits[value]);
+  return {out, end};
+}
+
+template <typename Char, typename UInt, typename Iterator,
+          FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<Iterator>>::value)>
+inline auto format_decimal(Iterator out, UInt value, int size)
+    -> format_decimal_result<Iterator> {
+  // Buffer is large enough to hold all digits (digits10 + 1).
+  Char buffer[digits10<UInt>() + 1];
+  auto end = format_decimal(buffer, value, size).end;
+  return {out, detail::copy_str_noinline<Char>(buffer, end, out)};
+}
+
+template <unsigned BASE_BITS, typename Char, typename UInt>
+FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits,
+                               bool upper = false) -> Char* {
+  buffer += num_digits;
+  Char* end = buffer;
+  do {
+    const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits;
+    unsigned digit = (value & ((1 << BASE_BITS) - 1));
+    *--buffer = static_cast<Char>(BASE_BITS < 4 ? static_cast<char>('0' + digit)
+                                                : digits[digit]);
+  } while ((value >>= BASE_BITS) != 0);
+  return end;
+}
+
+template <unsigned BASE_BITS, typename Char>
+auto format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits,
+                 bool = false) -> Char* {
+  auto char_digits = std::numeric_limits<unsigned char>::digits / 4;
+  int start = (num_digits + char_digits - 1) / char_digits - 1;
+  if (int start_digits = num_digits % char_digits) {
+    unsigned value = n.value[start--];
+    buffer = format_uint<BASE_BITS>(buffer, value, start_digits);
+  }
+  for (; start >= 0; --start) {
+    unsigned value = n.value[start];
+    buffer += char_digits;
+    auto p = buffer;
+    for (int i = 0; i < char_digits; ++i) {
+      unsigned digit = (value & ((1 << BASE_BITS) - 1));
+      *--p = static_cast<Char>(data::hex_digits[digit]);
+      value >>= BASE_BITS;
+    }
+  }
+  return buffer;
+}
+
+template <unsigned BASE_BITS, typename Char, typename It, typename UInt>
+inline auto format_uint(It out, UInt value, int num_digits, bool upper = false)
+    -> It {
+  if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) {
+    format_uint<BASE_BITS>(ptr, value, num_digits, upper);
+    return out;
+  }
+  // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1).
+  char buffer[num_bits<UInt>() / BASE_BITS + 1];
+  format_uint<BASE_BITS>(buffer, value, num_digits, upper);
+  return detail::copy_str_noinline<Char>(buffer, buffer + num_digits, out);
+}
+
+// A converter from UTF-8 to UTF-16.
+class utf8_to_utf16 {
+ private:
+  basic_memory_buffer<wchar_t> buffer_;
+
+ public:
+  FMT_API explicit utf8_to_utf16(string_view s);
+  operator basic_string_view<wchar_t>() const { return {&buffer_[0], size()}; }
+  auto size() const -> size_t { return buffer_.size() - 1; }
+  auto c_str() const -> const wchar_t* { return &buffer_[0]; }
+  auto str() const -> std::wstring { return {&buffer_[0], size()}; }
+};
+
+namespace dragonbox {
+
+// Type-specific information that Dragonbox uses.
+template <class T> struct float_info;
+
+template <> struct float_info<float> {
+  using carrier_uint = uint32_t;
+  static const int significand_bits = 23;
+  static const int exponent_bits = 8;
+  static const int min_exponent = -126;
+  static const int max_exponent = 127;
+  static const int exponent_bias = -127;
+  static const int decimal_digits = 9;
+  static const int kappa = 1;
+  static const int big_divisor = 100;
+  static const int small_divisor = 10;
+  static const int min_k = -31;
+  static const int max_k = 46;
+  static const int cache_bits = 64;
+  static const int divisibility_check_by_5_threshold = 39;
+  static const int case_fc_pm_half_lower_threshold = -1;
+  static const int case_fc_pm_half_upper_threshold = 6;
+  static const int case_fc_lower_threshold = -2;
+  static const int case_fc_upper_threshold = 6;
+  static const int case_shorter_interval_left_endpoint_lower_threshold = 2;
+  static const int case_shorter_interval_left_endpoint_upper_threshold = 3;
+  static const int shorter_interval_tie_lower_threshold = -35;
+  static const int shorter_interval_tie_upper_threshold = -35;
+  static const int max_trailing_zeros = 7;
+};
+
+template <> struct float_info<double> {
+  using carrier_uint = uint64_t;
+  static const int significand_bits = 52;
+  static const int exponent_bits = 11;
+  static const int min_exponent = -1022;
+  static const int max_exponent = 1023;
+  static const int exponent_bias = -1023;
+  static const int decimal_digits = 17;
+  static const int kappa = 2;
+  static const int big_divisor = 1000;
+  static const int small_divisor = 100;
+  static const int min_k = -292;
+  static const int max_k = 326;
+  static const int cache_bits = 128;
+  static const int divisibility_check_by_5_threshold = 86;
+  static const int case_fc_pm_half_lower_threshold = -2;
+  static const int case_fc_pm_half_upper_threshold = 9;
+  static const int case_fc_lower_threshold = -4;
+  static const int case_fc_upper_threshold = 9;
+  static const int case_shorter_interval_left_endpoint_lower_threshold = 2;
+  static const int case_shorter_interval_left_endpoint_upper_threshold = 3;
+  static const int shorter_interval_tie_lower_threshold = -77;
+  static const int shorter_interval_tie_upper_threshold = -77;
+  static const int max_trailing_zeros = 16;
+};
+
+template <typename T> struct decimal_fp {
+  using significand_type = typename float_info<T>::carrier_uint;
+  significand_type significand;
+  int exponent;
+};
+
+template <typename T>
+FMT_API auto to_decimal(T x) FMT_NOEXCEPT -> decimal_fp<T>;
+}  // namespace dragonbox
+
+template <typename T>
+constexpr auto exponent_mask() ->
+    typename dragonbox::float_info<T>::carrier_uint {
+  using uint = typename dragonbox::float_info<T>::carrier_uint;
+  return ((uint(1) << dragonbox::float_info<T>::exponent_bits) - 1)
+         << dragonbox::float_info<T>::significand_bits;
+}
+
+// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
+template <typename Char, typename It>
+auto write_exponent(int exp, It it) -> It {
+  FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
+  if (exp < 0) {
+    *it++ = static_cast<Char>('-');
+    exp = -exp;
+  } else {
+    *it++ = static_cast<Char>('+');
+  }
+  if (exp >= 100) {
+    const char* top = data::digits[exp / 100];
+    if (exp >= 1000) *it++ = static_cast<Char>(top[0]);
+    *it++ = static_cast<Char>(top[1]);
+    exp %= 100;
+  }
+  const char* d = data::digits[exp];
+  *it++ = static_cast<Char>(d[0]);
+  *it++ = static_cast<Char>(d[1]);
+  return it;
+}
+
+template <typename T>
+auto format_float(T value, int precision, float_specs specs, buffer<char>& buf)
+    -> int;
+
+// Formats a floating-point number with snprintf.
+template <typename T>
+auto snprintf_float(T value, int precision, float_specs specs,
+                    buffer<char>& buf) -> int;
+
+template <typename T> auto promote_float(T value) -> T { return value; }
+inline auto promote_float(float value) -> double {
+  return static_cast<double>(value);
+}
+
+template <typename OutputIt, typename Char>
+FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n,
+                                     const fill_t<Char>& fill) -> OutputIt {
+  auto fill_size = fill.size();
+  if (fill_size == 1) return detail::fill_n(it, n, fill[0]);
+  auto data = fill.data();
+  for (size_t i = 0; i < n; ++i)
+    it = copy_str<Char>(data, data + fill_size, it);
+  return it;
+}
+
+// Writes the output of f, padded according to format specifications in specs.
+// size: output size in code units.
+// width: output display width in (terminal) column positions.
+template <align::type align = align::left, typename OutputIt, typename Char,
+          typename F>
+FMT_CONSTEXPR auto write_padded(OutputIt out,
+                                const basic_format_specs<Char>& specs,
+                                size_t size, size_t width, F&& f) -> OutputIt {
+  static_assert(align == align::left || align == align::right, "");
+  unsigned spec_width = to_unsigned(specs.width);
+  size_t padding = spec_width > width ? spec_width - width : 0;
+  auto* shifts = align == align::left ? data::left_padding_shifts
+                                      : data::right_padding_shifts;
+  size_t left_padding = padding >> shifts[specs.align];
+  size_t right_padding = padding - left_padding;
+  auto it = reserve(out, size + padding * specs.fill.size());
+  if (left_padding != 0) it = fill(it, left_padding, specs.fill);
+  it = f(it);
+  if (right_padding != 0) it = fill(it, right_padding, specs.fill);
+  return base_iterator(out, it);
+}
+
+template <align::type align = align::left, typename OutputIt, typename Char,
+          typename F>
+constexpr auto write_padded(OutputIt out, const basic_format_specs<Char>& specs,
+                            size_t size, F&& f) -> OutputIt {
+  return write_padded<align>(out, specs, size, size, f);
+}
+
+template <align::type align = align::left, typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes,
+                               const basic_format_specs<Char>& specs)
+    -> OutputIt {
+  return write_padded<align>(
+      out, specs, bytes.size(), [bytes](reserve_iterator<OutputIt> it) {
+        const char* data = bytes.data();
+        return copy_str<Char>(data, data + bytes.size(), it);
+      });
+}
+
+template <typename Char, typename OutputIt, typename UIntPtr>
+auto write_ptr(OutputIt out, UIntPtr value,
+               const basic_format_specs<Char>* specs) -> OutputIt {
+  int num_digits = count_digits<4>(value);
+  auto size = to_unsigned(num_digits) + size_t(2);
+  auto write = [=](reserve_iterator<OutputIt> it) {
+    *it++ = static_cast<Char>('0');
+    *it++ = static_cast<Char>('x');
+    return format_uint<4, Char>(it, value, num_digits);
+  };
+  return specs ? write_padded<align::right>(out, *specs, size, write)
+               : base_iterator(out, write(reserve(out, size)));
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write_char(OutputIt out, Char value,
+                              const basic_format_specs<Char>& specs)
+    -> OutputIt {
+  return write_padded(out, specs, 1, [=](reserve_iterator<OutputIt> it) {
+    *it++ = value;
+    return it;
+  });
+}
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out, Char value,
+                         const basic_format_specs<Char>& specs,
+                         locale_ref loc = {}) -> OutputIt {
+  return check_char_specs(specs)
+             ? write_char(out, value, specs)
+             : write(out, static_cast<int>(value), specs, loc);
+}
+
+// Data for write_int that doesn't depend on output iterator type. It is used to
+// avoid template code bloat.
+template <typename Char> struct write_int_data {
+  size_t size;
+  size_t padding;
+
+  FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix,
+                               const basic_format_specs<Char>& specs)
+      : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) {
+    if (specs.align == align::numeric) {
+      auto width = to_unsigned(specs.width);
+      if (width > size) {
+        padding = width - size;
+        size = width;
+      }
+    } else if (specs.precision > num_digits) {
+      size = (prefix >> 24) + to_unsigned(specs.precision);
+      padding = to_unsigned(specs.precision - num_digits);
+    }
+  }
+};
+
+// Writes an integer in the format
+//   <left-padding><prefix><numeric-padding><digits><right-padding>
+// where <digits> are written by write_digits(it).
+// prefix contains chars in three lower bytes and the size in the fourth byte.
+template <typename OutputIt, typename Char, typename W>
+FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits,
+                                        unsigned prefix,
+                                        const basic_format_specs<Char>& specs,
+                                        W write_digits) -> OutputIt {
+  // Slightly faster check for specs.width == 0 && specs.precision == -1.
+  if ((specs.width | (specs.precision + 1)) == 0) {
+    auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24));
+    if (prefix != 0) {
+      for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)
+        *it++ = static_cast<Char>(p & 0xff);
+    }
+    return base_iterator(out, write_digits(it));
+  }
+  auto data = write_int_data<Char>(num_digits, prefix, specs);
+  return write_padded<align::right>(
+      out, specs, data.size, [=](reserve_iterator<OutputIt> it) {
+        for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)
+          *it++ = static_cast<Char>(p & 0xff);
+        it = detail::fill_n(it, data.padding, static_cast<Char>('0'));
+        return write_digits(it);
+      });
+}
+
+template <typename OutputIt, typename UInt, typename Char>
+auto write_int_localized(OutputIt& out, UInt value, unsigned prefix,
+                         const basic_format_specs<Char>& specs, locale_ref loc)
+    -> bool {
+  static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, "");
+  const auto sep_size = 1;
+  auto ts = thousands_sep<Char>(loc);
+  if (!ts.thousands_sep) return false;
+  int num_digits = count_digits(value);
+  int size = num_digits, n = num_digits;
+  const std::string& groups = ts.grouping;
+  std::string::const_iterator group = groups.cbegin();
+  while (group != groups.cend() && n > *group && *group > 0 &&
+         *group != max_value<char>()) {
+    size += sep_size;
+    n -= *group;
+    ++group;
+  }
+  if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back());
+  char digits[40];
+  format_decimal(digits, value, num_digits);
+  basic_memory_buffer<Char> buffer;
+  if (prefix != 0) ++size;
+  const auto usize = to_unsigned(size);
+  buffer.resize(usize);
+  basic_string_view<Char> s(&ts.thousands_sep, sep_size);
+  // Index of a decimal digit with the least significant digit having index 0.
+  int digit_index = 0;
+  group = groups.cbegin();
+  auto p = buffer.data() + size - 1;
+  for (int i = num_digits - 1; i > 0; --i) {
+    *p-- = static_cast<Char>(digits[i]);
+    if (*group <= 0 || ++digit_index % *group != 0 ||
+        *group == max_value<char>())
+      continue;
+    if (group + 1 != groups.cend()) {
+      digit_index = 0;
+      ++group;
+    }
+    std::uninitialized_copy(s.data(), s.data() + s.size(),
+                            make_checked(p, s.size()));
+    p -= s.size();
+  }
+  *p-- = static_cast<Char>(*digits);
+  if (prefix != 0) *p = static_cast<Char>(prefix);
+  auto data = buffer.data();
+  out = write_padded<align::right>(
+      out, specs, usize, usize, [=](reserve_iterator<OutputIt> it) {
+        return copy_str<Char>(data, data + size, it);
+      });
+  return true;
+}
+
+FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {
+  prefix |= prefix != 0 ? value << 8 : value;
+  prefix += (1u + (value > 0xff ? 1 : 0)) << 24;
+}
+
+template <typename UInt> struct write_int_arg {
+  UInt abs_value;
+  unsigned prefix;
+};
+
+template <typename T>
+FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign)
+    -> write_int_arg<uint32_or_64_or_128_t<T>> {
+  auto prefix = 0u;
+  auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
+  if (is_negative(value)) {
+    prefix = 0x01000000 | '-';
+    abs_value = 0 - abs_value;
+  } else {
+    prefix = data::prefixes[sign];
+  }
+  return {abs_value, prefix};
+}
+
+template <typename Char, typename OutputIt, typename T>
+FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
+                                        const basic_format_specs<Char>& specs,
+                                        locale_ref loc) -> OutputIt {
+  static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::value, "");
+  auto abs_value = arg.abs_value;
+  auto prefix = arg.prefix;
+  auto utype = static_cast<unsigned>(specs.type);
+  switch (specs.type) {
+  case 0:
+  case 'd': {
+    if (specs.localized &&
+        write_int_localized(out, static_cast<uint64_or_128_t<T>>(abs_value),
+                            prefix, specs, loc)) {
+      return out;
+    }
+    auto num_digits = count_digits(abs_value);
+    return write_int(
+        out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
+          return format_decimal<Char>(it, abs_value, num_digits).end;
+        });
+  }
+  case 'x':
+  case 'X': {
+    if (specs.alt) prefix_append(prefix, (utype << 8) | '0');
+    bool upper = specs.type != 'x';
+    int num_digits = count_digits<4>(abs_value);
+    return write_int(
+        out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
+          return format_uint<4, Char>(it, abs_value, num_digits, upper);
+        });
+  }
+  case 'b':
+  case 'B': {
+    if (specs.alt) prefix_append(prefix, (utype << 8) | '0');
+    int num_digits = count_digits<1>(abs_value);
+    return write_int(out, num_digits, prefix, specs,
+                     [=](reserve_iterator<OutputIt> it) {
+                       return format_uint<1, Char>(it, abs_value, num_digits);
+                     });
+  }
+  case 'o': {
+    int num_digits = count_digits<3>(abs_value);
+    if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
+      // Octal prefix '0' is counted as a digit, so only add it if precision
+      // is not greater than the number of digits.
+      prefix_append(prefix, '0');
+    }
+    return write_int(out, num_digits, prefix, specs,
+                     [=](reserve_iterator<OutputIt> it) {
+                       return format_uint<3, Char>(it, abs_value, num_digits);
+                     });
+  }
+  case 'c':
+    return write_char(out, static_cast<Char>(abs_value), specs);
+  default:
+    FMT_THROW(format_error("invalid type specifier"));
+  }
+  return out;
+}
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_integral<T>::value &&
+                        !std::is_same<T, bool>::value &&
+                        std::is_same<OutputIt, buffer_appender<Char>>::value)>
+FMT_CONSTEXPR auto write(OutputIt out, T value,
+                         const basic_format_specs<Char>& specs, locale_ref loc)
+    -> OutputIt {
+  return write_int(out, make_write_int_arg(value, specs.sign), specs, loc);
+}
+// An inlined version of write used in format string compilation.
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_integral<T>::value &&
+                        !std::is_same<T, bool>::value &&
+                        !std::is_same<OutputIt, buffer_appender<Char>>::value)>
+FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
+                                    const basic_format_specs<Char>& specs,
+                                    locale_ref loc) -> OutputIt {
+  return write_int(out, make_write_int_arg(value, specs.sign), specs, loc);
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
+                         const basic_format_specs<Char>& specs) -> OutputIt {
+  auto data = s.data();
+  auto size = s.size();
+  if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
+    size = code_point_index(s, to_unsigned(specs.precision));
+  auto width =
+      specs.width != 0 ? compute_width(basic_string_view<Char>(data, size)) : 0;
+  return write_padded(out, specs, size, width,
+                      [=](reserve_iterator<OutputIt> it) {
+                        return copy_str<Char>(data, data + size, it);
+                      });
+}
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out,
+                         basic_string_view<type_identity_t<Char>> s,
+                         const basic_format_specs<Char>& specs, locale_ref)
+    -> OutputIt {
+  check_string_type_spec(specs.type);
+  return write(out, s, specs);
+}
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out, const Char* s,
+                         const basic_format_specs<Char>& specs, locale_ref)
+    -> OutputIt {
+  return check_cstring_type_spec(specs.type)
+             ? write(out, basic_string_view<Char>(s), specs, {})
+             : write_ptr<Char>(out, to_uintptr(s), &specs);
+}
+
+template <typename Char, typename OutputIt>
+auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs<Char> specs,
+                     const float_specs& fspecs) -> OutputIt {
+  auto str =
+      isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan");
+  constexpr size_t str_size = 3;
+  auto sign = fspecs.sign;
+  auto size = str_size + (sign ? 1 : 0);
+  // Replace '0'-padding with space for non-finite values.
+  const bool is_zero_fill =
+      specs.fill.size() == 1 && *specs.fill.data() == static_cast<Char>('0');
+  if (is_zero_fill) specs.fill[0] = static_cast<Char>(' ');
+  return write_padded(out, specs, size, [=](reserve_iterator<OutputIt> it) {
+    if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+    return copy_str<Char>(str, str + str_size, it);
+  });
+}
+
+// A decimal floating-point number significand * pow(10, exp).
+struct big_decimal_fp {
+  const char* significand;
+  int significand_size;
+  int exponent;
+};
+
+inline auto get_significand_size(const big_decimal_fp& fp) -> int {
+  return fp.significand_size;
+}
+template <typename T>
+inline auto get_significand_size(const dragonbox::decimal_fp<T>& fp) -> int {
+  return count_digits(fp.significand);
+}
+
+template <typename Char, typename OutputIt>
+inline auto write_significand(OutputIt out, const char* significand,
+                              int& significand_size) -> OutputIt {
+  return copy_str<Char>(significand, significand + significand_size, out);
+}
+template <typename Char, typename OutputIt, typename UInt>
+inline auto write_significand(OutputIt out, UInt significand,
+                              int significand_size) -> OutputIt {
+  return format_decimal<Char>(out, significand, significand_size).end;
+}
+
+template <typename Char, typename UInt,
+          FMT_ENABLE_IF(std::is_integral<UInt>::value)>
+inline auto write_significand(Char* out, UInt significand, int significand_size,
+                              int integral_size, Char decimal_point) -> Char* {
+  if (!decimal_point)
+    return format_decimal(out, significand, significand_size).end;
+  auto end = format_decimal(out + 1, significand, significand_size).end;
+  if (integral_size == 1) {
+    out[0] = out[1];
+  } else {
+    std::uninitialized_copy_n(out + 1, integral_size,
+                              make_checked(out, to_unsigned(integral_size)));
+  }
+  out[integral_size] = decimal_point;
+  return end;
+}
+
+template <typename OutputIt, typename UInt, typename Char,
+          FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<OutputIt>>::value)>
+inline auto write_significand(OutputIt out, UInt significand,
+                              int significand_size, int integral_size,
+                              Char decimal_point) -> OutputIt {
+  // Buffer is large enough to hold digits (digits10 + 1) and a decimal point.
+  Char buffer[digits10<UInt>() + 2];
+  auto end = write_significand(buffer, significand, significand_size,
+                               integral_size, decimal_point);
+  return detail::copy_str_noinline<Char>(buffer, end, out);
+}
+
+template <typename OutputIt, typename Char>
+inline auto write_significand(OutputIt out, const char* significand,
+                              int significand_size, int integral_size,
+                              Char decimal_point) -> OutputIt {
+  out = detail::copy_str_noinline<Char>(significand,
+                                        significand + integral_size, out);
+  if (!decimal_point) return out;
+  *out++ = decimal_point;
+  return detail::copy_str_noinline<Char>(significand + integral_size,
+                                         significand + significand_size, out);
+}
+
+template <typename OutputIt, typename DecimalFP, typename Char>
+auto write_float(OutputIt out, const DecimalFP& fp,
+                 const basic_format_specs<Char>& specs, float_specs fspecs,
+                 Char decimal_point) -> OutputIt {
+  auto significand = fp.significand;
+  int significand_size = get_significand_size(fp);
+  static const Char zero = static_cast<Char>('0');
+  auto sign = fspecs.sign;
+  size_t size = to_unsigned(significand_size) + (sign ? 1 : 0);
+  using iterator = reserve_iterator<OutputIt>;
+
+  int output_exp = fp.exponent + significand_size - 1;
+  auto use_exp_format = [=]() {
+    if (fspecs.format == float_format::exp) return true;
+    if (fspecs.format != float_format::general) return false;
+    // Use the fixed notation if the exponent is in [exp_lower, exp_upper),
+    // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation.
+    const int exp_lower = -4, exp_upper = 16;
+    return output_exp < exp_lower ||
+           output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper);
+  };
+  if (use_exp_format()) {
+    int num_zeros = 0;
+    if (fspecs.showpoint) {
+      num_zeros = fspecs.precision - significand_size;
+      if (num_zeros < 0) num_zeros = 0;
+      size += to_unsigned(num_zeros);
+    } else if (significand_size == 1) {
+      decimal_point = Char();
+    }
+    auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp;
+    int exp_digits = 2;
+    if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3;
+
+    size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits);
+    char exp_char = fspecs.upper ? 'E' : 'e';
+    auto write = [=](iterator it) {
+      if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+      // Insert a decimal point after the first digit and add an exponent.
+      it = write_significand(it, significand, significand_size, 1,
+                             decimal_point);
+      if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero);
+      *it++ = static_cast<Char>(exp_char);
+      return write_exponent<Char>(output_exp, it);
+    };
+    return specs.width > 0 ? write_padded<align::right>(out, specs, size, write)
+                           : base_iterator(out, write(reserve(out, size)));
+  }
+
+  int exp = fp.exponent + significand_size;
+  if (fp.exponent >= 0) {
+    // 1234e5 -> 123400000[.0+]
+    size += to_unsigned(fp.exponent);
+    int num_zeros = fspecs.precision - exp;
+#ifdef FMT_FUZZ
+    if (num_zeros > 5000)
+      throw std::runtime_error("fuzz mode - avoiding excessive cpu use");
+#endif
+    if (fspecs.showpoint) {
+      if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1;
+      if (num_zeros > 0) size += to_unsigned(num_zeros) + 1;
+    }
+    return write_padded<align::right>(out, specs, size, [&](iterator it) {
+      if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+      it = write_significand<Char>(it, significand, significand_size);
+      it = detail::fill_n(it, fp.exponent, zero);
+      if (!fspecs.showpoint) return it;
+      *it++ = decimal_point;
+      return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
+    });
+  } else if (exp > 0) {
+    // 1234e-2 -> 12.34[0+]
+    int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0;
+    size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0);
+    return write_padded<align::right>(out, specs, size, [&](iterator it) {
+      if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+      it = write_significand(it, significand, significand_size, exp,
+                             decimal_point);
+      return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
+    });
+  }
+  // 1234e-6 -> 0.001234
+  int num_zeros = -exp;
+  if (significand_size == 0 && fspecs.precision >= 0 &&
+      fspecs.precision < num_zeros) {
+    num_zeros = fspecs.precision;
+  }
+  bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint;
+  size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros);
+  return write_padded<align::right>(out, specs, size, [&](iterator it) {
+    if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+    *it++ = zero;
+    if (!pointy) return it;
+    *it++ = decimal_point;
+    it = detail::fill_n(it, num_zeros, zero);
+    return write_significand<Char>(it, significand, significand_size);
+  });
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+auto write(OutputIt out, T value, basic_format_specs<Char> specs,
+           locale_ref loc = {}) -> OutputIt {
+  if (const_check(!is_supported_floating_point(value))) return out;
+  float_specs fspecs = parse_float_type_spec(specs);
+  fspecs.sign = specs.sign;
+  if (std::signbit(value)) {  // value < 0 is false for NaN so use signbit.
+    fspecs.sign = sign::minus;
+    value = -value;
+  } else if (fspecs.sign == sign::minus) {
+    fspecs.sign = sign::none;
+  }
+
+  if (!std::isfinite(value))
+    return write_nonfinite(out, std::isinf(value), specs, fspecs);
+
+  if (specs.align == align::numeric && fspecs.sign) {
+    auto it = reserve(out, 1);
+    *it++ = static_cast<Char>(data::signs[fspecs.sign]);
+    out = base_iterator(out, it);
+    fspecs.sign = sign::none;
+    if (specs.width != 0) --specs.width;
+  }
+
+  memory_buffer buffer;
+  if (fspecs.format == float_format::hex) {
+    if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]);
+    snprintf_float(promote_float(value), specs.precision, fspecs, buffer);
+    return write_bytes<align::right>(out, {buffer.data(), buffer.size()},
+                                     specs);
+  }
+  int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
+  if (fspecs.format == float_format::exp) {
+    if (precision == max_value<int>())
+      FMT_THROW(format_error("number is too big"));
+    else
+      ++precision;
+  }
+  if (const_check(std::is_same<T, float>())) fspecs.binary32 = true;
+  fspecs.use_grisu = is_fast_float<T>();
+  int exp = format_float(promote_float(value), precision, fspecs, buffer);
+  fspecs.precision = precision;
+  Char point =
+      fspecs.locale ? decimal_point<Char>(loc) : static_cast<Char>('.');
+  auto fp = big_decimal_fp{buffer.data(), static_cast<int>(buffer.size()), exp};
+  return write_float(out, fp, specs, fspecs, point);
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_fast_float<T>::value)>
+auto write(OutputIt out, T value) -> OutputIt {
+  if (const_check(!is_supported_floating_point(value))) return out;
+
+  using floaty = conditional_t<std::is_same<T, long double>::value, double, T>;
+  using uint = typename dragonbox::float_info<floaty>::carrier_uint;
+  auto bits = bit_cast<uint>(value);
+
+  auto fspecs = float_specs();
+  auto sign_bit = bits & (uint(1) << (num_bits<uint>() - 1));
+  if (sign_bit != 0) {
+    fspecs.sign = sign::minus;
+    value = -value;
+  }
+
+  static const auto specs = basic_format_specs<Char>();
+  uint mask = exponent_mask<floaty>();
+  if ((bits & mask) == mask)
+    return write_nonfinite(out, std::isinf(value), specs, fspecs);
+
+  auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
+  return write_float(out, dec, specs, fspecs, static_cast<Char>('.'));
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(std::is_floating_point<T>::value &&
+                        !is_fast_float<T>::value)>
+inline auto write(OutputIt out, T value) -> OutputIt {
+  return write(out, value, basic_format_specs<Char>());
+}
+
+template <typename Char, typename OutputIt>
+auto write(OutputIt out, monostate, basic_format_specs<Char> = {},
+           locale_ref = {}) -> OutputIt {
+  FMT_ASSERT(false, "");
+  return out;
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> value)
+    -> OutputIt {
+  auto it = reserve(out, value.size());
+  it = copy_str_noinline<Char>(value.begin(), value.end(), it);
+  return base_iterator(out, it);
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_string<T>::value)>
+constexpr auto write(OutputIt out, const T& value) -> OutputIt {
+  return write<Char>(out, to_string_view(value));
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_integral<T>::value &&
+                        !std::is_same<T, bool>::value &&
+                        !std::is_same<T, Char>::value)>
+FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt {
+  auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
+  bool negative = is_negative(value);
+  // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
+  if (negative) abs_value = ~abs_value + 1;
+  int num_digits = count_digits(abs_value);
+  auto size = (negative ? 1 : 0) + static_cast<size_t>(num_digits);
+  auto it = reserve(out, size);
+  if (auto ptr = to_pointer<Char>(it, size)) {
+    if (negative) *ptr++ = static_cast<Char>('-');
+    format_decimal<Char>(ptr, abs_value, num_digits);
+    return out;
+  }
+  if (negative) *it++ = static_cast<Char>('-');
+  it = format_decimal<Char>(it, abs_value, num_digits).end;
+  return base_iterator(out, it);
+}
+
+// FMT_ENABLE_IF() condition separated to workaround MSVC bug
+template <
+    typename Char, typename OutputIt, typename T,
+    bool check =
+        std::is_enum<T>::value && !std::is_same<T, Char>::value &&
+        mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value !=
+            type::custom_type,
+    FMT_ENABLE_IF(check)>
+FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt {
+  return write<Char>(
+      out, static_cast<typename std::underlying_type<T>::type>(value));
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(std::is_same<T, bool>::value)>
+FMT_CONSTEXPR auto write(OutputIt out, T value,
+                         const basic_format_specs<Char>& specs = {},
+                         locale_ref = {}) -> OutputIt {
+  return specs.type && specs.type != 's'
+             ? write(out, value ? 1 : 0, specs, {})
+             : write_bytes(out, value ? "true" : "false", specs);
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt {
+  auto it = reserve(out, 1);
+  *it++ = value;
+  return base_iterator(out, it);
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value)
+    -> OutputIt {
+  if (!value) {
+    FMT_THROW(format_error("string pointer is null"));
+  } else {
+    auto length = std::char_traits<Char>::length(value);
+    out = write(out, basic_string_view<Char>(value, length));
+  }
+  return out;
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(std::is_same<T, void>::value)>
+auto write(OutputIt out, const T* value,
+           const basic_format_specs<Char>& specs = {}, locale_ref = {})
+    -> OutputIt {
+  check_pointer_type_spec(specs.type, error_handler());
+  return write_ptr<Char>(out, to_uintptr(value), &specs);
+}
+
+template <typename Char, typename OutputIt, typename T>
+FMT_CONSTEXPR auto write(OutputIt out, const T& value) ->
+    typename std::enable_if<
+        mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value ==
+            type::custom_type,
+        OutputIt>::type {
+  using context_type = basic_format_context<OutputIt, Char>;
+  using formatter_type =
+      conditional_t<has_formatter<T, context_type>::value,
+                    typename context_type::template formatter_type<T>,
+                    fallback_formatter<T, Char>>;
+  context_type ctx(out, {}, {});
+  return formatter_type().format(value, ctx);
+}
+
+// An argument visitor that formats the argument and writes it via the output
+// iterator. It's a class and not a generic lambda for compatibility with C++11.
+template <typename Char> struct default_arg_formatter {
+  using iterator = buffer_appender<Char>;
+  using context = buffer_context<Char>;
+
+  iterator out;
+  basic_format_args<context> args;
+  locale_ref loc;
+
+  template <typename T> auto operator()(T value) -> iterator {
+    return write<Char>(out, value);
+  }
+  auto operator()(typename basic_format_arg<context>::handle h) -> iterator {
+    basic_format_parse_context<Char> parse_ctx({});
+    context format_ctx(out, args, loc);
+    h.format(parse_ctx, format_ctx);
+    return format_ctx.out();
+  }
+};
+
+template <typename Char> struct arg_formatter {
+  using iterator = buffer_appender<Char>;
+  using context = buffer_context<Char>;
+
+  iterator out;
+  const basic_format_specs<Char>& specs;
+  locale_ref locale;
+
+  template <typename T>
+  FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator {
+    return detail::write(out, value, specs, locale);
+  }
+  auto operator()(typename basic_format_arg<context>::handle) -> iterator {
+    // User-defined types are handled separately because they require access
+    // to the parse context.
+    return out;
+  }
+};
+
+template <typename Char> struct custom_formatter {
+  basic_format_parse_context<Char>& parse_ctx;
+  buffer_context<Char>& ctx;
+
+  void operator()(
+      typename basic_format_arg<buffer_context<Char>>::handle h) const {
+    h.format(parse_ctx, ctx);
+  }
+  template <typename T> void operator()(T) const {}
+};
+
+template <typename T>
+using is_integer =
+    bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value &&
+                  !std::is_same<T, char>::value &&
+                  !std::is_same<T, wchar_t>::value>;
+
+template <typename ErrorHandler> class width_checker {
+ public:
+  explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {}
+
+  template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
+  FMT_CONSTEXPR auto operator()(T value) -> unsigned long long {
+    if (is_negative(value)) handler_.on_error("negative width");
+    return static_cast<unsigned long long>(value);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
+  FMT_CONSTEXPR auto operator()(T) -> unsigned long long {
+    handler_.on_error("width is not integer");
+    return 0;
+  }
+
+ private:
+  ErrorHandler& handler_;
+};
+
+template <typename ErrorHandler> class precision_checker {
+ public:
+  explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {}
+
+  template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
+  FMT_CONSTEXPR auto operator()(T value) -> unsigned long long {
+    if (is_negative(value)) handler_.on_error("negative precision");
+    return static_cast<unsigned long long>(value);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
+  FMT_CONSTEXPR auto operator()(T) -> unsigned long long {
+    handler_.on_error("precision is not integer");
+    return 0;
+  }
+
+ private:
+  ErrorHandler& handler_;
+};
+
+template <template <typename> class Handler, typename FormatArg,
+          typename ErrorHandler>
+FMT_CONSTEXPR auto get_dynamic_spec(FormatArg arg, ErrorHandler eh) -> int {
+  unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg);
+  if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big");
+  return static_cast<int>(value);
+}
+
+template <typename Context, typename ID>
+FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) ->
+    typename Context::format_arg {
+  auto arg = ctx.arg(id);
+  if (!arg) ctx.on_error("argument not found");
+  return arg;
+}
+
+// The standard format specifier handler with checking.
+template <typename Char> class specs_handler : public specs_setter<Char> {
+ private:
+  basic_format_parse_context<Char>& parse_context_;
+  buffer_context<Char>& context_;
+
+  // This is only needed for compatibility with gcc 4.4.
+  using format_arg = basic_format_arg<buffer_context<Char>>;
+
+  FMT_CONSTEXPR auto get_arg(auto_id) -> format_arg {
+    return detail::get_arg(context_, parse_context_.next_arg_id());
+  }
+
+  FMT_CONSTEXPR auto get_arg(int arg_id) -> format_arg {
+    parse_context_.check_arg_id(arg_id);
+    return detail::get_arg(context_, arg_id);
+  }
+
+  FMT_CONSTEXPR auto get_arg(basic_string_view<Char> arg_id) -> format_arg {
+    parse_context_.check_arg_id(arg_id);
+    return detail::get_arg(context_, arg_id);
+  }
+
+ public:
+  FMT_CONSTEXPR specs_handler(basic_format_specs<Char>& specs,
+                              basic_format_parse_context<Char>& parse_ctx,
+                              buffer_context<Char>& ctx)
+      : specs_setter<Char>(specs), parse_context_(parse_ctx), context_(ctx) {}
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
+    this->specs_.width = get_dynamic_spec<width_checker>(
+        get_arg(arg_id), context_.error_handler());
+  }
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
+    this->specs_.precision = get_dynamic_spec<precision_checker>(
+        get_arg(arg_id), context_.error_handler());
+  }
+
+  void on_error(const char* message) { context_.on_error(message); }
+};
+
+template <template <typename> class Handler, typename Context>
+FMT_CONSTEXPR void handle_dynamic_spec(int& value,
+                                       arg_ref<typename Context::char_type> ref,
+                                       Context& ctx) {
+  switch (ref.kind) {
+  case arg_id_kind::none:
+    break;
+  case arg_id_kind::index:
+    value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
+                                              ctx.error_handler());
+    break;
+  case arg_id_kind::name:
+    value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.name),
+                                              ctx.error_handler());
+    break;
+  }
+}
+
+#define FMT_STRING_IMPL(s, base, explicit)                                 \
+  [] {                                                                     \
+    /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
+    /* Use a macro-like name to avoid shadowing warnings. */               \
+    struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base {           \
+      using char_type = fmt::remove_cvref_t<decltype(s[0])>;               \
+      FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit                              \
+      operator fmt::basic_string_view<char_type>() const {                 \
+        return fmt::detail_exported::compile_string_to_view<char_type>(s); \
+      }                                                                    \
+    };                                                                     \
+    return FMT_COMPILE_STRING();                                           \
+  }()
+
+/**
+  \rst
+  Constructs a compile-time format string from a string literal *s*.
+
+  **Example**::
+
+    // A compile-time error because 'd' is an invalid specifier for strings.
+    std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
+  \endrst
+ */
+#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )
+
+#if FMT_USE_USER_DEFINED_LITERALS
+template <typename Char> struct udl_formatter {
+  basic_string_view<Char> str;
+
+  template <typename... T>
+  auto operator()(T&&... args) const -> std::basic_string<Char> {
+    return vformat(str, fmt::make_args_checked<T...>(str, args...));
+  }
+};
+
+#  if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <typename T, typename Char, size_t N,
+          fmt::detail_exported::fixed_string<Char, N> Str>
+struct statically_named_arg : view {
+  static constexpr auto name = Str.data;
+
+  const T& value;
+  statically_named_arg(const T& v) : value(v) {}
+};
+
+template <typename T, typename Char, size_t N,
+          fmt::detail_exported::fixed_string<Char, N> Str>
+struct is_named_arg<statically_named_arg<T, Char, N, Str>> : std::true_type {};
+
+template <typename T, typename Char, size_t N,
+          fmt::detail_exported::fixed_string<Char, N> Str>
+struct is_statically_named_arg<statically_named_arg<T, Char, N, Str>>
+    : std::true_type {};
+
+template <typename Char, size_t N,
+          fmt::detail_exported::fixed_string<Char, N> Str>
+struct udl_arg {
+  template <typename T> auto operator=(T&& value) const {
+    return statically_named_arg<T, Char, N, Str>(std::forward<T>(value));
+  }
+};
+#  else
+template <typename Char> struct udl_arg {
+  const Char* str;
+
+  template <typename T> auto operator=(T&& value) const -> named_arg<Char, T> {
+    return {str, std::forward<T>(value)};
+  }
+};
+#  endif
+#endif  // FMT_USE_USER_DEFINED_LITERALS
+
+template <typename Locale, typename Char>
+auto vformat(const Locale& loc, basic_string_view<Char> format_str,
+             basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> std::basic_string<Char> {
+  basic_memory_buffer<Char> buffer;
+  detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc));
+  return {buffer.data(), buffer.size()};
+}
+
+using format_func = void (*)(detail::buffer<char>&, int, const char*);
+
+FMT_API void format_error_code(buffer<char>& out, int error_code,
+                               string_view message) FMT_NOEXCEPT;
+
+FMT_API void report_error(format_func func, int error_code,
+                          const char* message) FMT_NOEXCEPT;
+FMT_END_DETAIL_NAMESPACE
+
+FMT_API auto vsystem_error(int error_code, string_view format_str,
+                           format_args args) -> std::system_error;
+
+/**
+ \rst
+ Constructs :class:`std::system_error` with a message formatted with
+ ``fmt::format(fmt, args...)``.
+  *error_code* is a system error code as given by ``errno``.
+
+ **Example**::
+
+   // This throws std::system_error with the description
+   //   cannot open file 'madeup': No such file or directory
+   // or similar (system message may vary).
+   const char* filename = "madeup";
+   std::FILE* file = std::fopen(filename, "r");
+   if (!file)
+     throw fmt::system_error(errno, "cannot open file '{}'", filename);
+ \endrst
+*/
+template <typename... T>
+auto system_error(int error_code, format_string<T...> fmt, T&&... args)
+    -> std::system_error {
+  return vsystem_error(error_code, fmt, fmt::make_format_args(args...));
+}
+
+/**
+  \rst
+  Formats an error message for an error returned by an operating system or a
+  language runtime, for example a file opening error, and writes it to *out*.
+  The format is the same as the one used by ``std::system_error(ec, message)``
+  where ``ec`` is ``std::error_code(error_code, std::generic_category()})``.
+  It is implementation-defined but normally looks like:
+
+  .. parsed-literal::
+     *<message>*: *<system-message>*
+
+  where *<message>* is the passed message and *<system-message>* is the system
+  message corresponding to the error code.
+  *error_code* is a system error code as given by ``errno``.
+  \endrst
+ */
+FMT_API void format_system_error(detail::buffer<char>& out, int error_code,
+                                 const char* message) FMT_NOEXCEPT;
+
+// Reports a system error without throwing an exception.
+// Can be used to report errors from destructors.
+FMT_API void report_system_error(int error_code,
+                                 const char* message) FMT_NOEXCEPT;
+
+/** Fast integer formatter. */
+class format_int {
+ private:
+  // Buffer should be large enough to hold all digits (digits10 + 1),
+  // a sign and a null character.
+  enum { buffer_size = std::numeric_limits<unsigned long long>::digits10 + 3 };
+  mutable char buffer_[buffer_size];
+  char* str_;
+
+  template <typename UInt> auto format_unsigned(UInt value) -> char* {
+    auto n = static_cast<detail::uint32_or_64_or_128_t<UInt>>(value);
+    return detail::format_decimal(buffer_, n, buffer_size - 1).begin;
+  }
+
+  template <typename Int> auto format_signed(Int value) -> char* {
+    auto abs_value = static_cast<detail::uint32_or_64_or_128_t<Int>>(value);
+    bool negative = value < 0;
+    if (negative) abs_value = 0 - abs_value;
+    auto begin = format_unsigned(abs_value);
+    if (negative) *--begin = '-';
+    return begin;
+  }
+
+ public:
+  explicit format_int(int value) : str_(format_signed(value)) {}
+  explicit format_int(long value) : str_(format_signed(value)) {}
+  explicit format_int(long long value) : str_(format_signed(value)) {}
+  explicit format_int(unsigned value) : str_(format_unsigned(value)) {}
+  explicit format_int(unsigned long value) : str_(format_unsigned(value)) {}
+  explicit format_int(unsigned long long value)
+      : str_(format_unsigned(value)) {}
+
+  /** Returns the number of characters written to the output buffer. */
+  auto size() const -> size_t {
+    return detail::to_unsigned(buffer_ - str_ + buffer_size - 1);
+  }
+
+  /**
+    Returns a pointer to the output buffer content. No terminating null
+    character is appended.
+   */
+  auto data() const -> const char* { return str_; }
+
+  /**
+    Returns a pointer to the output buffer content with terminating null
+    character appended.
+   */
+  auto c_str() const -> const char* {
+    buffer_[buffer_size - 1] = '\0';
+    return str_;
+  }
+
+  /**
+    \rst
+    Returns the content of the output buffer as an ``std::string``.
+    \endrst
+   */
+  auto str() const -> std::string { return std::string(str_, size()); }
+};
+
+template <typename T, typename Char>
+template <typename FormatContext>
+FMT_CONSTEXPR FMT_INLINE auto
+formatter<T, Char,
+          enable_if_t<detail::type_constant<T, Char>::value !=
+                      detail::type::custom_type>>::format(const T& val,
+                                                          FormatContext& ctx)
+    const -> decltype(ctx.out()) {
+  if (specs_.width_ref.kind != detail::arg_id_kind::none ||
+      specs_.precision_ref.kind != detail::arg_id_kind::none) {
+    auto specs = specs_;
+    detail::handle_dynamic_spec<detail::width_checker>(specs.width,
+                                                       specs.width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(
+        specs.precision, specs.precision_ref, ctx);
+    return detail::write<Char>(ctx.out(), val, specs, ctx.locale());
+  }
+  return detail::write<Char>(ctx.out(), val, specs_, ctx.locale());
+}
+
+#define FMT_FORMAT_AS(Type, Base)                                        \
+  template <typename Char>                                               \
+  struct formatter<Type, Char> : formatter<Base, Char> {                 \
+    template <typename FormatContext>                                    \
+    auto format(Type const& val, FormatContext& ctx) const               \
+        -> decltype(ctx.out()) {                                         \
+      return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \
+    }                                                                    \
+  }
+
+FMT_FORMAT_AS(signed char, int);
+FMT_FORMAT_AS(unsigned char, unsigned);
+FMT_FORMAT_AS(short, int);
+FMT_FORMAT_AS(unsigned short, unsigned);
+FMT_FORMAT_AS(long, long long);
+FMT_FORMAT_AS(unsigned long, unsigned long long);
+FMT_FORMAT_AS(Char*, const Char*);
+FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>);
+FMT_FORMAT_AS(std::nullptr_t, const void*);
+FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);
+
+template <typename Char>
+struct formatter<void*, Char> : formatter<const void*, Char> {
+  template <typename FormatContext>
+  auto format(void* val, FormatContext& ctx) const -> decltype(ctx.out()) {
+    return formatter<const void*, Char>::format(val, ctx);
+  }
+};
+
+template <typename Char, size_t N>
+struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {
+  template <typename FormatContext>
+  FMT_CONSTEXPR auto format(const Char* val, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    return formatter<basic_string_view<Char>, Char>::format(val, ctx);
+  }
+};
+
+// A formatter for types known only at run time such as variant alternatives.
+//
+// Usage:
+//   using variant = std::variant<int, std::string>;
+//   template <>
+//   struct formatter<variant>: dynamic_formatter<> {
+//     auto format(const variant& v, format_context& ctx) {
+//       return visit([&](const auto& val) {
+//           return dynamic_formatter<>::format(val, ctx);
+//       }, v);
+//     }
+//   };
+template <typename Char = char> class dynamic_formatter {
+ private:
+  detail::dynamic_format_specs<Char> specs_;
+  const Char* format_str_;
+
+  struct null_handler : detail::error_handler {
+    void on_align(align_t) {}
+    void on_sign(sign_t) {}
+    void on_hash() {}
+  };
+
+  template <typename Context> void handle_specs(Context& ctx) {
+    detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
+                                                       specs_.width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(
+        specs_.precision, specs_.precision_ref, ctx);
+  }
+
+ public:
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    format_str_ = ctx.begin();
+    // Checks are deferred to formatting time when the argument type is known.
+    detail::dynamic_specs_handler<ParseContext> handler(specs_, ctx);
+    return detail::parse_format_specs(ctx.begin(), ctx.end(), handler);
+  }
+
+  template <typename T, typename FormatContext>
+  auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
+    handle_specs(ctx);
+    detail::specs_checker<null_handler> checker(
+        null_handler(), detail::mapped_type_constant<T, FormatContext>::value);
+    checker.on_align(specs_.align);
+    if (specs_.sign != sign::none) checker.on_sign(specs_.sign);
+    if (specs_.alt) checker.on_hash();
+    if (specs_.precision >= 0) checker.end_precision();
+    return detail::write<Char>(ctx.out(), val, specs_, ctx.locale());
+  }
+};
+
+/**
+  \rst
+  Converts ``p`` to ``const void*`` for pointer formatting.
+
+  **Example**::
+
+    auto s = fmt::format("{}", fmt::ptr(p));
+  \endrst
+ */
+template <typename T> auto ptr(T p) -> const void* {
+  static_assert(std::is_pointer<T>::value, "");
+  return detail::bit_cast<const void*>(p);
+}
+template <typename T> auto ptr(const std::unique_ptr<T>& p) -> const void* {
+  return p.get();
+}
+template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
+  return p.get();
+}
+
+class bytes {
+ private:
+  string_view data_;
+  friend struct formatter<bytes>;
+
+ public:
+  explicit bytes(string_view data) : data_(data) {}
+};
+
+template <> struct formatter<bytes> {
+ private:
+  detail::dynamic_format_specs<char> specs_;
+
+ public:
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    using handler_type = detail::dynamic_specs_handler<ParseContext>;
+    detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
+                                                detail::type::string_type);
+    auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
+    detail::check_string_type_spec(specs_.type, ctx.error_handler());
+    return it;
+  }
+
+  template <typename FormatContext>
+  auto format(bytes b, FormatContext& ctx) -> decltype(ctx.out()) {
+    detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
+                                                       specs_.width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(
+        specs_.precision, specs_.precision_ref, ctx);
+    return detail::write_bytes(ctx.out(), b.data_, specs_);
+  }
+};
+
+template <typename It, typename Sentinel, typename Char = char>
+struct join_view : detail::view {
+  It begin;
+  Sentinel end;
+  basic_string_view<Char> sep;
+
+  join_view(It b, Sentinel e, basic_string_view<Char> s)
+      : begin(b), end(e), sep(s) {}
+};
+
+template <typename It, typename Sentinel, typename Char>
+using arg_join FMT_DEPRECATED_ALIAS = join_view<It, Sentinel, Char>;
+
+template <typename It, typename Sentinel, typename Char>
+struct formatter<join_view<It, Sentinel, Char>, Char> {
+ private:
+  using value_type = typename std::iterator_traits<It>::value_type;
+  using context = buffer_context<Char>;
+  using mapper = detail::arg_mapper<context>;
+
+  template <typename T, FMT_ENABLE_IF(has_formatter<T, context>::value)>
+  static auto map(const T& value) -> const T& {
+    return value;
+  }
+  template <typename T, FMT_ENABLE_IF(!has_formatter<T, context>::value)>
+  static auto map(const T& value) -> decltype(mapper().map(value)) {
+    return mapper().map(value);
+  }
+
+  using formatter_type =
+      conditional_t<is_formattable<value_type, Char>::value,
+                    formatter<remove_cvref_t<decltype(map(
+                                  std::declval<const value_type&>()))>,
+                              Char>,
+                    detail::fallback_formatter<value_type, Char>>;
+
+  formatter_type value_formatter_;
+
+ public:
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return value_formatter_.parse(ctx);
+  }
+
+  template <typename FormatContext>
+  auto format(const join_view<It, Sentinel, Char>& value, FormatContext& ctx)
+      -> decltype(ctx.out()) {
+    auto it = value.begin;
+    auto out = ctx.out();
+    if (it != value.end) {
+      out = value_formatter_.format(map(*it++), ctx);
+      while (it != value.end) {
+        out = detail::copy_str<Char>(value.sep.begin(), value.sep.end(), out);
+        ctx.advance_to(out);
+        out = value_formatter_.format(map(*it++), ctx);
+      }
+    }
+    return out;
+  }
+};
+
+/**
+  Returns an object that formats the iterator range `[begin, end)` with
+  elements separated by `sep`.
+ */
+template <typename It, typename Sentinel>
+auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
+  return {begin, end, sep};
+}
+
+/**
+  \rst
+  Returns an object that formats `range` with elements separated by `sep`.
+
+  **Example**::
+
+    std::vector<int> v = {1, 2, 3};
+    fmt::print("{}", fmt::join(v, ", "));
+    // Output: "1, 2, 3"
+
+  ``fmt::join`` applies passed format specifiers to the range elements::
+
+    fmt::print("{:02}", fmt::join(v, ", "));
+    // Output: "01, 02, 03"
+  \endrst
+ */
+template <typename Range>
+auto join(Range&& range, string_view sep)
+    -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>> {
+  return join(std::begin(range), std::end(range), sep);
+}
+
+/**
+  \rst
+  Converts *value* to ``std::string`` using the default format for type *T*.
+
+  **Example**::
+
+    #include <fmt/format.h>
+
+    std::string answer = fmt::to_string(42);
+  \endrst
+ */
+template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+inline auto to_string(const T& value) -> std::string {
+  auto result = std::string();
+  detail::write<char>(std::back_inserter(result), value);
+  return result;
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline auto to_string(T value) -> std::string {
+  // The buffer should be large enough to store the number including the sign
+  // or "false" for bool.
+  constexpr int max_size = detail::digits10<T>() + 2;
+  char buffer[max_size > 5 ? static_cast<unsigned>(max_size) : 5];
+  char* begin = buffer;
+  return std::string(begin, detail::write<char>(begin, value));
+}
+
+template <typename Char, size_t SIZE>
+auto to_string(const basic_memory_buffer<Char, SIZE>& buf)
+    -> std::basic_string<Char> {
+  auto size = buf.size();
+  detail::assume(size < std::basic_string<Char>().max_size());
+  return std::basic_string<Char>(buf.data(), size);
+}
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+template <typename Char>
+void vformat_to(
+    buffer<Char>& buf, basic_string_view<Char> fmt,
+    basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
+    locale_ref loc) {
+  // workaround for msvc bug regarding name-lookup in module
+  // link names into function scope
+  using detail::arg_formatter;
+  using detail::buffer_appender;
+  using detail::custom_formatter;
+  using detail::default_arg_formatter;
+  using detail::get_arg;
+  using detail::locale_ref;
+  using detail::parse_format_specs;
+  using detail::specs_checker;
+  using detail::specs_handler;
+  using detail::to_unsigned;
+  using detail::type;
+  using detail::write;
+  auto out = buffer_appender<Char>(buf);
+  if (fmt.size() == 2 && equal2(fmt.data(), "{}")) {
+    auto arg = args.get(0);
+    if (!arg) error_handler().on_error("argument not found");
+    visit_format_arg(default_arg_formatter<Char>{out, args, loc}, arg);
+    return;
+  }
+
+  struct format_handler : error_handler {
+    basic_format_parse_context<Char> parse_context;
+    buffer_context<Char> context;
+
+    format_handler(buffer_appender<Char> out, basic_string_view<Char> str,
+                   basic_format_args<buffer_context<Char>> args, locale_ref loc)
+        : parse_context(str), context(out, args, loc) {}
+
+    void on_text(const Char* begin, const Char* end) {
+      auto text = basic_string_view<Char>(begin, to_unsigned(end - begin));
+      context.advance_to(write<Char>(context.out(), text));
+    }
+
+    FMT_CONSTEXPR auto on_arg_id() -> int {
+      return parse_context.next_arg_id();
+    }
+    FMT_CONSTEXPR auto on_arg_id(int id) -> int {
+      return parse_context.check_arg_id(id), id;
+    }
+    FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int {
+      int arg_id = context.arg_id(id);
+      if (arg_id < 0) on_error("argument not found");
+      return arg_id;
+    }
+
+    FMT_INLINE void on_replacement_field(int id, const Char*) {
+      auto arg = get_arg(context, id);
+      context.advance_to(visit_format_arg(
+          default_arg_formatter<Char>{context.out(), context.args(),
+                                      context.locale()},
+          arg));
+    }
+
+    auto on_format_specs(int id, const Char* begin, const Char* end)
+        -> const Char* {
+      auto arg = get_arg(context, id);
+      if (arg.type() == type::custom_type) {
+        parse_context.advance_to(parse_context.begin() +
+                                 (begin - &*parse_context.begin()));
+        visit_format_arg(custom_formatter<Char>{parse_context, context}, arg);
+        return parse_context.begin();
+      }
+      auto specs = basic_format_specs<Char>();
+      specs_checker<specs_handler<Char>> handler(
+          specs_handler<Char>(specs, parse_context, context), arg.type());
+      begin = parse_format_specs(begin, end, handler);
+      if (begin == end || *begin != '}')
+        on_error("missing '}' in format string");
+      auto f = arg_formatter<Char>{context.out(), specs, context.locale()};
+      context.advance_to(visit_format_arg(f, arg));
+      return begin;
+    }
+  };
+  detail::parse_format_string<false>(fmt, format_handler(out, fmt, args, loc));
+}
+
+#ifndef FMT_HEADER_ONLY
+extern template FMT_API auto thousands_sep_impl<char>(locale_ref)
+    -> thousands_sep_result<char>;
+extern template FMT_API auto thousands_sep_impl<wchar_t>(locale_ref)
+    -> thousands_sep_result<wchar_t>;
+extern template FMT_API auto decimal_point_impl(locale_ref) -> char;
+extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
+extern template auto format_float<double>(double value, int precision,
+                                          float_specs specs, buffer<char>& buf)
+    -> int;
+extern template auto format_float<long double>(long double value, int precision,
+                                               float_specs specs,
+                                               buffer<char>& buf) -> int;
+void snprintf_float(float, int, float_specs, buffer<char>&) = delete;
+extern template auto snprintf_float<double>(double value, int precision,
+                                            float_specs specs,
+                                            buffer<char>& buf) -> int;
+extern template auto snprintf_float<long double>(long double value,
+                                                 int precision,
+                                                 float_specs specs,
+                                                 buffer<char>& buf) -> int;
+#endif  // FMT_HEADER_ONLY
+
+FMT_END_DETAIL_NAMESPACE
+
+#if FMT_USE_USER_DEFINED_LITERALS
+inline namespace literals {
+/**
+  \rst
+  User-defined literal equivalent of :func:`fmt::arg`.
+
+  **Example**::
+
+    using namespace fmt::literals;
+    fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23);
+  \endrst
+ */
+#  if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <detail_exported::fixed_string Str>
+constexpr auto operator""_a()
+    -> detail::udl_arg<remove_cvref_t<decltype(Str.data[0])>,
+                       sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> {
+  return {};
+}
+#  else
+constexpr auto operator"" _a(const char* s, size_t) -> detail::udl_arg<char> {
+  return {s};
+}
+#  endif
+
+/**
+  \rst
+  User-defined literal equivalent of :func:`fmt::format`.
+
+  **Example**::
+
+    using namespace fmt::literals;
+    std::string message = "The answer is {}"_format(42);
+  \endrst
+ */
+constexpr auto operator"" _format(const char* s, size_t n)
+    -> detail::udl_formatter<char> {
+  return {{s, n}};
+}
+}  // namespace literals
+#endif  // FMT_USE_USER_DEFINED_LITERALS
+
+template <typename Locale, FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
+inline auto vformat(const Locale& loc, string_view fmt, format_args args)
+    -> std::string {
+  return detail::vformat(loc, fmt, args);
+}
+
+template <typename Locale, typename... T,
+          FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
+inline auto format(const Locale& loc, format_string<T...> fmt, T&&... args)
+    -> std::string {
+  return vformat(loc, string_view(fmt), fmt::make_format_args(args...));
+}
+
+template <typename... T, size_t SIZE, typename Allocator>
+FMT_DEPRECATED auto format_to(basic_memory_buffer<char, SIZE, Allocator>& buf,
+                              format_string<T...> fmt, T&&... args)
+    -> appender {
+  detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...));
+  return appender(buf);
+}
+
+template <typename OutputIt, typename Locale,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value&&
+                            detail::is_locale<Locale>::value)>
+auto vformat_to(OutputIt out, const Locale& loc, string_view fmt,
+                format_args args) -> OutputIt {
+  using detail::get_buffer;
+  auto&& buf = get_buffer<char>(out);
+  detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));
+  return detail::get_iterator(buf);
+}
+
+template <typename OutputIt, typename Locale, typename... T,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value&&
+                            detail::is_locale<Locale>::value)>
+FMT_INLINE auto format_to(OutputIt out, const Locale& loc,
+                          format_string<T...> fmt, T&&... args) -> OutputIt {
+  return vformat_to(out, loc, fmt, fmt::make_format_args(args...));
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#ifdef FMT_DEPRECATED_INCLUDE_XCHAR
+#  include "xchar.h"
+#endif
+
+#ifdef FMT_HEADER_ONLY
+#  define FMT_FUNC inline
+#  include "format-inl.h"
+#else
+#  define FMT_FUNC
+#endif
+
+#endif  // FMT_FORMAT_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/locale.h b/wpiutil/src/main/native/fmtlib/include/fmt/locale.h
new file mode 100644
index 0000000..7571b52
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/locale.h
@@ -0,0 +1,2 @@
+#include "xchar.h"
+#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/os.h b/wpiutil/src/main/native/fmtlib/include/fmt/os.h
new file mode 100644
index 0000000..f6c0f32
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/os.h
@@ -0,0 +1,515 @@
+// Formatting library for C++ - optional OS-specific functionality
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_OS_H_
+#define FMT_OS_H_
+
+#include <cerrno>
+#include <clocale>  // locale_t
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>       // strtod_l
+#include <system_error>  // std::system_error
+
+#if defined __APPLE__ || defined(__FreeBSD__)
+#  include <xlocale.h>  // for LC_NUMERIC_MASK on OS X
+#endif
+
+#include "format.h"
+
+// UWP doesn't provide _pipe.
+#if FMT_HAS_INCLUDE("winapifamily.h")
+#  include <winapifamily.h>
+#endif
+#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
+     defined(__linux__)) &&                              \
+    (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
+#  include <fcntl.h>  // for O_RDONLY
+#  define FMT_USE_FCNTL 1
+#else
+#  define FMT_USE_FCNTL 0
+#endif
+
+#ifndef FMT_POSIX
+#  if defined(_WIN32) && !defined(__MINGW32__)
+// Fix warnings about deprecated symbols.
+#    define FMT_POSIX(call) _##call
+#  else
+#    define FMT_POSIX(call) call
+#  endif
+#endif
+
+// Calls to system functions are wrapped in FMT_SYSTEM for testability.
+#ifdef FMT_SYSTEM
+#  define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
+#else
+#  define FMT_SYSTEM(call) ::call
+#  ifdef _WIN32
+// Fix warnings about deprecated symbols.
+#    define FMT_POSIX_CALL(call) ::_##call
+#  else
+#    define FMT_POSIX_CALL(call) ::call
+#  endif
+#endif
+
+// Retries the expression while it evaluates to error_result and errno
+// equals to EINTR.
+#ifndef _WIN32
+#  define FMT_RETRY_VAL(result, expression, error_result) \
+    do {                                                  \
+      (result) = (expression);                            \
+    } while ((result) == (error_result) && errno == EINTR)
+#else
+#  define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
+#endif
+
+#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
+
+FMT_BEGIN_NAMESPACE
+FMT_MODULE_EXPORT_BEGIN
+
+/**
+  \rst
+  A reference to a null-terminated string. It can be constructed from a C
+  string or ``std::string``.
+
+  You can use one of the following type aliases for common character types:
+
+  +---------------+-----------------------------+
+  | Type          | Definition                  |
+  +===============+=============================+
+  | cstring_view  | basic_cstring_view<char>    |
+  +---------------+-----------------------------+
+  | wcstring_view | basic_cstring_view<wchar_t> |
+  +---------------+-----------------------------+
+
+  This class is most useful as a parameter type to allow passing
+  different types of strings to a function, for example::
+
+    template <typename... Args>
+    std::string format(cstring_view format_str, const Args & ... args);
+
+    format("{}", 42);
+    format(std::string("{}"), 42);
+  \endrst
+ */
+template <typename Char> class basic_cstring_view {
+ private:
+  const Char* data_;
+
+ public:
+  /** Constructs a string reference object from a C string. */
+  basic_cstring_view(const Char* s) : data_(s) {}
+
+  /**
+    \rst
+    Constructs a string reference from an ``std::string`` object.
+    \endrst
+   */
+  basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
+
+  /** Returns the pointer to a C string. */
+  const Char* c_str() const { return data_; }
+};
+
+using cstring_view = basic_cstring_view<char>;
+using wcstring_view = basic_cstring_view<wchar_t>;
+
+template <typename Char> struct formatter<std::error_code, Char> {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    auto out = ctx.out();
+    out = detail::write_bytes(out, ec.category().name(),
+                              basic_format_specs<Char>());
+    out = detail::write<Char>(out, Char(':'));
+    out = detail::write<Char>(out, ec.value());
+    return out;
+  }
+};
+
+#ifdef _WIN32
+FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
+
+FMT_BEGIN_DETAIL_NAMESPACE
+// A converter from UTF-16 to UTF-8.
+// It is only provided for Windows since other systems support UTF-8 natively.
+class utf16_to_utf8 {
+ private:
+  memory_buffer buffer_;
+
+ public:
+  utf16_to_utf8() {}
+  FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
+  operator string_view() const { return string_view(&buffer_[0], size()); }
+  size_t size() const { return buffer_.size() - 1; }
+  const char* c_str() const { return &buffer_[0]; }
+  std::string str() const { return std::string(&buffer_[0], size()); }
+
+  // Performs conversion returning a system error code instead of
+  // throwing exception on conversion error. This method may still throw
+  // in case of memory allocation error.
+  FMT_API int convert(basic_string_view<wchar_t> s);
+};
+
+FMT_API void format_windows_error(buffer<char>& out, int error_code,
+                                  const char* message) FMT_NOEXCEPT;
+FMT_END_DETAIL_NAMESPACE
+
+FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
+                                         format_args args);
+
+/**
+ \rst
+ Constructs a :class:`std::system_error` object with the description
+ of the form
+
+ .. parsed-literal::
+   *<message>*: *<system-message>*
+
+ where *<message>* is the formatted message and *<system-message>* is the
+ system message corresponding to the error code.
+ *error_code* is a Windows error code as given by ``GetLastError``.
+ If *error_code* is not a valid error code such as -1, the system message
+ will look like "error -1".
+
+ **Example**::
+
+   // This throws a system_error with the description
+   //   cannot open file 'madeup': The system cannot find the file specified.
+   // or similar (system message may vary).
+   const char *filename = "madeup";
+   LPOFSTRUCT of = LPOFSTRUCT();
+   HFILE file = OpenFile(filename, &of, OF_READ);
+   if (file == HFILE_ERROR) {
+     throw fmt::windows_error(GetLastError(),
+                              "cannot open file '{}'", filename);
+   }
+ \endrst
+*/
+template <typename... Args>
+std::system_error windows_error(int error_code, string_view message,
+                                const Args&... args) {
+  return vwindows_error(error_code, message, fmt::make_format_args(args...));
+}
+
+// Reports a Windows error without throwing an exception.
+// Can be used to report errors from destructors.
+FMT_API void report_windows_error(int error_code,
+                                  const char* message) FMT_NOEXCEPT;
+#else
+inline const std::error_category& system_category() FMT_NOEXCEPT {
+  return std::system_category();
+}
+#endif  // _WIN32
+
+// std::system is not available on some platforms such as iOS (#2248).
+#ifdef __OSX__
+template <typename S, typename... Args, typename Char = char_t<S>>
+void say(const S& format_str, Args&&... args) {
+  std::system(format("say \"{}\"", format(format_str, args...)).c_str());
+}
+#endif
+
+// A buffered file.
+class buffered_file {
+ private:
+  FILE* file_;
+
+  friend class file;
+
+  explicit buffered_file(FILE* f) : file_(f) {}
+
+ public:
+  buffered_file(const buffered_file&) = delete;
+  void operator=(const buffered_file&) = delete;
+
+  // Constructs a buffered_file object which doesn't represent any file.
+  buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
+
+  // Destroys the object closing the file it represents if any.
+  FMT_API ~buffered_file() FMT_NOEXCEPT;
+
+ public:
+  buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
+    other.file_ = nullptr;
+  }
+
+  buffered_file& operator=(buffered_file&& other) {
+    close();
+    file_ = other.file_;
+    other.file_ = nullptr;
+    return *this;
+  }
+
+  // Opens a file.
+  FMT_API buffered_file(cstring_view filename, cstring_view mode);
+
+  // Closes the file.
+  FMT_API void close();
+
+  // Returns the pointer to a FILE object representing this file.
+  FILE* get() const FMT_NOEXCEPT { return file_; }
+
+  // We place parentheses around fileno to workaround a bug in some versions
+  // of MinGW that define fileno as a macro.
+  FMT_API int(fileno)() const;
+
+  void vprint(string_view format_str, format_args args) {
+    fmt::vprint(file_, format_str, args);
+  }
+
+  template <typename... Args>
+  inline void print(string_view format_str, const Args&... args) {
+    vprint(format_str, fmt::make_format_args(args...));
+  }
+};
+
+#if FMT_USE_FCNTL
+// A file. Closed file is represented by a file object with descriptor -1.
+// Methods that are not declared with FMT_NOEXCEPT may throw
+// fmt::system_error in case of failure. Note that some errors such as
+// closing the file multiple times will cause a crash on Windows rather
+// than an exception. You can get standard behavior by overriding the
+// invalid parameter handler with _set_invalid_parameter_handler.
+class file {
+ private:
+  int fd_;  // File descriptor.
+
+  // Constructs a file object with a given descriptor.
+  explicit file(int fd) : fd_(fd) {}
+
+ public:
+  // Possible values for the oflag argument to the constructor.
+  enum {
+    RDONLY = FMT_POSIX(O_RDONLY),  // Open for reading only.
+    WRONLY = FMT_POSIX(O_WRONLY),  // Open for writing only.
+    RDWR = FMT_POSIX(O_RDWR),      // Open for reading and writing.
+    CREATE = FMT_POSIX(O_CREAT),   // Create if the file doesn't exist.
+    APPEND = FMT_POSIX(O_APPEND),  // Open in append mode.
+    TRUNC = FMT_POSIX(O_TRUNC)     // Truncate the content of the file.
+  };
+
+  // Constructs a file object which doesn't represent any file.
+  file() FMT_NOEXCEPT : fd_(-1) {}
+
+  // Opens a file and constructs a file object representing this file.
+  FMT_API file(cstring_view path, int oflag);
+
+ public:
+  file(const file&) = delete;
+  void operator=(const file&) = delete;
+
+  file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
+
+  // Move assignment is not noexcept because close may throw.
+  file& operator=(file&& other) {
+    close();
+    fd_ = other.fd_;
+    other.fd_ = -1;
+    return *this;
+  }
+
+  // Destroys the object closing the file it represents if any.
+  FMT_API ~file() FMT_NOEXCEPT;
+
+  // Returns the file descriptor.
+  int descriptor() const FMT_NOEXCEPT { return fd_; }
+
+  // Closes the file.
+  FMT_API void close();
+
+  // Returns the file size. The size has signed type for consistency with
+  // stat::st_size.
+  FMT_API long long size() const;
+
+  // Attempts to read count bytes from the file into the specified buffer.
+  FMT_API size_t read(void* buffer, size_t count);
+
+  // Attempts to write count bytes from the specified buffer to the file.
+  FMT_API size_t write(const void* buffer, size_t count);
+
+  // Duplicates a file descriptor with the dup function and returns
+  // the duplicate as a file object.
+  FMT_API static file dup(int fd);
+
+  // Makes fd be the copy of this file descriptor, closing fd first if
+  // necessary.
+  FMT_API void dup2(int fd);
+
+  // Makes fd be the copy of this file descriptor, closing fd first if
+  // necessary.
+  FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
+
+  // Creates a pipe setting up read_end and write_end file objects for reading
+  // and writing respectively.
+  FMT_API static void pipe(file& read_end, file& write_end);
+
+  // Creates a buffered_file object associated with this file and detaches
+  // this file object from the file.
+  FMT_API buffered_file fdopen(const char* mode);
+};
+
+// Returns the memory page size.
+long getpagesize();
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+struct buffer_size {
+  buffer_size() = default;
+  size_t value = 0;
+  buffer_size operator=(size_t val) const {
+    auto bs = buffer_size();
+    bs.value = val;
+    return bs;
+  }
+};
+
+struct ostream_params {
+  int oflag = file::WRONLY | file::CREATE | file::TRUNC;
+  size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
+
+  ostream_params() {}
+
+  template <typename... T>
+  ostream_params(T... params, int new_oflag) : ostream_params(params...) {
+    oflag = new_oflag;
+  }
+
+  template <typename... T>
+  ostream_params(T... params, detail::buffer_size bs)
+      : ostream_params(params...) {
+    this->buffer_size = bs.value;
+  }
+};
+
+FMT_END_DETAIL_NAMESPACE
+
+constexpr detail::buffer_size buffer_size;
+
+/** A fast output stream which is not thread-safe. */
+class FMT_API ostream final : private detail::buffer<char> {
+ private:
+  file file_;
+
+  void flush() {
+    if (size() == 0) return;
+    file_.write(data(), size());
+    clear();
+  }
+
+  void grow(size_t) override;
+
+  ostream(cstring_view path, const detail::ostream_params& params)
+      : file_(path, params.oflag) {
+    set(new char[params.buffer_size], params.buffer_size);
+  }
+
+ public:
+  ostream(ostream&& other)
+      : detail::buffer<char>(other.data(), other.size(), other.capacity()),
+        file_(std::move(other.file_)) {
+    other.clear();
+    other.set(nullptr, 0);
+  }
+  ~ostream() {
+    flush();
+    delete[] data();
+  }
+
+  template <typename... T>
+  friend ostream output_file(cstring_view path, T... params);
+
+  void close() {
+    flush();
+    file_.close();
+  }
+
+  /**
+    Formats ``args`` according to specifications in ``fmt`` and writes the
+    output to the file.
+   */
+  template <typename... T> void print(format_string<T...> fmt, T&&... args) {
+    vformat_to(detail::buffer_appender<char>(*this), fmt,
+               fmt::make_format_args(args...));
+  }
+};
+
+/**
+  \rst
+  Opens a file for writing. Supported parameters passed in *params*:
+
+  * ``<integer>``: Flags passed to `open
+    <https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
+    (``file::WRONLY | file::CREATE`` by default)
+  * ``buffer_size=<integer>``: Output buffer size
+
+  **Example**::
+
+    auto out = fmt::output_file("guide.txt");
+    out.print("Don't {}", "Panic");
+  \endrst
+ */
+template <typename... T>
+inline ostream output_file(cstring_view path, T... params) {
+  return {path, detail::ostream_params(params...)};
+}
+#endif  // FMT_USE_FCNTL
+
+#ifdef FMT_LOCALE
+// A "C" numeric locale.
+class locale {
+ private:
+#  ifdef _WIN32
+  using locale_t = _locale_t;
+
+  static void freelocale(locale_t loc) { _free_locale(loc); }
+
+  static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
+    return _strtod_l(nptr, endptr, loc);
+  }
+#  endif
+
+  locale_t locale_;
+
+ public:
+  using type = locale_t;
+  locale(const locale&) = delete;
+  void operator=(const locale&) = delete;
+
+  locale() {
+#  ifndef _WIN32
+    locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
+#  else
+    locale_ = _create_locale(LC_NUMERIC, "C");
+#  endif
+    if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
+  }
+  ~locale() { freelocale(locale_); }
+
+  type get() const { return locale_; }
+
+  // Converts string to floating-point number and advances str past the end
+  // of the parsed input.
+  double strtod(const char*& str) const {
+    char* end = nullptr;
+    double result = strtod_l(str, &end, locale_);
+    str = end;
+    return result;
+  }
+};
+using Locale FMT_DEPRECATED_ALIAS = locale;
+#endif  // FMT_LOCALE
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_OS_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/ostream.h b/wpiutil/src/main/native/fmtlib/include/fmt/ostream.h
new file mode 100644
index 0000000..d66248a
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/ostream.h
@@ -0,0 +1,181 @@
+// Formatting library for C++ - std::ostream support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_OSTREAM_H_
+#define FMT_OSTREAM_H_
+
+#include <ostream>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+
+template <typename Char> class basic_printf_parse_context;
+template <typename OutputIt, typename Char> class basic_printf_context;
+
+namespace detail {
+
+template <class Char> class formatbuf : public std::basic_streambuf<Char> {
+ private:
+  using int_type = typename std::basic_streambuf<Char>::int_type;
+  using traits_type = typename std::basic_streambuf<Char>::traits_type;
+
+  buffer<Char>& buffer_;
+
+ public:
+  formatbuf(buffer<Char>& buf) : buffer_(buf) {}
+
+ protected:
+  // The put-area is actually always empty. This makes the implementation
+  // simpler and has the advantage that the streambuf and the buffer are always
+  // in sync and sputc never writes into uninitialized memory. The obvious
+  // disadvantage is that each call to sputc always results in a (virtual) call
+  // to overflow. There is no disadvantage here for sputn since this always
+  // results in a call to xsputn.
+
+  int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
+    if (!traits_type::eq_int_type(ch, traits_type::eof()))
+      buffer_.push_back(static_cast<Char>(ch));
+    return ch;
+  }
+
+  std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
+    buffer_.append(s, s + count);
+    return count;
+  }
+};
+
+struct converter {
+  template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
+};
+
+template <typename Char> struct test_stream : std::basic_ostream<Char> {
+ private:
+  void_t<> operator<<(converter);
+};
+
+// Hide insertion operators for built-in types.
+template <typename Char, typename Traits>
+void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
+template <typename Char, typename Traits>
+void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
+template <typename Traits>
+void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
+template <typename Traits>
+void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
+template <typename Traits>
+void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
+
+// Checks if T has a user-defined operator<< (e.g. not a member of
+// std::ostream).
+template <typename T, typename Char> class is_streamable {
+ private:
+  template <typename U>
+  static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
+                                              << std::declval<U>()),
+                                     void_t<>>::value>
+  test(int);
+
+  template <typename> static std::false_type test(...);
+
+  using result = decltype(test<T>(0));
+
+ public:
+  is_streamable() = default;
+
+  static const bool value = result::value;
+};
+
+// Write the content of buf to os.
+template <typename Char>
+void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
+  const Char* buf_data = buf.data();
+  using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
+  unsigned_streamsize size = buf.size();
+  unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
+  do {
+    unsigned_streamsize n = size <= max_size ? size : max_size;
+    os.write(buf_data, static_cast<std::streamsize>(n));
+    buf_data += n;
+    size -= n;
+  } while (size != 0);
+}
+
+template <typename Char, typename T>
+void format_value(buffer<Char>& buf, const T& value,
+                  locale_ref loc = locale_ref()) {
+  formatbuf<Char> format_buf(buf);
+  std::basic_ostream<Char> output(&format_buf);
+#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
+  if (loc) output.imbue(loc.get<std::locale>());
+#endif
+  output << value;
+  output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+  buf.try_resize(buf.size());
+}
+
+// Formats an object of type T that has an overloaded ostream operator<<.
+template <typename T, typename Char>
+struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
+    : private formatter<basic_string_view<Char>, Char> {
+  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
+      -> decltype(ctx.begin()) {
+    return formatter<basic_string_view<Char>, Char>::parse(ctx);
+  }
+  template <typename ParseCtx,
+            FMT_ENABLE_IF(std::is_same<
+                          ParseCtx, basic_printf_parse_context<Char>>::value)>
+  auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename OutputIt>
+  auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
+      -> OutputIt {
+    basic_memory_buffer<Char> buffer;
+    format_value(buffer, value, ctx.locale());
+    basic_string_view<Char> str(buffer.data(), buffer.size());
+    return formatter<basic_string_view<Char>, Char>::format(str, ctx);
+  }
+  template <typename OutputIt>
+  auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
+      -> OutputIt {
+    basic_memory_buffer<Char> buffer;
+    format_value(buffer, value, ctx.locale());
+    return std::copy(buffer.begin(), buffer.end(), ctx.out());
+  }
+};
+}  // namespace detail
+
+FMT_MODULE_EXPORT
+template <typename Char>
+void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
+            basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buffer;
+  detail::vformat_to(buffer, format_str, args);
+  detail::write_buffer(os, buffer);
+}
+
+/**
+  \rst
+  Prints formatted data to the stream *os*.
+
+  **Example**::
+
+    fmt::print(cerr, "Don't {}!", "panic");
+  \endrst
+ */
+FMT_MODULE_EXPORT
+template <typename S, typename... Args,
+          typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
+void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
+  vprint(os, to_string_view(format_str),
+         fmt::make_args_checked<Args...>(format_str, args...));
+}
+FMT_END_NAMESPACE
+
+#endif  // FMT_OSTREAM_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/printf.h b/wpiutil/src/main/native/fmtlib/include/fmt/printf.h
new file mode 100644
index 0000000..3a3cd15
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/printf.h
@@ -0,0 +1,652 @@
+// Formatting library for C++ - legacy printf implementation
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_PRINTF_H_
+#define FMT_PRINTF_H_
+
+#include <algorithm>  // std::max
+#include <limits>     // std::numeric_limits
+#include <ostream>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename T> struct printf_formatter { printf_formatter() = delete; };
+
+template <typename Char>
+class basic_printf_parse_context : public basic_format_parse_context<Char> {
+  using basic_format_parse_context<Char>::basic_format_parse_context;
+};
+
+template <typename OutputIt, typename Char> class basic_printf_context {
+ private:
+  OutputIt out_;
+  basic_format_args<basic_printf_context> args_;
+
+ public:
+  using char_type = Char;
+  using format_arg = basic_format_arg<basic_printf_context>;
+  using parse_context_type = basic_printf_parse_context<Char>;
+  template <typename T> using formatter_type = printf_formatter<T>;
+
+  /**
+    \rst
+    Constructs a ``printf_context`` object. References to the arguments are
+    stored in the context object so make sure they have appropriate lifetimes.
+    \endrst
+   */
+  basic_printf_context(OutputIt out,
+                       basic_format_args<basic_printf_context> args)
+      : out_(out), args_(args) {}
+
+  OutputIt out() { return out_; }
+  void advance_to(OutputIt it) { out_ = it; }
+
+  detail::locale_ref locale() { return {}; }
+
+  format_arg arg(int id) const { return args_.get(id); }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    detail::error_handler().on_error(message);
+  }
+};
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+// Checks if a value fits in int - used to avoid warnings about comparing
+// signed and unsigned integers.
+template <bool IsSigned> struct int_checker {
+  template <typename T> static bool fits_in_int(T value) {
+    unsigned max = max_value<int>();
+    return value <= max;
+  }
+  static bool fits_in_int(bool) { return true; }
+};
+
+template <> struct int_checker<true> {
+  template <typename T> static bool fits_in_int(T value) {
+    return value >= (std::numeric_limits<int>::min)() &&
+           value <= max_value<int>();
+  }
+  static bool fits_in_int(int) { return true; }
+};
+
+class printf_precision_handler {
+ public:
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  int operator()(T value) {
+    if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
+      FMT_THROW(format_error("number is too big"));
+    return (std::max)(static_cast<int>(value), 0);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  int operator()(T) {
+    FMT_THROW(format_error("precision is not integer"));
+    return 0;
+  }
+};
+
+// An argument visitor that returns true iff arg is a zero integer.
+class is_zero_int {
+ public:
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  bool operator()(T value) {
+    return value == 0;
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  bool operator()(T) {
+    return false;
+  }
+};
+
+template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
+
+template <> struct make_unsigned_or_bool<bool> { using type = bool; };
+
+template <typename T, typename Context> class arg_converter {
+ private:
+  using char_type = typename Context::char_type;
+
+  basic_format_arg<Context>& arg_;
+  char_type type_;
+
+ public:
+  arg_converter(basic_format_arg<Context>& arg, char_type type)
+      : arg_(arg), type_(type) {}
+
+  void operator()(bool value) {
+    if (type_ != 's') operator()<bool>(value);
+  }
+
+  template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
+  void operator()(U value) {
+    bool is_signed = type_ == 'd' || type_ == 'i';
+    using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
+    if (const_check(sizeof(target_type) <= sizeof(int))) {
+      // Extra casts are used to silence warnings.
+      if (is_signed) {
+        arg_ = detail::make_arg<Context>(
+            static_cast<int>(static_cast<target_type>(value)));
+      } else {
+        using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
+        arg_ = detail::make_arg<Context>(
+            static_cast<unsigned>(static_cast<unsigned_type>(value)));
+      }
+    } else {
+      if (is_signed) {
+        // glibc's printf doesn't sign extend arguments of smaller types:
+        //   std::printf("%lld", -42);  // prints "4294967254"
+        // but we don't have to do the same because it's a UB.
+        arg_ = detail::make_arg<Context>(static_cast<long long>(value));
+      } else {
+        arg_ = detail::make_arg<Context>(
+            static_cast<typename make_unsigned_or_bool<U>::type>(value));
+      }
+    }
+  }
+
+  template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
+  void operator()(U) {}  // No conversion needed for non-integral types.
+};
+
+// Converts an integer argument to T for printf, if T is an integral type.
+// If T is void, the argument is converted to corresponding signed or unsigned
+// type depending on the type specifier: 'd' and 'i' - signed, other -
+// unsigned).
+template <typename T, typename Context, typename Char>
+void convert_arg(basic_format_arg<Context>& arg, Char type) {
+  visit_format_arg(arg_converter<T, Context>(arg, type), arg);
+}
+
+// Converts an integer argument to char for printf.
+template <typename Context> class char_converter {
+ private:
+  basic_format_arg<Context>& arg_;
+
+ public:
+  explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
+
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  void operator()(T value) {
+    arg_ = detail::make_arg<Context>(
+        static_cast<typename Context::char_type>(value));
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  void operator()(T) {}  // No conversion needed for non-integral types.
+};
+
+// An argument visitor that return a pointer to a C string if argument is a
+// string or null otherwise.
+template <typename Char> struct get_cstring {
+  template <typename T> const Char* operator()(T) { return nullptr; }
+  const Char* operator()(const Char* s) { return s; }
+};
+
+// Checks if an argument is a valid printf width specifier and sets
+// left alignment if it is negative.
+template <typename Char> class printf_width_handler {
+ private:
+  using format_specs = basic_format_specs<Char>;
+
+  format_specs& specs_;
+
+ public:
+  explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
+
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  unsigned operator()(T value) {
+    auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
+    if (detail::is_negative(value)) {
+      specs_.align = align::left;
+      width = 0 - width;
+    }
+    unsigned int_max = max_value<int>();
+    if (width > int_max) FMT_THROW(format_error("number is too big"));
+    return static_cast<unsigned>(width);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  unsigned operator()(T) {
+    FMT_THROW(format_error("width is not integer"));
+    return 0;
+  }
+};
+
+// The ``printf`` argument formatter.
+template <typename OutputIt, typename Char>
+class printf_arg_formatter : public arg_formatter<Char> {
+ private:
+  using base = arg_formatter<Char>;
+  using context_type = basic_printf_context<OutputIt, Char>;
+  using format_specs = basic_format_specs<Char>;
+
+  context_type& context_;
+
+  OutputIt write_null_pointer(bool is_string = false) {
+    auto s = this->specs;
+    s.type = 0;
+    return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
+  }
+
+ public:
+  printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
+      : base{iter, s, locale_ref()}, context_(ctx) {}
+
+  OutputIt operator()(monostate value) { return base::operator()(value); }
+
+  template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
+  OutputIt operator()(T value) {
+    // MSVC2013 fails to compile separate overloads for bool and Char so use
+    // std::is_same instead.
+    if (std::is_same<T, Char>::value) {
+      format_specs fmt_specs = this->specs;
+      if (fmt_specs.type && fmt_specs.type != 'c')
+        return (*this)(static_cast<int>(value));
+      fmt_specs.sign = sign::none;
+      fmt_specs.alt = false;
+      fmt_specs.fill[0] = ' ';  // Ignore '0' flag for char types.
+      // align::numeric needs to be overwritten here since the '0' flag is
+      // ignored for non-numeric types
+      if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
+        fmt_specs.align = align::right;
+      return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
+    }
+    return base::operator()(value);
+  }
+
+  template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+  OutputIt operator()(T value) {
+    return base::operator()(value);
+  }
+
+  /** Formats a null-terminated C string. */
+  OutputIt operator()(const char* value) {
+    if (value) return base::operator()(value);
+    return write_null_pointer(this->specs.type != 'p');
+  }
+
+  /** Formats a null-terminated wide C string. */
+  OutputIt operator()(const wchar_t* value) {
+    if (value) return base::operator()(value);
+    return write_null_pointer(this->specs.type != 'p');
+  }
+
+  OutputIt operator()(basic_string_view<Char> value) {
+    return base::operator()(value);
+  }
+
+  /** Formats a pointer. */
+  OutputIt operator()(const void* value) {
+    return value ? base::operator()(value) : write_null_pointer();
+  }
+
+  /** Formats an argument of a custom (user-defined) type. */
+  OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
+    auto parse_ctx =
+        basic_printf_parse_context<Char>(basic_string_view<Char>());
+    handle.format(parse_ctx, context_);
+    return this->out;
+  }
+};
+
+template <typename Char>
+void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
+                 const Char* end) {
+  for (; it != end; ++it) {
+    switch (*it) {
+    case '-':
+      specs.align = align::left;
+      break;
+    case '+':
+      specs.sign = sign::plus;
+      break;
+    case '0':
+      specs.fill[0] = '0';
+      break;
+    case ' ':
+      if (specs.sign != sign::plus) {
+        specs.sign = sign::space;
+      }
+      break;
+    case '#':
+      specs.alt = true;
+      break;
+    default:
+      return;
+    }
+  }
+}
+
+template <typename Char, typename GetArg>
+int parse_header(const Char*& it, const Char* end,
+                 basic_format_specs<Char>& specs, GetArg get_arg) {
+  int arg_index = -1;
+  Char c = *it;
+  if (c >= '0' && c <= '9') {
+    // Parse an argument index (if followed by '$') or a width possibly
+    // preceded with '0' flag(s).
+    int value = parse_nonnegative_int(it, end, -1);
+    if (it != end && *it == '$') {  // value is an argument index
+      ++it;
+      arg_index = value != -1 ? value : max_value<int>();
+    } else {
+      if (c == '0') specs.fill[0] = '0';
+      if (value != 0) {
+        // Nonzero value means that we parsed width and don't need to
+        // parse it or flags again, so return now.
+        if (value == -1) FMT_THROW(format_error("number is too big"));
+        specs.width = value;
+        return arg_index;
+      }
+    }
+  }
+  parse_flags(specs, it, end);
+  // Parse width.
+  if (it != end) {
+    if (*it >= '0' && *it <= '9') {
+      specs.width = parse_nonnegative_int(it, end, -1);
+      if (specs.width == -1) FMT_THROW(format_error("number is too big"));
+    } else if (*it == '*') {
+      ++it;
+      specs.width = static_cast<int>(visit_format_arg(
+          detail::printf_width_handler<Char>(specs), get_arg(-1)));
+    }
+  }
+  return arg_index;
+}
+
+template <typename Char, typename Context>
+void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
+             basic_format_args<Context> args) {
+  using OutputIt = buffer_appender<Char>;
+  auto out = OutputIt(buf);
+  auto context = basic_printf_context<OutputIt, Char>(out, args);
+  auto parse_ctx = basic_printf_parse_context<Char>(format);
+
+  // Returns the argument with specified index or, if arg_index is -1, the next
+  // argument.
+  auto get_arg = [&](int arg_index) {
+    if (arg_index < 0)
+      arg_index = parse_ctx.next_arg_id();
+    else
+      parse_ctx.check_arg_id(--arg_index);
+    return detail::get_arg(context, arg_index);
+  };
+
+  const Char* start = parse_ctx.begin();
+  const Char* end = parse_ctx.end();
+  auto it = start;
+  while (it != end) {
+    if (!detail::find<false, Char>(it, end, '%', it)) {
+      it = end;  // detail::find leaves it == nullptr if it doesn't find '%'
+      break;
+    }
+    Char c = *it++;
+    if (it != end && *it == c) {
+      out = detail::write(
+          out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
+      start = ++it;
+      continue;
+    }
+    out = detail::write(out, basic_string_view<Char>(
+                                 start, detail::to_unsigned(it - 1 - start)));
+
+    basic_format_specs<Char> specs;
+    specs.align = align::right;
+
+    // Parse argument index, flags and width.
+    int arg_index = parse_header(it, end, specs, get_arg);
+    if (arg_index == 0) parse_ctx.on_error("argument not found");
+
+    // Parse precision.
+    if (it != end && *it == '.') {
+      ++it;
+      c = it != end ? *it : 0;
+      if ('0' <= c && c <= '9') {
+        specs.precision = parse_nonnegative_int(it, end, 0);
+      } else if (c == '*') {
+        ++it;
+        specs.precision = static_cast<int>(
+            visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
+      } else {
+        specs.precision = 0;
+      }
+    }
+
+    auto arg = get_arg(arg_index);
+    // For d, i, o, u, x, and X conversion specifiers, if a precision is
+    // specified, the '0' flag is ignored
+    if (specs.precision >= 0 && arg.is_integral())
+      specs.fill[0] =
+          ' ';  // Ignore '0' flag for non-numeric types or if '-' present.
+    if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
+      auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
+      auto str_end = str + specs.precision;
+      auto nul = std::find(str, str_end, Char());
+      arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
+          basic_string_view<Char>(
+              str, detail::to_unsigned(nul != str_end ? nul - str
+                                                      : specs.precision)));
+    }
+    if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
+      specs.alt = false;
+    if (specs.fill[0] == '0') {
+      if (arg.is_arithmetic() && specs.align != align::left)
+        specs.align = align::numeric;
+      else
+        specs.fill[0] = ' ';  // Ignore '0' flag for non-numeric types or if '-'
+                              // flag is also present.
+    }
+
+    // Parse length and convert the argument to the required type.
+    c = it != end ? *it++ : 0;
+    Char t = it != end ? *it : 0;
+    using detail::convert_arg;
+    switch (c) {
+    case 'h':
+      if (t == 'h') {
+        ++it;
+        t = it != end ? *it : 0;
+        convert_arg<signed char>(arg, t);
+      } else {
+        convert_arg<short>(arg, t);
+      }
+      break;
+    case 'l':
+      if (t == 'l') {
+        ++it;
+        t = it != end ? *it : 0;
+        convert_arg<long long>(arg, t);
+      } else {
+        convert_arg<long>(arg, t);
+      }
+      break;
+    case 'j':
+      convert_arg<intmax_t>(arg, t);
+      break;
+    case 'z':
+      convert_arg<size_t>(arg, t);
+      break;
+    case 't':
+      convert_arg<std::ptrdiff_t>(arg, t);
+      break;
+    case 'L':
+      // printf produces garbage when 'L' is omitted for long double, no
+      // need to do the same.
+      break;
+    default:
+      --it;
+      convert_arg<void>(arg, c);
+    }
+
+    // Parse type.
+    if (it == end) FMT_THROW(format_error("invalid format string"));
+    specs.type = static_cast<char>(*it++);
+    if (arg.is_integral()) {
+      // Normalize type.
+      switch (specs.type) {
+      case 'i':
+      case 'u':
+        specs.type = 'd';
+        break;
+      case 'c':
+        visit_format_arg(
+            detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
+            arg);
+        break;
+      }
+    }
+
+    start = it;
+
+    // Format argument.
+    out = visit_format_arg(
+        detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
+  }
+  detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
+}
+FMT_END_DETAIL_NAMESPACE
+
+template <typename Char>
+using basic_printf_context_t =
+    basic_printf_context<detail::buffer_appender<Char>, Char>;
+
+using printf_context = basic_printf_context_t<char>;
+using wprintf_context = basic_printf_context_t<wchar_t>;
+
+using printf_args = basic_format_args<printf_context>;
+using wprintf_args = basic_format_args<wprintf_context>;
+
+/**
+  \rst
+  Constructs an `~fmt::format_arg_store` object that contains references to
+  arguments and can be implicitly converted to `~fmt::printf_args`.
+  \endrst
+ */
+template <typename... T>
+inline auto make_printf_args(const T&... args)
+    -> format_arg_store<printf_context, T...> {
+  return {args...};
+}
+
+/**
+  \rst
+  Constructs an `~fmt::format_arg_store` object that contains references to
+  arguments and can be implicitly converted to `~fmt::wprintf_args`.
+  \endrst
+ */
+template <typename... T>
+inline auto make_wprintf_args(const T&... args)
+    -> format_arg_store<wprintf_context, T...> {
+  return {args...};
+}
+
+template <typename S, typename Char = char_t<S>>
+inline auto vsprintf(
+    const S& fmt,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
+    -> std::basic_string<Char> {
+  basic_memory_buffer<Char> buffer;
+  vprintf(buffer, to_string_view(fmt), args);
+  return to_string(buffer);
+}
+
+/**
+  \rst
+  Formats arguments and returns the result as a string.
+
+  **Example**::
+
+    std::string message = fmt::sprintf("The answer is %d", 42);
+  \endrst
+*/
+template <typename S, typename... T,
+          typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
+inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
+  using context = basic_printf_context_t<Char>;
+  return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
+}
+
+template <typename S, typename Char = char_t<S>>
+inline auto vfprintf(
+    std::FILE* f, const S& fmt,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
+    -> int {
+  basic_memory_buffer<Char> buffer;
+  vprintf(buffer, to_string_view(fmt), args);
+  size_t size = buffer.size();
+  return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
+             ? -1
+             : static_cast<int>(size);
+}
+
+/**
+  \rst
+  Prints formatted data to the file *f*.
+
+  **Example**::
+
+    fmt::fprintf(stderr, "Don't %s!", "panic");
+  \endrst
+ */
+template <typename S, typename... T, typename Char = char_t<S>>
+inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
+  using context = basic_printf_context_t<Char>;
+  return vfprintf(f, to_string_view(fmt),
+                  fmt::make_format_args<context>(args...));
+}
+
+template <typename S, typename Char = char_t<S>>
+inline auto vprintf(
+    const S& fmt,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
+    -> int {
+  return vfprintf(stdout, to_string_view(fmt), args);
+}
+
+/**
+  \rst
+  Prints formatted data to ``stdout``.
+
+  **Example**::
+
+    fmt::printf("Elapsed time: %.2f seconds", 1.23);
+  \endrst
+ */
+template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
+inline auto printf(const S& fmt, const T&... args) -> int {
+  return vprintf(
+      to_string_view(fmt),
+      fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
+}
+
+template <typename S, typename Char = char_t<S>>
+FMT_DEPRECATED auto vfprintf(
+    std::basic_ostream<Char>& os, const S& fmt,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
+    -> int {
+  basic_memory_buffer<Char> buffer;
+  vprintf(buffer, to_string_view(fmt), args);
+  os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
+  return static_cast<int>(buffer.size());
+}
+template <typename S, typename... T, typename Char = char_t<S>>
+FMT_DEPRECATED auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
+                            const T&... args) -> int {
+  return vfprintf(os, to_string_view(fmt),
+                  fmt::make_format_args<basic_printf_context_t<Char>>(args...));
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_PRINTF_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/ranges.h b/wpiutil/src/main/native/fmtlib/include/fmt/ranges.h
new file mode 100644
index 0000000..f0390df
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/ranges.h
@@ -0,0 +1,468 @@
+// Formatting library for C++ - experimental range support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+//
+// Copyright (c) 2018 - present, Remotion (Igor Schulz)
+// All Rights Reserved
+// {fmt} support for ranges, containers and types tuple interface.
+
+#ifndef FMT_RANGES_H_
+#define FMT_RANGES_H_
+
+#include <initializer_list>
+#include <type_traits>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+
+template <typename Char, typename Enable = void> struct formatting_range {
+#ifdef FMT_DEPRECATED_BRACED_RANGES
+  Char prefix = '{';
+  Char postfix = '}';
+#else
+  Char prefix = '[';
+  Char postfix = ']';
+#endif
+
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+};
+
+template <typename Char, typename Enable = void> struct formatting_tuple {
+  Char prefix = '(';
+  Char postfix = ')';
+
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+};
+
+namespace detail {
+
+template <typename RangeT, typename OutputIterator>
+OutputIterator copy(const RangeT& range, OutputIterator out) {
+  for (auto it = range.begin(), end = range.end(); it != end; ++it)
+    *out++ = *it;
+  return out;
+}
+
+template <typename OutputIterator>
+OutputIterator copy(const char* str, OutputIterator out) {
+  while (*str) *out++ = *str++;
+  return out;
+}
+
+template <typename OutputIterator>
+OutputIterator copy(char ch, OutputIterator out) {
+  *out++ = ch;
+  return out;
+}
+
+template <typename OutputIterator>
+OutputIterator copy(wchar_t ch, OutputIterator out) {
+  *out++ = ch;
+  return out;
+}
+
+/// Return true value if T has std::string interface, like std::string_view.
+template <typename T> class is_std_string_like {
+  template <typename U>
+  static auto check(U* p)
+      -> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
+  template <typename> static void check(...);
+
+ public:
+  static FMT_CONSTEXPR_DECL const bool value =
+      is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
+};
+
+template <typename Char>
+struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
+
+template <typename... Ts> struct conditional_helper {};
+
+template <typename T, typename _ = void> struct is_range_ : std::false_type {};
+
+#if !FMT_MSC_VER || FMT_MSC_VER > 1800
+
+#  define FMT_DECLTYPE_RETURN(val)  \
+    ->decltype(val) { return val; } \
+    static_assert(                  \
+        true, "")  // This makes it so that a semicolon is required after the
+                   // macro, which helps clang-format handle the formatting.
+
+// C array overload
+template <typename T, std::size_t N>
+auto range_begin(const T (&arr)[N]) -> const T* {
+  return arr;
+}
+template <typename T, std::size_t N>
+auto range_end(const T (&arr)[N]) -> const T* {
+  return arr + N;
+}
+
+template <typename T, typename Enable = void>
+struct has_member_fn_begin_end_t : std::false_type {};
+
+template <typename T>
+struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
+                                           decltype(std::declval<T>().end())>>
+    : std::true_type {};
+
+// Member function overload
+template <typename T>
+auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
+template <typename T>
+auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
+
+// ADL overload. Only participates in overload resolution if member functions
+// are not found.
+template <typename T>
+auto range_begin(T&& rng)
+    -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
+                   decltype(begin(static_cast<T&&>(rng)))> {
+  return begin(static_cast<T&&>(rng));
+}
+template <typename T>
+auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
+                                       decltype(end(static_cast<T&&>(rng)))> {
+  return end(static_cast<T&&>(rng));
+}
+
+template <typename T, typename Enable = void>
+struct has_const_begin_end : std::false_type {};
+template <typename T, typename Enable = void>
+struct has_mutable_begin_end : std::false_type {};
+
+template <typename T>
+struct has_const_begin_end<
+    T, void_t<decltype(detail::range_begin(
+                  std::declval<const remove_cvref_t<T>&>())),
+              decltype(detail::range_begin(
+                  std::declval<const remove_cvref_t<T>&>()))>>
+    : std::true_type {};
+
+template <typename T>
+struct has_mutable_begin_end<
+    T, void_t<decltype(detail::range_begin(std::declval<T>())),
+              decltype(detail::range_begin(std::declval<T>())),
+              enable_if_t<std::is_copy_constructible<T>::value>>>
+    : std::true_type {};
+
+template <typename T>
+struct is_range_<T, void>
+    : std::integral_constant<bool, (has_const_begin_end<T>::value ||
+                                    has_mutable_begin_end<T>::value)> {};
+
+template <typename T, typename Enable = void> struct range_to_view;
+template <typename T>
+struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
+  struct view_t {
+    const T* m_range_ptr;
+
+    auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
+    auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
+  };
+  static auto view(const T& range) -> view_t { return {&range}; }
+};
+
+template <typename T>
+struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
+                                    has_mutable_begin_end<T>::value>> {
+  struct view_t {
+    T m_range_copy;
+
+    auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
+    auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
+  };
+  static auto view(const T& range) -> view_t { return {range}; }
+};
+#  undef FMT_DECLTYPE_RETURN
+#endif
+
+/// tuple_size and tuple_element check.
+template <typename T> class is_tuple_like_ {
+  template <typename U>
+  static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
+  template <typename> static void check(...);
+
+ public:
+  static FMT_CONSTEXPR_DECL const bool value =
+      !std::is_void<decltype(check<T>(nullptr))>::value;
+};
+
+// Check for integer_sequence
+#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
+template <typename T, T... N>
+using integer_sequence = std::integer_sequence<T, N...>;
+template <size_t... N> using index_sequence = std::index_sequence<N...>;
+template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
+#else
+template <typename T, T... N> struct integer_sequence {
+  using value_type = T;
+
+  static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
+};
+
+template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
+
+template <typename T, size_t N, T... Ns>
+struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
+template <typename T, T... Ns>
+struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
+
+template <size_t N>
+using make_index_sequence = make_integer_sequence<size_t, N>;
+#endif
+
+template <class Tuple, class F, size_t... Is>
+void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
+  using std::get;
+  // using free function get<I>(T) now.
+  const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
+  (void)_;  // blocks warnings
+}
+
+template <class T>
+FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
+    T const&) {
+  return {};
+}
+
+template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
+  const auto indexes = get_indexes(tup);
+  for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
+}
+
+template <typename Range>
+using value_type =
+    remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
+
+template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
+  *out++ = ',';
+  *out++ = ' ';
+  return out;
+}
+
+template <
+    typename Char, typename OutputIt, typename Arg,
+    FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
+OutputIt write_range_entry(OutputIt out, const Arg& v) {
+  *out++ = '"';
+  out = write<Char>(out, v);
+  *out++ = '"';
+  return out;
+}
+
+template <typename Char, typename OutputIt, typename Arg,
+          FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
+OutputIt write_range_entry(OutputIt out, const Arg v) {
+  *out++ = '\'';
+  *out++ = v;
+  *out++ = '\'';
+  return out;
+}
+
+template <
+    typename Char, typename OutputIt, typename Arg,
+    FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
+                  !std::is_same<Arg, Char>::value)>
+OutputIt write_range_entry(OutputIt out, const Arg& v) {
+  return write<Char>(out, v);
+}
+
+}  // namespace detail
+
+template <typename T> struct is_tuple_like {
+  static FMT_CONSTEXPR_DECL const bool value =
+      detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
+};
+
+template <typename TupleT, typename Char>
+struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
+ private:
+  // C++11 generic lambda for format()
+  template <typename FormatContext> struct format_each {
+    template <typename T> void operator()(const T& v) {
+      if (i > 0) out = detail::write_delimiter(out);
+      out = detail::write_range_entry<Char>(out, v);
+      ++i;
+    }
+    formatting_tuple<Char>& formatting;
+    size_t& i;
+    typename std::add_lvalue_reference<
+        decltype(std::declval<FormatContext>().out())>::type out;
+  };
+
+ public:
+  formatting_tuple<Char> formatting;
+
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return formatting.parse(ctx);
+  }
+
+  template <typename FormatContext = format_context>
+  auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
+    auto out = ctx.out();
+    size_t i = 0;
+
+    detail::copy(formatting.prefix, out);
+    detail::for_each(values, format_each<FormatContext>{formatting, i, out});
+    detail::copy(formatting.postfix, out);
+
+    return ctx.out();
+  }
+};
+
+template <typename T, typename Char> struct is_range {
+  static FMT_CONSTEXPR_DECL const bool value =
+      detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
+      !std::is_convertible<T, std::basic_string<Char>>::value &&
+      !std::is_constructible<detail::std_string_view<Char>, T>::value;
+};
+
+template <typename T, typename Char>
+struct formatter<
+    T, Char,
+    enable_if_t<
+        fmt::is_range<T, Char>::value
+// Workaround a bug in MSVC 2017 and earlier.
+#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
+        && (has_formatter<detail::value_type<T>, format_context>::value ||
+            detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
+#endif
+        >> {
+  formatting_range<Char> formatting;
+
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return formatting.parse(ctx);
+  }
+
+  template <typename FormatContext>
+  typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
+    auto out = detail::copy(formatting.prefix, ctx.out());
+    size_t i = 0;
+    auto view = detail::range_to_view<T>::view(values);
+    auto it = view.begin();
+    auto end = view.end();
+    for (; it != end; ++it) {
+      if (i > 0) out = detail::write_delimiter(out);
+      out = detail::write_range_entry<Char>(out, *it);
+      ++i;
+    }
+    return detail::copy(formatting.postfix, out);
+  }
+};
+
+template <typename Char, typename... T> struct tuple_join_view : detail::view {
+  const std::tuple<T...>& tuple;
+  basic_string_view<Char> sep;
+
+  tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
+      : tuple(t), sep{s} {}
+};
+
+template <typename Char, typename... T>
+using tuple_arg_join = tuple_join_view<Char, T...>;
+
+template <typename Char, typename... T>
+struct formatter<tuple_join_view<Char, T...>, Char> {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
+      typename FormatContext::iterator {
+    return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
+  }
+
+ private:
+  template <typename FormatContext, size_t... N>
+  auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
+              detail::index_sequence<N...>) ->
+      typename FormatContext::iterator {
+    using std::get;
+    return format_args(value, ctx, get<N>(value.tuple)...);
+  }
+
+  template <typename FormatContext>
+  auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
+      typename FormatContext::iterator {
+    // NOTE: for compilers that support C++17, this empty function instantiation
+    // can be replaced with a constexpr branch in the variadic overload.
+    return ctx.out();
+  }
+
+  template <typename FormatContext, typename Arg, typename... Args>
+  auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
+                   const Arg& arg, const Args&... args) ->
+      typename FormatContext::iterator {
+    using base = formatter<typename std::decay<Arg>::type, Char>;
+    auto out = base().format(arg, ctx);
+    if (sizeof...(Args) > 0) {
+      out = std::copy(value.sep.begin(), value.sep.end(), out);
+      ctx.advance_to(out);
+      return format_args(value, ctx, args...);
+    }
+    return out;
+  }
+};
+
+FMT_MODULE_EXPORT_BEGIN
+
+/**
+  \rst
+  Returns an object that formats `tuple` with elements separated by `sep`.
+
+  **Example**::
+
+    std::tuple<int, char> t = {1, 'a'};
+    fmt::print("{}", fmt::join(t, ", "));
+    // Output: "1, a"
+  \endrst
+ */
+template <typename... T>
+FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
+    -> tuple_join_view<char, T...> {
+  return {tuple, sep};
+}
+
+template <typename... T>
+FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
+                        basic_string_view<wchar_t> sep)
+    -> tuple_join_view<wchar_t, T...> {
+  return {tuple, sep};
+}
+
+/**
+  \rst
+  Returns an object that formats `initializer_list` with elements separated by
+  `sep`.
+
+  **Example**::
+
+    fmt::print("{}", fmt::join({1, 2, 3}, ", "));
+    // Output: "1, 2, 3"
+  \endrst
+ */
+template <typename T>
+auto join(std::initializer_list<T> list, string_view sep)
+    -> join_view<const T*, const T*> {
+  return join(std::begin(list), std::end(list), sep);
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_RANGES_H_
diff --git a/wpiutil/src/main/native/fmtlib/include/fmt/xchar.h b/wpiutil/src/main/native/fmtlib/include/fmt/xchar.h
new file mode 100644
index 0000000..a0dd032
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/include/fmt/xchar.h
@@ -0,0 +1,236 @@
+// Formatting library for C++ - optional wchar_t and exotic character support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_WCHAR_H_
+#define FMT_WCHAR_H_
+
+#include <cwchar>
+#include <tuple>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+template <typename T>
+using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
+}
+
+FMT_MODULE_EXPORT_BEGIN
+
+using wstring_view = basic_string_view<wchar_t>;
+using wformat_parse_context = basic_format_parse_context<wchar_t>;
+using wformat_context = buffer_context<wchar_t>;
+using wformat_args = basic_format_args<wformat_context>;
+using wmemory_buffer = basic_memory_buffer<wchar_t>;
+
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+// Workaround broken conversion on older gcc.
+template <typename... Args> using wformat_string = wstring_view;
+#else
+template <typename... Args>
+using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
+#endif
+
+template <> struct is_char<wchar_t> : std::true_type {};
+template <> struct is_char<detail::char8_type> : std::true_type {};
+template <> struct is_char<char16_t> : std::true_type {};
+template <> struct is_char<char32_t> : std::true_type {};
+
+template <typename... Args>
+constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
+    const Args&... args) {
+  return {args...};
+}
+
+inline namespace literals {
+constexpr auto operator"" _format(const wchar_t* s, size_t n)
+    -> detail::udl_formatter<wchar_t> {
+  return {{s, n}};
+}
+
+#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
+  return {s};
+}
+#endif
+}  // namespace literals
+
+template <typename It, typename Sentinel>
+auto join(It begin, Sentinel end, wstring_view sep)
+    -> join_view<It, Sentinel, wchar_t> {
+  return {begin, end, sep};
+}
+
+template <typename Range>
+auto join(Range&& range, wstring_view sep)
+    -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
+                 wchar_t> {
+  return join(std::begin(range), std::end(range), sep);
+}
+
+template <typename T>
+auto join(std::initializer_list<T> list, wstring_view sep)
+    -> join_view<const T*, const T*, wchar_t> {
+  return join(std::begin(list), std::end(list), sep);
+}
+
+template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+auto vformat(basic_string_view<Char> format_str,
+             basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> std::basic_string<Char> {
+  basic_memory_buffer<Char> buffer;
+  detail::vformat_to(buffer, format_str, args);
+  return to_string(buffer);
+}
+
+// Pass char_t as a default template parameter instead of using
+// std::basic_string<char_t<S>> to reduce the symbol size.
+template <typename S, typename... Args, typename Char = char_t<S>,
+          FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return vformat(to_string_view(format_str), vargs);
+}
+
+template <typename Locale, typename S, typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
+                            detail::is_exotic_char<Char>::value)>
+inline auto vformat(
+    const Locale& loc, const S& format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> std::basic_string<Char> {
+  return detail::vformat(loc, to_string_view(format_str), args);
+}
+
+template <typename Locale, typename S, typename... Args,
+          typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
+                            detail::is_exotic_char<Char>::value)>
+inline auto format(const Locale& loc, const S& format_str, Args&&... args)
+    -> std::basic_string<Char> {
+  return detail::vformat(loc, to_string_view(format_str),
+                         fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+template <typename OutputIt, typename S, typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
+                            detail::is_exotic_char<Char>::value)>
+auto vformat_to(OutputIt out, const S& format_str,
+                basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> OutputIt {
+  auto&& buf = detail::get_buffer<Char>(out);
+  detail::vformat_to(buf, to_string_view(format_str), args);
+  return detail::get_iterator(buf);
+}
+
+template <typename OutputIt, typename S, typename... Args,
+          typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
+                            detail::is_exotic_char<Char>::value)>
+inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
+  const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
+  return vformat_to(out, to_string_view(fmt), vargs);
+}
+
+template <typename S, typename... Args, typename Char, size_t SIZE,
+          typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
+FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
+                              const S& format_str, Args&&... args) ->
+    typename buffer_context<Char>::iterator {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  detail::vformat_to(buf, to_string_view(format_str), vargs, {});
+  return detail::buffer_appender<Char>(buf);
+}
+
+template <typename Locale, typename S, typename OutputIt, typename... Args,
+          typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
+                            detail::is_locale<Locale>::value&&
+                                detail::is_exotic_char<Char>::value)>
+inline auto vformat_to(
+    OutputIt out, const Locale& loc, const S& format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
+  auto&& buf = detail::get_buffer<Char>(out);
+  vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
+  return detail::get_iterator(buf);
+}
+
+template <
+    typename OutputIt, typename Locale, typename S, typename... Args,
+    typename Char = char_t<S>,
+    bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
+        detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
+inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
+                      Args&&... args) ->
+    typename std::enable_if<enable, OutputIt>::type {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return vformat_to(out, loc, to_string_view(format_str), vargs);
+}
+
+template <typename OutputIt, typename Char, typename... Args,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
+                            detail::is_exotic_char<Char>::value)>
+inline auto vformat_to_n(
+    OutputIt out, size_t n, basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> format_to_n_result<OutputIt> {
+  detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
+                                                                           n);
+  detail::vformat_to(buf, format_str, args);
+  return {buf.out(), buf.count()};
+}
+
+template <typename OutputIt, typename S, typename... Args,
+          typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
+                            detail::is_exotic_char<Char>::value)>
+inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
+                        const Args&... args) -> format_to_n_result<OutputIt> {
+  const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
+  return vformat_to_n(out, n, to_string_view(fmt), vargs);
+}
+
+template <typename S, typename... Args, typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
+inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
+  detail::counting_buffer<Char> buf;
+  const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
+  detail::vformat_to(buf, to_string_view(fmt), vargs);
+  return buf.count();
+}
+
+inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
+  wmemory_buffer buffer;
+  detail::vformat_to(buffer, fmt, args);
+  buffer.push_back(L'\0');
+  if (std::fputws(buffer.data(), f) == -1)
+    FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
+}
+
+inline void vprint(wstring_view fmt, wformat_args args) {
+  vprint(stdout, fmt, args);
+}
+
+template <typename... T>
+void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
+  return vprint(f, wstring_view(fmt), make_wformat_args(args...));
+}
+
+template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
+  return vprint(wstring_view(fmt), make_wformat_args(args...));
+}
+
+/**
+  Converts *value* to ``std::wstring`` using the default format for type *T*.
+ */
+template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
+  return format(FMT_STRING(L"{}"), value);
+}
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_WCHAR_H_
diff --git a/wpiutil/src/main/native/fmtlib/src/format.cpp b/wpiutil/src/main/native/fmtlib/src/format.cpp
new file mode 100644
index 0000000..66925b4
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/src/format.cpp
@@ -0,0 +1,78 @@
+// Formatting library for C++
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/format-inl.h"
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+template <typename T>
+int format_float(char* buf, std::size_t size, const char* format, int precision,
+                 T value) {
+#ifdef FMT_FUZZ
+  if (precision > 100000)
+    throw std::runtime_error(
+        "fuzz mode - avoid large allocation inside snprintf");
+#endif
+  // Suppress the warning about nonliteral format string.
+  int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
+  return precision < 0 ? snprintf_ptr(buf, size, format, value)
+                       : snprintf_ptr(buf, size, format, precision, value);
+}
+
+template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
+    FMT_NOEXCEPT;
+template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
+    FMT_NOEXCEPT;
+}  // namespace detail
+
+// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
+int (*instantiate_format_float)(double, int, detail::float_specs,
+                                detail::buffer<char>&) = detail::format_float;
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
+template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
+#endif
+
+// Explicit instantiations for char.
+
+template FMT_API auto detail::thousands_sep_impl(locale_ref)
+    -> thousands_sep_result<char>;
+template FMT_API char detail::decimal_point_impl(locale_ref);
+
+template FMT_API void detail::buffer<char>::append(const char*, const char*);
+
+// DEPRECATED!
+// There is no correspondent extern template in format.h because of
+// incompatibility between clang and gcc (#2377).
+template FMT_API void detail::vformat_to(
+    detail::buffer<char>&, string_view,
+    basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
+
+template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
+                                            detail::buffer<char>&);
+template FMT_API int detail::snprintf_float(long double, int,
+                                            detail::float_specs,
+                                            detail::buffer<char>&);
+template FMT_API int detail::format_float(double, int, detail::float_specs,
+                                          detail::buffer<char>&);
+template FMT_API int detail::format_float(long double, int, detail::float_specs,
+                                          detail::buffer<char>&);
+
+// Explicit instantiations for wchar_t.
+
+template FMT_API auto detail::thousands_sep_impl(locale_ref)
+    -> thousands_sep_result<wchar_t>;
+template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
+
+template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
+                                                      const wchar_t*);
+
+template struct detail::basic_data<void>;
+
+FMT_END_NAMESPACE
diff --git a/wpiutil/src/main/native/fmtlib/src/os.cpp b/wpiutil/src/main/native/fmtlib/src/os.cpp
new file mode 100644
index 0000000..934629d
--- /dev/null
+++ b/wpiutil/src/main/native/fmtlib/src/os.cpp
@@ -0,0 +1,360 @@
+// Formatting library for C++ - optional OS-specific functionality
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+// Disable bogus MSVC warnings.
+#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
+#  define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "fmt/os.h"
+
+#include <climits>
+
+#if FMT_USE_FCNTL
+#  include <sys/stat.h>
+#  include <sys/types.h>
+
+#  ifndef _WIN32
+#    include <unistd.h>
+#  else
+#    ifndef WIN32_LEAN_AND_MEAN
+#      define WIN32_LEAN_AND_MEAN
+#    endif
+#    include <io.h>
+
+#    define O_CREAT _O_CREAT
+#    define O_TRUNC _O_TRUNC
+
+#    ifndef S_IRUSR
+#      define S_IRUSR _S_IREAD
+#    endif
+
+#    ifndef S_IWUSR
+#      define S_IWUSR _S_IWRITE
+#    endif
+
+#    ifdef __MINGW32__
+#      define _SH_DENYNO 0x40
+#    endif
+#  endif  // _WIN32
+#endif    // FMT_USE_FCNTL
+
+#ifdef _WIN32
+#  include <windows.h>
+#endif
+
+#ifdef fileno
+#  undef fileno
+#endif
+
+namespace {
+#ifdef _WIN32
+// Return type of read and write functions.
+using rwresult = int;
+
+// On Windows the count argument to read and write is unsigned, so convert
+// it from size_t preventing integer overflow.
+inline unsigned convert_rwcount(std::size_t count) {
+  return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
+}
+#elif FMT_USE_FCNTL
+// Return type of read and write functions.
+using rwresult = ssize_t;
+
+inline std::size_t convert_rwcount(std::size_t count) { return count; }
+#endif
+}  // namespace
+
+FMT_BEGIN_NAMESPACE
+
+#ifdef _WIN32
+detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
+  if (int error_code = convert(s)) {
+    FMT_THROW(windows_error(error_code,
+                            "cannot convert string from UTF-16 to UTF-8"));
+  }
+}
+
+int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
+  if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
+  int s_size = static_cast<int>(s.size());
+  if (s_size == 0) {
+    // WideCharToMultiByte does not support zero length, handle separately.
+    buffer_.resize(1);
+    buffer_[0] = 0;
+    return 0;
+  }
+
+  int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
+                                   nullptr, nullptr);
+  if (length == 0) return GetLastError();
+  buffer_.resize(length + 1);
+  length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
+                               length, nullptr, nullptr);
+  if (length == 0) return GetLastError();
+  buffer_[length] = 0;
+  return 0;
+}
+
+namespace detail {
+
+class system_message {
+  system_message(const system_message&) = delete;
+  void operator=(const system_message&) = delete;
+
+  unsigned long result_;
+  wchar_t* message_;
+
+  static bool is_whitespace(wchar_t c) FMT_NOEXCEPT {
+    return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
+  }
+
+ public:
+  explicit system_message(unsigned long error_code)
+      : result_(0), message_(nullptr) {
+    result_ = FormatMessageW(
+        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+            FORMAT_MESSAGE_IGNORE_INSERTS,
+        nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+        reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
+    if (result_ != 0) {
+      while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
+        --result_;
+      }
+    }
+  }
+  ~system_message() { LocalFree(message_); }
+  explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; }
+  operator basic_string_view<wchar_t>() const FMT_NOEXCEPT {
+    return basic_string_view<wchar_t>(message_, result_);
+  }
+};
+
+class utf8_system_category final : public std::error_category {
+ public:
+  const char* name() const FMT_NOEXCEPT override { return "system"; }
+  std::string message(int error_code) const override {
+    system_message msg(error_code);
+    if (msg) {
+      utf16_to_utf8 utf8_message;
+      if (utf8_message.convert(msg) == ERROR_SUCCESS) {
+        return utf8_message.str();
+      }
+    }
+    return "unknown error";
+  }
+};
+
+}  // namespace detail
+
+FMT_API const std::error_category& system_category() FMT_NOEXCEPT {
+  static const detail::utf8_system_category category;
+  return category;
+}
+
+std::system_error vwindows_error(int err_code, string_view format_str,
+                                 format_args args) {
+  auto ec = std::error_code(err_code, system_category());
+  return std::system_error(ec, vformat(format_str, args));
+}
+
+void detail::format_windows_error(detail::buffer<char>& out, int error_code,
+                                  const char* message) FMT_NOEXCEPT {
+  FMT_TRY {
+    system_message msg(error_code);
+    if (msg) {
+      utf16_to_utf8 utf8_message;
+      if (utf8_message.convert(msg) == ERROR_SUCCESS) {
+        format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
+        return;
+      }
+    }
+  }
+  FMT_CATCH(...) {}
+  format_error_code(out, error_code, message);
+}
+
+void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT {
+  report_error(detail::format_windows_error, error_code, message);
+}
+#endif  // _WIN32
+
+buffered_file::~buffered_file() FMT_NOEXCEPT {
+  if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
+    report_system_error(errno, "cannot close file");
+}
+
+buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
+  FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
+                nullptr);
+  if (!file_)
+    FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
+}
+
+void buffered_file::close() {
+  if (!file_) return;
+  int result = FMT_SYSTEM(fclose(file_));
+  file_ = nullptr;
+  if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
+}
+
+// A macro used to prevent expansion of fileno on broken versions of MinGW.
+#define FMT_ARGS
+
+int buffered_file::fileno() const {
+  int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
+  if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
+  return fd;
+}
+
+#if FMT_USE_FCNTL
+file::file(cstring_view path, int oflag) {
+  int mode = S_IRUSR | S_IWUSR;
+#  if defined(_WIN32) && !defined(__MINGW32__)
+  fd_ = -1;
+  FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
+#  else
+  FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
+#  endif
+  if (fd_ == -1)
+    FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
+}
+
+file::~file() FMT_NOEXCEPT {
+  // Don't retry close in case of EINTR!
+  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
+  if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
+    report_system_error(errno, "cannot close file");
+}
+
+void file::close() {
+  if (fd_ == -1) return;
+  // Don't retry close in case of EINTR!
+  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
+  int result = FMT_POSIX_CALL(close(fd_));
+  fd_ = -1;
+  if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
+}
+
+long long file::size() const {
+#  ifdef _WIN32
+  // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
+  // is less than 0x0500 as is the case with some default MinGW builds.
+  // Both functions support large file sizes.
+  DWORD size_upper = 0;
+  HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
+  DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
+  if (size_lower == INVALID_FILE_SIZE) {
+    DWORD error = GetLastError();
+    if (error != NO_ERROR)
+      FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
+  }
+  unsigned long long long_size = size_upper;
+  return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
+#  else
+  using Stat = struct stat;
+  Stat file_stat = Stat();
+  if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
+    FMT_THROW(system_error(errno, "cannot get file attributes"));
+  static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
+                "return type of file::size is not large enough");
+  return file_stat.st_size;
+#  endif
+}
+
+std::size_t file::read(void* buffer, std::size_t count) {
+  rwresult result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
+  if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
+  return detail::to_unsigned(result);
+}
+
+std::size_t file::write(const void* buffer, std::size_t count) {
+  rwresult result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
+  if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
+  return detail::to_unsigned(result);
+}
+
+file file::dup(int fd) {
+  // Don't retry as dup doesn't return EINTR.
+  // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
+  int new_fd = FMT_POSIX_CALL(dup(fd));
+  if (new_fd == -1)
+    FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
+  return file(new_fd);
+}
+
+void file::dup2(int fd) {
+  int result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
+  if (result == -1) {
+    FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
+                           fd_, fd));
+  }
+}
+
+void file::dup2(int fd, std::error_code& ec) FMT_NOEXCEPT {
+  int result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
+  if (result == -1) ec = std::error_code(errno, std::generic_category());
+}
+
+void file::pipe(file& read_end, file& write_end) {
+  // Close the descriptors first to make sure that assignments don't throw
+  // and there are no leaks.
+  read_end.close();
+  write_end.close();
+  int fds[2] = {};
+#  ifdef _WIN32
+  // Make the default pipe capacity same as on Linux 2.6.11+.
+  enum { DEFAULT_CAPACITY = 65536 };
+  int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
+#  else
+  // Don't retry as the pipe function doesn't return EINTR.
+  // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
+  int result = FMT_POSIX_CALL(pipe(fds));
+#  endif
+  if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
+  // The following assignments don't throw because read_fd and write_fd
+  // are closed.
+  read_end = file(fds[0]);
+  write_end = file(fds[1]);
+}
+
+buffered_file file::fdopen(const char* mode) {
+// Don't retry as fdopen doesn't return EINTR.
+#  if defined(__MINGW32__) && defined(_POSIX_)
+  FILE* f = ::fdopen(fd_, mode);
+#  else
+  FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
+#  endif
+  if (!f)
+    FMT_THROW(
+        system_error(errno, "cannot associate stream with file descriptor"));
+  buffered_file bf(f);
+  fd_ = -1;
+  return bf;
+}
+
+long getpagesize() {
+#  ifdef _WIN32
+  SYSTEM_INFO si;
+  GetSystemInfo(&si);
+  return si.dwPageSize;
+#  else
+  long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
+  if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
+  return size;
+#  endif
+}
+
+FMT_API void ostream::grow(size_t) {
+  if (this->size() == this->capacity()) flush();
+}
+#endif  // FMT_USE_FCNTL
+FMT_END_NAMESPACE
diff --git a/wpiutil/src/main/native/include/wpi/Algorithm.h b/wpiutil/src/main/native/include/wpi/Algorithm.h
index ec8dc95..d0744dc 100644
--- a/wpiutil/src/main/native/include/wpi/Algorithm.h
+++ b/wpiutil/src/main/native/include/wpi/Algorithm.h
@@ -1,12 +1,10 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
+#include <algorithm>
 #include <vector>
 
 namespace wpi {
diff --git a/wpiutil/src/main/native/include/wpi/ArrayRef.h b/wpiutil/src/main/native/include/wpi/ArrayRef.h
deleted file mode 100644
index c94312e..0000000
--- a/wpiutil/src/main/native/include/wpi/ArrayRef.h
+++ /dev/null
@@ -1,537 +0,0 @@
-//===- ArrayRef.h - Array Reference Wrapper ---------------------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_ARRAYREF_H
-#define WPIUTIL_WPI_ARRAYREF_H
-
-#include "wpi/Hashing.h"
-#include "wpi/SmallVector.h"
-#include "wpi/STLExtras.h"
-#include "wpi/Compiler.h"
-#include <algorithm>
-#include <array>
-#include <cassert>
-#include <cstddef>
-#include <initializer_list>
-#include <iterator>
-#include <memory>
-#include <optional>
-#include <type_traits>
-#include <vector>
-
-namespace wpi {
-
-  /// ArrayRef - Represent a constant reference to an array (0 or more elements
-  /// consecutively in memory), i.e. a start pointer and a length.  It allows
-  /// various APIs to take consecutive elements easily and conveniently.
-  ///
-  /// This class does not own the underlying data, it is expected to be used in
-  /// situations where the data resides in some other buffer, whose lifetime
-  /// extends past that of the ArrayRef. For this reason, it is not in general
-  /// safe to store an ArrayRef.
-  ///
-  /// This is intended to be trivially copyable, so it should be passed by
-  /// value.
-  template<typename T>
-  class LLVM_NODISCARD ArrayRef {
-  public:
-    using iterator = const T *;
-    using const_iterator = const T *;
-    using size_type = size_t;
-    using reverse_iterator = std::reverse_iterator<iterator>;
-    using value_type = T;
-
-  private:
-    /// The start of the array, in an external buffer.
-    const T *Data = nullptr;
-
-    /// The number of elements.
-    size_type Length = 0;
-
-  public:
-    /// @name Constructors
-    /// @{
-
-    /// Construct an empty ArrayRef.
-    /*implicit*/ ArrayRef() = default;
-
-    /// Construct an empty ArrayRef from nullopt.
-    /*implicit*/ ArrayRef(std::nullopt_t) {}
-
-    /// Construct an ArrayRef from a single element.
-    /*implicit*/ ArrayRef(const T &OneElt)
-      : Data(&OneElt), Length(1) {}
-
-    /// Construct an ArrayRef from a pointer and length.
-    /*implicit*/ ArrayRef(const T *data, size_t length)
-      : Data(data), Length(length) {}
-
-    /// Construct an ArrayRef from a range.
-    ArrayRef(const T *begin, const T *end)
-      : Data(begin), Length(end - begin) {}
-
-    /// Construct an ArrayRef from a SmallVector. This is templated in order to
-    /// avoid instantiating SmallVectorTemplateCommon<T> whenever we
-    /// copy-construct an ArrayRef.
-    template<typename U>
-    /*implicit*/ ArrayRef(const SmallVectorTemplateCommon<T, U> &Vec)
-      : Data(Vec.data()), Length(Vec.size()) {
-    }
-
-    /// Construct an ArrayRef from a std::vector.
-    template<typename A>
-    /*implicit*/ ArrayRef(const std::vector<T, A> &Vec)
-      : Data(Vec.data()), Length(Vec.size()) {}
-
-    /// Construct an ArrayRef from a std::array
-    template <size_t N>
-    /*implicit*/ constexpr ArrayRef(const std::array<T, N> &Arr)
-        : Data(Arr.data()), Length(N) {}
-
-    /// Construct an ArrayRef from a C array.
-    template <size_t N>
-    /*implicit*/ constexpr ArrayRef(const T (&Arr)[N]) : Data(Arr), Length(N) {}
-
-    /// Construct an ArrayRef<const T*> from ArrayRef<T*>. This uses SFINAE to
-    /// ensure that only ArrayRefs of pointers can be converted.
-    template <typename U>
-    ArrayRef(
-        const ArrayRef<U *> &A,
-        typename std::enable_if<
-           std::is_convertible<U *const *, T const *>::value>::type * = nullptr)
-      : Data(A.data()), Length(A.size()) {}
-
-    /// Construct an ArrayRef<const T*> from a SmallVector<T*>. This is
-    /// templated in order to avoid instantiating SmallVectorTemplateCommon<T>
-    /// whenever we copy-construct an ArrayRef.
-    template<typename U, typename DummyT>
-    /*implicit*/ ArrayRef(
-      const SmallVectorTemplateCommon<U *, DummyT> &Vec,
-      typename std::enable_if<
-          std::is_convertible<U *const *, T const *>::value>::type * = nullptr)
-      : Data(Vec.data()), Length(Vec.size()) {
-    }
-
-    /// Construct an ArrayRef<const T*> from std::vector<T*>. This uses SFINAE
-    /// to ensure that only vectors of pointers can be converted.
-    template<typename U, typename A>
-    ArrayRef(const std::vector<U *, A> &Vec,
-             typename std::enable_if<
-                 std::is_convertible<U *const *, T const *>::value>::type* = 0)
-      : Data(Vec.data()), Length(Vec.size()) {}
-
-    /// @}
-    /// @name Simple Operations
-    /// @{
-
-    iterator begin() const { return Data; }
-    iterator end() const { return Data + Length; }
-
-    reverse_iterator rbegin() const { return reverse_iterator(end()); }
-    reverse_iterator rend() const { return reverse_iterator(begin()); }
-
-    /// empty - Check if the array is empty.
-    bool empty() const { return Length == 0; }
-
-    const T *data() const { return Data; }
-
-    /// size - Get the array size.
-    size_t size() const { return Length; }
-
-    /// front - Get the first element.
-    const T &front() const {
-      assert(!empty());
-      return Data[0];
-    }
-
-    /// back - Get the last element.
-    const T &back() const {
-      assert(!empty());
-      return Data[Length-1];
-    }
-
-    // copy - Allocate copy in Allocator and return ArrayRef<T> to it.
-    template <typename Allocator> ArrayRef<T> copy(Allocator &A) {
-      T *Buff = A.template Allocate<T>(Length);
-      std::uninitialized_copy(begin(), end(), Buff);
-      return ArrayRef<T>(Buff, Length);
-    }
-
-    /// equals - Check for element-wise equality.
-    bool equals(ArrayRef RHS) const {
-      if (Length != RHS.Length)
-        return false;
-      return std::equal(begin(), end(), RHS.begin());
-    }
-
-    /// slice(n, m) - Chop off the first N elements of the array, and keep M
-    /// elements in the array.
-    ArrayRef<T> slice(size_t N, size_t M) const {
-      assert(N+M <= size() && "Invalid specifier");
-      return ArrayRef<T>(data()+N, M);
-    }
-
-    /// slice(n) - Chop off the first N elements of the array.
-    ArrayRef<T> slice(size_t N) const { return slice(N, size() - N); }
-
-    /// Drop the first \p N elements of the array.
-    ArrayRef<T> drop_front(size_t N = 1) const {
-      assert(size() >= N && "Dropping more elements than exist");
-      return slice(N, size() - N);
-    }
-
-    /// Drop the last \p N elements of the array.
-    ArrayRef<T> drop_back(size_t N = 1) const {
-      assert(size() >= N && "Dropping more elements than exist");
-      return slice(0, size() - N);
-    }
-
-    /// Return a copy of *this with the first N elements satisfying the
-    /// given predicate removed.
-    template <class PredicateT> ArrayRef<T> drop_while(PredicateT Pred) const {
-      return ArrayRef<T>(find_if_not(*this, Pred), end());
-    }
-
-    /// Return a copy of *this with the first N elements not satisfying
-    /// the given predicate removed.
-    template <class PredicateT> ArrayRef<T> drop_until(PredicateT Pred) const {
-      return ArrayRef<T>(find_if(*this, Pred), end());
-    }
-
-    /// Return a copy of *this with only the first \p N elements.
-    ArrayRef<T> take_front(size_t N = 1) const {
-      if (N >= size())
-        return *this;
-      return drop_back(size() - N);
-    }
-
-    /// Return a copy of *this with only the last \p N elements.
-    ArrayRef<T> take_back(size_t N = 1) const {
-      if (N >= size())
-        return *this;
-      return drop_front(size() - N);
-    }
-
-    /// Return the first N elements of this Array that satisfy the given
-    /// predicate.
-    template <class PredicateT> ArrayRef<T> take_while(PredicateT Pred) const {
-      return ArrayRef<T>(begin(), find_if_not(*this, Pred));
-    }
-
-    /// Return the first N elements of this Array that don't satisfy the
-    /// given predicate.
-    template <class PredicateT> ArrayRef<T> take_until(PredicateT Pred) const {
-      return ArrayRef<T>(begin(), find_if(*this, Pred));
-    }
-
-    /// @}
-    /// @name Operator Overloads
-    /// @{
-    const T &operator[](size_t Index) const {
-      assert(Index < Length && "Invalid index!");
-      return Data[Index];
-    }
-
-    /// Disallow accidental assignment from a temporary.
-    ///
-    /// The declaration here is extra complicated so that "arrayRef = {}"
-    /// continues to select the move assignment operator.
-    template <typename U>
-    typename std::enable_if<std::is_same<U, T>::value, ArrayRef<T>>::type &
-    operator=(U &&Temporary) = delete;
-
-    /// Disallow accidental assignment from a temporary.
-    ///
-    /// The declaration here is extra complicated so that "arrayRef = {}"
-    /// continues to select the move assignment operator.
-    template <typename U>
-    typename std::enable_if<std::is_same<U, T>::value, ArrayRef<T>>::type &
-    operator=(std::initializer_list<U>) = delete;
-
-    /// @}
-    /// @name Expensive Operations
-    /// @{
-    std::vector<T> vec() const {
-      return std::vector<T>(Data, Data+Length);
-    }
-
-    /// @}
-    /// @name Conversion operators
-    /// @{
-    operator std::vector<T>() const {
-      return std::vector<T>(Data, Data+Length);
-    }
-
-    /// @}
-  };
-
-  /// MutableArrayRef - Represent a mutable reference to an array (0 or more
-  /// elements consecutively in memory), i.e. a start pointer and a length.  It
-  /// allows various APIs to take and modify consecutive elements easily and
-  /// conveniently.
-  ///
-  /// This class does not own the underlying data, it is expected to be used in
-  /// situations where the data resides in some other buffer, whose lifetime
-  /// extends past that of the MutableArrayRef. For this reason, it is not in
-  /// general safe to store a MutableArrayRef.
-  ///
-  /// This is intended to be trivially copyable, so it should be passed by
-  /// value.
-  template<typename T>
-  class LLVM_NODISCARD MutableArrayRef : public ArrayRef<T> {
-  public:
-    using iterator = T *;
-    using reverse_iterator = std::reverse_iterator<iterator>;
-
-    /// Construct an empty MutableArrayRef.
-    /*implicit*/ MutableArrayRef() = default;
-
-    /// Construct an empty MutableArrayRef from nullopt.
-    /*implicit*/ MutableArrayRef(std::nullopt_t) : ArrayRef<T>() {}
-
-    /// Construct an MutableArrayRef from a single element.
-    /*implicit*/ MutableArrayRef(T &OneElt) : ArrayRef<T>(OneElt) {}
-
-    /// Construct an MutableArrayRef from a pointer and length.
-    /*implicit*/ MutableArrayRef(T *data, size_t length)
-      : ArrayRef<T>(data, length) {}
-
-    /// Construct an MutableArrayRef from a range.
-    MutableArrayRef(T *begin, T *end) : ArrayRef<T>(begin, end) {}
-
-    /// Construct an MutableArrayRef from a SmallVector.
-    /*implicit*/ MutableArrayRef(SmallVectorImpl<T> &Vec)
-    : ArrayRef<T>(Vec) {}
-
-    /// Construct a MutableArrayRef from a std::vector.
-    /*implicit*/ MutableArrayRef(std::vector<T> &Vec)
-    : ArrayRef<T>(Vec) {}
-
-    /// Construct an ArrayRef from a std::array
-    template <size_t N>
-    /*implicit*/ constexpr MutableArrayRef(std::array<T, N> &Arr)
-        : ArrayRef<T>(Arr) {}
-
-    /// Construct an MutableArrayRef from a C array.
-    template <size_t N>
-    /*implicit*/ constexpr MutableArrayRef(T (&Arr)[N]) : ArrayRef<T>(Arr) {}
-
-    T *data() const { return const_cast<T*>(ArrayRef<T>::data()); }
-
-    iterator begin() const { return data(); }
-    iterator end() const { return data() + this->size(); }
-
-    reverse_iterator rbegin() const { return reverse_iterator(end()); }
-    reverse_iterator rend() const { return reverse_iterator(begin()); }
-
-    /// front - Get the first element.
-    T &front() const {
-      assert(!this->empty());
-      return data()[0];
-    }
-
-    /// back - Get the last element.
-    T &back() const {
-      assert(!this->empty());
-      return data()[this->size()-1];
-    }
-
-    /// slice(n, m) - Chop off the first N elements of the array, and keep M
-    /// elements in the array.
-    MutableArrayRef<T> slice(size_t N, size_t M) const {
-      assert(N + M <= this->size() && "Invalid specifier");
-      return MutableArrayRef<T>(this->data() + N, M);
-    }
-
-    /// slice(n) - Chop off the first N elements of the array.
-    MutableArrayRef<T> slice(size_t N) const {
-      return slice(N, this->size() - N);
-    }
-
-    /// Drop the first \p N elements of the array.
-    MutableArrayRef<T> drop_front(size_t N = 1) const {
-      assert(this->size() >= N && "Dropping more elements than exist");
-      return slice(N, this->size() - N);
-    }
-
-    MutableArrayRef<T> drop_back(size_t N = 1) const {
-      assert(this->size() >= N && "Dropping more elements than exist");
-      return slice(0, this->size() - N);
-    }
-
-    /// Return a copy of *this with the first N elements satisfying the
-    /// given predicate removed.
-    template <class PredicateT>
-    MutableArrayRef<T> drop_while(PredicateT Pred) const {
-      return MutableArrayRef<T>(find_if_not(*this, Pred), end());
-    }
-
-    /// Return a copy of *this with the first N elements not satisfying
-    /// the given predicate removed.
-    template <class PredicateT>
-    MutableArrayRef<T> drop_until(PredicateT Pred) const {
-      return MutableArrayRef<T>(find_if(*this, Pred), end());
-    }
-
-    /// Return a copy of *this with only the first \p N elements.
-    MutableArrayRef<T> take_front(size_t N = 1) const {
-      if (N >= this->size())
-        return *this;
-      return drop_back(this->size() - N);
-    }
-
-    /// Return a copy of *this with only the last \p N elements.
-    MutableArrayRef<T> take_back(size_t N = 1) const {
-      if (N >= this->size())
-        return *this;
-      return drop_front(this->size() - N);
-    }
-
-    /// Return the first N elements of this Array that satisfy the given
-    /// predicate.
-    template <class PredicateT>
-    MutableArrayRef<T> take_while(PredicateT Pred) const {
-      return MutableArrayRef<T>(begin(), find_if_not(*this, Pred));
-    }
-
-    /// Return the first N elements of this Array that don't satisfy the
-    /// given predicate.
-    template <class PredicateT>
-    MutableArrayRef<T> take_until(PredicateT Pred) const {
-      return MutableArrayRef<T>(begin(), find_if(*this, Pred));
-    }
-
-    /// @}
-    /// @name Operator Overloads
-    /// @{
-    T &operator[](size_t Index) const {
-      assert(Index < this->size() && "Invalid index!");
-      return data()[Index];
-    }
-  };
-
-  /// This is a MutableArrayRef that owns its array.
-  template <typename T> class OwningArrayRef : public MutableArrayRef<T> {
-  public:
-    OwningArrayRef() = default;
-    OwningArrayRef(size_t Size) : MutableArrayRef<T>(new T[Size], Size) {}
-
-    OwningArrayRef(ArrayRef<T> Data)
-        : MutableArrayRef<T>(new T[Data.size()], Data.size()) {
-      std::copy(Data.begin(), Data.end(), this->begin());
-    }
-
-    OwningArrayRef(OwningArrayRef &&Other) { *this = Other; }
-
-    OwningArrayRef &operator=(OwningArrayRef &&Other) {
-      delete[] this->data();
-      this->MutableArrayRef<T>::operator=(Other);
-      Other.MutableArrayRef<T>::operator=(MutableArrayRef<T>());
-      return *this;
-    }
-
-    ~OwningArrayRef() { delete[] this->data(); }
-  };
-
-  /// @name ArrayRef Convenience constructors
-  /// @{
-
-  /// Construct an ArrayRef from a single element.
-  template<typename T>
-  ArrayRef<T> makeArrayRef(const T &OneElt) {
-    return OneElt;
-  }
-
-  /// Construct an ArrayRef from a pointer and length.
-  template<typename T>
-  ArrayRef<T> makeArrayRef(const T *data, size_t length) {
-    return ArrayRef<T>(data, length);
-  }
-
-  /// Construct an ArrayRef from a range.
-  template<typename T>
-  ArrayRef<T> makeArrayRef(const T *begin, const T *end) {
-    return ArrayRef<T>(begin, end);
-  }
-
-  /// Construct an ArrayRef from a SmallVector.
-  template <typename T>
-  ArrayRef<T> makeArrayRef(const SmallVectorImpl<T> &Vec) {
-    return Vec;
-  }
-
-  /// Construct an ArrayRef from a SmallVector.
-  template <typename T, unsigned N>
-  ArrayRef<T> makeArrayRef(const SmallVector<T, N> &Vec) {
-    return Vec;
-  }
-
-  /// Construct an ArrayRef from a std::vector.
-  template<typename T>
-  ArrayRef<T> makeArrayRef(const std::vector<T> &Vec) {
-    return Vec;
-  }
-
-  /// Construct an ArrayRef from an ArrayRef (no-op) (const)
-  template <typename T> ArrayRef<T> makeArrayRef(const ArrayRef<T> &Vec) {
-    return Vec;
-  }
-
-  /// Construct an ArrayRef from an ArrayRef (no-op)
-  template <typename T> ArrayRef<T> &makeArrayRef(ArrayRef<T> &Vec) {
-    return Vec;
-  }
-
-  /// Construct an ArrayRef from a C array.
-  template<typename T, size_t N>
-  ArrayRef<T> makeArrayRef(const T (&Arr)[N]) {
-    return ArrayRef<T>(Arr);
-  }
-
-  /// Construct a MutableArrayRef from a single element.
-  template<typename T>
-  MutableArrayRef<T> makeMutableArrayRef(T &OneElt) {
-    return OneElt;
-  }
-
-  /// Construct a MutableArrayRef from a pointer and length.
-  template<typename T>
-  MutableArrayRef<T> makeMutableArrayRef(T *data, size_t length) {
-    return MutableArrayRef<T>(data, length);
-  }
-
-  /// @}
-  /// @name ArrayRef Comparison Operators
-  /// @{
-
-  template<typename T>
-  inline bool operator==(ArrayRef<T> LHS, ArrayRef<T> RHS) {
-    return LHS.equals(RHS);
-  }
-
-  template<typename T>
-  inline bool operator!=(ArrayRef<T> LHS, ArrayRef<T> RHS) {
-    return !(LHS == RHS);
-  }
-
-  /// @}
-
-  // ArrayRefs can be treated like a POD type.
-  template <typename T> struct isPodLike;
-  template <typename T> struct isPodLike<ArrayRef<T>> {
-    static const bool value = true;
-  };
-
-  template <typename T> hash_code hash_value(ArrayRef<T> S) {
-    return hash_combine_range(S.begin(), S.end());
-  }
-
-} // end namespace wpi
-
-#endif // LLVM_ADT_ARRAYREF_H
diff --git a/wpiutil/src/main/native/include/wpi/Base64.h b/wpiutil/src/main/native/include/wpi/Base64.h
index cdbf611..41b0062 100644
--- a/wpiutil/src/main/native/include/wpi/Base64.h
+++ b/wpiutil/src/main/native/include/wpi/Base64.h
@@ -1,35 +1,47 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_BASE64_H_
 #define WPIUTIL_WPI_BASE64_H_
 
 #include <cstddef>
 #include <string>
+#include <string_view>
+#include <vector>
 
-#include "wpi/StringRef.h"
+#include "wpi/span.h"
 
 namespace wpi {
 template <typename T>
 class SmallVectorImpl;
 class raw_ostream;
 
-size_t Base64Decode(raw_ostream& os, StringRef encoded);
+size_t Base64Decode(raw_ostream& os, std::string_view encoded);
 
-size_t Base64Decode(StringRef encoded, std::string* plain);
+size_t Base64Decode(std::string_view encoded, std::string* plain);
 
-StringRef Base64Decode(StringRef encoded, size_t* num_read,
-                       SmallVectorImpl<char>& buf);
+std::string_view Base64Decode(std::string_view encoded, size_t* num_read,
+                              SmallVectorImpl<char>& buf);
 
-void Base64Encode(raw_ostream& os, StringRef plain);
+size_t Base64Decode(std::string_view encoded, std::vector<uint8_t>* plain);
 
-void Base64Encode(StringRef plain, std::string* encoded);
+span<uint8_t> Base64Decode(std::string_view encoded, size_t* num_read,
+                           SmallVectorImpl<uint8_t>& buf);
 
-StringRef Base64Encode(StringRef plain, SmallVectorImpl<char>& buf);
+void Base64Encode(raw_ostream& os, std::string_view plain);
+
+void Base64Encode(std::string_view plain, std::string* encoded);
+
+std::string_view Base64Encode(std::string_view plain,
+                              SmallVectorImpl<char>& buf);
+
+void Base64Encode(raw_ostream& os, span<const uint8_t> plain);
+
+void Base64Encode(span<const uint8_t> plain, std::string* encoded);
+
+std::string_view Base64Encode(span<const uint8_t> plain,
+                              SmallVectorImpl<char>& buf);
 
 }  // namespace wpi
 
diff --git a/wpiutil/src/main/native/include/wpi/CallbackManager.h b/wpiutil/src/main/native/include/wpi/CallbackManager.h
new file mode 100644
index 0000000..32e2add
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/CallbackManager.h
@@ -0,0 +1,395 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#ifndef WPIUTIL_WPI_CALLBACKMANAGER_H_
+#define WPIUTIL_WPI_CALLBACKMANAGER_H_
+
+#include <atomic>
+#include <climits>
+#include <functional>
+#include <memory>
+#include <queue>
+#include <utility>
+#include <vector>
+
+#include "wpi/SafeThread.h"
+#include "wpi/UidVector.h"
+#include "wpi/condition_variable.h"
+#include "wpi/mutex.h"
+#include "wpi/raw_ostream.h"
+
+namespace wpi {
+
+template <typename Callback>
+class CallbackListenerData {
+ public:
+  CallbackListenerData() = default;
+  explicit CallbackListenerData(Callback callback_) : callback(callback_) {}
+  explicit CallbackListenerData(unsigned int poller_uid_)
+      : poller_uid(poller_uid_) {}
+
+  explicit operator bool() const { return callback || poller_uid != UINT_MAX; }
+
+  Callback callback;
+  unsigned int poller_uid = UINT_MAX;
+};
+
+// CRTP callback manager thread
+// @tparam Derived        derived class
+// @tparam NotifierData   data buffered for each callback
+// @tparam ListenerData   data stored for each listener
+// Derived must define the following functions:
+//   bool Matches(const ListenerData& listener, const NotifierData& data);
+//   void SetListener(NotifierData* data, unsigned int listener_uid);
+//   void DoCallback(Callback callback, const NotifierData& data);
+template <typename Derived, typename TUserInfo,
+          typename TListenerData =
+              CallbackListenerData<std::function<void(const TUserInfo& info)>>,
+          typename TNotifierData = TUserInfo>
+class CallbackThread : public wpi::SafeThread {
+ public:
+  using UserInfo = TUserInfo;
+  using NotifierData = TNotifierData;
+  using ListenerData = TListenerData;
+
+  CallbackThread(std::function<void()> on_start, std::function<void()> on_exit)
+      : m_on_start(std::move(on_start)), m_on_exit(std::move(on_exit)) {}
+
+  ~CallbackThread() override {
+    // Wake up any blocked pollers
+    for (size_t i = 0; i < m_pollers.size(); ++i) {
+      if (auto poller = m_pollers[i]) {
+        poller->Terminate();
+      }
+    }
+  }
+
+  void Main() override;
+
+  wpi::UidVector<ListenerData, 64> m_listeners;
+
+  std::queue<std::pair<unsigned int, NotifierData>> m_queue;
+  wpi::condition_variable m_queue_empty;
+
+  struct Poller {
+    void Terminate() {
+      {
+        std::scoped_lock lock(poll_mutex);
+        terminating = true;
+      }
+      poll_cond.notify_all();
+    }
+    std::queue<NotifierData> poll_queue;
+    wpi::mutex poll_mutex;
+    wpi::condition_variable poll_cond;
+    bool terminating = false;
+    bool canceling = false;
+  };
+  wpi::UidVector<std::shared_ptr<Poller>, 64> m_pollers;
+
+  std::function<void()> m_on_start;
+  std::function<void()> m_on_exit;
+
+  // Must be called with m_mutex held
+  template <typename... Args>
+  void SendPoller(unsigned int poller_uid, Args&&... args) {
+    if (poller_uid > m_pollers.size()) {
+      return;
+    }
+    auto poller = m_pollers[poller_uid];
+    if (!poller) {
+      return;
+    }
+    {
+      std::scoped_lock lock(poller->poll_mutex);
+      poller->poll_queue.emplace(std::forward<Args>(args)...);
+    }
+    poller->poll_cond.notify_one();
+  }
+};
+
+template <typename Derived, typename TUserInfo, typename TListenerData,
+          typename TNotifierData>
+void CallbackThread<Derived, TUserInfo, TListenerData, TNotifierData>::Main() {
+  if (m_on_start) {
+    m_on_start();
+  }
+
+  std::unique_lock lock(m_mutex);
+  while (m_active) {
+    while (m_queue.empty()) {
+      m_cond.wait(lock);
+      if (!m_active) {
+        goto done;
+      }
+    }
+
+    while (!m_queue.empty()) {
+      if (!m_active) {
+        goto done;
+      }
+      auto item = std::move(m_queue.front());
+
+      if (item.first != UINT_MAX) {
+        if (item.first < m_listeners.size()) {
+          auto& listener = m_listeners[item.first];
+          if (listener &&
+              static_cast<Derived*>(this)->Matches(listener, item.second)) {
+            static_cast<Derived*>(this)->SetListener(&item.second, item.first);
+            if (listener.callback) {
+              lock.unlock();
+              static_cast<Derived*>(this)->DoCallback(listener.callback,
+                                                      item.second);
+              lock.lock();
+            } else if (listener.poller_uid != UINT_MAX) {
+              SendPoller(listener.poller_uid, std::move(item.second));
+            }
+          }
+        }
+      } else {
+        // Use index because iterator might get invalidated.
+        for (size_t i = 0; i < m_listeners.size(); ++i) {
+          auto& listener = m_listeners[i];
+          if (!listener) {
+            continue;
+          }
+
+          if (!static_cast<Derived*>(this)->Matches(listener, item.second)) {
+            continue;
+          }
+          static_cast<Derived*>(this)->SetListener(&item.second,
+                                                   static_cast<unsigned>(i));
+          if (listener.callback) {
+            lock.unlock();
+            static_cast<Derived*>(this)->DoCallback(listener.callback,
+                                                    item.second);
+            lock.lock();
+          } else if (listener.poller_uid != UINT_MAX) {
+            SendPoller(listener.poller_uid, item.second);
+          }
+        }
+      }
+      m_queue.pop();
+    }
+
+    m_queue_empty.notify_all();
+  }
+
+done:
+  if (m_on_exit) {
+    m_on_exit();
+  }
+}
+
+// CRTP callback manager
+// @tparam Derived  derived class
+// @tparam Thread   custom thread (must be derived from impl::CallbackThread)
+//
+// Derived must define the following functions:
+//   void Start();
+template <typename Derived, typename Thread>
+class CallbackManager {
+  friend class RpcServerTest;
+
+ public:
+  void SetOnStart(std::function<void()> on_start) {
+    m_on_start = std::move(on_start);
+  }
+
+  void SetOnExit(std::function<void()> on_exit) {
+    m_on_exit = std::move(on_exit);
+  }
+
+  void Stop() { m_owner.Stop(); }
+
+  void Remove(unsigned int listener_uid) {
+    auto thr = m_owner.GetThread();
+    if (!thr) {
+      return;
+    }
+    thr->m_listeners.erase(listener_uid);
+  }
+
+  unsigned int CreatePoller() {
+    static_cast<Derived*>(this)->Start();
+    auto thr = m_owner.GetThread();
+    return thr->m_pollers.emplace_back(
+        std::make_shared<typename Thread::Poller>());
+  }
+
+  void RemovePoller(unsigned int poller_uid) {
+    auto thr = m_owner.GetThread();
+    if (!thr) {
+      return;
+    }
+
+    // Remove any listeners that are associated with this poller
+    for (size_t i = 0; i < thr->m_listeners.size(); ++i) {
+      if (thr->m_listeners[i].poller_uid == poller_uid) {
+        thr->m_listeners.erase(i);
+      }
+    }
+
+    // Wake up any blocked pollers
+    if (poller_uid >= thr->m_pollers.size()) {
+      return;
+    }
+    auto poller = thr->m_pollers[poller_uid];
+    if (!poller) {
+      return;
+    }
+    poller->Terminate();
+    thr->m_pollers.erase(poller_uid);
+  }
+
+  bool WaitForQueue(double timeout) {
+    auto thr = m_owner.GetThread();
+    if (!thr) {
+      return true;
+    }
+
+    auto& lock = thr.GetLock();
+    auto timeout_time = std::chrono::steady_clock::now() +
+                        std::chrono::duration<double>(timeout);
+    while (!thr->m_queue.empty()) {
+      if (!thr->m_active) {
+        return true;
+      }
+      if (timeout == 0) {
+        return false;
+      }
+      if (timeout < 0) {
+        thr->m_queue_empty.wait(lock);
+      } else {
+        auto cond_timed_out = thr->m_queue_empty.wait_until(lock, timeout_time);
+        if (cond_timed_out == std::cv_status::timeout) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  std::vector<typename Thread::UserInfo> Poll(unsigned int poller_uid) {
+    bool timed_out = false;
+    return Poll(poller_uid, -1, &timed_out);
+  }
+
+  std::vector<typename Thread::UserInfo> Poll(unsigned int poller_uid,
+                                              double timeout, bool* timed_out) {
+    std::vector<typename Thread::UserInfo> infos;
+    std::shared_ptr<typename Thread::Poller> poller;
+    {
+      auto thr = m_owner.GetThread();
+      if (!thr) {
+        return infos;
+      }
+      if (poller_uid > thr->m_pollers.size()) {
+        return infos;
+      }
+      poller = thr->m_pollers[poller_uid];
+      if (!poller) {
+        return infos;
+      }
+    }
+
+    std::unique_lock lock(poller->poll_mutex);
+    auto timeout_time = std::chrono::steady_clock::now() +
+                        std::chrono::duration<double>(timeout);
+    *timed_out = false;
+    while (poller->poll_queue.empty()) {
+      if (poller->terminating) {
+        return infos;
+      }
+      if (poller->canceling) {
+        // Note: this only works if there's a single thread calling this
+        // function for any particular poller, but that's the intended use.
+        poller->canceling = false;
+        return infos;
+      }
+      if (timeout == 0) {
+        *timed_out = true;
+        return infos;
+      }
+      if (timeout < 0) {
+        poller->poll_cond.wait(lock);
+      } else {
+        auto cond_timed_out = poller->poll_cond.wait_until(lock, timeout_time);
+        if (cond_timed_out == std::cv_status::timeout) {
+          *timed_out = true;
+          return infos;
+        }
+      }
+    }
+
+    while (!poller->poll_queue.empty()) {
+      infos.emplace_back(std::move(poller->poll_queue.front()));
+      poller->poll_queue.pop();
+    }
+    return infos;
+  }
+
+  void CancelPoll(unsigned int poller_uid) {
+    std::shared_ptr<typename Thread::Poller> poller;
+    {
+      auto thr = m_owner.GetThread();
+      if (!thr) {
+        return;
+      }
+      if (poller_uid > thr->m_pollers.size()) {
+        return;
+      }
+      poller = thr->m_pollers[poller_uid];
+      if (!poller) {
+        return;
+      }
+    }
+
+    {
+      std::scoped_lock lock(poller->poll_mutex);
+      poller->canceling = true;
+    }
+    poller->poll_cond.notify_one();
+  }
+
+ protected:
+  template <typename... Args>
+  void DoStart(Args&&... args) {
+    m_owner.Start(m_on_start, m_on_exit, std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  unsigned int DoAdd(Args&&... args) {
+    static_cast<Derived*>(this)->Start();
+    auto thr = m_owner.GetThread();
+    return thr->m_listeners.emplace_back(std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  void Send(unsigned int only_listener, Args&&... args) {
+    auto thr = m_owner.GetThread();
+    if (!thr || thr->m_listeners.empty()) {
+      return;
+    }
+    thr->m_queue.emplace(std::piecewise_construct,
+                         std::make_tuple(only_listener),
+                         std::forward_as_tuple(std::forward<Args>(args)...));
+    thr->m_cond.notify_one();
+  }
+
+  typename wpi::SafeThreadOwner<Thread>::Proxy GetThread() const {
+    return m_owner.GetThread();
+  }
+
+ private:
+  wpi::SafeThreadOwner<Thread> m_owner;
+
+  std::function<void()> m_on_start;
+  std::function<void()> m_on_exit;
+};
+
+}  // namespace wpi
+
+#endif  // WPIUTIL_WPI_CALLBACKMANAGER_H_
diff --git a/wpiutil/src/main/native/include/wpi/ConvertUTF.h b/wpiutil/src/main/native/include/wpi/ConvertUTF.h
index a9bdb60..aa113b1 100644
--- a/wpiutil/src/main/native/include/wpi/ConvertUTF.h
+++ b/wpiutil/src/main/native/include/wpi/ConvertUTF.h
@@ -90,11 +90,11 @@
 #ifndef LLVM_SUPPORT_CONVERTUTF_H
 #define LLVM_SUPPORT_CONVERTUTF_H
 
-#include "wpi/ArrayRef.h"
-#include "wpi/StringRef.h"
+#include "wpi/span.h"
 
 #include <cstddef>
 #include <string>
+#include <string_view>
 #include <system_error>
 
 // Wrap everything in namespace wpi so that programs can link with wpiutil and
@@ -102,6 +102,9 @@
 
 namespace wpi {
 
+template <typename T>
+class SmallVectorImpl;
+
 /* ---------------------------------------------------------------------
     The following 4 definitions are compiler-specific.
     The C standard does not guarantee that wchar_t has at least
@@ -228,14 +231,14 @@
  * Returns true if a blob of text starts with a UTF-16 big or little endian byte
  * order mark.
  */
-bool hasUTF16ByteOrderMark(ArrayRef<char> SrcBytes);
+bool hasUTF16ByteOrderMark(span<const char> SrcBytes);
 
 /**
  * Converts a UTF-16 string into a UTF-8 string.
  *
  * \returns true on success
  */
-bool convertUTF16ToUTF8String(ArrayRef<UTF16> SrcUTF16,
+bool convertUTF16ToUTF8String(span<const UTF16> SrcUTF16,
                               SmallVectorImpl<char> &DstUTF8);
 
 /**
@@ -243,15 +246,15 @@
  *
  * \returns true on success
  */
-bool convertUTF8ToUTF16String(StringRef SrcUTF8,
+bool convertUTF8ToUTF16String(std::string_view SrcUTF8,
                               SmallVectorImpl<UTF16> &DstUTF16);
 
 #if defined(_WIN32)
 namespace sys {
 namespace windows {
-std::error_code UTF8ToUTF16(StringRef utf8, SmallVectorImpl<wchar_t> &utf16);
+std::error_code UTF8ToUTF16(std::string_view utf8, SmallVectorImpl<wchar_t> &utf16);
 /// Convert to UTF16 from the current code page used in the system
-std::error_code CurCPToUTF16(StringRef utf8, SmallVectorImpl<wchar_t> &utf16);
+std::error_code CurCPToUTF16(std::string_view utf8, SmallVectorImpl<wchar_t> &utf16);
 std::error_code UTF16ToUTF8(const wchar_t *utf16, size_t utf16_len,
                             SmallVectorImpl<char> &utf8);
 /// Convert from UTF16 to the current code page used in the system
diff --git a/wpiutil/src/main/native/include/wpi/Demangle.h b/wpiutil/src/main/native/include/wpi/Demangle.h
index 5989ccc..03a7d3f 100644
--- a/wpiutil/src/main/native/include/wpi/Demangle.h
+++ b/wpiutil/src/main/native/include/wpi/Demangle.h
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2008-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_DEMANGLE_H_
 #define WPIUTIL_WPI_DEMANGLE_H_
 
 #include <string>
-
-#include "wpi/Twine.h"
+#include <string_view>
 
 namespace wpi {
 
@@ -20,7 +16,7 @@
  * @param mangledSymbol the mangled symbol.
  * @return The demangled symbol, or mangledSymbol if demangling fails.
  */
-std::string Demangle(const Twine& mangledSymbol);
+std::string Demangle(std::string_view mangledSymbol);
 
 }  // namespace wpi
 
diff --git a/wpiutil/src/main/native/include/wpi/DenseMapInfo.h b/wpiutil/src/main/native/include/wpi/DenseMapInfo.h
index 81ccdc7..b6525a3 100644
--- a/wpiutil/src/main/native/include/wpi/DenseMapInfo.h
+++ b/wpiutil/src/main/native/include/wpi/DenseMapInfo.h
@@ -14,13 +14,13 @@
 #ifndef WPIUTIL_WPI_DENSEMAPINFO_H
 #define WPIUTIL_WPI_DENSEMAPINFO_H
 
-#include "wpi/ArrayRef.h"
 #include "wpi/Hashing.h"
-#include "wpi/StringRef.h"
 #include "wpi/PointerLikeTypeTraits.h"
+#include "wpi/span.h"
 #include <cassert>
 #include <cstddef>
 #include <cstdint>
+#include <string_view>
 #include <utility>
 
 namespace wpi {
@@ -206,26 +206,26 @@
   }
 };
 
-// Provide DenseMapInfo for StringRefs.
-template <> struct DenseMapInfo<StringRef> {
-  static inline StringRef getEmptyKey() {
-    return StringRef(reinterpret_cast<const char *>(~static_cast<uintptr_t>(0)),
+// Provide DenseMapInfo for std::string_view.
+template <> struct DenseMapInfo<std::string_view> {
+  static inline std::string_view getEmptyKey() {
+    return std::string_view(reinterpret_cast<const char *>(~static_cast<uintptr_t>(0)),
                      0);
   }
 
-  static inline StringRef getTombstoneKey() {
-    return StringRef(reinterpret_cast<const char *>(~static_cast<uintptr_t>(1)),
+  static inline std::string_view getTombstoneKey() {
+    return std::string_view(reinterpret_cast<const char *>(~static_cast<uintptr_t>(1)),
                      0);
   }
 
-  static unsigned getHashValue(StringRef Val) {
+  static unsigned getHashValue(std::string_view Val) {
     assert(Val.data() != getEmptyKey().data() && "Cannot hash the empty key!");
     assert(Val.data() != getTombstoneKey().data() &&
            "Cannot hash the tombstone key!");
     return (unsigned)(hash_value(Val));
   }
 
-  static bool isEqual(StringRef LHS, StringRef RHS) {
+  static bool isEqual(std::string_view LHS, std::string_view RHS) {
     if (RHS.data() == getEmptyKey().data())
       return LHS.data() == getEmptyKey().data();
     if (RHS.data() == getTombstoneKey().data())
@@ -234,26 +234,26 @@
   }
 };
 
-// Provide DenseMapInfo for ArrayRefs.
-template <typename T> struct DenseMapInfo<ArrayRef<T>> {
-  static inline ArrayRef<T> getEmptyKey() {
-    return ArrayRef<T>(reinterpret_cast<const T *>(~static_cast<uintptr_t>(0)),
-                       size_t(0));
+// Provide DenseMapInfo for spans.
+template <typename T> struct DenseMapInfo<span<T>> {
+  static inline span<T> getEmptyKey() {
+    return span<T>(reinterpret_cast<const T *>(~static_cast<uintptr_t>(0)),
+                   size_t(0));
   }
 
-  static inline ArrayRef<T> getTombstoneKey() {
-    return ArrayRef<T>(reinterpret_cast<const T *>(~static_cast<uintptr_t>(1)),
-                       size_t(0));
+  static inline span<T> getTombstoneKey() {
+    return span<T>(reinterpret_cast<const T *>(~static_cast<uintptr_t>(1)),
+                   size_t(0));
   }
 
-  static unsigned getHashValue(ArrayRef<T> Val) {
+  static unsigned getHashValue(span<T> Val) {
     assert(Val.data() != getEmptyKey().data() && "Cannot hash the empty key!");
     assert(Val.data() != getTombstoneKey().data() &&
            "Cannot hash the tombstone key!");
     return (unsigned)(hash_value(Val));
   }
 
-  static bool isEqual(ArrayRef<T> LHS, ArrayRef<T> RHS) {
+  static bool isEqual(span<T> LHS, span<T> RHS) {
     if (RHS.data() == getEmptyKey().data())
       return LHS.data() == getEmptyKey().data();
     if (RHS.data() == getTombstoneKey().data())
diff --git a/wpiutil/src/main/native/include/wpi/DsClient.h b/wpiutil/src/main/native/include/wpi/DsClient.h
new file mode 100644
index 0000000..c24ba3b
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/DsClient.h
@@ -0,0 +1,55 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "wpi/Signal.h"
+
+namespace wpi {
+
+class Logger;
+
+namespace uv {
+class Loop;
+class Tcp;
+class Timer;
+}  // namespace uv
+
+class DsClient : public std::enable_shared_from_this<DsClient> {
+  struct private_init {};
+
+ public:
+  static std::shared_ptr<DsClient> Create(wpi::uv::Loop& loop,
+                                          wpi::Logger& logger) {
+    return std::make_shared<DsClient>(loop, logger, private_init{});
+  }
+
+  DsClient(wpi::uv::Loop& loop, wpi::Logger& logger, const private_init&);
+  ~DsClient();
+  DsClient(const DsClient&) = delete;
+  DsClient& operator=(const DsClient&) = delete;
+
+  void Close();
+
+  sig::Signal<std::string_view> setIp;
+  sig::Signal<> clearIp;
+
+ private:
+  void Connect();
+  void HandleIncoming(std::string_view in);
+  void ParseJson();
+
+  wpi::Logger& m_logger;
+
+  std::shared_ptr<wpi::uv::Tcp> m_tcp;
+  std::shared_ptr<wpi::uv::Timer> m_timer;
+
+  std::string m_json;
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/Endian.h b/wpiutil/src/main/native/include/wpi/Endian.h
index ee4a51d..b31cb2d 100644
--- a/wpiutil/src/main/native/include/wpi/Endian.h
+++ b/wpiutil/src/main/native/include/wpi/Endian.h
@@ -38,7 +38,7 @@
 
 namespace detail {
 
-/// ::value is either alignment, or alignof(T) if alignment is 0.
+// value is either alignment, or alignof(T) if alignment is 0.
 template<class T, int alignment>
 struct PickAlignment {
  enum { value = alignment == 0 ? alignof(T) : alignment };
diff --git a/wpiutil/src/main/native/include/wpi/Error.h b/wpiutil/src/main/native/include/wpi/Error.h
deleted file mode 100644
index 1821a49..0000000
--- a/wpiutil/src/main/native/include/wpi/Error.h
+++ /dev/null
@@ -1,1197 +0,0 @@
-//===- llvm/Support/Error.h - Recoverable error handling --------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file defines an API used to report recoverable errors.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_ERROR_H
-#define WPIUTIL_WPI_ERROR_H
-
-#include "wpi/STLExtras.h"
-#include "wpi/SmallVector.h"
-#include "wpi/StringExtras.h"
-#include "wpi/Twine.h"
-#include "wpi/AlignOf.h"
-#include "wpi/Compiler.h"
-#include "wpi/ErrorHandling.h"
-#include "wpi/ErrorOr.h"
-#include "wpi/Format.h"
-#include "wpi/raw_ostream.h"
-#include <algorithm>
-#include <cassert>
-#include <cstdint>
-#include <cstdlib>
-#include <functional>
-#include <memory>
-#include <new>
-#include <string>
-#include <system_error>
-#include <type_traits>
-#include <utility>
-#include <vector>
-
-namespace wpi {
-
-class ErrorSuccess;
-
-/// Base class for error info classes. Do not extend this directly: Extend
-/// the ErrorInfo template subclass instead.
-class ErrorInfoBase {
-public:
-  virtual ~ErrorInfoBase() = default;
-
-  /// Print an error message to an output stream.
-  virtual void log(raw_ostream &OS) const = 0;
-
-  /// Return the error message as a string.
-  virtual std::string message() const {
-    std::string Msg;
-    raw_string_ostream OS(Msg);
-    log(OS);
-    return OS.str();
-  }
-
-  /// Convert this error to a std::error_code.
-  ///
-  /// This is a temporary crutch to enable interaction with code still
-  /// using std::error_code. It will be removed in the future.
-  virtual std::error_code convertToErrorCode() const = 0;
-
-  // Returns the class ID for this type.
-  static const void *classID() { return &ID; }
-
-  // Returns the class ID for the dynamic type of this ErrorInfoBase instance.
-  virtual const void *dynamicClassID() const = 0;
-
-  // Check whether this instance is a subclass of the class identified by
-  // ClassID.
-  virtual bool isA(const void *const ClassID) const {
-    return ClassID == classID();
-  }
-
-  // Check whether this instance is a subclass of ErrorInfoT.
-  template <typename ErrorInfoT> bool isA() const {
-    return isA(ErrorInfoT::classID());
-  }
-
-private:
-  virtual void anchor();
-
-  static char ID;
-};
-
-/// Lightweight error class with error context and mandatory checking.
-///
-/// Instances of this class wrap a ErrorInfoBase pointer. Failure states
-/// are represented by setting the pointer to a ErrorInfoBase subclass
-/// instance containing information describing the failure. Success is
-/// represented by a null pointer value.
-///
-/// Instances of Error also contains a 'Checked' flag, which must be set
-/// before the destructor is called, otherwise the destructor will trigger a
-/// runtime error. This enforces at runtime the requirement that all Error
-/// instances be checked or returned to the caller.
-///
-/// There are two ways to set the checked flag, depending on what state the
-/// Error instance is in. For Error instances indicating success, it
-/// is sufficient to invoke the boolean conversion operator. E.g.:
-///
-///   @code{.cpp}
-///   Error foo(<...>);
-///
-///   if (auto E = foo(<...>))
-///     return E; // <- Return E if it is in the error state.
-///   // We have verified that E was in the success state. It can now be safely
-///   // destroyed.
-///   @endcode
-///
-/// A success value *can not* be dropped. For example, just calling 'foo(<...>)'
-/// without testing the return value will raise a runtime error, even if foo
-/// returns success.
-///
-/// For Error instances representing failure, you must use either the
-/// handleErrors or handleAllErrors function with a typed handler. E.g.:
-///
-///   @code{.cpp}
-///   class MyErrorInfo : public ErrorInfo<MyErrorInfo> {
-///     // Custom error info.
-///   };
-///
-///   Error foo(<...>) { return make_error<MyErrorInfo>(...); }
-///
-///   auto E = foo(<...>); // <- foo returns failure with MyErrorInfo.
-///   auto NewE =
-///     handleErrors(E,
-///       [](const MyErrorInfo &M) {
-///         // Deal with the error.
-///       },
-///       [](std::unique_ptr<OtherError> M) -> Error {
-///         if (canHandle(*M)) {
-///           // handle error.
-///           return Error::success();
-///         }
-///         // Couldn't handle this error instance. Pass it up the stack.
-///         return Error(std::move(M));
-///       );
-///   // Note - we must check or return NewE in case any of the handlers
-///   // returned a new error.
-///   @endcode
-///
-/// The handleAllErrors function is identical to handleErrors, except
-/// that it has a void return type, and requires all errors to be handled and
-/// no new errors be returned. It prevents errors (assuming they can all be
-/// handled) from having to be bubbled all the way to the top-level.
-///
-/// *All* Error instances must be checked before destruction, even if
-/// they're moved-assigned or constructed from Success values that have already
-/// been checked. This enforces checking through all levels of the call stack.
-class LLVM_NODISCARD Error {
-  // Both ErrorList and FileError need to be able to yank ErrorInfoBase
-  // pointers out of this class to add to the error list.
-  friend class ErrorList;
-  friend class FileError;
-
-  // handleErrors needs to be able to set the Checked flag.
-  template <typename... HandlerTs>
-  friend Error handleErrors(Error E, HandlerTs &&... Handlers);
-
-  // Expected<T> needs to be able to steal the payload when constructed from an
-  // error.
-  template <typename T> friend class Expected;
-
-protected:
-  /// Create a success value. Prefer using 'Error::success()' for readability
-  Error() {
-    setPtr(nullptr);
-    setChecked(false);
-  }
-
-public:
-  /// Create a success value.
-  static ErrorSuccess success();
-
-  // Errors are not copy-constructable.
-  Error(const Error &Other) = delete;
-
-  /// Move-construct an error value. The newly constructed error is considered
-  /// unchecked, even if the source error had been checked. The original error
-  /// becomes a checked Success value, regardless of its original state.
-  Error(Error &&Other) noexcept {
-    setChecked(true);
-    *this = std::move(Other);
-  }
-
-  /// Create an error value. Prefer using the 'make_error' function, but
-  /// this constructor can be useful when "re-throwing" errors from handlers.
-  Error(std::unique_ptr<ErrorInfoBase> Payload) {
-    setPtr(Payload.release());
-    setChecked(false);
-  }
-
-  // Errors are not copy-assignable.
-  Error &operator=(const Error &Other) = delete;
-
-  /// Move-assign an error value. The current error must represent success, you
-  /// you cannot overwrite an unhandled error. The current error is then
-  /// considered unchecked. The source error becomes a checked success value,
-  /// regardless of its original state.
-  Error &operator=(Error &&Other) noexcept {
-    // Don't allow overwriting of unchecked values.
-    assertIsChecked();
-    setPtr(Other.getPtr());
-
-    // This Error is unchecked, even if the source error was checked.
-    setChecked(false);
-
-    // Null out Other's payload and set its checked bit.
-    Other.setPtr(nullptr);
-    Other.setChecked(true);
-
-    return *this;
-  }
-
-  /// Destroy a Error. Fails with a call to abort() if the error is
-  /// unchecked.
-  ~Error() {
-    assertIsChecked();
-    delete getPtr();
-  }
-
-  /// Bool conversion. Returns true if this Error is in a failure state,
-  /// and false if it is in an accept state. If the error is in a Success state
-  /// it will be considered checked.
-  explicit operator bool() {
-    setChecked(getPtr() == nullptr);
-    return getPtr() != nullptr;
-  }
-
-  /// Check whether one error is a subclass of another.
-  template <typename ErrT> bool isA() const {
-    return getPtr() && getPtr()->isA(ErrT::classID());
-  }
-
-  /// Returns the dynamic class id of this error, or null if this is a success
-  /// value.
-  const void* dynamicClassID() const {
-    if (!getPtr())
-      return nullptr;
-    return getPtr()->dynamicClassID();
-  }
-
-private:
-  void assertIsChecked() {
-  }
-
-  ErrorInfoBase *getPtr() const {
-    return reinterpret_cast<ErrorInfoBase*>(
-             reinterpret_cast<uintptr_t>(Payload) &
-             ~static_cast<uintptr_t>(0x1));
-  }
-
-  void setPtr(ErrorInfoBase *EI) {
-    Payload = EI;
-  }
-
-  bool getChecked() const {
-    return true;
-  }
-
-  void setChecked(bool V) {
-    Payload = reinterpret_cast<ErrorInfoBase*>(
-                (reinterpret_cast<uintptr_t>(Payload) &
-                  ~static_cast<uintptr_t>(0x1)) |
-                  (V ? 0 : 1));
-  }
-
-  std::unique_ptr<ErrorInfoBase> takePayload() {
-    std::unique_ptr<ErrorInfoBase> Tmp(getPtr());
-    setPtr(nullptr);
-    setChecked(true);
-    return Tmp;
-  }
-
-  friend raw_ostream &operator<<(raw_ostream &OS, const Error &E) {
-    if (auto P = E.getPtr())
-      P->log(OS);
-    else
-      OS << "success";
-    return OS;
-  }
-
-  ErrorInfoBase *Payload = nullptr;
-};
-
-/// Subclass of Error for the sole purpose of identifying the success path in
-/// the type system. This allows to catch invalid conversion to Expected<T> at
-/// compile time.
-class ErrorSuccess final : public Error {};
-
-inline ErrorSuccess Error::success() { return ErrorSuccess(); }
-
-/// Make a Error instance representing failure using the given error info
-/// type.
-template <typename ErrT, typename... ArgTs> Error make_error(ArgTs &&... Args) {
-  return Error(std::make_unique<ErrT>(std::forward<ArgTs>(Args)...));
-}
-
-/// Base class for user error types. Users should declare their error types
-/// like:
-///
-/// class MyError : public ErrorInfo<MyError> {
-///   ....
-/// };
-///
-/// This class provides an implementation of the ErrorInfoBase::kind
-/// method, which is used by the Error RTTI system.
-template <typename ThisErrT, typename ParentErrT = ErrorInfoBase>
-class ErrorInfo : public ParentErrT {
-public:
-  using ParentErrT::ParentErrT; // inherit constructors
-
-  static const void *classID() { return &ThisErrT::ID; }
-
-  const void *dynamicClassID() const override { return &ThisErrT::ID; }
-
-  bool isA(const void *const ClassID) const override {
-    return ClassID == classID() || ParentErrT::isA(ClassID);
-  }
-};
-
-/// Special ErrorInfo subclass representing a list of ErrorInfos.
-/// Instances of this class are constructed by joinError.
-class ErrorList final : public ErrorInfo<ErrorList> {
-  // handleErrors needs to be able to iterate the payload list of an
-  // ErrorList.
-  template <typename... HandlerTs>
-  friend Error handleErrors(Error E, HandlerTs &&... Handlers);
-
-  // joinErrors is implemented in terms of join.
-  friend Error joinErrors(Error, Error);
-
-public:
-  void log(raw_ostream &OS) const override {
-    OS << "Multiple errors:\n";
-    for (auto &ErrPayload : Payloads) {
-      ErrPayload->log(OS);
-      OS << "\n";
-    }
-  }
-
-  std::error_code convertToErrorCode() const override;
-
-  // Used by ErrorInfo::classID.
-  static char ID;
-
-private:
-  ErrorList(std::unique_ptr<ErrorInfoBase> Payload1,
-            std::unique_ptr<ErrorInfoBase> Payload2) {
-    assert(!Payload1->isA<ErrorList>() && !Payload2->isA<ErrorList>() &&
-           "ErrorList constructor payloads should be singleton errors");
-    Payloads.push_back(std::move(Payload1));
-    Payloads.push_back(std::move(Payload2));
-  }
-
-  static Error join(Error E1, Error E2) {
-    if (!E1)
-      return E2;
-    if (!E2)
-      return E1;
-    if (E1.isA<ErrorList>()) {
-      auto &E1List = static_cast<ErrorList &>(*E1.getPtr());
-      if (E2.isA<ErrorList>()) {
-        auto E2Payload = E2.takePayload();
-        auto &E2List = static_cast<ErrorList &>(*E2Payload);
-        for (auto &Payload : E2List.Payloads)
-          E1List.Payloads.push_back(std::move(Payload));
-      } else
-        E1List.Payloads.push_back(E2.takePayload());
-
-      return E1;
-    }
-    if (E2.isA<ErrorList>()) {
-      auto &E2List = static_cast<ErrorList &>(*E2.getPtr());
-      E2List.Payloads.insert(E2List.Payloads.begin(), E1.takePayload());
-      return E2;
-    }
-    return Error(std::unique_ptr<ErrorList>(
-        new ErrorList(E1.takePayload(), E2.takePayload())));
-  }
-
-  std::vector<std::unique_ptr<ErrorInfoBase>> Payloads;
-};
-
-/// Concatenate errors. The resulting Error is unchecked, and contains the
-/// ErrorInfo(s), if any, contained in E1, followed by the
-/// ErrorInfo(s), if any, contained in E2.
-inline Error joinErrors(Error E1, Error E2) {
-  return ErrorList::join(std::move(E1), std::move(E2));
-}
-
-/// Tagged union holding either a T or a Error.
-///
-/// This class parallels ErrorOr, but replaces error_code with Error. Since
-/// Error cannot be copied, this class replaces getError() with
-/// takeError(). It also adds an bool errorIsA<ErrT>() method for testing the
-/// error class type.
-template <class T> class LLVM_NODISCARD Expected {
-  template <class T1> friend class ExpectedAsOutParameter;
-  template <class OtherT> friend class Expected;
-
-  static const bool isRef = std::is_reference<T>::value;
-
-  using wrap = std::reference_wrapper<typename std::remove_reference<T>::type>;
-
-  using error_type = std::unique_ptr<ErrorInfoBase>;
-
-public:
-  using storage_type = typename std::conditional<isRef, wrap, T>::type;
-  using value_type = T;
-
-private:
-  using reference = typename std::remove_reference<T>::type &;
-  using const_reference = const typename std::remove_reference<T>::type &;
-  using pointer = typename std::remove_reference<T>::type *;
-  using const_pointer = const typename std::remove_reference<T>::type *;
-
-public:
-  /// Create an Expected<T> error value from the given Error.
-  Expected(Error Err)
-      : HasError(true)
-  {
-    assert(Err && "Cannot create Expected<T> from Error success value.");
-    new (getErrorStorage()) error_type(Err.takePayload());
-  }
-
-  /// Forbid to convert from Error::success() implicitly, this avoids having
-  /// Expected<T> foo() { return Error::success(); } which compiles otherwise
-  /// but triggers the assertion above.
-  Expected(ErrorSuccess) = delete;
-
-  /// Create an Expected<T> success value from the given OtherT value, which
-  /// must be convertible to T.
-  template <typename OtherT>
-  Expected(OtherT &&Val,
-           typename std::enable_if<std::is_convertible<OtherT, T>::value>::type
-               * = nullptr)
-      : HasError(false)
-  {
-    new (getStorage()) storage_type(std::forward<OtherT>(Val));
-  }
-
-  /// Move construct an Expected<T> value.
-  Expected(Expected &&Other) { moveConstruct(std::move(Other)); }
-
-  /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
-  /// must be convertible to T.
-  template <class OtherT>
-  Expected(Expected<OtherT> &&Other,
-           typename std::enable_if<std::is_convertible<OtherT, T>::value>::type
-               * = nullptr) {
-    moveConstruct(std::move(Other));
-  }
-
-  /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
-  /// isn't convertible to T.
-  template <class OtherT>
-  explicit Expected(
-      Expected<OtherT> &&Other,
-      typename std::enable_if<!std::is_convertible<OtherT, T>::value>::type * =
-          nullptr) {
-    moveConstruct(std::move(Other));
-  }
-
-  /// Move-assign from another Expected<T>.
-  Expected &operator=(Expected &&Other) {
-    moveAssign(std::move(Other));
-    return *this;
-  }
-
-  /// Destroy an Expected<T>.
-  ~Expected() {
-    assertIsChecked();
-    if (!HasError)
-      getStorage()->~storage_type();
-    else
-      getErrorStorage()->~error_type();
-  }
-
-  /// Return false if there is an error.
-  explicit operator bool() {
-    return !HasError;
-  }
-
-  /// Returns a reference to the stored T value.
-  reference get() {
-    assertIsChecked();
-    return *getStorage();
-  }
-
-  /// Returns a const reference to the stored T value.
-  const_reference get() const {
-    assertIsChecked();
-    return const_cast<Expected<T> *>(this)->get();
-  }
-
-  /// Check that this Expected<T> is an error of type ErrT.
-  template <typename ErrT> bool errorIsA() const {
-    return HasError && (*getErrorStorage())->template isA<ErrT>();
-  }
-
-  /// Take ownership of the stored error.
-  /// After calling this the Expected<T> is in an indeterminate state that can
-  /// only be safely destructed. No further calls (beside the destructor) should
-  /// be made on the Expected<T> vaule.
-  Error takeError() {
-    return HasError ? Error(std::move(*getErrorStorage())) : Error::success();
-  }
-
-  /// Returns a pointer to the stored T value.
-  pointer operator->() {
-    assertIsChecked();
-    return toPointer(getStorage());
-  }
-
-  /// Returns a const pointer to the stored T value.
-  const_pointer operator->() const {
-    assertIsChecked();
-    return toPointer(getStorage());
-  }
-
-  /// Returns a reference to the stored T value.
-  reference operator*() {
-    assertIsChecked();
-    return *getStorage();
-  }
-
-  /// Returns a const reference to the stored T value.
-  const_reference operator*() const {
-    assertIsChecked();
-    return *getStorage();
-  }
-
-private:
-  template <class T1>
-  static bool compareThisIfSameType(const T1 &a, const T1 &b) {
-    return &a == &b;
-  }
-
-  template <class T1, class T2>
-  static bool compareThisIfSameType(const T1 &a, const T2 &b) {
-    return false;
-  }
-
-  template <class OtherT> void moveConstruct(Expected<OtherT> &&Other) {
-    HasError = Other.HasError;
-
-    if (!HasError)
-      new (getStorage()) storage_type(std::move(*Other.getStorage()));
-    else
-      new (getErrorStorage()) error_type(std::move(*Other.getErrorStorage()));
-  }
-
-  template <class OtherT> void moveAssign(Expected<OtherT> &&Other) {
-    assertIsChecked();
-
-    if (compareThisIfSameType(*this, Other))
-      return;
-
-    this->~Expected();
-    new (this) Expected(std::move(Other));
-  }
-
-  pointer toPointer(pointer Val) { return Val; }
-
-  const_pointer toPointer(const_pointer Val) const { return Val; }
-
-  pointer toPointer(wrap *Val) { return &Val->get(); }
-
-  const_pointer toPointer(const wrap *Val) const { return &Val->get(); }
-
-  storage_type *getStorage() {
-    assert(!HasError && "Cannot get value when an error exists!");
-    return reinterpret_cast<storage_type *>(TStorage.buffer);
-  }
-
-  const storage_type *getStorage() const {
-    assert(!HasError && "Cannot get value when an error exists!");
-    return reinterpret_cast<const storage_type *>(TStorage.buffer);
-  }
-
-  error_type *getErrorStorage() {
-    assert(HasError && "Cannot get error when a value exists!");
-    return reinterpret_cast<error_type *>(ErrorStorage.buffer);
-  }
-
-  const error_type *getErrorStorage() const {
-    assert(HasError && "Cannot get error when a value exists!");
-    return reinterpret_cast<const error_type *>(ErrorStorage.buffer);
-  }
-
-  // Used by ExpectedAsOutParameter to reset the checked flag.
-  void setUnchecked() {
-  }
-
-  void assertIsChecked() {
-  }
-
-  union {
-    AlignedCharArrayUnion<storage_type> TStorage;
-    AlignedCharArrayUnion<error_type> ErrorStorage;
-  };
-  bool HasError : 1;
-};
-
-/// Report a serious error, calling any installed error handler. See
-/// ErrorHandling.h.
-LLVM_ATTRIBUTE_NORETURN void report_fatal_error(Error Err,
-                                                bool gen_crash_diag = true);
-
-/// Report a fatal error if Err is a failure value.
-///
-/// This function can be used to wrap calls to fallible functions ONLY when it
-/// is known that the Error will always be a success value. E.g.
-///
-///   @code{.cpp}
-///   // foo only attempts the fallible operation if DoFallibleOperation is
-///   // true. If DoFallibleOperation is false then foo always returns
-///   // Error::success().
-///   Error foo(bool DoFallibleOperation);
-///
-///   cantFail(foo(false));
-///   @endcode
-inline void cantFail(Error Err, const char *Msg = nullptr) {
-  if (Err) {
-    if (!Msg)
-      Msg = "Failure value returned from cantFail wrapped call";
-    wpi_unreachable(Msg);
-  }
-}
-
-/// Report a fatal error if ValOrErr is a failure value, otherwise unwraps and
-/// returns the contained value.
-///
-/// This function can be used to wrap calls to fallible functions ONLY when it
-/// is known that the Error will always be a success value. E.g.
-///
-///   @code{.cpp}
-///   // foo only attempts the fallible operation if DoFallibleOperation is
-///   // true. If DoFallibleOperation is false then foo always returns an int.
-///   Expected<int> foo(bool DoFallibleOperation);
-///
-///   int X = cantFail(foo(false));
-///   @endcode
-template <typename T>
-T cantFail(Expected<T> ValOrErr, const char *Msg = nullptr) {
-  if (ValOrErr)
-    return std::move(*ValOrErr);
-  else {
-    if (!Msg)
-      Msg = "Failure value returned from cantFail wrapped call";
-    wpi_unreachable(Msg);
-  }
-}
-
-/// Report a fatal error if ValOrErr is a failure value, otherwise unwraps and
-/// returns the contained reference.
-///
-/// This function can be used to wrap calls to fallible functions ONLY when it
-/// is known that the Error will always be a success value. E.g.
-///
-///   @code{.cpp}
-///   // foo only attempts the fallible operation if DoFallibleOperation is
-///   // true. If DoFallibleOperation is false then foo always returns a Bar&.
-///   Expected<Bar&> foo(bool DoFallibleOperation);
-///
-///   Bar &X = cantFail(foo(false));
-///   @endcode
-template <typename T>
-T& cantFail(Expected<T&> ValOrErr, const char *Msg = nullptr) {
-  if (ValOrErr)
-    return *ValOrErr;
-  else {
-    if (!Msg)
-      Msg = "Failure value returned from cantFail wrapped call";
-    wpi_unreachable(Msg);
-  }
-}
-
-/// Helper for testing applicability of, and applying, handlers for
-/// ErrorInfo types.
-template <typename HandlerT>
-class ErrorHandlerTraits
-    : public ErrorHandlerTraits<decltype(
-          &std::remove_reference<HandlerT>::type::operator())> {};
-
-// Specialization functions of the form 'Error (const ErrT&)'.
-template <typename ErrT> class ErrorHandlerTraits<Error (&)(ErrT &)> {
-public:
-  static bool appliesTo(const ErrorInfoBase &E) {
-    return E.template isA<ErrT>();
-  }
-
-  template <typename HandlerT>
-  static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
-    assert(appliesTo(*E) && "Applying incorrect handler");
-    return H(static_cast<ErrT &>(*E));
-  }
-};
-
-// Specialization functions of the form 'void (const ErrT&)'.
-template <typename ErrT> class ErrorHandlerTraits<void (&)(ErrT &)> {
-public:
-  static bool appliesTo(const ErrorInfoBase &E) {
-    return E.template isA<ErrT>();
-  }
-
-  template <typename HandlerT>
-  static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
-    assert(appliesTo(*E) && "Applying incorrect handler");
-    H(static_cast<ErrT &>(*E));
-    return Error::success();
-  }
-};
-
-/// Specialization for functions of the form 'Error (std::unique_ptr<ErrT>)'.
-template <typename ErrT>
-class ErrorHandlerTraits<Error (&)(std::unique_ptr<ErrT>)> {
-public:
-  static bool appliesTo(const ErrorInfoBase &E) {
-    return E.template isA<ErrT>();
-  }
-
-  template <typename HandlerT>
-  static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
-    assert(appliesTo(*E) && "Applying incorrect handler");
-    std::unique_ptr<ErrT> SubE(static_cast<ErrT *>(E.release()));
-    return H(std::move(SubE));
-  }
-};
-
-/// Specialization for functions of the form 'void (std::unique_ptr<ErrT>)'.
-template <typename ErrT>
-class ErrorHandlerTraits<void (&)(std::unique_ptr<ErrT>)> {
-public:
-  static bool appliesTo(const ErrorInfoBase &E) {
-    return E.template isA<ErrT>();
-  }
-
-  template <typename HandlerT>
-  static Error apply(HandlerT &&H, std::unique_ptr<ErrorInfoBase> E) {
-    assert(appliesTo(*E) && "Applying incorrect handler");
-    std::unique_ptr<ErrT> SubE(static_cast<ErrT *>(E.release()));
-    H(std::move(SubE));
-    return Error::success();
-  }
-};
-
-// Specialization for member functions of the form 'RetT (const ErrT&)'.
-template <typename C, typename RetT, typename ErrT>
-class ErrorHandlerTraits<RetT (C::*)(ErrT &)>
-    : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
-
-// Specialization for member functions of the form 'RetT (const ErrT&) const'.
-template <typename C, typename RetT, typename ErrT>
-class ErrorHandlerTraits<RetT (C::*)(ErrT &) const>
-    : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
-
-// Specialization for member functions of the form 'RetT (const ErrT&)'.
-template <typename C, typename RetT, typename ErrT>
-class ErrorHandlerTraits<RetT (C::*)(const ErrT &)>
-    : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
-
-// Specialization for member functions of the form 'RetT (const ErrT&) const'.
-template <typename C, typename RetT, typename ErrT>
-class ErrorHandlerTraits<RetT (C::*)(const ErrT &) const>
-    : public ErrorHandlerTraits<RetT (&)(ErrT &)> {};
-
-/// Specialization for member functions of the form
-/// 'RetT (std::unique_ptr<ErrT>)'.
-template <typename C, typename RetT, typename ErrT>
-class ErrorHandlerTraits<RetT (C::*)(std::unique_ptr<ErrT>)>
-    : public ErrorHandlerTraits<RetT (&)(std::unique_ptr<ErrT>)> {};
-
-/// Specialization for member functions of the form
-/// 'RetT (std::unique_ptr<ErrT>) const'.
-template <typename C, typename RetT, typename ErrT>
-class ErrorHandlerTraits<RetT (C::*)(std::unique_ptr<ErrT>) const>
-    : public ErrorHandlerTraits<RetT (&)(std::unique_ptr<ErrT>)> {};
-
-inline Error handleErrorImpl(std::unique_ptr<ErrorInfoBase> Payload) {
-  return Error(std::move(Payload));
-}
-
-template <typename HandlerT, typename... HandlerTs>
-Error handleErrorImpl(std::unique_ptr<ErrorInfoBase> Payload,
-                      HandlerT &&Handler, HandlerTs &&... Handlers) {
-  if (ErrorHandlerTraits<HandlerT>::appliesTo(*Payload))
-    return ErrorHandlerTraits<HandlerT>::apply(std::forward<HandlerT>(Handler),
-                                               std::move(Payload));
-  return handleErrorImpl(std::move(Payload),
-                         std::forward<HandlerTs>(Handlers)...);
-}
-
-/// Pass the ErrorInfo(s) contained in E to their respective handlers. Any
-/// unhandled errors (or Errors returned by handlers) are re-concatenated and
-/// returned.
-/// Because this function returns an error, its result must also be checked
-/// or returned. If you intend to handle all errors use handleAllErrors
-/// (which returns void, and will abort() on unhandled errors) instead.
-template <typename... HandlerTs>
-Error handleErrors(Error E, HandlerTs &&... Hs) {
-  if (!E)
-    return Error::success();
-
-  std::unique_ptr<ErrorInfoBase> Payload = E.takePayload();
-
-  if (Payload->isA<ErrorList>()) {
-    ErrorList &List = static_cast<ErrorList &>(*Payload);
-    Error R;
-    for (auto &P : List.Payloads)
-      R = ErrorList::join(
-          std::move(R),
-          handleErrorImpl(std::move(P), std::forward<HandlerTs>(Hs)...));
-    return R;
-  }
-
-  return handleErrorImpl(std::move(Payload), std::forward<HandlerTs>(Hs)...);
-}
-
-/// Behaves the same as handleErrors, except that by contract all errors
-/// *must* be handled by the given handlers (i.e. there must be no remaining
-/// errors after running the handlers, or wpi_unreachable is called).
-template <typename... HandlerTs>
-void handleAllErrors(Error E, HandlerTs &&... Handlers) {
-  cantFail(handleErrors(std::move(E), std::forward<HandlerTs>(Handlers)...));
-}
-
-/// Check that E is a non-error, then drop it.
-/// If E is an error, wpi_unreachable will be called.
-inline void handleAllErrors(Error E) {
-  cantFail(std::move(E));
-}
-
-/// Handle any errors (if present) in an Expected<T>, then try a recovery path.
-///
-/// If the incoming value is a success value it is returned unmodified. If it
-/// is a failure value then it the contained error is passed to handleErrors.
-/// If handleErrors is able to handle the error then the RecoveryPath functor
-/// is called to supply the final result. If handleErrors is not able to
-/// handle all errors then the unhandled errors are returned.
-///
-/// This utility enables the follow pattern:
-///
-///   @code{.cpp}
-///   enum FooStrategy { Aggressive, Conservative };
-///   Expected<Foo> foo(FooStrategy S);
-///
-///   auto ResultOrErr =
-///     handleExpected(
-///       foo(Aggressive),
-///       []() { return foo(Conservative); },
-///       [](AggressiveStrategyError&) {
-///         // Implicitly conusme this - we'll recover by using a conservative
-///         // strategy.
-///       });
-///
-///   @endcode
-template <typename T, typename RecoveryFtor, typename... HandlerTs>
-Expected<T> handleExpected(Expected<T> ValOrErr, RecoveryFtor &&RecoveryPath,
-                           HandlerTs &&... Handlers) {
-  if (ValOrErr)
-    return ValOrErr;
-
-  if (auto Err = handleErrors(ValOrErr.takeError(),
-                              std::forward<HandlerTs>(Handlers)...))
-    return std::move(Err);
-
-  return RecoveryPath();
-}
-
-/// Log all errors (if any) in E to OS. If there are any errors, ErrorBanner
-/// will be printed before the first one is logged. A newline will be printed
-/// after each error.
-///
-/// This function is compatible with the helpers from Support/WithColor.h. You
-/// can pass any of them as the OS. Please consider using them instead of
-/// including 'error: ' in the ErrorBanner.
-///
-/// This is useful in the base level of your program to allow clean termination
-/// (allowing clean deallocation of resources, etc.), while reporting error
-/// information to the user.
-void logAllUnhandledErrors(Error E, raw_ostream &OS, Twine ErrorBanner = {});
-
-/// Write all error messages (if any) in E to a string. The newline character
-/// is used to separate error messages.
-inline std::string toString(Error E) {
-  SmallVector<std::string, 2> Errors;
-  handleAllErrors(std::move(E), [&Errors](const ErrorInfoBase &EI) {
-    Errors.push_back(EI.message());
-  });
-  return join(Errors.begin(), Errors.end(), "\n");
-}
-
-/// Consume a Error without doing anything. This method should be used
-/// only where an error can be considered a reasonable and expected return
-/// value.
-///
-/// Uses of this method are potentially indicative of design problems: If it's
-/// legitimate to do nothing while processing an "error", the error-producer
-/// might be more clearly refactored to return an Optional<T>.
-inline void consumeError(Error Err) {
-  handleAllErrors(std::move(Err), [](const ErrorInfoBase &) {});
-}
-
-/// Helper for converting an Error to a bool.
-///
-/// This method returns true if Err is in an error state, or false if it is
-/// in a success state.  Puts Err in a checked state in both cases (unlike
-/// Error::operator bool(), which only does this for success states).
-inline bool errorToBool(Error Err) {
-  bool IsError = static_cast<bool>(Err);
-  if (IsError)
-    consumeError(std::move(Err));
-  return IsError;
-}
-
-/// Helper for Errors used as out-parameters.
-///
-/// This helper is for use with the Error-as-out-parameter idiom, where an error
-/// is passed to a function or method by reference, rather than being returned.
-/// In such cases it is helpful to set the checked bit on entry to the function
-/// so that the error can be written to (unchecked Errors abort on assignment)
-/// and clear the checked bit on exit so that clients cannot accidentally forget
-/// to check the result. This helper performs these actions automatically using
-/// RAII:
-///
-///   @code{.cpp}
-///   Result foo(Error &Err) {
-///     ErrorAsOutParameter ErrAsOutParam(&Err); // 'Checked' flag set
-///     // <body of foo>
-///     // <- 'Checked' flag auto-cleared when ErrAsOutParam is destructed.
-///   }
-///   @endcode
-///
-/// ErrorAsOutParameter takes an Error* rather than Error& so that it can be
-/// used with optional Errors (Error pointers that are allowed to be null). If
-/// ErrorAsOutParameter took an Error reference, an instance would have to be
-/// created inside every condition that verified that Error was non-null. By
-/// taking an Error pointer we can just create one instance at the top of the
-/// function.
-class ErrorAsOutParameter {
-public:
-  ErrorAsOutParameter(Error *Err) : Err(Err) {
-    // Raise the checked bit if Err is success.
-    if (Err)
-      (void)!!*Err;
-  }
-
-  ~ErrorAsOutParameter() {
-    // Clear the checked bit.
-    if (Err && !*Err)
-      *Err = Error::success();
-  }
-
-private:
-  Error *Err;
-};
-
-/// Helper for Expected<T>s used as out-parameters.
-///
-/// See ErrorAsOutParameter.
-template <typename T>
-class ExpectedAsOutParameter {
-public:
-  ExpectedAsOutParameter(Expected<T> *ValOrErr)
-    : ValOrErr(ValOrErr) {
-    if (ValOrErr)
-      (void)!!*ValOrErr;
-  }
-
-  ~ExpectedAsOutParameter() {
-    if (ValOrErr)
-      ValOrErr->setUnchecked();
-  }
-
-private:
-  Expected<T> *ValOrErr;
-};
-
-/// This class wraps a std::error_code in a Error.
-///
-/// This is useful if you're writing an interface that returns a Error
-/// (or Expected) and you want to call code that still returns
-/// std::error_codes.
-class ECError : public ErrorInfo<ECError> {
-  friend Error errorCodeToError(std::error_code);
-
-  virtual void anchor() override;
-
-public:
-  void setErrorCode(std::error_code EC) { this->EC = EC; }
-  std::error_code convertToErrorCode() const override { return EC; }
-  void log(raw_ostream &OS) const override { OS << EC.message(); }
-
-  // Used by ErrorInfo::classID.
-  static char ID;
-
-protected:
-  ECError() = default;
-  ECError(std::error_code EC) : EC(EC) {}
-
-  std::error_code EC;
-};
-
-/// The value returned by this function can be returned from convertToErrorCode
-/// for Error values where no sensible translation to std::error_code exists.
-/// It should only be used in this situation, and should never be used where a
-/// sensible conversion to std::error_code is available, as attempts to convert
-/// to/from this error will result in a fatal error. (i.e. it is a programmatic
-///error to try to convert such a value).
-std::error_code inconvertibleErrorCode();
-
-/// Helper for converting an std::error_code to a Error.
-Error errorCodeToError(std::error_code EC);
-
-/// Helper for converting an ECError to a std::error_code.
-///
-/// This method requires that Err be Error() or an ECError, otherwise it
-/// will trigger a call to abort().
-std::error_code errorToErrorCode(Error Err);
-
-/// Convert an ErrorOr<T> to an Expected<T>.
-template <typename T> Expected<T> errorOrToExpected(ErrorOr<T> &&EO) {
-  if (auto EC = EO.getError())
-    return errorCodeToError(EC);
-  return std::move(*EO);
-}
-
-/// Convert an Expected<T> to an ErrorOr<T>.
-template <typename T> ErrorOr<T> expectedToErrorOr(Expected<T> &&E) {
-  if (auto Err = E.takeError())
-    return errorToErrorCode(std::move(Err));
-  return std::move(*E);
-}
-
-/// This class wraps a string in an Error.
-///
-/// StringError is useful in cases where the client is not expected to be able
-/// to consume the specific error message programmatically (for example, if the
-/// error message is to be presented to the user).
-///
-/// StringError can also be used when additional information is to be printed
-/// along with a error_code message. Depending on the constructor called, this
-/// class can either display:
-///    1. the error_code message (ECError behavior)
-///    2. a string
-///    3. the error_code message and a string
-///
-/// These behaviors are useful when subtyping is required; for example, when a
-/// specific library needs an explicit error type. In the example below,
-/// PDBError is derived from StringError:
-///
-///   @code{.cpp}
-///   Expected<int> foo() {
-///      return wpi::make_error<PDBError>(pdb_error_code::dia_failed_loading,
-///                                       "Additional information");
-///   }
-///   @endcode
-///
-class StringError : public ErrorInfo<StringError> {
-public:
-  static char ID;
-
-  // Prints EC + S and converts to EC
-  StringError(std::error_code EC, const Twine &S = Twine());
-
-  // Prints S and converts to EC
-  StringError(const Twine &S, std::error_code EC);
-
-  void log(raw_ostream &OS) const override;
-  std::error_code convertToErrorCode() const override;
-
-  const std::string &getMessage() const { return Msg; }
-
-private:
-  std::string Msg;
-  std::error_code EC;
-  const bool PrintMsgOnly = false;
-};
-
-/// Create formatted StringError object.
-template <typename... Ts>
-Error createStringError(std::error_code EC, char const *Fmt,
-                        const Ts &... Vals) {
-  std::string Buffer;
-  raw_string_ostream Stream(Buffer);
-  Stream << format(Fmt, Vals...);
-  return make_error<StringError>(Stream.str(), EC);
-}
-
-Error createStringError(std::error_code EC, char const *Msg);
-
-/// This class wraps a filename and another Error.
-///
-/// In some cases, an error needs to live along a 'source' name, in order to
-/// show more detailed information to the user.
-class FileError final : public ErrorInfo<FileError> {
-
-  friend Error createFileError(std::string, Error);
-
-public:
-  void log(raw_ostream &OS) const override {
-    assert(Err && !FileName.empty() && "Trying to log after takeError().");
-    OS << "'" << FileName << "': ";
-    Err->log(OS);
-  }
-
-  Error takeError() { return Error(std::move(Err)); }
-
-  std::error_code convertToErrorCode() const override;
-
-  // Used by ErrorInfo::classID.
-  static char ID;
-
-private:
-  FileError(std::string F, std::unique_ptr<ErrorInfoBase> E) {
-    assert(E && "Cannot create FileError from Error success value.");
-    assert(!F.empty() &&
-           "The file name provided to FileError must not be empty.");
-    FileName = F;
-    Err = std::move(E);
-  }
-
-  static Error build(std::string F, Error E) {
-    return Error(std::unique_ptr<FileError>(new FileError(F, E.takePayload())));
-  }
-
-  std::string FileName;
-  std::unique_ptr<ErrorInfoBase> Err;
-};
-
-/// Concatenate a source file path and/or name with an Error. The resulting
-/// Error is unchecked.
-inline Error createFileError(std::string F, Error E) {
-  return FileError::build(F, std::move(E));
-}
-
-Error createFileError(std::string F, ErrorSuccess) = delete;
-
-/// Helper for check-and-exit error handling.
-///
-/// For tool use only. NOT FOR USE IN LIBRARY CODE.
-///
-class ExitOnError {
-public:
-  /// Create an error on exit helper.
-  ExitOnError(std::string Banner = "", int DefaultErrorExitCode = 1)
-      : Banner(std::move(Banner)),
-        GetExitCode([=](const Error &) { return DefaultErrorExitCode; }) {}
-
-  /// Set the banner string for any errors caught by operator().
-  void setBanner(std::string Banner) { this->Banner = std::move(Banner); }
-
-  /// Set the exit-code mapper function.
-  void setExitCodeMapper(std::function<int(const Error &)> GetExitCode) {
-    this->GetExitCode = std::move(GetExitCode);
-  }
-
-  /// Check Err. If it's in a failure state log the error(s) and exit.
-  void operator()(Error Err) const { checkError(std::move(Err)); }
-
-  /// Check E. If it's in a success state then return the contained value. If
-  /// it's in a failure state log the error(s) and exit.
-  template <typename T> T operator()(Expected<T> &&E) const {
-    checkError(E.takeError());
-    return std::move(*E);
-  }
-
-  /// Check E. If it's in a success state then return the contained reference. If
-  /// it's in a failure state log the error(s) and exit.
-  template <typename T> T& operator()(Expected<T&> &&E) const {
-    checkError(E.takeError());
-    return *E;
-  }
-
-private:
-  void checkError(Error Err) const {
-    if (Err) {
-      int ExitCode = GetExitCode(Err);
-      logAllUnhandledErrors(std::move(Err), errs(), Banner);
-      exit(ExitCode);
-    }
-  }
-
-  std::string Banner;
-  std::function<int(const Error &)> GetExitCode;
-};
-
-} // end namespace wpi
-
-#endif // WPIUTIL_WPI_ERROR_H
diff --git a/wpiutil/src/main/native/include/wpi/ErrorHandling.h b/wpiutil/src/main/native/include/wpi/ErrorHandling.h
index 5c89c5e..da9af4b 100644
--- a/wpiutil/src/main/native/include/wpi/ErrorHandling.h
+++ b/wpiutil/src/main/native/include/wpi/ErrorHandling.h
@@ -17,11 +17,9 @@
 
 #include "wpi/Compiler.h"
 #include <string>
+#include <string_view>
 
 namespace wpi {
-class StringRef;
-  class Twine;
-
   /// An error handler callback.
   typedef void (*fatal_error_handler_t)(void *user_data,
                                         const std::string& reason,
@@ -73,9 +71,7 @@
                                                 bool gen_crash_diag = true);
 LLVM_ATTRIBUTE_NORETURN void report_fatal_error(const std::string &reason,
                                                 bool gen_crash_diag = true);
-LLVM_ATTRIBUTE_NORETURN void report_fatal_error(StringRef reason,
-                                                bool gen_crash_diag = true);
-LLVM_ATTRIBUTE_NORETURN void report_fatal_error(const Twine &reason,
+LLVM_ATTRIBUTE_NORETURN void report_fatal_error(std::string_view reason,
                                                 bool gen_crash_diag = true);
 
 /// Installs a new bad alloc error handler that should be used whenever a
diff --git a/wpiutil/src/main/native/include/wpi/ErrorOr.h b/wpiutil/src/main/native/include/wpi/ErrorOr.h
deleted file mode 100644
index e1803fd..0000000
--- a/wpiutil/src/main/native/include/wpi/ErrorOr.h
+++ /dev/null
@@ -1,279 +0,0 @@
-//===- llvm/Support/ErrorOr.h - Error Smart Pointer -------------*- C++ -*-===//
-//
-//                             The LLVM Linker
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-///
-/// Provides ErrorOr<T> smart pointer.
-///
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_ERROROR_H
-#define WPIUTIL_WPI_ERROROR_H
-
-#include "wpi/AlignOf.h"
-#include <cassert>
-#include <system_error>
-#include <type_traits>
-#include <utility>
-
-namespace wpi {
-
-/// Represents either an error or a value T.
-///
-/// ErrorOr<T> is a pointer-like class that represents the result of an
-/// operation. The result is either an error, or a value of type T. This is
-/// designed to emulate the usage of returning a pointer where nullptr indicates
-/// failure. However instead of just knowing that the operation failed, we also
-/// have an error_code and optional user data that describes why it failed.
-///
-/// It is used like the following.
-/// \code
-///   ErrorOr<Buffer> getBuffer();
-///
-///   auto buffer = getBuffer();
-///   if (error_code ec = buffer.getError())
-///     return ec;
-///   buffer->write("adena");
-/// \endcode
-///
-///
-/// Implicit conversion to bool returns true if there is a usable value. The
-/// unary * and -> operators provide pointer like access to the value. Accessing
-/// the value when there is an error has undefined behavior.
-///
-/// When T is a reference type the behavior is slightly different. The reference
-/// is held in a std::reference_wrapper<std::remove_reference<T>::type>, and
-/// there is special handling to make operator -> work as if T was not a
-/// reference.
-///
-/// T cannot be a rvalue reference.
-template<class T>
-class ErrorOr {
-  template <class OtherT> friend class ErrorOr;
-
-  static const bool isRef = std::is_reference<T>::value;
-
-  using wrap = std::reference_wrapper<typename std::remove_reference<T>::type>;
-
-public:
-  using storage_type = typename std::conditional<isRef, wrap, T>::type;
-
-private:
-  using reference = typename std::remove_reference<T>::type &;
-  using const_reference = const typename std::remove_reference<T>::type &;
-  using pointer = typename std::remove_reference<T>::type *;
-  using const_pointer = const typename std::remove_reference<T>::type *;
-
-public:
-  template <class E>
-  ErrorOr(E ErrorCode,
-          typename std::enable_if<std::is_error_code_enum<E>::value ||
-                                      std::is_error_condition_enum<E>::value,
-                                  void *>::type = nullptr)
-      : HasError(true) {
-    new (getErrorStorage()) std::error_code(make_error_code(ErrorCode));
-  }
-
-  ErrorOr(std::error_code EC) : HasError(true) {
-    new (getErrorStorage()) std::error_code(EC);
-  }
-
-  template <class OtherT>
-  ErrorOr(OtherT &&Val,
-          typename std::enable_if<std::is_convertible<OtherT, T>::value>::type
-              * = nullptr)
-      : HasError(false) {
-    new (getStorage()) storage_type(std::forward<OtherT>(Val));
-  }
-
-  ErrorOr(const ErrorOr &Other) {
-    copyConstruct(Other);
-  }
-
-  template <class OtherT>
-  ErrorOr(
-      const ErrorOr<OtherT> &Other,
-      typename std::enable_if<std::is_convertible<OtherT, T>::value>::type * =
-          nullptr) {
-    copyConstruct(Other);
-  }
-
-  template <class OtherT>
-  explicit ErrorOr(
-      const ErrorOr<OtherT> &Other,
-      typename std::enable_if<
-          !std::is_convertible<OtherT, const T &>::value>::type * = nullptr) {
-    copyConstruct(Other);
-  }
-
-  ErrorOr(ErrorOr &&Other) {
-    moveConstruct(std::move(Other));
-  }
-
-  template <class OtherT>
-  ErrorOr(
-      ErrorOr<OtherT> &&Other,
-      typename std::enable_if<std::is_convertible<OtherT, T>::value>::type * =
-          nullptr) {
-    moveConstruct(std::move(Other));
-  }
-
-  // This might eventually need SFINAE but it's more complex than is_convertible
-  // & I'm too lazy to write it right now.
-  template <class OtherT>
-  explicit ErrorOr(
-      ErrorOr<OtherT> &&Other,
-      typename std::enable_if<!std::is_convertible<OtherT, T>::value>::type * =
-          nullptr) {
-    moveConstruct(std::move(Other));
-  }
-
-  ErrorOr &operator=(const ErrorOr &Other) {
-    copyAssign(Other);
-    return *this;
-  }
-
-  ErrorOr &operator=(ErrorOr &&Other) {
-    moveAssign(std::move(Other));
-    return *this;
-  }
-
-  ~ErrorOr() {
-    if (!HasError)
-      getStorage()->~storage_type();
-  }
-
-  /// Return false if there is an error.
-  explicit operator bool() const {
-    return !HasError;
-  }
-
-  reference get() { return *getStorage(); }
-  const_reference get() const { return const_cast<ErrorOr<T> *>(this)->get(); }
-
-  std::error_code getError() const {
-    return HasError ? *getErrorStorage() : std::error_code();
-  }
-
-  pointer operator ->() {
-    return toPointer(getStorage());
-  }
-
-  const_pointer operator->() const { return toPointer(getStorage()); }
-
-  reference operator *() {
-    return *getStorage();
-  }
-
-  const_reference operator*() const { return *getStorage(); }
-
-private:
-  template <class OtherT>
-  void copyConstruct(const ErrorOr<OtherT> &Other) {
-    if (!Other.HasError) {
-      // Get the other value.
-      HasError = false;
-      new (getStorage()) storage_type(*Other.getStorage());
-    } else {
-      // Get other's error.
-      HasError = true;
-      new (getErrorStorage()) std::error_code(Other.getError());
-    }
-  }
-
-  template <class T1>
-  static bool compareThisIfSameType(const T1 &a, const T1 &b) {
-    return &a == &b;
-  }
-
-  template <class T1, class T2>
-  static bool compareThisIfSameType(const T1 &a, const T2 &b) {
-    return false;
-  }
-
-  template <class OtherT>
-  void copyAssign(const ErrorOr<OtherT> &Other) {
-    if (compareThisIfSameType(*this, Other))
-      return;
-
-    this->~ErrorOr();
-    new (this) ErrorOr(Other);
-  }
-
-  template <class OtherT>
-  void moveConstruct(ErrorOr<OtherT> &&Other) {
-    if (!Other.HasError) {
-      // Get the other value.
-      HasError = false;
-      new (getStorage()) storage_type(std::move(*Other.getStorage()));
-    } else {
-      // Get other's error.
-      HasError = true;
-      new (getErrorStorage()) std::error_code(Other.getError());
-    }
-  }
-
-  template <class OtherT>
-  void moveAssign(ErrorOr<OtherT> &&Other) {
-    if (compareThisIfSameType(*this, Other))
-      return;
-
-    this->~ErrorOr();
-    new (this) ErrorOr(std::move(Other));
-  }
-
-  pointer toPointer(pointer Val) {
-    return Val;
-  }
-
-  const_pointer toPointer(const_pointer Val) const { return Val; }
-
-  pointer toPointer(wrap *Val) {
-    return &Val->get();
-  }
-
-  const_pointer toPointer(const wrap *Val) const { return &Val->get(); }
-
-  storage_type *getStorage() {
-    assert(!HasError && "Cannot get value when an error exists!");
-    return reinterpret_cast<storage_type*>(TStorage.buffer);
-  }
-
-  const storage_type *getStorage() const {
-    assert(!HasError && "Cannot get value when an error exists!");
-    return reinterpret_cast<const storage_type*>(TStorage.buffer);
-  }
-
-  std::error_code *getErrorStorage() {
-    assert(HasError && "Cannot get error when a value exists!");
-    return reinterpret_cast<std::error_code *>(ErrorStorage.buffer);
-  }
-
-  const std::error_code *getErrorStorage() const {
-    return const_cast<ErrorOr<T> *>(this)->getErrorStorage();
-  }
-
-  union {
-    AlignedCharArrayUnion<storage_type> TStorage;
-    AlignedCharArrayUnion<std::error_code> ErrorStorage;
-  };
-  bool HasError : 1;
-};
-
-template <class T, class E>
-typename std::enable_if<std::is_error_code_enum<E>::value ||
-                            std::is_error_condition_enum<E>::value,
-                        bool>::type
-operator==(const ErrorOr<T> &Err, E Code) {
-  return Err.getError() == Code;
-}
-
-} // end namespace wpi
-
-#endif // LLVM_SUPPORT_ERROROR_H
diff --git a/wpiutil/src/main/native/include/wpi/EventLoopRunner.h b/wpiutil/src/main/native/include/wpi/EventLoopRunner.h
index 8150091..5d35d40 100644
--- a/wpiutil/src/main/native/include/wpi/EventLoopRunner.h
+++ b/wpiutil/src/main/native/include/wpi/EventLoopRunner.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_EVENTLOOPRUNNER_H_
 #define WPIUTIL_WPI_EVENTLOOPRUNNER_H_
diff --git a/wpiutil/src/main/native/include/wpi/FileSystem.h b/wpiutil/src/main/native/include/wpi/FileSystem.h
deleted file mode 100644
index 668eea6..0000000
--- a/wpiutil/src/main/native/include/wpi/FileSystem.h
+++ /dev/null
@@ -1,1023 +0,0 @@
-//===- llvm/Support/FileSystem.h - File System OS Concept -------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file declares the wpi::sys::fs namespace. It is designed after
-// TR2/boost filesystem (v3), but modified to remove exception handling and the
-// path class.
-//
-// All functions return an error_code and their actual work via the last out
-// argument. The out argument is defined if and only if errc::success is
-// returned. A function may return any error code in the generic or system
-// category. However, they shall be equivalent to any error conditions listed
-// in each functions respective documentation if the condition applies. [ note:
-// this does not guarantee that error_code will be in the set of explicitly
-// listed codes, but it does guarantee that if any of the explicitly listed
-// errors occur, the correct error_code will be used ]. All functions may
-// return errc::not_enough_memory if there is not enough memory to complete the
-// operation.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_FILESYSTEM_H
-#define WPIUTIL_WPI_FILESYSTEM_H
-
-#include "wpi/Chrono.h"
-#include "wpi/SmallString.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
-#include "wpi/Error.h"
-#include "wpi/ErrorHandling.h"
-#include "wpi/ErrorOr.h"
-#include <cassert>
-#include <cstdint>
-#include <ctime>
-#include <memory>
-#include <stack>
-#include <string>
-#include <system_error>
-#include <tuple>
-#include <vector>
-
-#include <sys/stat.h>
-
-namespace wpi {
-namespace sys {
-namespace fs {
-
-#if defined(_WIN32)
-// A Win32 HANDLE is a typedef of void*
-using file_t = void *;
-#else
-using file_t = int;
-#endif
-
-extern const file_t kInvalidFile;
-
-/// An enumeration for the file system's view of the type.
-enum class file_type {
-  status_error,
-  file_not_found,
-  regular_file,
-  directory_file,
-  symlink_file,
-  block_file,
-  character_file,
-  fifo_file,
-  socket_file,
-  type_unknown
-};
-
-/// space_info - Self explanatory.
-struct space_info {
-  uint64_t capacity;
-  uint64_t free;
-  uint64_t available;
-};
-
-enum perms {
-  no_perms = 0,
-  owner_read = 0400,
-  owner_write = 0200,
-  owner_exe = 0100,
-  owner_all = owner_read | owner_write | owner_exe,
-  group_read = 040,
-  group_write = 020,
-  group_exe = 010,
-  group_all = group_read | group_write | group_exe,
-  others_read = 04,
-  others_write = 02,
-  others_exe = 01,
-  others_all = others_read | others_write | others_exe,
-  all_read = owner_read | group_read | others_read,
-  all_write = owner_write | group_write | others_write,
-  all_exe = owner_exe | group_exe | others_exe,
-  all_all = owner_all | group_all | others_all,
-  set_uid_on_exe = 04000,
-  set_gid_on_exe = 02000,
-  sticky_bit = 01000,
-  all_perms = all_all | set_uid_on_exe | set_gid_on_exe | sticky_bit,
-  perms_not_known = 0xFFFF
-};
-
-// Helper functions so that you can use & and | to manipulate perms bits:
-inline perms operator|(perms l, perms r) {
-  return static_cast<perms>(static_cast<unsigned short>(l) |
-                            static_cast<unsigned short>(r));
-}
-inline perms operator&(perms l, perms r) {
-  return static_cast<perms>(static_cast<unsigned short>(l) &
-                            static_cast<unsigned short>(r));
-}
-inline perms &operator|=(perms &l, perms r) {
-  l = l | r;
-  return l;
-}
-inline perms &operator&=(perms &l, perms r) {
-  l = l & r;
-  return l;
-}
-inline perms operator~(perms x) {
-  // Avoid UB by explicitly truncating the (unsigned) ~ result.
-  return static_cast<perms>(
-      static_cast<unsigned short>(~static_cast<unsigned short>(x)));
-}
-
-class UniqueID {
-  uint64_t Device;
-  uint64_t File;
-
-public:
-  UniqueID() = default;
-  UniqueID(uint64_t Device, uint64_t File) : Device(Device), File(File) {}
-
-  bool operator==(const UniqueID &Other) const {
-    return Device == Other.Device && File == Other.File;
-  }
-  bool operator!=(const UniqueID &Other) const { return !(*this == Other); }
-  bool operator<(const UniqueID &Other) const {
-    return std::tie(Device, File) < std::tie(Other.Device, Other.File);
-  }
-
-  uint64_t getDevice() const { return Device; }
-  uint64_t getFile() const { return File; }
-};
-
-/// Represents the result of a call to directory_iterator::status(). This is a
-/// subset of the information returned by a regular sys::fs::status() call, and
-/// represents the information provided by Windows FileFirstFile/FindNextFile.
-class basic_file_status {
-protected:
-  #ifndef _WIN32
-  time_t fs_st_atime = 0;
-  time_t fs_st_mtime = 0;
-  uint32_t fs_st_atime_nsec = 0;
-  uint32_t fs_st_mtime_nsec = 0;
-  uid_t fs_st_uid = 0;
-  gid_t fs_st_gid = 0;
-  off_t fs_st_size = 0;
-  #else
-  uint32_t LastAccessedTimeHigh = 0;
-  uint32_t LastAccessedTimeLow = 0;
-  uint32_t LastWriteTimeHigh = 0;
-  uint32_t LastWriteTimeLow = 0;
-  uint32_t FileSizeHigh = 0;
-  uint32_t FileSizeLow = 0;
-  #endif
-  file_type Type = file_type::status_error;
-  perms Perms = perms_not_known;
-
-public:
-  basic_file_status() = default;
-
-  explicit basic_file_status(file_type Type) : Type(Type) {}
-
-  #ifndef _WIN32
-  basic_file_status(file_type Type, perms Perms, time_t ATime,
-                    uint32_t ATimeNSec, time_t MTime, uint32_t MTimeNSec,
-                    uid_t UID, gid_t GID, off_t Size)
-      : fs_st_atime(ATime), fs_st_mtime(MTime),
-        fs_st_atime_nsec(ATimeNSec), fs_st_mtime_nsec(MTimeNSec),
-        fs_st_uid(UID), fs_st_gid(GID),
-        fs_st_size(Size), Type(Type), Perms(Perms) {}
-  #else
-  basic_file_status(file_type Type, perms Perms, uint32_t LastAccessTimeHigh,
-                    uint32_t LastAccessTimeLow, uint32_t LastWriteTimeHigh,
-                    uint32_t LastWriteTimeLow, uint32_t FileSizeHigh,
-                    uint32_t FileSizeLow)
-      : LastAccessedTimeHigh(LastAccessTimeHigh),
-        LastAccessedTimeLow(LastAccessTimeLow),
-        LastWriteTimeHigh(LastWriteTimeHigh),
-        LastWriteTimeLow(LastWriteTimeLow), FileSizeHigh(FileSizeHigh),
-        FileSizeLow(FileSizeLow), Type(Type), Perms(Perms) {}
-  #endif
-
-  // getters
-  file_type type() const { return Type; }
-  perms permissions() const { return Perms; }
-
-  /// The file access time as reported from the underlying file system.
-  ///
-  /// Also see comments on \c getLastModificationTime() related to the precision
-  /// of the returned value.
-  TimePoint<> getLastAccessedTime() const;
-
-  /// The file modification time as reported from the underlying file system.
-  ///
-  /// The returned value allows for nanosecond precision but the actual
-  /// resolution is an implementation detail of the underlying file system.
-  /// There is no guarantee for what kind of resolution you can expect, the
-  /// resolution can differ across platforms and even across mountpoints on the
-  /// same machine.
-  TimePoint<> getLastModificationTime() const;
-
-  #ifndef _WIN32
-  uint32_t getUser() const { return fs_st_uid; }
-  uint32_t getGroup() const { return fs_st_gid; }
-  uint64_t getSize() const { return fs_st_size; }
-  #else
-  uint32_t getUser() const {
-    return 9999; // Not applicable to Windows, so...
-  }
-
-  uint32_t getGroup() const {
-    return 9999; // Not applicable to Windows, so...
-  }
-
-  uint64_t getSize() const {
-    return (uint64_t(FileSizeHigh) << 32) + FileSizeLow;
-  }
-  #endif
-
-  // setters
-  void type(file_type v) { Type = v; }
-  void permissions(perms p) { Perms = p; }
-};
-
-/// Represents the result of a call to sys::fs::status().
-class file_status : public basic_file_status {
-  friend bool equivalent(file_status A, file_status B);
-
-  #ifndef _WIN32
-  dev_t fs_st_dev = 0;
-  nlink_t fs_st_nlinks = 0;
-  ino_t fs_st_ino = 0;
-  #else
-  uint32_t NumLinks = 0;
-  uint32_t VolumeSerialNumber = 0;
-  uint32_t FileIndexHigh = 0;
-  uint32_t FileIndexLow = 0;
-  #endif
-
-public:
-  file_status() = default;
-
-  explicit file_status(file_type Type) : basic_file_status(Type) {}
-
-  #ifndef _WIN32
-  file_status(file_type Type, perms Perms, dev_t Dev, nlink_t Links, ino_t Ino,
-              time_t ATime, uint32_t ATimeNSec,
-              time_t MTime, uint32_t MTimeNSec,
-              uid_t UID, gid_t GID, off_t Size)
-      : basic_file_status(Type, Perms, ATime, ATimeNSec, MTime, MTimeNSec,
-                          UID, GID, Size),
-        fs_st_dev(Dev), fs_st_nlinks(Links), fs_st_ino(Ino) {}
-  #else
-  file_status(file_type Type, perms Perms, uint32_t LinkCount,
-              uint32_t LastAccessTimeHigh, uint32_t LastAccessTimeLow,
-              uint32_t LastWriteTimeHigh, uint32_t LastWriteTimeLow,
-              uint32_t VolumeSerialNumber, uint32_t FileSizeHigh,
-              uint32_t FileSizeLow, uint32_t FileIndexHigh,
-              uint32_t FileIndexLow)
-      : basic_file_status(Type, Perms, LastAccessTimeHigh, LastAccessTimeLow,
-                          LastWriteTimeHigh, LastWriteTimeLow, FileSizeHigh,
-                          FileSizeLow),
-        NumLinks(LinkCount), VolumeSerialNumber(VolumeSerialNumber),
-        FileIndexHigh(FileIndexHigh), FileIndexLow(FileIndexLow) {}
-  #endif
-
-  UniqueID getUniqueID() const;
-  uint32_t getLinkCount() const;
-};
-
-/// @}
-/// @name Physical Operators
-/// @{
-
-/// Make \a path an absolute path.
-///
-/// Makes \a path absolute using the \a current_directory if it is not already.
-/// An empty \a path will result in the \a current_directory.
-///
-/// /absolute/path   => /absolute/path
-/// relative/../path => <current-directory>/relative/../path
-///
-/// @param path A path that is modified to be an absolute path.
-void make_absolute(const Twine &current_directory, SmallVectorImpl<char> &path);
-
-/// Make \a path an absolute path.
-///
-/// Makes \a path absolute using the current directory if it is not already. An
-/// empty \a path will result in the current directory.
-///
-/// /absolute/path   => /absolute/path
-/// relative/../path => <current-directory>/relative/../path
-///
-/// @param path A path that is modified to be an absolute path.
-/// @returns errc::success if \a path has been made absolute, otherwise a
-///          platform-specific error_code.
-std::error_code make_absolute(SmallVectorImpl<char> &path);
-
-/// Get the current path.
-///
-/// @param result Holds the current path on return.
-/// @returns errc::success if the current path has been stored in result,
-///          otherwise a platform-specific error_code.
-std::error_code current_path(SmallVectorImpl<char> &result);
-
-/// @}
-/// @name Physical Observers
-/// @{
-
-/// Does file exist?
-///
-/// @param status A basic_file_status previously returned from stat.
-/// @returns True if the file represented by status exists, false if it does
-///          not.
-bool exists(const basic_file_status &status);
-
-enum class AccessMode { Exist, Write, Execute };
-
-/// Can the file be accessed?
-///
-/// @param Path Input path.
-/// @returns errc::success if the path can be accessed, otherwise a
-///          platform-specific error_code.
-std::error_code access(const Twine &Path, AccessMode Mode);
-
-/// Does file exist?
-///
-/// @param Path Input path.
-/// @returns True if it exists, false otherwise.
-inline bool exists(const Twine &Path) {
-  return !access(Path, AccessMode::Exist);
-}
-
-/// Can we write this file?
-///
-/// @param Path Input path.
-/// @returns True if we can write to it, false otherwise.
-inline bool can_write(const Twine &Path) {
-  return !access(Path, AccessMode::Write);
-}
-
-/// Do file_status's represent the same thing?
-///
-/// @param A Input file_status.
-/// @param B Input file_status.
-///
-/// assert(status_known(A) || status_known(B));
-///
-/// @returns True if A and B both represent the same file system entity, false
-///          otherwise.
-bool equivalent(file_status A, file_status B);
-
-/// Do paths represent the same thing?
-///
-/// assert(status_known(A) || status_known(B));
-///
-/// @param A Input path A.
-/// @param B Input path B.
-/// @param result Set to true if stat(A) and stat(B) have the same device and
-///               inode (or equivalent).
-/// @returns errc::success if result has been successfully set, otherwise a
-///          platform-specific error_code.
-std::error_code equivalent(const Twine &A, const Twine &B, bool &result);
-
-/// Simpler version of equivalent for clients that don't need to
-///        differentiate between an error and false.
-inline bool equivalent(const Twine &A, const Twine &B) {
-  bool result;
-  return !equivalent(A, B, result) && result;
-}
-
-/// Does status represent a directory?
-///
-/// @param Path The path to get the type of.
-/// @param Follow For symbolic links, indicates whether to return the file type
-///               of the link itself, or of the target.
-/// @returns A value from the file_type enumeration indicating the type of file.
-file_type get_file_type(const Twine &Path, bool Follow = true);
-
-/// Does status represent a directory?
-///
-/// @param status A basic_file_status previously returned from status.
-/// @returns status.type() == file_type::directory_file.
-bool is_directory(const basic_file_status &status);
-
-/// Is path a directory?
-///
-/// @param path Input path.
-/// @param result Set to true if \a path is a directory (after following
-///               symlinks, false if it is not. Undefined otherwise.
-/// @returns errc::success if result has been successfully set, otherwise a
-///          platform-specific error_code.
-std::error_code is_directory(const Twine &path, bool &result);
-
-/// Simpler version of is_directory for clients that don't need to
-///        differentiate between an error and false.
-inline bool is_directory(const Twine &Path) {
-  bool Result;
-  return !is_directory(Path, Result) && Result;
-}
-
-/// Does status represent a regular file?
-///
-/// @param status A basic_file_status previously returned from status.
-/// @returns status_known(status) && status.type() == file_type::regular_file.
-bool is_regular_file(const basic_file_status &status);
-
-/// Is path a regular file?
-///
-/// @param path Input path.
-/// @param result Set to true if \a path is a regular file (after following
-///               symlinks), false if it is not. Undefined otherwise.
-/// @returns errc::success if result has been successfully set, otherwise a
-///          platform-specific error_code.
-std::error_code is_regular_file(const Twine &path, bool &result);
-
-/// Simpler version of is_regular_file for clients that don't need to
-///        differentiate between an error and false.
-inline bool is_regular_file(const Twine &Path) {
-  bool Result;
-  if (is_regular_file(Path, Result))
-    return false;
-  return Result;
-}
-
-/// Does status represent a symlink file?
-///
-/// @param status A basic_file_status previously returned from status.
-/// @returns status_known(status) && status.type() == file_type::symlink_file.
-bool is_symlink_file(const basic_file_status &status);
-
-/// Is path a symlink file?
-///
-/// @param path Input path.
-/// @param result Set to true if \a path is a symlink file, false if it is not.
-///               Undefined otherwise.
-/// @returns errc::success if result has been successfully set, otherwise a
-///          platform-specific error_code.
-std::error_code is_symlink_file(const Twine &path, bool &result);
-
-/// Simpler version of is_symlink_file for clients that don't need to
-///        differentiate between an error and false.
-inline bool is_symlink_file(const Twine &Path) {
-  bool Result;
-  if (is_symlink_file(Path, Result))
-    return false;
-  return Result;
-}
-
-/// Does this status represent something that exists but is not a
-///        directory or regular file?
-///
-/// @param status A basic_file_status previously returned from status.
-/// @returns exists(s) && !is_regular_file(s) && !is_directory(s)
-bool is_other(const basic_file_status &status);
-
-/// Is path something that exists but is not a directory,
-///        regular file, or symlink?
-///
-/// @param path Input path.
-/// @param result Set to true if \a path exists, but is not a directory, regular
-///               file, or a symlink, false if it does not. Undefined otherwise.
-/// @returns errc::success if result has been successfully set, otherwise a
-///          platform-specific error_code.
-std::error_code is_other(const Twine &path, bool &result);
-
-/// Get file status as if by POSIX stat().
-///
-/// @param path Input path.
-/// @param result Set to the file status.
-/// @param follow When true, follows symlinks.  Otherwise, the symlink itself is
-///               statted.
-/// @returns errc::success if result has been successfully set, otherwise a
-///          platform-specific error_code.
-std::error_code status(const Twine &path, file_status &result,
-                       bool follow = true);
-
-/// A version for when a file descriptor is already available.
-std::error_code status(int FD, file_status &Result);
-
-/// Is status available?
-///
-/// @param s Input file status.
-/// @returns True if status() != status_error.
-bool status_known(const basic_file_status &s);
-
-/// Is status available?
-///
-/// @param path Input path.
-/// @param result Set to true if status() != status_error.
-/// @returns errc::success if result has been successfully set, otherwise a
-///          platform-specific error_code.
-std::error_code status_known(const Twine &path, bool &result);
-
-enum CreationDisposition : unsigned {
-  /// CD_CreateAlways - When opening a file:
-  ///   * If it already exists, truncate it.
-  ///   * If it does not already exist, create a new file.
-  CD_CreateAlways = 0,
-
-  /// CD_CreateNew - When opening a file:
-  ///   * If it already exists, fail.
-  ///   * If it does not already exist, create a new file.
-  CD_CreateNew = 1,
-
-  /// CD_OpenExisting - When opening a file:
-  ///   * If it already exists, open the file with the offset set to 0.
-  ///   * If it does not already exist, fail.
-  CD_OpenExisting = 2,
-
-  /// CD_OpenAlways - When opening a file:
-  ///   * If it already exists, open the file with the offset set to 0.
-  ///   * If it does not already exist, create a new file.
-  CD_OpenAlways = 3,
-};
-
-enum FileAccess : unsigned {
-  FA_Read = 1,
-  FA_Write = 2,
-};
-
-enum OpenFlags : unsigned {
-  OF_None = 0,
-  F_None = 0, // For compatibility
-
-  /// The file should be opened in text mode on platforms that make this
-  /// distinction.
-  OF_Text = 1,
-  F_Text = 1, // For compatibility
-
-  /// The file should be opened in append mode.
-  OF_Append = 2,
-  F_Append = 2, // For compatibility
-
-  /// Delete the file on close. Only makes a difference on windows.
-  OF_Delete = 4,
-
-  /// When a child process is launched, this file should remain open in the
-  /// child process.
-  OF_ChildInherit = 8,
-
-  /// Force files Atime to be updated on access. Only makes a difference on windows.
-  OF_UpdateAtime = 16,
-};
-
-inline OpenFlags operator|(OpenFlags A, OpenFlags B) {
-  return OpenFlags(unsigned(A) | unsigned(B));
-}
-
-inline OpenFlags &operator|=(OpenFlags &A, OpenFlags B) {
-  A = A | B;
-  return A;
-}
-
-inline FileAccess operator|(FileAccess A, FileAccess B) {
-  return FileAccess(unsigned(A) | unsigned(B));
-}
-
-inline FileAccess &operator|=(FileAccess &A, FileAccess B) {
-  A = A | B;
-  return A;
-}
-
-/// @brief Opens a file with the specified creation disposition, access mode,
-/// and flags and returns a file descriptor.
-///
-/// The caller is responsible for closing the file descriptor once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param ResultFD If the file could be opened successfully, its descriptor
-///                 is stored in this location. Otherwise, this is set to -1.
-/// @param Disp Value specifying the existing-file behavior.
-/// @param Access Value specifying whether to open the file in read, write, or
-///               read-write mode.
-/// @param Flags Additional flags.
-/// @param Mode The access permissions of the file, represented in octal.
-/// @returns errc::success if \a Name has been opened, otherwise a
-///          platform-specific error_code.
-std::error_code openFile(const Twine &Name, int &ResultFD,
-                         CreationDisposition Disp, FileAccess Access,
-                         OpenFlags Flags, unsigned Mode = 0666);
-
-/// @brief Opens a file with the specified creation disposition, access mode,
-/// and flags and returns a platform-specific file object.
-///
-/// The caller is responsible for closing the file object once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param Disp Value specifying the existing-file behavior.
-/// @param Access Value specifying whether to open the file in read, write, or
-///               read-write mode.
-/// @param Flags Additional flags.
-/// @param Mode The access permissions of the file, represented in octal.
-/// @returns errc::success if \a Name has been opened, otherwise a
-///          platform-specific error_code.
-Expected<file_t> openNativeFile(const Twine &Name, CreationDisposition Disp,
-                                FileAccess Access, OpenFlags Flags,
-                                unsigned Mode = 0666);
-
-/// @brief Opens the file with the given name in a write-only or read-write
-/// mode, returning its open file descriptor. If the file does not exist, it
-/// is created.
-///
-/// The caller is responsible for closing the file descriptor once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param ResultFD If the file could be opened successfully, its descriptor
-///                 is stored in this location. Otherwise, this is set to -1.
-/// @param Flags Additional flags used to determine whether the file should be
-///              opened in, for example, read-write or in write-only mode.
-/// @param Mode The access permissions of the file, represented in octal.
-/// @returns errc::success if \a Name has been opened, otherwise a
-///          platform-specific error_code.
-inline std::error_code
-openFileForWrite(const Twine &Name, int &ResultFD,
-                 CreationDisposition Disp = CD_CreateAlways,
-                 OpenFlags Flags = OF_None, unsigned Mode = 0666) {
-  return openFile(Name, ResultFD, Disp, FA_Write, Flags, Mode);
-}
-
-/// @brief Opens the file with the given name in a write-only or read-write
-/// mode, returning its open file descriptor. If the file does not exist, it
-/// is created.
-///
-/// The caller is responsible for closing the freeing the file once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param Flags Additional flags used to determine whether the file should be
-///              opened in, for example, read-write or in write-only mode.
-/// @param Mode The access permissions of the file, represented in octal.
-/// @returns a platform-specific file descriptor if \a Name has been opened,
-///          otherwise an error object.
-inline Expected<file_t> openNativeFileForWrite(const Twine &Name,
-                                               CreationDisposition Disp,
-                                               OpenFlags Flags,
-                                               unsigned Mode = 0666) {
-  return openNativeFile(Name, Disp, FA_Write, Flags, Mode);
-}
-
-/// @brief Opens the file with the given name in a write-only or read-write
-/// mode, returning its open file descriptor. If the file does not exist, it
-/// is created.
-///
-/// The caller is responsible for closing the file descriptor once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param ResultFD If the file could be opened successfully, its descriptor
-///                 is stored in this location. Otherwise, this is set to -1.
-/// @param Flags Additional flags used to determine whether the file should be
-///              opened in, for example, read-write or in write-only mode.
-/// @param Mode The access permissions of the file, represented in octal.
-/// @returns errc::success if \a Name has been opened, otherwise a
-///          platform-specific error_code.
-inline std::error_code openFileForReadWrite(const Twine &Name, int &ResultFD,
-                                            CreationDisposition Disp,
-                                            OpenFlags Flags,
-                                            unsigned Mode = 0666) {
-  return openFile(Name, ResultFD, Disp, FA_Write | FA_Read, Flags, Mode);
-}
-
-/// @brief Opens the file with the given name in a write-only or read-write
-/// mode, returning its open file descriptor. If the file does not exist, it
-/// is created.
-///
-/// The caller is responsible for closing the freeing the file once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param Flags Additional flags used to determine whether the file should be
-///              opened in, for example, read-write or in write-only mode.
-/// @param Mode The access permissions of the file, represented in octal.
-/// @returns a platform-specific file descriptor if \a Name has been opened,
-///          otherwise an error object.
-inline Expected<file_t> openNativeFileForReadWrite(const Twine &Name,
-                                                   CreationDisposition Disp,
-                                                   OpenFlags Flags,
-                                                   unsigned Mode = 0666) {
-  return openNativeFile(Name, Disp, FA_Write | FA_Read, Flags, Mode);
-}
-
-/// @brief Opens the file with the given name in a read-only mode, returning
-/// its open file descriptor.
-///
-/// The caller is responsible for closing the file descriptor once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param ResultFD If the file could be opened successfully, its descriptor
-///                 is stored in this location. Otherwise, this is set to -1.
-/// @param RealPath If nonnull, extra work is done to determine the real path
-///                 of the opened file, and that path is stored in this
-///                 location.
-/// @returns errc::success if \a Name has been opened, otherwise a
-///          platform-specific error_code.
-std::error_code openFileForRead(const Twine &Name, int &ResultFD,
-                                OpenFlags Flags = OF_None,
-                                SmallVectorImpl<char> *RealPath = nullptr);
-
-/// @brief Opens the file with the given name in a read-only mode, returning
-/// its open file descriptor.
-///
-/// The caller is responsible for closing the freeing the file once they are
-/// finished with it.
-///
-/// @param Name The path of the file to open, relative or absolute.
-/// @param RealPath If nonnull, extra work is done to determine the real path
-///                 of the opened file, and that path is stored in this
-///                 location.
-/// @returns a platform-specific file descriptor if \a Name has been opened,
-///          otherwise an error object.
-Expected<file_t>
-openNativeFileForRead(const Twine &Name, OpenFlags Flags = OF_None,
-                      SmallVectorImpl<char> *RealPath = nullptr);
-
-/// @brief Close the file object.  This should be used instead of ::close for
-/// portability.
-///
-/// @param F On input, this is the file to close.  On output, the file is
-/// set to kInvalidFile.
-void closeFile(file_t &F);
-
-std::error_code getUniqueID(const Twine Path, UniqueID &Result);
-
-/// This class represents a memory mapped file. It is based on
-/// boost::iostreams::mapped_file.
-class mapped_file_region {
-public:
-  enum mapmode {
-    readonly, ///< May only access map via const_data as read only.
-    readwrite, ///< May access map via data and modify it. Written to path.
-    priv ///< May modify via data, but changes are lost on destruction.
-  };
-
-private:
-  /// Platform-specific mapping state.
-  size_t Size;
-  void *Mapping;
-#ifdef _WIN32
-  void *FileHandle;
-#endif
-  mapmode Mode;
-
-  std::error_code init(int FD, uint64_t Offset, mapmode Mode);
-
-public:
-  mapped_file_region() = delete;
-  mapped_file_region(mapped_file_region&) = delete;
-  mapped_file_region &operator =(mapped_file_region&) = delete;
-
-  /// \param fd An open file descriptor to map. mapped_file_region takes
-  ///   ownership if closefd is true. It must have been opended in the correct
-  ///   mode.
-  mapped_file_region(int fd, mapmode mode, size_t length, uint64_t offset,
-                     std::error_code &ec);
-
-  ~mapped_file_region();
-
-  size_t size() const;
-  char *data() const;
-
-  /// Get a const view of the data. Modifying this memory has undefined
-  /// behavior.
-  const char *const_data() const;
-
-  /// \returns The minimum alignment offset must be.
-  static int alignment();
-};
-
-/// @}
-/// @name Iterators
-/// @{
-
-/// directory_entry - A single entry in a directory. Caches the status either
-/// from the result of the iteration syscall, or the first time status is
-/// called.
-class directory_entry {
-  std::string Path;
-  file_type Type;           // Most platforms can provide this.
-  bool FollowSymlinks;      // Affects the behavior of status().
-  basic_file_status Status; // If available.
-
-public:
-  explicit directory_entry(const Twine &Path, bool FollowSymlinks = true,
-                           file_type Type = file_type::type_unknown,
-                           basic_file_status Status = basic_file_status())
-      : Path(Path.str()), Type(Type), FollowSymlinks(FollowSymlinks),
-        Status(Status) {}
-
-  directory_entry() = default;
-
-  void replace_filename(const Twine &Filename, file_type Type,
-                        basic_file_status Status = basic_file_status());
-
-  const std::string &path() const { return Path; }
-  ErrorOr<basic_file_status> status() const;
-  file_type type() const {
-    if (Type != file_type::type_unknown)
-      return Type;
-    auto S = status();
-    return S ? S->type() : file_type::type_unknown;
-  }
-
-  bool operator==(const directory_entry& RHS) const { return Path == RHS.Path; }
-  bool operator!=(const directory_entry& RHS) const { return !(*this == RHS); }
-  bool operator< (const directory_entry& RHS) const;
-  bool operator<=(const directory_entry& RHS) const;
-  bool operator> (const directory_entry& RHS) const;
-  bool operator>=(const directory_entry& RHS) const;
-};
-
-namespace detail {
-
-  struct DirIterState;
-
-  std::error_code directory_iterator_construct(DirIterState &, StringRef, bool);
-  std::error_code directory_iterator_increment(DirIterState &);
-  std::error_code directory_iterator_destruct(DirIterState &);
-
-  /// Keeps state for the directory_iterator.
-  struct DirIterState {
-    ~DirIterState() {
-      directory_iterator_destruct(*this);
-    }
-
-    intptr_t IterationHandle = 0;
-    directory_entry CurrentEntry;
-  };
-
-} // end namespace detail
-
-/// directory_iterator - Iterates through the entries in path. There is no
-/// operator++ because we need an error_code. If it's really needed we can make
-/// it call report_fatal_error on error.
-class directory_iterator {
-  std::shared_ptr<detail::DirIterState> State;
-  bool FollowSymlinks = true;
-
-public:
-  explicit directory_iterator(const Twine &path, std::error_code &ec,
-                              bool follow_symlinks = true)
-      : FollowSymlinks(follow_symlinks) {
-    State = std::make_shared<detail::DirIterState>();
-    SmallString<128> path_storage;
-    ec = detail::directory_iterator_construct(
-        *State, path.toStringRef(path_storage), FollowSymlinks);
-  }
-
-  explicit directory_iterator(const directory_entry &de, std::error_code &ec,
-                              bool follow_symlinks = true)
-      : FollowSymlinks(follow_symlinks) {
-    State = std::make_shared<detail::DirIterState>();
-    ec = detail::directory_iterator_construct(
-        *State, de.path(), FollowSymlinks);
-  }
-
-  /// Construct end iterator.
-  directory_iterator() = default;
-
-  // No operator++ because we need error_code.
-  directory_iterator &increment(std::error_code &ec) {
-    ec = directory_iterator_increment(*State);
-    return *this;
-  }
-
-  const directory_entry &operator*() const { return State->CurrentEntry; }
-  const directory_entry *operator->() const { return &State->CurrentEntry; }
-
-  bool operator==(const directory_iterator &RHS) const {
-    if (State == RHS.State)
-      return true;
-    if (!RHS.State)
-      return State->CurrentEntry == directory_entry();
-    if (!State)
-      return RHS.State->CurrentEntry == directory_entry();
-    return State->CurrentEntry == RHS.State->CurrentEntry;
-  }
-
-  bool operator!=(const directory_iterator &RHS) const {
-    return !(*this == RHS);
-  }
-};
-
-namespace detail {
-
-  /// Keeps state for the recursive_directory_iterator.
-  struct RecDirIterState {
-    std::stack<directory_iterator, std::vector<directory_iterator>> Stack;
-    uint16_t Level = 0;
-    bool HasNoPushRequest = false;
-  };
-
-} // end namespace detail
-
-/// recursive_directory_iterator - Same as directory_iterator except for it
-/// recurses down into child directories.
-class recursive_directory_iterator {
-  std::shared_ptr<detail::RecDirIterState> State;
-  bool Follow;
-
-public:
-  recursive_directory_iterator() = default;
-  explicit recursive_directory_iterator(const Twine &path, std::error_code &ec,
-                                        bool follow_symlinks = true)
-      : State(std::make_shared<detail::RecDirIterState>()),
-        Follow(follow_symlinks) {
-    State->Stack.push(directory_iterator(path, ec, Follow));
-    if (State->Stack.top() == directory_iterator())
-      State.reset();
-  }
-
-  // No operator++ because we need error_code.
-  recursive_directory_iterator &increment(std::error_code &ec) {
-    const directory_iterator end_itr = {};
-
-    if (State->HasNoPushRequest)
-      State->HasNoPushRequest = false;
-    else {
-      file_type type = State->Stack.top()->type();
-      if (type == file_type::symlink_file && Follow) {
-        // Resolve the symlink: is it a directory to recurse into?
-        ErrorOr<basic_file_status> status = State->Stack.top()->status();
-        if (status)
-          type = status->type();
-        // Otherwise broken symlink, and we'll continue.
-      }
-      if (type == file_type::directory_file) {
-        State->Stack.push(directory_iterator(*State->Stack.top(), ec, Follow));
-        if (State->Stack.top() != end_itr) {
-          ++State->Level;
-          return *this;
-        }
-        State->Stack.pop();
-      }
-    }
-
-    while (!State->Stack.empty()
-           && State->Stack.top().increment(ec) == end_itr) {
-      State->Stack.pop();
-      --State->Level;
-    }
-
-    // Check if we are done. If so, create an end iterator.
-    if (State->Stack.empty())
-      State.reset();
-
-    return *this;
-  }
-
-  const directory_entry &operator*() const { return *State->Stack.top(); }
-  const directory_entry *operator->() const { return &*State->Stack.top(); }
-
-  // observers
-  /// Gets the current level. Starting path is at level 0.
-  int level() const { return State->Level; }
-
-  /// Returns true if no_push has been called for this directory_entry.
-  bool no_push_request() const { return State->HasNoPushRequest; }
-
-  // modifiers
-  /// Goes up one level if Level > 0.
-  void pop() {
-    assert(State && "Cannot pop an end iterator!");
-    assert(State->Level > 0 && "Cannot pop an iterator with level < 1");
-
-    const directory_iterator end_itr = {};
-    std::error_code ec;
-    do {
-      if (ec) {
-        //report_fatal_error("Error incrementing directory iterator.");
-        while (!State->Stack.empty()) State->Stack.pop();
-        break;
-      }
-      State->Stack.pop();
-      --State->Level;
-    } while (!State->Stack.empty()
-             && State->Stack.top().increment(ec) == end_itr);
-
-    // Check if we are done. If so, create an end iterator.
-    if (State->Stack.empty())
-      State.reset();
-  }
-
-  /// Does not go down into the current directory_entry.
-  void no_push() { State->HasNoPushRequest = true; }
-
-  bool operator==(const recursive_directory_iterator &RHS) const {
-    return State == RHS.State;
-  }
-
-  bool operator!=(const recursive_directory_iterator &RHS) const {
-    return !(*this == RHS);
-  }
-};
-
-/// @}
-
-} // end namespace fs
-} // end namespace sys
-} // end namespace wpi
-
-#endif // LLVM_SUPPORT_FILESYSTEM_H
diff --git a/wpiutil/src/main/native/include/wpi/Format.h b/wpiutil/src/main/native/include/wpi/Format.h
deleted file mode 100644
index cb8b26b..0000000
--- a/wpiutil/src/main/native/include/wpi/Format.h
+++ /dev/null
@@ -1,265 +0,0 @@
-//===- Format.h - Efficient printf-style formatting for streams -*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the format() function, which can be used with other
-// LLVM subsystems to provide printf-style formatting.  This gives all the power
-// and risk of printf.  This can be used like this (with raw_ostreams as an
-// example):
-//
-//    OS << "mynumber: " << format("%4.5f", 1234.412) << '\n';
-//
-// Or if you prefer:
-//
-//  OS << format("mynumber: %4.5f\n", 1234.412);
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_FORMAT_H
-#define WPIUTIL_WPI_FORMAT_H
-
-#include "wpi/ArrayRef.h"
-#include "wpi/StringRef.h"
-#include <cassert>
-#include <cstdint>
-#include <cstdio>
-#include <optional>
-#include <tuple>
-#include <utility>
-
-namespace wpi {
-
-/// This is a helper class used for handling formatted output.  It is the
-/// abstract base class of a templated derived class.
-class format_object_base {
-protected:
-  const char *Fmt;
-  ~format_object_base() = default; // Disallow polymorphic deletion.
-  format_object_base(const format_object_base &) = default;
-  virtual void home(); // Out of line virtual method.
-
-  /// Call snprintf() for this object, on the given buffer and size.
-  virtual int snprint(char *Buffer, unsigned BufferSize) const = 0;
-
-public:
-  format_object_base(const char *fmt) : Fmt(fmt) {}
-
-  /// Format the object into the specified buffer.  On success, this returns
-  /// the length of the formatted string.  If the buffer is too small, this
-  /// returns a length to retry with, which will be larger than BufferSize.
-  unsigned print(char *Buffer, unsigned BufferSize) const {
-    assert(BufferSize && "Invalid buffer size!");
-
-    // Print the string, leaving room for the terminating null.
-    int N = snprint(Buffer, BufferSize);
-
-    // VC++ and old GlibC return negative on overflow, just double the size.
-    if (N < 0)
-      return BufferSize * 2;
-
-    // Other implementations yield number of bytes needed, not including the
-    // final '\0'.
-    if (unsigned(N) >= BufferSize)
-      return N + 1;
-
-    // Otherwise N is the length of output (not including the final '\0').
-    return N;
-  }
-};
-
-/// These are templated helper classes used by the format function that
-/// capture the object to be formatted and the format string. When actually
-/// printed, this synthesizes the string into a temporary buffer provided and
-/// returns whether or not it is big enough.
-
-// Helper to validate that format() parameters are scalars or pointers.
-template <typename... Args> struct validate_format_parameters;
-template <typename Arg, typename... Args>
-struct validate_format_parameters<Arg, Args...> {
-  static_assert(std::is_scalar<Arg>::value,
-                "format can't be used with non fundamental / non pointer type");
-  validate_format_parameters() { validate_format_parameters<Args...>(); }
-};
-template <> struct validate_format_parameters<> {};
-
-template <typename... Ts>
-class format_object final : public format_object_base {
-  std::tuple<Ts...> Vals;
-
-  template <std::size_t... Is>
-  int snprint_tuple(char *Buffer, unsigned BufferSize,
-                    std::index_sequence<Is...>) const {
-#ifdef _MSC_VER
-    return _snprintf_s(Buffer, BufferSize, BufferSize, Fmt, std::get<Is>(Vals)...);
-#else
-#ifdef __GNUC__
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
-#endif
-    return snprintf(Buffer, BufferSize, Fmt, std::get<Is>(Vals)...);
-#ifdef __GNUC__
-#pragma GCC diagnostic pop
-#endif
-#endif
-  }
-
-public:
-  format_object(const char *fmt, const Ts &... vals)
-      : format_object_base(fmt), Vals(vals...) {
-    validate_format_parameters<Ts...>();
-  }
-
-  int snprint(char *Buffer, unsigned BufferSize) const override {
-    return snprint_tuple(Buffer, BufferSize, std::index_sequence_for<Ts...>());
-  }
-};
-
-/// These are helper functions used to produce formatted output.  They use
-/// template type deduction to construct the appropriate instance of the
-/// format_object class to simplify their construction.
-///
-/// This is typically used like:
-/// \code
-///   OS << format("%0.4f", myfloat) << '\n';
-/// \endcode
-
-template <typename... Ts>
-inline format_object<Ts...> format(const char *Fmt, const Ts &... Vals) {
-  return format_object<Ts...>(Fmt, Vals...);
-}
-
-/// This is a helper class for left_justify, right_justify, and center_justify.
-class FormattedString {
-public:
-  enum Justification { JustifyNone, JustifyLeft, JustifyRight, JustifyCenter };
-  FormattedString(StringRef S, unsigned W, Justification J)
-      : Str(S), Width(W), Justify(J) {}
-
-private:
-  StringRef Str;
-  unsigned Width;
-  Justification Justify;
-  friend class raw_ostream;
-};
-
-/// left_justify - append spaces after string so total output is
-/// \p Width characters.  If \p Str is larger that \p Width, full string
-/// is written with no padding.
-inline FormattedString left_justify(StringRef Str, unsigned Width) {
-  return FormattedString(Str, Width, FormattedString::JustifyLeft);
-}
-
-/// right_justify - add spaces before string so total output is
-/// \p Width characters.  If \p Str is larger that \p Width, full string
-/// is written with no padding.
-inline FormattedString right_justify(StringRef Str, unsigned Width) {
-  return FormattedString(Str, Width, FormattedString::JustifyRight);
-}
-
-/// center_justify - add spaces before and after string so total output is
-/// \p Width characters.  If \p Str is larger that \p Width, full string
-/// is written with no padding.
-inline FormattedString center_justify(StringRef Str, unsigned Width) {
-  return FormattedString(Str, Width, FormattedString::JustifyCenter);
-}
-
-/// This is a helper class used for format_hex() and format_decimal().
-class FormattedNumber {
-  uint64_t HexValue;
-  int64_t DecValue;
-  unsigned Width;
-  bool Hex;
-  bool Upper;
-  bool HexPrefix;
-  friend class raw_ostream;
-
-public:
-  FormattedNumber(uint64_t HV, int64_t DV, unsigned W, bool H, bool U,
-                  bool Prefix)
-      : HexValue(HV), DecValue(DV), Width(W), Hex(H), Upper(U),
-        HexPrefix(Prefix) {}
-};
-
-/// format_hex - Output \p N as a fixed width hexadecimal. If number will not
-/// fit in width, full number is still printed.  Examples:
-///   OS << format_hex(255, 4)              => 0xff
-///   OS << format_hex(255, 4, true)        => 0xFF
-///   OS << format_hex(255, 6)              => 0x00ff
-///   OS << format_hex(255, 2)              => 0xff
-inline FormattedNumber format_hex(uint64_t N, unsigned Width,
-                                  bool Upper = false) {
-  assert(Width <= 18 && "hex width must be <= 18");
-  return FormattedNumber(N, 0, Width, true, Upper, true);
-}
-
-/// format_hex_no_prefix - Output \p N as a fixed width hexadecimal. Does not
-/// prepend '0x' to the outputted string.  If number will not fit in width,
-/// full number is still printed.  Examples:
-///   OS << format_hex_no_prefix(255, 2)              => ff
-///   OS << format_hex_no_prefix(255, 2, true)        => FF
-///   OS << format_hex_no_prefix(255, 4)              => 00ff
-///   OS << format_hex_no_prefix(255, 1)              => ff
-inline FormattedNumber format_hex_no_prefix(uint64_t N, unsigned Width,
-                                            bool Upper = false) {
-  assert(Width <= 16 && "hex width must be <= 16");
-  return FormattedNumber(N, 0, Width, true, Upper, false);
-}
-
-/// format_decimal - Output \p N as a right justified, fixed-width decimal. If
-/// number will not fit in width, full number is still printed.  Examples:
-///   OS << format_decimal(0, 5)     => "    0"
-///   OS << format_decimal(255, 5)   => "  255"
-///   OS << format_decimal(-1, 3)    => " -1"
-///   OS << format_decimal(12345, 3) => "12345"
-inline FormattedNumber format_decimal(int64_t N, unsigned Width) {
-  return FormattedNumber(0, N, Width, false, false, false);
-}
-
-class FormattedBytes {
-  ArrayRef<uint8_t> Bytes;
-
-  // If not nullopt, display offsets for each line relative to starting value.
-  std::optional<uint64_t> FirstByteOffset;
-  uint32_t IndentLevel;  // Number of characters to indent each line.
-  uint32_t NumPerLine;   // Number of bytes to show per line.
-  uint8_t ByteGroupSize; // How many hex bytes are grouped without spaces
-  bool Upper;            // Show offset and hex bytes as upper case.
-  bool ASCII;            // Show the ASCII bytes for the hex bytes to the right.
-  friend class raw_ostream;
-
-public:
-  FormattedBytes(ArrayRef<uint8_t> B, uint32_t IL, std::optional<uint64_t> O,
-                 uint32_t NPL, uint8_t BGS, bool U, bool A)
-      : Bytes(B), FirstByteOffset(O), IndentLevel(IL), NumPerLine(NPL),
-        ByteGroupSize(BGS), Upper(U), ASCII(A) {
-
-    if (ByteGroupSize > NumPerLine)
-      ByteGroupSize = NumPerLine;
-  }
-};
-
-inline FormattedBytes
-format_bytes(ArrayRef<uint8_t> Bytes, std::optional<uint64_t> FirstByteOffset = std::nullopt,
-             uint32_t NumPerLine = 16, uint8_t ByteGroupSize = 4,
-             uint32_t IndentLevel = 0, bool Upper = false) {
-  return FormattedBytes(Bytes, IndentLevel, FirstByteOffset, NumPerLine,
-                        ByteGroupSize, Upper, false);
-}
-
-inline FormattedBytes
-format_bytes_with_ascii(ArrayRef<uint8_t> Bytes,
-                        std::optional<uint64_t> FirstByteOffset = std::nullopt,
-                        uint32_t NumPerLine = 16, uint8_t ByteGroupSize = 4,
-                        uint32_t IndentLevel = 0, bool Upper = false) {
-  return FormattedBytes(Bytes, IndentLevel, FirstByteOffset, NumPerLine,
-                        ByteGroupSize, Upper, true);
-}
-
-} // end namespace wpi
-
-#endif
diff --git a/wpiutil/src/main/native/include/wpi/FunctionExtras.h b/wpiutil/src/main/native/include/wpi/FunctionExtras.h
index 8e45cda..57bc8dc 100644
--- a/wpiutil/src/main/native/include/wpi/FunctionExtras.h
+++ b/wpiutil/src/main/native/include/wpi/FunctionExtras.h
@@ -37,6 +37,7 @@
 #include "wpi/PointerIntPair.h"
 #include "wpi/PointerUnion.h"
 #include <memory>
+#include <type_traits>
 
 namespace wpi {
 
@@ -237,7 +238,11 @@
     return *this;
   }
 
-  template <typename CallableT> unique_function(CallableT Callable) {
+  template <typename CallableT>
+  unique_function(CallableT Callable,
+                  std::enable_if_t<
+                    std::is_invocable_r_v<
+                      ReturnT, CallableT, ParamTs...>>* = nullptr) {
     bool IsInlineStorage = true;
     void *CallableAddr = getInlineStorage();
     if (sizeof(CallableT) > InlineStorageSize ||
diff --git a/wpiutil/src/main/native/include/wpi/Hashing.h b/wpiutil/src/main/native/include/wpi/Hashing.h
index d59d695..e58bfdd 100644
--- a/wpiutil/src/main/native/include/wpi/Hashing.h
+++ b/wpiutil/src/main/native/include/wpi/Hashing.h
@@ -53,6 +53,7 @@
 #include <cassert>
 #include <cstring>
 #include <string>
+#include <string_view>
 #include <utility>
 
 #ifdef _WIN32
@@ -660,6 +661,11 @@
   return hash_combine_range(arg.begin(), arg.end());
 }
 
+template <typename T>
+hash_code hash_value(const std::basic_string_view<T> &arg) {
+  return hash_combine_range(arg.begin(), arg.end());
+}
+
 } // namespace wpi
 
 #ifdef _WIN32
diff --git a/wpiutil/src/main/native/include/wpi/HttpParser.h b/wpiutil/src/main/native/include/wpi/HttpParser.h
index b8b39de..72e5dad 100644
--- a/wpiutil/src/main/native/include/wpi/HttpParser.h
+++ b/wpiutil/src/main/native/include/wpi/HttpParser.h
@@ -1,18 +1,16 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_HTTPPARSER_H_
 #define WPIUTIL_WPI_HTTPPARSER_H_
 
 #include <stdint.h>
 
+#include <string_view>
+
 #include "wpi/Signal.h"
 #include "wpi/SmallString.h"
-#include "wpi/StringRef.h"
 #include "wpi/http_parser.h"
 
 namespace wpi {
@@ -61,9 +59,10 @@
    * @param in input data
    * @return Trailing input data after the parse.
    */
-  StringRef Execute(StringRef in) {
-    return in.drop_front(
+  std::string_view Execute(std::string_view in) {
+    in.remove_prefix(
         http_parser_execute(&m_parser, &m_settings, in.data(), in.size()));
+    return in;
   }
 
   /**
@@ -139,7 +138,7 @@
   /**
    * Get URL.  Valid in and after the url callback has been called.
    */
-  StringRef GetUrl() const { return m_urlBuf; }
+  std::string_view GetUrl() const { return m_urlBuf.str(); }
 
   /**
    * Message begin callback.
@@ -151,7 +150,7 @@
    *
    * The parameter to the callback is the complete URL string.
    */
-  sig::Signal<StringRef> url;
+  sig::Signal<std::string_view> url;
 
   /**
    * Status callback.
@@ -159,14 +158,14 @@
    * The parameter to the callback is the complete status string.
    * GetStatusCode() can be used to get the numeric status code.
    */
-  sig::Signal<StringRef> status;
+  sig::Signal<std::string_view> status;
 
   /**
    * Header field callback.
    *
    * The parameters to the callback are the field name and field value.
    */
-  sig::Signal<StringRef, StringRef> header;
+  sig::Signal<std::string_view, std::string_view> header;
 
   /**
    * Headers complete callback.
@@ -186,7 +185,7 @@
    * multiple times arbitrarily (e.g. it's possible that it may be called with
    * just a few characters at a time).
    */
-  sig::Signal<StringRef, bool> body;
+  sig::Signal<std::string_view, bool> body;
 
   /**
    * Headers complete callback.
diff --git a/wpiutil/src/main/native/include/wpi/HttpServerConnection.h b/wpiutil/src/main/native/include/wpi/HttpServerConnection.h
index 17c9ca2..a4d4cf8 100644
--- a/wpiutil/src/main/native/include/wpi/HttpServerConnection.h
+++ b/wpiutil/src/main/native/include/wpi/HttpServerConnection.h
@@ -1,19 +1,15 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_HTTPSERVERCONNECTION_H_
 #define WPIUTIL_WPI_HTTPSERVERCONNECTION_H_
 
 #include <memory>
+#include <string_view>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/HttpParser.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
+#include "wpi/span.h"
 #include "wpi/uv/Stream.h"
 
 namespace wpi {
@@ -39,7 +35,7 @@
    * Build common response headers.
    *
    * Called by SendHeader() to send headers common to every response.
-   * Each line must be terminated with \r\n.
+   * Each line must be terminated with "\r\n".
    *
    * The default implementation sends the following:
    * "Server: WebServer/1.0\r\n"
@@ -67,9 +63,9 @@
    *                      be set to false.
    * @param extra Extra HTTP headers to send, including final "\r\n"
    */
-  virtual void BuildHeader(raw_ostream& os, int code, const Twine& codeText,
-                           const Twine& contentType, uint64_t contentLength,
-                           const Twine& extra = Twine{});
+  virtual void BuildHeader(raw_ostream& os, int code, std::string_view codeText,
+                           std::string_view contentType, uint64_t contentLength,
+                           std::string_view extra = {});
 
   /**
    * Send data to client.
@@ -82,7 +78,7 @@
    *             is desired, call m_stream.Write() directly instead.
    * @param closeAfter close the connection after the write completes
    */
-  void SendData(ArrayRef<uv::Buffer> bufs, bool closeAfter = false);
+  void SendData(span<const uv::Buffer> bufs, bool closeAfter = false);
 
   /**
    * Send HTTP response, along with other header information like mimetype.
@@ -94,9 +90,10 @@
    * @param content Response message content
    * @param extraHeader Extra HTTP headers to send, including final "\r\n"
    */
-  virtual void SendResponse(int code, const Twine& codeText,
-                            const Twine& contentType, StringRef content,
-                            const Twine& extraHeader = Twine{});
+  virtual void SendResponse(int code, std::string_view codeText,
+                            std::string_view contentType,
+                            std::string_view content,
+                            std::string_view extraHeader = {});
 
   /**
    * Send HTTP response from static data, along with other header information
@@ -112,10 +109,10 @@
    * @param gzipped True if content is gzip compressed
    * @param extraHeader Extra HTTP headers to send, including final "\r\n"
    */
-  virtual void SendStaticResponse(int code, const Twine& codeText,
-                                  const Twine& contentType, StringRef content,
-                                  bool gzipped,
-                                  const Twine& extraHeader = Twine{});
+  virtual void SendStaticResponse(int code, std::string_view codeText,
+                                  std::string_view contentType,
+                                  std::string_view content, bool gzipped,
+                                  std::string_view extraHeader = {});
 
   /**
    * Send error header and message.
@@ -126,7 +123,7 @@
    * @param code HTTP error code (e.g. 404)
    * @param message Additional message text
    */
-  virtual void SendError(int code, const Twine& message = Twine{});
+  virtual void SendError(int code, std::string_view message = {});
 
   /** The HTTP request. */
   HttpParser m_request{HttpParser::kRequest};
diff --git a/wpiutil/src/main/native/include/wpi/HttpUtil.h b/wpiutil/src/main/native/include/wpi/HttpUtil.h
index 75b6e58..b10d127 100644
--- a/wpiutil/src/main/native/include/wpi/HttpUtil.h
+++ b/wpiutil/src/main/native/include/wpi/HttpUtil.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_HTTPUTIL_H_
 #define WPIUTIL_WPI_HTTPUTIL_H_
@@ -12,19 +9,18 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <string_view>
 #include <utility>
 #include <vector>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/NetworkStream.h"
 #include "wpi/SmallString.h"
 #include "wpi/SmallVector.h"
 #include "wpi/StringMap.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
 #include "wpi/raw_istream.h"
 #include "wpi/raw_socket_istream.h"
 #include "wpi/raw_socket_ostream.h"
+#include "wpi/span.h"
 
 namespace wpi {
 
@@ -32,15 +28,15 @@
 // @param buf Buffer for output
 // @param error Set to true if an error occurred
 // @return Escaped string
-StringRef UnescapeURI(const Twine& str, SmallVectorImpl<char>& buf,
-                      bool* error);
+std::string_view UnescapeURI(std::string_view str, SmallVectorImpl<char>& buf,
+                             bool* error);
 
 // Escape a string with %xx-encoding.
 // @param buf Buffer for output
 // @param spacePlus If true, encodes spaces to '+' rather than "%20"
 // @return Escaped string
-StringRef EscapeURI(const Twine& str, SmallVectorImpl<char>& buf,
-                    bool spacePlus = true);
+std::string_view EscapeURI(std::string_view str, SmallVectorImpl<char>& buf,
+                           bool spacePlus = true);
 
 // Parse a set of HTTP headers.  Saves just the Content-Type and Content-Length
 // fields.
@@ -58,7 +54,7 @@
 // @param saveBuf If not null, all scanned characters up to but not including
 //     the boundary are saved to this string
 // @return False if error occurred on input stream, true if boundary found.
-bool FindMultipartBoundary(wpi::raw_istream& is, StringRef boundary,
+bool FindMultipartBoundary(wpi::raw_istream& is, std::string_view boundary,
                            std::string* saveBuf);
 
 /**
@@ -80,7 +76,7 @@
    *
    * @param query query string
    */
-  explicit HttpQueryMap(StringRef query);
+  explicit HttpQueryMap(std::string_view query);
 
   /**
    * Gets an element of the query string.  Both the name and the returned
@@ -91,11 +87,11 @@
    * @return Optional unescaped value.  Returns an empty optional if the
    *         name is not present in the query map.
    */
-  std::optional<StringRef> Get(StringRef name,
-                               SmallVectorImpl<char>& buf) const;
+  std::optional<std::string_view> Get(std::string_view name,
+                                      SmallVectorImpl<char>& buf) const;
 
  private:
-  StringMap<StringRef> m_elems;
+  StringMap<std::string_view> m_elems;
 };
 
 class HttpPathRef;
@@ -123,7 +119,7 @@
    * Constructs a HTTP path from an escaped path string.  Makes a copy of the
    * path, so it's safe to be a temporary.
    */
-  explicit HttpPath(StringRef path);
+  explicit HttpPath(std::string_view path);
 
   /**
    * Evaluates to true if the path is not empty.
@@ -146,11 +142,13 @@
    * @param match match list
    * @return True if path equals match list
    */
-  bool equals(std::initializer_list<StringRef> match) const {
-    return equals(0, makeArrayRef(match.begin(), match.end()));
+  bool equals(std::initializer_list<std::string_view> match) const {
+    return equals(0, {match.begin(), match.end()});
   }
-  bool equals(ArrayRef<StringRef> match) const { return equals(0, match); }
-  bool equals(StringRef match) const { return equals(0, makeArrayRef(match)); }
+  bool equals(span<const std::string_view> match) const {
+    return equals(0, match);
+  }
+  bool equals(std::string_view match) const { return equals(0, {match}); }
 
   /**
    * Returns true if the elements of the path starting at the "start" element
@@ -160,15 +158,18 @@
    * @param match match list
    * @return True if match
    */
-  bool equals(size_t start, std::initializer_list<StringRef> match) const {
-    return equals(start, makeArrayRef(match.begin(), match.end()));
+  bool equals(size_t start,
+              std::initializer_list<std::string_view> match) const {
+    return equals(start, {match.begin(), match.end()});
   }
-  bool equals(size_t start, ArrayRef<StringRef> match) const {
-    if (m_pathEnds.size() != (start + match.size())) return false;
+  bool equals(size_t start, span<const std::string_view> match) const {
+    if (m_pathEnds.size() != (start + match.size())) {
+      return false;
+    }
     return startswith(start, match);
   }
-  bool equals(size_t start, StringRef match) const {
-    return equals(start, makeArrayRef(match));
+  bool equals(size_t start, std::string_view match) const {
+    return equals(start, {match});
   }
 
   /**
@@ -178,14 +179,14 @@
    * @param match match list
    * @return True if path starts with match list
    */
-  bool startswith(std::initializer_list<StringRef> match) const {
-    return startswith(0, makeArrayRef(match.begin(), match.end()));
+  bool startswith(std::initializer_list<std::string_view> match) const {
+    return startswith(0, {match.begin(), match.end()});
   }
-  bool startswith(ArrayRef<StringRef> match) const {
+  bool startswith(span<const std::string_view> match) const {
     return startswith(0, match);
   }
-  bool startswith(StringRef match) const {
-    return startswith(0, makeArrayRef(match));
+  bool startswith(std::string_view match) const {
+    return startswith(0, {match});
   }
 
   /**
@@ -196,22 +197,21 @@
    * @param match match list
    * @return True if path starting at the start element matches the match list
    */
-  bool startswith(size_t start, std::initializer_list<StringRef> match) const {
-    return startswith(start, makeArrayRef(match.begin(), match.end()));
+  bool startswith(size_t start,
+                  std::initializer_list<std::string_view> match) const {
+    return startswith(start, {match.begin(), match.end()});
   }
 
-  bool startswith(size_t start, ArrayRef<StringRef> match) const;
+  bool startswith(size_t start, span<const std::string_view> match) const;
 
-  bool startswith(size_t start, StringRef match) const {
-    return startswith(start, makeArrayRef(match));
+  bool startswith(size_t start, std::string_view match) const {
+    return startswith(start, {match});
   }
 
   /**
    * Gets a single element of the path.
    */
-  StringRef operator[](size_t n) const {
-    return m_pathBuf.slice(n == 0 ? 0 : m_pathEnds[n - 1], m_pathEnds[n]);
-  }
+  std::string_view operator[](size_t n) const;
 
   /**
    * Returns a path reference with the first N elements of the path removed.
@@ -229,7 +229,7 @@
 class HttpPathRef {
  public:
   HttpPathRef() = default;
-  /*implicit*/ HttpPathRef(const HttpPath& path,  // NOLINT(runtime/explicit)
+  /*implicit*/ HttpPathRef(const HttpPath& path,  // NOLINT
                            size_t start = 0)
       : m_path(&path), m_start(start) {}
 
@@ -237,44 +237,48 @@
   bool empty() const { return m_path && m_path->size() == m_start; }
   size_t size() const { return m_path ? m_path->size() - m_start : 0; }
 
-  bool equals(std::initializer_list<StringRef> match) const {
-    return equals(0, makeArrayRef(match.begin(), match.end()));
+  bool equals(std::initializer_list<std::string_view> match) const {
+    return equals(0, {match.begin(), match.end()});
   }
-  bool equals(ArrayRef<StringRef> match) const { return equals(0, match); }
-  bool equals(StringRef match) const { return equals(0, makeArrayRef(match)); }
+  bool equals(span<const std::string_view> match) const {
+    return equals(0, match);
+  }
+  bool equals(std::string_view match) const { return equals(0, {match}); }
 
-  bool equals(size_t start, std::initializer_list<StringRef> match) const {
-    return equals(start, makeArrayRef(match.begin(), match.end()));
+  bool equals(size_t start,
+              std::initializer_list<std::string_view> match) const {
+    return equals(start, {match.begin(), match.end()});
   }
-  bool equals(size_t start, ArrayRef<StringRef> match) const {
+  bool equals(size_t start, span<const std::string_view> match) const {
     return m_path ? m_path->equals(m_start + start, match) : false;
   }
-  bool equals(size_t start, StringRef match) const {
-    return equals(start, makeArrayRef(match));
+  bool equals(size_t start, std::string_view match) const {
+    return equals(start, {match});
   }
 
-  bool startswith(std::initializer_list<StringRef> match) const {
-    return startswith(0, makeArrayRef(match.begin(), match.end()));
+  bool startswith(std::initializer_list<std::string_view> match) const {
+    return startswith(0, {match.begin(), match.end()});
   }
-  bool startswith(ArrayRef<StringRef> match) const {
+  bool startswith(span<const std::string_view> match) const {
     return startswith(0, match);
   }
-  bool startswith(StringRef match) const {
-    return startswith(0, makeArrayRef(match));
+  bool startswith(std::string_view match) const {
+    return startswith(0, {match});
   }
 
-  bool startswith(size_t start, std::initializer_list<StringRef> match) const {
-    return startswith(start, makeArrayRef(match.begin(), match.end()));
+  bool startswith(size_t start,
+                  std::initializer_list<std::string_view> match) const {
+    return startswith(start, {match.begin(), match.end()});
   }
-  bool startswith(size_t start, ArrayRef<StringRef> match) const {
+  bool startswith(size_t start, span<const std::string_view> match) const {
     return m_path ? m_path->startswith(m_start + start, match) : false;
   }
-  bool startswith(size_t start, StringRef match) const {
-    return startswith(start, makeArrayRef(match));
+  bool startswith(size_t start, std::string_view match) const {
+    return startswith(start, {match});
   }
 
-  StringRef operator[](size_t n) const {
-    return m_path ? m_path->operator[](m_start + n) : StringRef{};
+  std::string_view operator[](size_t n) const {
+    return m_path ? m_path->operator[](m_start + n) : std::string_view{};
   }
   HttpPathRef drop_front(size_t n) const {
     return m_path ? m_path->drop_front(m_start + n) : HttpPathRef{};
@@ -288,7 +292,7 @@
 class HttpLocation {
  public:
   HttpLocation() = default;
-  HttpLocation(const Twine& url_, bool* error, std::string* errorMsg);
+  HttpLocation(std::string_view url_, bool* error, std::string* errorMsg);
 
   std::string url;       // retain copy
   std::string user;      // unescaped
@@ -313,13 +317,13 @@
   template <typename T>
   HttpRequest(const HttpLocation& loc, const T& extraParams);
 
-  HttpRequest(const HttpLocation& loc, StringRef path_)
+  HttpRequest(const HttpLocation& loc, std::string_view path_)
       : host{loc.host}, port{loc.port}, path{path_} {
     SetAuth(loc);
   }
 
   template <typename T>
-  HttpRequest(const HttpLocation& loc, StringRef path_, const T& params)
+  HttpRequest(const HttpLocation& loc, std::string_view path_, const T& params)
       : host{loc.host}, port{loc.port} {
     SetPath(path_, params);
     SetAuth(loc);
@@ -333,18 +337,18 @@
  private:
   void SetAuth(const HttpLocation& loc);
   template <typename T>
-  void SetPath(StringRef path_, const T& params);
+  void SetPath(std::string_view path_, const T& params);
 
   template <typename T>
-  static StringRef GetFirst(const T& elem) {
+  static std::string_view GetFirst(const T& elem) {
     return elem.first;
   }
   template <typename T>
-  static StringRef GetFirst(const StringMapEntry<T>& elem) {
+  static std::string_view GetFirst(const StringMapEntry<T>& elem) {
     return elem.getKey();
   }
   template <typename T>
-  static StringRef GetSecond(const T& elem) {
+  static std::string_view GetSecond(const T& elem) {
     return elem.second;
   }
 };
@@ -369,14 +373,15 @@
 
 class HttpMultipartScanner {
  public:
-  explicit HttpMultipartScanner(StringRef boundary, bool saveSkipped = false) {
+  explicit HttpMultipartScanner(std::string_view boundary,
+                                bool saveSkipped = false) {
     Reset(saveSkipped);
     SetBoundary(boundary);
   }
 
   // Change the boundary.  This is only safe to do when IsDone() is true (or
   // immediately after construction).
-  void SetBoundary(StringRef boundary);
+  void SetBoundary(std::string_view boundary);
 
   // Reset the scanner.  This allows reuse of internal buffers.
   void Reset(bool saveSkipped = false);
@@ -385,14 +390,14 @@
   // is true.
   // @param in input data
   // @return the input not consumed; empty if all input consumed
-  StringRef Execute(StringRef in);
+  std::string_view Execute(std::string_view in);
 
   // Returns true when the boundary has been found.
   bool IsDone() const { return m_state == kDone; }
 
   // Get the skipped data.  Will be empty if saveSkipped was false.
-  StringRef GetSkipped() const {
-    return m_saveSkipped ? StringRef{m_buf} : StringRef{};
+  std::string_view GetSkipped() const {
+    return m_saveSkipped ? std::string_view{m_buf} : std::string_view{};
   }
 
  private:
@@ -412,6 +417,6 @@
 
 }  // namespace wpi
 
-#include "HttpUtil.inl"
+#include "HttpUtil.inc"
 
 #endif  // WPIUTIL_WPI_HTTPUTIL_H_
diff --git a/wpiutil/src/main/native/include/wpi/HttpUtil.inc b/wpiutil/src/main/native/include/wpi/HttpUtil.inc
new file mode 100644
index 0000000..726cdfb
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/HttpUtil.inc
@@ -0,0 +1,55 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#ifndef WPIUTIL_WPI_HTTPUTIL_INC_
+#define WPIUTIL_WPI_HTTPUTIL_INC_
+
+#include <utility>
+
+#include "wpi/HttpUtil.h"
+
+namespace wpi {
+
+inline HttpPathRef HttpPath::drop_front(size_t n) const {
+  return HttpPathRef(*this, n);
+}
+
+template <typename T>
+HttpRequest::HttpRequest(const HttpLocation& loc, const T& extraParams)
+    : host{loc.host}, port{loc.port} {
+  StringMap<std::string_view> params;
+  for (const auto& p : loc.params) {
+    params.insert(std::make_pair(GetFirst(p), GetSecond(p)));
+  }
+  for (const auto& p : extraParams) {
+    params.insert(std::make_pair(GetFirst(p), GetSecond(p)));
+  }
+  SetPath(loc.path, params);
+  SetAuth(loc);
+}
+
+template <typename T>
+void HttpRequest::SetPath(std::string_view path_, const T& params) {
+  // Build location including query string
+  raw_svector_ostream pathOs{path};
+  pathOs << path_;
+  bool first = true;
+  for (const auto& param : params) {
+    if (first) {
+      pathOs << '?';
+      first = false;
+    } else {
+      pathOs << '&';
+    }
+    SmallString<64> escapeBuf;
+    pathOs << EscapeURI(GetFirst(param), escapeBuf, false);
+    if (!GetSecond(param).empty()) {
+      pathOs << '=' << EscapeURI(GetSecond(param), escapeBuf, false);
+    }
+  }
+}
+
+}  // namespace wpi
+
+#endif  // WPIUTIL_WPI_HTTPUTIL_INC_
diff --git a/wpiutil/src/main/native/include/wpi/HttpUtil.inl b/wpiutil/src/main/native/include/wpi/HttpUtil.inl
deleted file mode 100644
index f66c746..0000000
--- a/wpiutil/src/main/native/include/wpi/HttpUtil.inl
+++ /dev/null
@@ -1,54 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-#ifndef WPIUTIL_WPI_HTTPUTIL_INL_
-#define WPIUTIL_WPI_HTTPUTIL_INL_
-
-#include <utility>
-
-namespace wpi {
-
-inline HttpPathRef HttpPath::drop_front(size_t n) const {
-  return HttpPathRef(*this, n);
-}
-
-template <typename T>
-HttpRequest::HttpRequest(const HttpLocation& loc, const T& extraParams)
-    : host{loc.host}, port{loc.port} {
-  StringMap<StringRef> params;
-  for (const auto& p : loc.params)
-    params.insert(std::make_pair(GetFirst(p), GetSecond(p)));
-  for (const auto& p : extraParams)
-    params.insert(std::make_pair(GetFirst(p), GetSecond(p)));
-  SetPath(loc.path, params);
-  SetAuth(loc);
-}
-
-template <typename T>
-void HttpRequest::SetPath(StringRef path_, const T& params) {
-  // Build location including query string
-  raw_svector_ostream pathOs{path};
-  pathOs << path_;
-  bool first = true;
-  for (const auto& param : params) {
-    if (first) {
-      pathOs << '?';
-      first = false;
-    } else {
-      pathOs << '&';
-    }
-    SmallString<64> escapeBuf;
-    pathOs << EscapeURI(GetFirst(param), escapeBuf, false);
-    if (!GetSecond(param).empty()) {
-      pathOs << '=' << EscapeURI(GetSecond(param), escapeBuf, false);
-    }
-  }
-}
-
-}  // namespace wpi
-
-#endif  // WPIUTIL_WPI_HTTPUTIL_INL_
diff --git a/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h
index d662471..d89b61f 100644
--- a/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h
+++ b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_
 #define WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_
@@ -11,13 +8,13 @@
 #include <initializer_list>
 #include <memory>
 #include <string>
+#include <string_view>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/HttpServerConnection.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
 #include "wpi/WebSocket.h"
 #include "wpi/WebSocketServer.h"
+#include "wpi/span.h"
 #include "wpi/uv/Stream.h"
 
 namespace wpi {
@@ -39,7 +36,7 @@
    * @param protocols Acceptable subprotocols
    */
   HttpWebSocketServerConnection(std::shared_ptr<uv::Stream> stream,
-                                ArrayRef<StringRef> protocols);
+                                span<const std::string_view> protocols);
 
   /**
    * Constructor.
@@ -47,10 +44,11 @@
    * @param stream network stream
    * @param protocols Acceptable subprotocols
    */
-  HttpWebSocketServerConnection(std::shared_ptr<uv::Stream> stream,
-                                std::initializer_list<StringRef> protocols)
-      : HttpWebSocketServerConnection(
-            stream, makeArrayRef(protocols.begin(), protocols.end())) {}
+  HttpWebSocketServerConnection(
+      std::shared_ptr<uv::Stream> stream,
+      std::initializer_list<std::string_view> protocols)
+      : HttpWebSocketServerConnection(stream,
+                                      {protocols.begin(), protocols.end()}) {}
 
  protected:
   /**
@@ -62,7 +60,7 @@
    *
    * @param protocol negotiated subprotocol
    */
-  virtual bool IsValidWsUpgrade(StringRef protocol) { return true; }
+  virtual bool IsValidWsUpgrade(std::string_view protocol) { return true; }
 
   /**
    * Process an incoming WebSocket upgrade.  This is called after the header
@@ -89,6 +87,6 @@
 
 }  // namespace wpi
 
-#include "HttpWebSocketServerConnection.inl"
+#include "HttpWebSocketServerConnection.inc"
 
 #endif  // WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_
diff --git a/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inc b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inc
new file mode 100644
index 0000000..68b9f2d
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inc
@@ -0,0 +1,56 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#ifndef WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INC_
+#define WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INC_
+
+#include <memory>
+
+#include "wpi/HttpWebSocketServerConnection.h"
+
+namespace wpi {
+
+template <typename Derived>
+HttpWebSocketServerConnection<Derived>::HttpWebSocketServerConnection(
+    std::shared_ptr<uv::Stream> stream, span<const std::string_view> protocols)
+    : HttpServerConnection{stream},
+      m_helper{m_request},
+      m_protocols{protocols.begin(), protocols.end()} {
+  // Handle upgrade event
+  m_helper.upgrade.connect([this] {
+    // Negotiate sub-protocol
+    SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
+                                               m_protocols.end()};
+    std::string_view protocol = m_helper.MatchProtocol(protocols).second;
+
+    // Check that the upgrade is valid
+    if (!IsValidWsUpgrade(protocol)) {
+      return;
+    }
+
+    // Disconnect HttpServerConnection header reader
+    m_dataConn.disconnect();
+    m_messageCompleteConn.disconnect();
+
+    // Accepting the stream may destroy this (as it replaces the stream user
+    // data), so grab a shared pointer first.
+    auto self = this->shared_from_this();
+
+    // Accept the upgrade
+    auto ws = m_helper.Accept(m_stream, protocol);
+
+    // Set this as the websocket user data to keep it around
+    ws->SetData(self);
+
+    // Store in member
+    m_websocket = ws.get();
+
+    // Call derived class function
+    ProcessWsUpgrade();
+  });
+}
+
+}  // namespace wpi
+
+#endif  // WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INC_
diff --git a/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inl b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inl
deleted file mode 100644
index 586f189..0000000
--- a/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inl
+++ /dev/null
@@ -1,54 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-#ifndef WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INL_
-#define WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INL_
-
-#include <memory>
-
-namespace wpi {
-
-template <typename Derived>
-HttpWebSocketServerConnection<Derived>::HttpWebSocketServerConnection(
-    std::shared_ptr<uv::Stream> stream, ArrayRef<StringRef> protocols)
-    : HttpServerConnection{stream},
-      m_helper{m_request},
-      m_protocols{protocols.begin(), protocols.end()} {
-  // Handle upgrade event
-  m_helper.upgrade.connect([this] {
-    // Negotiate sub-protocol
-    SmallVector<StringRef, 2> protocols{m_protocols.begin(), m_protocols.end()};
-    StringRef protocol = m_helper.MatchProtocol(protocols).second;
-
-    // Check that the upgrade is valid
-    if (!IsValidWsUpgrade(protocol)) return;
-
-    // Disconnect HttpServerConnection header reader
-    m_dataConn.disconnect();
-    m_messageCompleteConn.disconnect();
-
-    // Accepting the stream may destroy this (as it replaces the stream user
-    // data), so grab a shared pointer first.
-    auto self = this->shared_from_this();
-
-    // Accept the upgrade
-    auto ws = m_helper.Accept(m_stream, protocol);
-
-    // Set this as the websocket user data to keep it around
-    ws->SetData(self);
-
-    // Store in member
-    m_websocket = ws.get();
-
-    // Call derived class function
-    ProcessWsUpgrade();
-  });
-}
-
-}  // namespace wpi
-
-#endif  // WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INL_
diff --git a/wpiutil/src/main/native/include/wpi/IntrusiveRefCntPtr.h b/wpiutil/src/main/native/include/wpi/IntrusiveRefCntPtr.h
deleted file mode 100644
index c677c10..0000000
--- a/wpiutil/src/main/native/include/wpi/IntrusiveRefCntPtr.h
+++ /dev/null
@@ -1,270 +0,0 @@
-//==- llvm/ADT/IntrusiveRefCntPtr.h - Smart Refcounting Pointer --*- C++ -*-==//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file defines the RefCountedBase, ThreadSafeRefCountedBase, and
-// IntrusiveRefCntPtr classes.
-//
-// IntrusiveRefCntPtr is a smart pointer to an object which maintains a
-// reference count.  (ThreadSafe)RefCountedBase is a mixin class that adds a
-// refcount member variable and methods for updating the refcount.  An object
-// that inherits from (ThreadSafe)RefCountedBase deletes itself when its
-// refcount hits zero.
-//
-// For example:
-//
-//   class MyClass : public RefCountedBase<MyClass> {};
-//
-//   void foo() {
-//     // Constructing an IntrusiveRefCntPtr increases the pointee's refcount by
-//     // 1 (from 0 in this case).
-//     IntrusiveRefCntPtr<MyClass> Ptr1(new MyClass());
-//
-//     // Copying an IntrusiveRefCntPtr increases the pointee's refcount by 1.
-//     IntrusiveRefCntPtr<MyClass> Ptr2(Ptr1);
-//
-//     // Constructing an IntrusiveRefCntPtr has no effect on the object's
-//     // refcount.  After a move, the moved-from pointer is null.
-//     IntrusiveRefCntPtr<MyClass> Ptr3(std::move(Ptr1));
-//     assert(Ptr1 == nullptr);
-//
-//     // Clearing an IntrusiveRefCntPtr decreases the pointee's refcount by 1.
-//     Ptr2.reset();
-//
-//     // The object deletes itself when we return from the function, because
-//     // Ptr3's destructor decrements its refcount to 0.
-//   }
-//
-// You can use IntrusiveRefCntPtr with isa<T>(), dyn_cast<T>(), etc.:
-//
-//   IntrusiveRefCntPtr<MyClass> Ptr(new MyClass());
-//   OtherClass *Other = dyn_cast<OtherClass>(Ptr);  // Ptr.get() not required
-//
-// IntrusiveRefCntPtr works with any class that
-//
-//  - inherits from (ThreadSafe)RefCountedBase,
-//  - has Retain() and Release() methods, or
-//  - specializes IntrusiveRefCntPtrInfo.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_INTRUSIVEREFCNTPTR_H
-#define WPIUTIL_WPI_INTRUSIVEREFCNTPTR_H
-
-#include <atomic>
-#include <cassert>
-#include <cstddef>
-
-namespace wpi {
-
-/// A CRTP mixin class that adds reference counting to a type.
-///
-/// The lifetime of an object which inherits from RefCountedBase is managed by
-/// calls to Release() and Retain(), which increment and decrement the object's
-/// refcount, respectively.  When a Release() call decrements the refcount to 0,
-/// the object deletes itself.
-template <class Derived> class RefCountedBase {
-  mutable unsigned RefCount = 0;
-
-public:
-  RefCountedBase() = default;
-  RefCountedBase(const RefCountedBase &) {}
-
-  void Retain() const { ++RefCount; }
-
-  void Release() const {
-    assert(RefCount > 0 && "Reference count is already zero.");
-    if (--RefCount == 0)
-      delete static_cast<const Derived *>(this);
-  }
-};
-
-/// A thread-safe version of \c RefCountedBase.
-template <class Derived> class ThreadSafeRefCountedBase {
-  mutable std::atomic<int> RefCount;
-
-protected:
-  ThreadSafeRefCountedBase() : RefCount(0) {}
-
-public:
-  void Retain() const { RefCount.fetch_add(1, std::memory_order_relaxed); }
-
-  void Release() const {
-    int NewRefCount = RefCount.fetch_sub(1, std::memory_order_acq_rel) - 1;
-    assert(NewRefCount >= 0 && "Reference count was already zero.");
-    if (NewRefCount == 0)
-      delete static_cast<const Derived *>(this);
-  }
-};
-
-/// Class you can specialize to provide custom retain/release functionality for
-/// a type.
-///
-/// Usually specializing this class is not necessary, as IntrusiveRefCntPtr
-/// works with any type which defines Retain() and Release() functions -- you
-/// can define those functions yourself if RefCountedBase doesn't work for you.
-///
-/// One case when you might want to specialize this type is if you have
-///  - Foo.h defines type Foo and includes Bar.h, and
-///  - Bar.h uses IntrusiveRefCntPtr<Foo> in inline functions.
-///
-/// Because Foo.h includes Bar.h, Bar.h can't include Foo.h in order to pull in
-/// the declaration of Foo.  Without the declaration of Foo, normally Bar.h
-/// wouldn't be able to use IntrusiveRefCntPtr<Foo>, which wants to call
-/// T::Retain and T::Release.
-///
-/// To resolve this, Bar.h could include a third header, FooFwd.h, which
-/// forward-declares Foo and specializes IntrusiveRefCntPtrInfo<Foo>.  Then
-/// Bar.h could use IntrusiveRefCntPtr<Foo>, although it still couldn't call any
-/// functions on Foo itself, because Foo would be an incomplete type.
-template <typename T> struct IntrusiveRefCntPtrInfo {
-  static void retain(T *obj) { obj->Retain(); }
-  static void release(T *obj) { obj->Release(); }
-};
-
-/// A smart pointer to a reference-counted object that inherits from
-/// RefCountedBase or ThreadSafeRefCountedBase.
-///
-/// This class increments its pointee's reference count when it is created, and
-/// decrements its refcount when it's destroyed (or is changed to point to a
-/// different object).
-template <typename T> class IntrusiveRefCntPtr {
-  T *Obj = nullptr;
-
-public:
-  using element_type = T;
-
-  explicit IntrusiveRefCntPtr() = default;
-  IntrusiveRefCntPtr(T *obj) : Obj(obj) { retain(); }
-  IntrusiveRefCntPtr(const IntrusiveRefCntPtr &S) : Obj(S.Obj) { retain(); }
-  IntrusiveRefCntPtr(IntrusiveRefCntPtr &&S) : Obj(S.Obj) { S.Obj = nullptr; }
-
-  template <class X>
-  IntrusiveRefCntPtr(IntrusiveRefCntPtr<X> &&S) : Obj(S.get()) {
-    S.Obj = nullptr;
-  }
-
-  template <class X>
-  IntrusiveRefCntPtr(const IntrusiveRefCntPtr<X> &S) : Obj(S.get()) {
-    retain();
-  }
-
-  ~IntrusiveRefCntPtr() { release(); }
-
-  IntrusiveRefCntPtr &operator=(IntrusiveRefCntPtr S) {
-    swap(S);
-    return *this;
-  }
-
-  T &operator*() const { return *Obj; }
-  T *operator->() const { return Obj; }
-  T *get() const { return Obj; }
-  explicit operator bool() const { return Obj; }
-
-  void swap(IntrusiveRefCntPtr &other) {
-    T *tmp = other.Obj;
-    other.Obj = Obj;
-    Obj = tmp;
-  }
-
-  void reset() {
-    release();
-    Obj = nullptr;
-  }
-
-  void resetWithoutRelease() { Obj = nullptr; }
-
-private:
-  void retain() {
-    if (Obj)
-      IntrusiveRefCntPtrInfo<T>::retain(Obj);
-  }
-
-  void release() {
-    if (Obj)
-      IntrusiveRefCntPtrInfo<T>::release(Obj);
-  }
-
-  template <typename X> friend class IntrusiveRefCntPtr;
-};
-
-template <class T, class U>
-inline bool operator==(const IntrusiveRefCntPtr<T> &A,
-                       const IntrusiveRefCntPtr<U> &B) {
-  return A.get() == B.get();
-}
-
-template <class T, class U>
-inline bool operator!=(const IntrusiveRefCntPtr<T> &A,
-                       const IntrusiveRefCntPtr<U> &B) {
-  return A.get() != B.get();
-}
-
-template <class T, class U>
-inline bool operator==(const IntrusiveRefCntPtr<T> &A, U *B) {
-  return A.get() == B;
-}
-
-template <class T, class U>
-inline bool operator!=(const IntrusiveRefCntPtr<T> &A, U *B) {
-  return A.get() != B;
-}
-
-template <class T, class U>
-inline bool operator==(T *A, const IntrusiveRefCntPtr<U> &B) {
-  return A == B.get();
-}
-
-template <class T, class U>
-inline bool operator!=(T *A, const IntrusiveRefCntPtr<U> &B) {
-  return A != B.get();
-}
-
-template <class T>
-bool operator==(std::nullptr_t A, const IntrusiveRefCntPtr<T> &B) {
-  return !B;
-}
-
-template <class T>
-bool operator==(const IntrusiveRefCntPtr<T> &A, std::nullptr_t B) {
-  return B == A;
-}
-
-template <class T>
-bool operator!=(std::nullptr_t A, const IntrusiveRefCntPtr<T> &B) {
-  return !(A == B);
-}
-
-template <class T>
-bool operator!=(const IntrusiveRefCntPtr<T> &A, std::nullptr_t B) {
-  return !(A == B);
-}
-
-// Make IntrusiveRefCntPtr work with dyn_cast, isa, and the other idioms from
-// Casting.h.
-template <typename From> struct simplify_type;
-
-template <class T> struct simplify_type<IntrusiveRefCntPtr<T>> {
-  using SimpleType = T *;
-
-  static SimpleType getSimplifiedValue(IntrusiveRefCntPtr<T> &Val) {
-    return Val.get();
-  }
-};
-
-template <class T> struct simplify_type<const IntrusiveRefCntPtr<T>> {
-  using SimpleType = /*const*/ T *;
-
-  static SimpleType getSimplifiedValue(const IntrusiveRefCntPtr<T> &Val) {
-    return Val.get();
-  }
-};
-
-} // end namespace wpi
-
-#endif // LLVM_ADT_INTRUSIVEREFCNTPTR_H
diff --git a/wpiutil/src/main/native/include/wpi/Logger.h b/wpiutil/src/main/native/include/wpi/Logger.h
index 20aa8ee..807e472 100644
--- a/wpiutil/src/main/native/include/wpi/Logger.h
+++ b/wpiutil/src/main/native/include/wpi/Logger.h
@@ -1,17 +1,14 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_LOGGER_H_
 #define WPIUTIL_WPI_LOGGER_H_
 
 #include <functional>
+#include <utility>
 
-#include "wpi/SmallString.h"
-#include "wpi/raw_ostream.h"
+#include "fmt/format.h"
 
 namespace wpi {
 
@@ -33,19 +30,28 @@
                                      unsigned int line, const char* msg)>;
 
   Logger() = default;
-  explicit Logger(const LogFunc& func) : m_func(func) {}
-  Logger(const LogFunc& func, unsigned int min_level)
-      : m_func(func), m_min_level(min_level) {}
+  explicit Logger(LogFunc func) : m_func(std::move(func)) {}
+  Logger(LogFunc func, unsigned int min_level)
+      : m_func(std::move(func)), m_min_level(min_level) {}
 
   void SetLogger(LogFunc func) { m_func = func; }
 
   void set_min_level(unsigned int level) { m_min_level = level; }
   unsigned int min_level() const { return m_min_level; }
 
+  void DoLog(unsigned int level, const char* file, unsigned int line,
+             const char* msg);
+
+  void LogV(unsigned int level, const char* file, unsigned int line,
+            fmt::string_view format, fmt::format_args args);
+
+  template <typename S, typename... Args>
   void Log(unsigned int level, const char* file, unsigned int line,
-           const char* msg) {
-    if (!m_func || level < m_min_level) return;
-    m_func(level, file, line, msg);
+           const S& format, Args&&... args) {
+    if (m_func && level >= m_min_level) {
+      LogV(level, file, line, format,
+           fmt::make_args_checked<Args...>(format, args...));
+    }
   }
 
   bool HasLogger() const { return m_func != nullptr; }
@@ -55,44 +61,43 @@
   unsigned int m_min_level = 20;
 };
 
-#define WPI_LOG(logger_inst, level, x)                                 \
-  do {                                                                 \
-    ::wpi::Logger& WPI_logger_ = logger_inst;                          \
-    if (WPI_logger_.min_level() <= static_cast<unsigned int>(level) && \
-        WPI_logger_.HasLogger()) {                                     \
-      ::wpi::SmallString<128> log_buf_;                                \
-      ::wpi::raw_svector_ostream log_os_{log_buf_};                    \
-      log_os_ << x;                                                    \
-      WPI_logger_.Log(level, __FILE__, __LINE__, log_buf_.c_str());    \
-    }                                                                  \
-  } while (0)
+#define WPI_LOG(logger_inst, level, format, ...) \
+  logger_inst.Log(level, __FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
 
-#define WPI_ERROR(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_ERROR, x)
-#define WPI_WARNING(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_WARNING, x)
-#define WPI_INFO(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_INFO, x)
+#define WPI_ERROR(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_ERROR, format, __VA_ARGS__)
+#define WPI_WARNING(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_WARNING, format, __VA_ARGS__)
+#define WPI_INFO(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_INFO, format, __VA_ARGS__)
 
 #ifdef NDEBUG
-#define WPI_DEBUG(inst, x) \
-  do {                     \
+#define WPI_DEBUG(inst, format, ...) \
+  do {                               \
   } while (0)
-#define WPI_DEBUG1(inst, x) \
-  do {                      \
+#define WPI_DEBUG1(inst, format, ...) \
+  do {                                \
   } while (0)
-#define WPI_DEBUG2(inst, x) \
-  do {                      \
+#define WPI_DEBUG2(inst, format, ...) \
+  do {                                \
   } while (0)
-#define WPI_DEBUG3(inst, x) \
-  do {                      \
+#define WPI_DEBUG3(inst, format, ...) \
+  do {                                \
   } while (0)
-#define WPI_DEBUG4(inst, x) \
-  do {                      \
+#define WPI_DEBUG4(inst, format, ...) \
+  do {                                \
   } while (0)
 #else
-#define WPI_DEBUG(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG, x)
-#define WPI_DEBUG1(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG1, x)
-#define WPI_DEBUG2(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG2, x)
-#define WPI_DEBUG3(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG3, x)
-#define WPI_DEBUG4(inst, x) WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG4, x)
+#define WPI_DEBUG(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG, format, __VA_ARGS__)
+#define WPI_DEBUG1(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG1, format, __VA_ARGS__)
+#define WPI_DEBUG2(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG2, format, __VA_ARGS__)
+#define WPI_DEBUG3(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG3, format, __VA_ARGS__)
+#define WPI_DEBUG4(inst, format, ...) \
+  WPI_LOG(inst, ::wpi::WPI_LOG_DEBUG4, format, __VA_ARGS__)
 #endif
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/MimeTypes.h b/wpiutil/src/main/native/include/wpi/MimeTypes.h
index b3dc17b..ce36ea2 100644
--- a/wpiutil/src/main/native/include/wpi/MimeTypes.h
+++ b/wpiutil/src/main/native/include/wpi/MimeTypes.h
@@ -1,18 +1,15 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_MIMETYPES_H_
 #define WPIUTIL_WPI_MIMETYPES_H_
 
-#include "wpi/StringRef.h"
+#include <string_view>
 
 namespace wpi {
 
-StringRef MimeTypeFromPath(StringRef path);
+std::string_view MimeTypeFromPath(std::string_view path);
 
 }  // namespace wpi
 
diff --git a/wpiutil/src/main/native/include/wpi/NativeFormatting.h b/wpiutil/src/main/native/include/wpi/NativeFormatting.h
deleted file mode 100644
index 976adc8..0000000
--- a/wpiutil/src/main/native/include/wpi/NativeFormatting.h
+++ /dev/null
@@ -1,49 +0,0 @@
-//===- NativeFormatting.h - Low level formatting helpers ---------*- C++-*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_NATIVE_FORMATTING_H
-#define WPIUTIL_WPI_NATIVE_FORMATTING_H
-
-#include "wpi/raw_ostream.h"
-
-#include <cstdint>
-#include <optional>
-
-namespace wpi {
-enum class FloatStyle { Exponent, ExponentUpper, Fixed, Percent };
-enum class IntegerStyle {
-  Integer,
-  Number,
-};
-enum class HexPrintStyle { Upper, Lower, PrefixUpper, PrefixLower };
-
-size_t getDefaultPrecision(FloatStyle Style);
-
-bool isPrefixedHexStyle(HexPrintStyle S);
-
-void write_integer(raw_ostream &S, unsigned int N, size_t MinDigits,
-                   IntegerStyle Style);
-void write_integer(raw_ostream &S, int N, size_t MinDigits, IntegerStyle Style);
-void write_integer(raw_ostream &S, unsigned long N, size_t MinDigits,
-                   IntegerStyle Style);
-void write_integer(raw_ostream &S, long N, size_t MinDigits,
-                   IntegerStyle Style);
-void write_integer(raw_ostream &S, unsigned long long N, size_t MinDigits,
-                   IntegerStyle Style);
-void write_integer(raw_ostream &S, long long N, size_t MinDigits,
-                   IntegerStyle Style);
-
-void write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style,
-               std::optional<size_t> Width = std::nullopt);
-void write_double(raw_ostream &S, double D, FloatStyle Style,
-                  std::optional<size_t> Precision = std::nullopt);
-}
-
-#endif
-
diff --git a/wpiutil/src/main/native/include/wpi/NetworkAcceptor.h b/wpiutil/src/main/native/include/wpi/NetworkAcceptor.h
index dd09e2d..9982360 100644
--- a/wpiutil/src/main/native/include/wpi/NetworkAcceptor.h
+++ b/wpiutil/src/main/native/include/wpi/NetworkAcceptor.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_NETWORKACCEPTOR_H_
 #define WPIUTIL_WPI_NETWORKACCEPTOR_H_
diff --git a/wpiutil/src/main/native/include/wpi/NetworkStream.h b/wpiutil/src/main/native/include/wpi/NetworkStream.h
index 1177167..b842ecd 100644
--- a/wpiutil/src/main/native/include/wpi/NetworkStream.h
+++ b/wpiutil/src/main/native/include/wpi/NetworkStream.h
@@ -1,16 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_NETWORKSTREAM_H_
 #define WPIUTIL_WPI_NETWORKSTREAM_H_
 
 #include <cstddef>
-
-#include "wpi/StringRef.h"
+#include <string_view>
 
 namespace wpi {
 
@@ -31,7 +27,7 @@
                          int timeout = 0) = 0;
   virtual void close() = 0;
 
-  virtual StringRef getPeerIP() const = 0;
+  virtual std::string_view getPeerIP() const = 0;
   virtual int getPeerPort() const = 0;
   virtual void setNoDelay() = 0;
 
diff --git a/wpiutil/src/main/native/include/wpi/NullDeleter.h b/wpiutil/src/main/native/include/wpi/NullDeleter.h
new file mode 100644
index 0000000..26146e5
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/NullDeleter.h
@@ -0,0 +1,16 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+namespace wpi {
+
+// A struct to use as a deleter when a std::shared_ptr must wrap a raw pointer
+// that is being deleted by someone else.
+template <class T>
+struct NullDeleter {
+  void operator()(T*) const noexcept {};
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/ParallelTcpConnector.h b/wpiutil/src/main/native/include/wpi/ParallelTcpConnector.h
new file mode 100644
index 0000000..47c29d0
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/ParallelTcpConnector.h
@@ -0,0 +1,119 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "wpi/span.h"
+#include "wpi/uv/Timer.h"
+
+namespace wpi {
+
+class Logger;
+
+namespace uv {
+class GetAddrInfoReq;
+class Loop;
+class Tcp;
+class Timer;
+}  // namespace uv
+
+/**
+ * Parallel TCP connector.  Attempts parallel resolution and connection to
+ * multiple servers with automatic retry if none connect.
+ *
+ * Each successful TCP connection results in a call to the connected callback.
+ * For correct operation, the consuming code (either the connected callback or
+ * e.g. task it starts) must call Succeeded() to indicate if the connection has
+ * succeeded prior to the reconnect rate timeout.  A successful connection
+ * results in the connector terminating all other connection attempts.
+ *
+ * After the reconnect rate times out, all remaining active connection attempts
+ * are canceled and new ones started.
+ */
+class ParallelTcpConnector
+    : public std::enable_shared_from_this<ParallelTcpConnector> {
+  struct private_init {};
+
+ public:
+  /**
+   * Create.
+   *
+   * @param loop loop
+   * @param reconnectRate how long to wait after starting connection attempts
+   *                      to cancel and attempt connecting again
+   * @param logger logger
+   * @param connected callback function when a connection succeeds; may be
+   *                  called multiple times if it does not call Succeeded()
+   *                  before returning
+   * @return Parallel connector
+   */
+  static std::shared_ptr<ParallelTcpConnector> Create(
+      wpi::uv::Loop& loop, wpi::uv::Timer::Time reconnectRate,
+      wpi::Logger& logger, std::function<void(wpi::uv::Tcp& tcp)> connected) {
+    return std::make_shared<ParallelTcpConnector>(
+        loop, reconnectRate, logger, std::move(connected), private_init{});
+  }
+
+  ParallelTcpConnector(wpi::uv::Loop& loop, wpi::uv::Timer::Time reconnectRate,
+                       wpi::Logger& logger,
+                       std::function<void(wpi::uv::Tcp& tcp)> connected,
+                       const private_init&);
+  ~ParallelTcpConnector();
+
+  ParallelTcpConnector(const ParallelTcpConnector&) = delete;
+  ParallelTcpConnector& operator=(const ParallelTcpConnector&) = delete;
+
+  /**
+   * Closes resources, canceling all pending action attempts.
+   */
+  void Close();
+
+  /**
+   * Changes the servers/ports to connect to.  Starts connection attempts if not
+   * already connected.
+   *
+   * @param servers array of server/port pairs
+   */
+  void SetServers(
+      wpi::span<const std::pair<std::string, unsigned int>> servers);
+
+  /**
+   * Tells the parallel connector that the current connection has terminated and
+   * it is necessary to start reconnection attempts.
+   */
+  void Disconnected();
+
+  /**
+   * Tells the parallel connector that a particular connection has succeeded and
+   * it should stop trying to connect.
+   *
+   * @param tcp connection passed to connected callback
+   */
+  void Succeeded(wpi::uv::Tcp& tcp);
+
+ private:
+  bool IsConnected() const { return m_isConnected || m_servers.empty(); }
+  void Connect();
+  void CancelAll(wpi::uv::Tcp* except = nullptr);
+
+  wpi::uv::Loop& m_loop;
+  wpi::Logger& m_logger;
+  wpi::uv::Timer::Time m_reconnectRate;
+  std::function<void(wpi::uv::Tcp& tcp)> m_connected;
+  std::shared_ptr<wpi::uv::Timer> m_reconnectTimer;
+  std::vector<std::pair<std::string, unsigned int>> m_servers;
+  std::vector<std::weak_ptr<wpi::uv::GetAddrInfoReq>> m_resolvers;
+  std::vector<std::weak_ptr<wpi::uv::Tcp>> m_attempts;
+  bool m_isConnected{false};
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/Path.h b/wpiutil/src/main/native/include/wpi/Path.h
deleted file mode 100644
index 2a8e94c..0000000
--- a/wpiutil/src/main/native/include/wpi/Path.h
+++ /dev/null
@@ -1,473 +0,0 @@
-//===- llvm/Support/Path.h - Path Operating System Concept ------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file declares the wpi::sys::path namespace. It is designed after
-// TR2/boost filesystem (v3), but modified to remove exception handling and the
-// path class.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_PATH_H_
-#define WPIUTIL_WPI_PATH_H_
-
-#include "wpi/Twine.h"
-#include "wpi/iterator.h"
-#include <cstdint>
-#include <iterator>
-#include <system_error>
-
-#ifdef _WIN32
-// Disable iterator deprecation warning
-#pragma warning(push)
-#pragma warning(disable : 4996)
-#endif
-
-namespace wpi {
-namespace sys {
-namespace path {
-
-enum class Style { windows, posix, native };
-
-/// @name Lexical Component Iterator
-/// @{
-
-/// Path iterator.
-///
-/// This is an input iterator that iterates over the individual components in
-/// \a path. The traversal order is as follows:
-/// * The root-name element, if present.
-/// * The root-directory element, if present.
-/// * Each successive filename element, if present.
-/// * Dot, if one or more trailing non-root slash characters are present.
-/// Traversing backwards is possible with \a reverse_iterator
-///
-/// Iteration examples. Each component is separated by ',':
-/// @code
-///   /          => /
-///   /foo       => /,foo
-///   foo/       => foo,.
-///   /foo/bar   => /,foo,bar
-///   ../        => ..,.
-///   C:\foo\bar => C:,/,foo,bar
-/// @endcode
-class const_iterator
-    : public iterator_facade_base<const_iterator, std::input_iterator_tag,
-                                  const StringRef> {
-  StringRef Path;      ///< The entire path.
-  StringRef Component; ///< The current component. Not necessarily in Path.
-  size_t    Position;  ///< The iterators current position within Path.
-  Style S;             ///< The path style to use.
-
-  // An end iterator has Position = Path.size() + 1.
-  friend const_iterator begin(StringRef path, Style style);
-  friend const_iterator end(StringRef path);
-
-public:
-  reference operator*() const { return Component; }
-  const_iterator &operator++();    // preincrement
-  bool operator==(const const_iterator &RHS) const;
-
-  /// Difference in bytes between this and RHS.
-  ptrdiff_t operator-(const const_iterator &RHS) const;
-};
-
-/// Reverse path iterator.
-///
-/// This is an input iterator that iterates over the individual components in
-/// \a path in reverse order. The traversal order is exactly reversed from that
-/// of \a const_iterator
-class reverse_iterator
-    : public iterator_facade_base<reverse_iterator, std::input_iterator_tag,
-                                  const StringRef> {
-  StringRef Path;      ///< The entire path.
-  StringRef Component; ///< The current component. Not necessarily in Path.
-  size_t    Position;  ///< The iterators current position within Path.
-  Style S;             ///< The path style to use.
-
-  friend reverse_iterator rbegin(StringRef path, Style style);
-  friend reverse_iterator rend(StringRef path);
-
-public:
-  reference operator*() const { return Component; }
-  reverse_iterator &operator++();    // preincrement
-  bool operator==(const reverse_iterator &RHS) const;
-
-  /// Difference in bytes between this and RHS.
-  ptrdiff_t operator-(const reverse_iterator &RHS) const;
-};
-
-/// Get begin iterator over \a path.
-/// @param path Input path.
-/// @returns Iterator initialized with the first component of \a path.
-const_iterator begin(StringRef path, Style style = Style::native);
-
-/// Get end iterator over \a path.
-/// @param path Input path.
-/// @returns Iterator initialized to the end of \a path.
-const_iterator end(StringRef path);
-
-/// Get reverse begin iterator over \a path.
-/// @param path Input path.
-/// @returns Iterator initialized with the first reverse component of \a path.
-reverse_iterator rbegin(StringRef path, Style style = Style::native);
-
-/// Get reverse end iterator over \a path.
-/// @param path Input path.
-/// @returns Iterator initialized to the reverse end of \a path.
-reverse_iterator rend(StringRef path);
-
-/// @}
-/// @name Lexical Modifiers
-/// @{
-
-/// Remove the last component from \a path unless it is the root dir.
-///
-/// @code
-///   directory/filename.cpp => directory/
-///   directory/             => directory
-///   filename.cpp           => <empty>
-///   /                      => /
-/// @endcode
-///
-/// @param path A path that is modified to not have a file component.
-void remove_filename(SmallVectorImpl<char> &path, Style style = Style::native);
-
-/// Replace the file extension of \a path with \a extension.
-///
-/// @code
-///   ./filename.cpp => ./filename.extension
-///   ./filename     => ./filename.extension
-///   ./             => ./.extension
-/// @endcode
-///
-/// @param path A path that has its extension replaced with \a extension.
-/// @param extension The extension to be added. It may be empty. It may also
-///                  optionally start with a '.', if it does not, one will be
-///                  prepended.
-void replace_extension(SmallVectorImpl<char> &path, const Twine &extension,
-                       Style style = Style::native);
-
-/// Replace matching path prefix with another path.
-///
-/// @code
-///   /foo, /old, /new => /foo
-///   /old/foo, /old, /new => /new/foo
-///   /foo, <empty>, /new => /new/foo
-///   /old/foo, /old, <empty> => /foo
-/// @endcode
-///
-/// @param Path If \a Path starts with \a OldPrefix modify to instead
-///        start with \a NewPrefix.
-/// @param OldPrefix The path prefix to strip from \a Path.
-/// @param NewPrefix The path prefix to replace \a NewPrefix with.
-void replace_path_prefix(SmallVectorImpl<char> &Path,
-                         const StringRef &OldPrefix, const StringRef &NewPrefix,
-                         Style style = Style::native);
-
-/// Append to path.
-///
-/// @code
-///   /foo  + bar/f => /foo/bar/f
-///   /foo/ + bar/f => /foo/bar/f
-///   foo   + bar/f => foo/bar/f
-/// @endcode
-///
-/// @param path Set to \a path + \a component.
-/// @param a The component to be appended to \a path.
-void append(SmallVectorImpl<char> &path, const Twine &a,
-                                         const Twine &b = "",
-                                         const Twine &c = "",
-                                         const Twine &d = "");
-
-void append(SmallVectorImpl<char> &path, Style style, const Twine &a,
-            const Twine &b = "", const Twine &c = "", const Twine &d = "");
-
-/// Append to path.
-///
-/// @code
-///   /foo  + [bar,f] => /foo/bar/f
-///   /foo/ + [bar,f] => /foo/bar/f
-///   foo   + [bar,f] => foo/bar/f
-/// @endcode
-///
-/// @param path Set to \a path + [\a begin, \a end).
-/// @param begin Start of components to append.
-/// @param end One past the end of components to append.
-void append(SmallVectorImpl<char> &path, const_iterator begin,
-            const_iterator end, Style style = Style::native);
-
-/// @}
-/// @name Transforms (or some other better name)
-/// @{
-
-/// Convert path to the native form. This is used to give paths to users and
-/// operating system calls in the platform's normal way. For example, on Windows
-/// all '/' are converted to '\'.
-///
-/// @param path A path that is transformed to native format.
-/// @param result Holds the result of the transformation.
-void native(const Twine &path, SmallVectorImpl<char> &result,
-            Style style = Style::native);
-
-/// Convert path to the native form in place. This is used to give paths to
-/// users and operating system calls in the platform's normal way. For example,
-/// on Windows all '/' are converted to '\'.
-///
-/// @param path A path that is transformed to native format.
-void native(SmallVectorImpl<char> &path, Style style = Style::native);
-
-/// Replaces backslashes with slashes if Windows.
-///
-/// @param path processed path
-/// @result The result of replacing backslashes with forward slashes if Windows.
-/// On Unix, this function is a no-op because backslashes are valid path
-/// chracters.
-std::string convert_to_slash(StringRef path, Style style = Style::native);
-
-/// @}
-/// @name Lexical Observers
-/// @{
-
-/// Get root name.
-///
-/// @code
-///   //net/hello => //net
-///   c:/hello    => c: (on Windows, on other platforms nothing)
-///   /hello      => <empty>
-/// @endcode
-///
-/// @param path Input path.
-/// @result The root name of \a path if it has one, otherwise "".
-StringRef root_name(StringRef path, Style style = Style::native);
-
-/// Get root directory.
-///
-/// @code
-///   /goo/hello => /
-///   c:/hello   => /
-///   d/file.txt => <empty>
-/// @endcode
-///
-/// @param path Input path.
-/// @result The root directory of \a path if it has one, otherwise
-///               "".
-StringRef root_directory(StringRef path, Style style = Style::native);
-
-/// Get root path.
-///
-/// Equivalent to root_name + root_directory.
-///
-/// @param path Input path.
-/// @result The root path of \a path if it has one, otherwise "".
-StringRef root_path(StringRef path, Style style = Style::native);
-
-/// Get relative path.
-///
-/// @code
-///   C:\hello\world => hello\world
-///   foo/bar        => foo/bar
-///   /foo/bar       => foo/bar
-/// @endcode
-///
-/// @param path Input path.
-/// @result The path starting after root_path if one exists, otherwise "".
-StringRef relative_path(StringRef path, Style style = Style::native);
-
-/// Get parent path.
-///
-/// @code
-///   /          => <empty>
-///   /foo       => /
-///   foo/../bar => foo/..
-/// @endcode
-///
-/// @param path Input path.
-/// @result The parent path of \a path if one exists, otherwise "".
-StringRef parent_path(StringRef path, Style style = Style::native);
-
-/// Get filename.
-///
-/// @code
-///   /foo.txt    => foo.txt
-///   .          => .
-///   ..         => ..
-///   /          => /
-/// @endcode
-///
-/// @param path Input path.
-/// @result The filename part of \a path. This is defined as the last component
-///         of \a path.
-StringRef filename(StringRef path, Style style = Style::native);
-
-/// Get stem.
-///
-/// If filename contains a dot but not solely one or two dots, result is the
-/// substring of filename ending at (but not including) the last dot. Otherwise
-/// it is filename.
-///
-/// @code
-///   /foo/bar.txt => bar
-///   /foo/bar     => bar
-///   /foo/.txt    => <empty>
-///   /foo/.       => .
-///   /foo/..      => ..
-/// @endcode
-///
-/// @param path Input path.
-/// @result The stem of \a path.
-StringRef stem(StringRef path, Style style = Style::native);
-
-/// Get extension.
-///
-/// If filename contains a dot but not solely one or two dots, result is the
-/// substring of filename starting at (and including) the last dot, and ending
-/// at the end of \a path. Otherwise "".
-///
-/// @code
-///   /foo/bar.txt => .txt
-///   /foo/bar     => <empty>
-///   /foo/.txt    => .txt
-/// @endcode
-///
-/// @param path Input path.
-/// @result The extension of \a path.
-StringRef extension(StringRef path, Style style = Style::native);
-
-/// Check whether the given char is a path separator on the host OS.
-///
-/// @param value a character
-/// @result true if \a value is a path separator character on the host OS
-bool is_separator(char value, Style style = Style::native);
-
-/// Return the preferred separator for this platform.
-///
-/// @result StringRef of the preferred separator, null-terminated.
-StringRef get_separator(Style style = Style::native);
-
-/// Get the typical temporary directory for the system, e.g.,
-/// "/var/tmp" or "C:/TEMP"
-///
-/// @param erasedOnReboot Whether to favor a path that is erased on reboot
-/// rather than one that potentially persists longer. This parameter will be
-/// ignored if the user or system has set the typical environment variable
-/// (e.g., TEMP on Windows, TMPDIR on *nix) to specify a temporary directory.
-///
-/// @param result Holds the resulting path name.
-void system_temp_directory(bool erasedOnReboot, SmallVectorImpl<char> &result);
-
-/// Get the user's home directory.
-///
-/// @param result Holds the resulting path name.
-/// @result True if a home directory is set, false otherwise.
-bool home_directory(SmallVectorImpl<char> &result);
-
-/// Has root name?
-///
-/// root_name != ""
-///
-/// @param path Input path.
-/// @result True if the path has a root name, false otherwise.
-bool has_root_name(const Twine &path, Style style = Style::native);
-
-/// Has root directory?
-///
-/// root_directory != ""
-///
-/// @param path Input path.
-/// @result True if the path has a root directory, false otherwise.
-bool has_root_directory(const Twine &path, Style style = Style::native);
-
-/// Has root path?
-///
-/// root_path != ""
-///
-/// @param path Input path.
-/// @result True if the path has a root path, false otherwise.
-bool has_root_path(const Twine &path, Style style = Style::native);
-
-/// Has relative path?
-///
-/// relative_path != ""
-///
-/// @param path Input path.
-/// @result True if the path has a relative path, false otherwise.
-bool has_relative_path(const Twine &path, Style style = Style::native);
-
-/// Has parent path?
-///
-/// parent_path != ""
-///
-/// @param path Input path.
-/// @result True if the path has a parent path, false otherwise.
-bool has_parent_path(const Twine &path, Style style = Style::native);
-
-/// Has filename?
-///
-/// filename != ""
-///
-/// @param path Input path.
-/// @result True if the path has a filename, false otherwise.
-bool has_filename(const Twine &path, Style style = Style::native);
-
-/// Has stem?
-///
-/// stem != ""
-///
-/// @param path Input path.
-/// @result True if the path has a stem, false otherwise.
-bool has_stem(const Twine &path, Style style = Style::native);
-
-/// Has extension?
-///
-/// extension != ""
-///
-/// @param path Input path.
-/// @result True if the path has a extension, false otherwise.
-bool has_extension(const Twine &path, Style style = Style::native);
-
-/// Is path absolute?
-///
-/// @param path Input path.
-/// @result True if the path is absolute, false if it is not.
-bool is_absolute(const Twine &path, Style style = Style::native);
-
-/// Is path relative?
-///
-/// @param path Input path.
-/// @result True if the path is relative, false if it is not.
-bool is_relative(const Twine &path, Style style = Style::native);
-
-/// Remove redundant leading "./" pieces and consecutive separators.
-///
-/// @param path Input path.
-/// @result The cleaned-up \a path.
-StringRef remove_leading_dotslash(StringRef path, Style style = Style::native);
-
-/// In-place remove any './' and optionally '../' components from a path.
-///
-/// @param path processed path
-/// @param remove_dot_dot specify if '../' (except for leading "../") should be
-/// removed
-/// @result True if path was changed
-bool remove_dots(SmallVectorImpl<char> &path, bool remove_dot_dot = false,
-                 Style style = Style::native);
-
-#if defined(_WIN32)
-std::error_code widenPath(const Twine &Path8, SmallVectorImpl<wchar_t> &Path16);
-#endif
-
-} // end namespace path
-} // end namespace sys
-} // end namespace wpi
-
-#ifdef _WIN32
-#pragma warning(pop)
-#endif
-
-#endif
diff --git a/wpiutil/src/main/native/include/wpi/PortForwarder.h b/wpiutil/src/main/native/include/wpi/PortForwarder.h
index 9ee6ce6..9e445a0 100644
--- a/wpiutil/src/main/native/include/wpi/PortForwarder.h
+++ b/wpiutil/src/main/native/include/wpi/PortForwarder.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_PORTFORWARDER_H_
 #define WPIUTIL_WPI_PORTFORWARDER_H_
@@ -11,8 +8,7 @@
 #pragma once
 
 #include <memory>
-
-#include "wpi/Twine.h"
+#include <string_view>
 
 namespace wpi {
 
@@ -41,7 +37,8 @@
    * @param remoteHost remote IP address / DNS name
    * @param remotePort remote port number
    */
-  void Add(unsigned int port, const Twine& remoteHost, unsigned int remotePort);
+  void Add(unsigned int port, std::string_view remoteHost,
+           unsigned int remotePort);
 
   /**
    * Stop TCP forwarding on a port.
diff --git a/wpiutil/src/main/native/include/wpi/STLExtras.h b/wpiutil/src/main/native/include/wpi/STLExtras.h
deleted file mode 100644
index d7ddd92..0000000
--- a/wpiutil/src/main/native/include/wpi/STLExtras.h
+++ /dev/null
@@ -1,1348 +0,0 @@
-//===- llvm/ADT/STLExtras.h - Useful STL related functions ------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file contains some templates that are useful if you are working with the
-// STL at all.
-//
-// No library is required when using these functions.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_STLEXTRAS_H
-#define WPIUTIL_WPI_STLEXTRAS_H
-
-#include "wpi/SmallVector.h"
-#include "wpi/iterator.h"
-#include "wpi/iterator_range.h"
-#include "wpi/ErrorHandling.h"
-#include <algorithm>
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-#include <cstdlib>
-#include <functional>
-#include <initializer_list>
-#include <iterator>
-#include <limits>
-#include <memory>
-#include <optional>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-
-namespace wpi {
-
-// Only used by compiler if both template types are the same.  Useful when
-// using SFINAE to test for the existence of member functions.
-template <typename T, T> struct SameType;
-
-namespace detail {
-
-template <typename RangeT>
-using IterOfRange = decltype(std::begin(std::declval<RangeT &>()));
-
-template <typename RangeT>
-using ValueOfRange = typename std::remove_reference<decltype(
-    *std::begin(std::declval<RangeT &>()))>::type;
-
-} // end namespace detail
-
-//===----------------------------------------------------------------------===//
-//     Extra additions to <type_traits>
-//===----------------------------------------------------------------------===//
-
-template <typename T> struct make_const_ptr {
-  using type =
-      typename std::add_pointer<typename std::add_const<T>::type>::type;
-};
-
-template <typename T> struct make_const_ref {
-  using type = typename std::add_lvalue_reference<
-      typename std::add_const<T>::type>::type;
-};
-
-//===----------------------------------------------------------------------===//
-//     Extra additions to <functional>
-//===----------------------------------------------------------------------===//
-
-template <class Ty> struct identity {
-  using argument_type = Ty;
-
-  Ty &operator()(Ty &self) const {
-    return self;
-  }
-  const Ty &operator()(const Ty &self) const {
-    return self;
-  }
-};
-
-template <class Ty> struct less_ptr {
-  bool operator()(const Ty* left, const Ty* right) const {
-    return *left < *right;
-  }
-};
-
-template <class Ty> struct greater_ptr {
-  bool operator()(const Ty* left, const Ty* right) const {
-    return *right < *left;
-  }
-};
-
-/// An efficient, type-erasing, non-owning reference to a callable. This is
-/// intended for use as the type of a function parameter that is not used
-/// after the function in question returns.
-///
-/// This class does not own the callable, so it is not in general safe to store
-/// a function_ref.
-template<typename Fn> class function_ref;
-
-template<typename Ret, typename ...Params>
-class function_ref<Ret(Params...)> {
-  Ret (*callback)(intptr_t callable, Params ...params) = nullptr;
-  intptr_t callable;
-
-  template<typename Callable>
-  static Ret callback_fn(intptr_t callable, Params ...params) {
-    return (*reinterpret_cast<Callable*>(callable))(
-        std::forward<Params>(params)...);
-  }
-
-public:
-  function_ref() = default;
-  function_ref(std::nullptr_t) {}
-
-  template <typename Callable>
-  function_ref(Callable &&callable,
-               typename std::enable_if<
-                   !std::is_same<typename std::remove_reference<Callable>::type,
-                                 function_ref>::value>::type * = nullptr)
-      : callback(callback_fn<typename std::remove_reference<Callable>::type>),
-        callable(reinterpret_cast<intptr_t>(&callable)) {}
-
-  Ret operator()(Params ...params) const {
-    return callback(callable, std::forward<Params>(params)...);
-  }
-
-  explicit operator bool() const { return callback; }
-};
-
-// deleter - Very very very simple method that is used to invoke operator
-// delete on something.  It is used like this:
-//
-//   for_each(V.begin(), B.end(), deleter<Interval>);
-template <class T>
-inline void deleter(T *Ptr) {
-  delete Ptr;
-}
-
-//===----------------------------------------------------------------------===//
-//     Extra additions to <iterator>
-//===----------------------------------------------------------------------===//
-
-namespace adl_detail {
-
-using std::begin;
-
-template <typename ContainerTy>
-auto adl_begin(ContainerTy &&container)
-    -> decltype(begin(std::forward<ContainerTy>(container))) {
-  return begin(std::forward<ContainerTy>(container));
-}
-
-using std::end;
-
-template <typename ContainerTy>
-auto adl_end(ContainerTy &&container)
-    -> decltype(end(std::forward<ContainerTy>(container))) {
-  return end(std::forward<ContainerTy>(container));
-}
-
-using std::swap;
-
-template <typename T>
-void adl_swap(T &&lhs, T &&rhs) noexcept(noexcept(swap(std::declval<T>(),
-                                                       std::declval<T>()))) {
-  swap(std::forward<T>(lhs), std::forward<T>(rhs));
-}
-
-} // end namespace adl_detail
-
-template <typename ContainerTy>
-auto adl_begin(ContainerTy &&container)
-    -> decltype(adl_detail::adl_begin(std::forward<ContainerTy>(container))) {
-  return adl_detail::adl_begin(std::forward<ContainerTy>(container));
-}
-
-template <typename ContainerTy>
-auto adl_end(ContainerTy &&container)
-    -> decltype(adl_detail::adl_end(std::forward<ContainerTy>(container))) {
-  return adl_detail::adl_end(std::forward<ContainerTy>(container));
-}
-
-template <typename T>
-void adl_swap(T &&lhs, T &&rhs) noexcept(
-    noexcept(adl_detail::adl_swap(std::declval<T>(), std::declval<T>()))) {
-  adl_detail::adl_swap(std::forward<T>(lhs), std::forward<T>(rhs));
-}
-
-/// Test whether \p RangeOrContainer is empty. Similar to C++17 std::empty.
-template <typename T>
-constexpr bool empty(const T &RangeOrContainer) {
-  return adl_begin(RangeOrContainer) == adl_end(RangeOrContainer);
-}
-
-// mapped_iterator - This is a simple iterator adapter that causes a function to
-// be applied whenever operator* is invoked on the iterator.
-
-template <typename ItTy, typename FuncTy,
-          typename FuncReturnTy =
-            decltype(std::declval<FuncTy>()(*std::declval<ItTy>()))>
-class mapped_iterator
-    : public iterator_adaptor_base<
-             mapped_iterator<ItTy, FuncTy>, ItTy,
-             typename std::iterator_traits<ItTy>::iterator_category,
-             typename std::remove_reference<FuncReturnTy>::type> {
-public:
-  mapped_iterator(ItTy U, FuncTy F)
-    : mapped_iterator::iterator_adaptor_base(std::move(U)), F(std::move(F)) {}
-
-  ItTy getCurrent() { return this->I; }
-
-  FuncReturnTy operator*() { return F(*this->I); }
-
-private:
-  FuncTy F;
-};
-
-// map_iterator - Provide a convenient way to create mapped_iterators, just like
-// make_pair is useful for creating pairs...
-template <class ItTy, class FuncTy>
-inline mapped_iterator<ItTy, FuncTy> map_iterator(ItTy I, FuncTy F) {
-  return mapped_iterator<ItTy, FuncTy>(std::move(I), std::move(F));
-}
-
-/// Helper to determine if type T has a member called rbegin().
-template <typename Ty> class has_rbegin_impl {
-  using yes = char[1];
-  using no = char[2];
-
-  template <typename Inner>
-  static yes& test(Inner *I, decltype(I->rbegin()) * = nullptr);
-
-  template <typename>
-  static no& test(...);
-
-public:
-  static const bool value = sizeof(test<Ty>(nullptr)) == sizeof(yes);
-};
-
-/// Metafunction to determine if T& or T has a member called rbegin().
-template <typename Ty>
-struct has_rbegin : has_rbegin_impl<typename std::remove_reference<Ty>::type> {
-};
-
-// Returns an iterator_range over the given container which iterates in reverse.
-// Note that the container must have rbegin()/rend() methods for this to work.
-template <typename ContainerTy>
-auto reverse(ContainerTy &&C,
-             typename std::enable_if<has_rbegin<ContainerTy>::value>::type * =
-                 nullptr) -> decltype(make_range(C.rbegin(), C.rend())) {
-  return make_range(C.rbegin(), C.rend());
-}
-
-// Returns a std::reverse_iterator wrapped around the given iterator.
-template <typename IteratorTy>
-std::reverse_iterator<IteratorTy> make_reverse_iterator(IteratorTy It) {
-  return std::reverse_iterator<IteratorTy>(It);
-}
-
-// Returns an iterator_range over the given container which iterates in reverse.
-// Note that the container must have begin()/end() methods which return
-// bidirectional iterators for this to work.
-template <typename ContainerTy>
-auto reverse(
-    ContainerTy &&C,
-    typename std::enable_if<!has_rbegin<ContainerTy>::value>::type * = nullptr)
-    -> decltype(make_range(wpi::make_reverse_iterator(std::end(C)),
-                           wpi::make_reverse_iterator(std::begin(C)))) {
-  return make_range(wpi::make_reverse_iterator(std::end(C)),
-                    wpi::make_reverse_iterator(std::begin(C)));
-}
-
-/// An iterator adaptor that filters the elements of given inner iterators.
-///
-/// The predicate parameter should be a callable object that accepts the wrapped
-/// iterator's reference type and returns a bool. When incrementing or
-/// decrementing the iterator, it will call the predicate on each element and
-/// skip any where it returns false.
-///
-/// \code
-///   int A[] = { 1, 2, 3, 4 };
-///   auto R = make_filter_range(A, [](int N) { return N % 2 == 1; });
-///   // R contains { 1, 3 }.
-/// \endcode
-///
-/// Note: filter_iterator_base implements support for forward iteration.
-/// filter_iterator_impl exists to provide support for bidirectional iteration,
-/// conditional on whether the wrapped iterator supports it.
-template <typename WrappedIteratorT, typename PredicateT, typename IterTag>
-class filter_iterator_base
-    : public iterator_adaptor_base<
-          filter_iterator_base<WrappedIteratorT, PredicateT, IterTag>,
-          WrappedIteratorT,
-          typename std::common_type<
-              IterTag, typename std::iterator_traits<
-                           WrappedIteratorT>::iterator_category>::type> {
-  using BaseT = iterator_adaptor_base<
-      filter_iterator_base<WrappedIteratorT, PredicateT, IterTag>,
-      WrappedIteratorT,
-      typename std::common_type<
-          IterTag, typename std::iterator_traits<
-                       WrappedIteratorT>::iterator_category>::type>;
-
-protected:
-  WrappedIteratorT End;
-  PredicateT Pred;
-
-  void findNextValid() {
-    while (this->I != End && !Pred(*this->I))
-      BaseT::operator++();
-  }
-
-  // Construct the iterator. The begin iterator needs to know where the end
-  // is, so that it can properly stop when it gets there. The end iterator only
-  // needs the predicate to support bidirectional iteration.
-  filter_iterator_base(WrappedIteratorT Begin, WrappedIteratorT End,
-                       PredicateT Pred)
-      : BaseT(Begin), End(End), Pred(Pred) {
-    findNextValid();
-  }
-
-public:
-  using BaseT::operator++;
-
-  filter_iterator_base &operator++() {
-    BaseT::operator++();
-    findNextValid();
-    return *this;
-  }
-};
-
-/// Specialization of filter_iterator_base for forward iteration only.
-template <typename WrappedIteratorT, typename PredicateT,
-          typename IterTag = std::forward_iterator_tag>
-class filter_iterator_impl
-    : public filter_iterator_base<WrappedIteratorT, PredicateT, IterTag> {
-  using BaseT = filter_iterator_base<WrappedIteratorT, PredicateT, IterTag>;
-
-public:
-  filter_iterator_impl(WrappedIteratorT Begin, WrappedIteratorT End,
-                       PredicateT Pred)
-      : BaseT(Begin, End, Pred) {}
-};
-
-/// Specialization of filter_iterator_base for bidirectional iteration.
-template <typename WrappedIteratorT, typename PredicateT>
-class filter_iterator_impl<WrappedIteratorT, PredicateT,
-                           std::bidirectional_iterator_tag>
-    : public filter_iterator_base<WrappedIteratorT, PredicateT,
-                                  std::bidirectional_iterator_tag> {
-  using BaseT = filter_iterator_base<WrappedIteratorT, PredicateT,
-                                     std::bidirectional_iterator_tag>;
-  void findPrevValid() {
-    while (!this->Pred(*this->I))
-      BaseT::operator--();
-  }
-
-public:
-  using BaseT::operator--;
-
-  filter_iterator_impl(WrappedIteratorT Begin, WrappedIteratorT End,
-                       PredicateT Pred)
-      : BaseT(Begin, End, Pred) {}
-
-  filter_iterator_impl &operator--() {
-    BaseT::operator--();
-    findPrevValid();
-    return *this;
-  }
-};
-
-namespace detail {
-
-template <bool is_bidirectional> struct fwd_or_bidi_tag_impl {
-  using type = std::forward_iterator_tag;
-};
-
-template <> struct fwd_or_bidi_tag_impl<true> {
-  using type = std::bidirectional_iterator_tag;
-};
-
-/// Helper which sets its type member to forward_iterator_tag if the category
-/// of \p IterT does not derive from bidirectional_iterator_tag, and to
-/// bidirectional_iterator_tag otherwise.
-template <typename IterT> struct fwd_or_bidi_tag {
-  using type = typename fwd_or_bidi_tag_impl<std::is_base_of<
-      std::bidirectional_iterator_tag,
-      typename std::iterator_traits<IterT>::iterator_category>::value>::type;
-};
-
-} // namespace detail
-
-/// Defines filter_iterator to a suitable specialization of
-/// filter_iterator_impl, based on the underlying iterator's category.
-template <typename WrappedIteratorT, typename PredicateT>
-using filter_iterator = filter_iterator_impl<
-    WrappedIteratorT, PredicateT,
-    typename detail::fwd_or_bidi_tag<WrappedIteratorT>::type>;
-
-/// Convenience function that takes a range of elements and a predicate,
-/// and return a new filter_iterator range.
-///
-/// FIXME: Currently if RangeT && is a rvalue reference to a temporary, the
-/// lifetime of that temporary is not kept by the returned range object, and the
-/// temporary is going to be dropped on the floor after the make_iterator_range
-/// full expression that contains this function call.
-template <typename RangeT, typename PredicateT>
-iterator_range<filter_iterator<detail::IterOfRange<RangeT>, PredicateT>>
-make_filter_range(RangeT &&Range, PredicateT Pred) {
-  using FilterIteratorT =
-      filter_iterator<detail::IterOfRange<RangeT>, PredicateT>;
-  return make_range(
-      FilterIteratorT(std::begin(std::forward<RangeT>(Range)),
-                      std::end(std::forward<RangeT>(Range)), Pred),
-      FilterIteratorT(std::end(std::forward<RangeT>(Range)),
-                      std::end(std::forward<RangeT>(Range)), Pred));
-}
-
-// forward declarations required by zip_shortest/zip_first
-template <typename R, typename UnaryPredicate>
-bool all_of(R &&range, UnaryPredicate P);
-template <typename R, typename UnaryPredicate>
-bool any_of(R &&range, UnaryPredicate P);
-
-namespace detail {
-
-using std::declval;
-
-// We have to alias this since inlining the actual type at the usage site
-// in the parameter list of iterator_facade_base<> below ICEs MSVC 2017.
-template<typename... Iters> struct ZipTupleType {
-  using type = std::tuple<decltype(*declval<Iters>())...>;
-};
-
-template <typename ZipType, typename... Iters>
-using zip_traits = iterator_facade_base<
-    ZipType, typename std::common_type<std::bidirectional_iterator_tag,
-                                       typename std::iterator_traits<
-                                           Iters>::iterator_category...>::type,
-    // ^ TODO: Implement random access methods.
-    typename ZipTupleType<Iters...>::type,
-    typename std::iterator_traits<typename std::tuple_element<
-        0, std::tuple<Iters...>>::type>::difference_type,
-    // ^ FIXME: This follows boost::make_zip_iterator's assumption that all
-    // inner iterators have the same difference_type. It would fail if, for
-    // instance, the second field's difference_type were non-numeric while the
-    // first is.
-    typename ZipTupleType<Iters...>::type *,
-    typename ZipTupleType<Iters...>::type>;
-
-template <typename ZipType, typename... Iters>
-struct zip_common : public zip_traits<ZipType, Iters...> {
-  using Base = zip_traits<ZipType, Iters...>;
-  using value_type = typename Base::value_type;
-
-  std::tuple<Iters...> iterators;
-
-protected:
-  template <size_t... Ns> value_type deref(std::index_sequence<Ns...>) const {
-    return value_type(*std::get<Ns>(iterators)...);
-  }
-
-  template <size_t... Ns>
-  decltype(iterators) tup_inc(std::index_sequence<Ns...>) const {
-    return std::tuple<Iters...>(std::next(std::get<Ns>(iterators))...);
-  }
-
-  template <size_t... Ns>
-  decltype(iterators) tup_dec(std::index_sequence<Ns...>) const {
-    return std::tuple<Iters...>(std::prev(std::get<Ns>(iterators))...);
-  }
-
-public:
-  zip_common(Iters &&... ts) : iterators(std::forward<Iters>(ts)...) {}
-
-  value_type operator*() { return deref(std::index_sequence_for<Iters...>{}); }
-
-  const value_type operator*() const {
-    return deref(std::index_sequence_for<Iters...>{});
-  }
-
-  ZipType &operator++() {
-    iterators = tup_inc(std::index_sequence_for<Iters...>{});
-    return *reinterpret_cast<ZipType *>(this);
-  }
-
-  ZipType &operator--() {
-    static_assert(Base::IsBidirectional,
-                  "All inner iterators must be at least bidirectional.");
-    iterators = tup_dec(std::index_sequence_for<Iters...>{});
-    return *reinterpret_cast<ZipType *>(this);
-  }
-};
-
-template <typename... Iters>
-struct zip_first : public zip_common<zip_first<Iters...>, Iters...> {
-  using Base = zip_common<zip_first<Iters...>, Iters...>;
-
-  bool operator==(const zip_first<Iters...> &other) const {
-    return std::get<0>(this->iterators) == std::get<0>(other.iterators);
-  }
-
-  zip_first(Iters &&... ts) : Base(std::forward<Iters>(ts)...) {}
-};
-
-template <typename... Iters>
-class zip_shortest : public zip_common<zip_shortest<Iters...>, Iters...> {
-  template <size_t... Ns>
-  bool test(const zip_shortest<Iters...> &other, std::index_sequence<Ns...>) const {
-    return all_of(std::initializer_list<bool>{std::get<Ns>(this->iterators) !=
-                                              std::get<Ns>(other.iterators)...},
-                  identity<bool>{});
-  }
-
-public:
-  using Base = zip_common<zip_shortest<Iters...>, Iters...>;
-
-  zip_shortest(Iters &&... ts) : Base(std::forward<Iters>(ts)...) {}
-
-  bool operator==(const zip_shortest<Iters...> &other) const {
-    return !test(other, std::index_sequence_for<Iters...>{});
-  }
-};
-
-template <template <typename...> class ItType, typename... Args> class zippy {
-public:
-  using iterator = ItType<decltype(std::begin(std::declval<Args>()))...>;
-  using iterator_category = typename iterator::iterator_category;
-  using value_type = typename iterator::value_type;
-  using difference_type = typename iterator::difference_type;
-  using pointer = typename iterator::pointer;
-  using reference = typename iterator::reference;
-
-private:
-  std::tuple<Args...> ts;
-
-  template <size_t... Ns> iterator begin_impl(std::index_sequence<Ns...>) const {
-    return iterator(std::begin(std::get<Ns>(ts))...);
-  }
-  template <size_t... Ns> iterator end_impl(std::index_sequence<Ns...>) const {
-    return iterator(std::end(std::get<Ns>(ts))...);
-  }
-
-public:
-  zippy(Args &&... ts_) : ts(std::forward<Args>(ts_)...) {}
-
-  iterator begin() const { return begin_impl(std::index_sequence_for<Args...>{}); }
-  iterator end() const { return end_impl(std::index_sequence_for<Args...>{}); }
-};
-
-} // end namespace detail
-
-/// zip iterator for two or more iteratable types.
-template <typename T, typename U, typename... Args>
-detail::zippy<detail::zip_shortest, T, U, Args...> zip(T &&t, U &&u,
-                                                       Args &&... args) {
-  return detail::zippy<detail::zip_shortest, T, U, Args...>(
-      std::forward<T>(t), std::forward<U>(u), std::forward<Args>(args)...);
-}
-
-/// zip iterator that, for the sake of efficiency, assumes the first iteratee to
-/// be the shortest.
-template <typename T, typename U, typename... Args>
-detail::zippy<detail::zip_first, T, U, Args...> zip_first(T &&t, U &&u,
-                                                          Args &&... args) {
-  return detail::zippy<detail::zip_first, T, U, Args...>(
-      std::forward<T>(t), std::forward<U>(u), std::forward<Args>(args)...);
-}
-
-namespace detail {
-template <typename Iter>
-static Iter next_or_end(const Iter &I, const Iter &End) {
-  if (I == End)
-    return End;
-  return std::next(I);
-}
-
-template <typename Iter>
-static auto deref_or_none(const Iter &I, const Iter &End)
-    -> std::optional<typename std::remove_const<
-        typename std::remove_reference<decltype(*I)>::type>::type> {
-  if (I == End)
-    return std::nullopt;
-  return *I;
-}
-
-template <typename Iter> struct ZipLongestItemType {
-  using type =
-      std::optional<typename std::remove_const<typename std::remove_reference<
-          decltype(*std::declval<Iter>())>::type>::type>;
-};
-
-template <typename... Iters> struct ZipLongestTupleType {
-  using type = std::tuple<typename ZipLongestItemType<Iters>::type...>;
-};
-
-template <typename... Iters>
-class zip_longest_iterator
-    : public iterator_facade_base<
-          zip_longest_iterator<Iters...>,
-          typename std::common_type<
-              std::forward_iterator_tag,
-              typename std::iterator_traits<Iters>::iterator_category...>::type,
-          typename ZipLongestTupleType<Iters...>::type,
-          typename std::iterator_traits<typename std::tuple_element<
-              0, std::tuple<Iters...>>::type>::difference_type,
-          typename ZipLongestTupleType<Iters...>::type *,
-          typename ZipLongestTupleType<Iters...>::type> {
-public:
-  using value_type = typename ZipLongestTupleType<Iters...>::type;
-
-private:
-  std::tuple<Iters...> iterators;
-  std::tuple<Iters...> end_iterators;
-
-  template <size_t... Ns>
-  bool test(const zip_longest_iterator<Iters...> &other,
-            std::index_sequence<Ns...>) const {
-    return wpi::any_of(
-        std::initializer_list<bool>{std::get<Ns>(this->iterators) !=
-                                    std::get<Ns>(other.iterators)...},
-        identity<bool>{});
-  }
-
-  template <size_t... Ns> value_type deref(std::index_sequence<Ns...>) const {
-    return value_type(
-        deref_or_none(std::get<Ns>(iterators), std::get<Ns>(end_iterators))...);
-  }
-
-  template <size_t... Ns>
-  decltype(iterators) tup_inc(std::index_sequence<Ns...>) const {
-    return std::tuple<Iters...>(
-        next_or_end(std::get<Ns>(iterators), std::get<Ns>(end_iterators))...);
-  }
-
-public:
-  zip_longest_iterator(std::pair<Iters &&, Iters &&>... ts)
-      : iterators(std::forward<Iters>(ts.first)...),
-        end_iterators(std::forward<Iters>(ts.second)...) {}
-
-  value_type operator*() { return deref(std::index_sequence_for<Iters...>{}); }
-
-  value_type operator*() const { return deref(std::index_sequence_for<Iters...>{}); }
-
-  zip_longest_iterator<Iters...> &operator++() {
-    iterators = tup_inc(std::index_sequence_for<Iters...>{});
-    return *this;
-  }
-
-  bool operator==(const zip_longest_iterator<Iters...> &other) const {
-    return !test(other, std::index_sequence_for<Iters...>{});
-  }
-};
-
-template <typename... Args> class zip_longest_range {
-public:
-  using iterator =
-      zip_longest_iterator<decltype(adl_begin(std::declval<Args>()))...>;
-  using iterator_category = typename iterator::iterator_category;
-  using value_type = typename iterator::value_type;
-  using difference_type = typename iterator::difference_type;
-  using pointer = typename iterator::pointer;
-  using reference = typename iterator::reference;
-
-private:
-  std::tuple<Args...> ts;
-
-  template <size_t... Ns> iterator begin_impl(std::index_sequence<Ns...>) const {
-    return iterator(std::make_pair(adl_begin(std::get<Ns>(ts)),
-                                   adl_end(std::get<Ns>(ts)))...);
-  }
-
-  template <size_t... Ns> iterator end_impl(std::index_sequence<Ns...>) const {
-    return iterator(std::make_pair(adl_end(std::get<Ns>(ts)),
-                                   adl_end(std::get<Ns>(ts)))...);
-  }
-
-public:
-  zip_longest_range(Args &&... ts_) : ts(std::forward<Args>(ts_)...) {}
-
-  iterator begin() const { return begin_impl(std::index_sequence_for<Args...>{}); }
-  iterator end() const { return end_impl(std::index_sequence_for<Args...>{}); }
-};
-} // namespace detail
-
-/// Iterate over two or more iterators at the same time. Iteration continues
-/// until all iterators reach the end. The std::optional only contains a value
-/// if the iterator has not reached the end.
-template <typename T, typename U, typename... Args>
-detail::zip_longest_range<T, U, Args...> zip_longest(T &&t, U &&u,
-                                                     Args &&... args) {
-  return detail::zip_longest_range<T, U, Args...>(
-      std::forward<T>(t), std::forward<U>(u), std::forward<Args>(args)...);
-}
-
-/// Iterator wrapper that concatenates sequences together.
-///
-/// This can concatenate different iterators, even with different types, into
-/// a single iterator provided the value types of all the concatenated
-/// iterators expose `reference` and `pointer` types that can be converted to
-/// `ValueT &` and `ValueT *` respectively. It doesn't support more
-/// interesting/customized pointer or reference types.
-///
-/// Currently this only supports forward or higher iterator categories as
-/// inputs and always exposes a forward iterator interface.
-template <typename ValueT, typename... IterTs>
-class concat_iterator
-    : public iterator_facade_base<concat_iterator<ValueT, IterTs...>,
-                                  std::forward_iterator_tag, ValueT> {
-  using BaseT = typename concat_iterator::iterator_facade_base;
-
-  /// We store both the current and end iterators for each concatenated
-  /// sequence in a tuple of pairs.
-  ///
-  /// Note that something like iterator_range seems nice at first here, but the
-  /// range properties are of little benefit and end up getting in the way
-  /// because we need to do mutation on the current iterators.
-  std::tuple<IterTs...> Begins;
-  std::tuple<IterTs...> Ends;
-
-  /// Attempts to increment a specific iterator.
-  ///
-  /// Returns true if it was able to increment the iterator. Returns false if
-  /// the iterator is already at the end iterator.
-  template <size_t Index> bool incrementHelper() {
-    auto &Begin = std::get<Index>(Begins);
-    auto &End = std::get<Index>(Ends);
-    if (Begin == End)
-      return false;
-
-    ++Begin;
-    return true;
-  }
-
-  /// Increments the first non-end iterator.
-  ///
-  /// It is an error to call this with all iterators at the end.
-  template <size_t... Ns> void increment(std::index_sequence<Ns...>) {
-    // Build a sequence of functions to increment each iterator if possible.
-    bool (concat_iterator::*IncrementHelperFns[])() = {
-        &concat_iterator::incrementHelper<Ns>...};
-
-    // Loop over them, and stop as soon as we succeed at incrementing one.
-    for (auto &IncrementHelperFn : IncrementHelperFns)
-      if ((this->*IncrementHelperFn)())
-        return;
-
-    assert(false && "Attempted to increment an end concat iterator!");
-  }
-
-  /// Returns null if the specified iterator is at the end. Otherwise,
-  /// dereferences the iterator and returns the address of the resulting
-  /// reference.
-  template <size_t Index> ValueT *getHelper() const {
-    auto &Begin = std::get<Index>(Begins);
-    auto &End = std::get<Index>(Ends);
-    if (Begin == End)
-      return nullptr;
-
-    return &*Begin;
-  }
-
-  /// Finds the first non-end iterator, dereferences, and returns the resulting
-  /// reference.
-  ///
-  /// It is an error to call this with all iterators at the end.
-  template <size_t... Ns> ValueT &get(std::index_sequence<Ns...>) const {
-    // Build a sequence of functions to get from iterator if possible.
-    ValueT *(concat_iterator::*GetHelperFns[])() const = {
-        &concat_iterator::getHelper<Ns>...};
-
-    // Loop over them, and return the first result we find.
-    for (auto &GetHelperFn : GetHelperFns)
-      if (ValueT *P = (this->*GetHelperFn)())
-        return *P;
-
-    assert(false && "Attempted to get a pointer from an end concat iterator!");
-  }
-
-public:
-  /// Constructs an iterator from a squence of ranges.
-  ///
-  /// We need the full range to know how to switch between each of the
-  /// iterators.
-  template <typename... RangeTs>
-  explicit concat_iterator(RangeTs &&... Ranges)
-      : Begins(std::begin(Ranges)...), Ends(std::end(Ranges)...) {}
-
-  using BaseT::operator++;
-
-  concat_iterator &operator++() {
-    increment(std::index_sequence_for<IterTs...>());
-    return *this;
-  }
-
-  ValueT &operator*() const { return get(std::index_sequence_for<IterTs...>()); }
-
-  bool operator==(const concat_iterator &RHS) const {
-    return Begins == RHS.Begins && Ends == RHS.Ends;
-  }
-};
-
-namespace detail {
-
-/// Helper to store a sequence of ranges being concatenated and access them.
-///
-/// This is designed to facilitate providing actual storage when temporaries
-/// are passed into the constructor such that we can use it as part of range
-/// based for loops.
-template <typename ValueT, typename... RangeTs> class concat_range {
-public:
-  using iterator =
-      concat_iterator<ValueT,
-                      decltype(std::begin(std::declval<RangeTs &>()))...>;
-
-private:
-  std::tuple<RangeTs...> Ranges;
-
-  template <size_t... Ns> iterator begin_impl(std::index_sequence<Ns...>) {
-    return iterator(std::get<Ns>(Ranges)...);
-  }
-  template <size_t... Ns> iterator end_impl(std::index_sequence<Ns...>) {
-    return iterator(make_range(std::end(std::get<Ns>(Ranges)),
-                               std::end(std::get<Ns>(Ranges)))...);
-  }
-
-public:
-  concat_range(RangeTs &&... Ranges)
-      : Ranges(std::forward<RangeTs>(Ranges)...) {}
-
-  iterator begin() { return begin_impl(std::index_sequence_for<RangeTs...>{}); }
-  iterator end() { return end_impl(std::index_sequence_for<RangeTs...>{}); }
-};
-
-} // end namespace detail
-
-/// Concatenated range across two or more ranges.
-///
-/// The desired value type must be explicitly specified.
-template <typename ValueT, typename... RangeTs>
-detail::concat_range<ValueT, RangeTs...> concat(RangeTs &&... Ranges) {
-  static_assert(sizeof...(RangeTs) > 1,
-                "Need more than one range to concatenate!");
-  return detail::concat_range<ValueT, RangeTs...>(
-      std::forward<RangeTs>(Ranges)...);
-}
-
-//===----------------------------------------------------------------------===//
-//     Extra additions to <utility>
-//===----------------------------------------------------------------------===//
-
-/// Function object to check whether the first component of a std::pair
-/// compares less than the first component of another std::pair.
-struct less_first {
-  template <typename T> bool operator()(const T &lhs, const T &rhs) const {
-    return lhs.first < rhs.first;
-  }
-};
-
-/// Function object to check whether the second component of a std::pair
-/// compares less than the second component of another std::pair.
-struct less_second {
-  template <typename T> bool operator()(const T &lhs, const T &rhs) const {
-    return lhs.second < rhs.second;
-  }
-};
-
-/// \brief Function object to apply a binary function to the first component of
-/// a std::pair.
-template<typename FuncTy>
-struct on_first {
-  FuncTy func;
-
-  template <typename T>
-  auto operator()(const T &lhs, const T &rhs) const
-      -> decltype(func(lhs.first, rhs.first)) {
-    return func(lhs.first, rhs.first);
-  }
-};
-
-/// Utility type to build an inheritance chain that makes it easy to rank
-/// overload candidates.
-template <int N> struct rank : rank<N - 1> {};
-template <> struct rank<0> {};
-
-/// traits class for checking whether type T is one of any of the given
-/// types in the variadic list.
-template <typename T, typename... Ts> struct is_one_of {
-  static const bool value = false;
-};
-
-template <typename T, typename U, typename... Ts>
-struct is_one_of<T, U, Ts...> {
-  static const bool value =
-      std::is_same<T, U>::value || is_one_of<T, Ts...>::value;
-};
-
-/// traits class for checking whether type T is a base class for all
-///  the given types in the variadic list.
-template <typename T, typename... Ts> struct are_base_of {
-  static const bool value = true;
-};
-
-template <typename T, typename U, typename... Ts>
-struct are_base_of<T, U, Ts...> {
-  static const bool value =
-      std::is_base_of<T, U>::value && are_base_of<T, Ts...>::value;
-};
-
-//===----------------------------------------------------------------------===//
-//     Extra additions for arrays
-//===----------------------------------------------------------------------===//
-
-/// Find the length of an array.
-template <class T, std::size_t N>
-constexpr inline size_t array_lengthof(T (&)[N]) {
-  return N;
-}
-
-/// Adapt std::less<T> for array_pod_sort.
-template<typename T>
-inline int array_pod_sort_comparator(const void *P1, const void *P2) {
-  if (std::less<T>()(*reinterpret_cast<const T*>(P1),
-                     *reinterpret_cast<const T*>(P2)))
-    return -1;
-  if (std::less<T>()(*reinterpret_cast<const T*>(P2),
-                     *reinterpret_cast<const T*>(P1)))
-    return 1;
-  return 0;
-}
-
-/// get_array_pod_sort_comparator - This is an internal helper function used to
-/// get type deduction of T right.
-template<typename T>
-inline int (*get_array_pod_sort_comparator(const T &))
-             (const void*, const void*) {
-  return array_pod_sort_comparator<T>;
-}
-
-/// array_pod_sort - This sorts an array with the specified start and end
-/// extent.  This is just like std::sort, except that it calls qsort instead of
-/// using an inlined template.  qsort is slightly slower than std::sort, but
-/// most sorts are not performance critical in LLVM and std::sort has to be
-/// template instantiated for each type, leading to significant measured code
-/// bloat.  This function should generally be used instead of std::sort where
-/// possible.
-///
-/// This function assumes that you have simple POD-like types that can be
-/// compared with std::less and can be moved with memcpy.  If this isn't true,
-/// you should use std::sort.
-///
-/// NOTE: If qsort_r were portable, we could allow a custom comparator and
-/// default to std::less.
-template<class IteratorTy>
-inline void array_pod_sort(IteratorTy Start, IteratorTy End) {
-  // Don't inefficiently call qsort with one element or trigger undefined
-  // behavior with an empty sequence.
-  auto NElts = End - Start;
-  if (NElts <= 1) return;
-  qsort(&*Start, NElts, sizeof(*Start), get_array_pod_sort_comparator(*Start));
-}
-
-template <class IteratorTy>
-inline void array_pod_sort(
-    IteratorTy Start, IteratorTy End,
-    int (*Compare)(
-        const typename std::iterator_traits<IteratorTy>::value_type *,
-        const typename std::iterator_traits<IteratorTy>::value_type *)) {
-  // Don't inefficiently call qsort with one element or trigger undefined
-  // behavior with an empty sequence.
-  auto NElts = End - Start;
-  if (NElts <= 1) return;
-  qsort(&*Start, NElts, sizeof(*Start),
-        reinterpret_cast<int (*)(const void *, const void *)>(Compare));
-}
-
-//===----------------------------------------------------------------------===//
-//     Extra additions to <algorithm>
-//===----------------------------------------------------------------------===//
-
-/// For a container of pointers, deletes the pointers and then clears the
-/// container.
-template<typename Container>
-void DeleteContainerPointers(Container &C) {
-  for (auto V : C)
-    delete V;
-  C.clear();
-}
-
-/// In a container of pairs (usually a map) whose second element is a pointer,
-/// deletes the second elements and then clears the container.
-template<typename Container>
-void DeleteContainerSeconds(Container &C) {
-  for (auto &V : C)
-    delete V.second;
-  C.clear();
-}
-
-/// Get the size of a range. This is a wrapper function around std::distance
-/// which is only enabled when the operation is O(1).
-template <typename R>
-auto size(R &&Range, typename std::enable_if<
-                         std::is_same<typename std::iterator_traits<decltype(
-                                          Range.begin())>::iterator_category,
-                                      std::random_access_iterator_tag>::value,
-                         void>::type * = nullptr)
-    -> decltype(std::distance(Range.begin(), Range.end())) {
-  return std::distance(Range.begin(), Range.end());
-}
-
-/// Provide wrappers to std::for_each which take ranges instead of having to
-/// pass begin/end explicitly.
-template <typename R, typename UnaryPredicate>
-UnaryPredicate for_each(R &&Range, UnaryPredicate P) {
-  return std::for_each(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Provide wrappers to std::all_of which take ranges instead of having to pass
-/// begin/end explicitly.
-template <typename R, typename UnaryPredicate>
-bool all_of(R &&Range, UnaryPredicate P) {
-  return std::all_of(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Provide wrappers to std::any_of which take ranges instead of having to pass
-/// begin/end explicitly.
-template <typename R, typename UnaryPredicate>
-bool any_of(R &&Range, UnaryPredicate P) {
-  return std::any_of(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Provide wrappers to std::none_of which take ranges instead of having to pass
-/// begin/end explicitly.
-template <typename R, typename UnaryPredicate>
-bool none_of(R &&Range, UnaryPredicate P) {
-  return std::none_of(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Provide wrappers to std::find which take ranges instead of having to pass
-/// begin/end explicitly.
-template <typename R, typename T>
-auto find(R &&Range, const T &Val) -> decltype(adl_begin(Range)) {
-  return std::find(adl_begin(Range), adl_end(Range), Val);
-}
-
-/// Provide wrappers to std::find_if which take ranges instead of having to pass
-/// begin/end explicitly.
-template <typename R, typename UnaryPredicate>
-auto find_if(R &&Range, UnaryPredicate P) -> decltype(adl_begin(Range)) {
-  return std::find_if(adl_begin(Range), adl_end(Range), P);
-}
-
-template <typename R, typename UnaryPredicate>
-auto find_if_not(R &&Range, UnaryPredicate P) -> decltype(adl_begin(Range)) {
-  return std::find_if_not(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Provide wrappers to std::remove_if which take ranges instead of having to
-/// pass begin/end explicitly.
-template <typename R, typename UnaryPredicate>
-auto remove_if(R &&Range, UnaryPredicate P) -> decltype(adl_begin(Range)) {
-  return std::remove_if(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Provide wrappers to std::copy_if which take ranges instead of having to
-/// pass begin/end explicitly.
-template <typename R, typename OutputIt, typename UnaryPredicate>
-OutputIt copy_if(R &&Range, OutputIt Out, UnaryPredicate P) {
-  return std::copy_if(adl_begin(Range), adl_end(Range), Out, P);
-}
-
-template <typename R, typename OutputIt>
-OutputIt copy(R &&Range, OutputIt Out) {
-  return std::copy(adl_begin(Range), adl_end(Range), Out);
-}
-
-/// Wrapper function around std::find to detect if an element exists
-/// in a container.
-template <typename R, typename E>
-bool is_contained(R &&Range, const E &Element) {
-  return std::find(adl_begin(Range), adl_end(Range), Element) != adl_end(Range);
-}
-
-/// Wrapper function around std::count to count the number of times an element
-/// \p Element occurs in the given range \p Range.
-template <typename R, typename E>
-auto count(R &&Range, const E &Element) ->
-    typename std::iterator_traits<decltype(adl_begin(Range))>::difference_type {
-  return std::count(adl_begin(Range), adl_end(Range), Element);
-}
-
-/// Wrapper function around std::count_if to count the number of times an
-/// element satisfying a given predicate occurs in a range.
-template <typename R, typename UnaryPredicate>
-auto count_if(R &&Range, UnaryPredicate P) ->
-    typename std::iterator_traits<decltype(adl_begin(Range))>::difference_type {
-  return std::count_if(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Wrapper function around std::transform to apply a function to a range and
-/// store the result elsewhere.
-template <typename R, typename OutputIt, typename UnaryPredicate>
-OutputIt transform(R &&Range, OutputIt d_first, UnaryPredicate P) {
-  return std::transform(adl_begin(Range), adl_end(Range), d_first, P);
-}
-
-/// Provide wrappers to std::partition which take ranges instead of having to
-/// pass begin/end explicitly.
-template <typename R, typename UnaryPredicate>
-auto partition(R &&Range, UnaryPredicate P) -> decltype(adl_begin(Range)) {
-  return std::partition(adl_begin(Range), adl_end(Range), P);
-}
-
-/// Provide wrappers to std::lower_bound which take ranges instead of having to
-/// pass begin/end explicitly.
-template <typename R, typename ForwardIt>
-auto lower_bound(R &&Range, ForwardIt I) -> decltype(adl_begin(Range)) {
-  return std::lower_bound(adl_begin(Range), adl_end(Range), I);
-}
-
-template <typename R, typename ForwardIt, typename Compare>
-auto lower_bound(R &&Range, ForwardIt I, Compare C)
-    -> decltype(adl_begin(Range)) {
-  return std::lower_bound(adl_begin(Range), adl_end(Range), I, C);
-}
-
-/// Provide wrappers to std::upper_bound which take ranges instead of having to
-/// pass begin/end explicitly.
-template <typename R, typename ForwardIt>
-auto upper_bound(R &&Range, ForwardIt I) -> decltype(adl_begin(Range)) {
-  return std::upper_bound(adl_begin(Range), adl_end(Range), I);
-}
-
-template <typename R, typename ForwardIt, typename Compare>
-auto upper_bound(R &&Range, ForwardIt I, Compare C)
-    -> decltype(adl_begin(Range)) {
-  return std::upper_bound(adl_begin(Range), adl_end(Range), I, C);
-}
-/// Wrapper function around std::equal to detect if all elements
-/// in a container are same.
-template <typename R>
-bool is_splat(R &&Range) {
-  size_t range_size = size(Range);
-  return range_size != 0 && (range_size == 1 ||
-         std::equal(adl_begin(Range) + 1, adl_end(Range), adl_begin(Range)));
-}
-
-/// Given a range of type R, iterate the entire range and return a
-/// SmallVector with elements of the vector.  This is useful, for example,
-/// when you want to iterate a range and then sort the results.
-template <unsigned Size, typename R>
-SmallVector<typename std::remove_const<detail::ValueOfRange<R>>::type, Size>
-to_vector(R &&Range) {
-  return {adl_begin(Range), adl_end(Range)};
-}
-
-/// Provide a container algorithm similar to C++ Library Fundamentals v2's
-/// `erase_if` which is equivalent to:
-///
-///   C.erase(remove_if(C, pred), C.end());
-///
-/// This version works for any container with an erase method call accepting
-/// two iterators.
-template <typename Container, typename UnaryPredicate>
-void erase_if(Container &C, UnaryPredicate P) {
-  C.erase(remove_if(C, P), C.end());
-}
-
-//===----------------------------------------------------------------------===//
-//     Extra additions to <memory>
-//===----------------------------------------------------------------------===//
-
-struct FreeDeleter {
-  void operator()(void* v) {
-    ::free(v);
-  }
-};
-
-template<typename First, typename Second>
-struct pair_hash {
-  size_t operator()(const std::pair<First, Second> &P) const {
-    return std::hash<First>()(P.first) * 31 + std::hash<Second>()(P.second);
-  }
-};
-
-/// Binary functor that adapts to any other binary functor after dereferencing
-/// operands.
-template <typename T> struct deref {
-  T func;
-
-  // Could be further improved to cope with non-derivable functors and
-  // non-binary functors (should be a variadic template member function
-  // operator()).
-  template <typename A, typename B>
-  auto operator()(A &lhs, B &rhs) const -> decltype(func(*lhs, *rhs)) {
-    assert(lhs);
-    assert(rhs);
-    return func(*lhs, *rhs);
-  }
-};
-
-namespace detail {
-
-template <typename R> class enumerator_iter;
-
-template <typename R> struct result_pair {
-  friend class enumerator_iter<R>;
-
-  result_pair() = default;
-  result_pair(std::size_t Index, IterOfRange<R> Iter)
-      : Index(Index), Iter(Iter) {}
-
-  result_pair<R> &operator=(const result_pair<R> &Other) {
-    Index = Other.Index;
-    Iter = Other.Iter;
-    return *this;
-  }
-
-  std::size_t index() const { return Index; }
-  const ValueOfRange<R> &value() const { return *Iter; }
-  ValueOfRange<R> &value() { return *Iter; }
-
-private:
-  std::size_t Index = (std::numeric_limits<std::size_t>::max)();
-  IterOfRange<R> Iter;
-};
-
-template <typename R>
-class enumerator_iter
-    : public iterator_facade_base<
-          enumerator_iter<R>, std::forward_iterator_tag, result_pair<R>,
-          typename std::iterator_traits<IterOfRange<R>>::difference_type,
-          typename std::iterator_traits<IterOfRange<R>>::pointer,
-          typename std::iterator_traits<IterOfRange<R>>::reference> {
-  using result_type = result_pair<R>;
-
-public:
-  explicit enumerator_iter(IterOfRange<R> EndIter)
-      : Result((std::numeric_limits<size_t>::max)(), EndIter) {}
-
-  enumerator_iter(std::size_t Index, IterOfRange<R> Iter)
-      : Result(Index, Iter) {}
-
-  result_type &operator*() { return Result; }
-  const result_type &operator*() const { return Result; }
-
-  enumerator_iter<R> &operator++() {
-    assert(Result.Index != (std::numeric_limits<size_t>::max)());
-    ++Result.Iter;
-    ++Result.Index;
-    return *this;
-  }
-
-  bool operator==(const enumerator_iter<R> &RHS) const {
-    // Don't compare indices here, only iterators.  It's possible for an end
-    // iterator to have different indices depending on whether it was created
-    // by calling std::end() versus incrementing a valid iterator.
-    return Result.Iter == RHS.Result.Iter;
-  }
-
-  enumerator_iter<R> &operator=(const enumerator_iter<R> &Other) {
-    Result = Other.Result;
-    return *this;
-  }
-
-private:
-  result_type Result;
-};
-
-template <typename R> class enumerator {
-public:
-  explicit enumerator(R &&Range) : TheRange(std::forward<R>(Range)) {}
-
-  enumerator_iter<R> begin() {
-    return enumerator_iter<R>(0, std::begin(TheRange));
-  }
-
-  enumerator_iter<R> end() {
-    return enumerator_iter<R>(std::end(TheRange));
-  }
-
-private:
-  R TheRange;
-};
-
-} // end namespace detail
-
-/// Given an input range, returns a new range whose values are are pair (A,B)
-/// such that A is the 0-based index of the item in the sequence, and B is
-/// the value from the original sequence.  Example:
-///
-/// std::vector<char> Items = {'A', 'B', 'C', 'D'};
-/// for (auto X : enumerate(Items)) {
-///   printf("Item %d - %c\n", X.index(), X.value());
-/// }
-///
-/// Output:
-///   Item 0 - A
-///   Item 1 - B
-///   Item 2 - C
-///   Item 3 - D
-///
-template <typename R> detail::enumerator<R> enumerate(R &&TheRange) {
-  return detail::enumerator<R>(std::forward<R>(TheRange));
-}
-
-/// Return true if the sequence [Begin, End) has exactly N items. Runs in O(N)
-/// time. Not meant for use with random-access iterators.
-template <typename IterTy>
-bool hasNItems(
-    IterTy &&Begin, IterTy &&End, unsigned N,
-    typename std::enable_if<
-        !std::is_same<
-            typename std::iterator_traits<typename std::remove_reference<
-                decltype(Begin)>::type>::iterator_category,
-            std::random_access_iterator_tag>::value,
-        void>::type * = nullptr) {
-  for (; N; --N, ++Begin)
-    if (Begin == End)
-      return false; // Too few.
-  return Begin == End;
-}
-
-/// Return true if the sequence [Begin, End) has N or more items. Runs in O(N)
-/// time. Not meant for use with random-access iterators.
-template <typename IterTy>
-bool hasNItemsOrMore(
-    IterTy &&Begin, IterTy &&End, unsigned N,
-    typename std::enable_if<
-        !std::is_same<
-            typename std::iterator_traits<typename std::remove_reference<
-                decltype(Begin)>::type>::iterator_category,
-            std::random_access_iterator_tag>::value,
-        void>::type * = nullptr) {
-  for (; N; --N, ++Begin)
-    if (Begin == End)
-      return false; // Too few.
-  return true;
-}
-
-} // end namespace wpi
-
-#endif // LLVM_ADT_STLEXTRAS_H
diff --git a/wpiutil/src/main/native/include/wpi/SafeThread.h b/wpiutil/src/main/native/include/wpi/SafeThread.h
index d4b48f7..bf37732 100644
--- a/wpiutil/src/main/native/include/wpi/SafeThread.h
+++ b/wpiutil/src/main/native/include/wpi/SafeThread.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_SAFETHREAD_H_
 #define WPIUTIL_WPI_SAFETHREAD_H_
@@ -18,7 +15,9 @@
 
 namespace wpi {
 
-// Base class for SafeThreadOwner threads.
+/**
+ * Base class for SafeThreadOwner threads.
+ */
 class SafeThread {
  public:
   virtual ~SafeThread() = default;
@@ -32,7 +31,9 @@
 
 namespace detail {
 
-// Non-template proxy base class for common proxy code.
+/**
+ * Non-template proxy base class for common proxy code.
+ */
 class SafeThreadProxyBase {
  public:
   explicit SafeThreadProxyBase(std::shared_ptr<SafeThread> thr);
@@ -44,8 +45,11 @@
   std::unique_lock<wpi::mutex> m_lock;
 };
 
-// A proxy for SafeThread.
-// Also serves as a scoped lock on SafeThread::m_mutex.
+/**
+ * A proxy for SafeThread.
+ *
+ * Also serves as a scoped lock on SafeThread::m_mutex.
+ */
 template <typename T>
 class SafeThreadProxy : public SafeThreadProxyBase {
  public:
@@ -55,7 +59,9 @@
   T* operator->() const { return static_cast<T*>(m_thread.get()); }
 };
 
-// Non-template owner base class for common owner code.
+/**
+ * Non-template owner base class for common owner code.
+ */
 class SafeThreadOwnerBase {
  public:
   void Stop();
diff --git a/wpiutil/src/main/native/include/wpi/Signal.h b/wpiutil/src/main/native/include/wpi/Signal.h
index 64b774c..d4993d9 100644
--- a/wpiutil/src/main/native/include/wpi/Signal.h
+++ b/wpiutil/src/main/native/include/wpi/Signal.h
@@ -565,7 +565,7 @@
      *         signal object, it does not cover thread safety of potentially
      *         shared state used in slot functions.
      *
-     * @param a... arguments to emit
+     * @param a arguments to emit
      */
     template <typename... A>
     void operator()(A && ... a) const {
@@ -581,7 +581,6 @@
      * Safety: Thread-safety depends on locking policy.
      *
      * @param c a callable
-     * @return a Connection object that can be used to interact with the slot
      */
     template <typename Callable>
     void connect(Callable && c) {
diff --git a/wpiutil/src/main/native/include/wpi/SmallString.h b/wpiutil/src/main/native/include/wpi/SmallString.h
index 32969b1..1d44f24 100644
--- a/wpiutil/src/main/native/include/wpi/SmallString.h
+++ b/wpiutil/src/main/native/include/wpi/SmallString.h
@@ -15,8 +15,8 @@
 #define WPIUTIL_WPI_SMALLSTRING_H
 
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
 #include <cstddef>
+#include <string_view>
 
 namespace wpi {
 
@@ -28,8 +28,9 @@
   /// Default ctor - Initialize to empty.
   SmallString() = default;
 
-  /// Initialize from a StringRef.
-  SmallString(StringRef S) : SmallVector<char, InternalLen>(S.begin(), S.end()) {}
+  /// Initialize from a std::string_view.
+  SmallString(std::string_view S)
+    : SmallVector<char, InternalLen>(S.begin(), S.end()) {}
 
   /// Initialize with a range.
   template<typename ItTy>
@@ -54,8 +55,8 @@
     SmallVectorImpl<char>::append(S, E);
   }
 
-  /// Assign from a StringRef.
-  void assign(StringRef RHS) {
+  /// Assign from a std::string_view.
+  void assign(std::string_view RHS) {
     this->clear();
     SmallVectorImpl<char>::append(RHS.begin(), RHS.end());
   }
@@ -80,8 +81,8 @@
     SmallVectorImpl<char>::append(NumInputs, Elt);
   }
 
-  /// Append from a StringRef.
-  void append(StringRef RHS) {
+  /// Append from a std::string_view.
+  void append(std::string_view RHS) {
     SmallVectorImpl<char>::append(RHS.begin(), RHS.end());
   }
 
@@ -94,48 +95,12 @@
   /// @name String Comparison
   /// @{
 
-  /// Check for string equality.  This is more efficient than compare() when
-  /// the relative ordering of inequal strings isn't needed.
-  bool equals(StringRef RHS) const {
-    return str().equals(RHS);
-  }
-
-  /// Check for string equality, ignoring case.
-  bool equals_lower(StringRef RHS) const {
-    return str().equals_lower(RHS);
-  }
-
   /// Compare two strings; the result is -1, 0, or 1 if this string is
   /// lexicographically less than, equal to, or greater than the \p RHS.
-  int compare(StringRef RHS) const {
+  int compare(std::string_view RHS) const {
     return str().compare(RHS);
   }
 
-  /// compare_lower - Compare two strings, ignoring case.
-  int compare_lower(StringRef RHS) const {
-    return str().compare_lower(RHS);
-  }
-
-  /// compare_numeric - Compare two strings, treating sequences of digits as
-  /// numbers.
-  int compare_numeric(StringRef RHS) const {
-    return str().compare_numeric(RHS);
-  }
-
-  /// @}
-  /// @name String Predicates
-  /// @{
-
-  /// startswith - Check if this string starts with the given \p Prefix.
-  bool startswith(StringRef Prefix) const {
-    return str().startswith(Prefix);
-  }
-
-  /// endswith - Check if this string ends with the given \p Suffix.
-  bool endswith(StringRef Suffix) const {
-    return str().endswith(Suffix);
-  }
-
   /// @}
   /// @name String Searching
   /// @{
@@ -152,7 +117,7 @@
   ///
   /// \returns The index of the first occurrence of \p Str, or npos if not
   /// found.
-  size_t find(StringRef Str, size_t From = 0) const {
+  size_t find(std::string_view Str, size_t From = 0) const {
     return str().find(Str, From);
   }
 
@@ -160,7 +125,7 @@
   ///
   /// \returns The index of the last occurrence of \p C, or npos if not
   /// found.
-  size_t rfind(char C, size_t From = StringRef::npos) const {
+  size_t rfind(char C, size_t From = std::string_view::npos) const {
     return str().rfind(C, From);
   }
 
@@ -168,7 +133,7 @@
   ///
   /// \returns The index of the last occurrence of \p Str, or npos if not
   /// found.
-  size_t rfind(StringRef Str) const {
+  size_t rfind(std::string_view Str) const {
     return str().rfind(Str);
   }
 
@@ -182,7 +147,7 @@
   /// not found.
   ///
   /// Complexity: O(size() + Chars.size())
-  size_t find_first_of(StringRef Chars, size_t From = 0) const {
+  size_t find_first_of(std::string_view Chars, size_t From = 0) const {
     return str().find_first_of(Chars, From);
   }
 
@@ -196,13 +161,13 @@
   /// \p Chars, or npos if not found.
   ///
   /// Complexity: O(size() + Chars.size())
-  size_t find_first_not_of(StringRef Chars, size_t From = 0) const {
+  size_t find_first_not_of(std::string_view Chars, size_t From = 0) const {
     return str().find_first_not_of(Chars, From);
   }
 
   /// Find the last character in the string that is \p C, or npos if not
   /// found.
-  size_t find_last_of(char C, size_t From = StringRef::npos) const {
+  size_t find_last_of(char C, size_t From = std::string_view::npos) const {
     return str().find_last_of(C, From);
   }
 
@@ -211,26 +176,11 @@
   ///
   /// Complexity: O(size() + Chars.size())
   size_t find_last_of(
-      StringRef Chars, size_t From = StringRef::npos) const {
+      std::string_view Chars, size_t From = std::string_view::npos) const {
     return str().find_last_of(Chars, From);
   }
 
   /// @}
-  /// @name Helpful Algorithms
-  /// @{
-
-  /// Return the number of occurrences of \p C in the string.
-  size_t count(char C) const {
-    return str().count(C);
-  }
-
-  /// Return the number of non-overlapped occurrences of \p Str in the
-  /// string.
-  size_t count(StringRef Str) const {
-    return str().count(Str);
-  }
-
-  /// @}
   /// @name Substring Operations
   /// @{
 
@@ -243,28 +193,17 @@
   /// \param N The number of characters to included in the substring. If \p N
   /// exceeds the number of characters remaining in the string, the string
   /// suffix (starting with \p Start) will be returned.
-  StringRef substr(size_t Start, size_t N = StringRef::npos) const {
+  std::string_view substr(size_t Start, size_t N = std::string_view::npos) const {
     return str().substr(Start, N);
   }
 
-  /// Return a reference to the substring from [Start, End).
-  ///
-  /// \param Start The index of the starting character in the substring; if
-  /// the index is npos or greater than the length of the string then the
-  /// empty substring will be returned.
-  ///
-  /// \param End The index following the last character to include in the
-  /// substring. If this is npos, or less than \p Start, or exceeds the
-  /// number of characters remaining in the string, the string suffix
-  /// (starting with \p Start) will be returned.
-  StringRef slice(size_t Start, size_t End) const {
-    return str().slice(Start, End);
-  }
-
   // Extra methods.
 
-  /// Explicit conversion to StringRef.
-  StringRef str() const { return StringRef(this->begin(), this->size()); }
+  /// Explicit conversion to std::string_view.
+  std::string_view str() const { return {this->begin(), this->size()}; }
+
+  /// Explicit conversion to std::string.
+  std::string string() const { return {this->begin(), this->size()}; }
 
   // TODO: Make this const, if it's safe...
   const char* c_str() {
@@ -273,16 +212,19 @@
     return this->data();
   }
 
-  /// Implicit conversion to StringRef.
-  operator StringRef() const { return str(); }
+  /// Implicit conversion to std::string_view.
+  operator std::string_view() const { return str(); }
+
+  /// Implicit conversion to std::string.
+  operator std::string() const { return string(); }
 
   // Extra operators.
-  const SmallString &operator=(StringRef RHS) {
+  const SmallString &operator=(std::string_view RHS) {
     this->clear();
     return *this += RHS;
   }
 
-  SmallString &operator+=(StringRef RHS) {
+  SmallString &operator+=(std::string_view RHS) {
     this->append(RHS.begin(), RHS.end());
     return *this;
   }
diff --git a/wpiutil/src/main/native/include/wpi/SocketError.h b/wpiutil/src/main/native/include/wpi/SocketError.h
index d5f3ff0..b893ac6 100644
--- a/wpiutil/src/main/native/include/wpi/SocketError.h
+++ b/wpiutil/src/main/native/include/wpi/SocketError.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_SOCKETERROR_H_
 #define WPIUTIL_WPI_SOCKETERROR_H_
@@ -16,7 +13,7 @@
 
 std::string SocketStrerror(int code);
 
-static inline std::string SocketStrerror() {
+inline std::string SocketStrerror() {
   return SocketStrerror(SocketErrno());
 }
 
diff --git a/wpiutil/src/main/native/include/wpi/SpanExtras.h b/wpiutil/src/main/native/include/wpi/SpanExtras.h
new file mode 100644
index 0000000..41e4014
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/SpanExtras.h
@@ -0,0 +1,27 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <cassert>
+
+#include "wpi/span.h"
+
+namespace wpi {
+
+/// Drop the first \p N elements of the array.
+template <typename T>
+constexpr span<T> drop_front(span<T> in, typename span<T>::size_type n = 1) {
+  assert(in.size() >= n && "Dropping more elements than exist");
+  return in.subspan(n, in.size() - n);
+}
+
+/// Drop the last \p N elements of the array.
+template <typename T>
+constexpr span<T> drop_back(span<T> in, typename span<T>::size_type n = 1) {
+  assert(in.size() >= n && "Dropping more elements than exist");
+  return in.subspan(0, in.size() - n);
+}
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/StackTrace.h b/wpiutil/src/main/native/include/wpi/StackTrace.h
index 56a3770..f9ee989 100644
--- a/wpiutil/src/main/native/include/wpi/StackTrace.h
+++ b/wpiutil/src/main/native/include/wpi/StackTrace.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2008-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_STACKTRACE_H_
 #define WPIUTIL_WPI_STACKTRACE_H_
diff --git a/wpiutil/src/main/native/include/wpi/StringExtras.h b/wpiutil/src/main/native/include/wpi/StringExtras.h
index f420e2a..74874ff 100644
--- a/wpiutil/src/main/native/include/wpi/StringExtras.h
+++ b/wpiutil/src/main/native/include/wpi/StringExtras.h
@@ -1,3 +1,7 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
 //===- llvm/ADT/StringExtras.h - Useful string functions --------*- C++ -*-===//
 //
 //                     The LLVM Compiler Infrastructure
@@ -11,395 +15,651 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef WPIUTIL_WPI_STRINGEXTRAS_H
-#define WPIUTIL_WPI_STRINGEXTRAS_H
+#pragma once
 
-#include "wpi/ArrayRef.h"
-#include "wpi/SmallString.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-#include <cstdlib>
-#include <cstring>
-#include <iterator>
+#include <limits>
+#include <optional>
 #include <string>
+#include <string_view>
+#include <type_traits>
 #include <utility>
 
 namespace wpi {
 
-template<typename T> class SmallVectorImpl;
-class raw_ostream;
+template <typename T>
+class SmallVectorImpl;
 
 /// hexdigit - Return the hexadecimal character for the
 /// given number \p X (which should be less than 16).
-inline char hexdigit(unsigned X, bool LowerCase = false) {
+constexpr char hexdigit(unsigned X, bool LowerCase = false) noexcept {
   const char HexChar = LowerCase ? 'a' : 'A';
   return X < 10 ? '0' + X : HexChar + X - 10;
 }
 
-/// Construct a string ref from a boolean.
-inline StringRef toStringRef(bool B) { return StringRef(B ? "true" : "false"); }
-
-/// Construct a string ref from an array ref of unsigned chars.
-inline StringRef toStringRef(ArrayRef<uint8_t> Input) {
-  return StringRef(reinterpret_cast<const char *>(Input.begin()), Input.size());
-}
-
-/// Construct a string ref from an array ref of unsigned chars.
-inline ArrayRef<uint8_t> arrayRefFromStringRef(StringRef Input) {
-  return {Input.bytes_begin(), Input.bytes_end()};
-}
-
 /// Interpret the given character \p C as a hexadecimal digit and return its
 /// value.
 ///
 /// If \p C is not a valid hex digit, -1U is returned.
-inline unsigned hexDigitValue(char C) {
-  if (C >= '0' && C <= '9') return C-'0';
-  if (C >= 'a' && C <= 'f') return C-'a'+10U;
-  if (C >= 'A' && C <= 'F') return C-'A'+10U;
+constexpr unsigned hexDigitValue(char C) noexcept {
+  if (C >= '0' && C <= '9') {
+    return C - '0';
+  }
+  if (C >= 'a' && C <= 'f') {
+    return C - 'a' + 10U;
+  }
+  if (C >= 'A' && C <= 'F') {
+    return C - 'A' + 10U;
+  }
   return (std::numeric_limits<unsigned>::max)();
 }
 
 /// Checks if character \p C is one of the 10 decimal digits.
-inline bool isDigit(char C) { return C >= '0' && C <= '9'; }
+constexpr bool isDigit(char C) noexcept {
+  return C >= '0' && C <= '9';
+}
 
 /// Checks if character \p C is a hexadecimal numeric character.
-inline bool isHexDigit(char C) { return hexDigitValue(C) != (std::numeric_limits<unsigned>::max)(); }
+constexpr bool isHexDigit(char C) noexcept {
+  return hexDigitValue(C) != (std::numeric_limits<unsigned>::max)();
+}
 
 /// Checks if character \p C is a valid letter as classified by "C" locale.
-inline bool isAlpha(char C) {
+constexpr bool isAlpha(char C) noexcept {
   return ('a' <= C && C <= 'z') || ('A' <= C && C <= 'Z');
 }
 
 /// Checks whether character \p C is either a decimal digit or an uppercase or
 /// lowercase letter as classified by "C" locale.
-inline bool isAlnum(char C) { return isAlpha(C) || isDigit(C); }
+constexpr bool isAlnum(char C) noexcept {
+  return isAlpha(C) || isDigit(C);
+}
 
 /// Checks whether character \p C is valid ASCII (high bit is zero).
-inline bool isASCII(char C) { return static_cast<unsigned char>(C) <= 127; }
-
-/// Checks whether all characters in S are ASCII.
-inline bool isASCII(wpi::StringRef S) {
-  for (char C : S)
-    if (LLVM_UNLIKELY(!isASCII(C)))
-      return false;
-  return true;
+constexpr bool isASCII(char C) noexcept {
+  return static_cast<unsigned char>(C) <= 127;
 }
 
 /// Checks whether character \p C is printable.
 ///
 /// Locale-independent version of the C standard library isprint whose results
 /// may differ on different platforms.
-inline bool isPrint(char C) {
+constexpr bool isPrint(char C) noexcept {
   unsigned char UC = static_cast<unsigned char>(C);
   return (0x20 <= UC) && (UC <= 0x7E);
 }
 
 /// Returns the corresponding lowercase character if \p x is uppercase.
-inline char toLower(char x) {
-  if (x >= 'A' && x <= 'Z')
+constexpr char toLower(char x) noexcept {
+  if (x >= 'A' && x <= 'Z') {
     return x - 'A' + 'a';
+  }
   return x;
 }
 
 /// Returns the corresponding uppercase character if \p x is lowercase.
-inline char toUpper(char x) {
-  if (x >= 'a' && x <= 'z')
+constexpr char toUpper(char x) noexcept {
+  if (x >= 'a' && x <= 'z') {
     return x - 'a' + 'A';
+  }
   return x;
 }
 
-inline std::string utohexstr(uint64_t X, bool LowerCase = false) {
-  char Buffer[17];
-  char *BufPtr = std::end(Buffer);
+inline std::string utohexstr(unsigned long long val,  // NOLINT(runtime/int)
+                             bool lowerCase = false) {
+  char buf[17];
+  char* bufptr = std::end(buf);
 
-  if (X == 0) *--BufPtr = '0';
-
-  while (X) {
-    unsigned char Mod = static_cast<unsigned char>(X) & 15;
-    *--BufPtr = hexdigit(Mod, LowerCase);
-    X >>= 4;
+  if (val == 0) {
+    *--bufptr = '0';
   }
 
-  return std::string(BufPtr, std::end(Buffer));
-}
-
-/// Convert buffer \p Input to its hexadecimal representation.
-/// The returned string is double the size of \p Input.
-inline std::string toHex(StringRef Input, bool LowerCase = false) {
-  static const char *const LUT = "0123456789ABCDEF";
-  const uint8_t Offset = LowerCase ? 32 : 0;
-  size_t Length = Input.size();
-
-  std::string Output;
-  Output.reserve(2 * Length);
-  for (size_t i = 0; i < Length; ++i) {
-    const unsigned char c = Input[i];
-    Output.push_back(LUT[c >> 4] | Offset);
-    Output.push_back(LUT[c & 15] | Offset);
-  }
-  return Output;
-}
-
-inline std::string toHex(ArrayRef<uint8_t> Input, bool LowerCase = false) {
-  return toHex(toStringRef(Input), LowerCase);
-}
-
-inline uint8_t hexFromNibbles(char MSB, char LSB) {
-  unsigned U1 = hexDigitValue(MSB);
-  unsigned U2 = hexDigitValue(LSB);
-  assert(U1 != (std::numeric_limits<unsigned>::max)() && U2 != (std::numeric_limits<unsigned>::max)());
-
-  return static_cast<uint8_t>((U1 << 4) | U2);
-}
-
-/// Convert hexadecimal string \p Input to its binary representation.
-/// The return string is half the size of \p Input.
-inline std::string fromHex(StringRef Input) {
-  if (Input.empty())
-    return std::string();
-
-  std::string Output;
-  Output.reserve((Input.size() + 1) / 2);
-  if (Input.size() % 2 == 1) {
-    Output.push_back(hexFromNibbles('0', Input.front()));
-    Input = Input.drop_front();
+  while (val) {
+    unsigned char mod = static_cast<unsigned char>(val) & 15;
+    *--bufptr = hexdigit(mod, lowerCase);
+    val >>= 4;
   }
 
-  assert(Input.size() % 2 == 0);
-  while (!Input.empty()) {
-    uint8_t Hex = hexFromNibbles(Input[0], Input[1]);
-    Output.push_back(Hex);
-    Input = Input.drop_front(2);
-  }
-  return Output;
+  return std::string(bufptr, std::end(buf));
 }
 
-/// Convert the string \p S to an integer of the specified type using
-/// the radix \p Base.  If \p Base is 0, auto-detects the radix.
-/// Returns true if the number was successfully converted, false otherwise.
-template <typename N> bool to_integer(StringRef S, N &Num, unsigned Base = 0) {
-  return !S.getAsInteger(Base, Num);
+/**
+ * equals - Check for string equality, this is more efficient than
+ * compare() when the relative ordering of inequal strings isn't needed.
+ */
+constexpr bool equals(std::string_view lhs, std::string_view rhs) noexcept {
+  auto length = lhs.size();
+  return length == rhs.size() && std::string_view::traits_type::compare(
+                                     lhs.data(), rhs.data(), length) == 0;
+}
+
+/**
+ * compare_lower - Compare two strings, ignoring case.
+ */
+int compare_lower(std::string_view lhs, std::string_view rhs) noexcept;
+
+/**
+ * equals_lower - Check for string equality, ignoring case.
+ */
+constexpr bool equals_lower(std::string_view lhs,
+                            std::string_view rhs) noexcept {
+  return lhs.size() == rhs.size() && compare_lower(lhs, rhs) == 0;
+}
+
+/**
+ * Search for the first character @p ch in @p str, ignoring case.
+ *
+ * @returns The index of the first occurrence of @p ch, or npos if not
+ * found.
+ */
+std::string_view::size_type find_lower(
+    std::string_view str, char ch,
+    std::string_view::size_type from = 0) noexcept;
+
+/**
+ * Search for the first string @p other in @p str, ignoring case.
+ *
+ * @returns The index of the first occurrence of @p other, or npos if not
+ * found.
+ */
+std::string_view::size_type find_lower(
+    std::string_view str, std::string_view other,
+    std::string_view::size_type from = 0) noexcept;
+
+/**
+ * Search for the first string @p other in @p str, ignoring case.
+ *
+ * @returns The index of the first occurrence of @p other, or npos if not
+ * found.
+ */
+inline std::string_view::size_type find_lower(
+    std::string_view str, const char* other,
+    std::string_view::size_type from = 0) noexcept {
+  return find_lower(str, std::string_view{other}, from);
+}
+
+/**
+ * Search for the last character @p ch in @p str, ignoring case.
+ *
+ * @returns The index of the last occurrence of @p ch, or npos if not
+ * found.
+ */
+std::string_view::size_type rfind_lower(
+    std::string_view str, char ch,
+    std::string_view::size_type from = std::string_view::npos) noexcept;
+
+/**
+ * Search for the last string @p other in @p str, ignoring case.
+ *
+ * @returns The index of the last occurrence of @p other, or npos if not
+ * found.
+ */
+std::string_view::size_type rfind_lower(std::string_view str,
+                                        std::string_view other) noexcept;
+
+/**
+ * Search for the last string @p other in @p str, ignoring case.
+ *
+ * @returns The index of the last occurrence of @p other, or npos if not
+ * found.
+ */
+inline std::string_view::size_type rfind_lower(std::string_view str,
+                                               const char* other) noexcept {
+  return rfind_lower(str, std::string_view{other});
+}
+
+/**
+ * Checks if @p str starts with the given @p prefix.
+ */
+constexpr bool starts_with(std::string_view str,
+                           std::string_view prefix) noexcept {
+  return str.substr(0, prefix.size()) == prefix;
+}
+
+/**
+ * Checks if @p str starts with the given @p prefix.
+ */
+constexpr bool starts_with(std::string_view str, char prefix) noexcept {
+  return !str.empty() && std::string_view::traits_type::eq(str.front(), prefix);
+}
+
+/**
+ * Checks if @p str starts with the given @p prefix.
+ */
+constexpr bool starts_with(std::string_view str, const char* prefix) noexcept {
+  return starts_with(str, std::string_view(prefix));
+}
+
+/**
+ * Checks if @p str starts with the given @p prefix, ignoring case.
+ */
+bool starts_with_lower(std::string_view str, std::string_view prefix) noexcept;
+
+/**
+ * Checks if @p str starts with the given @p prefix, ignoring case.
+ */
+constexpr bool starts_with_lower(std::string_view str, char prefix) noexcept {
+  return !str.empty() && toLower(str.front()) == toLower(prefix);
+}
+
+/**
+ * Checks if @p str starts with the given @p prefix, ignoring case.
+ */
+inline bool starts_with_lower(std::string_view str,
+                              const char* prefix) noexcept {
+  return starts_with_lower(str, std::string_view(prefix));
+}
+
+/**
+ * Checks if @p str ends with the given @p suffix.
+ */
+constexpr bool ends_with(std::string_view str,
+                         std::string_view suffix) noexcept {
+  return str.size() >= suffix.size() &&
+         str.compare(str.size() - suffix.size(), std::string_view::npos,
+                     suffix) == 0;
+}
+
+/**
+ * Checks if @p str ends with the given @p suffix.
+ */
+constexpr bool ends_with(std::string_view str, char suffix) noexcept {
+  return !str.empty() && std::string_view::traits_type::eq(str.back(), suffix);
+}
+
+/**
+ * Checks if @p str ends with the given @p suffix.
+ */
+constexpr bool ends_with(std::string_view str, const char* suffix) noexcept {
+  return ends_with(str, std::string_view(suffix));
+}
+
+/**
+ * Checks if @p str ends with the given @p suffix, ignoring case.
+ */
+bool ends_with_lower(std::string_view str, std::string_view suffix) noexcept;
+
+/**
+ * Checks if @p str ends with the given @p suffix, ignoring case.
+ */
+constexpr bool ends_with_lower(std::string_view str, char suffix) noexcept {
+  return !str.empty() && toLower(str.back()) == toLower(suffix);
+}
+
+/**
+ * Checks if @p str ends with the given @p suffix, ignoring case.
+ */
+inline bool ends_with_lower(std::string_view str, const char* suffix) noexcept {
+  return ends_with_lower(str, std::string_view(suffix));
+}
+
+/**
+ * Checks if @p str contains the substring @p other.
+ */
+constexpr bool contains(std::string_view str, std::string_view other) noexcept {
+  return str.find(other) != std::string_view::npos;
+}
+
+/**
+ * Checks if @p str contains the substring @p other.
+ */
+constexpr bool contains(std::string_view str, char ch) noexcept {
+  return str.find(ch) != std::string_view::npos;
+}
+
+/**
+ * Checks if @p str contains the substring @p other.
+ */
+constexpr bool contains(std::string_view str, const char* other) noexcept {
+  return str.find(other) != std::string_view::npos;
+}
+
+/**
+ * Checks if @p str contains the substring @p other, ignoring case.
+ */
+inline bool contains_lower(std::string_view str,
+                           std::string_view other) noexcept {
+  return find_lower(str, other) != std::string_view::npos;
+}
+
+/**
+ * Checks if @p str contains the substring @p other, ignoring case.
+ */
+inline bool contains_lower(std::string_view str, char ch) noexcept {
+  return find_lower(str, ch) != std::string_view::npos;
+}
+
+/**
+ * Checks if @p str contains the substring @p other, ignoring case.
+ */
+inline bool contains_lower(std::string_view str, const char* other) noexcept {
+  return find_lower(str, other) != std::string_view::npos;
+}
+
+/**
+ * Return a string_view equal to @p str but with the first @p n elements
+ * dropped.
+ */
+constexpr std::string_view drop_front(
+    std::string_view str, std::string_view::size_type n = 1) noexcept {
+  str.remove_prefix(n);
+  return str;
+}
+
+/**
+ * Return a string_view equal to @p str but with the last @p n elements dropped.
+ */
+constexpr std::string_view drop_back(
+    std::string_view str, std::string_view::size_type n = 1) noexcept {
+  str.remove_suffix(n);
+  return str;
+}
+
+/**
+ * Returns a reference to the substring of @p str from [start, end).
+ *
+ * @param start The index of the starting character in the substring; if
+ * the index is npos or greater than the length of the string then the
+ * empty substring will be returned.
+ *
+ * @param end The index following the last character to include in the
+ * substring. If this is npos or exceeds the number of characters
+ * remaining in the string, the string suffix (starting with @p start)
+ * will be returned. If this is less than @p start, an empty string will
+ * be returned.
+ */
+constexpr std::string_view slice(std::string_view str,
+                                 std::string_view::size_type start,
+                                 std::string_view::size_type end) noexcept {
+  auto length = str.size();
+  start = (std::min)(start, length);
+  end = (std::min)((std::max)(start, end), length);
+  return {str.data() + start, end - start};
+}
+
+/**
+ * Splits @p str into two substrings around the first occurrence of a separator
+ * character.
+ *
+ * If @p separator is in the string, then the result is a pair (LHS, RHS)
+ * such that (str == LHS + separator + RHS) is true and RHS is
+ * maximal. If @p separator is not in the string, then the result is a
+ * pair (LHS, RHS) where (str == LHS) and (RHS == "").
+ *
+ * @param separator The character to split on.
+ * @returns The split substrings.
+ */
+constexpr std::pair<std::string_view, std::string_view> split(
+    std::string_view str, char separator) noexcept {
+  auto idx = str.find(separator);
+  if (idx == std::string_view::npos) {
+    return {str, {}};
+  }
+  return {slice(str, 0, idx), slice(str, idx + 1, std::string_view::npos)};
+}
+
+/**
+ * Splits @p str into two substrings around the first occurrence of a separator
+ * string.
+ *
+ * If @p separator is in the string, then the result is a pair (LHS, RHS)
+ * such that (str == LHS + separator + RHS) is true and RHS is
+ * maximal. If @p separator is not in the string, then the result is a
+ * pair (LHS, RHS) where (str == LHS) and (RHS == "").
+ *
+ * @param separator The string to split on.
+ * @return The split substrings.
+ */
+constexpr std::pair<std::string_view, std::string_view> split(
+    std::string_view str, std::string_view separator) noexcept {
+  auto idx = str.find(separator);
+  if (idx == std::string_view::npos) {
+    return {str, {}};
+  }
+  return {slice(str, 0, idx),
+          slice(str, idx + separator.size(), std::string_view::npos)};
+}
+
+/**
+ * Splits @p str into two substrings around the last occurrence of a separator
+ * character.
+ *
+ * If @p separator is in the string, then the result is a pair (LHS, RHS)
+ * such that (str == LHS + separator + RHS) is true and RHS is
+ * minimal. If @p separator is not in the string, then the result is a
+ * pair (LHS, RHS) where (str == LHS) and (RHS == "").
+ *
+ * @param separator The string to split on.
+ * @return The split substrings.
+ */
+constexpr std::pair<std::string_view, std::string_view> rsplit(
+    std::string_view str, char separator) noexcept {
+  auto idx = str.rfind(separator);
+  if (idx == std::string_view::npos) {
+    return {str, {}};
+  }
+  return {slice(str, 0, idx), slice(str, idx + 1, std::string_view::npos)};
+}
+
+/**
+ * Splits @p str into two substrings around the last occurrence of a separator
+ * string.
+ *
+ * If @p separator is in the string, then the result is a pair (LHS, RHS)
+ * such that (str == LHS + separator + RHS) is true and RHS is
+ * minimal. If @p separator is not in the string, then the result is a
+ * pair (LHS, RHS) where (str == LHS) and (RHS == "").
+ *
+ * @param separator The string to split on.
+ * @return The split substrings.
+ */
+constexpr std::pair<std::string_view, std::string_view> rsplit(
+    std::string_view str, std::string_view separator) noexcept {
+  auto idx = str.rfind(separator);
+  if (idx == std::string_view::npos) {
+    return {str, {}};
+  }
+  return {slice(str, 0, idx),
+          slice(str, idx + separator.size(), std::string_view::npos)};
+}
+
+/**
+ * Splits @p str into substrings around the occurrences of a separator string.
+ *
+ * Each substring is stored in @p arr. If @p maxSplit is >= 0, at most
+ * @p maxSplit splits are done and consequently <= @p maxSplit + 1
+ * elements are added to arr.
+ * If @p keepEmpty is false, empty strings are not added to @p arr. They
+ * still count when considering @p maxSplit
+ * An useful invariant is that
+ * separator.join(arr) == str if maxSplit == -1 and keepEmpty == true
+ *
+ * @param arr Where to put the substrings.
+ * @param separator The string to split on.
+ * @param maxSplit The maximum number of times the string is split.
+ * @param keepEmpty True if empty substring should be added.
+ */
+void split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
+           std::string_view separator, int maxSplit = -1,
+           bool keepEmpty = true) noexcept;
+
+/**
+ * Splits @p str into substrings around the occurrences of a separator
+ * character.
+ *
+ * Each substring is stored in @p arr. If @p maxSplit is >= 0, at most
+ * @p maxSplit splits are done and consequently <= @p maxSplit + 1
+ * elements are added to arr.
+ * If @p keepEmpty is false, empty strings are not added to @p arr. They
+ * still count when considering @p maxSplit
+ * An useful invariant is that
+ * separator.join(arr) == str if maxSplit == -1 and keepEmpty == true
+ *
+ * @param arr Where to put the substrings.
+ * @param separator The character to split on.
+ * @param maxSplit The maximum number of times the string is split.
+ * @param keepEmpty True if empty substring should be added.
+ */
+void split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
+           char separator, int maxSplit = -1, bool keepEmpty = true) noexcept;
+
+/**
+ * Returns @p str with consecutive @p ch characters starting from the
+ * the left removed.
+ */
+constexpr std::string_view ltrim(std::string_view str, char ch) noexcept {
+  return drop_front(str, (std::min)(str.size(), str.find_first_not_of(ch)));
+}
+
+/**
+ * Returns @p str with consecutive characters in @p chars starting from
+ * the left removed.
+ */
+constexpr std::string_view ltrim(
+    std::string_view str, std::string_view chars = " \t\n\v\f\r") noexcept {
+  return drop_front(str, (std::min)(str.size(), str.find_first_not_of(chars)));
+}
+
+/**
+ * Returns @p str with consecutive @p Char characters starting from the
+ * right removed.
+ */
+constexpr std::string_view rtrim(std::string_view str, char ch) noexcept {
+  return drop_back(
+      str, str.size() - (std::min)(str.size(), str.find_last_not_of(ch) + 1));
+}
+
+/**
+ * Returns @p str with consecutive characters in @p chars starting from
+ * the right removed.
+ */
+constexpr std::string_view rtrim(
+    std::string_view str, std::string_view chars = " \t\n\v\f\r") noexcept {
+  return drop_back(
+      str,
+      str.size() - (std::min)(str.size(), str.find_last_not_of(chars) + 1));
+}
+
+/**
+ * Returns @p str with consecutive @p ch characters starting from the
+ * left and right removed.
+ */
+constexpr std::string_view trim(std::string_view str, char ch) noexcept {
+  return rtrim(ltrim(str, ch), ch);
+}
+
+/**
+ * Returns @p str with consecutive characters in @p chars starting from
+ * the left and right removed.
+ */
+constexpr std::string_view trim(
+    std::string_view str, std::string_view chars = " \t\n\v\f\r") noexcept {
+  return rtrim(ltrim(str, chars), chars);
 }
 
 namespace detail {
-template <typename N>
-inline bool to_float(const Twine &T, N &Num, N (*StrTo)(const char *, char **)) {
-  SmallString<32> Storage;
-  StringRef S = T.toNullTerminatedStringRef(Storage);
-  char *End;
-  N Temp = StrTo(S.data(), &End);
-  if (*End != '\0')
-    return false;
-  Num = Temp;
-  return true;
-}
-}
+bool GetAsUnsignedInteger(
+    std::string_view str, unsigned radix,
+    unsigned long long& result) noexcept;  // NOLINT(runtime/int)
+bool GetAsSignedInteger(std::string_view str, unsigned radix,
+                        long long& result) noexcept;  // NOLINT(runtime/int)
 
-inline bool to_float(const Twine &T, float &Num) {
-  return detail::to_float(T, Num, strtof);
-}
+bool ConsumeUnsignedInteger(
+    std::string_view& str, unsigned radix,
+    unsigned long long& result) noexcept;  // NOLINT(runtime/int)
+bool ConsumeSignedInteger(std::string_view& str, unsigned radix,
+                          long long& result) noexcept;  // NOLINT(runtime/int)
+}  // namespace detail
 
-inline bool to_float(const Twine &T, double &Num) {
-  return detail::to_float(T, Num, strtod);
-}
-
-inline bool to_float(const Twine &T, long double &Num) {
-  return detail::to_float(T, Num, strtold);
-}
-
-inline std::string utostr(uint64_t X, bool isNeg = false) {
-  char Buffer[21];
-  char *BufPtr = std::end(Buffer);
-
-  if (X == 0) *--BufPtr = '0';  // Handle special case...
-
-  while (X) {
-    *--BufPtr = '0' + char(X % 10);
-    X /= 10;
+/**
+ * Parses the string @p str as an integer of the specified radix.  If
+ * @p radix is specified as zero, this does radix autosensing using
+ * extended C rules: 0 is octal, 0x is hex, 0b is binary.
+ *
+ * If the string is invalid or if only a subset of the string is valid,
+ * this returns nullopt to signify the error.  The string is considered
+ * erroneous if empty or if it overflows T.
+ */
+template <typename T,
+          std::enable_if_t<std::numeric_limits<T>::is_signed, bool> = true>
+inline std::optional<T> parse_integer(std::string_view str,
+                                      unsigned radix) noexcept {
+  long long val;  // NOLINT(runtime/int)
+  if (detail::GetAsSignedInteger(str, radix, val) ||
+      static_cast<T>(val) != val) {
+    return std::nullopt;
   }
-
-  if (isNeg) *--BufPtr = '-';   // Add negative sign...
-  return std::string(BufPtr, std::end(Buffer));
+  return val;
 }
 
-inline std::string itostr(int64_t X) {
-  if (X < 0)
-    return utostr(static_cast<uint64_t>(-X), true);
-  else
-    return utostr(static_cast<uint64_t>(X));
-}
-
-/// StrInStrNoCase - Portable version of strcasestr.  Locates the first
-/// occurrence of string 's1' in string 's2', ignoring case.  Returns
-/// the offset of s2 in s1 or npos if s2 cannot be found.
-StringRef::size_type StrInStrNoCase(StringRef s1, StringRef s2);
-
-/// getToken - This function extracts one token from source, ignoring any
-/// leading characters that appear in the Delimiters string, and ending the
-/// token at any of the characters that appear in the Delimiters string.  If
-/// there are no tokens in the source string, an empty string is returned.
-/// The function returns a pair containing the extracted token and the
-/// remaining tail string.
-std::pair<StringRef, StringRef> getToken(StringRef Source,
-                                         StringRef Delimiters = " \t\n\v\f\r");
-
-/// SplitString - Split up the specified string according to the specified
-/// delimiters, appending the result fragments to the output list.
-void SplitString(StringRef Source,
-                 SmallVectorImpl<StringRef> &OutFragments,
-                 StringRef Delimiters = " \t\n\v\f\r");
-
-/// HashString - Hash function for strings.
-///
-/// This is the Bernstein hash function.
-//
-// FIXME: Investigate whether a modified bernstein hash function performs
-// better: http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
-//   X*33+c -> X*33^c
-static inline unsigned HashString(StringRef Str, unsigned Result = 0) {
-  for (StringRef::size_type i = 0, e = Str.size(); i != e; ++i)
-    Result = Result * 33 + (unsigned char)Str[i];
-  return Result;
-}
-
-/// Returns the English suffix for an ordinal integer (-st, -nd, -rd, -th).
-inline StringRef getOrdinalSuffix(unsigned Val) {
-  // It is critically important that we do this perfectly for
-  // user-written sequences with over 100 elements.
-  switch (Val % 100) {
-  case 11:
-  case 12:
-  case 13:
-    return "th";
-  default:
-    switch (Val % 10) {
-      case 1: return "st";
-      case 2: return "nd";
-      case 3: return "rd";
-      default: return "th";
-    }
+template <typename T,
+          std::enable_if_t<!std::numeric_limits<T>::is_signed, bool> = true>
+inline std::optional<T> parse_integer(std::string_view str,
+                                      unsigned radix) noexcept {
+  using Int = unsigned long long;  // NOLINT(runtime/int)
+  Int val;
+  // The additional cast to unsigned long long is required to avoid the
+  // Visual C++ warning C4805: '!=' : unsafe mix of type 'bool' and type
+  // 'unsigned __int64' when instantiating getAsInteger with T = bool.
+  if (detail::GetAsUnsignedInteger(str, radix, val) ||
+      static_cast<Int>(static_cast<T>(val)) != val) {
+    return std::nullopt;
   }
+  return val;
 }
 
-/// Print each character of the specified string, escaping it if it is not
-/// printable or if it is an escape char.
-void printEscapedString(StringRef Name, raw_ostream &Out);
-
-/// Print each character of the specified string, escaping HTML special
-/// characters.
-void printHTMLEscaped(StringRef String, raw_ostream &Out);
-
-/// printLowerCase - Print each character as lowercase if it is uppercase.
-void printLowerCase(StringRef String, raw_ostream &Out);
-
-namespace detail {
-
-template <typename IteratorT>
-inline std::string join_impl(IteratorT Begin, IteratorT End,
-                             StringRef Separator, std::input_iterator_tag) {
-  std::string S;
-  if (Begin == End)
-    return S;
-
-  S += (*Begin);
-  while (++Begin != End) {
-    S += Separator;
-    S += (*Begin);
+/**
+ * Parses the string @p str as an integer of the specified radix.  If
+ * @p radix is specified as zero, this does radix autosensing using
+ * extended C rules: 0 is octal, 0x is hex, 0b is binary.
+ *
+ * If the string does not begin with a number of the specified radix,
+ * this returns nullopt to signify the error. The string is considered
+ * erroneous if empty or if it overflows T.
+ * The portion of the string representing the discovered numeric value
+ * is removed from the beginning of the string.
+ */
+template <typename T,
+          std::enable_if_t<std::numeric_limits<T>::is_signed, bool> = true>
+inline std::optional<T> consume_integer(std::string_view* str,
+                                        unsigned radix) noexcept {
+  using Int = long long;  // NOLINT(runtime/int)
+  Int val;
+  if (detail::ConsumeSignedInteger(*str, radix, val) ||
+      static_cast<Int>(static_cast<T>(val)) != val) {
+    return std::nullopt;
   }
-  return S;
+  return val;
 }
 
-template <typename IteratorT>
-inline std::string join_impl(IteratorT Begin, IteratorT End,
-                             StringRef Separator, std::forward_iterator_tag) {
-  std::string S;
-  if (Begin == End)
-    return S;
-
-  size_t Len = (std::distance(Begin, End) - 1) * Separator.size();
-  for (IteratorT I = Begin; I != End; ++I)
-    Len += (*Begin).size();
-  S.reserve(Len);
-  S += (*Begin);
-  while (++Begin != End) {
-    S += Separator;
-    S += (*Begin);
+template <typename T,
+          std::enable_if_t<!std::numeric_limits<T>::is_signed, bool> = true>
+inline std::optional<T> consume_integer(std::string_view* str,
+                                        unsigned radix) noexcept {
+  using Int = unsigned long long;  // NOLINT(runtime/int)
+  Int val;
+  if (detail::ConsumeUnsignedInteger(*str, radix, val) ||
+      static_cast<Int>(static_cast<T>(val)) != val) {
+    return std::nullopt;
   }
-  return S;
+  return val;
 }
 
-template <typename Sep>
-inline void join_items_impl(std::string &Result, Sep Separator) {}
+/**
+ * Parses the string @p str as a floating point value.
+ *
+ * If the string is invalid or if only a subset of the string is valid,
+ * this returns nullopt to signify the error.  The string is considered
+ * erroneous if empty or if it overflows T.
+ */
+template <typename T>
+std::optional<T> parse_float(std::string_view str) noexcept;
 
-template <typename Sep, typename Arg>
-inline void join_items_impl(std::string &Result, Sep Separator,
-                            const Arg &Item) {
-  Result += Item;
-}
+template <>
+std::optional<float> parse_float<float>(std::string_view str) noexcept;
+template <>
+std::optional<double> parse_float<double>(std::string_view str) noexcept;
+template <>
+std::optional<long double> parse_float<long double>(
+    std::string_view str) noexcept;
 
-template <typename Sep, typename Arg1, typename... Args>
-inline void join_items_impl(std::string &Result, Sep Separator, const Arg1 &A1,
-                            Args &&... Items) {
-  Result += A1;
-  Result += Separator;
-  join_items_impl(Result, Separator, std::forward<Args>(Items)...);
-}
-
-inline size_t join_one_item_size(char C) { return 1; }
-inline size_t join_one_item_size(const char *S) { return S ? ::strlen(S) : 0; }
-
-template <typename T> inline size_t join_one_item_size(const T &Str) {
-  return Str.size();
-}
-
-inline size_t join_items_size() { return 0; }
-
-template <typename A1> inline size_t join_items_size(const A1 &A) {
-  return join_one_item_size(A);
-}
-template <typename A1, typename... Args>
-inline size_t join_items_size(const A1 &A, Args &&... Items) {
-  return join_one_item_size(A) + join_items_size(std::forward<Args>(Items)...);
-}
-
-} // end namespace detail
-
-/// Joins the strings in the range [Begin, End), adding Separator between
-/// the elements.
-template <typename IteratorT>
-inline std::string join(IteratorT Begin, IteratorT End, StringRef Separator) {
-  using tag = typename std::iterator_traits<IteratorT>::iterator_category;
-  return detail::join_impl(Begin, End, Separator, tag());
-}
-
-/// Joins the strings in the range [R.begin(), R.end()), adding Separator
-/// between the elements.
-template <typename Range>
-inline std::string join(Range &&R, StringRef Separator) {
-  return join(R.begin(), R.end(), Separator);
-}
-
-/// Joins the strings in the parameter pack \p Items, adding \p Separator
-/// between the elements.  All arguments must be implicitly convertible to
-/// std::string, or there should be an overload of std::string::operator+=()
-/// that accepts the argument explicitly.
-template <typename Sep, typename... Args>
-inline std::string join_items(Sep Separator, Args &&... Items) {
-  std::string Result;
-  if (sizeof...(Items) == 0)
-    return Result;
-
-  size_t NS = detail::join_one_item_size(Separator);
-  size_t NI = detail::join_items_size(std::forward<Args>(Items)...);
-  Result.reserve(NI + (sizeof...(Items) - 1) * NS + 1);
-  detail::join_items_impl(Result, Separator, std::forward<Args>(Items)...);
-  return Result;
-}
-
-} // end namespace wpi
-
-#endif // WPIUTIL_WPI_STRINGEXTRAS_H
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/StringMap.h b/wpiutil/src/main/native/include/wpi/StringMap.h
index dbfe8ed..dac2f20 100644
--- a/wpiutil/src/main/native/include/wpi/StringMap.h
+++ b/wpiutil/src/main/native/include/wpi/StringMap.h
@@ -15,13 +15,11 @@
 #define WPIUTIL_WPI_STRINGMAP_H
 
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
 #include "wpi/iterator.h"
 #include "wpi/iterator_range.h"
 #include "wpi/MemAlloc.h"
 #include "wpi/PointerLikeTypeTraits.h"
 #include "wpi/ErrorHandling.h"
-#include "wpi/deprecated.h"
 #include <algorithm>
 #include <cassert>
 #include <cstdint>
@@ -29,6 +27,7 @@
 #include <cstring>
 #include <initializer_list>
 #include <iterator>
+#include <string_view>
 #include <utility>
 
 namespace wpi {
@@ -82,12 +81,12 @@
   /// specified bucket will be non-null.  Otherwise, it will be null.  In either
   /// case, the FullHashValue field of the bucket will be set to the hash value
   /// of the string.
-  unsigned LookupBucketFor(StringRef Key);
+  unsigned LookupBucketFor(std::string_view Key);
 
   /// FindKey - Look up the bucket that contains the specified key. If it exists
   /// in the map, return the bucket number of the key.  Otherwise return -1.
   /// This does not modify the map.
-  int FindKey(StringRef Key) const;
+  int FindKey(std::string_view Key) const;
 
   /// RemoveKey - Remove the specified StringMapEntry from the table, but do not
   /// delete it.  This aborts if the value isn't in the table.
@@ -95,7 +94,7 @@
 
   /// RemoveKey - Remove the StringMapEntry for the specified key from the
   /// table, returning it.  If the key is not in the table, this returns null.
-  StringMapEntryBase *RemoveKey(StringRef Key);
+  StringMapEntryBase *RemoveKey(std::string_view Key);
 
   /// Allocate the table with the specified number of buckets and otherwise
   /// setup the map as empty.
@@ -137,8 +136,8 @@
       : StringMapEntryBase(strLen), second(std::forward<InitTy>(InitVals)...) {}
   StringMapEntry(StringMapEntry &E) = delete;
 
-  StringRef getKey() const {
-    return StringRef(getKeyData(), getKeyLength());
+  std::string_view getKey() const {
+    return {getKeyData(), getKeyLength()};
   }
 
   const ValueTy &getValue() const { return second; }
@@ -151,12 +150,12 @@
   /// StringMapEntry object.
   const char *getKeyData() const {return reinterpret_cast<const char*>(this+1);}
 
-  StringRef first() const { return StringRef(getKeyData(), getKeyLength()); }
+  std::string_view first() const { return {getKeyData(), getKeyLength()}; }
 
   /// Create a StringMapEntry for the specified key construct the value using
   /// \p InitiVals.
   template <typename... InitTy>
-  static StringMapEntry *Create(StringRef Key, InitTy &&... InitVals) {
+  static StringMapEntry *Create(std::string_view Key, InitTy &&... InitVals) {
     size_t KeyLength = Key.size();
 
     // Allocate a new item with space for the string at the end and a null
@@ -177,7 +176,7 @@
     return NewItem;
   }
 
-  static StringMapEntry *Create(StringRef Key) {
+  static StringMapEntry *Create(std::string_view Key) {
     return Create(Key, ValueTy());
   }
 
@@ -212,7 +211,7 @@
   explicit StringMap(unsigned InitialSize)
     : StringMapImpl(InitialSize, static_cast<unsigned>(sizeof(MapEntryTy))) {}
 
-  StringMap(std::initializer_list<std::pair<StringRef, ValueTy>> List)
+  StringMap(std::initializer_list<std::pair<std::string_view, ValueTy>> List)
       : StringMapImpl(List.size(), static_cast<unsigned>(sizeof(MapEntryTy))) {
     for (const auto &P : List) {
       insert(P);
@@ -302,13 +301,13 @@
                       StringMapKeyIterator<ValueTy>(end()));
   }
 
-  iterator find(StringRef Key) {
+  iterator find(std::string_view Key) {
     int Bucket = FindKey(Key);
     if (Bucket == -1) return end();
     return iterator(TheTable+Bucket, true);
   }
 
-  const_iterator find(StringRef Key) const {
+  const_iterator find(std::string_view Key) const {
     int Bucket = FindKey(Key);
     if (Bucket == -1) return end();
     return const_iterator(TheTable+Bucket, true);
@@ -316,7 +315,7 @@
 
   /// lookup - Return the entry for the specified key, or a default
   /// constructed value if no such entry exists.
-  ValueTy lookup(StringRef Key) const {
+  ValueTy lookup(std::string_view Key) const {
     const_iterator it = find(Key);
     if (it != end())
       return it->second;
@@ -325,10 +324,10 @@
 
   /// Lookup the ValueTy for the \p Key, or create a default constructed value
   /// if the key is not in the map.
-  ValueTy &operator[](StringRef Key) { return try_emplace(Key).first->second; }
+  ValueTy &operator[](std::string_view Key) { return try_emplace(Key).first->second; }
 
   /// count - Return 1 if the element is in the map, 0 otherwise.
-  size_type count(StringRef Key) const {
+  size_type count(std::string_view Key) const {
     return find(Key) == end() ? 0 : 1;
   }
 
@@ -355,7 +354,7 @@
   /// isn't already in the map. The bool component of the returned pair is true
   /// if and only if the insertion takes place, and the iterator component of
   /// the pair points to the element with key equivalent to the key of the pair.
-  std::pair<iterator, bool> insert(std::pair<StringRef, ValueTy> KV) {
+  std::pair<iterator, bool> insert(std::pair<std::string_view, ValueTy> KV) {
     return try_emplace(KV.first, std::move(KV.second));
   }
 
@@ -364,7 +363,7 @@
   /// if and only if the insertion takes place, and the iterator component of
   /// the pair points to the element with key equivalent to the key of the pair.
   template <typename... ArgsTy>
-  std::pair<iterator, bool> try_emplace(StringRef Key, ArgsTy &&... Args) {
+  std::pair<iterator, bool> try_emplace(std::string_view Key, ArgsTy &&... Args) {
     unsigned BucketNo = LookupBucketFor(Key);
     StringMapEntryBase *&Bucket = TheTable[BucketNo];
     if (Bucket && Bucket != getTombstoneVal())
@@ -411,7 +410,7 @@
     V.Destroy();
   }
 
-  bool erase(StringRef Key) {
+  bool erase(std::string_view Key) {
     iterator I = find(Key);
     if (I == end()) return false;
     erase(I);
@@ -440,7 +439,13 @@
     return static_cast<DerivedTy &>(*this);
   }
 
+#if __cplusplus < 202002L
   bool operator==(const DerivedTy &RHS) const { return Ptr == RHS.Ptr; }
+#else
+  friend bool operator==(const DerivedTy &LHS, const DerivedTy &RHS) {
+    return LHS.Ptr == RHS.Ptr;
+  }
+#endif
 
   DerivedTy &operator++() { // Preincrement
     ++Ptr;
@@ -524,23 +529,23 @@
 class StringMapKeyIterator
     : public iterator_adaptor_base<StringMapKeyIterator<ValueTy>,
                                    StringMapConstIterator<ValueTy>,
-                                   std::forward_iterator_tag, StringRef> {
+                                   std::forward_iterator_tag, std::string_view> {
   using base = iterator_adaptor_base<StringMapKeyIterator<ValueTy>,
                                      StringMapConstIterator<ValueTy>,
-                                     std::forward_iterator_tag, StringRef>;
+                                     std::forward_iterator_tag, std::string_view>;
 
 public:
   StringMapKeyIterator() = default;
   explicit StringMapKeyIterator(StringMapConstIterator<ValueTy> Iter)
       : base(std::move(Iter)) {}
 
-  StringRef &operator*() {
+  std::string_view &operator*() {
     Key = this->wrapped()->getKey();
     return Key;
   }
 
 private:
-  StringRef Key;
+  std::string_view Key;
 };
 
 template <typename ValueTy>
@@ -594,13 +599,13 @@
   if (&lhs == &rhs) return false;
 
   // copy into vectors and sort by key
-  SmallVector<StringRef, 16> lhs_keys;
+  SmallVector<std::string_view, 16> lhs_keys;
   lhs_keys.reserve(lhs.size());
   for (auto i = lhs.begin(), end = lhs.end(); i != end; ++i)
     lhs_keys.push_back(i->getKey());
   std::sort(lhs_keys.begin(), lhs_keys.end());
 
-  SmallVector<StringRef, 16> rhs_keys;
+  SmallVector<std::string_view, 16> rhs_keys;
   rhs_keys.reserve(rhs.size());
   for (auto i = rhs.begin(), end = rhs.end(); i != end; ++i)
     rhs_keys.push_back(i->getKey());
diff --git a/wpiutil/src/main/native/include/wpi/StringRef.h b/wpiutil/src/main/native/include/wpi/StringRef.h
deleted file mode 100644
index 60fb789..0000000
--- a/wpiutil/src/main/native/include/wpi/StringRef.h
+++ /dev/null
@@ -1,947 +0,0 @@
-//===- StringRef.h - Constant String Reference Wrapper ----------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_STRINGREF_H
-#define WPIUTIL_WPI_STRINGREF_H
-
-#include "wpi/STLExtras.h"
-#include "wpi/iterator_range.h"
-#include "wpi/Compiler.h"
-#include <algorithm>
-#include <cassert>
-#include <cstddef>
-#include <cstring>
-#include <iosfwd>
-#include <limits>
-#include <string>
-#include <type_traits>
-#include <utility>
-
-namespace wpi {
-
-  class hash_code;
-  template <typename T> class SmallVectorImpl;
-  class StringRef;
-
-  /// Helper functions for StringRef::getAsInteger.
-  bool getAsUnsignedInteger(StringRef Str, unsigned Radix,
-                            unsigned long long &Result) noexcept;
-
-  bool getAsSignedInteger(StringRef Str, unsigned Radix, long long &Result) noexcept;
-
-  bool consumeUnsignedInteger(StringRef &Str, unsigned Radix,
-                              unsigned long long &Result) noexcept;
-  bool consumeSignedInteger(StringRef &Str, unsigned Radix, long long &Result) noexcept;
-
-  /// StringRef - Represent a constant reference to a string, i.e. a character
-  /// array and a length, which need not be null terminated.
-  ///
-  /// This class does not own the string data, it is expected to be used in
-  /// situations where the character data resides in some other buffer, whose
-  /// lifetime extends past that of the StringRef. For this reason, it is not in
-  /// general safe to store a StringRef.
-  class StringRef {
-  public:
-    static const size_t npos = ~size_t(0);
-
-    using iterator = const char *;
-    using const_iterator = const char *;
-    using size_type = size_t;
-
-  private:
-    /// The start of the string, in an external buffer.
-    const char *Data = nullptr;
-
-    /// The length of the string.
-    size_t Length = 0;
-
-    // Workaround memcmp issue with null pointers (undefined behavior)
-    // by providing a specialized version
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    static int compareMemory(const char *Lhs, const char *Rhs, size_t Length) noexcept {
-      if (Length == 0) { return 0; }
-      return ::memcmp(Lhs,Rhs,Length);
-    }
-
-  public:
-    /// @name Constructors
-    /// @{
-
-    /// Construct an empty string ref.
-    /*implicit*/ StringRef() = default;
-
-    /// Disable conversion from nullptr.  This prevents things like
-    /// if (S == nullptr)
-    StringRef(std::nullptr_t) = delete;
-
-    /// Construct a string ref from a cstring.
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    /*implicit*/ StringRef(const char *Str)
-        : Data(Str), Length(Str ? ::strlen(Str) : 0) {}
-
-    /// Construct a string ref from a pointer and length.
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    /*implicit*/ constexpr StringRef(const char *data, size_t length)
-        : Data(data), Length(length) {}
-
-    /// Construct a string ref from an std::string.
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    /*implicit*/ StringRef(const std::string &Str)
-      : Data(Str.data()), Length(Str.length()) {}
-
-    static StringRef withNullAsEmpty(const char *data) {
-      return StringRef(data ? data : "");
-    }
-
-    /// @}
-    /// @name Iterators
-    /// @{
-
-    iterator begin() const noexcept { return Data; }
-
-    iterator end() const noexcept { return Data + Length; }
-
-    const unsigned char *bytes_begin() const noexcept {
-      return reinterpret_cast<const unsigned char *>(begin());
-    }
-    const unsigned char *bytes_end() const noexcept {
-      return reinterpret_cast<const unsigned char *>(end());
-    }
-    iterator_range<const unsigned char *> bytes() const noexcept {
-      return make_range(bytes_begin(), bytes_end());
-    }
-
-    /// @}
-    /// @name String Operations
-    /// @{
-
-    /// data - Get a pointer to the start of the string (which may not be null
-    /// terminated).
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    const char *data() const noexcept { return Data; }
-
-    /// empty - Check if the string is empty.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool empty() const noexcept { return Length == 0; }
-
-    /// size - Get the string size.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    size_t size() const noexcept { return Length; }
-
-    /// front - Get the first character in the string.
-    LLVM_NODISCARD
-    char front() const noexcept {
-      assert(!empty());
-      return Data[0];
-    }
-
-    /// back - Get the last character in the string.
-    LLVM_NODISCARD
-    char back() const noexcept {
-      assert(!empty());
-      return Data[Length-1];
-    }
-
-    // copy - Allocate copy in Allocator and return StringRef to it.
-    template <typename Allocator>
-    LLVM_NODISCARD StringRef copy(Allocator &A) const {
-      // Don't request a length 0 copy from the allocator.
-      if (empty())
-        return StringRef();
-      char *S = A.template Allocate<char>(Length);
-      std::copy(begin(), end(), S);
-      return StringRef(S, Length);
-    }
-
-    /// equals - Check for string equality, this is more efficient than
-    /// compare() when the relative ordering of inequal strings isn't needed.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool equals(StringRef RHS) const noexcept {
-      return (Length == RHS.Length &&
-              compareMemory(Data, RHS.Data, RHS.Length) == 0);
-    }
-
-    /// equals_lower - Check for string equality, ignoring case.
-    LLVM_NODISCARD
-    bool equals_lower(StringRef RHS) const noexcept {
-      return Length == RHS.Length && compare_lower(RHS) == 0;
-    }
-
-    /// compare - Compare two strings; the result is -1, 0, or 1 if this string
-    /// is lexicographically less than, equal to, or greater than the \p RHS.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    int compare(StringRef RHS) const noexcept {
-      // Check the prefix for a mismatch.
-      if (int Res = compareMemory(Data, RHS.Data, (std::min)(Length, RHS.Length)))
-        return Res < 0 ? -1 : 1;
-
-      // Otherwise the prefixes match, so we only need to check the lengths.
-      if (Length == RHS.Length)
-        return 0;
-      return Length < RHS.Length ? -1 : 1;
-    }
-
-    /// compare_lower - Compare two strings, ignoring case.
-    LLVM_NODISCARD
-    int compare_lower(StringRef RHS) const noexcept;
-
-    /// compare_numeric - Compare two strings, treating sequences of digits as
-    /// numbers.
-    LLVM_NODISCARD
-    int compare_numeric(StringRef RHS) const noexcept;
-
-    /// str - Get the contents as an std::string.
-    LLVM_NODISCARD
-    std::string str() const {
-      if (!Data) return std::string();
-      return std::string(Data, Length);
-    }
-
-    /// @}
-    /// @name Operator Overloads
-    /// @{
-
-    LLVM_NODISCARD
-    char operator[](size_t Index) const noexcept {
-      assert(Index < Length && "Invalid index!");
-      return Data[Index];
-    }
-
-    /// Disallow accidental assignment from a temporary std::string.
-    ///
-    /// The declaration here is extra complicated so that `stringRef = {}`
-    /// and `stringRef = "abc"` continue to select the move assignment operator.
-    template <typename T>
-    typename std::enable_if<std::is_same<T, std::string>::value,
-                            StringRef>::type &
-    operator=(T &&Str) = delete;
-
-    /// @}
-    /// @name Type Conversions
-    /// @{
-
-    operator std::string() const {
-      return str();
-    }
-
-    /// @}
-    /// @name String Predicates
-    /// @{
-
-    /// Check if this string starts with the given \p Prefix.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool startswith(StringRef Prefix) const noexcept {
-      return Length >= Prefix.Length &&
-             compareMemory(Data, Prefix.Data, Prefix.Length) == 0;
-    }
-
-    /// Check if this string starts with the given \p Prefix, ignoring case.
-    LLVM_NODISCARD
-    bool startswith_lower(StringRef Prefix) const noexcept;
-
-    /// Check if this string ends with the given \p Suffix.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool endswith(StringRef Suffix) const noexcept {
-      return Length >= Suffix.Length &&
-        compareMemory(end() - Suffix.Length, Suffix.Data, Suffix.Length) == 0;
-    }
-
-    /// Check if this string ends with the given \p Suffix, ignoring case.
-    LLVM_NODISCARD
-    bool endswith_lower(StringRef Suffix) const noexcept;
-
-    /// @}
-    /// @name String Searching
-    /// @{
-
-    /// Search for the first character \p C in the string.
-    ///
-    /// \returns The index of the first occurrence of \p C, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    size_t find(char C, size_t From = 0) const noexcept {
-      size_t FindBegin = (std::min)(From, Length);
-      if (FindBegin < Length) { // Avoid calling memchr with nullptr.
-        // Just forward to memchr, which is faster than a hand-rolled loop.
-        if (const void *P = ::memchr(Data + FindBegin, C, Length - FindBegin))
-          return static_cast<const char *>(P) - Data;
-      }
-      return npos;
-    }
-
-    /// Search for the first character \p C in the string, ignoring case.
-    ///
-    /// \returns The index of the first occurrence of \p C, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t find_lower(char C, size_t From = 0) const noexcept;
-
-    /// Search for the first character satisfying the predicate \p F
-    ///
-    /// \returns The index of the first character satisfying \p F starting from
-    /// \p From, or npos if not found.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    size_t find_if(function_ref<bool(char)> F, size_t From = 0) const noexcept {
-      StringRef S = drop_front(From);
-      while (!S.empty()) {
-        if (F(S.front()))
-          return size() - S.size();
-        S = S.drop_front();
-      }
-      return npos;
-    }
-
-    /// Search for the first character not satisfying the predicate \p F
-    ///
-    /// \returns The index of the first character not satisfying \p F starting
-    /// from \p From, or npos if not found.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    size_t find_if_not(function_ref<bool(char)> F, size_t From = 0) const noexcept {
-      return find_if([F](char c) { return !F(c); }, From);
-    }
-
-    /// Search for the first string \p Str in the string.
-    ///
-    /// \returns The index of the first occurrence of \p Str, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t find(StringRef Str, size_t From = 0) const noexcept;
-
-    /// Search for the first string \p Str in the string, ignoring case.
-    ///
-    /// \returns The index of the first occurrence of \p Str, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t find_lower(StringRef Str, size_t From = 0) const noexcept;
-
-    /// Search for the last character \p C in the string.
-    ///
-    /// \returns The index of the last occurrence of \p C, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t rfind(char C, size_t From = npos) const noexcept {
-      From = (std::min)(From, Length);
-      size_t i = From;
-      while (i != 0) {
-        --i;
-        if (Data[i] == C)
-          return i;
-      }
-      return npos;
-    }
-
-    /// Search for the last character \p C in the string, ignoring case.
-    ///
-    /// \returns The index of the last occurrence of \p C, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t rfind_lower(char C, size_t From = npos) const noexcept;
-
-    /// Search for the last string \p Str in the string.
-    ///
-    /// \returns The index of the last occurrence of \p Str, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t rfind(StringRef Str) const noexcept;
-
-    /// Search for the last string \p Str in the string, ignoring case.
-    ///
-    /// \returns The index of the last occurrence of \p Str, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t rfind_lower(StringRef Str) const noexcept;
-
-    /// Find the first character in the string that is \p C, or npos if not
-    /// found. Same as find.
-    LLVM_NODISCARD
-    size_t find_first_of(char C, size_t From = 0) const noexcept {
-      return find(C, From);
-    }
-
-    /// Find the first character in the string that is in \p Chars, or npos if
-    /// not found.
-    ///
-    /// Complexity: O(size() + Chars.size())
-    LLVM_NODISCARD
-    size_t find_first_of(StringRef Chars, size_t From = 0) const noexcept;
-
-    /// Find the first character in the string that is not \p C or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t find_first_not_of(char C, size_t From = 0) const noexcept;
-
-    /// Find the first character in the string that is not in the string
-    /// \p Chars, or npos if not found.
-    ///
-    /// Complexity: O(size() + Chars.size())
-    LLVM_NODISCARD
-    size_t find_first_not_of(StringRef Chars, size_t From = 0) const noexcept;
-
-    /// Find the last character in the string that is \p C, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t find_last_of(char C, size_t From = npos) const noexcept {
-      return rfind(C, From);
-    }
-
-    /// Find the last character in the string that is in \p C, or npos if not
-    /// found.
-    ///
-    /// Complexity: O(size() + Chars.size())
-    LLVM_NODISCARD
-    size_t find_last_of(StringRef Chars, size_t From = npos) const noexcept;
-
-    /// Find the last character in the string that is not \p C, or npos if not
-    /// found.
-    LLVM_NODISCARD
-    size_t find_last_not_of(char C, size_t From = npos) const noexcept;
-
-    /// Find the last character in the string that is not in \p Chars, or
-    /// npos if not found.
-    ///
-    /// Complexity: O(size() + Chars.size())
-    LLVM_NODISCARD
-    size_t find_last_not_of(StringRef Chars, size_t From = npos) const noexcept;
-
-    /// Return true if the given string is a substring of *this, and false
-    /// otherwise.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool contains(StringRef Other) const noexcept { return find(Other) != npos; }
-
-    /// Return true if the given character is contained in *this, and false
-    /// otherwise.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool contains(char C) const noexcept { return find_first_of(C) != npos; }
-
-    /// Return true if the given string is a substring of *this, and false
-    /// otherwise.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool contains_lower(StringRef Other) const noexcept {
-      return find_lower(Other) != npos;
-    }
-
-    /// Return true if the given character is contained in *this, and false
-    /// otherwise.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool contains_lower(char C) const noexcept { return find_lower(C) != npos; }
-
-    /// @}
-    /// @name Helpful Algorithms
-    /// @{
-
-    /// Return the number of occurrences of \p C in the string.
-    LLVM_NODISCARD
-    size_t count(char C) const noexcept {
-      size_t Count = 0;
-      for (size_t i = 0, e = Length; i != e; ++i)
-        if (Data[i] == C)
-          ++Count;
-      return Count;
-    }
-
-    /// Return the number of non-overlapped occurrences of \p Str in
-    /// the string.
-    size_t count(StringRef Str) const noexcept;
-
-    /// Parse the current string as an integer of the specified radix.  If
-    /// \p Radix is specified as zero, this does radix autosensing using
-    /// extended C rules: 0 is octal, 0x is hex, 0b is binary.
-    ///
-    /// If the string is invalid or if only a subset of the string is valid,
-    /// this returns true to signify the error.  The string is considered
-    /// erroneous if empty or if it overflows T.
-    template <typename T>
-    typename std::enable_if<std::numeric_limits<T>::is_signed, bool>::type
-    getAsInteger(unsigned Radix, T &Result) const noexcept {
-      long long LLVal;
-      if (getAsSignedInteger(*this, Radix, LLVal) ||
-            static_cast<T>(LLVal) != LLVal)
-        return true;
-      Result = LLVal;
-      return false;
-    }
-
-    template <typename T>
-    typename std::enable_if<!std::numeric_limits<T>::is_signed, bool>::type
-    getAsInteger(unsigned Radix, T &Result) const noexcept {
-      unsigned long long ULLVal;
-      // The additional cast to unsigned long long is required to avoid the
-      // Visual C++ warning C4805: '!=' : unsafe mix of type 'bool' and type
-      // 'unsigned __int64' when instantiating getAsInteger with T = bool.
-      if (getAsUnsignedInteger(*this, Radix, ULLVal) ||
-          static_cast<unsigned long long>(static_cast<T>(ULLVal)) != ULLVal)
-        return true;
-      Result = ULLVal;
-      return false;
-    }
-
-    /// Parse the current string as an integer of the specified radix.  If
-    /// \p Radix is specified as zero, this does radix autosensing using
-    /// extended C rules: 0 is octal, 0x is hex, 0b is binary.
-    ///
-    /// If the string does not begin with a number of the specified radix,
-    /// this returns true to signify the error. The string is considered
-    /// erroneous if empty or if it overflows T.
-    /// The portion of the string representing the discovered numeric value
-    /// is removed from the beginning of the string.
-    template <typename T>
-    typename std::enable_if<std::numeric_limits<T>::is_signed, bool>::type
-    consumeInteger(unsigned Radix, T &Result) noexcept {
-      long long LLVal;
-      if (consumeSignedInteger(*this, Radix, LLVal) ||
-          static_cast<long long>(static_cast<T>(LLVal)) != LLVal)
-        return true;
-      Result = LLVal;
-      return false;
-    }
-
-    template <typename T>
-    typename std::enable_if<!std::numeric_limits<T>::is_signed, bool>::type
-    consumeInteger(unsigned Radix, T &Result) noexcept {
-      unsigned long long ULLVal;
-      if (consumeUnsignedInteger(*this, Radix, ULLVal) ||
-          static_cast<unsigned long long>(static_cast<T>(ULLVal)) != ULLVal)
-        return true;
-      Result = ULLVal;
-      return false;
-    }
-
-    /// @}
-    /// @name String Operations
-    /// @{
-
-    // Convert the given ASCII string to lowercase.
-    LLVM_NODISCARD
-    std::string lower() const;
-
-    /// Convert the given ASCII string to uppercase.
-    LLVM_NODISCARD
-    std::string upper() const;
-
-    /// @}
-    /// @name Substring Operations
-    /// @{
-
-    /// Return a reference to the substring from [Start, Start + N).
-    ///
-    /// \param Start The index of the starting character in the substring; if
-    /// the index is npos or greater than the length of the string then the
-    /// empty substring will be returned.
-    ///
-    /// \param N The number of characters to included in the substring. If N
-    /// exceeds the number of characters remaining in the string, the string
-    /// suffix (starting with \p Start) will be returned.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef substr(size_t Start, size_t N = npos) const noexcept {
-      Start = (std::min)(Start, Length);
-      return StringRef(Data + Start, (std::min)(N, Length - Start));
-    }
-
-    /// Return a StringRef equal to 'this' but with only the first \p N
-    /// elements remaining.  If \p N is greater than the length of the
-    /// string, the entire string is returned.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef take_front(size_t N = 1) const noexcept {
-      if (N >= size())
-        return *this;
-      return drop_back(size() - N);
-    }
-
-    /// Return a StringRef equal to 'this' but with only the last \p N
-    /// elements remaining.  If \p N is greater than the length of the
-    /// string, the entire string is returned.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef take_back(size_t N = 1) const noexcept {
-      if (N >= size())
-        return *this;
-      return drop_front(size() - N);
-    }
-
-    /// Return the longest prefix of 'this' such that every character
-    /// in the prefix satisfies the given predicate.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef take_while(function_ref<bool(char)> F) const noexcept {
-      return substr(0, find_if_not(F));
-    }
-
-    /// Return the longest prefix of 'this' such that no character in
-    /// the prefix satisfies the given predicate.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef take_until(function_ref<bool(char)> F) const noexcept {
-      return substr(0, find_if(F));
-    }
-
-    /// Return a StringRef equal to 'this' but with the first \p N elements
-    /// dropped.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef drop_front(size_t N = 1) const noexcept {
-      assert(size() >= N && "Dropping more elements than exist");
-      return substr(N);
-    }
-
-    /// Return a StringRef equal to 'this' but with the last \p N elements
-    /// dropped.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef drop_back(size_t N = 1) const noexcept {
-      assert(size() >= N && "Dropping more elements than exist");
-      return substr(0, size()-N);
-    }
-
-    /// Return a StringRef equal to 'this', but with all characters satisfying
-    /// the given predicate dropped from the beginning of the string.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef drop_while(function_ref<bool(char)> F) const noexcept {
-      return substr(find_if_not(F));
-    }
-
-    /// Return a StringRef equal to 'this', but with all characters not
-    /// satisfying the given predicate dropped from the beginning of the string.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef drop_until(function_ref<bool(char)> F) const noexcept {
-      return substr(find_if(F));
-    }
-
-    /// Returns true if this StringRef has the given prefix and removes that
-    /// prefix.
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool consume_front(StringRef Prefix) noexcept {
-      if (!startswith(Prefix))
-        return false;
-
-      *this = drop_front(Prefix.size());
-      return true;
-    }
-
-    /// Returns true if this StringRef has the given suffix and removes that
-    /// suffix.
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    bool consume_back(StringRef Suffix) noexcept {
-      if (!endswith(Suffix))
-        return false;
-
-      *this = drop_back(Suffix.size());
-      return true;
-    }
-
-    /// Return a reference to the substring from [Start, End).
-    ///
-    /// \param Start The index of the starting character in the substring; if
-    /// the index is npos or greater than the length of the string then the
-    /// empty substring will be returned.
-    ///
-    /// \param End The index following the last character to include in the
-    /// substring. If this is npos or exceeds the number of characters
-    /// remaining in the string, the string suffix (starting with \p Start)
-    /// will be returned. If this is less than \p Start, an empty string will
-    /// be returned.
-    LLVM_NODISCARD
-    LLVM_ATTRIBUTE_ALWAYS_INLINE
-    StringRef slice(size_t Start, size_t End) const noexcept {
-      Start = (std::min)(Start, Length);
-      End = (std::min)((std::max)(Start, End), Length);
-      return StringRef(Data + Start, End - Start);
-    }
-
-    /// Split into two substrings around the first occurrence of a separator
-    /// character.
-    ///
-    /// If \p Separator is in the string, then the result is a pair (LHS, RHS)
-    /// such that (*this == LHS + Separator + RHS) is true and RHS is
-    /// maximal. If \p Separator is not in the string, then the result is a
-    /// pair (LHS, RHS) where (*this == LHS) and (RHS == "").
-    ///
-    /// \param Separator The character to split on.
-    /// \returns The split substrings.
-    LLVM_NODISCARD
-    std::pair<StringRef, StringRef> split(char Separator) const {
-      return split(StringRef(&Separator, 1));
-    }
-
-    /// Split into two substrings around the first occurrence of a separator
-    /// string.
-    ///
-    /// If \p Separator is in the string, then the result is a pair (LHS, RHS)
-    /// such that (*this == LHS + Separator + RHS) is true and RHS is
-    /// maximal. If \p Separator is not in the string, then the result is a
-    /// pair (LHS, RHS) where (*this == LHS) and (RHS == "").
-    ///
-    /// \param Separator - The string to split on.
-    /// \return - The split substrings.
-    LLVM_NODISCARD
-    std::pair<StringRef, StringRef> split(StringRef Separator) const {
-      size_t Idx = find(Separator);
-      if (Idx == npos)
-        return std::make_pair(*this, StringRef());
-      return std::make_pair(slice(0, Idx), slice(Idx + Separator.size(), npos));
-    }
-
-    /// Split into two substrings around the last occurrence of a separator
-    /// string.
-    ///
-    /// If \p Separator is in the string, then the result is a pair (LHS, RHS)
-    /// such that (*this == LHS + Separator + RHS) is true and RHS is
-    /// minimal. If \p Separator is not in the string, then the result is a
-    /// pair (LHS, RHS) where (*this == LHS) and (RHS == "").
-    ///
-    /// \param Separator - The string to split on.
-    /// \return - The split substrings.
-    LLVM_NODISCARD
-    std::pair<StringRef, StringRef> rsplit(StringRef Separator) const {
-      size_t Idx = rfind(Separator);
-      if (Idx == npos)
-        return std::make_pair(*this, StringRef());
-      return std::make_pair(slice(0, Idx), slice(Idx + Separator.size(), npos));
-    }
-
-    /// Split into substrings around the occurrences of a separator string.
-    ///
-    /// Each substring is stored in \p A. If \p MaxSplit is >= 0, at most
-    /// \p MaxSplit splits are done and consequently <= \p MaxSplit + 1
-    /// elements are added to A.
-    /// If \p KeepEmpty is false, empty strings are not added to \p A. They
-    /// still count when considering \p MaxSplit
-    /// An useful invariant is that
-    /// Separator.join(A) == *this if MaxSplit == -1 and KeepEmpty == true
-    ///
-    /// \param A - Where to put the substrings.
-    /// \param Separator - The string to split on.
-    /// \param MaxSplit - The maximum number of times the string is split.
-    /// \param KeepEmpty - True if empty substring should be added.
-    void split(SmallVectorImpl<StringRef> &A,
-               StringRef Separator, int MaxSplit = -1,
-               bool KeepEmpty = true) const;
-
-    /// Split into substrings around the occurrences of a separator character.
-    ///
-    /// Each substring is stored in \p A. If \p MaxSplit is >= 0, at most
-    /// \p MaxSplit splits are done and consequently <= \p MaxSplit + 1
-    /// elements are added to A.
-    /// If \p KeepEmpty is false, empty strings are not added to \p A. They
-    /// still count when considering \p MaxSplit
-    /// An useful invariant is that
-    /// Separator.join(A) == *this if MaxSplit == -1 and KeepEmpty == true
-    ///
-    /// \param A - Where to put the substrings.
-    /// \param Separator - The string to split on.
-    /// \param MaxSplit - The maximum number of times the string is split.
-    /// \param KeepEmpty - True if empty substring should be added.
-    void split(SmallVectorImpl<StringRef> &A, char Separator, int MaxSplit = -1,
-               bool KeepEmpty = true) const;
-
-    /// Split into two substrings around the last occurrence of a separator
-    /// character.
-    ///
-    /// If \p Separator is in the string, then the result is a pair (LHS, RHS)
-    /// such that (*this == LHS + Separator + RHS) is true and RHS is
-    /// minimal. If \p Separator is not in the string, then the result is a
-    /// pair (LHS, RHS) where (*this == LHS) and (RHS == "").
-    ///
-    /// \param Separator - The character to split on.
-    /// \return - The split substrings.
-    LLVM_NODISCARD
-    std::pair<StringRef, StringRef> rsplit(char Separator) const {
-      return rsplit(StringRef(&Separator, 1));
-    }
-
-    /// Return string with consecutive \p Char characters starting from the
-    /// the left removed.
-    LLVM_NODISCARD
-    StringRef ltrim(char Char) const noexcept {
-      return drop_front((std::min)(Length, find_first_not_of(Char)));
-    }
-
-    /// Return string with consecutive characters in \p Chars starting from
-    /// the left removed.
-    LLVM_NODISCARD
-    StringRef ltrim(StringRef Chars = " \t\n\v\f\r") const noexcept {
-      return drop_front((std::min)(Length, find_first_not_of(Chars)));
-    }
-
-    /// Return string with consecutive \p Char characters starting from the
-    /// right removed.
-    LLVM_NODISCARD
-    StringRef rtrim(char Char) const noexcept {
-      return drop_back(size() - (std::min)(Length, find_last_not_of(Char) + 1));
-    }
-
-    /// Return string with consecutive characters in \p Chars starting from
-    /// the right removed.
-    LLVM_NODISCARD
-    StringRef rtrim(StringRef Chars = " \t\n\v\f\r") const noexcept {
-      return drop_back(size() - (std::min)(Length, find_last_not_of(Chars) + 1));
-    }
-
-    /// Return string with consecutive \p Char characters starting from the
-    /// left and right removed.
-    LLVM_NODISCARD
-    StringRef trim(char Char) const noexcept {
-      return ltrim(Char).rtrim(Char);
-    }
-
-    /// Return string with consecutive characters in \p Chars starting from
-    /// the left and right removed.
-    LLVM_NODISCARD
-    StringRef trim(StringRef Chars = " \t\n\v\f\r") const noexcept {
-      return ltrim(Chars).rtrim(Chars);
-    }
-
-    /// @}
-  };
-
-  /// A wrapper around a string literal that serves as a proxy for constructing
-  /// global tables of StringRefs with the length computed at compile time.
-  /// In order to avoid the invocation of a global constructor, StringLiteral
-  /// should *only* be used in a constexpr context, as such:
-  ///
-  /// constexpr StringLiteral S("test");
-  ///
-  class StringLiteral : public StringRef {
-  private:
-    constexpr StringLiteral(const char *Str, size_t N) : StringRef(Str, N) {
-    }
-
-  public:
-    template <size_t N>
-    constexpr StringLiteral(const char (&Str)[N])
-#if defined(__clang__) && __has_attribute(enable_if)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wgcc-compat"
-        __attribute((enable_if(__builtin_strlen(Str) == N - 1,
-                               "invalid string literal")))
-#pragma clang diagnostic pop
-#endif
-        : StringRef(Str, N - 1) {
-    }
-
-    // Explicit construction for strings like "foo\0bar".
-    template <size_t N>
-    static constexpr StringLiteral withInnerNUL(const char (&Str)[N]) {
-      return StringLiteral(Str, N - 1);
-    }
-  };
-
-  /// @name StringRef Comparison Operators
-  /// @{
-
-  LLVM_ATTRIBUTE_ALWAYS_INLINE
-  bool operator==(StringRef LHS, StringRef RHS) noexcept {
-    return LHS.equals(RHS);
-  }
-
-  LLVM_ATTRIBUTE_ALWAYS_INLINE
-  bool operator!=(StringRef LHS, StringRef RHS) noexcept {
-    return !(LHS == RHS);
-  }
-
-  inline bool operator<(StringRef LHS, StringRef RHS) noexcept {
-    return LHS.compare(RHS) == -1;
-  }
-
-  inline bool operator<=(StringRef LHS, StringRef RHS) noexcept {
-    return LHS.compare(RHS) != 1;
-  }
-
-  inline bool operator>(StringRef LHS, StringRef RHS) noexcept {
-    return LHS.compare(RHS) == 1;
-  }
-
-  inline bool operator>=(StringRef LHS, StringRef RHS) noexcept {
-    return LHS.compare(RHS) != -1;
-  }
-
-  inline bool operator==(StringRef LHS, const char *RHS) noexcept {
-    return LHS.equals(StringRef(RHS));
-  }
-
-  inline bool operator!=(StringRef LHS, const char *RHS) noexcept {
-    return !(LHS == StringRef(RHS));
-  }
-
-  inline bool operator<(StringRef LHS, const char *RHS) noexcept {
-    return LHS.compare(StringRef(RHS)) == -1;
-  }
-
-  inline bool operator<=(StringRef LHS, const char *RHS) noexcept {
-    return LHS.compare(StringRef(RHS)) != 1;
-  }
-
-  inline bool operator>(StringRef LHS, const char *RHS) noexcept {
-    return LHS.compare(StringRef(RHS)) == 1;
-  }
-
-  inline bool operator>=(StringRef LHS, const char *RHS) noexcept {
-    return LHS.compare(StringRef(RHS)) != -1;
-  }
-
-  inline bool operator==(const char *LHS, StringRef RHS) noexcept {
-    return StringRef(LHS).equals(RHS);
-  }
-
-  inline bool operator!=(const char *LHS, StringRef RHS) noexcept {
-    return !(StringRef(LHS) == RHS);
-  }
-
-  inline bool operator<(const char *LHS, StringRef RHS) noexcept {
-    return StringRef(LHS).compare(RHS) == -1;
-  }
-
-  inline bool operator<=(const char *LHS, StringRef RHS) noexcept {
-    return StringRef(LHS).compare(RHS) != 1;
-  }
-
-  inline bool operator>(const char *LHS, StringRef RHS) noexcept {
-    return StringRef(LHS).compare(RHS) == 1;
-  }
-
-  inline bool operator>=(const char *LHS, StringRef RHS) noexcept {
-    return StringRef(LHS).compare(RHS) != -1;
-  }
-
-  inline std::string &operator+=(std::string &buffer, StringRef string) {
-    return buffer.append(string.data(), string.size());
-  }
-
-  std::ostream &operator<<(std::ostream &os, StringRef string);
-
-  /// @}
-
-  /// Compute a hash_code for a StringRef.
-  LLVM_NODISCARD
-  hash_code hash_value(StringRef S);
-
-  // StringRefs can be treated like a POD type.
-  template <typename T> struct isPodLike;
-  template <> struct isPodLike<StringRef> { static const bool value = true; };
-
-} // end namespace wpi
-
-#endif // LLVM_ADT_STRINGREF_H
diff --git a/wpiutil/src/main/native/include/wpi/SymbolExports.h b/wpiutil/src/main/native/include/wpi/SymbolExports.h
new file mode 100644
index 0000000..4e84825
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/SymbolExports.h
@@ -0,0 +1,23 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#ifdef _WIN32
+#pragma warning(disable : 4251)
+
+#ifdef WPILIB_EXPORTS
+#ifdef __GNUC__
+#define WPILIB_DLLEXPORT __attribute__((dllexport))
+#else
+#define WPILIB_DLLEXPORT __declspec(dllexport)
+#endif
+
+#else
+#define WPILIB_DLLEXPORT
+#endif
+
+#else
+#define WPILIB_DLLEXPORT __attribute__((visibility("default")))
+#endif
diff --git a/wpiutil/src/main/native/include/wpi/Synchronization.h b/wpiutil/src/main/native/include/wpi/Synchronization.h
new file mode 100644
index 0000000..8050358
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/Synchronization.h
@@ -0,0 +1,610 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <limits.h>  // NOLINT
+
+#ifdef __cplusplus
+#include <initializer_list>
+
+#include "wpi/span.h"
+#endif
+
+/**
+ * Generic handle for all WPI handle-based interfaces.
+ *
+ * Handle data layout:
+ * - Bits 0-23:  Type-specific
+ * - Bits 24-30: Type
+ * - Bit 31:     Error
+ */
+typedef unsigned int WPI_Handle;  // NOLINT
+
+/** An event handle. */
+typedef WPI_Handle WPI_EventHandle;  // NOLINT
+
+/** A semaphore handle. */
+typedef WPI_Handle WPI_SemaphoreHandle;  // NOLINT
+
+#ifdef __cplusplus
+
+namespace wpi {
+
+/** Constant representing an invalid handle. */
+constexpr unsigned int kInvalidHandle = 0;
+
+/**
+ * Standard types for handles.
+ * @{
+ */
+constexpr int kHandleTypeEvent = 1;
+constexpr int kHandleTypeSemaphore = 2;
+constexpr int kHandleTypeCSBase = 3;
+constexpr int kHandleTypeNTBase = 16;
+constexpr int kHandleTypeHALBase = 32;
+constexpr int kHandleTypeUserBase = 64;
+/** @} */
+
+/**
+ * Creates an event.  Events have binary state (signaled or not signaled) and
+ * may be either automatically reset or manually reset.  Automatic-reset events
+ * go to non-signaled state when a WaitForObject is woken up by the event;
+ * manual-reset events require ResetEvent() to be called to set the event to
+ * non-signaled state; if ResetEvent() is not called, any waiter on that event
+ * will immediately wake when called.
+ *
+ * @param manualReset true for manual reset, false for automatic reset
+ * @param initialState true to make the event initially in signaled state
+ * @return Event handle
+ */
+WPI_EventHandle CreateEvent(bool manualReset = false,
+                            bool initialState = false);
+
+/**
+ * Destroys an event.  Destruction wakes up any waiters.
+ *
+ * @param handle event handle
+ */
+void DestroyEvent(WPI_EventHandle handle);
+
+/**
+ * Sets an event to signaled state.
+ *
+ * @param handle event handle
+ */
+void SetEvent(WPI_EventHandle handle);
+
+/**
+ * Sets an event to non-signaled state.
+ *
+ * @param handle event handle
+ */
+void ResetEvent(WPI_EventHandle handle);
+
+/**
+ * Creates a semaphore.  Semaphores keep an internal counter.  Releasing the
+ * semaphore increases the count.  A semaphore with a non-zero count is
+ * considered signaled.  When a waiter wakes up it atomically decrements the
+ * count by 1.  This is generally useful in a single-supplier,
+ * multiple-consumer scenario.
+ *
+ * @param initialCount initial value for the semaphore's internal counter
+ * @param maximumCount maximum value for the samephore's internal counter
+ * @return Semaphore handle
+ */
+WPI_SemaphoreHandle CreateSemaphore(int initialCount = 0,
+                                    int maximumCount = INT_MAX);
+
+/**
+ * Destroys a semaphore.  Destruction wakes up any waiters.
+ *
+ * @param handle semaphore handle
+ */
+void DestroySemaphore(WPI_SemaphoreHandle handle);
+
+/**
+ * Releases N counts of a semaphore.
+ *
+ * @param handle semaphore handle
+ * @param releaseCount amount to add to semaphore's internal counter;
+ *        must be positive
+ * @param prevCount if non-null, previous count (output parameter)
+ * @return True on successful release, false on failure (e.g. release count
+ *         would exceed maximum value, or handle invalid)
+ */
+bool ReleaseSemaphore(WPI_SemaphoreHandle handle, int releaseCount = 1,
+                      int* prevCount = nullptr);
+
+/**
+ * Waits for an handle to be signaled.
+ *
+ * @param handle handle to wait on
+ * @return True if handle was signaled, false otherwise (e.g. object was
+ *         destroyed)
+ */
+bool WaitForObject(WPI_Handle handle);
+
+/**
+ * Waits for an handle to be signaled, with timeout.
+ *
+ * @param handle handle to wait on
+ * @param timeout timeout in seconds
+ * @param timedOut if non-null, set to true if timeout reached without handle
+ *        being signaled; set to false otherwise (output)
+ * @return True if handle was signaled, false otherwise (e.g. object was
+ *         destroyed or timed out)
+ */
+bool WaitForObject(WPI_Handle handle, double timeout, bool* timedOut);
+
+/**
+ * Waits for one or more handles to be signaled.
+ *
+ * Invalid handles are treated as signaled; the returned array will have the
+ * handle error bit set for any invalid handles.
+ *
+ * @param handles array of handles to wait on
+ * @param signaled output array for storage of signaled handles; must be at
+ *        least the size of the handles input array
+ * @return array of signaled handles (points into signaled array)
+ */
+wpi::span<WPI_Handle> WaitForObjects(wpi::span<const WPI_Handle> handles,
+                                     wpi::span<WPI_Handle> signaled);
+
+/**
+ * Waits for one or more handles to be signaled.
+ *
+ * Invalid handles are treated as signaled; the returned array will have the
+ * handle error bit set for any invalid handles.
+ *
+ * @param handles array of handles to wait on
+ * @param signaled output array for storage of signaled handles; must be at
+ *        least the size of the handles input array
+ * @return array of signaled handles (points into signaled array)
+ */
+inline wpi::span<WPI_Handle> WaitForObjects(
+    std::initializer_list<WPI_Handle> handles, wpi::span<WPI_Handle> signaled) {
+  return WaitForObjects(wpi::span{handles.begin(), handles.size()}, signaled);
+}
+
+/**
+ * Waits for one or more handles to be signaled, with timeout.
+ *
+ * Invalid handles are treated as signaled; the returned array will have the
+ * handle error bit set for any invalid handles.
+ *
+ * @param handles array of handles to wait on
+ * @param signaled output array for storage of signaled handles; must be at
+ *        least the size of the handles input array
+ * @param timeout timeout in seconds
+ * @param timedOut if non-null, set to true if timeout reached without any
+ *        handle being signaled; set to false otherwise (output)
+ * @return array of signaled handles (points into signaled array)
+ */
+wpi::span<WPI_Handle> WaitForObjects(wpi::span<const WPI_Handle> handles,
+                                     wpi::span<WPI_Handle> signaled,
+                                     double timeout, bool* timedOut);
+/**
+ * Waits for one or more handles to be signaled, with timeout.
+ *
+ * Invalid handles are treated as signaled; the returned array will have the
+ * handle error bit set for any invalid handles.
+ *
+ * @param handles array of handles to wait on
+ * @param signaled output array for storage of signaled handles; must be at
+ *        least the size of the handles input array
+ * @param timeout timeout in seconds
+ * @param timedOut if non-null, set to true if timeout reached without any
+ *        handle being signaled; set to false otherwise (output)
+ * @return array of signaled handles (points into signaled array)
+ */
+inline wpi::span<WPI_Handle> WaitForObjects(
+    std::initializer_list<WPI_Handle> handles, wpi::span<WPI_Handle> signaled,
+    double timeout, bool* timedOut) {
+  return WaitForObjects(wpi::span{handles.begin(), handles.size()}, signaled,
+                        timeout, timedOut);
+}
+
+/**
+ * Sets up signaling for an arbitrary handle.  With this function, any handle
+ * can operate like an event handle.
+ *
+ * @param handle handle
+ * @param manualReset true for manual reset, false for automatic reset
+ * @param initialState true to make the handle initially in signaled state
+ * @return Event handle
+ */
+void CreateSignalObject(WPI_Handle handle, bool manualReset = false,
+                        bool initialState = false);
+
+/**
+ * Sets a handle to signaled state.
+ *
+ * @param handle handle
+ */
+void SetSignalObject(WPI_Handle handle);
+
+/**
+ * Sets a handle to non-signaled state.
+ *
+ * @param handle handle
+ */
+void ResetSignalObject(WPI_Handle handle);
+
+/**
+ * Cleans up signaling for a handle.  Destruction wakes up any waiters.
+ *
+ * @param handle handle
+ */
+void DestroySignalObject(WPI_Handle handle);
+
+/**
+ * An atomic signaling event for synchronization.
+ *
+ * Events have binary state (signaled or not signaled) and may be either
+ * automatically reset or manually reset.  Automatic-reset events go to
+ * non-signaled state when a WaitForObject is woken up by the event;
+ * manual-reset events require Reset() to be called to set the event to
+ * non-signaled state; if Reset() is not called, any waiter on that event
+ * will immediately wake when called.
+ */
+class Event final {
+ public:
+  /**
+   * Constructor.
+   *
+   * @param manualReset true for manual reset, false for automatic reset
+   * @param initialState true to make the event initially in signaled state
+   */
+  explicit Event(bool manualReset = false, bool initialState = false)
+      : m_handle{CreateEvent(manualReset, initialState)} {}
+  ~Event() {
+    if (m_handle != 0) {
+      DestroyEvent(m_handle);
+    }
+  }
+
+  Event(const Event&) = delete;
+  Event& operator=(const Event&) = delete;
+
+  Event(Event&& rhs) : m_handle{rhs.m_handle} { rhs.m_handle = 0; }
+  Event& operator=(Event&& rhs) {
+    if (m_handle != 0) {
+      DestroyEvent(m_handle);
+    }
+    m_handle = rhs.m_handle;
+    rhs.m_handle = 0;
+    return *this;
+  }
+
+  /**
+   * Gets the event handle (e.g. for WaitForObject).
+   *
+   * @return handle
+   */
+  explicit operator WPI_Handle() const { return m_handle; }
+
+  /**
+   * Gets the event handle (e.g. for WaitForObject).
+   *
+   * @return handle
+   */
+  WPI_EventHandle GetHandle() const { return m_handle; }
+
+  /**
+   * Sets the event to signaled state.
+   */
+  void Set() { SetEvent(m_handle); }
+
+  /**
+   * Sets the event to non-signaled state.
+   */
+  void Reset() { ResetEvent(m_handle); }
+
+ private:
+  WPI_EventHandle m_handle;
+};
+
+/**
+ * A semaphore for synchronization.
+ *
+ * Semaphores keep an internal counter.  Releasing the semaphore increases
+ * the count.  A semaphore with a non-zero count is considered signaled.
+ * When a waiter wakes up it atomically decrements the count by 1.  This
+ * is generally useful in a single-supplier, multiple-consumer scenario.
+ */
+class Semaphore final {
+ public:
+  /**
+   * Constructor.
+   *
+   * @param initialCount initial value for the semaphore's internal counter
+   * @param maximumCount maximum value for the samephore's internal counter
+   */
+  explicit Semaphore(int initialCount = 0, int maximumCount = INT_MAX)
+      : m_handle{CreateSemaphore(initialCount, maximumCount)} {}
+  ~Semaphore() {
+    if (m_handle != 0) {
+      DestroySemaphore(m_handle);
+    }
+  }
+
+  Semaphore(const Semaphore&) = delete;
+  Semaphore& operator=(const Semaphore&) = delete;
+
+  Semaphore(Semaphore&& rhs) : m_handle{rhs.m_handle} { rhs.m_handle = 0; }
+  Semaphore& operator=(Semaphore&& rhs) {
+    if (m_handle != 0) {
+      DestroySemaphore(m_handle);
+    }
+    m_handle = rhs.m_handle;
+    rhs.m_handle = 0;
+    return *this;
+  }
+
+  /**
+   * Gets the semaphore handle (e.g. for WaitForObject).
+   *
+   * @return handle
+   */
+  explicit operator WPI_Handle() const { return m_handle; }
+
+  /**
+   * Gets the semaphore handle (e.g. for WaitForObject).
+   *
+   * @return handle
+   */
+  WPI_SemaphoreHandle GetHandle() const { return m_handle; }
+
+  /**
+   * Releases N counts of the semaphore.
+   *
+   * @param releaseCount amount to add to semaphore's internal counter;
+   *        must be positive
+   * @param prevCount if non-null, previous count (output parameter)
+   * @return True on successful release, false on failure (e.g. release count
+   *         would exceed maximum value, or handle invalid)
+   */
+  bool Release(int releaseCount = 1, int* prevCount = nullptr) {
+    return ReleaseSemaphore(m_handle, releaseCount, prevCount);
+  }
+
+ private:
+  WPI_SemaphoreHandle m_handle;
+};
+
+/**
+ * RAII wrapper for signaling handles.
+ *
+ * Sets up signaling for an arbitrary handle.  This enables any handle
+ * to operate like an event handle.
+ */
+template <typename T>
+class SignalObject final {
+ public:
+  /**
+   * Constructor.
+   *
+   * @param handle handle
+   * @param manualReset true for manual reset, false for automatic reset
+   * @param initialState true to make the handle initially in signaled state
+   */
+  explicit SignalObject(T handle = 0, bool manualReset = false,
+                        bool initialState = false)
+      : m_handle{handle} {
+    CreateSignalObject(handle, manualReset, initialState);
+  }
+  ~SignalObject() {
+    if (m_handle != 0) {
+      DestroySignalObject(m_handle);
+    }
+  }
+
+  SignalObject(const SignalObject&) = delete;
+  SignalObject& operator=(const SignalObject&) = delete;
+
+  SignalObject(SignalObject&& rhs) : m_handle{rhs.m_handle} {
+    rhs.m_handle = 0;
+  }
+  SignalObject& operator=(SignalObject&& rhs) {
+    if (m_handle != 0) {
+      DestroySemaphore(m_handle);
+    }
+    m_handle = rhs.m_handle;
+    rhs.m_handle = 0;
+    return *this;
+  }
+
+  /**
+   * Gets the handle.
+   *
+   * @return handle
+   */
+  /*implicit*/ operator T() const { return m_handle; }  // NOLINT
+
+  /**
+   * Gets the handle (e.g. for WaitForObject).
+   *
+   * @return handle
+   */
+  T GetHandle() const { return m_handle; }
+
+  /**
+   * Sets the handle to signaled state.
+   */
+  void Set() { SetSignalObject(m_handle); }
+
+  /**
+   * Sets the handle to non-signaled state.
+   */
+  void Reset() { ResetSignalObject(m_handle); }
+
+ private:
+  T m_handle;
+};
+
+}  // namespace wpi
+
+extern "C" {
+
+#endif  // __cplusplus
+
+/**
+ * Creates an event.  Events have binary state (signaled or not signaled) and
+ * may be either automatically reset or manually reset.  Automatic-reset events
+ * go to non-signaled state when a WaitForObject is woken up by the event;
+ * manual-reset events require ResetEvent() to be called to set the event to
+ * non-signaled state; if ResetEvent() is not called, any waiter on that event
+ * will immediately wake when called.
+ *
+ * @param manual_reset true for manual reset, false for automatic reset
+ * @param initial_state true to make the event initially in signaled state
+ * @return Event handle
+ */
+WPI_EventHandle WPI_CreateEvent(int manual_reset, int initial_state);
+
+/**
+ * Destroys an event.  Destruction wakes up any waiters.
+ *
+ * @param handle event handle
+ */
+void WPI_DestroyEvent(WPI_EventHandle handle);
+
+/**
+ * Sets an event to signaled state.
+ *
+ * @param handle event handle
+ */
+void WPI_SetEvent(WPI_EventHandle handle);
+
+/**
+ * Sets an event to non-signaled state.
+ *
+ * @param handle event handle
+ */
+void WPI_ResetEvent(WPI_EventHandle handle);
+
+/**
+ * Creates a semaphore.  Semaphores keep an internal counter.  Releasing the
+ * semaphore increases the count.  A semaphore with a non-zero count is
+ * considered signaled.  When a waiter wakes up it atomically decrements the
+ * count by 1.  This is generally useful in a single-supplier,
+ * multiple-consumer scenario.
+ *
+ * @param initial_count initial value for the semaphore's internal counter
+ * @param maximum_count maximum value for the samephore's internal counter
+ * @return Semaphore handle
+ */
+WPI_SemaphoreHandle WPI_CreateSemaphore(int initial_count, int maximum_count);
+
+/**
+ * Destroys a semaphore.  Destruction wakes up any waiters.
+ *
+ * @param handle semaphore handle
+ */
+void WPI_DestroySemaphore(WPI_SemaphoreHandle handle);
+
+/**
+ * Releases N counts of a semaphore.
+ *
+ * @param handle semaphore handle
+ * @param release_count amount to add to semaphore's internal counter;
+ *        must be positive
+ * @param prev_count if non-null, previous count (output parameter)
+ * @return Non-zero on successful release, zero on failure (e.g. release count
+ *         would exceed maximum value, or handle invalid)
+ */
+int WPI_ReleaseSemaphore(WPI_SemaphoreHandle handle, int release_count,
+                         int* prev_count);
+
+/**
+ * Waits for an handle to be signaled.
+ *
+ * @param handle handle to wait on
+ * @return Non-zero if handle was signaled, zero otherwise (e.g. object was
+ *         destroyed)
+ */
+int WPI_WaitForObject(WPI_Handle handle);
+
+/**
+ * Waits for an handle to be signaled, with timeout.
+ *
+ * @param handle handle to wait on
+ * @param timeout timeout in seconds
+ * @param timed_out if non-null, set to non-zero if timeout reached without
+ *        handle being signaled; set to zero otherwise (output)
+ * @return Non-zero if handle was signaled, zero otherwise (e.g. object was
+ *         destroyed or timed out)
+ */
+int WPI_WaitForObjectTimeout(WPI_Handle handle, double timeout, int* timed_out);
+
+/**
+ * Waits for one or more handles to be signaled.
+ *
+ * Invalid handles are treated as signaled; the returned array will have the
+ * handle error bit set for any invalid handles.
+ *
+ * @param handles array of handles to wait on
+ * @param handles_count length of the handles array
+ * @param signaled output array for storage of signaled handles; must be at
+ *        least the size of the handles input array
+ * @return number of signaled handles
+ */
+int WPI_WaitForObjects(const WPI_Handle* handles, int handles_count,
+                       WPI_Handle* signaled);
+
+/**
+ * Waits for one or more handles to be signaled, with timeout.
+ *
+ * Invalid handles are treated as signaled; the returned array will have the
+ * handle error bit set for any invalid handles.
+ *
+ * @param handles array of handles to wait on
+ * @param handles_count length of the handles array
+ * @param signaled output array for storage of signaled handles; must be at
+ *        least the size of the handles input array
+ * @param timeout timeout in seconds
+ * @param timed_out if non-null, set to non-zero if timeout reached without any
+ *        handle being signaled; set to zero otherwise (output)
+ * @return number of signaled handles
+ */
+int WPI_WaitForObjectsTimeout(const WPI_Handle* handles, int handles_count,
+                              WPI_Handle* signaled, double timeout,
+                              int* timed_out);
+
+/**
+ * Sets up signaling for an arbitrary handle.  With this function, any handle
+ * can operate like an event handle.
+ *
+ * @param handle handle
+ * @param manual_reset true for manual reset, false for automatic reset
+ * @param initial_state true to make the handle initially in signaled state
+ */
+void WPI_CreateSignalObject(WPI_Handle handle, int manual_reset,
+                            int initial_state);
+
+/**
+ * Sets a handle to signaled state.
+ *
+ * @param handle handle
+ */
+void WPI_SetSignalObject(WPI_Handle handle);
+
+/**
+ * Sets a handle to non-signaled state.
+ *
+ * @param handle handle
+ */
+void WPI_ResetSignalObject(WPI_Handle handle);
+
+/**
+ * Cleans up signaling for a handle.  Destruction wakes up any waiters.
+ *
+ * @param handle handle
+ */
+void WPI_DestroySignalObject(WPI_Handle handle);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
diff --git a/wpiutil/src/main/native/include/wpi/TCPAcceptor.h b/wpiutil/src/main/native/include/wpi/TCPAcceptor.h
index da82a8a..083897a 100644
--- a/wpiutil/src/main/native/include/wpi/TCPAcceptor.h
+++ b/wpiutil/src/main/native/include/wpi/TCPAcceptor.h
@@ -27,6 +27,7 @@
 #include <atomic>
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "wpi/NetworkAcceptor.h"
 #include "wpi/TCPStream.h"
@@ -44,11 +45,11 @@
   Logger& m_logger;
 
  public:
-  TCPAcceptor(int port, const char* address, Logger& logger);
-  ~TCPAcceptor();
+  TCPAcceptor(int port, std::string_view address, Logger& logger);
+  ~TCPAcceptor() override;
 
   int start() override;
-  void shutdown() override;
+  void shutdown() final;
   std::unique_ptr<NetworkStream> accept() override;
 };
 
diff --git a/wpiutil/src/main/native/include/wpi/TCPConnector.h b/wpiutil/src/main/native/include/wpi/TCPConnector.h
index a05342b..63e8906 100644
--- a/wpiutil/src/main/native/include/wpi/TCPConnector.h
+++ b/wpiutil/src/main/native/include/wpi/TCPConnector.h
@@ -27,8 +27,8 @@
 #include <memory>
 #include <utility>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/NetworkStream.h"
+#include "wpi/span.h"
 
 namespace wpi {
 
@@ -40,7 +40,7 @@
                                                 Logger& logger,
                                                 int timeout = 0);
   static std::unique_ptr<NetworkStream> connect_parallel(
-      ArrayRef<std::pair<const char*, int>> servers, Logger& logger,
+      span<const std::pair<const char*, int>> servers, Logger& logger,
       int timeout = 0);
 };
 
diff --git a/wpiutil/src/main/native/include/wpi/TCPStream.h b/wpiutil/src/main/native/include/wpi/TCPStream.h
index 0c6e8a9..2d54300 100644
--- a/wpiutil/src/main/native/include/wpi/TCPStream.h
+++ b/wpiutil/src/main/native/include/wpi/TCPStream.h
@@ -26,6 +26,7 @@
 
 #include <cstddef>
 #include <string>
+#include <string_view>
 
 #include "wpi/NetworkStream.h"
 
@@ -43,14 +44,14 @@
   friend class TCPAcceptor;
   friend class TCPConnector;
 
-  ~TCPStream();
+  ~TCPStream() override;
 
   size_t send(const char* buffer, size_t len, Error* err) override;
   size_t receive(char* buffer, size_t len, Error* err,
                  int timeout = 0) override;
-  void close() override;
+  void close() final;
 
-  StringRef getPeerIP() const override;
+  std::string_view getPeerIP() const override;
   int getPeerPort() const override;
   void setNoDelay() override;
   bool setBlocking(bool enabled) override;
diff --git a/wpiutil/src/main/native/include/wpi/Twine.h b/wpiutil/src/main/native/include/wpi/Twine.h
deleted file mode 100644
index a5b7e97..0000000
--- a/wpiutil/src/main/native/include/wpi/Twine.h
+++ /dev/null
@@ -1,543 +0,0 @@
-//===- Twine.h - Fast Temporary String Concatenation ------------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef WPIUTIL_WPI_TWINE_H
-#define WPIUTIL_WPI_TWINE_H
-
-#include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
-#include "wpi/ErrorHandling.h"
-#include <cassert>
-#include <cstdint>
-#include <string>
-
-#ifdef _WIN32
-#pragma warning(push)
-#pragma warning(disable : 26495)
-#endif
-
-namespace wpi {
-
-  class raw_ostream;
-
-  /// Twine - A lightweight data structure for efficiently representing the
-  /// concatenation of temporary values as strings.
-  ///
-  /// A Twine is a kind of rope, it represents a concatenated string using a
-  /// binary-tree, where the string is the preorder of the nodes. Since the
-  /// Twine can be efficiently rendered into a buffer when its result is used,
-  /// it avoids the cost of generating temporary values for intermediate string
-  /// results -- particularly in cases when the Twine result is never
-  /// required. By explicitly tracking the type of leaf nodes, we can also avoid
-  /// the creation of temporary strings for conversions operations (such as
-  /// appending an integer to a string).
-  ///
-  /// A Twine is not intended for use directly and should not be stored, its
-  /// implementation relies on the ability to store pointers to temporary stack
-  /// objects which may be deallocated at the end of a statement. Twines should
-  /// only be used accepted as const references in arguments, when an API wishes
-  /// to accept possibly-concatenated strings.
-  ///
-  /// Twines support a special 'null' value, which always concatenates to form
-  /// itself, and renders as an empty string. This can be returned from APIs to
-  /// effectively nullify any concatenations performed on the result.
-  ///
-  /// \b Implementation
-  ///
-  /// Given the nature of a Twine, it is not possible for the Twine's
-  /// concatenation method to construct interior nodes; the result must be
-  /// represented inside the returned value. For this reason a Twine object
-  /// actually holds two values, the left- and right-hand sides of a
-  /// concatenation. We also have nullary Twine objects, which are effectively
-  /// sentinel values that represent empty strings.
-  ///
-  /// Thus, a Twine can effectively have zero, one, or two children. The \see
-  /// isNullary(), \see isUnary(), and \see isBinary() predicates exist for
-  /// testing the number of children.
-  ///
-  /// We maintain a number of invariants on Twine objects (FIXME: Why):
-  ///  - Nullary twines are always represented with their Kind on the left-hand
-  ///    side, and the Empty kind on the right-hand side.
-  ///  - Unary twines are always represented with the value on the left-hand
-  ///    side, and the Empty kind on the right-hand side.
-  ///  - If a Twine has another Twine as a child, that child should always be
-  ///    binary (otherwise it could have been folded into the parent).
-  ///
-  /// These invariants are check by \see isValid().
-  ///
-  /// \b Efficiency Considerations
-  ///
-  /// The Twine is designed to yield efficient and small code for common
-  /// situations. For this reason, the concat() method is inlined so that
-  /// concatenations of leaf nodes can be optimized into stores directly into a
-  /// single stack allocated object.
-  ///
-  /// In practice, not all compilers can be trusted to optimize concat() fully,
-  /// so we provide two additional methods (and accompanying operator+
-  /// overloads) to guarantee that particularly important cases (cstring plus
-  /// StringRef) codegen as desired.
-  class Twine {
-    /// NodeKind - Represent the type of an argument.
-    enum NodeKind : unsigned char {
-      /// An empty string; the result of concatenating anything with it is also
-      /// empty.
-      NullKind,
-
-      /// The empty string.
-      EmptyKind,
-
-      /// A pointer to a Twine instance.
-      TwineKind,
-
-      /// A pointer to a C string instance.
-      CStringKind,
-
-      /// A pointer to an std::string instance.
-      StdStringKind,
-
-      /// A pointer to a StringRef instance.
-      StringRefKind,
-
-      /// A pointer to a SmallString instance.
-      SmallStringKind,
-
-      /// A char value, to render as a character.
-      CharKind,
-
-      /// An unsigned int value, to render as an unsigned decimal integer.
-      DecUIKind,
-
-      /// An int value, to render as a signed decimal integer.
-      DecIKind,
-
-      /// A pointer to an unsigned long value, to render as an unsigned decimal
-      /// integer.
-      DecULKind,
-
-      /// A pointer to a long value, to render as a signed decimal integer.
-      DecLKind,
-
-      /// A pointer to an unsigned long long value, to render as an unsigned
-      /// decimal integer.
-      DecULLKind,
-
-      /// A pointer to a long long value, to render as a signed decimal integer.
-      DecLLKind,
-
-      /// A pointer to a uint64_t value, to render as an unsigned hexadecimal
-      /// integer.
-      UHexKind
-    };
-
-    union Child
-    {
-      const Twine *twine;
-      const char *cString;
-      const std::string *stdString;
-      const StringRef *stringRef;
-      const SmallVectorImpl<char> *smallString;
-      char character;
-      unsigned int decUI;
-      int decI;
-      const unsigned long *decUL;
-      const long *decL;
-      const unsigned long long *decULL;
-      const long long *decLL;
-      const uint64_t *uHex;
-    };
-
-    /// LHS - The prefix in the concatenation, which may be uninitialized for
-    /// Null or Empty kinds.
-    Child LHS;
-
-    /// RHS - The suffix in the concatenation, which may be uninitialized for
-    /// Null or Empty kinds.
-    Child RHS;
-
-    /// LHSKind - The NodeKind of the left hand side, \see getLHSKind().
-    NodeKind LHSKind = EmptyKind;
-
-    /// RHSKind - The NodeKind of the right hand side, \see getRHSKind().
-    NodeKind RHSKind = EmptyKind;
-
-    /// Construct a nullary twine; the kind must be NullKind or EmptyKind.
-    explicit Twine(NodeKind Kind) : LHSKind(Kind) {
-      assert(isNullary() && "Invalid kind!");
-    }
-
-    /// Construct a binary twine.
-    explicit Twine(const Twine &LHS, const Twine &RHS)
-        : LHSKind(TwineKind), RHSKind(TwineKind) {
-      this->LHS.twine = &LHS;
-      this->RHS.twine = &RHS;
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Construct a twine from explicit values.
-    explicit Twine(Child LHS, NodeKind LHSKind, Child RHS, NodeKind RHSKind)
-        : LHS(LHS), RHS(RHS), LHSKind(LHSKind), RHSKind(RHSKind) {
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Check for the empty twine.
-    bool isEmpty() const {
-      return getLHSKind() == EmptyKind;
-    }
-
-    /// Check if this is a nullary twine (null or empty).
-    bool isNullary() const {
-      return isNull() || isEmpty();
-    }
-
-    /// Check if this is a unary twine.
-    bool isUnary() const {
-      return getRHSKind() == EmptyKind && !isNullary();
-    }
-
-    /// Check if this is a binary twine.
-    bool isBinary() const {
-      return getLHSKind() != NullKind && getRHSKind() != EmptyKind;
-    }
-
-    /// Check if this is a valid twine (satisfying the invariants on
-    /// order and number of arguments).
-    bool isValid() const {
-      // Nullary twines always have Empty on the RHS.
-      if (isNullary() && getRHSKind() != EmptyKind)
-        return false;
-
-      // Null should never appear on the RHS.
-      if (getRHSKind() == NullKind)
-        return false;
-
-      // The RHS cannot be non-empty if the LHS is empty.
-      if (getRHSKind() != EmptyKind && getLHSKind() == EmptyKind)
-        return false;
-#if 0 // causes spurious warnings
-      // A twine child should always be binary.
-      if (getLHSKind() == TwineKind &&
-          !LHS.twine->isBinary())
-        return false;
-      if (getRHSKind() == TwineKind &&
-          !RHS.twine->isBinary())
-        return false;
-#endif
-      return true;
-    }
-
-    /// Get the NodeKind of the left-hand side.
-    NodeKind getLHSKind() const { return LHSKind; }
-
-    /// Get the NodeKind of the right-hand side.
-    NodeKind getRHSKind() const { return RHSKind; }
-
-    /// Print one child from a twine.
-    void printOneChild(raw_ostream &OS, Child Ptr, NodeKind Kind) const;
-
-    /// Print the representation of one child from a twine.
-    void printOneChildRepr(raw_ostream &OS, Child Ptr,
-                           NodeKind Kind) const;
-
-  public:
-    /// @name Constructors
-    /// @{
-
-    /// Construct from an empty string.
-    /*implicit*/ Twine() {
-      assert(isValid() && "Invalid twine!");
-    }
-
-    Twine(const Twine &) = default;
-
-    /// Construct from a C string.
-    ///
-    /// We take care here to optimize "" into the empty twine -- this will be
-    /// optimized out for string constants. This allows Twine arguments have
-    /// default "" values, without introducing unnecessary string constants.
-    /*implicit*/ Twine(const char *Str) {
-      if (Str[0] != '\0') {
-        LHS.cString = Str;
-        LHSKind = CStringKind;
-      } else
-        LHSKind = EmptyKind;
-
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Construct from an std::string.
-    /*implicit*/ Twine(const std::string &Str) : LHSKind(StdStringKind) {
-      LHS.stdString = &Str;
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Construct from a StringRef.
-    /*implicit*/ Twine(const StringRef &Str) : LHSKind(StringRefKind) {
-      LHS.stringRef = &Str;
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Construct from a SmallString.
-    /*implicit*/ Twine(const SmallVectorImpl<char> &Str)
-        : LHSKind(SmallStringKind) {
-      LHS.smallString = &Str;
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Construct from a char.
-    explicit Twine(char Val) : LHSKind(CharKind) {
-      LHS.character = Val;
-    }
-
-    /// Construct from a signed char.
-    explicit Twine(signed char Val) : LHSKind(CharKind) {
-      LHS.character = static_cast<char>(Val);
-    }
-
-    /// Construct from an unsigned char.
-    explicit Twine(unsigned char Val) : LHSKind(CharKind) {
-      LHS.character = static_cast<char>(Val);
-    }
-
-    /// Construct a twine to print \p Val as an unsigned decimal integer.
-    explicit Twine(unsigned Val) : LHSKind(DecUIKind) {
-      LHS.decUI = Val;
-    }
-
-    /// Construct a twine to print \p Val as a signed decimal integer.
-    explicit Twine(int Val) : LHSKind(DecIKind) {
-      LHS.decI = Val;
-    }
-
-    /// Construct a twine to print \p Val as an unsigned decimal integer.
-    explicit Twine(const unsigned long &Val) : LHSKind(DecULKind) {
-      LHS.decUL = &Val;
-    }
-
-    /// Construct a twine to print \p Val as a signed decimal integer.
-    explicit Twine(const long &Val) : LHSKind(DecLKind) {
-      LHS.decL = &Val;
-    }
-
-    /// Construct a twine to print \p Val as an unsigned decimal integer.
-    explicit Twine(const unsigned long long &Val) : LHSKind(DecULLKind) {
-      LHS.decULL = &Val;
-    }
-
-    /// Construct a twine to print \p Val as a signed decimal integer.
-    explicit Twine(const long long &Val) : LHSKind(DecLLKind) {
-      LHS.decLL = &Val;
-    }
-
-    // FIXME: Unfortunately, to make sure this is as efficient as possible we
-    // need extra binary constructors from particular types. We can't rely on
-    // the compiler to be smart enough to fold operator+()/concat() down to the
-    // right thing. Yet.
-
-    /// Construct as the concatenation of a C string and a StringRef.
-    /*implicit*/ Twine(const char *LHS, const StringRef &RHS)
-        : LHSKind(CStringKind), RHSKind(StringRefKind) {
-      this->LHS.cString = LHS;
-      this->RHS.stringRef = &RHS;
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Construct as the concatenation of a StringRef and a C string.
-    /*implicit*/ Twine(const StringRef &LHS, const char *RHS)
-        : LHSKind(StringRefKind), RHSKind(CStringKind) {
-      this->LHS.stringRef = &LHS;
-      this->RHS.cString = RHS;
-      assert(isValid() && "Invalid twine!");
-    }
-
-    /// Since the intended use of twines is as temporary objects, assignments
-    /// when concatenating might cause undefined behavior or stack corruptions
-    Twine &operator=(const Twine &) = delete;
-
-    /// Create a 'null' string, which is an empty string that always
-    /// concatenates to form another empty string.
-    static Twine createNull() {
-      return Twine(NullKind);
-    }
-
-    /// @}
-    /// @name Numeric Conversions
-    /// @{
-
-    // Construct a twine to print \p Val as an unsigned hexadecimal integer.
-    static Twine utohexstr(const uint64_t &Val) {
-      Child LHS, RHS;
-      LHS.uHex = &Val;
-      RHS.twine = nullptr;
-      return Twine(LHS, UHexKind, RHS, EmptyKind);
-    }
-
-    /// @}
-    /// @name Predicate Operations
-    /// @{
-
-    /// Check for the null twine.
-    bool isNull() const {
-      return getLHSKind() == NullKind;
-    }
-
-    /// Check if this twine is trivially empty; a false return value does not
-    /// necessarily mean the twine is empty.
-    bool isTriviallyEmpty() const {
-      return isNullary();
-    }
-
-    /// Return true if this twine can be dynamically accessed as a single
-    /// StringRef value with getSingleStringRef().
-    bool isSingleStringRef() const {
-      if (getRHSKind() != EmptyKind) return false;
-
-      switch (getLHSKind()) {
-      case EmptyKind:
-      case CStringKind:
-      case StdStringKind:
-      case StringRefKind:
-      case SmallStringKind:
-      case CharKind:
-        return true;
-      default:
-        return false;
-      }
-    }
-
-    /// @}
-    /// @name String Operations
-    /// @{
-
-    Twine concat(const Twine &Suffix) const;
-
-    /// @}
-    /// @name Output & Conversion.
-    /// @{
-
-    /// Return the twine contents as a std::string.
-    std::string str() const;
-
-    /// Append the concatenated string into the given SmallString or SmallVector.
-    void toVector(SmallVectorImpl<char> &Out) const;
-
-    /// This returns the twine as a single StringRef.  This method is only valid
-    /// if isSingleStringRef() is true.
-    StringRef getSingleStringRef() const {
-      assert(isSingleStringRef() &&"This cannot be had as a single stringref!");
-      switch (getLHSKind()) {
-      default:
-        // unreachable("Out of sync with isSingleStringRef");
-        return StringRef();
-      case EmptyKind:      return StringRef();
-      case CStringKind:    return StringRef(LHS.cString);
-      case StdStringKind:  return StringRef(*LHS.stdString);
-      case StringRefKind:  return *LHS.stringRef;
-      case SmallStringKind:
-        return StringRef(LHS.smallString->data(), LHS.smallString->size());
-      case CharKind:       return StringRef(&LHS.character, 1);
-      }
-    }
-
-    /// This returns the twine as a single StringRef if it can be
-    /// represented as such. Otherwise the twine is written into the given
-    /// SmallVector and a StringRef to the SmallVector's data is returned.
-    StringRef toStringRef(SmallVectorImpl<char> &Out) const {
-      if (isSingleStringRef())
-        return getSingleStringRef();
-      toVector(Out);
-      return StringRef(Out.data(), Out.size());
-    }
-
-    /// This returns the twine as a single null terminated StringRef if it
-    /// can be represented as such. Otherwise the twine is written into the
-    /// given SmallVector and a StringRef to the SmallVector's data is returned.
-    ///
-    /// The returned StringRef's size does not include the null terminator.
-    StringRef toNullTerminatedStringRef(SmallVectorImpl<char> &Out) const;
-
-    /// Write the concatenated string represented by this twine to the
-    /// stream \p OS.
-    void print(raw_ostream &OS) const;
-
-    /// Dump the concatenated string represented by this twine to stderr.
-    void dump() const;
-
-    /// Write the representation of this twine to the stream \p OS.
-    void printRepr(raw_ostream &OS) const;
-
-    /// Dump the representation of this twine to stderr.
-    void dumpRepr() const;
-
-    /// @}
-  };
-
-  /// @name Twine Inline Implementations
-  /// @{
-
-  inline Twine Twine::concat(const Twine &Suffix) const {
-    // Concatenation with null is null.
-    if (isNull() || Suffix.isNull())
-      return Twine(NullKind);
-
-    // Concatenation with empty yields the other side.
-    if (isEmpty())
-      return Suffix;
-    if (Suffix.isEmpty())
-      return *this;
-
-    // Otherwise we need to create a new node, taking care to fold in unary
-    // twines.
-    Child NewLHS, NewRHS;
-    NewLHS.twine = this;
-    NewRHS.twine = &Suffix;
-    NodeKind NewLHSKind = TwineKind, NewRHSKind = TwineKind;
-    if (isUnary()) {
-      NewLHS = LHS;
-      NewLHSKind = getLHSKind();
-    }
-    if (Suffix.isUnary()) {
-      NewRHS = Suffix.LHS;
-      NewRHSKind = Suffix.getLHSKind();
-    }
-
-    return Twine(NewLHS, NewLHSKind, NewRHS, NewRHSKind);
-  }
-
-  inline Twine operator+(const Twine &LHS, const Twine &RHS) {
-    return LHS.concat(RHS);
-  }
-
-  /// Additional overload to guarantee simplified codegen; this is equivalent to
-  /// concat().
-
-  inline Twine operator+(const char *LHS, const StringRef &RHS) {
-    return Twine(LHS, RHS);
-  }
-
-  /// Additional overload to guarantee simplified codegen; this is equivalent to
-  /// concat().
-
-  inline Twine operator+(const StringRef &LHS, const char *RHS) {
-    return Twine(LHS, RHS);
-  }
-
-  inline raw_ostream &operator<<(raw_ostream &OS, const Twine &RHS) {
-    RHS.print(OS);
-    return OS;
-  }
-
-  /// @}
-
-} // end namespace wpi
-
-#ifdef _WIN32
-#pragma warning(pop)
-#endif
-
-#endif // LLVM_ADT_TWINE_H
diff --git a/wpiutil/src/main/native/include/wpi/UDPClient.h b/wpiutil/src/main/native/include/wpi/UDPClient.h
index 635eca8..fd2e30b 100644
--- a/wpiutil/src/main/native/include/wpi/UDPClient.h
+++ b/wpiutil/src/main/native/include/wpi/UDPClient.h
@@ -1,20 +1,16 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UDPCLIENT_H_
 #define WPIUTIL_WPI_UDPCLIENT_H_
 
 #include <string>
+#include <string_view>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
 #include "wpi/mutex.h"
+#include "wpi/span.h"
 
 namespace wpi {
 
@@ -28,7 +24,7 @@
 
  public:
   explicit UDPClient(Logger& logger);
-  UDPClient(const Twine& address, Logger& logger);
+  UDPClient(std::string_view address, Logger& logger);
   UDPClient(const UDPClient& other) = delete;
   UDPClient(UDPClient&& other);
   ~UDPClient();
@@ -40,8 +36,8 @@
   int start(int port);
   void shutdown();
   // The passed in address MUST be a resolved IP address.
-  int send(ArrayRef<uint8_t> data, const Twine& server, int port);
-  int send(StringRef data, const Twine& server, int port);
+  int send(span<const uint8_t> data, std::string_view server, int port);
+  int send(std::string_view data, std::string_view server, int port);
   int receive(uint8_t* data_received, int receive_len);
   int receive(uint8_t* data_received, int receive_len,
               SmallVectorImpl<char>* addr_received, int* port_received);
diff --git a/wpiutil/src/main/native/include/wpi/UidVector.h b/wpiutil/src/main/native/include/wpi/UidVector.h
index 077dc27..3ffd4c3 100644
--- a/wpiutil/src/main/native/include/wpi/UidVector.h
+++ b/wpiutil/src/main/native/include/wpi/UidVector.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UIDVECTOR_H_
 #define WPIUTIL_WPI_UIDVECTOR_H_
@@ -27,7 +24,9 @@
   UidVectorIterator() = default;
   explicit UidVectorIterator(It it, It end) : m_it(it), m_end(end) {
     // advance to first non-empty element
-    while (m_it != m_end && !*m_it) ++m_it;
+    while (m_it != m_end && !*m_it) {
+      ++m_it;
+    }
   }
 
   reference operator*() const noexcept { return *m_it; }
@@ -61,12 +60,15 @@
 };
 }  // namespace impl
 
-// Vector which provides an integrated freelist for removal and reuse of
-// individual elements.
-// @tparam T element type; must be default-constructible and evaluate in
-//           boolean context to false when "empty"
-// @tparam reuse_threshold how many free elements to store up before starting
-//                         to recycle them
+/**
+ * Vector which provides an integrated freelist for removal and reuse of
+ * individual elements.
+ *
+ * @tparam T element type; must be default-constructible and evaluate in
+ *           boolean context to false when "empty"
+ * @tparam reuse_threshold how many free elements to store up before starting
+ *                         to recycle them
+ */
 template <typename T, typename std::vector<T>::size_type reuse_threshold>
 class UidVector {
  public:
@@ -81,8 +83,8 @@
   using const_iterator =
       impl::UidVectorIterator<typename std::vector<T>::const_iterator>;
 
-  bool empty() const { return m_active_count == 0; }
-  size_type size() const { return m_vector.size(); }
+  bool empty() const noexcept { return m_active_count == 0; }
+  size_type size() const noexcept { return m_vector.size(); }
   T& operator[](size_type i) { return m_vector[i]; }
   const T& operator[](size_type i) const { return m_vector[i]; }
 
@@ -104,17 +106,25 @@
     return uid;
   }
 
-  // Removes the identified element by replacing it with a default-constructed
-  // one.  The element is added to the freelist for later reuse.
-  void erase(size_type uid) {
-    if (uid >= m_vector.size() || !m_vector[uid]) return;
+  /**
+   * Removes the identified element by replacing it with a default-constructed
+   * one. The element is added to the freelist for later reuse.
+   */
+  T erase(size_type uid) {
+    if (uid >= m_vector.size() || !m_vector[uid]) {
+      return T();
+    }
     m_free.push_back(uid);
+    auto rv = std::move(m_vector[uid]);
     m_vector[uid] = T();
     --m_active_count;
+    return rv;
   }
 
-  // Removes all elements.
-  void clear() {
+  /**
+   * Removes all elements.
+   */
+  void clear() noexcept {
     m_vector.clear();
     m_free.clear();
     m_active_count = 0;
diff --git a/wpiutil/src/main/native/include/wpi/UrlParser.h b/wpiutil/src/main/native/include/wpi/UrlParser.h
index e3da269..3b6107b 100644
--- a/wpiutil/src/main/native/include/wpi/UrlParser.h
+++ b/wpiutil/src/main/native/include/wpi/UrlParser.h
@@ -1,14 +1,12 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_URLPARSER_H_
 #define WPIUTIL_WPI_URLPARSER_H_
 
-#include "wpi/StringRef.h"
+#include <string_view>
+
 #include "wpi/http_parser.h"
 
 namespace wpi {
@@ -24,7 +22,7 @@
    * @param in input
    * @param isConnect
    */
-  UrlParser(StringRef in, bool isConnect) {
+  UrlParser(std::string_view in, bool isConnect) {
     m_data = in;
     http_parser_url_init(&m_url);
     m_error = http_parser_parse_url(in.data(), in.size(), isConnect, &m_url);
@@ -53,41 +51,41 @@
     return (m_url.field_set & (1 << UF_USERINFO)) != 0;
   }
 
-  StringRef GetSchema() const {
+  std::string_view GetSchema() const {
     return m_data.substr(m_url.field_data[UF_SCHEMA].off,
                          m_url.field_data[UF_SCHEMA].len);
   }
 
-  StringRef GetHost() const {
+  std::string_view GetHost() const {
     return m_data.substr(m_url.field_data[UF_HOST].off,
                          m_url.field_data[UF_HOST].len);
   }
 
   unsigned int GetPort() const { return m_url.port; }
 
-  StringRef GetPath() const {
+  std::string_view GetPath() const {
     return m_data.substr(m_url.field_data[UF_PATH].off,
                          m_url.field_data[UF_PATH].len);
   }
 
-  StringRef GetQuery() const {
+  std::string_view GetQuery() const {
     return m_data.substr(m_url.field_data[UF_QUERY].off,
                          m_url.field_data[UF_QUERY].len);
   }
 
-  StringRef GetFragment() const {
+  std::string_view GetFragment() const {
     return m_data.substr(m_url.field_data[UF_FRAGMENT].off,
                          m_url.field_data[UF_FRAGMENT].len);
   }
 
-  StringRef GetUserInfo() const {
+  std::string_view GetUserInfo() const {
     return m_data.substr(m_url.field_data[UF_USERINFO].off,
                          m_url.field_data[UF_USERINFO].len);
   }
 
  private:
   bool m_error;
-  StringRef m_data;
+  std::string_view m_data;
   http_parser_url m_url;
 };
 
diff --git a/wpiutil/src/main/native/include/wpi/VersionTuple.h b/wpiutil/src/main/native/include/wpi/VersionTuple.h
index e54ca92..59df41c 100644
--- a/wpiutil/src/main/native/include/wpi/VersionTuple.h
+++ b/wpiutil/src/main/native/include/wpi/VersionTuple.h
@@ -15,11 +15,8 @@
 #ifndef WPIUTIL_WPI_VERSIONTUPLE_H
 #define WPIUTIL_WPI_VERSIONTUPLE_H
 
-#include "wpi/StringRef.h"
-#include "wpi/raw_ostream.h"
 #include <optional>
 #include <string>
-#include <tuple>
 
 namespace wpi {
 
@@ -137,18 +134,7 @@
   friend bool operator>=(const VersionTuple &X, const VersionTuple &Y) {
     return !(X < Y);
   }
-
-  /// Retrieve a string representation of the version number.
-  std::string getAsString() const;
-
-  /// Try to parse the given string as a version number.
-  /// \returns \c true if the string does not match the regular expression
-  ///   [0-9]+(\.[0-9]+){0,3}
-  bool tryParse(StringRef string);
 };
 
-/// Print a version number.
-raw_ostream &operator<<(raw_ostream &Out, const VersionTuple &V);
-
 } // end namespace wpi
 #endif // WPIUTIL_WPI_VERSIONTUPLE_H
diff --git a/wpiutil/src/main/native/include/wpi/WebSocket.h b/wpiutil/src/main/native/include/wpi/WebSocket.h
index 8347d0c..fc2ae4a 100644
--- a/wpiutil/src/main/native/include/wpi/WebSocket.h
+++ b/wpiutil/src/main/native/include/wpi/WebSocket.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_WEBSOCKET_H_
 #define WPIUTIL_WPI_WEBSOCKET_H_
@@ -14,13 +11,12 @@
 #include <initializer_list>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <utility>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/Signal.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
+#include "wpi/span.h"
 #include "wpi/uv/Buffer.h"
 #include "wpi/uv/Error.h"
 #include "wpi/uv/Timer.h"
@@ -79,10 +75,10 @@
     ClientOptions() : handshakeTimeout{(uv::Timer::Time::max)()} {}
 
     /** Timeout for the handshake request. */
-    uv::Timer::Time handshakeTimeout;
+    uv::Timer::Time handshakeTimeout;  // NOLINT
 
     /** Additional headers to include in handshake. */
-    ArrayRef<std::pair<StringRef, StringRef>> extraHeaders;
+    span<const std::pair<std::string_view, std::string_view>> extraHeaders;
   };
 
   /**
@@ -96,9 +92,9 @@
    * @param options Handshake options
    */
   static std::shared_ptr<WebSocket> CreateClient(
-      uv::Stream& stream, const Twine& uri, const Twine& host,
-      ArrayRef<StringRef> protocols = ArrayRef<StringRef>{},
-      const ClientOptions& options = ClientOptions{});
+      uv::Stream& stream, std::string_view uri, std::string_view host,
+      span<const std::string_view> protocols = {},
+      const ClientOptions& options = {});
 
   /**
    * Starts a client connection by performing the initial client handshake.
@@ -111,11 +107,10 @@
    * @param options Handshake options
    */
   static std::shared_ptr<WebSocket> CreateClient(
-      uv::Stream& stream, const Twine& uri, const Twine& host,
-      std::initializer_list<StringRef> protocols,
-      const ClientOptions& options = ClientOptions{}) {
-    return CreateClient(stream, uri, host,
-                        makeArrayRef(protocols.begin(), protocols.end()),
+      uv::Stream& stream, std::string_view uri, std::string_view host,
+      std::initializer_list<std::string_view> protocols,
+      const ClientOptions& options = {}) {
+    return CreateClient(stream, uri, host, {protocols.begin(), protocols.end()},
                         options);
   }
 
@@ -133,8 +128,8 @@
    *                 Sec-WebSocket-Protocol header field).
    */
   static std::shared_ptr<WebSocket> CreateServer(
-      uv::Stream& stream, StringRef key, StringRef version,
-      StringRef protocol = StringRef{});
+      uv::Stream& stream, std::string_view key, std::string_view version,
+      std::string_view protocol = {});
 
   /**
    * Get connection state.
@@ -155,7 +150,7 @@
   /**
    * Get the selected sub-protocol.  Only valid in or after the open() event.
    */
-  StringRef GetProtocol() const { return m_protocol; }
+  std::string_view GetProtocol() const { return m_protocol; }
 
   /**
    * Set the maximum message size.  Default is 128 KB.  If configured to combine
@@ -179,17 +174,26 @@
    * @param reason A human-readable string explaining why the connection is
    *               closing (optional).
    */
-  void Close(uint16_t code = 1005, const Twine& reason = Twine{});
+  void Close(uint16_t code = 1005, std::string_view reason = {});
 
   /**
    * Send a text message.
    * @param data UTF-8 encoded data to send
    * @param callback Callback which is invoked when the write completes.
    */
-  void SendText(
-      ArrayRef<uv::Buffer> data,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    Send(kFlagFin | kOpText, data, callback);
+  void SendText(span<const uv::Buffer> data,
+                std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    Send(kFlagFin | kOpText, data, std::move(callback));
+  }
+
+  /**
+   * Send a text message.
+   * @param data UTF-8 encoded data to send
+   * @param callback Callback which is invoked when the write completes.
+   */
+  void SendText(std::initializer_list<uv::Buffer> data,
+                std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    SendText({data.begin(), data.end()}, std::move(callback));
   }
 
   /**
@@ -197,10 +201,19 @@
    * @param data Data to send
    * @param callback Callback which is invoked when the write completes.
    */
-  void SendBinary(
-      ArrayRef<uv::Buffer> data,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    Send(kFlagFin | kOpBinary, data, callback);
+  void SendBinary(span<const uv::Buffer> data,
+                  std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    Send(kFlagFin | kOpBinary, data, std::move(callback));
+  }
+
+  /**
+   * Send a binary message.
+   * @param data Data to send
+   * @param callback Callback which is invoked when the write completes.
+   */
+  void SendBinary(std::initializer_list<uv::Buffer> data,
+                  std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    SendBinary({data.begin(), data.end()}, std::move(callback));
   }
 
   /**
@@ -211,9 +224,22 @@
    * @param callback Callback which is invoked when the write completes.
    */
   void SendTextFragment(
-      ArrayRef<uv::Buffer> data,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    Send(kOpText, data, callback);
+      span<const uv::Buffer> data,
+      std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    Send(kOpText, data, std::move(callback));
+  }
+
+  /**
+   * Send a text message fragment.  This must be followed by one or more
+   * SendFragment() calls, where the last one has fin=True, to complete the
+   * message.
+   * @param data UTF-8 encoded data to send
+   * @param callback Callback which is invoked when the write completes.
+   */
+  void SendTextFragment(
+      std::initializer_list<uv::Buffer> data,
+      std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    SendTextFragment({data.begin(), data.end()}, std::move(callback));
   }
 
   /**
@@ -224,9 +250,22 @@
    * @param callback Callback which is invoked when the write completes.
    */
   void SendBinaryFragment(
-      ArrayRef<uv::Buffer> data,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    Send(kOpBinary, data, callback);
+      span<const uv::Buffer> data,
+      std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    Send(kOpBinary, data, std::move(callback));
+  }
+
+  /**
+   * Send a text message fragment.  This must be followed by one or more
+   * SendFragment() calls, where the last one has fin=True, to complete the
+   * message.
+   * @param data Data to send
+   * @param callback Callback which is invoked when the write completes.
+   */
+  void SendBinaryFragment(
+      std::initializer_list<uv::Buffer> data,
+      std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    SendBinaryFragment({data.begin(), data.end()}, std::move(callback));
   }
 
   /**
@@ -236,10 +275,21 @@
    * @param fin Set to true if this is the final fragment of the message
    * @param callback Callback which is invoked when the write completes.
    */
-  void SendFragment(
-      ArrayRef<uv::Buffer> data, bool fin,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    Send(kOpCont | (fin ? kFlagFin : 0), data, callback);
+  void SendFragment(span<const uv::Buffer> data, bool fin,
+                    std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    Send(kOpCont | (fin ? kFlagFin : 0), data, std::move(callback));
+  }
+
+  /**
+   * Send a continuation frame.  This is used to send additional parts of a
+   * message started with SendTextFragment() or SendBinaryFragment().
+   * @param data Data to send
+   * @param fin Set to true if this is the final fragment of the message
+   * @param callback Callback which is invoked when the write completes.
+   */
+  void SendFragment(std::initializer_list<uv::Buffer> data, bool fin,
+                    std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    SendFragment({data.begin(), data.end()}, fin, std::move(callback));
   }
 
   /**
@@ -248,8 +298,10 @@
    *                 write completes.
    */
   void SendPing(std::function<void(uv::Error)> callback = nullptr) {
-    SendPing(ArrayRef<uv::Buffer>{}, [callback](auto bufs, uv::Error err) {
-      if (callback) callback(err);
+    SendPing({}, [f = std::move(callback)](auto bufs, uv::Error err) {
+      if (f) {
+        f(err);
+      }
     });
   }
 
@@ -259,10 +311,20 @@
    * @param callback Callback which is invoked when the ping frame
    *                 write completes.
    */
-  void SendPing(
-      ArrayRef<uv::Buffer> data,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    Send(kFlagFin | kOpPing, data, callback);
+  void SendPing(span<const uv::Buffer> data,
+                std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    Send(kFlagFin | kOpPing, data, std::move(callback));
+  }
+
+  /**
+   * Send a ping frame.
+   * @param data Data to send in the ping frame
+   * @param callback Callback which is invoked when the ping frame
+   *                 write completes.
+   */
+  void SendPing(std::initializer_list<uv::Buffer> data,
+                std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    SendPing({data.begin(), data.end()}, std::move(callback));
   }
 
   /**
@@ -271,8 +333,10 @@
    *                 write completes.
    */
   void SendPong(std::function<void(uv::Error)> callback = nullptr) {
-    SendPong(ArrayRef<uv::Buffer>{}, [callback](auto bufs, uv::Error err) {
-      if (callback) callback(err);
+    SendPong({}, [f = std::move(callback)](auto bufs, uv::Error err) {
+      if (f) {
+        f(err);
+      }
     });
   }
 
@@ -282,21 +346,31 @@
    * @param callback Callback which is invoked when the pong frame
    *                 write completes.
    */
-  void SendPong(
-      ArrayRef<uv::Buffer> data,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback) {
-    Send(kFlagFin | kOpPong, data, callback);
+  void SendPong(span<const uv::Buffer> data,
+                std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    Send(kFlagFin | kOpPong, data, std::move(callback));
+  }
+
+  /**
+   * Send a pong frame.
+   * @param data Data to send in the pong frame
+   * @param callback Callback which is invoked when the pong frame
+   *                 write completes.
+   */
+  void SendPong(std::initializer_list<uv::Buffer> data,
+                std::function<void(span<uv::Buffer>, uv::Error)> callback) {
+    SendPong({data.begin(), data.end()}, std::move(callback));
   }
 
   /**
    * Fail the connection.
    */
-  void Fail(uint16_t code = 1002, const Twine& reason = "protocol error");
+  void Fail(uint16_t code = 1002, std::string_view reason = "protocol error");
 
   /**
    * Forcibly close the connection.
    */
-  void Terminate(uint16_t code = 1006, const Twine& reason = "terminated");
+  void Terminate(uint16_t code = 1006, std::string_view reason = "terminated");
 
   /**
    * Gets user-defined data.
@@ -314,10 +388,15 @@
   void SetData(std::shared_ptr<void> data) { m_data = std::move(data); }
 
   /**
+   * Shuts down and closes the underlying stream.
+   */
+  void Shutdown();
+
+  /**
    * Open event.  Emitted when the connection is open and ready to communicate.
    * The parameter is the selected subprotocol.
    */
-  sig::Signal<StringRef> open;
+  sig::Signal<std::string_view> open;
 
   /**
    * Close event.  Emitted when the connection is closed.  The first parameter
@@ -325,31 +404,31 @@
    * has been closed.  The second parameter is a human-readable string
    * explaining the reason why the connection has been closed.
    */
-  sig::Signal<uint16_t, StringRef> closed;
+  sig::Signal<uint16_t, std::string_view> closed;
 
   /**
    * Text message event.  Emitted when a text message is received.
    * The first parameter is the data, the second parameter is true if the
    * data is the last fragment of the message.
    */
-  sig::Signal<StringRef, bool> text;
+  sig::Signal<std::string_view, bool> text;
 
   /**
    * Binary message event.  Emitted when a binary message is received.
    * The first parameter is the data, the second parameter is true if the
    * data is the last fragment of the message.
    */
-  sig::Signal<ArrayRef<uint8_t>, bool> binary;
+  sig::Signal<span<const uint8_t>, bool> binary;
 
   /**
    * Ping event.  Emitted when a ping message is received.
    */
-  sig::Signal<ArrayRef<uint8_t>> ping;
+  sig::Signal<span<const uint8_t>> ping;
 
   /**
    * Pong event.  Emitted when a pong message is received.
    */
-  sig::Signal<ArrayRef<uint8_t>> pong;
+  sig::Signal<span<const uint8_t>> pong;
 
  private:
   // user data
@@ -381,16 +460,16 @@
   class ClientHandshakeData;
   std::unique_ptr<ClientHandshakeData> m_clientHandshake;
 
-  void StartClient(const Twine& uri, const Twine& host,
-                   ArrayRef<StringRef> protocols, const ClientOptions& options);
-  void StartServer(StringRef key, StringRef version, StringRef protocol);
-  void SendClose(uint16_t code, const Twine& reason);
-  void SetClosed(uint16_t code, const Twine& reason, bool failed = false);
-  void Shutdown();
+  void StartClient(std::string_view uri, std::string_view host,
+                   span<const std::string_view> protocols,
+                   const ClientOptions& options);
+  void StartServer(std::string_view key, std::string_view version,
+                   std::string_view protocol);
+  void SendClose(uint16_t code, std::string_view reason);
+  void SetClosed(uint16_t code, std::string_view reason, bool failed = false);
   void HandleIncoming(uv::Buffer& buf, size_t size);
-  void Send(
-      uint8_t opcode, ArrayRef<uv::Buffer> data,
-      std::function<void(MutableArrayRef<uv::Buffer>, uv::Error)> callback);
+  void Send(uint8_t opcode, span<const uv::Buffer> data,
+            std::function<void(span<uv::Buffer>, uv::Error)> callback);
 };
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/WebSocketServer.h b/wpiutil/src/main/native/include/wpi/WebSocketServer.h
index a58a2c9..1f76d16 100644
--- a/wpiutil/src/main/native/include/wpi/WebSocketServer.h
+++ b/wpiutil/src/main/native/include/wpi/WebSocketServer.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_WEBSOCKETSERVER_H_
 #define WPIUTIL_WPI_WEBSOCKETSERVER_H_
@@ -12,15 +9,15 @@
 #include <initializer_list>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <utility>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/HttpParser.h"
 #include "wpi/Signal.h"
 #include "wpi/SmallString.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
 #include "wpi/WebSocket.h"
+#include "wpi/span.h"
 
 namespace wpi {
 
@@ -55,7 +52,8 @@
    *         Second item is the matched protocol if a match was made, otherwise
    *         is empty.
    */
-  std::pair<bool, StringRef> MatchProtocol(ArrayRef<StringRef> protocols);
+  std::pair<bool, std::string_view> MatchProtocol(
+      span<const std::string_view> protocols);
 
   /**
    * Try to find a match to the list of sub-protocols provided by the client.
@@ -66,9 +64,9 @@
    *         Second item is the matched protocol if a match was made, otherwise
    *         is empty.
    */
-  std::pair<bool, StringRef> MatchProtocol(
-      std::initializer_list<StringRef> protocols) {
-    return MatchProtocol(makeArrayRef(protocols.begin(), protocols.end()));
+  std::pair<bool, std::string_view> MatchProtocol(
+      std::initializer_list<std::string_view> protocols) {
+    return MatchProtocol({protocols.begin(), protocols.end()});
   }
 
   /**
@@ -78,7 +76,7 @@
    * @param protocol The subprotocol to send to the client
    */
   std::shared_ptr<WebSocket> Accept(uv::Stream& stream,
-                                    StringRef protocol = StringRef{}) {
+                                    std::string_view protocol = {}) {
     return WebSocket::CreateServer(stream, m_key, m_version, protocol);
   }
 
@@ -112,20 +110,20 @@
      * Checker for URL.  Return true if URL should be accepted.  By default all
      * URLs are accepted.
      */
-    std::function<bool(StringRef)> checkUrl;
+    std::function<bool(std::string_view)> checkUrl;
 
     /**
      * Checker for Host header.  Return true if Host should be accepted.  By
      * default all hosts are accepted.
      */
-    std::function<bool(StringRef)> checkHost;
+    std::function<bool(std::string_view)> checkHost;
   };
 
   /**
    * Private constructor.
    */
-  WebSocketServer(uv::Stream& stream, ArrayRef<StringRef> protocols,
-                  const ServerOptions& options, const private_init&);
+  WebSocketServer(uv::Stream& stream, span<const std::string_view> protocols,
+                  ServerOptions options, const private_init&);
 
   /**
    * Starts a dedicated WebSocket server on the provided connection.  The
@@ -137,8 +135,8 @@
    * @param options Handshake options
    */
   static std::shared_ptr<WebSocketServer> Create(
-      uv::Stream& stream, ArrayRef<StringRef> protocols = ArrayRef<StringRef>{},
-      const ServerOptions& options = ServerOptions{});
+      uv::Stream& stream, span<const std::string_view> protocols = {},
+      const ServerOptions& options = {});
 
   /**
    * Starts a dedicated WebSocket server on the provided connection.  The
@@ -150,16 +148,15 @@
    * @param options Handshake options
    */
   static std::shared_ptr<WebSocketServer> Create(
-      uv::Stream& stream, std::initializer_list<StringRef> protocols,
-      const ServerOptions& options = ServerOptions{}) {
-    return Create(stream, makeArrayRef(protocols.begin(), protocols.end()),
-                  options);
+      uv::Stream& stream, std::initializer_list<std::string_view> protocols,
+      const ServerOptions& options = {}) {
+    return Create(stream, {protocols.begin(), protocols.end()}, options);
   }
 
   /**
    * Connected event.  First parameter is the URL, second is the websocket.
    */
-  sig::Signal<StringRef, WebSocket&> connected;
+  sig::Signal<std::string_view, WebSocket&> connected;
 
  private:
   uv::Stream& m_stream;
@@ -172,7 +169,7 @@
   sig::ScopedConnection m_errorConn;
   sig::ScopedConnection m_endConn;
 
-  void Abort(uint16_t code, StringRef reason);
+  void Abort(uint16_t code, std::string_view reason);
 };
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/WorkerThread.h b/wpiutil/src/main/native/include/wpi/WorkerThread.h
index 4db94c4..6c8852e 100644
--- a/wpiutil/src/main/native/include/wpi/WorkerThread.h
+++ b/wpiutil/src/main/native/include/wpi/WorkerThread.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_WORKERTHREAD_H_
 #define WPIUTIL_WPI_WORKERTHREAD_H_
@@ -108,8 +105,9 @@
                             WorkerThreadRequest<R, T...>& req) {
   R result = std::apply(req.work, std::move(req.params));
   if (req.afterWork) {
-    if (auto async = thr.m_async.m_async.lock())
+    if (auto async = thr.m_async.m_async.lock()) {
       async->Send(std::move(req.afterWork), std::move(result));
+    }
   } else {
     thr.m_promises.SetValue(req.promiseId, std::move(result));
   }
@@ -120,8 +118,9 @@
                             WorkerThreadRequest<void, T...>& req) {
   std::apply(req.work, req.params);
   if (req.afterWork) {
-    if (auto async = thr.m_async.m_async.lock())
+    if (auto async = thr.m_async.m_async.lock()) {
       async->Send(std::move(req.afterWork));
+    }
   } else {
     thr.m_promises.SetValue(req.promiseId);
   }
@@ -133,14 +132,18 @@
   while (m_active) {
     std::unique_lock lock(m_mutex);
     m_cond.wait(lock, [&] { return !m_active || !m_requests.empty(); });
-    if (!m_active) break;
+    if (!m_active) {
+      break;
+    }
 
     // don't want to hold the lock while executing the callbacks
     requests.swap(m_requests);
     lock.unlock();
 
     for (auto&& req : requests) {
-      if (!m_active) break;  // requests may be long-running
+      if (!m_active) {
+        break;  // requests may be long-running
+      }
       RunWorkerThreadRequest(*this, req);
     }
     requests.clear();
@@ -172,7 +175,9 @@
    * @param loop the loop to use for running afterWork routines
    */
   void SetLoop(uv::Loop& loop) {
-    if (auto thr = m_owner.GetThread()) thr->m_async.SetLoop(loop);
+    if (auto thr = m_owner.GetThread()) {
+      thr->m_async.SetLoop(loop);
+    }
   }
 
   /**
@@ -189,7 +194,9 @@
    * Subsequent calls to QueueWorkThen will no longer run afterWork.
    */
   void UnsetLoop() {
-    if (auto thr = m_owner.GetThread()) thr->m_async.UnsetLoop();
+    if (auto thr = m_owner.GetThread()) {
+      thr->m_async.UnsetLoop();
+    }
   }
 
   /**
@@ -200,10 +207,11 @@
    * @return The handle (if nullptr, no handle is set)
    */
   std::shared_ptr<uv::Handle> GetHandle() const {
-    if (auto thr = m_owner.GetThread())
+    if (auto thr = m_owner.GetThread()) {
       return thr->m_async.m_async.lock();
-    else
+    } else {
       return nullptr;
+    }
   }
 
   /**
@@ -217,6 +225,7 @@
    * destroyed while waiting for a result.
    *
    * @param work Work function (called on worker thread)
+   * @param u Arguments to work function
    */
   template <typename... U>
   future<R> QueueWork(WorkFunction work, U&&... u) {
@@ -252,6 +261,7 @@
    *
    * @param work Work function (called on worker thread)
    * @param afterWork After work function (called on loop thread)
+   * @param u Arguments to work function
    */
   template <typename... U>
   void QueueWorkThen(WorkFunction work, AfterWorkFunction afterWork, U&&... u) {
diff --git a/wpiutil/src/main/native/include/wpi/array.h b/wpiutil/src/main/native/include/wpi/array.h
new file mode 100644
index 0000000..5a03371
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/array.h
@@ -0,0 +1,101 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <tuple>
+#include <utility>
+
+namespace wpi {
+
+struct empty_array_t {};
+constexpr empty_array_t empty_array;
+
+/**
+ * This class is a wrapper around std::array that does compile time size
+ * checking.
+ *
+ * std::array's implicit constructor can lead result in uninitialized elements
+ * if the number of arguments doesn't match the std::array size.
+ */
+template <typename T, size_t N>
+class array : public std::array<T, N> {
+ public:
+  explicit array(empty_array_t) {}
+
+  template <typename... Ts>
+  array(T arg, Ts&&... args)  // NOLINT
+      : std::array<T, N>{arg, std::forward<Ts>(args)...} {
+    static_assert(1 + sizeof...(args) == N, "Dimension mismatch");
+  }
+
+  array(const array<T, N>&) = default;
+  array& operator=(const array<T, N>&) = default;
+  array(array<T, N>&&) = default;
+  array& operator=(array<T, N>&&) = default;
+
+  array(const std::array<T, N>& rhs) {  // NOLINT
+    *static_cast<std::array<T, N>*>(this) = rhs;
+  }
+
+  array& operator=(const std::array<T, N>& rhs) {
+    *static_cast<std::array<T, N>*>(this) = rhs;
+    return *this;
+  }
+
+  array(std::array<T, N>&& rhs) {  // NOLINT
+    *static_cast<std::array<T, N>*>(this) = rhs;
+  }
+
+  array& operator=(std::array<T, N>&& rhs) {
+    *static_cast<std::array<T, N>*>(this) = rhs;
+    return *this;
+  }
+};
+
+template <typename T, typename... Ts>
+array(T, Ts...) -> array<std::enable_if_t<(std::is_same_v<T, Ts> && ...), T>,
+                         1 + sizeof...(Ts)>;
+
+}  // namespace wpi
+
+template <size_t I, typename T, size_t N>
+constexpr T& get(wpi::array<T, N>& arr) noexcept {
+  static_assert(I < N, "array index is within bounds");
+  return std::get<I>(static_cast<std::array<T, N>>(arr));
+}
+
+template <size_t I, typename T, size_t N>
+constexpr T&& get(wpi::array<T, N>&& arr) noexcept {
+  static_assert(I < N, "array index is within bounds");
+  return std::move(std::get<I>(arr));
+}
+
+template <size_t I, typename T, size_t N>
+constexpr const T& get(const wpi::array<T, N>& arr) noexcept {
+  static_assert(I < N, "array index is within bounds");
+  return std::get<I>(static_cast<std::array<T, N>>(arr));
+}
+
+template <size_t I, typename T, size_t N>
+constexpr const T&& get(const wpi::array<T, N>&& arr) noexcept {
+  static_assert(I < N, "array index is within bounds");
+  return std::move(std::get<I>(arr));
+}
+
+// Enables structured bindings
+namespace std {  // NOLINT
+// Partial specialization for wpi::array
+template <typename T, size_t N>
+struct tuple_size<wpi::array<T, N>> : public integral_constant<size_t, N> {};
+
+// Partial specialization for wpi::array
+template <size_t I, typename T, size_t N>
+struct tuple_element<I, wpi::array<T, N>> {
+  static_assert(I < N, "index is out of bounds");
+  using type = T;
+};
+}  // namespace std
diff --git a/wpiutil/src/main/native/include/wpi/circular_buffer.h b/wpiutil/src/main/native/include/wpi/circular_buffer.h
index 5dc12f8..40913f9 100644
--- a/wpiutil/src/main/native/include/wpi/circular_buffer.h
+++ b/wpiutil/src/main/native/include/wpi/circular_buffer.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
@@ -19,44 +16,284 @@
 template <class T>
 class circular_buffer {
  public:
-  explicit circular_buffer(size_t size);
+  explicit circular_buffer(size_t size) : m_data(size, T{}) {}
 
-  using value_type = T;
-  using reference = value_type&;
-  using const_reference = const value_type&;
-  using pointer = value_type*;
-  using size_type = size_t;
-  using iterator_category = std::forward_iterator_tag;
-  using difference_type = std::ptrdiff_t;
+  circular_buffer(const circular_buffer&) = default;
+  circular_buffer& operator=(const circular_buffer&) = default;
+  circular_buffer(circular_buffer&&) = default;
+  circular_buffer& operator=(circular_buffer&&) = default;
 
-  size_type size() const;
-  T& front();
-  const T& front() const;
-  T& back();
-  const T& back() const;
-  void push_front(T value);
-  void push_back(T value);
-  T pop_front();
-  T pop_back();
+  class iterator {
+   public:
+    using iterator_category = std::forward_iterator_tag;
+    using value_type = T;
+    using difference_type = std::ptrdiff_t;
+    using pointer = T*;
+    using reference = T&;
+
+    iterator(circular_buffer* buffer, size_t index)
+        : m_buffer(buffer), m_index(index) {}
+
+    iterator& operator++() {
+      ++m_index;
+      return *this;
+    }
+    iterator operator++(int) {
+      iterator retval = *this;
+      ++(*this);
+      return retval;
+    }
+    bool operator==(const iterator& other) const {
+      return m_buffer == other.m_buffer && m_index == other.m_index;
+    }
+    bool operator!=(const iterator& other) const { return !(*this == other); }
+    reference operator*() { return (*m_buffer)[m_index]; }
+
+   private:
+    circular_buffer* m_buffer;
+    size_t m_index;
+  };
+
+  class const_iterator {
+   public:
+    using iterator_category = std::forward_iterator_tag;
+    using value_type = T;
+    using difference_type = std::ptrdiff_t;
+    using pointer = T*;
+    using const_reference = const T&;
+
+    const_iterator(const circular_buffer* buffer, size_t index)
+        : m_buffer(buffer), m_index(index) {}
+
+    const_iterator& operator++() {
+      ++m_index;
+      return *this;
+    }
+    const_iterator operator++(int) {
+      const_iterator retval = *this;
+      ++(*this);
+      return retval;
+    }
+    bool operator==(const const_iterator& other) const {
+      return m_buffer == other.m_buffer && m_index == other.m_index;
+    }
+    bool operator!=(const const_iterator& other) const {
+      return !(*this == other);
+    }
+    const_reference operator*() const { return (*m_buffer)[m_index]; }
+
+   private:
+    const circular_buffer* m_buffer;
+    size_t m_index;
+  };
+
+  iterator begin() { return iterator(this, 0); }
+  iterator end() { return iterator(this, ::wpi::circular_buffer<T>::size()); }
+
+  const_iterator begin() const { return const_iterator(this, 0); }
+  const_iterator end() const {
+    return const_iterator(this, ::wpi::circular_buffer<T>::size());
+  }
+
+  const_iterator cbegin() const { return const_iterator(this, 0); }
+  const_iterator cend() const {
+    return const_iterator(this, ::wpi::circular_buffer<T>::size());
+  }
+
+  /**
+   * Returns number of elements in buffer
+   */
+  size_t size() const { return m_length; }
+
+  /**
+   * Returns value at front of buffer
+   */
+  T& front() { return (*this)[0]; }
+
+  /**
+   * Returns value at front of buffer
+   */
+  const T& front() const { return (*this)[0]; }
+
+  /**
+   * Returns value at back of buffer
+   *
+   * If there are no elements in the buffer, calling this function results in
+   * undefined behavior.
+   */
+  T& back() { return m_data[(m_front + m_length - 1) % m_data.size()]; }
+
+  /**
+   * Returns value at back of buffer
+   *
+   * If there are no elements in the buffer, calling this function results in
+   * undefined behavior.
+   */
+  const T& back() const {
+    return m_data[(m_front + m_length - 1) % m_data.size()];
+  }
+
+  /**
+   * Push a new value onto the front of the buffer.
+   *
+   * The value at the back is overwritten if the buffer is full.
+   */
+  void push_front(T value) {
+    if (m_data.size() == 0) {
+      return;
+    }
+
+    m_front = ModuloDec(m_front);
+
+    m_data[m_front] = value;
+
+    if (m_length < m_data.size()) {
+      m_length++;
+    }
+  }
+
+  /**
+   * Push a new value onto the back of the buffer.
+   *
+   * The value at the front is overwritten if the buffer is full.
+   */
+  void push_back(T value) {
+    if (m_data.size() == 0) {
+      return;
+    }
+
+    m_data[(m_front + m_length) % m_data.size()] = value;
+
+    if (m_length < m_data.size()) {
+      m_length++;
+    } else {
+      // Increment front if buffer is full to maintain size
+      m_front = ModuloInc(m_front);
+    }
+  }
+
+  /**
+   * Push a new value onto the front of the buffer that is constructed with the
+   * provided constructor arguments.
+   *
+   * The value at the back is overwritten if the buffer is full.
+   */
+  template <class... Args>
+  void emplace_front(Args&&... args) {
+    if (m_data.size() == 0) {
+      return;
+    }
+
+    m_front = ModuloDec(m_front);
+
+    m_data[m_front] = T{args...};
+
+    if (m_length < m_data.size()) {
+      m_length++;
+    }
+  }
+
+  /**
+   * Push a new value onto the back of the buffer that is constructed with the
+   * provided constructor arguments.
+   *
+   * The value at the front is overwritten if the buffer is full.
+   */
+  template <class... Args>
+  void emplace_back(Args&&... args) {
+    if (m_data.size() == 0) {
+      return;
+    }
+
+    m_data[(m_front + m_length) % m_data.size()] = T{args...};
+
+    if (m_length < m_data.size()) {
+      m_length++;
+    } else {
+      // Increment front if buffer is full to maintain size
+      m_front = ModuloInc(m_front);
+    }
+  }
+
+  /**
+   * Pop value at front of buffer.
+   *
+   * If there are no elements in the buffer, calling this function results in
+   * undefined behavior.
+   */
+  T pop_front() {
+    T& temp = m_data[m_front];
+    m_front = ModuloInc(m_front);
+    m_length--;
+    return temp;
+  }
+
+  /**
+   * Pop value at back of buffer.
+   *
+   * If there are no elements in the buffer, calling this function results in
+   * undefined behavior.
+   */
+  T pop_back() {
+    m_length--;
+    return m_data[(m_front + m_length) % m_data.size()];
+  }
+
+  /**
+   * Resizes internal buffer to given size.
+   */
   void resize(size_t size);
-  void reset();
 
-  T& operator[](size_t index);
-  const T& operator[](size_t index) const;
+  /**
+   * Empties internal buffer.
+   */
+  void reset() {
+    m_front = 0;
+    m_length = 0;
+  }
+
+  /**
+   * @return Element at index starting from front of buffer.
+   */
+  T& operator[](size_t index) {
+    return m_data[(m_front + index) % m_data.size()];
+  }
+
+  /**
+   * @return Element at index starting from front of buffer.
+   */
+  const T& operator[](size_t index) const {
+    return m_data[(m_front + index) % m_data.size()];
+  }
 
  private:
   std::vector<T> m_data;
 
-  T zero_val{0};
-
   // Index of element at front of buffer
   size_t m_front = 0;
 
   // Number of elements used in buffer
   size_t m_length = 0;
 
-  size_t ModuloInc(size_t index);
-  size_t ModuloDec(size_t index);
+  /**
+   * Increment an index modulo the length of the buffer.
+   *
+   * @return The result of the modulo operation.
+   */
+  size_t ModuloInc(size_t index) { return (index + 1) % m_data.size(); }
+
+  /**
+   * Decrement an index modulo the length of the buffer.
+   *
+   * @return The result of the modulo operation.
+   */
+  size_t ModuloDec(size_t index) {
+    if (index == 0) {
+      return m_data.size() - 1;
+    } else {
+      return index - 1;
+    }
+  }
 };
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/circular_buffer.inc b/wpiutil/src/main/native/include/wpi/circular_buffer.inc
index 20690b7..038af50 100644
--- a/wpiutil/src/main/native/include/wpi/circular_buffer.inc
+++ b/wpiutil/src/main/native/include/wpi/circular_buffer.inc
@@ -1,138 +1,13 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
-#include <algorithm>
+#include "wpi/circular_buffer.h"
 
 namespace wpi {
 
-template <class T>
-circular_buffer<T>::circular_buffer(size_t size) : m_data(size, T{0}) {}
-
-/**
- * Returns number of elements in buffer
- */
-template <class T>
-typename circular_buffer<T>::size_type circular_buffer<T>::size() const {
-  return m_length;
-}
-
-/**
- * Returns value at front of buffer
- */
-template <class T>
-T& circular_buffer<T>::front() {
-  return (*this)[0];
-}
-
-/**
- * Returns value at front of buffer
- */
-template <class T>
-const T& circular_buffer<T>::front() const {
-  return (*this)[0];
-}
-
-/**
- * Returns value at back of buffer
- */
-template <class T>
-T& circular_buffer<T>::back() {
-  // If there are no elements in the buffer, do nothing
-  if (m_length == 0) {
-    return zero_val;
-  }
-
-  return m_data[(m_front + m_length - 1) % m_data.size()];
-}
-
-/**
- * Returns value at back of buffer
- */
-template <class T>
-const T& circular_buffer<T>::back() const {
-  // If there are no elements in the buffer, do nothing
-  if (m_length == 0) {
-    return zero_val;
-  }
-
-  return m_data[(m_front + m_length - 1) % m_data.size()];
-}
-
-/**
- * Push new value onto front of the buffer. The value at the back is overwritten
- * if the buffer is full.
- */
-template <class T>
-void circular_buffer<T>::push_front(T value) {
-  if (m_data.size() == 0) {
-    return;
-  }
-
-  m_front = ModuloDec(m_front);
-
-  m_data[m_front] = value;
-
-  if (m_length < m_data.size()) {
-    m_length++;
-  }
-}
-
-/**
- * Push new value onto back of the buffer. The value at the front is overwritten
- * if the buffer is full.
- */
-template <class T>
-void circular_buffer<T>::push_back(T value) {
-  if (m_data.size() == 0) {
-    return;
-  }
-
-  m_data[(m_front + m_length) % m_data.size()] = value;
-
-  if (m_length < m_data.size()) {
-    m_length++;
-  } else {
-    // Increment front if buffer is full to maintain size
-    m_front = ModuloInc(m_front);
-  }
-}
-
-/**
- * Pop value at front of buffer.
- */
-template <class T>
-T circular_buffer<T>::pop_front() {
-  // If there are no elements in the buffer, do nothing
-  if (m_length == 0) {
-    return T{0};
-  }
-
-  T& temp = m_data[m_front];
-  m_front = ModuloInc(m_front);
-  m_length--;
-  return temp;
-}
-
-/**
- * Pop value at back of buffer.
- */
-template <class T>
-T circular_buffer<T>::pop_back() {
-  // If there are no elements in the buffer, do nothing
-  if (m_length == 0) {
-    return T{0};
-  }
-
-  m_length--;
-  return m_data[(m_front + m_length) % m_data.size()];
-}
-
 /**
  * Resizes internal buffer to given size.
  */
@@ -186,54 +61,4 @@
   }
 }
 
-/**
- * Sets internal buffer contents to zero.
- */
-template <class T>
-void circular_buffer<T>::reset() {
-  std::fill(m_data.begin(), m_data.end(), T{0});
-  m_front = 0;
-  m_length = 0;
-}
-
-/**
- * @return Element at index starting from front of buffer.
- */
-template <class T>
-T& circular_buffer<T>::operator[](size_t index) {
-  return m_data[(m_front + index) % m_data.size()];
-}
-
-/**
- * @return Element at index starting from front of buffer.
- */
-template <class T>
-const T& circular_buffer<T>::operator[](size_t index) const {
-  return m_data[(m_front + index) % m_data.size()];
-}
-
-/**
- * Increment an index modulo the length of the buffer.
- *
- * @return The result of the modulo operation.
- */
-template <class T>
-size_t circular_buffer<T>::ModuloInc(size_t index) {
-  return (index + 1) % m_data.size();
-}
-
-/**
- * Decrement an index modulo the length of the buffer.
- *
- * @return The result of the modulo operation.
- */
-template <class T>
-size_t circular_buffer<T>::ModuloDec(size_t index) {
-  if (index == 0) {
-    return m_data.size() - 1;
-  } else {
-    return index - 1;
-  }
-}
-
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/condition_variable.h b/wpiutil/src/main/native/include/wpi/condition_variable.h
index 1599e19..7c97ec8 100644
--- a/wpiutil/src/main/native/include/wpi/condition_variable.h
+++ b/wpiutil/src/main/native/include/wpi/condition_variable.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
diff --git a/wpiutil/src/main/native/include/wpi/deprecated.h b/wpiutil/src/main/native/include/wpi/deprecated.h
index 6d77dde..6e6a3bf 100644
--- a/wpiutil/src/main/native/include/wpi/deprecated.h
+++ b/wpiutil/src/main/native/include/wpi/deprecated.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_DEPRECATED_H_
 #define WPIUTIL_WPI_DEPRECATED_H_
diff --git a/wpiutil/src/main/native/include/wpi/fmt/raw_ostream.h b/wpiutil/src/main/native/include/wpi/fmt/raw_ostream.h
new file mode 100644
index 0000000..8779bfc
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/fmt/raw_ostream.h
@@ -0,0 +1,30 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#ifndef WPIUTIL_WPI_FMT_RAW_OSTREAM_H_
+#define WPIUTIL_WPI_FMT_RAW_OSTREAM_H_
+
+#include "fmt/format.h"
+#include "wpi/raw_ostream.h"
+
+FMT_BEGIN_NAMESPACE
+
+inline void vprint(wpi::raw_ostream& os, string_view format_str,
+                   fmt::format_args args) {
+  memory_buffer buffer;
+  detail::vformat_to(buffer, format_str, args);
+  os.write(buffer.data(), buffer.size());
+}
+
+/**
+ * Prints formatted data to the stream *os*.
+ */
+template <typename S, typename... Args>
+void print(wpi::raw_ostream& os, const S& format_str, Args&&... args) {
+  vprint(os, format_str, fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+FMT_END_NAMESPACE
+
+#endif  // WPIUTIL_WPI_FMT_RAW_OSTREAM_H_
diff --git a/wpiutil/src/main/native/include/wpi/fs.h b/wpiutil/src/main/native/include/wpi/fs.h
new file mode 100644
index 0000000..fca8069
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/fs.h
@@ -0,0 +1,235 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include <string_view>
+#include <system_error>
+
+#if defined(__APPLE__)
+#include <Availability.h>
+#endif
+#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) \
+     || (defined(__cplusplus) && __cplusplus >= 201703L)) \
+    && defined(__has_include)
+#if __has_include(<filesystem>) \
+    && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \
+        || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) \
+    && (defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 10 \
+        || (__GNUC__ >= 9 && __GNUC_MINOR__ >= 1))
+#define GHC_USE_STD_FS
+#include <filesystem>
+namespace fs {
+using namespace std::filesystem;
+using ifstream = std::ifstream;
+using ofstream = std::ofstream;
+using fstream = std::fstream;
+}  // namespace fs
+#endif
+#endif
+#ifndef GHC_USE_STD_FS
+// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE
+#define GHC_FILESYSTEM_FWD
+#include "ghc/filesystem.hpp"
+namespace fs {
+using namespace ghc::filesystem;
+using ifstream = ghc::filesystem::ifstream;
+using ofstream = ghc::filesystem::ofstream;
+using fstream = ghc::filesystem::fstream;
+}  // namespace fs
+#endif
+
+namespace fs {
+
+#if defined(_WIN32)
+// A Win32 HANDLE is a typedef of void*
+using file_t = void*;
+#else
+using file_t = int;
+#endif
+
+extern const file_t kInvalidFile;
+
+enum CreationDisposition : unsigned {
+  /// CD_CreateAlways - When opening a file:
+  ///   * If it already exists, truncate it.
+  ///   * If it does not already exist, create a new file.
+  CD_CreateAlways = 0,
+
+  /// CD_CreateNew - When opening a file:
+  ///   * If it already exists, fail.
+  ///   * If it does not already exist, create a new file.
+  CD_CreateNew = 1,
+
+  /// CD_OpenExisting - When opening a file:
+  ///   * If it already exists, open the file with the offset set to 0.
+  ///   * If it does not already exist, fail.
+  CD_OpenExisting = 2,
+
+  /// CD_OpenAlways - When opening a file:
+  ///   * If it already exists, open the file with the offset set to 0.
+  ///   * If it does not already exist, create a new file.
+  CD_OpenAlways = 3,
+};
+
+enum FileAccess : unsigned {
+  FA_Read = 1,
+  FA_Write = 2,
+};
+
+enum OpenFlags : unsigned {
+  OF_None = 0,
+  F_None = 0,  // For compatibility
+
+  /// The file should be opened in text mode on platforms that make this
+  /// distinction.
+  OF_Text = 1,
+  F_Text = 1,  // For compatibility
+
+  /// The file should be opened in append mode.
+  OF_Append = 2,
+  F_Append = 2,  // For compatibility
+
+  /// Delete the file on close. Only makes a difference on windows.
+  OF_Delete = 4,
+
+  /// When a child process is launched, this file should remain open in the
+  /// child process.
+  OF_ChildInherit = 8,
+
+  /// Force files Atime to be updated on access. Only makes a difference on
+  /// windows.
+  OF_UpdateAtime = 16,
+};
+
+inline OpenFlags operator|(OpenFlags A, OpenFlags B) {
+  return OpenFlags(unsigned(A) | unsigned(B));
+}
+
+inline OpenFlags& operator|=(OpenFlags& A, OpenFlags B) {
+  A = A | B;
+  return A;
+}
+
+inline FileAccess operator|(FileAccess A, FileAccess B) {
+  return FileAccess(unsigned(A) | unsigned(B));
+}
+
+inline FileAccess& operator|=(FileAccess& A, FileAccess B) {
+  A = A | B;
+  return A;
+}
+
+/**
+ * Opens a file with the specified creation disposition, access mode,
+ * and flags and returns a platform-specific file object.
+ *
+ * The caller is responsible for closing the file object once they are
+ * finished with it.
+ *
+ * @param Path The path of the file to open, relative or absolute.
+ * @param EC Error code output, set to non-zero on error
+ * @param Disp Value specifying the existing-file behavior
+ * @param Access Value specifying whether to open the file in read, write, or
+ *               read-write mode.
+ * @param Flags Additional flags.
+ * @param Mode The access permissions of the file, represented in octal.
+ * @returns errc::success if \a Name has been opened, otherwise a
+ *          platform-specific error_code.
+ */
+file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp,
+                FileAccess Access, OpenFlags Flags, unsigned Mode = 0666);
+
+/**
+ * @brief Opens the file with the given name in a write-only or read-write
+ * mode, returning its open file descriptor. If the file does not exist, it
+ * is created.
+ *
+ * The caller is responsible for closing the freeing the file once they are
+ * finished with it.
+ *
+ * @param Path The path of the file to open, relative or absolute.
+ * @param EC Error code output, set to non-zero on error
+ * @param Disp Value specifying the existing-file behavior
+ * @param Flags Additional flags used to determine whether the file should be
+ *              opened in, for example, read-write or in write-only mode.
+ * @param Mode The access permissions of the file, represented in octal.
+ * @returns a platform-specific file descriptor if \a Name has been opened,
+ *          otherwise kInvalidFile.
+ */
+inline file_t OpenFileForWrite(const path& Path, std::error_code& EC,
+                               CreationDisposition Disp, OpenFlags Flags,
+                               unsigned Mode = 0666) {
+  return OpenFile(Path, EC, Disp, FA_Write, Flags, Mode);
+}
+
+/**
+ * @brief Opens the file with the given name in a write-only or read-write
+ * mode, returning its open file descriptor. If the file does not exist, it
+ * is created.
+ *
+ * The caller is responsible for closing the freeing the file once they are
+ * finished with it.
+ *
+ * @param Path The path of the file to open, relative or absolute.
+ * @param EC Error code output, set to non-zero on error
+ * @param Disp Value specifying the existing-file behavior
+ * @param Flags Additional flags used to determine whether the file should be
+ *              opened in, for example, read-write or in write-only mode.
+ * @param Mode The access permissions of the file, represented in octal.
+ * @return a platform-specific file descriptor if \a Name has been opened,
+ *         otherwise kInvalidFile.
+ */
+inline file_t OpenFileForReadWrite(const path& Path, std::error_code& EC,
+                                   CreationDisposition Disp, OpenFlags Flags,
+                                   unsigned Mode = 0666) {
+  return OpenFile(Path, EC, Disp, FA_Write | FA_Read, Flags, Mode);
+}
+
+/**
+ * Opens the file with the given name in a read-only mode, returning
+ * its open file descriptor.
+ *
+ * The caller is responsible for closing the freeing the file once they are
+ * finished with it.
+ *
+ * @param Path The path of the file to open, relative or absolute.
+ * @param EC Error code output, set to non-zero on error
+ * @param Flags Additional flags
+ * @return a platform-specific file descriptor if \a Name has been opened,
+ *         otherwise kInvalidFile.
+ */
+file_t OpenFileForRead(const path& Path, std::error_code& EC,
+                       OpenFlags Flags = OF_None);
+
+/**
+ * Converts a file object to a file descriptor.  The returned file descriptor
+ * must be closed with ::close() instead of CloseFile().
+ *
+ * @param F On input, this is the file to convert to a file descriptor.
+ *          On output, the file is set to kInvalidFile.
+ * @param EC Error code output, set to non-zero on error
+ * @param Flags Flags passed to the OpenFile function that created file_t
+ * @return file descriptor, or -1 on error
+ */
+int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags);
+
+/**
+ * Closes the file object.
+ *
+ * @param F On input, this is the file to close.  On output, the file is
+ *          set to kInvalidFile.
+ */
+void CloseFile(file_t& F);
+
+}  // namespace fs
diff --git a/wpiutil/src/main/native/include/wpi/function_ref.h b/wpiutil/src/main/native/include/wpi/function_ref.h
new file mode 100644
index 0000000..868c164
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/function_ref.h
@@ -0,0 +1,66 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+//===- llvm/ADT/STLExtras.h - Useful STL related functions ------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef WPIUTIL_WPI_FUNCTION_REF_H_
+#define WPIUTIL_WPI_FUNCTION_REF_H_
+
+#include <stdint.h>
+
+#include <type_traits>
+#include <utility>
+
+namespace wpi {
+
+/// An efficient, type-erasing, non-owning reference to a callable. This is
+/// intended for use as the type of a function parameter that is not used
+/// after the function in question returns.
+///
+/// This class does not own the callable, so it is not in general safe to store
+/// a function_ref.
+template <typename Fn>
+class function_ref;
+
+template <typename Ret, typename... Params>
+class function_ref<Ret(Params...)> {
+  Ret (*callback)(intptr_t callable, Params... params) = nullptr;
+  intptr_t callable;
+
+  template <typename Callable>
+  static Ret callback_fn(intptr_t callable, Params... params) {
+    return (*reinterpret_cast<Callable*>(callable))(std::forward<Params>(
+        params)...);
+  }
+
+ public:
+  function_ref() = default;
+  /*implicit*/ function_ref(std::nullptr_t) {}  // NOLINT
+
+  template <typename Callable>
+  /*implicit*/ function_ref(  // NOLINT
+      Callable&& callable,
+      typename std::enable_if<
+          !std::is_same<typename std::remove_reference<Callable>::type,
+                        function_ref>::value>::type* = nullptr)
+      : callback(callback_fn<typename std::remove_reference<Callable>::type>),
+        callable(reinterpret_cast<intptr_t>(&callable)) {}
+
+  Ret operator()(Params... params) const {
+    return callback(callable, std::forward<Params>(params)...);
+  }
+
+  explicit operator bool() const { return callback; }
+};
+
+}  // namespace wpi
+
+#endif  // WPIUTIL_WPI_FUNCTION_REF_H_
diff --git a/wpiutil/src/main/native/include/wpi/future.h b/wpiutil/src/main/native/include/wpi/future.h
index db46d58..5cb4a8d 100644
--- a/wpiutil/src/main/native/include/wpi/future.h
+++ b/wpiutil/src/main/native/include/wpi/future.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_FUTURE_H_
 #define WPIUTIL_WPI_FUTURE_H_
@@ -290,14 +287,16 @@
   future(const future&) = delete;
 
   template <typename R>
-  future(future<R>&& oth) noexcept
+  future(future<R>&& oth) noexcept  // NOLINT
       : future(oth.then([](R&& val) -> T { return val; })) {}
 
   /**
    * Ignores the result of the future if it has not been retrieved.
    */
   ~future() {
-    if (m_promises) m_promises->IgnoreResult(m_request);
+    if (m_promises) {
+      m_promises->IgnoreResult(m_request);
+    }
   }
 
   future& operator=(future&& oth) noexcept {
@@ -316,10 +315,11 @@
    * @return The value provided by the corresponding promise.set_value().
    */
   T get() {
-    if (m_promises)
+    if (m_promises) {
       return m_promises->GetResult(m_request);
-    else
+    } else {
       return T();
+    }
   }
 
   template <typename R, typename F>
@@ -358,7 +358,9 @@
    * If the value has already been provided, returns immediately.
    */
   void wait() const {
-    if (m_promises) m_promises->WaitResult(m_request);
+    if (m_promises) {
+      m_promises->WaitResult(m_request);
+    }
   }
 
   /**
@@ -419,7 +421,9 @@
    * Ignores the result of the future if it has not been retrieved.
    */
   ~future() {
-    if (m_promises) m_promises->IgnoreResult(m_request);
+    if (m_promises) {
+      m_promises->IgnoreResult(m_request);
+    }
   }
 
   future& operator=(future&& oth) noexcept {
@@ -436,7 +440,9 @@
    * Can only be called once.  The future will be marked invalid after the call.
    */
   void get() {
-    if (m_promises) m_promises->GetResult(m_request);
+    if (m_promises) {
+      m_promises->GetResult(m_request);
+    }
   }
 
   template <typename R, typename F>
@@ -474,7 +480,9 @@
    * If the value has already been provided, returns immediately.
    */
   void wait() const {
-    if (m_promises) m_promises->WaitResult(m_request);
+    if (m_promises) {
+      m_promises->WaitResult(m_request);
+    }
   }
 
   /**
@@ -540,7 +548,9 @@
    * Sets the promised value to a default-constructed T if not already set.
    */
   ~promise() {
-    if (m_promises) m_promises->SetValue(m_request, T());
+    if (m_promises) {
+      m_promises->SetValue(m_request, T());
+    }
   }
 
   promise& operator=(promise&& oth) noexcept {
@@ -575,7 +585,9 @@
    * @param value The value to provide to the waiting future
    */
   void set_value(const T& value) {
-    if (m_promises) m_promises->SetValue(m_request, value);
+    if (m_promises) {
+      m_promises->SetValue(m_request, value);
+    }
     m_promises = nullptr;
   }
 
@@ -586,7 +598,9 @@
    * @param value The value to provide to the waiting future
    */
   void set_value(T&& value) {
-    if (m_promises) m_promises->SetValue(m_request, std::move(value));
+    if (m_promises) {
+      m_promises->SetValue(m_request, std::move(value));
+    }
     m_promises = nullptr;
   }
 
@@ -625,7 +639,9 @@
    * Sets the promised value if not already set.
    */
   ~promise() {
-    if (m_promises) m_promises->SetValue(m_request);
+    if (m_promises) {
+      m_promises->SetValue(m_request);
+    }
   }
 
   promise& operator=(promise&& oth) noexcept {
@@ -660,7 +676,9 @@
    * Only effective once (subsequent calls will be ignored).
    */
   void set_value() {
-    if (m_promises) m_promises->SetValue(m_request);
+    if (m_promises) {
+      m_promises->SetValue(m_request);
+    }
     m_promises = nullptr;
   }
 
@@ -710,7 +728,9 @@
 template <typename T>
 void PromiseFactory<T>::SetValue(uint64_t request, const T& value) {
   std::unique_lock lock(GetResultMutex());
-  if (!EraseRequest(request)) return;
+  if (!EraseRequest(request)) {
+    return;
+  }
   auto it = std::find_if(m_thens.begin(), m_thens.end(),
                          [=](const auto& x) { return x.request == request; });
   if (it != m_thens.end()) {
@@ -729,7 +749,9 @@
 template <typename T>
 void PromiseFactory<T>::SetValue(uint64_t request, T&& value) {
   std::unique_lock lock(GetResultMutex());
-  if (!EraseRequest(request)) return;
+  if (!EraseRequest(request)) {
+    return;
+  }
   auto it = std::find_if(m_thens.begin(), m_thens.end(),
                          [=](const auto& x) { return x.request == request; });
   if (it != m_thens.end()) {
@@ -796,7 +818,9 @@
     // Did we get a response to *our* request?
     auto it = std::find_if(m_results.begin(), m_results.end(),
                            [=](const auto& r) { return r.first == request; });
-    if (it != m_results.end()) return;
+    if (it != m_results.end()) {
+      return;
+    }
     // No, keep waiting for a response
     Wait(lock);
   }
@@ -813,10 +837,16 @@
     // Did we get a response to *our* request?
     auto it = std::find_if(m_results.begin(), m_results.end(),
                            [=](const auto& r) { return r.first == request; });
-    if (it != m_results.end()) return true;
-    if (timeout) break;
+    if (it != m_results.end()) {
+      return true;
+    }
+    if (timeout) {
+      break;
+    }
     // No, keep waiting for a response
-    if (!WaitUntil(lock, timeout_time)) timeout = true;
+    if (!WaitUntil(lock, timeout_time)) {
+      timeout = true;
+    }
   }
   return false;
 }
@@ -845,10 +875,16 @@
     // Did we get a response to *our* request?
     auto it = std::find_if(m_results.begin(), m_results.end(),
                            [=](const auto& r) { return r == request; });
-    if (it != m_results.end()) return true;
-    if (timeout) break;
+    if (it != m_results.end()) {
+      return true;
+    }
+    if (timeout) {
+      break;
+    }
     // No, keep waiting for a response
-    if (!WaitUntil(lock, timeout_time)) timeout = true;
+    if (!WaitUntil(lock, timeout_time)) {
+      timeout = true;
+    }
   }
   return false;
 }
diff --git a/wpiutil/src/main/native/include/wpi/ghc/filesystem.hpp b/wpiutil/src/main/native/include/wpi/ghc/filesystem.hpp
new file mode 100644
index 0000000..6cbc892
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/ghc/filesystem.hpp
@@ -0,0 +1,5942 @@
+//---------------------------------------------------------------------------------------
+//
+// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17/C++20
+//
+//---------------------------------------------------------------------------------------
+//
+// Copyright (c) 2018, Steffen Schümann <s.schuemann@pobox.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+//---------------------------------------------------------------------------------------
+//
+// To dynamically select std::filesystem where available on most platforms,
+// you could use:
+//
+// #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
+// #if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
+// #define GHC_USE_STD_FS
+// #include <filesystem>
+// namespace fs = std::filesystem;
+// #endif
+// #endif
+// #ifndef GHC_USE_STD_FS
+// #include <ghc/filesystem.hpp>
+// namespace fs = ghc::filesystem;
+// #endif
+//
+//---------------------------------------------------------------------------------------
+#ifndef GHC_FILESYSTEM_H
+#define GHC_FILESYSTEM_H
+
+// #define BSD manifest constant only in
+// sys/param.h
+#ifndef _WIN32
+#include <sys/param.h>
+#endif
+
+#ifndef GHC_OS_DETECTED
+#if defined(__APPLE__) && defined(__MACH__)
+#define GHC_OS_MACOS
+#elif defined(__linux__)
+#define GHC_OS_LINUX
+#if defined(__ANDROID__)
+#define GHC_OS_ANDROID
+#endif
+#elif defined(_WIN64)
+#define GHC_OS_WINDOWS
+#define GHC_OS_WIN64
+#elif defined(_WIN32)
+#define GHC_OS_WINDOWS
+#define GHC_OS_WIN32
+#elif defined(__CYGWIN__)
+#define GHC_OS_CYGWIN
+#elif defined(__svr4__)
+#define GHC_OS_SYS5R4
+#elif defined(BSD)
+#define GHC_OS_BSD
+#elif defined(__EMSCRIPTEN__)
+#define GHC_OS_WEB
+#include <wasi/api.h>
+#elif defined(__QNX__)
+#define GHC_OS_QNX
+#define GHC_NO_DIRENT_D_TYPE
+#else
+#error "Operating system currently not supported!"
+#endif
+#define GHC_OS_DETECTED
+#if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#if _MSVC_LANG == 201703L
+#define GHC_FILESYSTEM_RUNNING_CPP17
+#else
+#define GHC_FILESYSTEM_RUNNING_CPP20
+#endif
+#elif (defined(__cplusplus) && __cplusplus >= 201703L)
+#if __cplusplus == 201703L
+#define GHC_FILESYSTEM_RUNNING_CPP17
+#else
+#define GHC_FILESYSTEM_RUNNING_CPP20
+#endif
+#endif
+#endif
+
+#if defined(GHC_FILESYSTEM_IMPLEMENTATION)
+#define GHC_EXPAND_IMPL
+#define GHC_INLINE
+#ifdef GHC_OS_WINDOWS
+#ifndef GHC_FS_API
+#define GHC_FS_API
+#endif
+#ifndef GHC_FS_API_CLASS
+#define GHC_FS_API_CLASS
+#endif
+#else
+#ifndef GHC_FS_API
+#define GHC_FS_API __attribute__((visibility("default")))
+#endif
+#ifndef GHC_FS_API_CLASS
+#define GHC_FS_API_CLASS __attribute__((visibility("default")))
+#endif
+#endif
+#elif defined(GHC_FILESYSTEM_FWD)
+#define GHC_INLINE
+#ifdef GHC_OS_WINDOWS
+#ifndef GHC_FS_API
+#define GHC_FS_API extern
+#endif
+#ifndef GHC_FS_API_CLASS
+#define GHC_FS_API_CLASS
+#endif
+#else
+#ifndef GHC_FS_API
+#define GHC_FS_API extern
+#endif
+#ifndef GHC_FS_API_CLASS
+#define GHC_FS_API_CLASS
+#endif
+#endif
+#else
+#define GHC_EXPAND_IMPL
+#define GHC_INLINE inline
+#ifndef GHC_FS_API
+#define GHC_FS_API
+#endif
+#ifndef GHC_FS_API_CLASS
+#define GHC_FS_API_CLASS
+#endif
+#endif
+
+#ifdef GHC_EXPAND_IMPL
+
+#ifdef GHC_OS_WINDOWS
+#include <windows.h>
+// additional includes
+#include <shellapi.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <wchar.h>
+#include <winioctl.h>
+#else
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef GHC_OS_ANDROID
+#include <android/api-level.h>
+#if __ANDROID_API__ < 12
+#include <sys/syscall.h>
+#endif
+#include <sys/vfs.h>
+#define statvfs statfs
+#else
+#include <sys/statvfs.h>
+#endif
+#ifdef GHC_OS_CYGWIN
+#include <strings.h>
+#endif
+#if !defined(__ANDROID__) || __ANDROID_API__ >= 26
+#include <langinfo.h>
+#endif
+#endif
+#ifdef GHC_OS_MACOS
+#include <Availability.h>
+#endif
+
+#if defined(__cpp_impl_three_way_comparison) && defined(__has_include)
+#if __has_include(<compare>)
+#define GHC_HAS_THREEWAY_COMP
+#include <compare>
+#endif
+#endif
+
+#include <algorithm>
+#include <cctype>
+#include <chrono>
+#include <clocale>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <functional>
+#include <memory>
+#include <stack>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#else  // GHC_EXPAND_IMPL
+
+#if defined(__cpp_impl_three_way_comparison) && defined(__has_include)
+#if __has_include(<compare>)
+#define GHC_HAS_THREEWAY_COMP
+#include <compare>
+#endif
+#endif
+#include <chrono>
+#include <fstream>
+#include <memory>
+#include <stack>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+#ifdef GHC_OS_WINDOWS
+#include <vector>
+#endif
+#endif  // GHC_EXPAND_IMPL
+
+// After standard library includes.
+// Standard library support for std::string_view.
+#if defined(__cpp_lib_string_view)
+#define GHC_HAS_STD_STRING_VIEW
+#elif defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 4000) && (__cplusplus >= 201402)
+#define GHC_HAS_STD_STRING_VIEW
+#elif defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 7) && (__cplusplus >= 201703)
+#define GHC_HAS_STD_STRING_VIEW
+#elif defined(_MSC_VER) && (_MSC_VER >= 1910 && _MSVC_LANG >= 201703)
+#define GHC_HAS_STD_STRING_VIEW
+#endif
+
+// Standard library support for std::experimental::string_view.
+#if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 3700 && _LIBCPP_VERSION < 7000) && (__cplusplus >= 201402)
+#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW
+#elif defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)) || (__GNUC__ > 4)) && (__cplusplus >= 201402)
+#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW
+#elif defined(__GLIBCXX__) && defined(_GLIBCXX_USE_DUAL_ABI) && (__cplusplus >= 201402)
+// macro _GLIBCXX_USE_DUAL_ABI is always defined in libstdc++ from gcc-5 and newer
+#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW
+#endif
+
+#if defined(GHC_HAS_STD_STRING_VIEW)
+#include <string_view>
+#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW)
+#include <experimental/string_view>
+#endif
+
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp):
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Enforce C++17 API where possible when compiling for C++20, handles the following cases:
+// * fs::path::u8string() returns std::string instead of std::u8string
+// #define GHC_FILESYSTEM_ENFORCE_CPP17_API
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories
+// configure LWG conformance ()
+#define LWG_2682_BEHAVIOUR
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular
+// file with that name, it is superseded by P1164R1, so only activate if really needed
+// #define LWG_2935_BEHAVIOUR
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// LWG #2936 enables new element wise (more expensive) path comparison
+// * if this->root_name().native().compare(p.root_name().native()) != 0 return result
+// * if this->has_root_directory() and !p.has_root_directory() return -1
+// * if !this->has_root_directory() and p.has_root_directory() return -1
+// * else result of element wise comparison of path iteration where first comparison is != 0 or 0
+//   if all comparisons are 0 (on Windows this implementation does case insensitive root_name()
+//   comparison)
+#define LWG_2936_BEHAVIOUR
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2)
+#define LWG_2937_BEHAVIOUR
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the windows
+// version defaults to std::wstring storage backend. Still all std::string will be interpreted
+// as UTF-8 encoded. With this define you can enfoce the old behavior on Windows, using
+// std::string as backend and for fs::path::native() and char for fs::path::c_str(). This
+// needs more conversions so it is (an was before v1.5) slower, bot might help keeping source
+// homogeneous in a multi platform project.
+// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found,
+// instead of replacing them with the unicode replacement character (U+FFFD).
+// #define GHC_RAISE_UNICODE_ERRORS
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length.
+// instead of replacing them with the unicode replacement character (U+FFFD).
+#ifndef GHC_WIN_DISABLE_AUTO_PREFIXES
+#define GHC_WIN_AUTO_PREFIX_LONG_PATH
+#endif  // GHC_WIN_DISABLE_AUTO_PREFIXES
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch)
+#define GHC_FILESYSTEM_VERSION 10506L
+
+#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND))
+#define GHC_WITH_EXCEPTIONS
+#endif
+#if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS)
+#error "Can't raise unicode errors with exception support disabled"
+#endif
+
+namespace ghc {
+namespace filesystem {
+
+#if defined(GHC_HAS_CUSTOM_STRING_VIEW)
+#define GHC_WITH_STRING_VIEW
+#elif defined(GHC_HAS_STD_STRING_VIEW)
+#define GHC_WITH_STRING_VIEW
+using std::basic_string_view;
+#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW)
+#define GHC_WITH_STRING_VIEW
+using std::experimental::basic_string_view;
+#endif
+
+// temporary existing exception type for yet unimplemented parts
+class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error
+{
+public:
+    not_implemented_exception()
+        : std::logic_error("function not implemented yet.")
+    {
+    }
+};
+
+template <typename char_type>
+class path_helper_base
+{
+public:
+    using value_type = char_type;
+#ifdef GHC_OS_WINDOWS
+    static constexpr value_type preferred_separator = '\\';
+#else
+    static constexpr value_type preferred_separator = '/';
+#endif
+};
+
+#if __cplusplus < 201703L
+template <typename char_type>
+constexpr char_type path_helper_base<char_type>::preferred_separator;
+#endif
+
+#ifdef GHC_OS_WINDOWS
+class path;
+namespace detail {
+bool has_executable_extension(const path& p);
+}
+#endif
+
+// [fs.class.path] class path
+class GHC_FS_API_CLASS path
+#if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE)
+#define GHC_USE_WCHAR_T
+#define GHC_NATIVEWP(p) p.c_str()
+#define GHC_PLATFORM_LITERAL(str) L##str
+    : private path_helper_base<std::wstring::value_type>
+{
+public:
+    using path_helper_base<std::wstring::value_type>::value_type;
+#else
+#define GHC_NATIVEWP(p) p.wstring().c_str()
+#define GHC_PLATFORM_LITERAL(str) str
+    : private path_helper_base<std::string::value_type>
+{
+public:
+    using path_helper_base<std::string::value_type>::value_type;
+#endif
+    using string_type = std::basic_string<value_type>;
+    using path_helper_base<value_type>::preferred_separator;
+
+    // [fs.enum.path.format] enumeration format
+    /// The path format in which the constructor argument is given.
+    enum format {
+        generic_format,  ///< The generic format, internally used by
+                         ///< ghc::filesystem::path with slashes
+        native_format,   ///< The format native to the current platform this code
+                         ///< is build for
+        auto_format,     ///< Try to auto-detect the format, fallback to native
+    };
+
+    template <class T>
+    struct _is_basic_string : std::false_type
+    {
+    };
+    template <class CharT, class Traits, class Alloc>
+    struct _is_basic_string<std::basic_string<CharT, Traits, Alloc>> : std::true_type
+    {
+    };
+    template <class CharT>
+    struct _is_basic_string<std::basic_string<CharT, std::char_traits<CharT>, std::allocator<CharT>>> : std::true_type
+    {
+    };
+#ifdef GHC_WITH_STRING_VIEW
+    template <class CharT, class Traits>
+    struct _is_basic_string<basic_string_view<CharT, Traits>> : std::true_type
+    {
+    };
+    template <class CharT>
+    struct _is_basic_string<basic_string_view<CharT, std::char_traits<CharT>>> : std::true_type
+    {
+    };
+#endif
+
+    template <typename T1, typename T2 = void>
+    using path_type = typename std::enable_if<!std::is_same<path, T1>::value, path>::type;
+    template <typename T>
+#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API)
+    using path_from_string =
+        typename std::enable_if<_is_basic_string<T>::value || std::is_same<char const*, typename std::decay<T>::type>::value || std::is_same<char*, typename std::decay<T>::type>::value || std::is_same<char8_t const*, typename std::decay<T>::type>::value ||
+                                    std::is_same<char8_t*, typename std::decay<T>::type>::value || std::is_same<char16_t const*, typename std::decay<T>::type>::value || std::is_same<char16_t*, typename std::decay<T>::type>::value ||
+                                    std::is_same<char32_t const*, typename std::decay<T>::type>::value || std::is_same<char32_t*, typename std::decay<T>::type>::value || std::is_same<wchar_t const*, typename std::decay<T>::type>::value ||
+                                    std::is_same<wchar_t*, typename std::decay<T>::type>::value,
+                                path>::type;
+    template <typename T>
+    using path_type_EcharT = typename std::enable_if<std::is_same<T, char>::value || std::is_same<T, char8_t>::value || std::is_same<T, char16_t>::value || std::is_same<T, char32_t>::value || std::is_same<T, wchar_t>::value, path>::type;
+#else
+    using path_from_string = typename std::enable_if<_is_basic_string<T>::value || std::is_same<char const*, typename std::decay<T>::type>::value || std::is_same<char*, typename std::decay<T>::type>::value ||
+                                    std::is_same<char16_t const*, typename std::decay<T>::type>::value || std::is_same<char16_t*, typename std::decay<T>::type>::value || std::is_same<char32_t const*, typename std::decay<T>::type>::value ||
+                                    std::is_same<char32_t*, typename std::decay<T>::type>::value || std::is_same<wchar_t const*, typename std::decay<T>::type>::value || std::is_same<wchar_t*, typename std::decay<T>::type>::value,
+        path>::type;
+    template <typename T>
+    using path_type_EcharT = typename std::enable_if<std::is_same<T, char>::value || std::is_same<T, char16_t>::value || std::is_same<T, char32_t>::value || std::is_same<T, wchar_t>::value, path>::type;
+#endif
+    // [fs.path.construct] constructors and destructor
+    path() noexcept;
+    path(const path& p);
+    path(path&& p) noexcept;
+    path(string_type&& source, format fmt = auto_format);
+    template <class Source, typename = path_from_string<Source>>
+    path(const Source& source, format fmt = auto_format);
+    template <class InputIterator>
+    path(InputIterator first, InputIterator last, format fmt = auto_format);
+#ifdef GHC_WITH_EXCEPTIONS
+    template <class Source, typename = path_from_string<Source>>
+    path(const Source& source, const std::locale& loc, format fmt = auto_format);
+    template <class InputIterator>
+    path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format);
+#endif
+    ~path();
+
+    // [fs.path.assign] assignments
+    path& operator=(const path& p);
+    path& operator=(path&& p) noexcept;
+    path& operator=(string_type&& source);
+    path& assign(string_type&& source);
+    template <class Source>
+    path& operator=(const Source& source);
+    template <class Source>
+    path& assign(const Source& source);
+    template <class InputIterator>
+    path& assign(InputIterator first, InputIterator last);
+
+    // [fs.path.append] appends
+    path& operator/=(const path& p);
+    template <class Source>
+    path& operator/=(const Source& source);
+    template <class Source>
+    path& append(const Source& source);
+    template <class InputIterator>
+    path& append(InputIterator first, InputIterator last);
+
+    // [fs.path.concat] concatenation
+    path& operator+=(const path& x);
+    path& operator+=(const string_type& x);
+#ifdef GHC_WITH_STRING_VIEW
+    path& operator+=(basic_string_view<value_type> x);
+#endif
+    path& operator+=(const value_type* x);
+    path& operator+=(value_type x);
+    template <class Source>
+    path_from_string<Source>& operator+=(const Source& x);
+    template <class EcharT>
+    path_type_EcharT<EcharT>& operator+=(EcharT x);
+    template <class Source>
+    path& concat(const Source& x);
+    template <class InputIterator>
+    path& concat(InputIterator first, InputIterator last);
+
+    // [fs.path.modifiers] modifiers
+    void clear() noexcept;
+    path& make_preferred();
+    path& remove_filename();
+    path& replace_filename(const path& replacement);
+    path& replace_extension(const path& replacement = path());
+    void swap(path& rhs) noexcept;
+
+    // [fs.path.native.obs] native format observers
+    const string_type& native() const noexcept;
+    const value_type* c_str() const noexcept;
+    operator string_type() const;
+    template <class EcharT, class traits = std::char_traits<EcharT>, class Allocator = std::allocator<EcharT>>
+    std::basic_string<EcharT, traits, Allocator> string(const Allocator& a = Allocator()) const;
+    std::string string() const;
+    std::wstring wstring() const;
+#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API)
+    std::u8string u8string() const;
+#else
+    std::string u8string() const;
+#endif
+    std::u16string u16string() const;
+    std::u32string u32string() const;
+
+    // [fs.path.generic.obs] generic format observers
+    template <class EcharT, class traits = std::char_traits<EcharT>, class Allocator = std::allocator<EcharT>>
+    std::basic_string<EcharT, traits, Allocator> generic_string(const Allocator& a = Allocator()) const;
+    std::string generic_string() const;
+    std::wstring generic_wstring() const;
+#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API)
+    std::u8string generic_u8string() const;
+#else
+    std::string generic_u8string() const;
+#endif
+    std::u16string generic_u16string() const;
+    std::u32string generic_u32string() const;
+
+    // [fs.path.compare] compare
+    int compare(const path& p) const noexcept;
+    int compare(const string_type& s) const;
+#ifdef GHC_WITH_STRING_VIEW
+    int compare(basic_string_view<value_type> s) const;
+#endif
+    int compare(const value_type* s) const;
+
+    // [fs.path.decompose] decomposition
+    path root_name() const;
+    path root_directory() const;
+    path root_path() const;
+    path relative_path() const;
+    path parent_path() const;
+    path filename() const;
+    path stem() const;
+    path extension() const;
+
+    // [fs.path.query] query
+    bool empty() const noexcept;
+    bool has_root_name() const;
+    bool has_root_directory() const;
+    bool has_root_path() const;
+    bool has_relative_path() const;
+    bool has_parent_path() const;
+    bool has_filename() const;
+    bool has_stem() const;
+    bool has_extension() const;
+    bool is_absolute() const;
+    bool is_relative() const;
+
+    // [fs.path.gen] generation
+    path lexically_normal() const;
+    path lexically_relative(const path& base) const;
+    path lexically_proximate(const path& base) const;
+
+    // [fs.path.itr] iterators
+    class iterator;
+    using const_iterator = iterator;
+    iterator begin() const;
+    iterator end() const;
+
+private:
+    using impl_value_type = value_type;
+    using impl_string_type = std::basic_string<impl_value_type>;
+    friend class directory_iterator;
+    void append_name(const value_type* name);
+    static constexpr impl_value_type generic_separator = '/';
+    template <typename InputIterator>
+    class input_iterator_range
+    {
+    public:
+        typedef InputIterator iterator;
+        typedef InputIterator const_iterator;
+        typedef typename InputIterator::difference_type difference_type;
+
+        input_iterator_range(const InputIterator& first, const InputIterator& last)
+            : _first(first)
+            , _last(last)
+        {
+        }
+
+        InputIterator begin() const { return _first; }
+        InputIterator end() const { return _last; }
+
+    private:
+        InputIterator _first;
+        InputIterator _last;
+    };
+    friend void swap(path& lhs, path& rhs) noexcept;
+    friend size_t hash_value(const path& p) noexcept;
+    friend path canonical(const path& p, std::error_code& ec);
+    string_type::size_type root_name_length() const noexcept;
+    void postprocess_path_with_format(format fmt);
+    void check_long_path();
+    impl_string_type _path;
+#ifdef GHC_OS_WINDOWS
+    void handle_prefixes();
+    friend bool detail::has_executable_extension(const path& p);
+#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH
+    string_type::size_type _prefixLength{0};
+#else  // GHC_WIN_AUTO_PREFIX_LONG_PATH
+    static const string_type::size_type _prefixLength{0};
+#endif  // GHC_WIN_AUTO_PREFIX_LONG_PATH
+#else
+    static const string_type::size_type _prefixLength{0};
+#endif
+};
+
+// [fs.path.nonmember] path non-member functions
+GHC_FS_API void swap(path& lhs, path& rhs) noexcept;
+GHC_FS_API size_t hash_value(const path& p) noexcept;
+#ifdef GHC_HAS_THREEWAY_COMP
+GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept;
+#endif
+GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept;
+GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept;
+GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept;
+GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept;
+GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept;
+GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept;
+GHC_FS_API path operator/(const path& lhs, const path& rhs);
+
+// [fs.path.io] path inserter and extractor
+template <class charT, class traits>
+std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& os, const path& p);
+template <class charT, class traits>
+std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& is, path& p);
+
+// [pfs.path.factory] path factory functions
+template <class Source, typename = path::path_from_string<Source>>
+#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API)
+[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]]
+#endif
+path u8path(const Source& source);
+template <class InputIterator>
+#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API)
+[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]]
+#endif
+path u8path(InputIterator first, InputIterator last);
+
+// [fs.class.filesystem_error] class filesystem_error
+class GHC_FS_API_CLASS filesystem_error : public std::system_error
+{
+public:
+    filesystem_error(const std::string& what_arg, std::error_code ec);
+    filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec);
+    filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec);
+    const path& path1() const noexcept;
+    const path& path2() const noexcept;
+    const char* what() const noexcept override;
+
+private:
+    std::string _what_arg;
+    std::error_code _ec;
+    path _p1, _p2;
+};
+
+class GHC_FS_API_CLASS path::iterator
+{
+public:
+    using value_type = const path;
+    using difference_type = std::ptrdiff_t;
+    using pointer = const path*;
+    using reference = const path&;
+    using iterator_category = std::bidirectional_iterator_tag;
+
+    iterator();
+    iterator(const path& p, const impl_string_type::const_iterator& pos);
+    iterator& operator++();
+    iterator operator++(int);
+    iterator& operator--();
+    iterator operator--(int);
+    bool operator==(const iterator& other) const;
+    bool operator!=(const iterator& other) const;
+    reference operator*() const;
+    pointer operator->() const;
+
+private:
+    friend class path;
+    impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const;
+    impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const;
+    void updateCurrent();
+    impl_string_type::const_iterator _first;
+    impl_string_type::const_iterator _last;
+    impl_string_type::const_iterator _prefix;
+    impl_string_type::const_iterator _root;
+    impl_string_type::const_iterator _iter;
+    path _current;
+};
+
+struct space_info
+{
+    uintmax_t capacity;
+    uintmax_t free;
+    uintmax_t available;
+};
+
+// [fs.enum] enumerations
+// [fs.enum.file_type]
+enum class file_type {
+    none,
+    not_found,
+    regular,
+    directory,
+    symlink,
+    block,
+    character,
+    fifo,
+    socket,
+    unknown,
+};
+
+// [fs.enum.perms]
+enum class perms : uint16_t {
+    none = 0,
+
+    owner_read = 0400,
+    owner_write = 0200,
+    owner_exec = 0100,
+    owner_all = 0700,
+
+    group_read = 040,
+    group_write = 020,
+    group_exec = 010,
+    group_all = 070,
+
+    others_read = 04,
+    others_write = 02,
+    others_exec = 01,
+    others_all = 07,
+
+    all = 0777,
+    set_uid = 04000,
+    set_gid = 02000,
+    sticky_bit = 01000,
+
+    mask = 07777,
+    unknown = 0xffff
+};
+
+// [fs.enum.perm.opts]
+enum class perm_options : uint16_t {
+    replace = 3,
+    add = 1,
+    remove = 2,
+    nofollow = 4,
+};
+
+// [fs.enum.copy.opts]
+enum class copy_options : uint16_t {
+    none = 0,
+
+    skip_existing = 1,
+    overwrite_existing = 2,
+    update_existing = 4,
+
+    recursive = 8,
+
+    copy_symlinks = 0x10,
+    skip_symlinks = 0x20,
+
+    directories_only = 0x40,
+    create_symlinks = 0x80,
+#ifndef GHC_OS_WEB
+    create_hard_links = 0x100
+#endif
+};
+
+// [fs.enum.dir.opts]
+enum class directory_options : uint16_t {
+    none = 0,
+    follow_directory_symlink = 1,
+    skip_permission_denied = 2,
+};
+
+// [fs.class.file_status] class file_status
+class GHC_FS_API_CLASS file_status
+{
+public:
+    // [fs.file_status.cons] constructors and destructor
+    file_status() noexcept;
+    explicit file_status(file_type ft, perms prms = perms::unknown) noexcept;
+    file_status(const file_status&) noexcept;
+    file_status(file_status&&) noexcept;
+    ~file_status();
+    // assignments:
+    file_status& operator=(const file_status&) noexcept;
+    file_status& operator=(file_status&&) noexcept;
+    // [fs.file_status.mods] modifiers
+    void type(file_type ft) noexcept;
+    void permissions(perms prms) noexcept;
+    // [fs.file_status.obs] observers
+    file_type type() const noexcept;
+    perms permissions() const noexcept;
+    friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); }
+private:
+    file_type _type;
+    perms _perms;
+};
+
+using file_time_type = std::chrono::time_point<std::chrono::system_clock>;
+
+// [fs.class.directory_entry] Class directory_entry
+class GHC_FS_API_CLASS directory_entry
+{
+public:
+    // [fs.dir.entry.cons] constructors and destructor
+    directory_entry() noexcept = default;
+    directory_entry(const directory_entry&) = default;
+    directory_entry(directory_entry&&) noexcept = default;
+#ifdef GHC_WITH_EXCEPTIONS
+    explicit directory_entry(const path& p);
+#endif
+    directory_entry(const path& p, std::error_code& ec);
+    ~directory_entry();
+
+    // assignments:
+    directory_entry& operator=(const directory_entry&) = default;
+    directory_entry& operator=(directory_entry&&) noexcept = default;
+
+    // [fs.dir.entry.mods] modifiers
+#ifdef GHC_WITH_EXCEPTIONS
+    void assign(const path& p);
+    void replace_filename(const path& p);
+    void refresh();
+#endif
+    void assign(const path& p, std::error_code& ec);
+    void replace_filename(const path& p, std::error_code& ec);
+    void refresh(std::error_code& ec) noexcept;
+
+    // [fs.dir.entry.obs] observers
+    const filesystem::path& path() const noexcept;
+    operator const filesystem::path&() const noexcept;
+#ifdef GHC_WITH_EXCEPTIONS
+    bool exists() const;
+    bool is_block_file() const;
+    bool is_character_file() const;
+    bool is_directory() const;
+    bool is_fifo() const;
+    bool is_other() const;
+    bool is_regular_file() const;
+    bool is_socket() const;
+    bool is_symlink() const;
+    uintmax_t file_size() const;
+    file_time_type last_write_time() const;
+    file_status status() const;
+    file_status symlink_status() const;
+#endif
+    bool exists(std::error_code& ec) const noexcept;
+    bool is_block_file(std::error_code& ec) const noexcept;
+    bool is_character_file(std::error_code& ec) const noexcept;
+    bool is_directory(std::error_code& ec) const noexcept;
+    bool is_fifo(std::error_code& ec) const noexcept;
+    bool is_other(std::error_code& ec) const noexcept;
+    bool is_regular_file(std::error_code& ec) const noexcept;
+    bool is_socket(std::error_code& ec) const noexcept;
+    bool is_symlink(std::error_code& ec) const noexcept;
+    uintmax_t file_size(std::error_code& ec) const noexcept;
+    file_time_type last_write_time(std::error_code& ec) const noexcept;
+    file_status status(std::error_code& ec) const noexcept;
+    file_status symlink_status(std::error_code& ec) const noexcept;
+
+#ifndef GHC_OS_WEB
+#ifdef GHC_WITH_EXCEPTIONS
+    uintmax_t hard_link_count() const;
+#endif
+    uintmax_t hard_link_count(std::error_code& ec) const noexcept;
+#endif
+
+#ifdef GHC_HAS_THREEWAY_COMP
+    std::strong_ordering operator<=>(const directory_entry& rhs) const noexcept;
+#endif
+    bool operator<(const directory_entry& rhs) const noexcept;
+    bool operator==(const directory_entry& rhs) const noexcept;
+    bool operator!=(const directory_entry& rhs) const noexcept;
+    bool operator<=(const directory_entry& rhs) const noexcept;
+    bool operator>(const directory_entry& rhs) const noexcept;
+    bool operator>=(const directory_entry& rhs) const noexcept;
+
+private:
+    friend class directory_iterator;
+#ifdef GHC_WITH_EXCEPTIONS
+    file_type status_file_type() const;
+#endif
+    file_type status_file_type(std::error_code& ec) const noexcept;
+    filesystem::path _path;
+    file_status _status;
+    file_status _symlink_status;
+    uintmax_t _file_size = static_cast<uintmax_t>(-1);
+#ifndef GHC_OS_WINDOWS
+    uintmax_t _hard_link_count = static_cast<uintmax_t>(-1);
+#endif
+    time_t _last_write_time = 0;
+};
+
+// [fs.class.directory.iterator] Class directory_iterator
+class GHC_FS_API_CLASS directory_iterator
+{
+public:
+    class GHC_FS_API_CLASS proxy
+    {
+    public:
+        const directory_entry& operator*() const& noexcept { return _dir_entry; }
+        directory_entry operator*() && noexcept { return std::move(_dir_entry); }
+
+    private:
+        explicit proxy(const directory_entry& dir_entry)
+            : _dir_entry(dir_entry)
+        {
+        }
+        friend class directory_iterator;
+        friend class recursive_directory_iterator;
+        directory_entry _dir_entry;
+    };
+    using iterator_category = std::input_iterator_tag;
+    using value_type = directory_entry;
+    using difference_type = std::ptrdiff_t;
+    using pointer = const directory_entry*;
+    using reference = const directory_entry&;
+
+    // [fs.dir.itr.members] member functions
+    directory_iterator() noexcept;
+#ifdef GHC_WITH_EXCEPTIONS
+    explicit directory_iterator(const path& p);
+    directory_iterator(const path& p, directory_options options);
+#endif
+    directory_iterator(const path& p, std::error_code& ec) noexcept;
+    directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept;
+    directory_iterator(const directory_iterator& rhs);
+    directory_iterator(directory_iterator&& rhs) noexcept;
+    ~directory_iterator();
+    directory_iterator& operator=(const directory_iterator& rhs);
+    directory_iterator& operator=(directory_iterator&& rhs) noexcept;
+    const directory_entry& operator*() const;
+    const directory_entry* operator->() const;
+#ifdef GHC_WITH_EXCEPTIONS
+    directory_iterator& operator++();
+#endif
+    directory_iterator& increment(std::error_code& ec) noexcept;
+
+    // other members as required by [input.iterators]
+#ifdef GHC_WITH_EXCEPTIONS
+    proxy operator++(int)
+    {
+        proxy p{**this};
+        ++*this;
+        return p;
+    }
+#endif
+    bool operator==(const directory_iterator& rhs) const;
+    bool operator!=(const directory_iterator& rhs) const;
+
+private:
+    friend class recursive_directory_iterator;
+    class impl;
+    std::shared_ptr<impl> _impl;
+};
+
+// [fs.dir.itr.nonmembers] directory_iterator non-member functions
+GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept;
+GHC_FS_API directory_iterator end(const directory_iterator&) noexcept;
+
+// [fs.class.re.dir.itr] class recursive_directory_iterator
+class GHC_FS_API_CLASS recursive_directory_iterator
+{
+public:
+    using iterator_category = std::input_iterator_tag;
+    using value_type = directory_entry;
+    using difference_type = std::ptrdiff_t;
+    using pointer = const directory_entry*;
+    using reference = const directory_entry&;
+
+    // [fs.rec.dir.itr.members] constructors and destructor
+    recursive_directory_iterator() noexcept;
+#ifdef GHC_WITH_EXCEPTIONS
+    explicit recursive_directory_iterator(const path& p);
+    recursive_directory_iterator(const path& p, directory_options options);
+#endif
+    recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept;
+    recursive_directory_iterator(const path& p, std::error_code& ec) noexcept;
+    recursive_directory_iterator(const recursive_directory_iterator& rhs);
+    recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept;
+    ~recursive_directory_iterator();
+
+    // [fs.rec.dir.itr.members] observers
+    directory_options options() const;
+    int depth() const;
+    bool recursion_pending() const;
+
+    const directory_entry& operator*() const;
+    const directory_entry* operator->() const;
+
+    // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator&
+    recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs);
+    recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept;
+#ifdef GHC_WITH_EXCEPTIONS
+    recursive_directory_iterator& operator++();
+#endif
+    recursive_directory_iterator& increment(std::error_code& ec) noexcept;
+
+#ifdef GHC_WITH_EXCEPTIONS
+    void pop();
+#endif
+    void pop(std::error_code& ec);
+    void disable_recursion_pending();
+
+    // other members as required by [input.iterators]
+#ifdef GHC_WITH_EXCEPTIONS
+    directory_iterator::proxy operator++(int)
+    {
+        directory_iterator::proxy proxy{**this};
+        ++*this;
+        return proxy;
+    }
+#endif
+    bool operator==(const recursive_directory_iterator& rhs) const;
+    bool operator!=(const recursive_directory_iterator& rhs) const;
+
+private:
+    struct recursive_directory_iterator_impl
+    {
+        directory_options _options;
+        bool _recursion_pending;
+        std::stack<directory_iterator> _dir_iter_stack;
+        recursive_directory_iterator_impl(directory_options options, bool recursion_pending)
+            : _options(options)
+            , _recursion_pending(recursion_pending)
+        {
+        }
+    };
+    std::shared_ptr<recursive_directory_iterator_impl> _impl;
+};
+
+// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions
+GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept;
+GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept;
+
+// [fs.op.funcs] filesystem operations
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_FS_API path absolute(const path& p);
+GHC_FS_API path canonical(const path& p);
+GHC_FS_API void copy(const path& from, const path& to);
+GHC_FS_API void copy(const path& from, const path& to, copy_options options);
+GHC_FS_API bool copy_file(const path& from, const path& to);
+GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option);
+GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink);
+GHC_FS_API bool create_directories(const path& p);
+GHC_FS_API bool create_directory(const path& p);
+GHC_FS_API bool create_directory(const path& p, const path& attributes);
+GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink);
+GHC_FS_API void create_symlink(const path& to, const path& new_symlink);
+GHC_FS_API path current_path();
+GHC_FS_API void current_path(const path& p);
+GHC_FS_API bool exists(const path& p);
+GHC_FS_API bool equivalent(const path& p1, const path& p2);
+GHC_FS_API uintmax_t file_size(const path& p);
+GHC_FS_API bool is_block_file(const path& p);
+GHC_FS_API bool is_character_file(const path& p);
+GHC_FS_API bool is_directory(const path& p);
+GHC_FS_API bool is_empty(const path& p);
+GHC_FS_API bool is_fifo(const path& p);
+GHC_FS_API bool is_other(const path& p);
+GHC_FS_API bool is_regular_file(const path& p);
+GHC_FS_API bool is_socket(const path& p);
+GHC_FS_API bool is_symlink(const path& p);
+GHC_FS_API file_time_type last_write_time(const path& p);
+GHC_FS_API void last_write_time(const path& p, file_time_type new_time);
+GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace);
+GHC_FS_API path proximate(const path& p, const path& base = current_path());
+GHC_FS_API path read_symlink(const path& p);
+GHC_FS_API path relative(const path& p, const path& base = current_path());
+GHC_FS_API bool remove(const path& p);
+GHC_FS_API uintmax_t remove_all(const path& p);
+GHC_FS_API void rename(const path& from, const path& to);
+GHC_FS_API void resize_file(const path& p, uintmax_t size);
+GHC_FS_API space_info space(const path& p);
+GHC_FS_API file_status status(const path& p);
+GHC_FS_API file_status symlink_status(const path& p);
+GHC_FS_API path temp_directory_path();
+GHC_FS_API path weakly_canonical(const path& p);
+#endif
+GHC_FS_API path absolute(const path& p, std::error_code& ec);
+GHC_FS_API path canonical(const path& p, std::error_code& ec);
+GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept;
+GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept;
+GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept;
+GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept;
+GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept;
+GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept;
+GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept;
+GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept;
+GHC_FS_API path current_path(std::error_code& ec);
+GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool exists(file_status s) noexcept;
+GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept;
+GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_block_file(file_status s) noexcept;
+GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_character_file(file_status s) noexcept;
+GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_directory(file_status s) noexcept;
+GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_fifo(file_status s) noexcept;
+GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_other(file_status s) noexcept;
+GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_regular_file(file_status s) noexcept;
+GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_socket(file_status s) noexcept;
+GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool is_symlink(file_status s) noexcept;
+GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept;
+GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept;
+GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept;
+GHC_FS_API path proximate(const path& p, std::error_code& ec);
+GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec);
+GHC_FS_API path read_symlink(const path& p, std::error_code& ec);
+GHC_FS_API path relative(const path& p, std::error_code& ec);
+GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec);
+GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept;
+GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept;
+GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API bool status_known(file_status s) noexcept;
+GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept;
+GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept;
+GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept;
+
+#ifndef GHC_OS_WEB
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link);
+GHC_FS_API uintmax_t hard_link_count(const path& p);
+#endif
+GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept;
+GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept;
+#endif
+
+// Non-C++17 add-on std::fstream wrappers with path
+template <class charT, class traits = std::char_traits<charT>>
+class basic_filebuf : public std::basic_filebuf<charT, traits>
+{
+public:
+    basic_filebuf() {}
+    ~basic_filebuf() override {}
+    basic_filebuf(const basic_filebuf&) = delete;
+    const basic_filebuf& operator=(const basic_filebuf&) = delete;
+    basic_filebuf<charT, traits>* open(const path& p, std::ios_base::openmode mode)
+    {
+#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
+        return std::basic_filebuf<charT, traits>::open(p.wstring().c_str(), mode) ? this : 0;
+#else
+        return std::basic_filebuf<charT, traits>::open(p.string().c_str(), mode) ? this : 0;
+#endif
+    }
+};
+
+template <class charT, class traits = std::char_traits<charT>>
+class basic_ifstream : public std::basic_ifstream<charT, traits>
+{
+public:
+    basic_ifstream() {}
+#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
+    explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in)
+        : std::basic_ifstream<charT, traits>(p.wstring().c_str(), mode)
+    {
+    }
+    void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream<charT, traits>::open(p.wstring().c_str(), mode); }
+#else
+    explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in)
+        : std::basic_ifstream<charT, traits>(p.string().c_str(), mode)
+    {
+    }
+    void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream<charT, traits>::open(p.string().c_str(), mode); }
+#endif
+    basic_ifstream(const basic_ifstream&) = delete;
+    const basic_ifstream& operator=(const basic_ifstream&) = delete;
+    ~basic_ifstream() override {}
+};
+
+template <class charT, class traits = std::char_traits<charT>>
+class basic_ofstream : public std::basic_ofstream<charT, traits>
+{
+public:
+    basic_ofstream() {}
+#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
+    explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out)
+        : std::basic_ofstream<charT, traits>(p.wstring().c_str(), mode)
+    {
+    }
+    void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream<charT, traits>::open(p.wstring().c_str(), mode); }
+#else
+    explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out)
+        : std::basic_ofstream<charT, traits>(p.string().c_str(), mode)
+    {
+    }
+    void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream<charT, traits>::open(p.string().c_str(), mode); }
+#endif
+    basic_ofstream(const basic_ofstream&) = delete;
+    const basic_ofstream& operator=(const basic_ofstream&) = delete;
+    ~basic_ofstream() override {}
+};
+
+template <class charT, class traits = std::char_traits<charT>>
+class basic_fstream : public std::basic_fstream<charT, traits>
+{
+public:
+    basic_fstream() {}
+#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
+    explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
+        : std::basic_fstream<charT, traits>(p.wstring().c_str(), mode)
+    {
+    }
+    void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream<charT, traits>::open(p.wstring().c_str(), mode); }
+#else
+    explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
+        : std::basic_fstream<charT, traits>(p.string().c_str(), mode)
+    {
+    }
+    void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream<charT, traits>::open(p.string().c_str(), mode); }
+#endif
+    basic_fstream(const basic_fstream&) = delete;
+    const basic_fstream& operator=(const basic_fstream&) = delete;
+    ~basic_fstream() override {}
+};
+
+typedef basic_filebuf<char> filebuf;
+typedef basic_filebuf<wchar_t> wfilebuf;
+typedef basic_ifstream<char> ifstream;
+typedef basic_ifstream<wchar_t> wifstream;
+typedef basic_ofstream<char> ofstream;
+typedef basic_ofstream<wchar_t> wofstream;
+typedef basic_fstream<char> fstream;
+typedef basic_fstream<wchar_t> wfstream;
+
+class GHC_FS_API_CLASS u8arguments
+{
+public:
+    u8arguments(int& argc, char**& argv);
+    ~u8arguments()
+    {
+        _refargc = _argc;
+        _refargv = _argv;
+    }
+
+    bool valid() const { return _isvalid; }
+
+private:
+    int _argc;
+    char** _argv;
+    int& _refargc;
+    char**& _refargv;
+    bool _isvalid;
+#ifdef GHC_OS_WINDOWS
+    std::vector<std::string> _args;
+    std::vector<char*> _argp;
+#endif
+};
+
+//-------------------------------------------------------------------------------------------------
+//  Implementation
+//-------------------------------------------------------------------------------------------------
+
+namespace detail {
+enum utf8_states_t { S_STRT = 0, S_RJCT = 8 };
+GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode);
+GHC_FS_API bool is_surrogate(uint32_t c);
+GHC_FS_API bool is_high_surrogate(uint32_t c);
+GHC_FS_API bool is_low_surrogate(uint32_t c);
+GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint);
+enum class portable_error {
+    none = 0,
+    exists,
+    not_found,
+    not_supported,
+    not_implemented,
+    invalid_argument,
+    is_a_directory,
+};
+GHC_FS_API std::error_code make_error_code(portable_error err);
+#ifdef GHC_OS_WINDOWS
+GHC_FS_API std::error_code make_system_error(uint32_t err = 0);
+#else
+GHC_FS_API std::error_code make_system_error(int err = 0);
+#endif
+}  // namespace detail
+
+namespace detail {
+
+#ifdef GHC_EXPAND_IMPL
+
+GHC_INLINE std::error_code make_error_code(portable_error err)
+{
+#ifdef GHC_OS_WINDOWS
+    switch (err) {
+        case portable_error::none:
+            return std::error_code();
+        case portable_error::exists:
+            return std::error_code(ERROR_ALREADY_EXISTS, std::system_category());
+        case portable_error::not_found:
+            return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category());
+        case portable_error::not_supported:
+            return std::error_code(ERROR_NOT_SUPPORTED, std::system_category());
+        case portable_error::not_implemented:
+            return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category());
+        case portable_error::invalid_argument:
+            return std::error_code(ERROR_INVALID_PARAMETER, std::system_category());
+        case portable_error::is_a_directory:
+#ifdef ERROR_DIRECTORY_NOT_SUPPORTED
+            return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category());
+#else
+            return std::error_code(ERROR_NOT_SUPPORTED, std::system_category());
+#endif
+    }
+#else
+    switch (err) {
+        case portable_error::none:
+            return std::error_code();
+        case portable_error::exists:
+            return std::error_code(EEXIST, std::system_category());
+        case portable_error::not_found:
+            return std::error_code(ENOENT, std::system_category());
+        case portable_error::not_supported:
+            return std::error_code(ENOTSUP, std::system_category());
+        case portable_error::not_implemented:
+            return std::error_code(ENOSYS, std::system_category());
+        case portable_error::invalid_argument:
+            return std::error_code(EINVAL, std::system_category());
+        case portable_error::is_a_directory:
+            return std::error_code(EISDIR, std::system_category());
+    }
+#endif
+    return std::error_code();
+}
+
+#ifdef GHC_OS_WINDOWS
+GHC_INLINE std::error_code make_system_error(uint32_t err)
+{
+    return std::error_code(err ? static_cast<int>(err) : static_cast<int>(::GetLastError()), std::system_category());
+}
+#else
+GHC_INLINE std::error_code make_system_error(int err)
+{
+    return std::error_code(err ? err : errno, std::system_category());
+}
+#endif
+
+#endif  // GHC_EXPAND_IMPL
+
+template <typename Enum>
+using EnableBitmask = typename std::enable_if<std::is_same<Enum, perms>::value || std::is_same<Enum, perm_options>::value || std::is_same<Enum, copy_options>::value || std::is_same<Enum, directory_options>::value, Enum>::type;
+}  // namespace detail
+
+template <typename Enum>
+constexpr detail::EnableBitmask<Enum> operator&(Enum X, Enum Y)
+{
+    using underlying = typename std::underlying_type<Enum>::type;
+    return static_cast<Enum>(static_cast<underlying>(X) & static_cast<underlying>(Y));
+}
+
+template <typename Enum>
+constexpr detail::EnableBitmask<Enum> operator|(Enum X, Enum Y)
+{
+    using underlying = typename std::underlying_type<Enum>::type;
+    return static_cast<Enum>(static_cast<underlying>(X) | static_cast<underlying>(Y));
+}
+
+template <typename Enum>
+constexpr detail::EnableBitmask<Enum> operator^(Enum X, Enum Y)
+{
+    using underlying = typename std::underlying_type<Enum>::type;
+    return static_cast<Enum>(static_cast<underlying>(X) ^ static_cast<underlying>(Y));
+}
+
+template <typename Enum>
+constexpr detail::EnableBitmask<Enum> operator~(Enum X)
+{
+    using underlying = typename std::underlying_type<Enum>::type;
+    return static_cast<Enum>(~static_cast<underlying>(X));
+}
+
+template <typename Enum>
+detail::EnableBitmask<Enum>& operator&=(Enum& X, Enum Y)
+{
+    X = X & Y;
+    return X;
+}
+
+template <typename Enum>
+detail::EnableBitmask<Enum>& operator|=(Enum& X, Enum Y)
+{
+    X = X | Y;
+    return X;
+}
+
+template <typename Enum>
+detail::EnableBitmask<Enum>& operator^=(Enum& X, Enum Y)
+{
+    X = X ^ Y;
+    return X;
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+namespace detail {
+
+GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi)
+{
+    return (static_cast<uint32_t>(c - lo) < (hi - lo + 1));
+}
+
+GHC_INLINE bool is_surrogate(uint32_t c)
+{
+    return in_range(c, 0xd800, 0xdfff);
+}
+
+GHC_INLINE bool is_high_surrogate(uint32_t c)
+{
+    return (c & 0xfffffc00) == 0xd800;
+}
+
+GHC_INLINE bool is_low_surrogate(uint32_t c)
+{
+    return (c & 0xfffffc00) == 0xdc00;
+}
+
+GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode)
+{
+    if (unicode <= 0x7f) {
+        str.push_back(static_cast<char>(unicode));
+    }
+    else if (unicode >= 0x80 && unicode <= 0x7ff) {
+        str.push_back(static_cast<char>((unicode >> 6) + 192));
+        str.push_back(static_cast<char>((unicode & 0x3f) + 128));
+    }
+    else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) {
+        str.push_back(static_cast<char>((unicode >> 12) + 224));
+        str.push_back(static_cast<char>(((unicode & 0xfff) >> 6) + 128));
+        str.push_back(static_cast<char>((unicode & 0x3f) + 128));
+    }
+    else if (unicode >= 0x10000 && unicode <= 0x10ffff) {
+        str.push_back(static_cast<char>((unicode >> 18) + 240));
+        str.push_back(static_cast<char>(((unicode & 0x3ffff) >> 12) + 128));
+        str.push_back(static_cast<char>(((unicode & 0xfff) >> 6) + 128));
+        str.push_back(static_cast<char>((unicode & 0x3f) + 128));
+    }
+    else {
+#ifdef GHC_RAISE_UNICODE_ERRORS
+        throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence));
+#else
+        appendUTF8(str, 0xfffd);
+#endif
+    }
+}
+
+// Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/)
+// and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding;
+// Generating debugging and shrinking my own DFA from scratch was a day of fun!
+GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint)
+{
+    static const uint32_t utf8_state_info[] = {
+        // encoded states
+        0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u,
+        0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u,          0u,          0u,          0u,
+    };
+    uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf;
+    codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment);
+    return state == S_RJCT ? static_cast<unsigned>(S_RJCT) : static_cast<unsigned>((utf8_state_info[category + 16] >> (state << 2)) & 0xf);
+}
+
+GHC_INLINE bool validUtf8(const std::string& utf8String)
+{
+    std::string::const_iterator iter = utf8String.begin();
+    unsigned utf8_state = S_STRT;
+    std::uint32_t codepoint = 0;
+    while (iter < utf8String.end()) {
+        if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast<uint8_t>(*iter++), codepoint)) == S_RJCT) {
+            return false;
+        }
+    }
+    if (utf8_state) {
+        return false;
+    }
+    return true;
+}
+
+}  // namespace detail
+
+#endif
+
+namespace detail {
+
+template <class StringType, class Utf8String, typename std::enable_if<path::_is_basic_string<Utf8String>::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr>
+inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type())
+{
+    return StringType(utf8String.begin(), utf8String.end(), alloc);
+}
+
+template <class StringType, class Utf8String, typename std::enable_if<path::_is_basic_string<Utf8String>::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 2)>::type* = nullptr>
+inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type())
+{
+    StringType result(alloc);
+    result.reserve(utf8String.length());
+    auto iter = utf8String.cbegin();
+    unsigned utf8_state = S_STRT;
+    std::uint32_t codepoint = 0;
+    while (iter < utf8String.cend()) {
+        if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast<uint8_t>(*iter++), codepoint)) == S_STRT) {
+            if (codepoint <= 0xffff) {
+                result += static_cast<typename StringType::value_type>(codepoint);
+            }
+            else {
+                codepoint -= 0x10000;
+                result += static_cast<typename StringType::value_type>((codepoint >> 10) + 0xd800);
+                result += static_cast<typename StringType::value_type>((codepoint & 0x3ff) + 0xdc00);
+            }
+            codepoint = 0;
+        }
+        else if (utf8_state == S_RJCT) {
+#ifdef GHC_RAISE_UNICODE_ERRORS
+            throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence));
+#else
+            result += static_cast<typename StringType::value_type>(0xfffd);
+            utf8_state = S_STRT;
+            codepoint = 0;
+#endif
+        }
+    }
+    if (utf8_state) {
+#ifdef GHC_RAISE_UNICODE_ERRORS
+        throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence));
+#else
+        result += static_cast<typename StringType::value_type>(0xfffd);
+#endif
+    }
+    return result;
+}
+
+template <class StringType, class Utf8String, typename std::enable_if<path::_is_basic_string<Utf8String>::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 4)>::type* = nullptr>
+inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type())
+{
+    StringType result(alloc);
+    result.reserve(utf8String.length());
+    auto iter = utf8String.cbegin();
+    unsigned utf8_state = S_STRT;
+    std::uint32_t codepoint = 0;
+    while (iter < utf8String.cend()) {
+        if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast<uint8_t>(*iter++), codepoint)) == S_STRT) {
+            result += static_cast<typename StringType::value_type>(codepoint);
+            codepoint = 0;
+        }
+        else if (utf8_state == S_RJCT) {
+#ifdef GHC_RAISE_UNICODE_ERRORS
+            throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence));
+#else
+            result += static_cast<typename StringType::value_type>(0xfffd);
+            utf8_state = S_STRT;
+            codepoint = 0;
+#endif
+        }
+    }
+    if (utf8_state) {
+#ifdef GHC_RAISE_UNICODE_ERRORS
+        throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence));
+#else
+        result += static_cast<typename StringType::value_type>(0xfffd);
+#endif
+    }
+    return result;
+}
+
+template <class StringType, typename charT, std::size_t N>
+inline StringType fromUtf8(const charT (&utf8String)[N])
+{
+#ifdef GHC_WITH_STRING_VIEW
+    return fromUtf8<StringType>(basic_string_view<charT>(utf8String, N - 1));
+#else
+    return fromUtf8<StringType>(std::basic_string<charT>(utf8String, N - 1));
+#endif
+}
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value && (sizeof(typename strT::value_type) == 1), int>::type size = 1>
+inline std::string toUtf8(const strT& unicodeString)
+{
+    return std::string(unicodeString.begin(), unicodeString.end());
+}
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value && (sizeof(typename strT::value_type) == 2), int>::type size = 2>
+inline std::string toUtf8(const strT& unicodeString)
+{
+    std::string result;
+    for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) {
+        char32_t c = *iter;
+        if (is_surrogate(c)) {
+            ++iter;
+            if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) {
+                appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00);
+            }
+            else {
+#ifdef GHC_RAISE_UNICODE_ERRORS
+                throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence));
+#else
+                appendUTF8(result, 0xfffd);
+                if (iter == unicodeString.end()) {
+                    break;
+                }
+#endif
+            }
+        }
+        else {
+            appendUTF8(result, c);
+        }
+    }
+    return result;
+}
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value && (sizeof(typename strT::value_type) == 4), int>::type size = 4>
+inline std::string toUtf8(const strT& unicodeString)
+{
+    std::string result;
+    for (auto c : unicodeString) {
+        appendUTF8(result, static_cast<uint32_t>(c));
+    }
+    return result;
+}
+
+template <typename charT>
+inline std::string toUtf8(const charT* unicodeString)
+{
+#ifdef GHC_WITH_STRING_VIEW
+    return toUtf8(basic_string_view<charT, std::char_traits<charT>>(unicodeString));
+#else
+    return toUtf8(std::basic_string<charT, std::char_traits<charT>>(unicodeString));
+#endif
+}
+
+#ifdef GHC_USE_WCHAR_T
+template <class StringType, class WString, typename std::enable_if<path::_is_basic_string<WString>::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false>
+inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type())
+{
+    auto temp = toUtf8(wString);
+    return StringType(temp.begin(), temp.end(), alloc);
+}
+
+template <class StringType, class WString, typename std::enable_if<path::_is_basic_string<WString>::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false>
+inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type())
+{
+    return StringType(wString.begin(), wString.end(), alloc);
+}
+
+template <class StringType, class WString, typename std::enable_if<path::_is_basic_string<WString>::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false>
+inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type())
+{
+    auto temp = toUtf8(wString);
+    return fromUtf8<StringType>(temp, alloc);
+}
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value && (sizeof(typename strT::value_type) == 1), bool>::type = false>
+inline std::wstring toWChar(const strT& unicodeString)
+{
+    return fromUtf8<std::wstring>(unicodeString);
+}
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value && (sizeof(typename strT::value_type) == 2), bool>::type = false>
+inline std::wstring toWChar(const strT& unicodeString)
+{
+    return std::wstring(unicodeString.begin(), unicodeString.end());
+}
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value && (sizeof(typename strT::value_type) == 4), bool>::type = false>
+inline std::wstring toWChar(const strT& unicodeString)
+{
+    auto temp = toUtf8(unicodeString);
+    return fromUtf8<std::wstring>(temp);
+}
+
+template <typename charT>
+inline std::wstring toWChar(const charT* unicodeString)
+{
+#ifdef GHC_WITH_STRING_VIEW
+    return toWChar(basic_string_view<charT, std::char_traits<charT>>(unicodeString));
+#else
+    return toWChar(std::basic_string<charT, std::char_traits<charT>>(unicodeString));
+#endif
+}
+#endif // GHC_USE_WCHAR_T
+
+}  // namespace detail
+
+#ifdef GHC_EXPAND_IMPL
+
+namespace detail {
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value, bool>::type = true>
+GHC_INLINE bool startsWith(const strT& what, const strT& with)
+{
+    return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin());
+}
+
+template <typename strT, typename std::enable_if<path::_is_basic_string<strT>::value, bool>::type = true>
+GHC_INLINE bool endsWith(const strT& what, const strT& with)
+{
+    return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0;
+}
+
+}  // namespace detail
+
+GHC_INLINE void path::check_long_path()
+{
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) {
+        postprocess_path_with_format(native_format);
+    }
+#endif
+}
+
+GHC_INLINE void path::postprocess_path_with_format(path::format fmt)
+{
+#ifdef GHC_RAISE_UNICODE_ERRORS
+    if (!detail::validUtf8(_path)) {
+        path t;
+        t._path = _path;
+        throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence));
+    }
+#endif
+    switch (fmt) {
+#ifdef GHC_OS_WINDOWS
+        case path::native_format:
+        case path::auto_format:
+        case path::generic_format:
+            for (auto& c : _path) {
+                if (c == generic_separator) {
+                    c = preferred_separator;
+                }
+            }
+#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH
+            if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) {
+                _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path;
+            }
+#endif
+            handle_prefixes();
+            break;
+#else
+        case path::auto_format:
+        case path::native_format:
+        case path::generic_format:
+            // nothing to do
+            break;
+#endif
+    }
+    if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) {
+        impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast<string_type::difference_type>(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; });
+        _path.erase(new_end, _path.end());
+    }
+    else {
+        impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast<string_type::difference_type>(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; });
+        _path.erase(new_end, _path.end());
+    }
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+template <class Source, typename>
+inline path::path(const Source& source, format fmt)
+#ifdef GHC_USE_WCHAR_T
+    : _path(detail::toWChar(source))
+#else
+    : _path(detail::toUtf8(source))
+#endif
+{
+    postprocess_path_with_format(fmt);
+}
+
+template <class Source, typename>
+inline path u8path(const Source& source)
+{
+    return path(source);
+}
+template <class InputIterator>
+inline path u8path(InputIterator first, InputIterator last)
+{
+    return path(first, last);
+}
+
+template <class InputIterator>
+inline path::path(InputIterator first, InputIterator last, format fmt)
+    : path(std::basic_string<typename std::iterator_traits<InputIterator>::value_type>(first, last), fmt)
+{
+    // delegated
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+namespace detail {
+
+GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2)
+{
+#ifdef GHC_OS_WINDOWS
+#ifdef __GNUC__
+    while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) {
+        if (*str1++ == 0)
+            return true;
+    }
+    return false;
+#else // __GNUC__
+#ifdef GHC_USE_WCHAR_T
+    return 0 == ::_wcsicmp(str1, str2);
+#else // GHC_USE_WCHAR_T
+    return 0 == ::_stricmp(str1, str2);
+#endif // GHC_USE_WCHAR_T
+#endif // __GNUC__
+#else // GHC_OS_WINDOWS
+    return 0 == ::strcasecmp(str1, str2);
+#endif // GHC_OS_WINDOWS
+}
+
+GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2)
+{
+    while (len1 > 0 && len2 > 0 && ::tolower(static_cast<unsigned char>(*str1)) == ::tolower(static_cast<unsigned char>(*str2))) {
+        --len1;
+        --len2;
+        ++str1;
+        ++str2;
+    }
+    if (len1 && len2) {
+        return *str1 < *str2 ? -1 : 1;
+    }
+    if (len1 == 0 && len2 == 0) {
+        return 0;
+    }
+    return len1 == 0 ? -1 : 1;
+}
+
+GHC_INLINE const char* strerror_adapter(char* gnu, char*)
+{
+    return gnu;
+}
+
+GHC_INLINE const char* strerror_adapter(int posix, char* buffer)
+{
+    if (posix) {
+        return "Error in strerror_r!";
+    }
+    return buffer;
+}
+
+template <typename ErrorNumber>
+GHC_INLINE std::string systemErrorText(ErrorNumber code = 0)
+{
+#if defined(GHC_OS_WINDOWS)
+    LPVOID msgBuf;
+    DWORD dw = code ? static_cast<DWORD>(code) : ::GetLastError();
+    FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL);
+    std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf));
+    LocalFree(msgBuf);
+    return msg;
+#else
+    char buffer[512];
+    return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer);
+#endif
+}
+
+#ifdef GHC_OS_WINDOWS
+using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD);
+using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
+
+GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec)
+{
+    std::error_code tec;
+    auto fs = status(target_name, tec);
+    if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) {
+        ec = detail::make_error_code(detail::portable_error::not_supported);
+        return;
+    }
+#if defined(__GNUC__) && __GNUC__ >= 8
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+    static CreateSymbolicLinkW_fp api_call = reinterpret_cast<CreateSymbolicLinkW_fp>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW"));
+#if defined(__GNUC__) && __GNUC__ >= 8
+#pragma GCC diagnostic pop
+#endif
+    if (api_call) {
+        if (api_call(detail::fromUtf8<std::wstring>(new_symlink.u8string()).c_str(), detail::fromUtf8<std::wstring>(target_name.u8string()).c_str(), to_directory ? 1 : 0) == 0) {
+            auto result = ::GetLastError();
+            if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(detail::fromUtf8<std::wstring>(new_symlink.u8string()).c_str(), detail::fromUtf8<std::wstring>(target_name.u8string()).c_str(), to_directory ? 3 : 2) != 0) {
+                return;
+            }
+            ec = detail::make_system_error(result);
+        }
+    }
+    else {
+        ec = detail::make_system_error(ERROR_NOT_SUPPORTED);
+    }
+}
+
+GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec)
+{
+#if defined(__GNUC__) && __GNUC__ >= 8
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+    static CreateHardLinkW_fp api_call = reinterpret_cast<CreateHardLinkW_fp>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW"));
+#if defined(__GNUC__) && __GNUC__ >= 8
+#pragma GCC diagnostic pop
+#endif
+    if (api_call) {
+        if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) {
+            ec = detail::make_system_error();
+        }
+    }
+    else {
+        ec = detail::make_system_error(ERROR_NOT_SUPPORTED);
+    }
+}
+
+GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec)
+{
+    ULONG size = ::GetFullPathNameW(p, 0, 0, 0);
+    if (size) {
+        std::vector<wchar_t> buf(size, 0);
+        ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr);
+        if (s2 && s2 < size) {
+            return path(std::wstring(buf.data(), s2));
+        }
+    }
+    ec = detail::make_system_error();
+    return path();
+}
+
+#else
+GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec)
+{
+    if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) {
+        ec = detail::make_system_error();
+    }
+}
+
+#ifndef GHC_OS_WEB
+GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec)
+{
+    if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) {
+        ec = detail::make_system_error();
+    }
+}
+#endif
+#endif
+
+template <typename T>
+GHC_INLINE file_status file_status_from_st_mode(T mode)
+{
+#ifdef GHC_OS_WINDOWS
+    file_type ft = file_type::unknown;
+    if ((mode & _S_IFDIR) == _S_IFDIR) {
+        ft = file_type::directory;
+    }
+    else if ((mode & _S_IFREG) == _S_IFREG) {
+        ft = file_type::regular;
+    }
+    else if ((mode & _S_IFCHR) == _S_IFCHR) {
+        ft = file_type::character;
+    }
+    perms prms = static_cast<perms>(mode & 0xfff);
+    return file_status(ft, prms);
+#else
+    file_type ft = file_type::unknown;
+    if (S_ISDIR(mode)) {
+        ft = file_type::directory;
+    }
+    else if (S_ISREG(mode)) {
+        ft = file_type::regular;
+    }
+    else if (S_ISCHR(mode)) {
+        ft = file_type::character;
+    }
+    else if (S_ISBLK(mode)) {
+        ft = file_type::block;
+    }
+    else if (S_ISFIFO(mode)) {
+        ft = file_type::fifo;
+    }
+    else if (S_ISLNK(mode)) {
+        ft = file_type::symlink;
+    }
+    else if (S_ISSOCK(mode)) {
+        ft = file_type::socket;
+    }
+    perms prms = static_cast<perms>(mode & 0xfff);
+    return file_status(ft, prms);
+#endif
+}
+
+#ifdef GHC_OS_WINDOWS
+#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
+typedef struct _REPARSE_DATA_BUFFER
+{
+    ULONG ReparseTag;
+    USHORT ReparseDataLength;
+    USHORT Reserved;
+    union
+    {
+        struct
+        {
+            USHORT SubstituteNameOffset;
+            USHORT SubstituteNameLength;
+            USHORT PrintNameOffset;
+            USHORT PrintNameLength;
+            ULONG Flags;
+            WCHAR PathBuffer[1];
+        } SymbolicLinkReparseBuffer;
+        struct
+        {
+            USHORT SubstituteNameOffset;
+            USHORT SubstituteNameLength;
+            USHORT PrintNameOffset;
+            USHORT PrintNameLength;
+            WCHAR PathBuffer[1];
+        } MountPointReparseBuffer;
+        struct
+        {
+            UCHAR DataBuffer[1];
+        } GenericReparseBuffer;
+    } DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER;
+#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE
+#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024)
+#endif
+#endif
+
+GHC_INLINE std::shared_ptr<REPARSE_DATA_BUFFER> getReparseData(const path& p, std::error_code& ec)
+{
+    std::shared_ptr<void> file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
+    if (file.get() == INVALID_HANDLE_VALUE) {
+        ec = detail::make_system_error();
+        return nullptr;
+    }
+
+    std::shared_ptr<REPARSE_DATA_BUFFER> reparseData((REPARSE_DATA_BUFFER*)std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE), std::free);
+    ULONG bufferUsed;
+    if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) {
+        return reparseData;
+    }
+    else {
+        ec = detail::make_system_error();
+    }
+    return nullptr;
+}
+#endif
+
+GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec)
+{
+#ifdef GHC_OS_WINDOWS
+    path result;
+    auto reparseData = detail::getReparseData(p, ec);
+    if (!ec) {
+        if (reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag)) {
+            switch (reparseData->ReparseTag) {
+                case IO_REPARSE_TAG_SYMLINK: {
+                    auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR));
+                    auto substituteName =
+                        std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
+                    if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) {
+                        result = printName;
+                    }
+                    else {
+                        result = substituteName;
+                    }
+                    if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) {
+                        result = p.parent_path() / result;
+                    }
+                    break;
+                }
+                case IO_REPARSE_TAG_MOUNT_POINT:
+                    result = detail::getFullPathName(GHC_NATIVEWP(p), ec);
+                    //result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    return result;
+#else
+    size_t bufferSize = 256;
+    while (true) {
+        std::vector<char> buffer(bufferSize, static_cast<char>(0));
+        auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size());
+        if (rc < 0) {
+            ec = detail::make_system_error();
+            return path();
+        }
+        else if (rc < static_cast<int>(bufferSize)) {
+            return path(std::string(buffer.data(), static_cast<std::string::size_type>(rc)));
+        }
+        bufferSize *= 2;
+    }
+    return path();
+#endif
+}
+
+#ifdef GHC_OS_WINDOWS
+GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft)
+{
+    ULARGE_INTEGER ull;
+    ull.LowPart = ft.dwLowDateTime;
+    ull.HighPart = ft.dwHighDateTime;
+    return static_cast<time_t>(ull.QuadPart / 10000000ULL - 11644473600ULL);
+}
+
+GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft)
+{
+    LONGLONG ll;
+    ll = Int32x32To64(t, 10000000) + 116444736000000000;
+    ft.dwLowDateTime = static_cast<DWORD>(ll);
+    ft.dwHighDateTime = static_cast<DWORD>(ll >> 32);
+}
+
+template <typename INFO>
+GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info)
+{
+    return static_cast<uintmax_t>(-1);
+}
+
+template <>
+GHC_INLINE uintmax_t hard_links_from_INFO<BY_HANDLE_FILE_INFORMATION>(const BY_HANDLE_FILE_INFORMATION* info)
+{
+    return info->nNumberOfLinks;
+}
+
+template <typename INFO>
+GHC_INLINE DWORD reparse_tag_from_INFO(const INFO*)
+{
+    return 0;
+}
+
+template <>
+GHC_INLINE DWORD reparse_tag_from_INFO(const WIN32_FIND_DATAW* info)
+{
+    return info->dwReserved0;
+}
+
+template <typename INFO>
+GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr)
+{
+    file_type ft = file_type::unknown;
+    if (sizeof(INFO) == sizeof(WIN32_FIND_DATAW)) {
+        if (detail::reparse_tag_from_INFO(info) == IO_REPARSE_TAG_SYMLINK) {
+            ft = file_type::symlink;
+        }
+    }
+    else {
+        if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+            auto reparseData = detail::getReparseData(p, ec);
+            if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
+                ft = file_type::symlink;
+            }
+        }
+    }
+    if (ft == file_type::unknown) {
+        if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+            ft = file_type::directory;
+        }
+        else {
+            ft = file_type::regular;
+        }
+    }
+    perms prms = perms::owner_read | perms::group_read | perms::others_read;
+    if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
+        prms = prms | perms::owner_write | perms::group_write | perms::others_write;
+    }
+    if (has_executable_extension(p)) {
+        prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec;
+    }
+    if (sz) {
+        *sz = static_cast<uintmax_t>(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow;
+    }
+    if (lwt) {
+        *lwt = detail::timeFromFILETIME(info->ftLastWriteTime);
+    }
+    return file_status(ft, prms);
+}
+
+#endif
+
+GHC_INLINE bool is_not_found_error(std::error_code& ec)
+{
+#ifdef GHC_OS_WINDOWS
+    return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME;
+#else
+    return ec.value() == ENOENT || ec.value() == ENOTDIR;
+#endif
+}
+
+GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept
+{
+#ifdef GHC_OS_WINDOWS
+    file_status fs;
+    WIN32_FILE_ATTRIBUTE_DATA attr;
+    if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) {
+        ec = detail::make_system_error();
+    }
+    else {
+        ec.clear();
+        fs = detail::status_from_INFO(p, &attr, ec, sz, lwt);
+        if (nhl) {
+            *nhl = 0;
+        }
+    }
+    if (detail::is_not_found_error(ec)) {
+        return file_status(file_type::not_found);
+    }
+    return ec ? file_status(file_type::none) : fs;
+#else
+    (void)sz;
+    (void)nhl;
+    (void)lwt;
+    struct ::stat fs;
+    auto result = ::lstat(p.c_str(), &fs);
+    if (result == 0) {
+        ec.clear();
+        file_status f_s = detail::file_status_from_st_mode(fs.st_mode);
+        return f_s;
+    }
+    ec = detail::make_system_error();
+    if (detail::is_not_found_error(ec)) {
+        return file_status(file_type::not_found, perms::unknown);
+    }
+    return file_status(file_type::none);
+#endif
+}
+
+GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    if (recurse_count > 16) {
+        ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/);
+        return file_status(file_type::unknown);
+    }
+    WIN32_FILE_ATTRIBUTE_DATA attr;
+    if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) {
+        ec = detail::make_system_error();
+    }
+    else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+        auto reparseData = detail::getReparseData(p, ec);
+        if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
+            path target = resolveSymlink(p, ec);
+            file_status result;
+            if (!ec && !target.empty()) {
+                if (sls) {
+                    *sls = status_from_INFO(p, &attr, ec);
+                }
+                return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1);
+            }
+            return file_status(file_type::unknown);
+        }
+    }
+    if (ec) {
+        if (detail::is_not_found_error(ec)) {
+            return file_status(file_type::not_found);
+        }
+        return file_status(file_type::none);
+    }
+    if (nhl) {
+        *nhl = 0;
+    }
+    return detail::status_from_INFO(p, &attr, ec, sz, lwt);
+#else
+    (void)recurse_count;
+    struct ::stat st;
+    auto result = ::lstat(p.c_str(), &st);
+    if (result == 0) {
+        ec.clear();
+        file_status fs = detail::file_status_from_st_mode(st.st_mode);
+        if (sls) {
+            *sls = fs;
+        }
+        if (fs.type() == file_type::symlink) {
+            result = ::stat(p.c_str(), &st);
+            if (result == 0) {
+                fs = detail::file_status_from_st_mode(st.st_mode);
+            }
+            else {
+                ec = detail::make_system_error();
+                if (detail::is_not_found_error(ec)) {
+                    return file_status(file_type::not_found, perms::unknown);
+                }
+                return file_status(file_type::none);
+            }
+        }
+        if (sz) {
+            *sz = static_cast<uintmax_t>(st.st_size);
+        }
+        if (nhl) {
+            *nhl = st.st_nlink;
+        }
+        if (lwt) {
+            *lwt = st.st_mtime;
+        }
+        return fs;
+    }
+    else {
+        ec = detail::make_system_error();
+        if (detail::is_not_found_error(ec)) {
+            return file_status(file_type::not_found, perms::unknown);
+        }
+        return file_status(file_type::none);
+    }
+#endif
+}
+
+}  // namespace detail
+
+GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv)
+    : _argc(argc)
+    , _argv(argv)
+    , _refargc(argc)
+    , _refargv(argv)
+    , _isvalid(false)
+{
+#ifdef GHC_OS_WINDOWS
+    LPWSTR* p;
+    p = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+    _args.reserve(static_cast<size_t>(argc));
+    _argp.reserve(static_cast<size_t>(argc));
+    for (size_t i = 0; i < static_cast<size_t>(argc); ++i) {
+        _args.push_back(detail::toUtf8(std::wstring(p[i])));
+        _argp.push_back((char*)_args[i].data());
+    }
+    argv = _argp.data();
+    ::LocalFree(p);
+    _isvalid = true;
+#else
+    std::setlocale(LC_ALL, "");
+#if defined(__ANDROID__) && __ANDROID_API__ < 26
+    _isvalid = true;
+#else
+    if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) {
+        _isvalid = true;
+    }
+#endif
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// [fs.path.construct] constructors and destructor
+
+GHC_INLINE path::path() noexcept {}
+
+GHC_INLINE path::path(const path& p)
+    : _path(p._path)
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    , _prefixLength(p._prefixLength)
+#endif
+{
+}
+
+GHC_INLINE path::path(path&& p) noexcept
+    : _path(std::move(p._path))
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    , _prefixLength(p._prefixLength)
+#endif
+{
+}
+
+GHC_INLINE path::path(string_type&& source, format fmt)
+    : _path(std::move(source))
+{
+    postprocess_path_with_format(fmt);
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+#ifdef GHC_WITH_EXCEPTIONS
+template <class Source, typename>
+inline path::path(const Source& source, const std::locale& loc, format fmt)
+    : path(source, fmt)
+{
+    std::string locName = loc.name();
+    if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) {
+        throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported));
+    }
+}
+
+template <class InputIterator>
+inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt)
+    : path(std::basic_string<typename std::iterator_traits<InputIterator>::value_type>(first, last), fmt)
+{
+    std::string locName = loc.name();
+    if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) {
+        throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported));
+    }
+}
+#endif
+
+#ifdef GHC_EXPAND_IMPL
+
+GHC_INLINE path::~path() {}
+
+//-----------------------------------------------------------------------------
+// [fs.path.assign] assignments
+
+GHC_INLINE path& path::operator=(const path& p)
+{
+    _path = p._path;
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    _prefixLength = p._prefixLength;
+#endif
+    return *this;
+}
+
+GHC_INLINE path& path::operator=(path&& p) noexcept
+{
+    _path = std::move(p._path);
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    _prefixLength = p._prefixLength;
+#endif
+    return *this;
+}
+
+GHC_INLINE path& path::operator=(path::string_type&& source)
+{
+    return assign(source);
+}
+
+GHC_INLINE path& path::assign(path::string_type&& source)
+{
+    _path = std::move(source);
+    postprocess_path_with_format(native_format);
+    return *this;
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+template <class Source>
+inline path& path::operator=(const Source& source)
+{
+    return assign(source);
+}
+
+template <class Source>
+inline path& path::assign(const Source& source)
+{
+#ifdef GHC_USE_WCHAR_T
+    _path.assign(detail::toWChar(source));
+#else
+    _path.assign(detail::toUtf8(source));
+#endif
+    postprocess_path_with_format(native_format);
+    return *this;
+}
+
+template <>
+inline path& path::assign<path>(const path& source)
+{
+    _path = source._path;
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    _prefixLength = source._prefixLength;
+#endif
+    return *this;
+}
+
+template <class InputIterator>
+inline path& path::assign(InputIterator first, InputIterator last)
+{
+    _path.assign(first, last);
+    postprocess_path_with_format(native_format);
+    return *this;
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+//-----------------------------------------------------------------------------
+// [fs.path.append] appends
+
+GHC_INLINE path& path::operator/=(const path& p)
+{
+    if (p.empty()) {
+        // was: if ((!has_root_directory() && is_absolute()) || has_filename())
+        if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') {
+            _path += preferred_separator;
+        }
+        return *this;
+    }
+    if ((p.is_absolute() && (_path != root_name()._path || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) {
+        assign(p);
+        return *this;
+    }
+    if (p.has_root_directory()) {
+        assign(root_name());
+    }
+    else if ((!has_root_directory() && is_absolute()) || has_filename()) {
+        _path += preferred_separator;
+    }
+    auto iter = p.begin();
+    bool first = true;
+    if (p.has_root_name()) {
+        ++iter;
+    }
+    while (iter != p.end()) {
+        if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) {
+            _path += preferred_separator;
+        }
+        first = false;
+        _path += (*iter++).native();
+    }
+    check_long_path();
+    return *this;
+}
+
+GHC_INLINE void path::append_name(const value_type* name)
+{
+    if (_path.empty()) {
+        this->operator/=(path(name));
+    }
+    else {
+        if (_path.back() != path::preferred_separator) {
+            _path.push_back(path::preferred_separator);
+        }
+        _path += name;
+        check_long_path();
+    }
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+template <class Source>
+inline path& path::operator/=(const Source& source)
+{
+    return append(source);
+}
+
+template <class Source>
+inline path& path::append(const Source& source)
+{
+    return this->operator/=(path(source));
+}
+
+template <>
+inline path& path::append<path>(const path& p)
+{
+    return this->operator/=(p);
+}
+
+template <class InputIterator>
+inline path& path::append(InputIterator first, InputIterator last)
+{
+    std::basic_string<typename std::iterator_traits<InputIterator>::value_type> part(first, last);
+    return append(part);
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+//-----------------------------------------------------------------------------
+// [fs.path.concat] concatenation
+
+GHC_INLINE path& path::operator+=(const path& x)
+{
+    return concat(x._path);
+}
+
+GHC_INLINE path& path::operator+=(const string_type& x)
+{
+    return concat(x);
+}
+
+#ifdef GHC_WITH_STRING_VIEW
+GHC_INLINE path& path::operator+=(basic_string_view<value_type> x)
+{
+    return concat(x);
+}
+#endif
+
+GHC_INLINE path& path::operator+=(const value_type* x)
+{
+#ifdef GHC_WITH_STRING_VIEW
+    basic_string_view<value_type> part(x);
+#else
+    string_type part(x);
+#endif
+    return concat(part);
+}
+
+GHC_INLINE path& path::operator+=(value_type x)
+{
+#ifdef GHC_OS_WINDOWS
+    if (x == generic_separator) {
+        x = preferred_separator;
+    }
+#endif
+    if (_path.empty() || _path.back() != preferred_separator) {
+        _path += x;
+    }
+    check_long_path();
+    return *this;
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+template <class Source>
+inline path::path_from_string<Source>& path::operator+=(const Source& x)
+{
+    return concat(x);
+}
+
+template <class EcharT>
+inline path::path_type_EcharT<EcharT>& path::operator+=(EcharT x)
+{
+#ifdef GHC_WITH_STRING_VIEW
+    basic_string_view<EcharT> part(&x, 1);
+#else
+    std::basic_string<EcharT> part(1, x);
+#endif
+    concat(part);
+    return *this;
+}
+
+template <class Source>
+inline path& path::concat(const Source& x)
+{
+    path p(x);
+    _path += p._path;
+    postprocess_path_with_format(native_format);
+    return *this;
+}
+template <class InputIterator>
+inline path& path::concat(InputIterator first, InputIterator last)
+{
+    _path.append(first, last);
+    postprocess_path_with_format(native_format);
+    return *this;
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+//-----------------------------------------------------------------------------
+// [fs.path.modifiers] modifiers
+GHC_INLINE void path::clear() noexcept
+{
+    _path.clear();
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    _prefixLength = 0;
+#endif
+}
+
+GHC_INLINE path& path::make_preferred()
+{
+    // as this filesystem implementation only uses generic_format
+    // internally, this must be a no-op
+    return *this;
+}
+
+GHC_INLINE path& path::remove_filename()
+{
+    if (has_filename()) {
+        _path.erase(_path.size() - filename()._path.size());
+    }
+    return *this;
+}
+
+GHC_INLINE path& path::replace_filename(const path& replacement)
+{
+    remove_filename();
+    return append(replacement);
+}
+
+GHC_INLINE path& path::replace_extension(const path& replacement)
+{
+    if (has_extension()) {
+        _path.erase(_path.size() - extension()._path.size());
+    }
+    if (!replacement.empty() && replacement._path[0] != '.') {
+        _path += '.';
+    }
+    return concat(replacement);
+}
+
+GHC_INLINE void path::swap(path& rhs) noexcept
+{
+    _path.swap(rhs._path);
+#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    std::swap(_prefixLength, rhs._prefixLength);
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// [fs.path.native.obs] native format observers
+GHC_INLINE const path::string_type& path::native() const noexcept
+{
+    return _path;
+}
+
+GHC_INLINE const path::value_type* path::c_str() const noexcept
+{
+    return native().c_str();
+}
+
+GHC_INLINE path::operator path::string_type() const
+{
+    return native();
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+template <class EcharT, class traits, class Allocator>
+inline std::basic_string<EcharT, traits, Allocator> path::string(const Allocator& a) const
+{
+#ifdef GHC_USE_WCHAR_T
+    return detail::fromWChar<std::basic_string<EcharT, traits, Allocator>>(_path, a);
+#else
+    return detail::fromUtf8<std::basic_string<EcharT, traits, Allocator>>(_path, a);
+#endif
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+GHC_INLINE std::string path::string() const
+{
+#ifdef GHC_USE_WCHAR_T
+    return detail::toUtf8(native());
+#else
+    return native();
+#endif
+}
+
+GHC_INLINE std::wstring path::wstring() const
+{
+#ifdef GHC_USE_WCHAR_T
+    return native();
+#else
+    return detail::fromUtf8<std::wstring>(native());
+#endif
+}
+
+#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API)
+GHC_INLINE std::u8string path::u8string() const
+{
+#ifdef GHC_USE_WCHAR_T
+    return std::u8string(reinterpret_cast<const char8_t*>(detail::toUtf8(native()).c_str()));
+#else
+    return std::u8string(reinterpret_cast<const char8_t*>(c_str()));
+#endif
+}
+#else
+GHC_INLINE std::string path::u8string() const
+{
+#ifdef GHC_USE_WCHAR_T
+    return detail::toUtf8(native());
+#else
+    return native();
+#endif
+}
+#endif
+
+GHC_INLINE std::u16string path::u16string() const
+{
+    // TODO: optimize
+    return detail::fromUtf8<std::u16string>(string());
+}
+
+GHC_INLINE std::u32string path::u32string() const
+{
+    // TODO: optimize
+    return detail::fromUtf8<std::u32string>(string());
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+//-----------------------------------------------------------------------------
+// [fs.path.generic.obs] generic format observers
+template <class EcharT, class traits, class Allocator>
+inline std::basic_string<EcharT, traits, Allocator> path::generic_string(const Allocator& a) const
+{
+#ifdef GHC_OS_WINDOWS
+#ifdef GHC_USE_WCHAR_T
+    auto result = detail::fromWChar<std::basic_string<EcharT, traits, Allocator>, path::string_type>(_path, a);
+#else
+    auto result = detail::fromUtf8<std::basic_string<EcharT, traits, Allocator>>(_path, a);
+#endif
+    for (auto& c : result) {
+        if (c == preferred_separator) {
+            c = generic_separator;
+        }
+    }
+    return result;
+#else
+    return detail::fromUtf8<std::basic_string<EcharT, traits, Allocator>>(_path, a);
+#endif
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+GHC_INLINE std::string path::generic_string() const
+{
+#ifdef GHC_OS_WINDOWS
+    return generic_string<std::string::value_type, std::string::traits_type, std::string::allocator_type>();
+#else
+    return _path;
+#endif
+}
+
+GHC_INLINE std::wstring path::generic_wstring() const
+{
+#ifdef GHC_OS_WINDOWS
+    return generic_string<std::wstring::value_type, std::wstring::traits_type, std::wstring::allocator_type>();
+#else
+    return detail::fromUtf8<std::wstring>(_path);
+#endif
+}  // namespace filesystem
+
+#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API)
+GHC_INLINE std::u8string path::generic_u8string() const
+{
+#ifdef GHC_OS_WINDOWS
+    return generic_string<std::u8string::value_type, std::u8string::traits_type, std::u8string::allocator_type>();
+#else
+    return std::u8string(reinterpret_cast<const char8_t*>(_path.c_str()));
+#endif
+}
+#else
+GHC_INLINE std::string path::generic_u8string() const
+{
+#ifdef GHC_OS_WINDOWS
+    return generic_string<std::string::value_type, std::string::traits_type, std::string::allocator_type>();
+#else
+    return _path;
+#endif
+}
+#endif
+
+GHC_INLINE std::u16string path::generic_u16string() const
+{
+#ifdef GHC_OS_WINDOWS
+    return generic_string<std::u16string::value_type, std::u16string::traits_type, std::u16string::allocator_type>();
+#else
+    return detail::fromUtf8<std::u16string>(_path);
+#endif
+}
+
+GHC_INLINE std::u32string path::generic_u32string() const
+{
+#ifdef GHC_OS_WINDOWS
+    return generic_string<std::u32string::value_type, std::u32string::traits_type, std::u32string::allocator_type>();
+#else
+    return detail::fromUtf8<std::u32string>(_path);
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// [fs.path.compare] compare
+GHC_INLINE int path::compare(const path& p) const noexcept
+{
+#ifdef LWG_2936_BEHAVIOUR
+    auto rnl1 = root_name_length();
+    auto rnl2 = p.root_name_length();
+#ifdef GHC_OS_WINDOWS
+    auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2);
+#else
+    auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2)));
+#endif
+    if (rnc) {
+        return rnc;
+    }
+    bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory();
+    if (hrd1 != hrd2) {
+        return hrd1 ? 1 : -1;
+    }
+    if (hrd1) {
+        ++rnl1;
+        ++rnl2;
+    }
+    auto iter1 = _path.begin() + static_cast<int>(rnl1);
+    auto iter2 = p._path.begin() + static_cast<int>(rnl2);
+    while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) {
+        ++iter1;
+        ++iter2;
+    }
+    if (iter1 == _path.end()) {
+        return iter2 == p._path.end() ? 0 : -1;
+    }
+    if (iter2 == p._path.end()) {
+        return 1;
+    }
+    if (*iter1 == preferred_separator) {
+        return -1;
+    }
+    if (*iter2 == preferred_separator) {
+        return 1;
+    }
+    return *iter1 < *iter2 ? -1 : 1;
+#else  // LWG_2936_BEHAVIOUR
+#ifdef GHC_OS_WINDOWS
+    auto rnl1 = root_name_length();
+    auto rnl2 = p.root_name_length();
+    auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2);
+    if (rnc) {
+        return rnc;
+    }
+    return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos);
+#else
+    return _path.compare(p._path);
+#endif
+#endif
+}
+
+GHC_INLINE int path::compare(const string_type& s) const
+{
+    return compare(path(s));
+}
+
+#ifdef GHC_WITH_STRING_VIEW
+GHC_INLINE int path::compare(basic_string_view<value_type> s) const
+{
+    return compare(path(s));
+}
+#endif
+
+GHC_INLINE int path::compare(const value_type* s) const
+{
+    return compare(path(s));
+}
+
+//-----------------------------------------------------------------------------
+// [fs.path.decompose] decomposition
+#ifdef GHC_OS_WINDOWS
+GHC_INLINE void path::handle_prefixes()
+{
+#if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH)
+    _prefixLength = 0;
+    if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast<unsigned char>(_path[4])) >= 'A' && std::toupper(static_cast<unsigned char>(_path[4])) <= 'Z' && _path[5] == ':') {
+        if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) {
+            _prefixLength = 4;
+        }
+    }
+#endif  // GHC_WIN_AUTO_PREFIX_LONG_PATH
+}
+#endif
+
+GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept
+{
+#ifdef GHC_OS_WINDOWS
+    if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast<unsigned char>(_path[_prefixLength])) >= 'A' && std::toupper(static_cast<unsigned char>(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') {
+        return 2;
+    }
+#endif
+    if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) {
+        impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3);
+        if (pos == impl_string_type::npos) {
+            return _path.length();
+        }
+        else {
+            return pos;
+        }
+    }
+    return 0;
+}
+
+GHC_INLINE path path::root_name() const
+{
+    return path(_path.substr(_prefixLength, root_name_length()), native_format);
+}
+
+GHC_INLINE path path::root_directory() const
+{
+    if (has_root_directory()) {
+        static const path _root_dir(std::string(1, preferred_separator), native_format);
+        return _root_dir;
+    }
+    return path();
+}
+
+GHC_INLINE path path::root_path() const
+{
+    return path(root_name().string() + root_directory().string(), native_format);
+}
+
+GHC_INLINE path path::relative_path() const
+{
+    auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0);
+    return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format);
+}
+
+GHC_INLINE path path::parent_path() const
+{
+    auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0);
+    if (rootPathLen < _path.length()) {
+        if (empty()) {
+            return path();
+        }
+        else {
+            auto piter = end();
+            auto iter = piter.decrement(_path.end());
+            if (iter > _path.begin() + static_cast<long>(rootPathLen) && *iter != preferred_separator) {
+                --iter;
+            }
+            return path(_path.begin(), iter, native_format);
+        }
+    }
+    else {
+        return *this;
+    }
+}
+
+GHC_INLINE path path::filename() const
+{
+    return !has_relative_path() ? path() : path(*--end());
+}
+
+GHC_INLINE path path::stem() const
+{
+    impl_string_type fn = filename().native();
+    if (fn != "." && fn != "..") {
+        impl_string_type::size_type pos = fn.rfind('.');
+        if (pos != impl_string_type::npos && pos > 0) {
+            return path{fn.substr(0, pos), native_format};
+        }
+    }
+    return path{fn, native_format};
+}
+
+GHC_INLINE path path::extension() const
+{
+    if (has_relative_path()) {
+        auto iter = end();
+        const auto& fn = *--iter;
+        impl_string_type::size_type pos = fn._path.rfind('.');
+        if (pos != std::string::npos && pos > 0) {
+            return path(fn._path.substr(pos), native_format);
+        }
+    }
+    return path();
+}
+
+#ifdef GHC_OS_WINDOWS
+namespace detail {
+GHC_INLINE bool has_executable_extension(const path& p)
+{
+    if (p.has_relative_path()) {
+        auto iter = p.end();
+        const auto& fn = *--iter;
+        auto pos = fn._path.find_last_of('.');
+        if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) {
+            return false;
+        }
+        const path::value_type* ext = fn._path.c_str() + pos + 1;
+        if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) {
+            return true;
+        }
+    }
+    return false;
+}
+}  // namespace detail
+#endif
+
+//-----------------------------------------------------------------------------
+// [fs.path.query] query
+GHC_INLINE bool path::empty() const noexcept
+{
+    return _path.empty();
+}
+
+GHC_INLINE bool path::has_root_name() const
+{
+    return root_name_length() > 0;
+}
+
+GHC_INLINE bool path::has_root_directory() const
+{
+    auto rootLen = _prefixLength + root_name_length();
+    return (_path.length() > rootLen && _path[rootLen] == preferred_separator);
+}
+
+GHC_INLINE bool path::has_root_path() const
+{
+    return has_root_name() || has_root_directory();
+}
+
+GHC_INLINE bool path::has_relative_path() const
+{
+    auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0);
+    return rootPathLen < _path.length();
+}
+
+GHC_INLINE bool path::has_parent_path() const
+{
+    return !parent_path().empty();
+}
+
+GHC_INLINE bool path::has_filename() const
+{
+    return has_relative_path() && !filename().empty();
+}
+
+GHC_INLINE bool path::has_stem() const
+{
+    return !stem().empty();
+}
+
+GHC_INLINE bool path::has_extension() const
+{
+    return !extension().empty();
+}
+
+GHC_INLINE bool path::is_absolute() const
+{
+#ifdef GHC_OS_WINDOWS
+    return has_root_name() && has_root_directory();
+#else
+    return has_root_directory();
+#endif
+}
+
+GHC_INLINE bool path::is_relative() const
+{
+    return !is_absolute();
+}
+
+//-----------------------------------------------------------------------------
+// [fs.path.gen] generation
+GHC_INLINE path path::lexically_normal() const
+{
+    path dest;
+    bool lastDotDot = false;
+    for (string_type s : *this) {
+        if (s == ".") {
+            dest /= "";
+            continue;
+        }
+        else if (s == ".." && !dest.empty()) {
+            auto root = root_path();
+            if (dest == root) {
+                continue;
+            }
+            else if (*(--dest.end()) != "..") {
+                if (dest._path.back() == preferred_separator) {
+                    dest._path.pop_back();
+                }
+                dest.remove_filename();
+                continue;
+            }
+        }
+        if (!(s.empty() && lastDotDot)) {
+            dest /= s;
+        }
+        lastDotDot = s == "..";
+    }
+    if (dest.empty()) {
+        dest = ".";
+    }
+    return dest;
+}
+
+GHC_INLINE path path::lexically_relative(const path& base) const
+{
+    if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) {
+        return path();
+    }
+    const_iterator a = begin(), b = base.begin();
+    while (a != end() && b != base.end() && *a == *b) {
+        ++a;
+        ++b;
+    }
+    if (a == end() && b == base.end()) {
+        return path(".");
+    }
+    int count = 0;
+    for (const auto& element : input_iterator_range<const_iterator>(b, base.end())) {
+        if (element != "." && element != "" && element != "..") {
+            ++count;
+        }
+        else if (element == "..") {
+            --count;
+        }
+    }
+    if (count < 0) {
+        return path();
+    }
+    path result;
+    for (int i = 0; i < count; ++i) {
+        result /= "..";
+    }
+    for (const auto& element : input_iterator_range<const_iterator>(a, end())) {
+        result /= element;
+    }
+    return result;
+}
+
+GHC_INLINE path path::lexically_proximate(const path& base) const
+{
+    path result = lexically_relative(base);
+    return result.empty() ? *this : result;
+}
+
+//-----------------------------------------------------------------------------
+// [fs.path.itr] iterators
+GHC_INLINE path::iterator::iterator() {}
+
+GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos)
+    : _first(p._path.begin())
+    , _last(p._path.end())
+    , _prefix(_first + static_cast<string_type::difference_type>(p._prefixLength))
+    , _root(p.has_root_directory() ? _first + static_cast<string_type::difference_type>(p._prefixLength + p.root_name_length()) : _last)
+    , _iter(pos)
+{
+    if(pos != _last) {
+        updateCurrent();
+    }
+}
+
+GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const
+{
+    path::impl_string_type::const_iterator i = pos;
+    bool fromStart = i == _first || i == _prefix;
+    if (i != _last) {
+        if (fromStart && i == _first && _prefix > _first) {
+            i = _prefix;
+        }
+        else if (*i++ == preferred_separator) {
+            // we can only sit on a slash if it is a network name or a root
+            if (i != _last && *i == preferred_separator) {
+                if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) {
+                    // leadind double slashes detected, treat this and the
+                    // following until a slash as one unit
+                    i = std::find(++i, _last, preferred_separator);
+                }
+                else {
+                    // skip redundant slashes
+                    while (i != _last && *i == preferred_separator) {
+                        ++i;
+                    }
+                }
+            }
+        }
+        else {
+            if (fromStart && i != _last && *i == ':') {
+                ++i;
+            }
+            else {
+                i = std::find(i, _last, preferred_separator);
+            }
+        }
+    }
+    return i;
+}
+
+GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const
+{
+    path::impl_string_type::const_iterator i = pos;
+    if (i != _first) {
+        --i;
+        // if this is now the root slash or the trailing slash, we are done,
+        // else check for network name
+        if (i != _root && (pos != _last || *i != preferred_separator)) {
+#ifdef GHC_OS_WINDOWS
+            static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:");
+            i = std::find_first_of(std::reverse_iterator<path::impl_string_type::const_iterator>(i), std::reverse_iterator<path::impl_string_type::const_iterator>(_first), seps.begin(), seps.end()).base();
+            if (i > _first && *i == ':') {
+                i++;
+            }
+#else
+            i = std::find(std::reverse_iterator<path::impl_string_type::const_iterator>(i), std::reverse_iterator<path::impl_string_type::const_iterator>(_first), preferred_separator).base();
+#endif
+            // Now we have to check if this is a network name
+            if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) {
+                i -= 2;
+            }
+        }
+    }
+    return i;
+}
+
+GHC_INLINE void path::iterator::updateCurrent()
+{
+    if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) {
+        _current.clear();
+    }
+    else {
+        _current.assign(_iter, increment(_iter));
+    }
+}
+
+GHC_INLINE path::iterator& path::iterator::operator++()
+{
+    _iter = increment(_iter);
+    while (_iter != _last &&                // we didn't reach the end
+           _iter != _root &&                // this is not a root position
+           *_iter == preferred_separator &&  // we are on a separator
+           (_iter + 1) != _last             // the slash is not the last char
+    ) {
+        ++_iter;
+    }
+    updateCurrent();
+    return *this;
+}
+
+GHC_INLINE path::iterator path::iterator::operator++(int)
+{
+    path::iterator i{*this};
+    ++(*this);
+    return i;
+}
+
+GHC_INLINE path::iterator& path::iterator::operator--()
+{
+    _iter = decrement(_iter);
+    updateCurrent();
+    return *this;
+}
+
+GHC_INLINE path::iterator path::iterator::operator--(int)
+{
+    auto i = *this;
+    --(*this);
+    return i;
+}
+
+GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const
+{
+    return _iter == other._iter;
+}
+
+GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const
+{
+    return _iter != other._iter;
+}
+
+GHC_INLINE path::iterator::reference path::iterator::operator*() const
+{
+    return _current;
+}
+
+GHC_INLINE path::iterator::pointer path::iterator::operator->() const
+{
+    return &_current;
+}
+
+GHC_INLINE path::iterator path::begin() const
+{
+    return iterator(*this, _path.begin());
+}
+
+GHC_INLINE path::iterator path::end() const
+{
+    return iterator(*this, _path.end());
+}
+
+//-----------------------------------------------------------------------------
+// [fs.path.nonmember] path non-member functions
+GHC_INLINE void swap(path& lhs, path& rhs) noexcept
+{
+    swap(lhs._path, rhs._path);
+}
+
+GHC_INLINE size_t hash_value(const path& p) noexcept
+{
+    return std::hash<std::string>()(p.generic_string());
+}
+
+#ifdef GHC_HAS_THREEWAY_COMP
+GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept
+{
+    return lhs.compare(rhs) <=> 0;
+}
+#endif
+
+GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept
+{
+    return lhs.compare(rhs) == 0;
+}
+
+GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept
+{
+    return !(lhs == rhs);
+}
+
+GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept
+{
+    return lhs.compare(rhs) < 0;
+}
+
+GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept
+{
+    return lhs.compare(rhs) <= 0;
+}
+
+GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept
+{
+    return lhs.compare(rhs) > 0;
+}
+
+GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept
+{
+    return lhs.compare(rhs) >= 0;
+}
+
+GHC_INLINE path operator/(const path& lhs, const path& rhs)
+{
+    path result(lhs);
+    result /= rhs;
+    return result;
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+//-----------------------------------------------------------------------------
+// [fs.path.io] path inserter and extractor
+template <class charT, class traits>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& os, const path& p)
+{
+    os << "\"";
+    auto ps = p.string<charT, traits>();
+    for (auto c : ps) {
+        if (c == '"' || c == '\\') {
+            os << '\\';
+        }
+        os << c;
+    }
+    os << "\"";
+    return os;
+}
+
+template <class charT, class traits>
+inline std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& is, path& p)
+{
+    std::basic_string<charT, traits> tmp;
+    charT c;
+    is >> c;
+    if (c == '"') {
+        auto sf = is.flags();
+        is >> std::noskipws;
+        while (is) {
+            auto c2 = is.get();
+            if (is) {
+                if (c2 == '\\') {
+                    c2 = is.get();
+                    if (is) {
+                        tmp += static_cast<charT>(c2);
+                    }
+                }
+                else if (c2 == '"') {
+                    break;
+                }
+                else {
+                    tmp += static_cast<charT>(c2);
+                }
+            }
+        }
+        if ((sf & std::ios_base::skipws) == std::ios_base::skipws) {
+            is >> std::skipws;
+        }
+        p = path(tmp);
+    }
+    else {
+        is >> tmp;
+        p = path(static_cast<charT>(c) + tmp);
+    }
+    return is;
+}
+
+#ifdef GHC_EXPAND_IMPL
+
+//-----------------------------------------------------------------------------
+// [fs.class.filesystem_error] Class filesystem_error
+GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec)
+    : std::system_error(ec, what_arg)
+    , _what_arg(what_arg)
+    , _ec(ec)
+{
+}
+
+GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec)
+    : std::system_error(ec, what_arg)
+    , _what_arg(what_arg)
+    , _ec(ec)
+    , _p1(p1)
+{
+    if (!_p1.empty()) {
+        _what_arg += ": '" + _p1.string() + "'";
+    }
+}
+
+GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec)
+    : std::system_error(ec, what_arg)
+    , _what_arg(what_arg)
+    , _ec(ec)
+    , _p1(p1)
+    , _p2(p2)
+{
+    if (!_p1.empty()) {
+        _what_arg += ": '" + _p1.string() + "'";
+    }
+    if (!_p2.empty()) {
+        _what_arg += ", '" + _p2.string() + "'";
+    }
+}
+
+GHC_INLINE const path& filesystem_error::path1() const noexcept
+{
+    return _p1;
+}
+
+GHC_INLINE const path& filesystem_error::path2() const noexcept
+{
+    return _p2;
+}
+
+GHC_INLINE const char* filesystem_error::what() const noexcept
+{
+    return _what_arg.c_str();
+}
+
+//-----------------------------------------------------------------------------
+// [fs.op.funcs] filesystem operations
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path absolute(const path& p)
+{
+    std::error_code ec;
+    path result = absolute(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE path absolute(const path& p, std::error_code& ec)
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    if (p.empty()) {
+        return absolute(current_path(ec), ec) / "";
+    }
+    ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0);
+    if (size) {
+        std::vector<wchar_t> buf(size, 0);
+        ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr);
+        if (s2 && s2 < size) {
+            path result = path(std::wstring(buf.data(), s2));
+            if (p.filename() == ".") {
+                result /= ".";
+            }
+            return result;
+        }
+    }
+    ec = detail::make_system_error();
+    return path();
+#else
+    path base = current_path(ec);
+    if (!ec) {
+        if (p.empty()) {
+            return base / p;
+        }
+        if (p.has_root_name()) {
+            if (p.has_root_directory()) {
+                return p;
+            }
+            else {
+                return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path();
+            }
+        }
+        else {
+            if (p.has_root_directory()) {
+                return base.root_name() / p;
+            }
+            else {
+                return base / p;
+            }
+        }
+    }
+    ec = detail::make_system_error();
+    return path();
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path canonical(const path& p)
+{
+    std::error_code ec;
+    auto result = canonical(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE path canonical(const path& p, std::error_code& ec)
+{
+    if (p.empty()) {
+        ec = detail::make_error_code(detail::portable_error::not_found);
+        return path();
+    }
+    path work = p.is_absolute() ? p : absolute(p, ec);
+    path result;
+
+    auto fs = status(work, ec);
+    if (ec) {
+        return path();
+    }
+    if (fs.type() == file_type::not_found) {
+        ec = detail::make_error_code(detail::portable_error::not_found);
+        return path();
+    }
+    bool redo;
+    do {
+        auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0);
+        redo = false;
+        result.clear();
+        for (auto pe : work) {
+            if (pe.empty() || pe == ".") {
+                continue;
+            }
+            else if (pe == "..") {
+                result = result.parent_path();
+                continue;
+            }
+            else if ((result / pe).string().length() <= rootPathLen) {
+                result /= pe;
+                continue;
+            }
+            auto sls = symlink_status(result / pe, ec);
+            if (ec) {
+                return path();
+            }
+            if (is_symlink(sls)) {
+                redo = true;
+                auto target = read_symlink(result / pe, ec);
+                if (ec) {
+                    return path();
+                }
+                if (target.is_absolute()) {
+                    result = target;
+                    continue;
+                }
+                else {
+                    result /= target;
+                    continue;
+                }
+            }
+            else {
+                result /= pe;
+            }
+        }
+        work = result;
+    } while (redo);
+    ec.clear();
+    return result;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void copy(const path& from, const path& to)
+{
+    copy(from, to, copy_options::none);
+}
+
+GHC_INLINE void copy(const path& from, const path& to, copy_options options)
+{
+    std::error_code ec;
+    copy(from, to, options, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec);
+    }
+}
+#endif
+
+GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept
+{
+    copy(from, to, copy_options::none, ec);
+}
+
+GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept
+{
+    std::error_code tec;
+    file_status fs_from, fs_to;
+    ec.clear();
+    if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) {
+        fs_from = symlink_status(from, ec);
+    }
+    else {
+        fs_from = status(from, ec);
+    }
+    if (!exists(fs_from)) {
+        if (!ec) {
+            ec = detail::make_error_code(detail::portable_error::not_found);
+        }
+        return;
+    }
+    if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) {
+        fs_to = symlink_status(to, tec);
+    }
+    else {
+        fs_to = status(to, tec);
+    }
+    if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) {
+        ec = detail::make_error_code(detail::portable_error::invalid_argument);
+    }
+    else if (is_symlink(fs_from)) {
+        if ((options & copy_options::skip_symlinks) == copy_options::none) {
+            if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) {
+                copy_symlink(from, to, ec);
+            }
+            else {
+                ec = detail::make_error_code(detail::portable_error::invalid_argument);
+            }
+        }
+    }
+    else if (is_regular_file(fs_from)) {
+        if ((options & copy_options::directories_only) == copy_options::none) {
+            if ((options & copy_options::create_symlinks) != copy_options::none) {
+                create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec);
+            }
+#ifndef GHC_OS_WEB
+            else if ((options & copy_options::create_hard_links) != copy_options::none) {
+                create_hard_link(from, to, ec);
+            }
+#endif
+            else if (is_directory(fs_to)) {
+                copy_file(from, to / from.filename(), options, ec);
+            }
+            else {
+                copy_file(from, to, options, ec);
+            }
+        }
+    }
+#ifdef LWG_2682_BEHAVIOUR
+    else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) {
+        ec = detail::make_error_code(detail::portable_error::is_a_directory);
+    }
+#endif
+    else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) {
+        if (!exists(fs_to)) {
+            create_directory(to, from, ec);
+            if (ec) {
+                return;
+            }
+        }
+        for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) {
+            if (!ec) {
+                copy(iter->path(), to / iter->path().filename(), options | static_cast<copy_options>(0x8000), ec);
+            }
+            if (ec) {
+                return;
+            }
+        }
+    }
+    return;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool copy_file(const path& from, const path& to)
+{
+    return copy_file(from, to, copy_options::none);
+}
+
+GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option)
+{
+    std::error_code ec;
+    auto result = copy_file(from, to, option, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept
+{
+    return copy_file(from, to, copy_options::none, ec);
+}
+
+GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept
+{
+    std::error_code tecf, tect;
+    auto sf = status(from, tecf);
+    auto st = status(to, tect);
+    bool overwrite = false;
+    ec.clear();
+    if (!is_regular_file(sf)) {
+        ec = tecf;
+        return false;
+    }
+    if (exists(st) && (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::skip_existing | copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none)) {
+        ec = tect ? tect : detail::make_error_code(detail::portable_error::exists);
+        return false;
+    }
+    if (exists(st)) {
+        if ((options & copy_options::update_existing) == copy_options::update_existing) {
+            auto from_time = last_write_time(from, ec);
+            if (ec) {
+                ec = detail::make_system_error();
+                return false;
+            }
+            auto to_time = last_write_time(to, ec);
+            if (ec) {
+                ec = detail::make_system_error();
+                return false;
+            }
+            if (from_time <= to_time) {
+                return false;
+            }
+        }
+        overwrite = true;
+    }
+#ifdef GHC_OS_WINDOWS
+    if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) {
+        ec = detail::make_system_error();
+        return false;
+    }
+    return true;
+#else
+    std::vector<char> buffer(16384, '\0');
+    int in = -1, out = -1;
+    if ((in = ::open(from.c_str(), O_RDONLY)) < 0) {
+        ec = detail::make_system_error();
+        return false;
+    }
+    int mode = O_CREAT | O_WRONLY | O_TRUNC;
+    if (!overwrite) {
+        mode |= O_EXCL;
+    }
+    if ((out = ::open(to.c_str(), mode, static_cast<int>(sf.permissions() & perms::all))) < 0) {
+        ec = detail::make_system_error();
+        ::close(in);
+        return false;
+    }
+    ssize_t br, bw;
+    while ((br = ::read(in, buffer.data(), buffer.size())) > 0) {
+        ssize_t offset = 0;
+        do {
+            if ((bw = ::write(out, buffer.data() + offset, static_cast<size_t>(br))) > 0) {
+                br -= bw;
+                offset += bw;
+            }
+            else if (bw < 0) {
+                ec = detail::make_system_error();
+                ::close(in);
+                ::close(out);
+                return false;
+            }
+        } while (br);
+    }
+    ::close(in);
+    ::close(out);
+    return true;
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink)
+{
+    std::error_code ec;
+    copy_symlink(existing_symlink, new_symlink, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec);
+    }
+}
+#endif
+
+GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept
+{
+    ec.clear();
+    auto to = read_symlink(existing_symlink, ec);
+    if (!ec) {
+        if (exists(to, ec) && is_directory(to, ec)) {
+            create_directory_symlink(to, new_symlink, ec);
+        }
+        else {
+            create_symlink(to, new_symlink, ec);
+        }
+    }
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool create_directories(const path& p)
+{
+    std::error_code ec;
+    auto result = create_directories(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept
+{
+    path current;
+    ec.clear();
+    bool didCreate = false;
+    for (path::string_type part : p) {
+        current /= part;
+        if (current != p.root_name() && current != p.root_path()) {
+            std::error_code tec;
+            auto fs = status(current, tec);
+            if (tec && fs.type() != file_type::not_found) {
+                ec = tec;
+                return false;
+            }
+            if (!exists(fs)) {
+                create_directory(current, ec);
+                if (ec) {
+                    std::error_code tmp_ec;
+                    if (is_directory(current, tmp_ec)) {
+                        ec.clear();
+                    }
+                    else {
+                        return false;
+                    }
+                }
+                didCreate = true;
+            }
+#ifndef LWG_2935_BEHAVIOUR
+            else if (!is_directory(fs)) {
+                ec = detail::make_error_code(detail::portable_error::exists);
+                return false;
+            }
+#endif
+        }
+    }
+    return didCreate;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool create_directory(const path& p)
+{
+    std::error_code ec;
+    auto result = create_directory(p, path(), ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept
+{
+    return create_directory(p, path(), ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool create_directory(const path& p, const path& attributes)
+{
+    std::error_code ec;
+    auto result = create_directory(p, attributes, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept
+{
+    std::error_code tec;
+    ec.clear();
+    auto fs = status(p, tec);
+#ifdef LWG_2935_BEHAVIOUR
+    if (status_known(fs) && exists(fs)) {
+        return false;
+    }
+#else
+    if (status_known(fs) && exists(fs) && is_directory(fs)) {
+        return false;
+    }
+#endif
+#ifdef GHC_OS_WINDOWS
+    if (!attributes.empty()) {
+        if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) {
+            ec = detail::make_system_error();
+            return false;
+        }
+    }
+    else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) {
+        ec = detail::make_system_error();
+        return false;
+    }
+#else
+    ::mode_t attribs = static_cast<mode_t>(perms::all);
+    if (!attributes.empty()) {
+        struct ::stat fileStat;
+        if (::stat(attributes.c_str(), &fileStat) != 0) {
+            ec = detail::make_system_error();
+            return false;
+        }
+        attribs = fileStat.st_mode;
+    }
+    if (::mkdir(p.c_str(), attribs) != 0) {
+        ec = detail::make_system_error();
+        return false;
+    }
+#endif
+    return true;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink)
+{
+    std::error_code ec;
+    create_directory_symlink(to, new_symlink, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec);
+    }
+}
+#endif
+
+GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept
+{
+    detail::create_symlink(to, new_symlink, true, ec);
+}
+
+#ifndef GHC_OS_WEB
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link)
+{
+    std::error_code ec;
+    create_hard_link(to, new_hard_link, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec);
+    }
+}
+#endif
+
+GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept
+{
+    detail::create_hardlink(to, new_hard_link, ec);
+}
+#endif
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void create_symlink(const path& to, const path& new_symlink)
+{
+    std::error_code ec;
+    create_symlink(to, new_symlink, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec);
+    }
+}
+#endif
+
+GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept
+{
+    detail::create_symlink(to, new_symlink, false, ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path current_path()
+{
+    std::error_code ec;
+    auto result = current_path(ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE path current_path(std::error_code& ec)
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    DWORD pathlen = ::GetCurrentDirectoryW(0, 0);
+    std::unique_ptr<wchar_t[]> buffer(new wchar_t[size_t(pathlen) + 1]);
+    if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) {
+        ec = detail::make_system_error();
+        return path();
+    }
+    return path(std::wstring(buffer.get()), path::native_format);
+#else
+    size_t pathlen = static_cast<size_t>(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX)));
+    std::unique_ptr<char[]> buffer(new char[pathlen + 1]);
+    if (::getcwd(buffer.get(), pathlen) == nullptr) {
+        ec = detail::make_system_error();
+        return path();
+    }
+    return path(buffer.get());
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void current_path(const path& p)
+{
+    std::error_code ec;
+    current_path(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+}
+#endif
+
+GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) {
+        ec = detail::make_system_error();
+    }
+#else
+    if (::chdir(p.string().c_str()) == -1) {
+        ec = detail::make_system_error();
+    }
+#endif
+}
+
+GHC_INLINE bool exists(file_status s) noexcept
+{
+    return status_known(s) && s.type() != file_type::not_found;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool exists(const path& p)
+{
+    return exists(status(p));
+}
+#endif
+
+GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept
+{
+    file_status s = status(p, ec);
+    if (status_known(s)) {
+        ec.clear();
+    }
+    return exists(s);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool equivalent(const path& p1, const path& p2)
+{
+    std::error_code ec;
+    bool result = equivalent(p1, p2, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    std::shared_ptr<void> file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
+    auto e1 = ::GetLastError();
+    std::shared_ptr<void> file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
+    if (file1.get() == INVALID_HANDLE_VALUE || file2.get() == INVALID_HANDLE_VALUE) {
+#ifdef LWG_2937_BEHAVIOUR
+        ec = detail::make_system_error(e1 ? e1 : ::GetLastError());
+#else
+        if (file1 == file2) {
+            ec = detail::make_system_error(e1 ? e1 : ::GetLastError());
+        }
+#endif
+        return false;
+    }
+    BY_HANDLE_FILE_INFORMATION inf1, inf2;
+    if (!::GetFileInformationByHandle(file1.get(), &inf1)) {
+        ec = detail::make_system_error();
+        return false;
+    }
+    if (!::GetFileInformationByHandle(file2.get(), &inf2)) {
+        ec = detail::make_system_error();
+        return false;
+    }
+    return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow &&
+           inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber;
+#else
+    struct ::stat s1, s2;
+    auto rc1 = ::stat(p1.c_str(), &s1);
+    auto e1 = errno;
+    auto rc2 = ::stat(p2.c_str(), &s2);
+    if (rc1 || rc2) {
+#ifdef LWG_2937_BEHAVIOUR
+        ec = detail::make_system_error(e1 ? e1 : errno);
+#else
+        if (rc1 && rc2) {
+            ec = detail::make_system_error(e1 ? e1 : errno);
+        }
+#endif
+        return false;
+    }
+    return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime;
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE uintmax_t file_size(const path& p)
+{
+    std::error_code ec;
+    auto result = file_size(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    WIN32_FILE_ATTRIBUTE_DATA attr;
+    if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) {
+        ec = detail::make_system_error();
+        return static_cast<uintmax_t>(-1);
+    }
+    return static_cast<uintmax_t>(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow;
+#else
+    struct ::stat fileStat;
+    if (::stat(p.c_str(), &fileStat) == -1) {
+        ec = detail::make_system_error();
+        return static_cast<uintmax_t>(-1);
+    }
+    return static_cast<uintmax_t>(fileStat.st_size);
+#endif
+}
+
+#ifndef GHC_OS_WEB
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE uintmax_t hard_link_count(const path& p)
+{
+    std::error_code ec;
+    auto result = hard_link_count(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    uintmax_t result = static_cast<uintmax_t>(-1);
+    std::shared_ptr<void> file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
+    BY_HANDLE_FILE_INFORMATION inf;
+    if (file.get() == INVALID_HANDLE_VALUE) {
+        ec = detail::make_system_error();
+    }
+    else {
+        if (!::GetFileInformationByHandle(file.get(), &inf)) {
+            ec = detail::make_system_error();
+        }
+        else {
+            result = inf.nNumberOfLinks;
+        }
+    }
+    return result;
+#else
+    uintmax_t result = 0;
+    file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr);
+    if (fs.type() == file_type::not_found) {
+        ec = detail::make_error_code(detail::portable_error::not_found);
+    }
+    return ec ? static_cast<uintmax_t>(-1) : result;
+#endif
+}
+#endif
+
+GHC_INLINE bool is_block_file(file_status s) noexcept
+{
+    return s.type() == file_type::block;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_block_file(const path& p)
+{
+    return is_block_file(status(p));
+}
+#endif
+
+GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept
+{
+    return is_block_file(status(p, ec));
+}
+
+GHC_INLINE bool is_character_file(file_status s) noexcept
+{
+    return s.type() == file_type::character;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_character_file(const path& p)
+{
+    return is_character_file(status(p));
+}
+#endif
+
+GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept
+{
+    return is_character_file(status(p, ec));
+}
+
+GHC_INLINE bool is_directory(file_status s) noexcept
+{
+    return s.type() == file_type::directory;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_directory(const path& p)
+{
+    return is_directory(status(p));
+}
+#endif
+
+GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept
+{
+    return is_directory(status(p, ec));
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_empty(const path& p)
+{
+    if (is_directory(p)) {
+        return directory_iterator(p) == directory_iterator();
+    }
+    else {
+        return file_size(p) == 0;
+    }
+}
+#endif
+
+GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept
+{
+    auto fs = status(p, ec);
+    if (ec) {
+        return false;
+    }
+    if (is_directory(fs)) {
+        directory_iterator iter(p, ec);
+        if (ec) {
+            return false;
+        }
+        return iter == directory_iterator();
+    }
+    else {
+        auto sz = file_size(p, ec);
+        if (ec) {
+            return false;
+        }
+        return sz == 0;
+    }
+}
+
+GHC_INLINE bool is_fifo(file_status s) noexcept
+{
+    return s.type() == file_type::fifo;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_fifo(const path& p)
+{
+    return is_fifo(status(p));
+}
+#endif
+
+GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept
+{
+    return is_fifo(status(p, ec));
+}
+
+GHC_INLINE bool is_other(file_status s) noexcept
+{
+    return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_other(const path& p)
+{
+    return is_other(status(p));
+}
+#endif
+
+GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept
+{
+    return is_other(status(p, ec));
+}
+
+GHC_INLINE bool is_regular_file(file_status s) noexcept
+{
+    return s.type() == file_type::regular;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_regular_file(const path& p)
+{
+    return is_regular_file(status(p));
+}
+#endif
+
+GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept
+{
+    return is_regular_file(status(p, ec));
+}
+
+GHC_INLINE bool is_socket(file_status s) noexcept
+{
+    return s.type() == file_type::socket;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_socket(const path& p)
+{
+    return is_socket(status(p));
+}
+#endif
+
+GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept
+{
+    return is_socket(status(p, ec));
+}
+
+GHC_INLINE bool is_symlink(file_status s) noexcept
+{
+    return s.type() == file_type::symlink;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool is_symlink(const path& p)
+{
+    return is_symlink(symlink_status(p));
+}
+#endif
+
+GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept
+{
+    return is_symlink(symlink_status(p, ec));
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE file_time_type last_write_time(const path& p)
+{
+    std::error_code ec;
+    auto result = last_write_time(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept
+{
+    time_t result = 0;
+    ec.clear();
+    file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result);
+    return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void last_write_time(const path& p, file_time_type new_time)
+{
+    std::error_code ec;
+    last_write_time(p, new_time, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+}
+#endif
+
+GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept
+{
+    ec.clear();
+    auto d = new_time.time_since_epoch();
+#ifdef GHC_OS_WINDOWS
+    std::shared_ptr<void> file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL), ::CloseHandle);
+    FILETIME ft;
+    auto tt = std::chrono::duration_cast<std::chrono::microseconds>(d).count() * 10 + 116444736000000000;
+    ft.dwLowDateTime = static_cast<DWORD>(tt);
+    ft.dwHighDateTime = static_cast<DWORD>(tt >> 32);
+    if (!::SetFileTime(file.get(), 0, 0, &ft)) {
+        ec = detail::make_system_error();
+    }
+#elif defined(GHC_OS_MACOS)
+#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
+#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300
+    struct ::stat fs;
+    if (::stat(p.c_str(), &fs) == 0) {
+        struct ::timeval tv[2];
+        tv[0].tv_sec = fs.st_atimespec.tv_sec;
+        tv[0].tv_usec = static_cast<int>(fs.st_atimespec.tv_nsec / 1000);
+        tv[1].tv_sec = std::chrono::duration_cast<std::chrono::seconds>(d).count();
+        tv[1].tv_usec = static_cast<int>(std::chrono::duration_cast<std::chrono::microseconds>(d).count() % 1000000);
+        if (::utimes(p.c_str(), tv) == 0) {
+            return;
+        }
+    }
+    ec = detail::make_system_error();
+    return;
+#else
+    struct ::timespec times[2];
+    times[0].tv_sec = 0;
+    times[0].tv_nsec = UTIME_OMIT;
+    times[1].tv_sec = std::chrono::duration_cast<std::chrono::seconds>(d).count();
+    times[1].tv_nsec = 0;  // std::chrono::duration_cast<std::chrono::nanoseconds>(d).count() % 1000000000;
+    if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) {
+        ec = detail::make_system_error();
+    }
+    return;
+#endif
+#endif
+#else
+#ifndef UTIME_OMIT
+#define UTIME_OMIT ((1l << 30) - 2l)
+#endif
+    struct ::timespec times[2];
+    times[0].tv_sec = 0;
+    times[0].tv_nsec = UTIME_OMIT;
+    times[1].tv_sec = static_cast<decltype(times[1].tv_sec)>(std::chrono::duration_cast<std::chrono::seconds>(d).count());
+    times[1].tv_nsec = static_cast<decltype(times[1].tv_nsec)>(std::chrono::duration_cast<std::chrono::nanoseconds>(d).count() % 1000000000);
+#if defined(__ANDROID_API__) && __ANDROID_API__ < 12
+    if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) {
+#else
+    if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) {
+#endif
+        ec = detail::make_system_error();
+    }
+    return;
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void permissions(const path& p, perms prms, perm_options opts)
+{
+    std::error_code ec;
+    permissions(p, prms, opts, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+}
+#endif
+
+GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept
+{
+    permissions(p, prms, perm_options::replace, ec);
+}
+
+GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept
+{
+    if (static_cast<int>(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) {
+        ec = detail::make_error_code(detail::portable_error::invalid_argument);
+        return;
+    }
+    auto fs = symlink_status(p, ec);
+    if ((opts & perm_options::replace) != perm_options::replace) {
+        if ((opts & perm_options::add) == perm_options::add) {
+            prms = fs.permissions() | prms;
+        }
+        else {
+            prms = fs.permissions() & ~prms;
+        }
+    }
+#ifdef GHC_OS_WINDOWS
+#ifdef __GNUC__
+    auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p));
+    if (oldAttr != INVALID_FILE_ATTRIBUTES) {
+        DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast<DWORD>(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY;
+        if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) {
+            return;
+        }
+    }
+    ec = detail::make_system_error();
+#else
+    int mode = 0;
+    if ((prms & perms::owner_read) == perms::owner_read) {
+        mode |= _S_IREAD;
+    }
+    if ((prms & perms::owner_write) == perms::owner_write) {
+        mode |= _S_IWRITE;
+    }
+    if (::_wchmod(p.wstring().c_str(), mode) != 0) {
+        ec = detail::make_system_error();
+    }
+#endif
+#else
+    if ((opts & perm_options::nofollow) != perm_options::nofollow) {
+        if (::chmod(p.c_str(), static_cast<mode_t>(prms)) != 0) {
+            ec = detail::make_system_error();
+        }
+    }
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path proximate(const path& p, std::error_code& ec)
+{
+    auto cp = current_path(ec);
+    if (!ec) {
+        return proximate(p, cp, ec);
+    }
+    return path();
+}
+#endif
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path proximate(const path& p, const path& base)
+{
+    return weakly_canonical(p).lexically_proximate(weakly_canonical(base));
+}
+#endif
+
+GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec)
+{
+    return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec));
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path read_symlink(const path& p)
+{
+    std::error_code ec;
+    auto result = read_symlink(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE path read_symlink(const path& p, std::error_code& ec)
+{
+    file_status fs = symlink_status(p, ec);
+    if (fs.type() != file_type::symlink) {
+        ec = detail::make_error_code(detail::portable_error::invalid_argument);
+        return path();
+    }
+    auto result = detail::resolveSymlink(p, ec);
+    return ec ? path() : result;
+}
+
+GHC_INLINE path relative(const path& p, std::error_code& ec)
+{
+    return relative(p, current_path(ec), ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path relative(const path& p, const path& base)
+{
+    return weakly_canonical(p).lexically_relative(weakly_canonical(base));
+}
+#endif
+
+GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec)
+{
+    return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec));
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool remove(const path& p)
+{
+    std::error_code ec;
+    auto result = remove(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+#ifdef GHC_USE_WCHAR_T
+    auto cstr = p.c_str();
+#else
+    std::wstring np = detail::fromUtf8<std::wstring>(p.u8string());
+    auto cstr = np.c_str();
+#endif
+    DWORD attr = GetFileAttributesW(cstr);
+    if (attr == INVALID_FILE_ATTRIBUTES) {
+        auto error = ::GetLastError();
+        if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) {
+            return false;
+        }
+        ec = detail::make_system_error(error);
+    }
+    else if(attr & FILE_ATTRIBUTE_READONLY) {
+        auto new_attr = attr & ~static_cast<DWORD>(FILE_ATTRIBUTE_READONLY);
+        if(!SetFileAttributesW(cstr, new_attr)) {
+            auto error = ::GetLastError();
+            ec = detail::make_system_error(error);
+        }
+    }
+    if (!ec) {
+        if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+            if (!RemoveDirectoryW(cstr)) {
+                ec = detail::make_system_error();
+            }
+        }
+        else {
+            if (!DeleteFileW(cstr)) {
+                ec = detail::make_system_error();
+            }
+        }
+    }
+#else
+    if (::remove(p.c_str()) == -1) {
+        auto error = errno;
+        if (error == ENOENT) {
+            return false;
+        }
+        ec = detail::make_system_error();
+    }
+#endif
+    return ec ? false : true;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE uintmax_t remove_all(const path& p)
+{
+    std::error_code ec;
+    auto result = remove_all(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept
+{
+    ec.clear();
+    uintmax_t count = 0;
+    if (p == "/") {
+        ec = detail::make_error_code(detail::portable_error::not_supported);
+        return static_cast<uintmax_t>(-1);
+    }
+    std::error_code tec;
+    auto fs = status(p, tec);
+    if (exists(fs) && is_directory(fs)) {
+        for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) {
+            if (ec && !detail::is_not_found_error(ec)) {
+                break;
+            }
+            bool is_symlink_result = iter->is_symlink(ec);
+            if (ec)
+                return static_cast<uintmax_t>(-1);
+            if (!is_symlink_result && iter->is_directory(ec)) {
+                count += remove_all(iter->path(), ec);
+                if (ec) {
+                    return static_cast<uintmax_t>(-1);
+                }
+            }
+            else {
+                if (!ec) {
+                    remove(iter->path(), ec);
+                }
+                if (ec) {
+                    return static_cast<uintmax_t>(-1);
+                }
+                ++count;
+            }
+        }
+    }
+    if (!ec) {
+        if (remove(p, ec)) {
+            ++count;
+        }
+    }
+    if (ec) {
+        return static_cast<uintmax_t>(-1);
+    }
+    return count;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void rename(const path& from, const path& to)
+{
+    std::error_code ec;
+    rename(from, to, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec);
+    }
+}
+#endif
+
+GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    if (from != to) {
+        if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) {
+            ec = detail::make_system_error();
+        }
+    }
+#else
+    if (from != to) {
+        if (::rename(from.c_str(), to.c_str()) != 0) {
+            ec = detail::make_system_error();
+        }
+    }
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void resize_file(const path& p, uintmax_t size)
+{
+    std::error_code ec;
+    resize_file(p, size, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+}
+#endif
+
+GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    LARGE_INTEGER lisize;
+    lisize.QuadPart = static_cast<LONGLONG>(size);
+    if (lisize.QuadPart < 0) {
+#ifdef ERROR_FILE_TOO_LARGE
+        ec = detail::make_system_error(ERROR_FILE_TOO_LARGE);
+#else
+        ec = detail::make_system_error(223);
+#endif
+        return;
+    }
+    std::shared_ptr<void> file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL), CloseHandle);
+    if (file.get() == INVALID_HANDLE_VALUE) {
+        ec = detail::make_system_error();
+    }
+    else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) {
+        ec = detail::make_system_error();
+    }
+#else
+    if (::truncate(p.c_str(), static_cast<off_t>(size)) != 0) {
+        ec = detail::make_system_error();
+    }
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE space_info space(const path& p)
+{
+    std::error_code ec;
+    auto result = space(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }};
+    ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }};
+    ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }};
+    if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) {
+        ec = detail::make_system_error();
+        return {static_cast<uintmax_t>(-1), static_cast<uintmax_t>(-1), static_cast<uintmax_t>(-1)};
+    }
+    return {static_cast<uintmax_t>(totalNumberOfBytes.QuadPart), static_cast<uintmax_t>(totalNumberOfFreeBytes.QuadPart), static_cast<uintmax_t>(freeBytesAvailableToCaller.QuadPart)};
+#else
+    struct ::statvfs sfs;
+    if (::statvfs(p.c_str(), &sfs) != 0) {
+        ec = detail::make_system_error();
+        return {static_cast<uintmax_t>(-1), static_cast<uintmax_t>(-1), static_cast<uintmax_t>(-1)};
+    }
+    return {static_cast<uintmax_t>(sfs.f_blocks * sfs.f_frsize), static_cast<uintmax_t>(sfs.f_bfree * sfs.f_frsize), static_cast<uintmax_t>(sfs.f_bavail * sfs.f_frsize)};
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE file_status status(const path& p)
+{
+    std::error_code ec;
+    auto result = status(p, ec);
+    if (result.type() == file_type::none) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept
+{
+    return detail::status_ex(p, ec);
+}
+
+GHC_INLINE bool status_known(file_status s) noexcept
+{
+    return s.type() != file_type::none;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE file_status symlink_status(const path& p)
+{
+    std::error_code ec;
+    auto result = symlink_status(p, ec);
+    if (result.type() == file_type::none) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept
+{
+    return detail::symlink_status_ex(p, ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path temp_directory_path()
+{
+    std::error_code ec;
+    path result = temp_directory_path(ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept
+{
+    ec.clear();
+#ifdef GHC_OS_WINDOWS
+    wchar_t buffer[512];
+    auto rc = GetTempPathW(511, buffer);
+    if (!rc || rc > 511) {
+        ec = detail::make_system_error();
+        return path();
+    }
+    return path(std::wstring(buffer));
+#else
+    static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr};
+    const char* temp_path = nullptr;
+    for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) {
+        temp_path = std::getenv(*temp_name);
+        if (temp_path) {
+            return path(temp_path);
+        }
+    }
+    return path("/tmp");
+#endif
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE path weakly_canonical(const path& p)
+{
+    std::error_code ec;
+    auto result = weakly_canonical(p, ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), p, ec);
+    }
+    return result;
+}
+#endif
+
+GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept
+{
+    path result;
+    ec.clear();
+    bool scan = true;
+    for (auto pe : p) {
+        if (scan) {
+            std::error_code tec;
+            if (exists(result / pe, tec)) {
+                result /= pe;
+            }
+            else {
+                if (ec) {
+                    return path();
+                }
+                scan = false;
+                if (!result.empty()) {
+                    result = canonical(result, ec) / pe;
+                    if (ec) {
+                        break;
+                    }
+                }
+                else {
+                    result /= pe;
+                }
+            }
+        }
+        else {
+            result /= pe;
+        }
+    }
+    if (scan) {
+        if (!result.empty()) {
+            result = canonical(result, ec);
+        }
+    }
+    return ec ? path() : result.lexically_normal();
+}
+
+//-----------------------------------------------------------------------------
+// [fs.class.file_status] class file_status
+// [fs.file_status.cons] constructors and destructor
+GHC_INLINE file_status::file_status() noexcept
+    : file_status(file_type::none)
+{
+}
+
+GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept
+    : _type(ft)
+    , _perms(prms)
+{
+}
+
+GHC_INLINE file_status::file_status(const file_status& other) noexcept
+    : _type(other._type)
+    , _perms(other._perms)
+{
+}
+
+GHC_INLINE file_status::file_status(file_status&& other) noexcept
+    : _type(other._type)
+    , _perms(other._perms)
+{
+}
+
+GHC_INLINE file_status::~file_status() {}
+
+// assignments:
+GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept
+{
+    _type = rhs._type;
+    _perms = rhs._perms;
+    return *this;
+}
+
+GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept
+{
+    _type = rhs._type;
+    _perms = rhs._perms;
+    return *this;
+}
+
+// [fs.file_status.mods] modifiers
+GHC_INLINE void file_status::type(file_type ft) noexcept
+{
+    _type = ft;
+}
+
+GHC_INLINE void file_status::permissions(perms prms) noexcept
+{
+    _perms = prms;
+}
+
+// [fs.file_status.obs] observers
+GHC_INLINE file_type file_status::type() const noexcept
+{
+    return _type;
+}
+
+GHC_INLINE perms file_status::permissions() const noexcept
+{
+    return _perms;
+}
+
+//-----------------------------------------------------------------------------
+// [fs.class.directory_entry] class directory_entry
+// [fs.dir.entry.cons] constructors and destructor
+// directory_entry::directory_entry() noexcept = default;
+// directory_entry::directory_entry(const directory_entry&) = default;
+// directory_entry::directory_entry(directory_entry&&) noexcept = default;
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE directory_entry::directory_entry(const filesystem::path& p)
+    : _path(p)
+    , _file_size(static_cast<uintmax_t>(-1))
+#ifndef GHC_OS_WINDOWS
+    , _hard_link_count(static_cast<uintmax_t>(-1))
+#endif
+    , _last_write_time(0)
+{
+    refresh();
+}
+#endif
+
+GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec)
+    : _path(p)
+    , _file_size(static_cast<uintmax_t>(-1))
+#ifndef GHC_OS_WINDOWS
+    , _hard_link_count(static_cast<uintmax_t>(-1))
+#endif
+    , _last_write_time(0)
+{
+    refresh(ec);
+}
+
+GHC_INLINE directory_entry::~directory_entry() {}
+
+// assignments:
+// directory_entry& directory_entry::operator=(const directory_entry&) = default;
+// directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default;
+
+// [fs.dir.entry.mods] directory_entry modifiers
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void directory_entry::assign(const filesystem::path& p)
+{
+    _path = p;
+    refresh();
+}
+#endif
+
+GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec)
+{
+    _path = p;
+    refresh(ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p)
+{
+    _path.replace_filename(p);
+    refresh();
+}
+#endif
+
+GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec)
+{
+    _path.replace_filename(p);
+    refresh(ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void directory_entry::refresh()
+{
+    std::error_code ec;
+    refresh(ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec);
+    }
+}
+#endif
+
+GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept
+{
+#ifdef GHC_OS_WINDOWS
+    _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time);
+#else
+    _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time);
+#endif
+}
+
+// [fs.dir.entry.obs] directory_entry observers
+GHC_INLINE const filesystem::path& directory_entry::path() const noexcept
+{
+    return _path;
+}
+
+GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept
+{
+    return _path;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE file_type directory_entry::status_file_type() const
+{
+    return _status.type() != file_type::none ? _status.type() : filesystem::status(path()).type();
+}
+#endif
+
+GHC_INLINE file_type directory_entry::status_file_type(std::error_code& ec) const noexcept
+{
+    if(_status.type() != file_type::none) {
+        ec.clear();
+        return _status.type();
+    }
+    return filesystem::status(path(), ec).type();
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::exists() const
+{
+    return status_file_type() != file_type::not_found;
+}
+#endif
+
+GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept
+{
+    return status_file_type(ec) != file_type::not_found;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_block_file() const
+{
+    return status_file_type() == file_type::block;
+}
+#endif
+GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept
+{
+    return status_file_type(ec) == file_type::block;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_character_file() const
+{
+    return status_file_type() == file_type::character;
+}
+#endif
+
+GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept
+{
+    return status_file_type(ec) == file_type::character;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_directory() const
+{
+    return status_file_type() == file_type::directory;
+}
+#endif
+
+GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept
+{
+    return status_file_type(ec) == file_type::directory;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_fifo() const
+{
+    return status_file_type() == file_type::fifo;
+}
+#endif
+
+GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept
+{
+    return status_file_type(ec) == file_type::fifo;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_other() const
+{
+    auto ft = status_file_type();
+    return ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink();
+}
+#endif
+
+GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept
+{
+    auto ft = status_file_type(ec);
+    bool other = ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(ec);
+    return !ec && other;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_regular_file() const
+{
+    return status_file_type() == file_type::regular;
+}
+#endif
+
+GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept
+{
+    return status_file_type(ec) == file_type::regular;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_socket() const
+{
+    return status_file_type() == file_type::socket;
+}
+#endif
+
+GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept
+{
+    return status_file_type(ec) == file_type::socket;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE bool directory_entry::is_symlink() const
+{
+    return _symlink_status.type() != file_type::none ? _symlink_status.type() == file_type::symlink : filesystem::is_symlink(symlink_status());
+}
+#endif
+
+GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept
+{
+    if(_symlink_status.type() != file_type::none) {
+        ec.clear();
+        return _symlink_status.type() == file_type::symlink;
+    }
+    return filesystem::is_symlink(symlink_status(ec));
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE uintmax_t directory_entry::file_size() const
+{
+    if (_file_size != static_cast<uintmax_t>(-1)) {
+        return _file_size;
+    }
+    return filesystem::file_size(path());
+}
+#endif
+
+GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept
+{
+    if (_file_size != static_cast<uintmax_t>(-1)) {
+        ec.clear();
+        return _file_size;
+    }
+    return filesystem::file_size(path(), ec);
+}
+
+#ifndef GHC_OS_WEB
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE uintmax_t directory_entry::hard_link_count() const
+{
+#ifndef GHC_OS_WINDOWS
+    if (_hard_link_count != static_cast<uintmax_t>(-1)) {
+        return _hard_link_count;
+    }
+#endif
+    return filesystem::hard_link_count(path());
+}
+#endif
+
+GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept
+{
+#ifndef GHC_OS_WINDOWS
+    if (_hard_link_count != static_cast<uintmax_t>(-1)) {
+        ec.clear();
+        return _hard_link_count;
+    }
+#endif
+    return filesystem::hard_link_count(path(), ec);
+}
+#endif
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE file_time_type directory_entry::last_write_time() const
+{
+    if (_last_write_time != 0) {
+        return std::chrono::system_clock::from_time_t(_last_write_time);
+    }
+    return filesystem::last_write_time(path());
+}
+#endif
+
+GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept
+{
+    if (_last_write_time != 0) {
+        ec.clear();
+        return std::chrono::system_clock::from_time_t(_last_write_time);
+    }
+    return filesystem::last_write_time(path(), ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE file_status directory_entry::status() const
+{
+    if (_status.type() != file_type::none && _status.permissions() != perms::unknown) {
+        return _status;
+    }
+    return filesystem::status(path());
+}
+#endif
+
+GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept
+{
+    if (_status.type() != file_type::none && _status.permissions() != perms::unknown) {
+        ec.clear();
+        return _status;
+    }
+    return filesystem::status(path(), ec);
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE file_status directory_entry::symlink_status() const
+{
+    if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) {
+        return _symlink_status;
+    }
+    return filesystem::symlink_status(path());
+}
+#endif
+
+GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept
+{
+    if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) {
+        ec.clear();
+        return _symlink_status;
+    }
+    return filesystem::symlink_status(path(), ec);
+}
+
+#ifdef GHC_HAS_THREEWAY_COMP
+GHC_INLINE std::strong_ordering directory_entry::operator<=>(const directory_entry& rhs) const noexcept
+{
+    return _path <=> rhs._path;
+}
+#endif
+
+GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept
+{
+    return _path < rhs._path;
+}
+
+GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept
+{
+    return _path == rhs._path;
+}
+
+GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept
+{
+    return _path != rhs._path;
+}
+
+GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept
+{
+    return _path <= rhs._path;
+}
+
+GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept
+{
+    return _path > rhs._path;
+}
+
+GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept
+{
+    return _path >= rhs._path;
+}
+
+//-----------------------------------------------------------------------------
+// [fs.class.directory_iterator] class directory_iterator
+
+#ifdef GHC_OS_WINDOWS
+class directory_iterator::impl
+{
+public:
+    impl(const path& p, directory_options options)
+        : _base(p)
+        , _options(options)
+        , _dirHandle(INVALID_HANDLE_VALUE)
+    {
+        if (!_base.empty()) {
+            ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW));
+            if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) {
+                if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") {
+                    increment(_ec);
+                }
+                else {
+                    _dir_entry._path = _base / std::wstring(_findData.cFileName);
+                    copyToDirEntry(_ec);
+                }
+            }
+            else {
+                auto error = ::GetLastError();
+                _base = filesystem::path();
+                if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) {
+                    _ec = detail::make_system_error();
+                }
+            }
+        }
+    }
+    impl(const impl& other) = delete;
+    ~impl()
+    {
+        if (_dirHandle != INVALID_HANDLE_VALUE) {
+            FindClose(_dirHandle);
+            _dirHandle = INVALID_HANDLE_VALUE;
+        }
+    }
+    void increment(std::error_code& ec)
+    {
+        if (_dirHandle != INVALID_HANDLE_VALUE) {
+            do {
+                if (FindNextFileW(_dirHandle, &_findData)) {
+                    _dir_entry._path = _base;
+#ifdef GHC_USE_WCHAR_T
+                    _dir_entry._path.append_name(_findData.cFileName);
+#else
+#ifdef GHC_RAISE_UNICODE_ERRORS
+                    try {
+                        _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str());
+                    }
+                    catch (filesystem_error& fe) {
+                        ec = fe.code();
+                        return;
+                    }
+#else
+                    _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str());
+#endif
+#endif
+                    copyToDirEntry(ec);
+                }
+                else {
+                    auto err = ::GetLastError();
+                    if (err != ERROR_NO_MORE_FILES) {
+                        _ec = ec = detail::make_system_error(err);
+                    }
+                    FindClose(_dirHandle);
+                    _dirHandle = INVALID_HANDLE_VALUE;
+                    _dir_entry._path.clear();
+                    break;
+                }
+            } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..");
+        }
+        else {
+            ec = _ec;
+        }
+    }
+    void copyToDirEntry(std::error_code& ec)
+    {
+        if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+            _dir_entry._status = detail::status_ex(_dir_entry._path, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time);
+        }
+        else {
+            _dir_entry._status = detail::status_from_INFO(_dir_entry._path, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time);
+            _dir_entry._symlink_status = _dir_entry._status;
+        }
+        if (ec) {
+            if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) {
+                ec.clear();
+            }
+            else {
+                _dir_entry._file_size = static_cast<uintmax_t>(-1);
+                _dir_entry._last_write_time = 0;
+            }
+        }
+    }
+    path _base;
+    directory_options _options;
+    WIN32_FIND_DATAW _findData;
+    HANDLE _dirHandle;
+    directory_entry _dir_entry;
+    std::error_code _ec;
+};
+#else
+// POSIX implementation
+class directory_iterator::impl
+{
+public:
+    impl(const path& path, directory_options options)
+        : _base(path)
+        , _options(options)
+        , _dir(nullptr)
+        , _entry(nullptr)
+    {
+        if (!path.empty()) {
+            _dir = ::opendir(path.native().c_str());
+            if (!_dir) {
+                auto error = errno;
+                _base = filesystem::path();
+                if ((error != EACCES && error != EPERM) || (options & directory_options::skip_permission_denied) == directory_options::none) {
+                    _ec = detail::make_system_error();
+                }
+            }
+            else {
+                increment(_ec);
+            }
+        }
+    }
+    impl(const impl& other) = delete;
+    ~impl()
+    {
+        if (_dir) {
+            ::closedir(_dir);
+        }
+    }
+    void increment(std::error_code& ec)
+    {
+        if (_dir) {
+            bool skip;
+            do {
+                skip = false;
+                errno = 0;
+                _entry = ::readdir(_dir);
+                if (_entry) {
+                    _dir_entry._path = _base;
+                    _dir_entry._path.append_name(_entry->d_name);
+                    copyToDirEntry();
+                    if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) {
+                        ec.clear();
+                        skip = true;
+                    }
+                }
+                else {
+                    ::closedir(_dir);
+                    _dir = nullptr;
+                    _dir_entry._path.clear();
+                    if (errno) {
+                        ec = detail::make_system_error();
+                    }
+                    break;
+                }
+            } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0);
+        }
+    }
+
+    void copyToDirEntry()
+    {
+#ifdef GHC_NO_DIRENT_D_TYPE
+        _dir_entry._symlink_status = file_status();
+        _dir_entry._status = file_status();
+#else
+        _dir_entry._symlink_status.permissions(perms::unknown);
+        switch(_entry->d_type) {
+            case DT_BLK: _dir_entry._symlink_status.type(file_type::block); break;
+            case DT_CHR: _dir_entry._symlink_status.type(file_type::character); break;
+            case DT_DIR: _dir_entry._symlink_status.type(file_type::directory); break;
+            case DT_FIFO: _dir_entry._symlink_status.type(file_type::fifo); break;
+            case DT_LNK: _dir_entry._symlink_status.type(file_type::symlink); break;
+            case DT_REG: _dir_entry._symlink_status.type(file_type::regular); break;
+            case DT_SOCK: _dir_entry._symlink_status.type(file_type::socket); break;
+            case DT_UNKNOWN: _dir_entry._symlink_status.type(file_type::none); break;
+            default: _dir_entry._symlink_status.type(file_type::unknown); break;
+        }
+        if (_entry->d_type != DT_LNK) {
+            _dir_entry._status = _dir_entry._symlink_status;
+        }
+        else {
+            _dir_entry._status.type(file_type::none);
+            _dir_entry._status.permissions(perms::unknown);
+        }
+#endif
+        _dir_entry._file_size = static_cast<uintmax_t>(-1);
+        _dir_entry._hard_link_count = static_cast<uintmax_t>(-1);
+        _dir_entry._last_write_time = 0;
+    }
+    path _base;
+    directory_options _options;
+    DIR* _dir;
+    struct ::dirent* _entry;
+    directory_entry _dir_entry;
+    std::error_code _ec;
+};
+#endif
+
+// [fs.dir.itr.members] member functions
+GHC_INLINE directory_iterator::directory_iterator() noexcept
+    : _impl(new impl(path(), directory_options::none))
+{
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE directory_iterator::directory_iterator(const path& p)
+    : _impl(new impl(p, directory_options::none))
+{
+    if (_impl->_ec) {
+        throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec);
+    }
+    _impl->_ec.clear();
+}
+
+GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options)
+    : _impl(new impl(p, options))
+{
+    if (_impl->_ec) {
+        throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec);
+    }
+}
+#endif
+
+GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept
+    : _impl(new impl(p, directory_options::none))
+{
+    if (_impl->_ec) {
+        ec = _impl->_ec;
+    }
+}
+
+GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept
+    : _impl(new impl(p, options))
+{
+    if (_impl->_ec) {
+        ec = _impl->_ec;
+    }
+}
+
+GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs)
+    : _impl(rhs._impl)
+{
+}
+
+GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept
+    : _impl(std::move(rhs._impl))
+{
+}
+
+GHC_INLINE directory_iterator::~directory_iterator() {}
+
+GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs)
+{
+    _impl = rhs._impl;
+    return *this;
+}
+
+GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept
+{
+    _impl = std::move(rhs._impl);
+    return *this;
+}
+
+GHC_INLINE const directory_entry& directory_iterator::operator*() const
+{
+    return _impl->_dir_entry;
+}
+
+GHC_INLINE const directory_entry* directory_iterator::operator->() const
+{
+    return &_impl->_dir_entry;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE directory_iterator& directory_iterator::operator++()
+{
+    std::error_code ec;
+    _impl->increment(ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_entry._path, ec);
+    }
+    return *this;
+}
+#endif
+
+GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept
+{
+    _impl->increment(ec);
+    return *this;
+}
+
+GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const
+{
+    return _impl->_dir_entry._path == rhs._impl->_dir_entry._path;
+}
+
+GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const
+{
+    return _impl->_dir_entry._path != rhs._impl->_dir_entry._path;
+}
+
+// [fs.dir.itr.nonmembers] directory_iterator non-member functions
+
+GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept
+{
+    return iter;
+}
+
+GHC_INLINE directory_iterator end(const directory_iterator&) noexcept
+{
+    return directory_iterator();
+}
+
+//-----------------------------------------------------------------------------
+// [fs.class.rec.dir.itr] class recursive_directory_iterator
+
+GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept
+    : _impl(new recursive_directory_iterator_impl(directory_options::none, true))
+{
+    _impl->_dir_iter_stack.push(directory_iterator());
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p)
+    : _impl(new recursive_directory_iterator_impl(directory_options::none, true))
+{
+    _impl->_dir_iter_stack.push(directory_iterator(p));
+}
+
+GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options)
+    : _impl(new recursive_directory_iterator_impl(options, true))
+{
+    _impl->_dir_iter_stack.push(directory_iterator(p, options));
+}
+#endif
+
+GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept
+    : _impl(new recursive_directory_iterator_impl(options, true))
+{
+    _impl->_dir_iter_stack.push(directory_iterator(p, options, ec));
+}
+
+GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept
+    : _impl(new recursive_directory_iterator_impl(directory_options::none, true))
+{
+    _impl->_dir_iter_stack.push(directory_iterator(p, ec));
+}
+
+GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs)
+    : _impl(rhs._impl)
+{
+}
+
+GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept
+    : _impl(std::move(rhs._impl))
+{
+}
+
+GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {}
+
+// [fs.rec.dir.itr.members] observers
+GHC_INLINE directory_options recursive_directory_iterator::options() const
+{
+    return _impl->_options;
+}
+
+GHC_INLINE int recursive_directory_iterator::depth() const
+{
+    return static_cast<int>(_impl->_dir_iter_stack.size() - 1);
+}
+
+GHC_INLINE bool recursive_directory_iterator::recursion_pending() const
+{
+    return _impl->_recursion_pending;
+}
+
+GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const
+{
+    return *(_impl->_dir_iter_stack.top());
+}
+
+GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const
+{
+    return &(*(_impl->_dir_iter_stack.top()));
+}
+
+// [fs.rec.dir.itr.members] modifiers recursive_directory_iterator&
+GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs)
+{
+    _impl = rhs._impl;
+    return *this;
+}
+
+GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept
+{
+    _impl = std::move(rhs._impl);
+    return *this;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++()
+{
+    std::error_code ec;
+    increment(ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec);
+    }
+    return *this;
+}
+#endif
+
+GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept
+{
+    bool isSymLink = (*this)->is_symlink(ec);
+    bool isDir = !ec && (*this)->is_directory(ec);
+    if(isSymLink && detail::is_not_found_error(ec)) {
+        ec.clear();
+    }
+    if(!ec) {
+        if (recursion_pending() && isDir && (!isSymLink || (options() & directory_options::follow_directory_symlink) != directory_options::none)) {
+            _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec));
+        }
+        else {
+            _impl->_dir_iter_stack.top().increment(ec);
+        }
+        if (!ec) {
+            while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) {
+                _impl->_dir_iter_stack.pop();
+                _impl->_dir_iter_stack.top().increment(ec);
+            }
+        }
+        else if (!_impl->_dir_iter_stack.empty()) {
+            _impl->_dir_iter_stack.pop();
+        }
+        _impl->_recursion_pending = true;
+    }
+    return *this;
+}
+
+#ifdef GHC_WITH_EXCEPTIONS
+GHC_INLINE void recursive_directory_iterator::pop()
+{
+    std::error_code ec;
+    pop(ec);
+    if (ec) {
+        throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec);
+    }
+}
+#endif
+
+GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec)
+{
+    if (depth() == 0) {
+        *this = recursive_directory_iterator();
+    }
+    else {
+        do {
+            _impl->_dir_iter_stack.pop();
+            _impl->_dir_iter_stack.top().increment(ec);
+        } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator());
+    }
+}
+
+GHC_INLINE void recursive_directory_iterator::disable_recursion_pending()
+{
+    _impl->_recursion_pending = false;
+}
+
+// other members as required by [input.iterators]
+GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const
+{
+    return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top();
+}
+
+GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const
+{
+    return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top();
+}
+
+// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions
+GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept
+{
+    return iter;
+}
+
+GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept
+{
+    return recursive_directory_iterator();
+}
+
+#endif  // GHC_EXPAND_IMPL
+
+}  // namespace filesystem
+}  // namespace ghc
+
+// cleanup some macros
+#undef GHC_INLINE
+#undef GHC_EXPAND_IMPL
+
+#endif  // GHC_FILESYSTEM_H
diff --git a/wpiutil/src/main/native/include/wpi/hostname.h b/wpiutil/src/main/native/include/wpi/hostname.h
index b2f9f0c..bd93b4a 100644
--- a/wpiutil/src/main/native/include/wpi/hostname.h
+++ b/wpiutil/src/main/native/include/wpi/hostname.h
@@ -1,23 +1,19 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_HOSTNAME_H_
 #define WPIUTIL_WPI_HOSTNAME_H_
 
 #include <string>
-
-#include "wpi/StringRef.h"
+#include <string_view>
 
 namespace wpi {
 template <typename T>
 class SmallVectorImpl;
 
 std::string GetHostname();
-StringRef GetHostname(SmallVectorImpl<char>& name);
+std::string_view GetHostname(SmallVectorImpl<char>& name);
 }  // namespace wpi
 
 #endif  // WPIUTIL_WPI_HOSTNAME_H_
diff --git a/wpiutil/src/main/native/include/wpi/jni_util.h b/wpiutil/src/main/native/include/wpi/jni_util.h
index 15f59be..195e37e 100644
--- a/wpiutil/src/main/native/include/wpi/jni_util.h
+++ b/wpiutil/src/main/native/include/wpi/jni_util.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_JNI_UTIL_H_
 #define WPIUTIL_WPI_JNI_UTIL_H_
@@ -12,48 +9,61 @@
 
 #include <queue>
 #include <string>
+#include <string_view>
 #include <type_traits>
 #include <utility>
 #include <vector>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/ConvertUTF.h"
 #include "wpi/SafeThread.h"
 #include "wpi/SmallString.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
-#include "wpi/deprecated.h"
+#include "wpi/StringExtras.h"
 #include "wpi/mutex.h"
 #include "wpi/raw_ostream.h"
-
-/** WPILib C++ utilities (wpiutil) namespace */
-namespace wpi {
+#include "wpi/span.h"
 
 /** Java Native Interface (JNI) utility functions */
-namespace java {
+namespace wpi::java {
 
-// Gets a Java stack trace.  Also provides the last function
-// in the stack trace not starting with excludeFuncPrefix (useful for e.g.
-// finding the first user call to a series of library functions).
+/**
+ * Gets a Java stack trace.
+ *
+ * Also provides the last function in the stack trace not starting with
+ * excludeFuncPrefix (useful for e.g. finding the first user call to a series of
+ * library functions).
+ *
+ * @param env JRE environment.
+ * @param func Storage for last function in the stack trace not starting with
+ *             excludeFuncPrefix.
+ * @param excludeFuncPrefix Prefix for functions to ignore in stack trace.
+ */
 std::string GetJavaStackTrace(JNIEnv* env, std::string* func = nullptr,
-                              StringRef excludeFuncPrefix = StringRef());
+                              std::string_view excludeFuncPrefix = {});
 
-// Finds a class and keep it as a global reference.
-// Use with caution, as the destructor does NOT call DeleteGlobalRef due
-// to potential shutdown issues with doing so.
+/**
+ * Finds a class and keeps it as a global reference.
+ *
+ * Use with caution, as the destructor does NOT call DeleteGlobalRef due to
+ * potential shutdown issues with doing so.
+ */
 class JClass {
  public:
   JClass() = default;
 
   JClass(JNIEnv* env, const char* name) {
     jclass local = env->FindClass(name);
-    if (!local) return;
+    if (!local) {
+      return;
+    }
     m_cls = static_cast<jclass>(env->NewGlobalRef(local));
     env->DeleteLocalRef(local);
   }
 
   void free(JNIEnv* env) {
-    if (m_cls) env->DeleteGlobalRef(m_cls);
+    if (m_cls) {
+      env->DeleteGlobalRef(m_cls);
+    }
     m_cls = nullptr;
   }
 
@@ -80,20 +90,25 @@
   }
 
   void free(JNIEnv* env) {
-    if (m_cls) env->DeleteGlobalRef(m_cls);
+    if (m_cls) {
+      env->DeleteGlobalRef(m_cls);
+    }
     m_cls = nullptr;
   }
 
   explicit operator bool() const { return m_cls; }
 
-  operator T() const { return m_cls; }
+  operator T() const { return m_cls; }  // NOLINT
 
  protected:
   T m_cls = nullptr;
 };
 
-// Container class for cleaning up Java local references.
-// The destructor calls DeleteLocalRef.
+/**
+ * Container class for cleaning up Java local references.
+ *
+ * The destructor calls DeleteLocalRef.
+ */
 template <typename T>
 class JLocal {
  public:
@@ -110,9 +125,11 @@
     return *this;
   }
   ~JLocal() {
-    if (m_obj) m_env->DeleteLocalRef(m_obj);
+    if (m_obj) {
+      m_env->DeleteLocalRef(m_obj);
+    }
   }
-  operator T() { return m_obj; }
+  operator T() { return m_obj; }  // NOLINT
   T obj() { return m_obj; }
 
  private:
@@ -124,9 +141,12 @@
 // Conversions from Java objects to C++
 //
 
-// Java string (jstring) reference.  The string is provided as UTF8.
-// This is not actually a reference, as it makes a copy of the string
-// characters, but it's named this way for consistency.
+/**
+ * Java string (jstring) reference.
+ *
+ * The string is provided as UTF8. This is not actually a reference, as it makes
+ * a copy of the string characters, but it's named this way for consistency.
+ */
 class JStringRef {
  public:
   JStringRef(JNIEnv* env, jstring str) {
@@ -134,7 +154,7 @@
       jsize size = env->GetStringLength(str);
       const jchar* chars = env->GetStringCritical(str, nullptr);
       if (chars) {
-        convertUTF16ToUTF8String(makeArrayRef(chars, size), m_str);
+        convertUTF16ToUTF8String(wpi::span<const jchar>(chars, size), m_str);
         env->ReleaseStringCritical(str, chars);
       }
     } else {
@@ -146,8 +166,8 @@
     m_str.pop_back();
   }
 
-  operator StringRef() const { return m_str; }
-  StringRef str() const { return m_str; }
+  operator std::string_view() const { return m_str.str(); }  // NOLINT
+  std::string_view str() const { return m_str.str(); }
   const char* c_str() const { return m_str.data(); }
   size_t size() const { return m_str.size(); }
 
@@ -161,32 +181,44 @@
 template <typename C, typename T>
 class JArrayRefInner {};
 
-// Specialization of JArrayRefBase to provide StringRef conversion.
+/**
+ * Specialization of JArrayRefBase to provide std::string_view conversion.
+ */
 template <typename C>
 class JArrayRefInner<C, jbyte> {
  public:
-  operator StringRef() const { return str(); }
+  operator std::string_view() const { return str(); }
 
-  StringRef str() const {
+  std::string_view str() const {
     auto arr = static_cast<const C*>(this)->array();
-    if (arr.empty()) return StringRef{};
-    return StringRef{reinterpret_cast<const char*>(arr.data()), arr.size()};
+    if (arr.empty()) {
+      return {};
+    }
+    return {reinterpret_cast<const char*>(arr.data()), arr.size()};
   }
 };
 
-// Base class for J*ArrayRef and CriticalJ*ArrayRef
+/**
+ * Base class for J*ArrayRef and CriticalJ*ArrayRef
+ */
 template <typename T>
 class JArrayRefBase : public JArrayRefInner<JArrayRefBase<T>, T> {
  public:
   explicit operator bool() const { return this->m_elements != nullptr; }
 
-  operator ArrayRef<T>() const { return array(); }
+  operator span<const T>() const { return array(); }  // NOLINT
 
-  ArrayRef<T> array() const {
-    if (!this->m_elements) return ArrayRef<T>{};
-    return ArrayRef<T>{this->m_elements, this->m_size};
+  span<const T> array() const {
+    if (!this->m_elements) {
+      return {};
+    }
+    return {this->m_elements, this->m_size};
   }
 
+  size_t size() const { return this->m_size; }
+  T& operator[](size_t i) { return this->m_elements[i]; }
+  const T& operator[](size_t i) const { return this->m_elements[i]; }
+
   JArrayRefBase(const JArrayRefBase&) = delete;
   JArrayRefBase& operator=(const JArrayRefBase&) = delete;
 
@@ -245,30 +277,34 @@
               env,                                                             \
               static_cast<T*>(bb ? env->GetDirectBufferAddress(bb) : nullptr), \
               len) {                                                           \
-      if (!bb)                                                                 \
+      if (!bb) {                                                               \
         errs() << "JArrayRef was passed a null pointer at \n"                  \
                << GetJavaStackTrace(env);                                      \
+      }                                                                        \
     }                                                                          \
     J##F##ArrayRef(JNIEnv* env, T##Array jarr, int len)                        \
         : detail::JArrayRefBase<T>(env, jarr, len) {                           \
-      if (jarr)                                                                \
+      if (jarr) {                                                              \
         m_elements = env->Get##F##ArrayElements(jarr, nullptr);                \
-      else                                                                     \
+      } else {                                                                 \
         errs() << "JArrayRef was passed a null pointer at \n"                  \
                << GetJavaStackTrace(env);                                      \
+      }                                                                        \
     }                                                                          \
     J##F##ArrayRef(JNIEnv* env, T##Array jarr)                                 \
         : detail::JArrayRefBase<T>(env, jarr) {                                \
-      if (jarr)                                                                \
+      if (jarr) {                                                              \
         m_elements = env->Get##F##ArrayElements(jarr, nullptr);                \
-      else                                                                     \
+      } else {                                                                 \
         errs() << "JArrayRef was passed a null pointer at \n"                  \
                << GetJavaStackTrace(env);                                      \
+      }                                                                        \
     }                                                                          \
     ~J##F##ArrayRef() {                                                        \
-      if (m_jarr && m_elements)                                                \
+      if (m_jarr && m_elements) {                                              \
         m_env->Release##F##ArrayElements(static_cast<T##Array>(m_jarr),        \
                                          m_elements, JNI_ABORT);               \
+      }                                                                        \
     }                                                                          \
   };                                                                           \
                                                                                \
@@ -276,25 +312,28 @@
    public:                                                                     \
     CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr, int len)                \
         : detail::JArrayRefBase<T>(env, jarr, len) {                           \
-      if (jarr)                                                                \
+      if (jarr) {                                                              \
         m_elements =                                                           \
             static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr));    \
-      else                                                                     \
+      } else {                                                                 \
         errs() << "JArrayRef was passed a null pointer at \n"                  \
                << GetJavaStackTrace(env);                                      \
+      }                                                                        \
     }                                                                          \
     CriticalJ##F##ArrayRef(JNIEnv* env, T##Array jarr)                         \
         : detail::JArrayRefBase<T>(env, jarr) {                                \
-      if (jarr)                                                                \
+      if (jarr) {                                                              \
         m_elements =                                                           \
             static_cast<T*>(env->GetPrimitiveArrayCritical(jarr, nullptr));    \
-      else                                                                     \
+      } else {                                                                 \
         errs() << "JArrayRef was passed a null pointer at \n"                  \
                << GetJavaStackTrace(env);                                      \
+      }                                                                        \
     }                                                                          \
     ~CriticalJ##F##ArrayRef() {                                                \
-      if (m_jarr && m_elements)                                                \
+      if (m_jarr && m_elements) {                                              \
         m_env->ReleasePrimitiveArrayCritical(m_jarr, m_elements, JNI_ABORT);   \
+      }                                                                        \
     }                                                                          \
   };
 
@@ -312,8 +351,13 @@
 // Conversions from C++ to Java objects
 //
 
-// Convert a UTF8 string into a jstring.
-inline jstring MakeJString(JNIEnv* env, StringRef str) {
+/**
+ * Convert a UTF8 string into a jstring.
+ *
+ * @param env JRE environment.
+ * @param str String to convert.
+ */
+inline jstring MakeJString(JNIEnv* env, std::string_view str) {
   SmallVector<UTF16, 128> chars;
   convertUTF8ToUTF16String(str, chars);
   return env->NewString(chars.begin(), chars.size());
@@ -322,31 +366,43 @@
 // details for MakeJIntArray
 namespace detail {
 
-// Slow path (get primitive array and set individual elements).  This
-// is used if the input type is not an integer of the same size (note
-// signed/unsigned is ignored).
+/**
+ * Slow path (get primitive array and set individual elements).
+ *
+ * This is used if the input type is not an integer of the same size (note
+ * signed/unsigned is ignored).
+ */
 template <typename T,
           bool = (std::is_integral<T>::value && sizeof(jint) == sizeof(T))>
 struct ConvertIntArray {
-  static jintArray ToJava(JNIEnv* env, ArrayRef<T> arr) {
+  static jintArray ToJava(JNIEnv* env, span<const T> arr) {
     jintArray jarr = env->NewIntArray(arr.size());
-    if (!jarr) return nullptr;
+    if (!jarr) {
+      return nullptr;
+    }
     jint* elements =
         static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
-    if (!elements) return nullptr;
-    for (size_t i = 0; i < arr.size(); ++i)
+    if (!elements) {
+      return nullptr;
+    }
+    for (size_t i = 0; i < arr.size(); ++i) {
       elements[i] = static_cast<jint>(arr[i]);
+    }
     env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
     return jarr;
   }
 };
 
-// Fast path (use SetIntArrayRegion)
+/**
+ * Fast path (use SetIntArrayRegion).
+ */
 template <typename T>
 struct ConvertIntArray<T, true> {
-  static jintArray ToJava(JNIEnv* env, ArrayRef<T> arr) {
+  static jintArray ToJava(JNIEnv* env, span<const T> arr) {
     jintArray jarr = env->NewIntArray(arr.size());
-    if (!jarr) return nullptr;
+    if (!jarr) {
+      return nullptr;
+    }
     env->SetIntArrayRegion(jarr, 0, arr.size(),
                            reinterpret_cast<const jint*>(arr.data()));
     return jarr;
@@ -355,69 +411,128 @@
 
 }  // namespace detail
 
-// Convert an ArrayRef to a jintArray.
+/**
+ * Convert a span to a jintArray.
+ *
+ * @param env JRE environment.
+ * @param arr Span to convert.
+ */
 template <typename T>
-inline jintArray MakeJIntArray(JNIEnv* env, ArrayRef<T> arr) {
+inline jintArray MakeJIntArray(JNIEnv* env, span<const T> arr) {
   return detail::ConvertIntArray<T>::ToJava(env, arr);
 }
 
-// Convert a SmallVector to a jintArray.  This is required in addition to
-// ArrayRef because template resolution occurs prior to implicit conversions.
+/**
+ * Convert a span to a jintArray.
+ *
+ * @param env JRE environment.
+ * @param arr Span to convert.
+ */
+template <typename T>
+inline jintArray MakeJIntArray(JNIEnv* env, span<T> arr) {
+  return detail::ConvertIntArray<T>::ToJava(env, arr);
+}
+
+/**
+ * Convert a SmallVector to a jintArray.
+ *
+ * This is required in addition to ArrayRef because template resolution occurs
+ * prior to implicit conversions.
+ *
+ * @param env JRE environment.
+ * @param arr SmallVector to convert.
+ */
 template <typename T>
 inline jintArray MakeJIntArray(JNIEnv* env, const SmallVectorImpl<T>& arr) {
   return detail::ConvertIntArray<T>::ToJava(env, arr);
 }
 
-// Convert a std::vector to a jintArray.  This is required in addition to
-// ArrayRef because template resolution occurs prior to implicit conversions.
+/**
+ * Convert a std::vector to a jintArray.
+ *
+ * This is required in addition to ArrayRef because template resolution occurs
+ * prior to implicit conversions.
+ *
+ * @param env JRE environment.
+ * @param arr SmallVector to convert.
+ */
 template <typename T>
 inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
   return detail::ConvertIntArray<T>::ToJava(env, arr);
 }
 
-// Convert a StringRef into a jbyteArray.
-inline jbyteArray MakeJByteArray(JNIEnv* env, StringRef str) {
+/**
+ * Convert a std::string_view into a jbyteArray.
+ *
+ * @param env JRE environment.
+ * @param str std::string_view to convert.
+ */
+inline jbyteArray MakeJByteArray(JNIEnv* env, std::string_view str) {
   jbyteArray jarr = env->NewByteArray(str.size());
-  if (!jarr) return nullptr;
+  if (!jarr) {
+    return nullptr;
+  }
   env->SetByteArrayRegion(jarr, 0, str.size(),
                           reinterpret_cast<const jbyte*>(str.data()));
   return jarr;
 }
 
-// Convert an array of integers into a jbooleanArray.
-inline jbooleanArray MakeJBooleanArray(JNIEnv* env, ArrayRef<int> arr) {
+/**
+ * Convert an array of integers into a jbooleanArray.
+ *
+ * @param env JRE environment.
+ * @param arr Array to convert.
+ */
+inline jbooleanArray MakeJBooleanArray(JNIEnv* env, span<const int> arr) {
   jbooleanArray jarr = env->NewBooleanArray(arr.size());
-  if (!jarr) return nullptr;
+  if (!jarr) {
+    return nullptr;
+  }
   jboolean* elements =
       static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
-  if (!elements) return nullptr;
-  for (size_t i = 0; i < arr.size(); ++i)
+  if (!elements) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < arr.size(); ++i) {
     elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
+  }
   env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
   return jarr;
 }
 
-// Convert an array of booleans into a jbooleanArray.
-inline jbooleanArray MakeJBooleanArray(JNIEnv* env, ArrayRef<bool> arr) {
+/**
+ * Convert an array of booleans into a jbooleanArray.
+ *
+ * @param env JRE environment.
+ * @param arr Array to convert.
+ */
+inline jbooleanArray MakeJBooleanArray(JNIEnv* env, span<const bool> arr) {
   jbooleanArray jarr = env->NewBooleanArray(arr.size());
-  if (!jarr) return nullptr;
+  if (!jarr) {
+    return nullptr;
+  }
   jboolean* elements =
       static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
-  if (!elements) return nullptr;
-  for (size_t i = 0; i < arr.size(); ++i)
+  if (!elements) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < arr.size(); ++i) {
     elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
+  }
   env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
   return jarr;
 }
 
 // Other MakeJ*Array conversions.
 
-#define WPI_JNI_MAKEJARRAY(T, F)                                  \
-  inline T##Array MakeJ##F##Array(JNIEnv* env, ArrayRef<T> arr) { \
-    T##Array jarr = env->New##F##Array(arr.size());               \
-    if (!jarr) return nullptr;                                    \
-    env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data());    \
-    return jarr;                                                  \
+#define WPI_JNI_MAKEJARRAY(T, F)                                    \
+  inline T##Array MakeJ##F##Array(JNIEnv* env, span<const T> arr) { \
+    T##Array jarr = env->New##F##Array(arr.size());                 \
+    if (!jarr) {                                                    \
+      return nullptr;                                               \
+    }                                                               \
+    env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data());      \
+    return jarr;                                                    \
   }
 
 WPI_JNI_MAKEJARRAY(jboolean, Boolean)
@@ -429,12 +544,21 @@
 
 #undef WPI_JNI_MAKEJARRAY
 
-// Convert an array of std::string into a jarray of jstring.
-inline jobjectArray MakeJStringArray(JNIEnv* env, ArrayRef<std::string> arr) {
+/**
+ * Convert an array of std::string into a jarray of jstring.
+ *
+ * @param env JRE environment.
+ * @param arr Array to convert.
+ */
+inline jobjectArray MakeJStringArray(JNIEnv* env, span<const std::string> arr) {
   static JClass stringCls{env, "java/lang/String"};
-  if (!stringCls) return nullptr;
+  if (!stringCls) {
+    return nullptr;
+  }
   jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
-  if (!jarr) return nullptr;
+  if (!jarr) {
+    return nullptr;
+  }
   for (size_t i = 0; i < arr.size(); ++i) {
     JLocal<jstring> elem{env, MakeJString(env, arr[i])};
     env->SetObjectArrayElement(jarr, i, elem.obj());
@@ -442,25 +566,27 @@
   return jarr;
 }
 
-// Generic callback thread implementation.
-//
-// JNI's AttachCurrentThread() creates a Java Thread object on every
-// invocation, which is both time inefficient and causes issues with Eclipse
-// (which tries to keep a thread list up-to-date and thus gets swamped).
-//
-// Instead, this class attaches just once.  When a hardware notification
-// occurs, a condition variable wakes up this thread and this thread actually
-// makes the call into Java.
-//
-// The template parameter T is the message being passed to the callback, but
-// also needs to provide the following functions:
-//  static JavaVM* GetJVM();
-//  static const char* GetName();
-//  void CallJava(JNIEnv *env, jobject func, jmethodID mid);
+/**
+ * Generic callback thread implementation.
+ *
+ * JNI's AttachCurrentThread() creates a Java Thread object on every
+ * invocation, which is both time inefficient and causes issues with Eclipse
+ * (which tries to keep a thread list up-to-date and thus gets swamped).
+ *
+ * Instead, this class attaches just once.  When a hardware notification
+ * occurs, a condition variable wakes up this thread and this thread actually
+ * makes the call into Java.
+ *
+ * The template parameter T is the message being passed to the callback, but
+ * also needs to provide the following functions:
+ *  static JavaVM* GetJVM();
+ *  static const char* GetName();
+ *  void CallJava(JNIEnv *env, jobject func, jmethodID mid);
+ */
 template <typename T>
 class JCallbackThread : public SafeThread {
  public:
-  void Main();
+  void Main() override;
 
   std::queue<T> m_queue;
   jobject m_func = nullptr;
@@ -480,9 +606,13 @@
 template <typename T>
 void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
   auto thr = this->GetThread();
-  if (!thr) return;
+  if (!thr) {
+    return;
+  }
   // free global reference
-  if (thr->m_func) env->DeleteGlobalRef(thr->m_func);
+  if (thr->m_func) {
+    env->DeleteGlobalRef(thr->m_func);
+  }
   // create global reference
   thr->m_func = env->NewGlobalRef(func);
   thr->m_mid = mid;
@@ -492,7 +622,9 @@
 template <typename... Args>
 void JCallbackManager<T>::Send(Args&&... args) {
   auto thr = this->GetThread();
-  if (!thr) return;
+  if (!thr) {
+    return;
+  }
   thr->m_queue.emplace(std::forward<Args>(args)...);
   thr->m_cond.notify_one();
 }
@@ -506,14 +638,20 @@
   args.group = nullptr;
   jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon(
       reinterpret_cast<void**>(&env), &args);
-  if (rs != JNI_OK) return;
+  if (rs != JNI_OK) {
+    return;
+  }
 
   std::unique_lock lock(m_mutex);
   while (m_active) {
     m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
-    if (!m_active) break;
+    if (!m_active) {
+      break;
+    }
     while (!m_queue.empty()) {
-      if (!m_active) break;
+      if (!m_active) {
+        break;
+      }
       auto item = std::move(m_queue.front());
       m_queue.pop();
       auto func = m_func;
@@ -529,7 +667,9 @@
   }
 
   JavaVM* jvm = T::GetJVM();
-  if (jvm) jvm->DetachCurrentThread();
+  if (jvm) {
+    jvm->DetachCurrentThread();
+  }
 }
 
 template <typename T>
@@ -541,41 +681,125 @@
   }
 };
 
-inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
-                                     StringRef excludeFuncPrefix) {
+inline std::string GetJavaStackTrace(JNIEnv* env, std::string_view skipPrefix) {
   // create a throwable
   static JClass throwableCls(env, "java/lang/Throwable");
-  if (!throwableCls) return "";
+  if (!throwableCls) {
+    return "";
+  }
   static jmethodID constructorId = nullptr;
-  if (!constructorId)
+  if (!constructorId) {
     constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
+  }
   JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
 
   // retrieve information from the exception.
   // get method id
   // getStackTrace returns an array of StackTraceElement
   static jmethodID getStackTraceId = nullptr;
-  if (!getStackTraceId)
+  if (!getStackTraceId) {
     getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
                                        "()[Ljava/lang/StackTraceElement;");
+  }
 
   // call getStackTrace
   JLocal<jobjectArray> stackTrace(
       env, static_cast<jobjectArray>(
                env->CallObjectMethod(throwable, getStackTraceId)));
 
-  if (!stackTrace) return "";
+  if (!stackTrace) {
+    return "";
+  }
 
   // get length of the array
   jsize stackTraceLength = env->GetArrayLength(stackTrace);
 
   // get toString methodId of StackTraceElement class
   static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
-  if (!stackTraceElementCls) return "";
+  if (!stackTraceElementCls) {
+    return "";
+  }
   static jmethodID toStringId = nullptr;
-  if (!toStringId)
+  if (!toStringId) {
     toStringId = env->GetMethodID(stackTraceElementCls, "toString",
                                   "()Ljava/lang/String;");
+  }
+
+  bool foundFirst = false;
+  std::string buf;
+  raw_string_ostream oss(buf);
+  for (jsize i = 0; i < stackTraceLength; i++) {
+    // add the result of toString method of each element in the result
+    JLocal<jobject> curStackTraceElement(
+        env, env->GetObjectArrayElement(stackTrace, i));
+
+    // call to string on the object
+    JLocal<jstring> stackElementString(
+        env, static_cast<jstring>(
+                 env->CallObjectMethod(curStackTraceElement, toStringId)));
+
+    if (!stackElementString) {
+      return "";
+    }
+
+    // add a line to res
+    JStringRef elem(env, stackElementString);
+    if (!foundFirst) {
+      if (wpi::starts_with(elem, skipPrefix)) {
+        continue;
+      }
+      foundFirst = true;
+    }
+    oss << "\tat " << elem << '\n';
+  }
+
+  return oss.str();
+}
+
+inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
+                                     std::string_view excludeFuncPrefix) {
+  // create a throwable
+  static JClass throwableCls(env, "java/lang/Throwable");
+  if (!throwableCls) {
+    return "";
+  }
+  static jmethodID constructorId = nullptr;
+  if (!constructorId) {
+    constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
+  }
+  JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
+
+  // retrieve information from the exception.
+  // get method id
+  // getStackTrace returns an array of StackTraceElement
+  static jmethodID getStackTraceId = nullptr;
+  if (!getStackTraceId) {
+    getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
+                                       "()[Ljava/lang/StackTraceElement;");
+  }
+
+  // call getStackTrace
+  JLocal<jobjectArray> stackTrace(
+      env, static_cast<jobjectArray>(
+               env->CallObjectMethod(throwable, getStackTraceId)));
+
+  if (!stackTrace) {
+    return "";
+  }
+
+  // get length of the array
+  jsize stackTraceLength = env->GetArrayLength(stackTrace);
+
+  // get toString methodId of StackTraceElement class
+  static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
+  if (!stackTraceElementCls) {
+    return "";
+  }
+  static jmethodID toStringId = nullptr;
+  if (!toStringId) {
+    toStringId = env->GetMethodID(stackTraceElementCls, "toString",
+                                  "()Ljava/lang/String;");
+  }
 
   bool haveLoc = false;
   std::string buf;
@@ -590,7 +814,9 @@
         env, static_cast<jstring>(
                  env->CallObjectMethod(curStackTraceElement, toStringId)));
 
-    if (!stackElementString) return "";
+    if (!stackElementString) {
+      return "";
+    }
 
     // add a line to res
     JStringRef elem(env, stackElementString);
@@ -602,7 +828,7 @@
       if (i == 1) {
         *func = elem.str();
       } else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
-                 !elem.str().startswith(excludeFuncPrefix)) {
+                 !wpi::starts_with(elem, excludeFuncPrefix)) {
         *func = elem.str();
         haveLoc = true;
       }
@@ -612,17 +838,21 @@
   return oss.str();
 }
 
-// Finds an exception class and keep it as a global reference.
-// Similar to JClass, but provides Throw methods.
-// Use with caution, as the destructor does NOT call DeleteGlobalRef due
-// to potential shutdown issues with doing so.
+/**
+ * Finds an exception class and keep it as a global reference.
+ *
+ * Similar to JClass, but provides Throw methods. Use with caution, as the
+ * destructor does NOT call DeleteGlobalRef due to potential shutdown issues
+ * with doing so.
+ */
 class JException : public JClass {
  public:
   JException() = default;
   JException(JNIEnv* env, const char* name) : JClass(env, name) {
-    if (m_cls)
+    if (m_cls) {
       m_constructor =
           env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
+    }
   }
 
   void Throw(JNIEnv* env, jstring msg) {
@@ -630,7 +860,9 @@
     env->Throw(static_cast<jthrowable>(exception));
   }
 
-  void Throw(JNIEnv* env, StringRef msg) { Throw(env, MakeJString(env, msg)); }
+  void Throw(JNIEnv* env, std::string_view msg) {
+    Throw(env, MakeJString(env, msg));
+  }
 
   explicit operator bool() const { return m_constructor; }
 
@@ -643,7 +875,6 @@
   JException* cls;
 };
 
-}  // namespace java
-}  // namespace wpi
+}  // namespace wpi::java
 
 #endif  // WPIUTIL_WPI_JNI_UTIL_H_
diff --git a/wpiutil/src/main/native/include/wpi/json.h b/wpiutil/src/main/native/include/wpi/json.h
index a6368b7..1a4e410 100644
--- a/wpiutil/src/main/native/include/wpi/json.h
+++ b/wpiutil/src/main/native/include/wpi/json.h
@@ -54,15 +54,14 @@
 #include <memory> // allocator, shared_ptr, make_shared, addressof
 #include <stdexcept> // runtime_error
 #include <string> // string, char_traits, stoi, to_string
+#include <string_view>
 #include <tuple> // tuple, get, make_tuple
 #include <type_traits>
 #include <utility>
 #include <vector> // vector
 
-#include "wpi/ArrayRef.h"
 #include "wpi/StringMap.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
+#include "wpi/span.h"
 
 namespace wpi
 {
@@ -239,7 +238,7 @@
 struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType>
 {
     static constexpr auto value =
-        std::is_constructible<StringRef, typename CompatibleObjectType::key_type>::value and
+        std::is_constructible<std::string_view, typename CompatibleObjectType::key_type>::value and
         std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value;
 };
 
@@ -269,7 +268,7 @@
         std::conjunction<std::negation<std::is_same<void, CompatibleArrayType>>,
         std::negation<is_compatible_object_type<
         BasicJsonType, CompatibleArrayType>>,
-        std::negation<std::is_constructible<StringRef,
+        std::negation<std::is_constructible<std::string_view,
         CompatibleArrayType>>,
         std::negation<is_json_nested_type<BasicJsonType, CompatibleArrayType>>,
         has_value_type<CompatibleArrayType>,
@@ -423,7 +422,7 @@
     const int id;
 
   protected:
-    exception(int id_, const Twine& what_arg);
+    exception(int id_, std::string_view what_arg);
 
   private:
     /// an exception object as storage for error messages
@@ -484,7 +483,7 @@
     @param[in] what_arg  the explanatory string
     @return parse_error object
     */
-    static parse_error create(int id_, std::size_t byte_, const Twine& what_arg);
+    static parse_error create(int id_, std::size_t byte_, std::string_view what_arg);
 
     /*!
     @brief byte index of the parse error
@@ -498,7 +497,7 @@
     const std::size_t byte;
 
   private:
-    parse_error(int id_, std::size_t byte_, const Twine& what_arg)
+    parse_error(int id_, std::size_t byte_, std::string_view what_arg)
         : exception(id_, what_arg), byte(byte_) {}
 };
 
@@ -542,10 +541,11 @@
 class invalid_iterator : public exception
 {
   public:
-    static invalid_iterator create(int id_, const Twine& what_arg);
+    static invalid_iterator create(int id_, std::string_view what_arg);
+    static invalid_iterator create(int id_, std::string_view what_arg, std::string_view type_info);
 
   private:
-    invalid_iterator(int id_, const Twine& what_arg)
+    invalid_iterator(int id_, std::string_view what_arg)
         : exception(id_, what_arg) {}
 };
 
@@ -590,10 +590,11 @@
 class type_error : public exception
 {
   public:
-    static type_error create(int id_, const Twine& what_arg);
+    static type_error create(int id_, std::string_view what_arg);
+    static type_error create(int id_, std::string_view what_arg, std::string_view type_info);
 
   private:
-    type_error(int id_, const Twine& what_arg) : exception(id_, what_arg) {}
+    type_error(int id_, std::string_view what_arg) : exception(id_, what_arg) {}
 };
 
 /*!
@@ -631,10 +632,10 @@
 class out_of_range : public exception
 {
   public:
-    static out_of_range create(int id_, const Twine& what_arg);
+    static out_of_range create(int id_, std::string_view what_arg);
 
   private:
-    out_of_range(int id_, const Twine& what_arg) : exception(id_, what_arg) {}
+    out_of_range(int id_, std::string_view what_arg) : exception(id_, what_arg) {}
 };
 
 /*!
@@ -664,10 +665,10 @@
 class other_error : public exception
 {
   public:
-    static other_error create(int id_, const Twine& what_arg);
+    static other_error create(int id_, std::string_view what_arg);
 
   private:
-    other_error(int id_, const Twine& what_arg) : exception(id_, what_arg) {}
+    other_error(int id_, std::string_view what_arg) : exception(id_, what_arg) {}
 };
 
 ///////////////////////////
@@ -760,7 +761,7 @@
         }
 
         default:
-            JSON_THROW(type_error::create(302, "type must be number, but is " + Twine(j.type_name())));
+            JSON_THROW(type_error::create(302, "type must be number, but is", j.type_name()));
     }
 }
 
@@ -769,7 +770,7 @@
 {
     if (JSON_UNLIKELY(not j.is_boolean()))
     {
-        JSON_THROW(type_error::create(302, "type must be boolean, but is " + Twine(j.type_name())));
+        JSON_THROW(type_error::create(302, "type must be boolean, but is", j.type_name()));
     }
     b = *j.template get_ptr<const bool*>();
 }
@@ -779,7 +780,7 @@
 {
     if (JSON_UNLIKELY(not j.is_string()))
     {
-        JSON_THROW(type_error::create(302, "type must be string, but is " + Twine(j.type_name())));
+        JSON_THROW(type_error::create(302, "type must be string, but is", j.type_name()));
     }
     s = *j.template get_ptr<const std::string*>();
 }
@@ -816,7 +817,7 @@
 {
     if (JSON_UNLIKELY(not j.is_array()))
     {
-        JSON_THROW(type_error::create(302, "type must be array, but is " + Twine(j.type_name())));
+        JSON_THROW(type_error::create(302, "type must be array, but is", j.type_name()));
     }
     arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
 }
@@ -875,8 +876,7 @@
 {
     if (JSON_UNLIKELY(not j.is_array()))
     {
-        JSON_THROW(type_error::create(302, "type must be array, but is " +
-                                      Twine(j.type_name())));
+        JSON_THROW(type_error::create(302, "type must be array, but is", j.type_name()));
     }
 
     from_json_array_impl(j, arr, priority_tag<2> {});
@@ -888,7 +888,7 @@
 {
     if (!j.is_object())
     {
-        JSON_THROW(type_error::create(302, "type must be object, but is " + Twine(j.type_name())));
+        JSON_THROW(type_error::create(302, "type must be object, but is", j.type_name()));
     }
 
     auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
@@ -904,7 +904,7 @@
 {
     if (JSON_UNLIKELY(not j.is_object()))
     {
-        JSON_THROW(type_error::create(302, "type must be object, but is " + Twine(j.type_name())));
+        JSON_THROW(type_error::create(302, "type must be object, but is", j.type_name()));
     }
 
     auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
@@ -965,7 +965,7 @@
         }
 
         default:
-            JSON_THROW(type_error::create(302, "type must be number, but is " + Twine(j.type_name())));
+            JSON_THROW(type_error::create(302, "type must be number, but is", j.type_name()));
     }
 }
 
@@ -1053,7 +1053,7 @@
 struct external_constructor<value_t::string>
 {
     template<typename BasicJsonType>
-    static void construct(BasicJsonType& j, StringRef s)
+    static void construct(BasicJsonType& j, std::string_view s)
     {
         j.m_type = value_t::string;
         j.m_value = s;
@@ -1126,7 +1126,7 @@
     }
 
     template<typename BasicJsonType, typename T>
-    static void construct(BasicJsonType& j, ArrayRef<T> arr)
+    static void construct(BasicJsonType& j, span<T> arr)
     {
         using std::begin;
         using std::end;
@@ -1206,7 +1206,7 @@
 }
 
 template<typename BasicJsonType, typename CompatibleString,
-         enable_if_t<std::is_constructible<StringRef, CompatibleString>::value, int> = 0>
+         enable_if_t<std::is_constructible<std::string_view, CompatibleString>::value, int> = 0>
 void to_json(BasicJsonType& j, const CompatibleString& s)
 {
     external_constructor<value_t::string>::construct(j, s);
@@ -1283,7 +1283,7 @@
 }
 
 template<typename BasicJsonType, typename T, std::size_t N,
-         enable_if_t<not std::is_constructible<StringRef, T (&)[N]>::value, int> = 0>
+         enable_if_t<not std::is_constructible<std::string_view, T (&)[N]>::value, int> = 0>
 void to_json(BasicJsonType& j, T (&arr)[N])
 {
     external_constructor<value_t::array>::construct(j, arr);
@@ -2044,7 +2044,7 @@
     @brief  return the key of an object iterator
     @pre The iterator is initialized; i.e. `m_object != nullptr`.
     */
-    StringRef key() const
+    std::string_view key() const
     {
         assert(m_object != nullptr);
 
@@ -2122,7 +2122,7 @@
 
                 // use key from the object
                 case value_t::object:
-                    return anchor.key();
+                    return std::string{anchor.key()};
 
                 // use an empty key for all primitive types
                 default:
@@ -2348,7 +2348,7 @@
 
     @since version 2.0.0
     */
-    explicit json_pointer(const Twine& s = {})
+    explicit json_pointer(std::string_view s = {})
         : reference_tokens(split(s))
     {}
 
@@ -2382,7 +2382,7 @@
 
     @throw out_of_range.404 if string @a s could not be converted to an integer
     */
-    static int array_index(const Twine& s);
+    static int array_index(std::string_view s);
 
   private:
     /*!
@@ -2490,7 +2490,7 @@
     @throw parse_error.107  if the pointer is not empty or begins with '/'
     @throw parse_error.108  if character '~' is not followed by '0' or '1'
     */
-    static std::vector<std::string> split(const Twine& reference_string);
+    static std::vector<std::string> split(std::string_view reference_string);
 
     /*!
     @brief replace all occurrences of a substring by another string
@@ -2521,7 +2521,7 @@
 
     @note Empty objects or arrays are flattened to `null`.
     */
-    static void flatten(const Twine& reference_string,
+    static void flatten(std::string_view reference_string,
                         const json& value,
                         json& result);
 
@@ -2678,9 +2678,10 @@
     class binary_writer;
     class lexer;
     class parser;
-    class serializer;
 
   public:
+    class serializer;
+
     using value_t = detail::value_t;
     /// @copydoc wpi::json_pointer
     using json_pointer = ::wpi::json_pointer;
@@ -2970,7 +2971,7 @@
         json_value(value_t t);
 
         /// constructor for strings
-        json_value(StringRef value)
+        json_value(std::string_view value)
         {
             string = create<std::string>(value);
         }
@@ -3576,8 +3577,7 @@
             }
 
             default:
-                JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " +
-                                                    Twine(first.m_object->type_name())));
+                JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from", first.m_object->type_name()));
         }
 
         assert_invariant();
@@ -4162,7 +4162,7 @@
             return m_value.boolean;
         }
 
-        JSON_THROW(type_error::create(302, "type must be boolean, but is " + Twine(type_name())));
+        JSON_THROW(type_error::create(302, "type must be boolean, but is", type_name()));
     }
 
     /// get a pointer to the value (object)
@@ -4271,7 +4271,7 @@
             return *ptr;
         }
 
-        JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + Twine(obj.type_name())));
+        JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is", obj.type_name()));
     }
 
   public:
@@ -4716,7 +4716,7 @@
     written using `at()`. It also demonstrates the different exceptions that
     can be thrown.,at__object_t_key_type}
     */
-    reference at(StringRef key);
+    reference at(std::string_view key);
 
     /*!
     @brief access specified object element with bounds checking
@@ -4748,7 +4748,7 @@
     `at()`. It also demonstrates the different exceptions that can be thrown.,
     at__object_t_key_type_const}
     */
-    const_reference at(StringRef key) const;
+    const_reference at(std::string_view key) const;
 
     /*!
     @brief access specified array element
@@ -4825,7 +4825,7 @@
 
     @since version 1.0.0
     */
-    reference operator[](StringRef key);
+    reference operator[](std::string_view key);
 
     /*!
     @brief read-only access specified object element
@@ -4857,7 +4857,7 @@
 
     @since version 1.0.0
     */
-    const_reference operator[](StringRef key) const;
+    const_reference operator[](std::string_view key) const;
 
     /*!
     @brief access specified object element
@@ -4903,7 +4903,7 @@
             return m_value.object->operator[](key);
         }
 
-        JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name())));
+        JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
     }
 
     /*!
@@ -4946,7 +4946,7 @@
             return m_value.object->find(key)->second;
         }
 
-        JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name())));
+        JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
     }
 
     /*!
@@ -4999,7 +4999,7 @@
     */
     template<class ValueType, typename std::enable_if<
                  std::is_convertible<json_t, ValueType>::value, int>::type = 0>
-    ValueType value(StringRef key, const ValueType& default_value) const
+    ValueType value(std::string_view key, const ValueType& default_value) const
     {
         // at only works for objects
         if (JSON_LIKELY(is_object()))
@@ -5014,14 +5014,14 @@
             return default_value;
         }
 
-        JSON_THROW(type_error::create(306, "cannot use value() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(306, "cannot use value() with", type_name()));
     }
 
     /*!
     @brief overload for a default value of type const char*
     @copydoc json::value(const typename object_t::key_type&, ValueType) const
     */
-    std::string value(StringRef key, const char* default_value) const
+    std::string value(std::string_view key, const char* default_value) const
     {
         return value(key, std::string(default_value));
     }
@@ -5085,7 +5085,7 @@
             }
         }
 
-        JSON_THROW(type_error::create(306, "cannot use value() with " + Twine(type_name())));
+        JSON_THROW(type_error::create(306, "cannot use value() with", type_name()));
     }
 
     /*!
@@ -5220,7 +5220,7 @@
 
     @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
     the given range
-    @sa @ref erase(StringRef) -- removes the element
+    @sa @ref erase(std::string_view) -- removes the element
     from an object at the given key
     @sa @ref erase(const size_type) -- removes the element from an array at
     the given index
@@ -5278,7 +5278,7 @@
             }
 
             default:
-                JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name())));
+                JSON_THROW(type_error::create(307, "cannot use erase() with", type_name()));
         }
     }
 
@@ -5377,7 +5377,7 @@
             }
 
             default:
-                JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name())));
+                JSON_THROW(type_error::create(307, "cannot use erase() with", type_name()));
         }
 
         return result;
@@ -5412,7 +5412,7 @@
 
     @since version 1.0.0
     */
-    size_type erase(StringRef key);
+    size_type erase(std::string_view key);
 
     /*!
     @brief remove element from a JSON array given an index
@@ -5472,13 +5472,13 @@
 
     @since version 1.0.0
     */
-    iterator find(StringRef key);
+    iterator find(std::string_view key);
 
     /*!
     @brief find an element in a JSON object
     @copydoc find(KeyT&&)
     */
-    const_iterator find(StringRef key) const;
+    const_iterator find(std::string_view key) const;
 
     /*!
     @brief returns the number of occurrences of a key in a JSON object
@@ -5501,7 +5501,7 @@
 
     @since version 1.0.0
     */
-    size_type count(StringRef key) const;
+    size_type count(std::string_view key) const;
 
     /// @}
 
@@ -6113,7 +6113,7 @@
         // push_back only works for null objects or objects
         if (JSON_UNLIKELY(not(is_null() or is_object())))
         {
-            JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name())));
+            JSON_THROW(type_error::create(308, "cannot use push_back() with", type_name()));
         }
 
         // transform null object into an object
@@ -6203,7 +6203,7 @@
         // emplace_back only works for null objects or arrays
         if (JSON_UNLIKELY(not(is_null() or is_array())))
         {
-            JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + Twine(type_name())));
+            JSON_THROW(type_error::create(311, "cannot use emplace_back() with", type_name()));
         }
 
         // transform null object into an array
@@ -6246,12 +6246,12 @@
     @since version 2.0.8
     */
     template<class... Args>
-    std::pair<iterator, bool> emplace(StringRef key, Args&& ... args)
+    std::pair<iterator, bool> emplace(std::string_view key, Args&& ... args)
     {
         // emplace only works for null objects or arrays
         if (JSON_UNLIKELY(not(is_null() or is_object())))
         {
-            JSON_THROW(type_error::create(311, "cannot use emplace() with " + Twine(type_name())));
+            JSON_THROW(type_error::create(311, "cannot use emplace() with", type_name()));
         }
 
         // transform null object into an object
@@ -6521,7 +6521,7 @@
         }
         else
         {
-            JSON_THROW(type_error::create(310, "cannot use swap() with " + Twine(type_name())));
+            JSON_THROW(type_error::create(310, "cannot use swap() with", type_name()));
         }
     }
 
@@ -6554,7 +6554,7 @@
         }
         else
         {
-            JSON_THROW(type_error::create(310, "cannot use swap() with " + Twine(type_name())));
+            JSON_THROW(type_error::create(310, "cannot use swap() with", type_name()));
         }
     }
 
@@ -6587,7 +6587,7 @@
         }
         else
         {
-            JSON_THROW(type_error::create(310, "cannot use swap() with " + Twine(type_name())));
+            JSON_THROW(type_error::create(310, "cannot use swap() with", type_name()));
         }
     }
 
@@ -7011,11 +7011,11 @@
 
     @since version 2.0.3 (contiguous containers)
     */
-    static json parse(StringRef s,
+    static json parse(std::string_view s,
                             const parser_callback_t cb = nullptr,
                             const bool allow_exceptions = true);
 
-    static json parse(ArrayRef<uint8_t> arr,
+    static json parse(span<const uint8_t> arr,
                             const parser_callback_t cb = nullptr,
                             const bool allow_exceptions = true);
 
@@ -7026,9 +7026,9 @@
                             const parser_callback_t cb = nullptr,
                             const bool allow_exceptions = true);
 
-    static bool accept(StringRef s);
+    static bool accept(std::string_view s);
 
-    static bool accept(ArrayRef<uint8_t> arr);
+    static bool accept(span<const uint8_t> arr);
 
     static bool accept(raw_istream& i);
 
@@ -7206,8 +7206,8 @@
     @since version 2.0.9
     */
     static std::vector<uint8_t> to_cbor(const json& j);
-    static ArrayRef<uint8_t> to_cbor(const json& j, std::vector<uint8_t>& buf);
-    static ArrayRef<uint8_t> to_cbor(const json& j, SmallVectorImpl<uint8_t>& buf);
+    static span<uint8_t> to_cbor(const json& j, std::vector<uint8_t>& buf);
+    static span<uint8_t> to_cbor(const json& j, SmallVectorImpl<uint8_t>& buf);
     static void to_cbor(raw_ostream& os, const json& j);
 
     /*!
@@ -7291,8 +7291,8 @@
     @since version 2.0.9
     */
     static std::vector<uint8_t> to_msgpack(const json& j);
-    static ArrayRef<uint8_t> to_msgpack(const json& j, std::vector<uint8_t>& buf);
-    static ArrayRef<uint8_t> to_msgpack(const json& j, SmallVectorImpl<uint8_t>& buf);
+    static span<uint8_t> to_msgpack(const json& j, std::vector<uint8_t>& buf);
+    static span<uint8_t> to_msgpack(const json& j, SmallVectorImpl<uint8_t>& buf);
     static void to_msgpack(raw_ostream& os, const json& j);
 
     /*!
@@ -7378,10 +7378,10 @@
     static std::vector<uint8_t> to_ubjson(const json& j,
                                           const bool use_size = false,
                                           const bool use_type = false);
-    static ArrayRef<uint8_t> to_ubjson(const json& j, std::vector<uint8_t>& buf,
-                                       const bool use_size = false, const bool use_type = false);
-    static ArrayRef<uint8_t> to_ubjson(const json& j, SmallVectorImpl<uint8_t>& buf,
-                                       const bool use_size = false, const bool use_type = false);
+    static span<uint8_t> to_ubjson(const json& j, std::vector<uint8_t>& buf,
+                                   const bool use_size = false, const bool use_type = false);
+    static span<uint8_t> to_ubjson(const json& j, SmallVectorImpl<uint8_t>& buf,
+                                   const bool use_size = false, const bool use_type = false);
     static void to_ubjson(raw_ostream& os, const json& j,
                           const bool use_size = false, const bool use_type = false);
 
@@ -7484,7 +7484,7 @@
     /*!
     @copydoc from_cbor(raw_istream&, const bool)
     */
-    static json from_cbor(ArrayRef<uint8_t> arr, const bool strict = true);
+    static json from_cbor(span<const uint8_t> arr, const bool strict = true);
 
     /*!
     @brief create a JSON value from an input in MessagePack format
@@ -7563,9 +7563,9 @@
                                    const bool strict = true);
 
     /*!
-    @copydoc from_msgpack(raw_istream, const bool)
+    @copydoc from_msgpack(raw_istream&, const bool)
     */
-    static json from_msgpack(ArrayRef<uint8_t> arr, const bool strict = true);
+    static json from_msgpack(span<const uint8_t> arr, const bool strict = true);
 
     /*!
     @brief create a JSON value from an input in UBJSON format
@@ -7623,7 +7623,7 @@
     static json from_ubjson(raw_istream& is,
                                   const bool strict = true);
 
-    static json from_ubjson(ArrayRef<uint8_t> arr, const bool strict = true);
+    static json from_ubjson(span<const uint8_t> arr, const bool strict = true);
 
     /// @}
 
@@ -8074,7 +8074,7 @@
 */
 inline wpi::json operator "" _json(const char* s, std::size_t n)
 {
-    return wpi::json::parse(wpi::StringRef(s, n));
+    return wpi::json::parse(std::string_view(s, n));
 }
 
 /*!
@@ -8092,7 +8092,7 @@
 */
 inline wpi::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n)
 {
-    return wpi::json::json_pointer(wpi::StringRef(s, n));
+    return wpi::json::json_pointer(std::string_view(s, n));
 }
 
 #ifndef WPI_JSON_IMPLEMENTATION
diff --git a/wpiutil/src/main/native/cpp/json_serializer.h b/wpiutil/src/main/native/include/wpi/json_serializer.h
similarity index 93%
rename from wpiutil/src/main/native/cpp/json_serializer.h
rename to wpiutil/src/main/native/include/wpi/json_serializer.h
index 6c5ffbe..3796c95 100644
--- a/wpiutil/src/main/native/cpp/json_serializer.h
+++ b/wpiutil/src/main/native/include/wpi/json_serializer.h
@@ -35,7 +35,6 @@
 
 #include <clocale> // lconv, localeconv
 #include <cmath>  // labs, isfinite, isnan, signbit, ldexp
-#include <locale> // locale
 #include <type_traits>
 
 #include "wpi/raw_ostream.h"
@@ -51,10 +50,11 @@
     /*!
     @param[in] s  output stream to serialize to
     @param[in] ichar  indentation character to use
+    @param[in] indent_init_len  initial length of indentation string buffer
     */
-    serializer(raw_ostream& s, const char ichar)
-        : o(s), loc(std::localeconv()), indent_char(ichar),
-          indent_string(512, indent_char)
+    serializer(raw_ostream& s, const char ichar, size_t indent_init_len = 512)
+        : o(s), indent_char(ichar),
+          indent_string(indent_init_len, indent_char)
     {}
 
     // delete because of pointer members
@@ -75,6 +75,7 @@
 
     @param[in] val             value to serialize
     @param[in] pretty_print    whether the output shall be pretty-printed
+    @param[in] ensure_ascii    whether the output shall only use ASCII chars
     @param[in] indent_step     the indent level
     @param[in] current_indent  the current indent level (only used internally)
     */
@@ -83,7 +84,6 @@
               const unsigned int indent_step,
               const unsigned int current_indent = 0);
 
-  private:
     /*!
     @brief dump escaped string
 
@@ -94,11 +94,11 @@
 
     @param[in] s  the string to escape
     @param[in] ensure_ascii  whether to escape non-ASCII characters with
-                             \uXXXX sequences
+                             "\uXXXX" sequences
 
-    @complexity Linear in the length of string @a s.
+    Complexity: Linear in the length of string @a s.
     */
-    void dump_escaped(StringRef s, const bool ensure_ascii);
+    void dump_escaped(std::string_view s, const bool ensure_ascii);
 
     template <typename NumberType,
               detail::enable_if_t<std::is_same_v<NumberType, uint64_t>, int> = 0>
@@ -198,9 +198,6 @@
     /// a (hopefully) large enough character buffer
     std::array<char, 64> number_buffer{{}};
 
-    /// the locale
-    const std::lconv* loc = nullptr;
-
     /// the indentation character
     const char indent_char;
     /// the indentation string
diff --git a/wpiutil/src/main/native/include/wpi/leb128.h b/wpiutil/src/main/native/include/wpi/leb128.h
index c70f471..04752c6 100644
--- a/wpiutil/src/main/native/include/wpi/leb128.h
+++ b/wpiutil/src/main/native/include/wpi/leb128.h
@@ -1,68 +1,112 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_LEB128_H_
 #define WPIUTIL_WPI_LEB128_H_
 
-#include <cstddef>
+#include <stdint.h>
 
-#include "wpi/SmallVector.h"
+#include <optional>
+
+#include "wpi/span.h"
 
 namespace wpi {
 
+template <typename T>
+class SmallVectorImpl;
 class raw_istream;
+class raw_ostream;
 
 /**
- * Get size of unsigned LEB128 data
- * @val: value
+ * Get size of unsigned LEB128 data.
  *
  * Determine the number of bytes required to encode an unsigned LEB128 datum.
  * The algorithm is taken from Appendix C of the DWARF 3 spec. For information
  * on the encodings refer to section "7.6 - Variable Length Data". Return
  * the number of bytes required.
+ *
+ * @param val LEB128 data.
  */
 uint64_t SizeUleb128(uint64_t val);
 
 /**
- * Write unsigned LEB128 data
- * @addr: the address where the ULEB128 data is to be stored
- * @val: value to be stored
+ * Write unsigned LEB128 data.
  *
  * Encode an unsigned LEB128 encoded datum. The algorithm is taken
  * from Appendix C of the DWARF 3 spec. For information on the
  * encodings refer to section "7.6 - Variable Length Data". Return
  * the number of bytes written.
+ *
+ * @param dest The address where the ULEB128 data is to be stored.
+ * @param val Value to be stored.
  */
 uint64_t WriteUleb128(SmallVectorImpl<char>& dest, uint64_t val);
 
 /**
- * Read unsigned LEB128 data
- * @addr: the address where the ULEB128 data is stored
- * @ret: address to store the result
+ * Write unsigned LEB128 data.
+ *
+ * Encode an unsigned LEB128 encoded datum. The algorithm is taken
+ * from Appendix C of the DWARF 3 spec. For information on the
+ * encodings refer to section "7.6 - Variable Length Data".
+ *
+ * @param os Output stream.
+ * @param val Value to be stored.
+ */
+void WriteUleb128(raw_ostream& os, uint64_t val);
+
+/**
+ * Read unsigned LEB128 data.
  *
  * Decode an unsigned LEB128 encoded datum. The algorithm is taken
  * from Appendix C of the DWARF 3 spec. For information on the
  * encodings refer to section "7.6 - Variable Length Data". Return
  * the number of bytes read.
+ *
+ * @param addr The address where the ULEB128 data is stored.
+ * @param ret Address to store the result.
  */
 uint64_t ReadUleb128(const char* addr, uint64_t* ret);
 
 /**
- * Read unsigned LEB128 data from a stream
- * @is: the input stream where the ULEB128 data is to be read from
- * @ret: address to store the result
+ * Read unsigned LEB128 data from a stream.
  *
  * Decode an unsigned LEB128 encoded datum. The algorithm is taken
  * from Appendix C of the DWARF 3 spec. For information on the
  * encodings refer to section "7.6 - Variable Length Data". Return
  * false on stream error, true on success.
+ *
+ * @param is The input stream where the ULEB128 data is to be read from.
+ * @param ret Address to store the result.
  */
 bool ReadUleb128(raw_istream& is, uint64_t* ret);
 
+/**
+ * Unsigned LEB128 streaming reader.
+ *
+ * Decode an unsigned LEB128 encoded datum. The algorithm is taken
+ * from Appendix C of the DWARF 3 spec. For information on the
+ * encodings refer to section "7.6 - Variable Length Data".
+ */
+class Uleb128Reader {
+ public:
+  /**
+   * Decode a single ULEB128 value.  Returns after a single ULEB128 value has
+   * been read or insufficient input (call in a loop to get multiple values).
+   * If a value is returned, internal state is reset so it's safe to immediately
+   * call this function again to decode another value.
+   *
+   * @param in Input data; modified as data is consumed (any unconsumed data
+   *           is left when function returns).
+   * @return value (in std::optional)
+   */
+  std::optional<uint64_t> ReadOne(span<const uint8_t>* in);
+
+ private:
+  uint64_t m_result = 0;
+  int m_shift = 0;
+};
+
 }  // namespace wpi
 
 #endif  // WPIUTIL_WPI_LEB128_H_
diff --git a/wpiutil/src/main/native/include/wpi/math b/wpiutil/src/main/native/include/wpi/math
index d9c18c7..25f2a64 100644
--- a/wpiutil/src/main/native/include/wpi/math
+++ b/wpiutil/src/main/native/include/wpi/math
@@ -1,67 +1,20 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
-#include <type_traits>
+#include "wpi/numbers"
+
+// clang-format off
+#ifdef _MSC_VER
+#pragma message("warning: Use <wpi/numbers> and wpi::numbers instead to reflect C++20 <numbers> and std::numbers")
+#else
+#warning "Use <wpi/numbers> and wpi::numbers instead to reflect C++20 <numbers> and std::numbers"
+#endif
+
+// clang-format on
 
 namespace wpi::math {
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T e_v = 2.718281828459045235360287471352662498L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T log2e_v = 1.442695040888963407359924681001892137L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T log10e_v = 0.434294481903251827651128918916605082L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T pi_v = 3.141592653589793238462643383279502884L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T inv_pi_v = 0.318309886183790671537767526745028724L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T inv_sqrtpi_v = 0.564189583547756286948079451560772586L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T ln2_v = 0.693147180559945309417232121458176568L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T ln10_v = 2.302585092994045684017991454684364208L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T sqrt2_v = 1.414213562373095048801688724209698078L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T sqrt3_v = 1.732050807568877293527446341505872366L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T inv_sqrt3_v = 0.577350269189625764509148780501957456L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T egamma_v = 0.577215664901532860606512090082402431L;
-
-template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
-inline constexpr T phi_v = 1.618033988749894848204586834365638117L;
-
-inline constexpr double e          = e_v<double>;
-inline constexpr double log2e      = log2e_v<double>;
-inline constexpr double log10e     = log10e_v<double>;
-inline constexpr double pi         = pi_v<double>;
-inline constexpr double inv_pi     = inv_pi_v<double>;
-inline constexpr double inv_sqrtpi = inv_sqrtpi_v<double>;
-inline constexpr double ln2        = ln2_v<double>;
-inline constexpr double ln10       = ln10_v<double>;
-inline constexpr double sqrt2      = sqrt2_v<double>;
-inline constexpr double sqrt3      = sqrt3_v<double>;
-inline constexpr double inv_sqrt3  = inv_sqrt3_v<double>;
-inline constexpr double egamma     = egamma_v<double>;
-inline constexpr double phi        = phi_v<double>;
-
+using namespace wpi::numbers;
 }  // namespace wpi::math
diff --git a/wpiutil/src/main/native/include/wpi/mpack.h b/wpiutil/src/main/native/include/wpi/mpack.h
new file mode 100644
index 0000000..870c26b
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/mpack.h
@@ -0,0 +1,8207 @@
+/**
+ * The MIT License (MIT)
+ * 
+ * Copyright (c) 2015-2021 Nicholas Fraser and the MPack authors
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ */
+
+/*
+ * This is the MPack 1.1 amalgamation package.
+ *
+ * http://github.com/ludocode/mpack
+ */
+
+#ifndef MPACK_H
+#define MPACK_H 1
+
+#define MPACK_AMALGAMATED 1
+
+#if defined(MPACK_HAS_CONFIG) && MPACK_HAS_CONFIG
+#include "mpack-config.h"
+#endif
+
+
+/* mpack/mpack-platform.h.h */
+
+/**
+ * @file
+ *
+ * Abstracts all platform-specific code from MPack and handles configuration
+ * options.
+ *
+ * This verifies the configuration and sets defaults based on the platform,
+ * contains implementations of standard C functions when libc is not available,
+ * and provides wrappers to all library functions.
+ *
+ * Documentation for configuration options is available here:
+ *
+ *     https://ludocode.github.io/mpack/group__config.html
+ */
+
+#ifndef MPACK_PLATFORM_H
+#define MPACK_PLATFORM_H 1
+
+
+
+/**
+ * @defgroup config Configuration Options
+ *
+ * Defines the MPack configuration options.
+ *
+ * Custom configuration of MPack is not usually necessary. In almost all
+ * cases you can ignore this and use the defaults.
+ *
+ * If you do want to configure MPack, you can define the below options as part
+ * of your build system or project settings. This will override the below
+ * defaults.
+ *
+ * If you'd like to use a file for configuration instead, define
+ * @ref MPACK_HAS_CONFIG to 1 in your build system or project settings.
+ * This will cause MPack to include a file you create called @c mpack-config.h
+ * in which you can define your configuration. This is useful if you need to
+ * include specific headers (such as a custom allocator) in order to configure
+ * MPack to use it.
+ *
+ * @warning The value of all configuration options must be the same in
+ * all translation units of your project, as well as in the mpack source
+ * itself. These configuration options affect the layout of structs, among
+ * other things, which cannot be different in source files that are linked
+ * together.
+ *
+ * @note MPack does not contain defaults for building inside the Linux kernel.
+ * There is a <a href="https://github.com/ludocode/mpack-linux-kernel">
+ * configuration file for the Linux kernel</a> that can be used instead.
+ *
+ * @{
+ */
+
+
+
+/*
+ * Pre-include checks
+ *
+ * These need to come before the user's mpack-config.h because they might be
+ * including headers in it.
+ */
+
+/** @cond */
+#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(__cplusplus)
+    #error "In Visual Studio 2012 and earlier, MPack must be compiled as C++. Enable the /Tp compiler flag."
+#endif
+
+#if defined(_WIN32) && MPACK_INTERNAL
+    #define _CRT_SECURE_NO_WARNINGS 1
+#endif
+
+#ifndef __STDC_LIMIT_MACROS
+    #define __STDC_LIMIT_MACROS 1
+#endif
+#ifndef __STDC_FORMAT_MACROS
+    #define __STDC_FORMAT_MACROS 1
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+    #define __STDC_CONSTANT_MACROS 1
+#endif
+/** @endcond */
+
+
+
+/**
+ * @name File Configuration
+ * @{
+ */
+
+/**
+ * @def MPACK_HAS_CONFIG
+ *
+ * Causes MPack to include a file you create called @c mpack-config.h .
+ *
+ * The file is included before MPack sets any defaults for undefined
+ * configuration options. You can use it to configure MPack.
+ *
+ * This is off by default.
+ */
+#if defined(MPACK_HAS_CONFIG)
+    #if MPACK_HAS_CONFIG
+        #include "mpack-config.h"
+    #endif
+#else
+    #define MPACK_HAS_CONFIG 0
+#endif
+
+/**
+ * @}
+ */
+
+// this needs to come first since some stuff depends on it
+/** @cond */
+#ifndef MPACK_NO_BUILTINS
+    #define MPACK_NO_BUILTINS 0
+#endif
+/** @endcond */
+
+
+
+/**
+ * @name Features
+ * @{
+ */
+
+/**
+ * @def MPACK_READER
+ *
+ * Enables compilation of the base Tag Reader.
+ */
+#ifndef MPACK_READER
+#define MPACK_READER 1
+#endif
+
+/**
+ * @def MPACK_EXPECT
+ *
+ * Enables compilation of the static Expect API.
+ */
+#ifndef MPACK_EXPECT
+#define MPACK_EXPECT 1
+#endif
+
+/**
+ * @def MPACK_NODE
+ *
+ * Enables compilation of the dynamic Node API.
+ */
+#ifndef MPACK_NODE
+#define MPACK_NODE 1
+#endif
+
+/**
+ * @def MPACK_WRITER
+ *
+ * Enables compilation of the Writer.
+ */
+#ifndef MPACK_WRITER
+#define MPACK_WRITER 1
+#endif
+
+/**
+ * @def MPACK_BUILDER
+ *
+ * Enables compilation of the Builder.
+ *
+ * The Builder API provides additional functions to the Writer for
+ * automatically determining the element count of compound elements so you do
+ * not have to specify them up-front.
+ *
+ * This requires a @c malloc(). It is enabled by default if MPACK_WRITER is
+ * enabled and MPACK_MALLOC is defined.
+ *
+ * @see mpack_build_map()
+ * @see mpack_build_array()
+ * @see mpack_complete_map()
+ * @see mpack_complete_array()
+ */
+// This is defined furthur below after we've resolved whether we have malloc().
+
+/**
+ * @def MPACK_COMPATIBILITY
+ *
+ * Enables compatibility features for reading and writing older
+ * versions of MessagePack.
+ *
+ * This is disabled by default. When disabled, the behaviour is equivalent to
+ * using the default version, @ref mpack_version_current.
+ *
+ * Enable this if you need to interoperate with applications or data that do
+ * not support the new (v5) MessagePack spec. See the section on v4
+ * compatibility in @ref docs/protocol.md for more information.
+ */
+#ifndef MPACK_COMPATIBILITY
+#define MPACK_COMPATIBILITY 0
+#endif
+
+/**
+ * @def MPACK_EXTENSIONS
+ *
+ * Enables the use of extension types.
+ *
+ * This is disabled by default. Define it to 1 to enable it. If disabled,
+ * functions to read and write extensions will not exist, and any occurrence of
+ * extension types in parsed messages will flag @ref mpack_error_invalid.
+ *
+ * MPack discourages the use of extension types. See the section on extension
+ * types in @ref docs/protocol.md for more information.
+ */
+#ifndef MPACK_EXTENSIONS
+#define MPACK_EXTENSIONS 0
+#endif
+
+/**
+ * @}
+ */
+
+
+
+// workarounds for Doxygen
+#if defined(MPACK_DOXYGEN)
+#if MPACK_DOXYGEN
+// We give these their default values of 0 here even though they are defined to
+// 1 in the doxyfile. Doxygen will show this as the value in the docs, even
+// though it ignores it when parsing the rest of the source. This is what we
+// want, since we want the documentation to show these defaults but still
+// generate documentation for the functions they add when they're on.
+#define MPACK_COMPATIBILITY 0
+#define MPACK_EXTENSIONS 0
+#endif
+#endif
+
+
+
+/**
+ * @name Dependencies
+ * @{
+ */
+
+/**
+ * @def MPACK_CONFORMING
+ *
+ * Enables the inclusion of basic C headers to define standard types and
+ * macros.
+ *
+ * This causes MPack to include headers required for conforming implementations
+ * of C99 even in freestanding, in particular <stddef.h>, <stdint.h>,
+ * <stdbool.h> and <limits.h>. It also includes <inttypes.h>; this is
+ * technically not required for freestanding but MPack needs it to detect
+ * integer limits.
+ *
+ * You can disable this if these headers are unavailable or if they do not
+ * define the standard types and macros (for example inside the Linux kernel.)
+ * If this is disabled, MPack will include no headers and will assume a 32-bit
+ * int. You will probably also want to define @ref MPACK_HAS_CONFIG to 1 and
+ * include your own headers in the config file. You must provide definitions
+ * for standard types such as @c size_t, @c bool, @c int32_t and so on.
+ *
+ * @see <a href="https://en.cppreference.com/w/c/language/conformance">
+ * cppreference.com documentation on Conformance</a>
+ */
+#ifndef MPACK_CONFORMING
+    #define MPACK_CONFORMING 1
+#endif
+
+/**
+ * @def MPACK_STDLIB
+ *
+ * Enables the use of the C stdlib.
+ *
+ * This allows the library to use basic functions like @c memcmp() and @c
+ * strlen(), as well as @c malloc() for debugging and in allocation helpers.
+ *
+ * If this is disabled, allocation helper functions will not be defined, and
+ * MPack will attempt to detect compiler intrinsics for functions like @c
+ * memcmp() (assuming @ref MPACK_NO_BUILTINS is not set.) It will fallback to
+ * its own (slow) implementations if it cannot use builtins. You may want to
+ * define @ref MPACK_MEMCMP and friends if you disable this.
+ *
+ * @see MPACK_MEMCMP
+ * @see MPACK_MEMCPY
+ * @see MPACK_MEMMOVE
+ * @see MPACK_MEMSET
+ * @see MPACK_STRLEN
+ * @see MPACK_MALLOC
+ * @see MPACK_REALLOC
+ * @see MPACK_FREE
+ */
+#ifndef MPACK_STDLIB
+    #if !MPACK_CONFORMING
+        // If we don't even have a proper <limits.h> we assume we won't have
+        // malloc() either.
+        #define MPACK_STDLIB 0
+    #else
+        #define MPACK_STDLIB 1
+    #endif
+#endif
+
+/**
+ * @def MPACK_STDIO
+ *
+ * Enables the use of C stdio. This adds helpers for easily
+ * reading/writing C files and makes debugging easier.
+ */
+#ifndef MPACK_STDIO
+    #if !MPACK_STDLIB || defined(__AVR__)
+        #define MPACK_STDIO 0
+    #else
+        #define MPACK_STDIO 1
+    #endif
+#endif
+
+/**
+ * Whether the 'float' type and floating point operations are supported.
+ *
+ * If @ref MPACK_FLOAT is disabled, floats are read and written as @c uint32_t
+ * instead. This way messages with floats do not result in errors and you can
+ * still perform manual float parsing yourself.
+ */
+#ifndef MPACK_FLOAT
+    #define MPACK_FLOAT 1
+#endif
+
+/**
+ * Whether the 'double' type is supported. This requires support for 'float'.
+ *
+ * If @ref MPACK_DOUBLE is disabled, doubles are read and written as @c
+ * uint32_t instead. This way messages with doubles do not result in errors and
+ * you can still perform manual doubles parsing yourself.
+ *
+ * If @ref MPACK_FLOAT is enabled but @ref MPACK_DOUBLE is not, doubles can be
+ * read as floats using the shortening conversion functions, e.g. @ref
+ * mpack_expect_float() or @ref mpack_node_float().
+ */
+#ifndef MPACK_DOUBLE
+    #if !MPACK_FLOAT || defined(__AVR__)
+        // AVR supports only float, not double.
+        #define MPACK_DOUBLE 0
+    #else
+        #define MPACK_DOUBLE 1
+    #endif
+#endif
+
+/**
+ * @}
+ */
+
+
+
+/**
+ * @name Allocation Functions
+ * @{
+ */
+
+/**
+ * @def MPACK_MALLOC
+ *
+ * Defines the memory allocation function used by MPack. This is used by
+ * helpers for automatically allocating data the correct size, and for
+ * debugging functions. If this macro is undefined, the allocation helpers
+ * will not be compiled.
+ *
+ * Set this to use a custom @c malloc() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * void* malloc(size_t size);
+ * @endcode
+ *
+ * The default is @c malloc() if @ref MPACK_STDLIB is enabled.
+ */
+/**
+ * @def MPACK_FREE
+ *
+ * Defines the memory free function used by MPack. This is used by helpers
+ * for automatically allocating data the correct size. If this macro is
+ * undefined, the allocation helpers will not be compiled.
+ *
+ * Set this to use a custom @c free() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * void free(void* p);
+ * @endcode
+ *
+ * The default is @c free() if @ref MPACK_MALLOC has not been customized and
+ * @ref MPACK_STDLIB is enabled.
+ */
+/**
+ * @def MPACK_REALLOC
+ *
+ * Defines the realloc function used by MPack. It is used by growable
+ * buffers to resize more efficiently.
+ *
+ * The default is @c realloc() if @ref MPACK_MALLOC has not been customized and
+ * @ref MPACK_STDLIB is enabled.
+ *
+ * Set this to use a custom @c realloc() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * void* realloc(void* p, size_t new_size);
+ * @endcode
+ *
+ * This is optional, even when @ref MPACK_MALLOC is used. If @ref MPACK_MALLOC is
+ * set and @ref MPACK_REALLOC is not, @ref MPACK_MALLOC is used with a simple copy
+ * to grow buffers.
+ */
+
+#if defined(MPACK_MALLOC) && !defined(MPACK_FREE)
+    #error "MPACK_MALLOC requires MPACK_FREE."
+#endif
+#if !defined(MPACK_MALLOC) && defined(MPACK_FREE)
+    #error "MPACK_FREE requires MPACK_MALLOC."
+#endif
+
+// These were never configurable in lowercase but we check anyway.
+#ifdef mpack_malloc
+    #error "Define MPACK_MALLOC, not mpack_malloc."
+#endif
+#ifdef mpack_realloc
+    #error "Define MPACK_REALLOC, not mpack_realloc."
+#endif
+#ifdef mpack_free
+    #error "Define MPACK_FREE, not mpack_free."
+#endif
+
+// We don't use calloc() at all.
+#ifdef MPACK_CALLOC
+    #error "Don't define MPACK_CALLOC. MPack does not use calloc()."
+#endif
+#ifdef mpack_calloc
+    #error "Don't define mpack_calloc. MPack does not use calloc()."
+#endif
+
+// Use defaults in stdlib if we have them. Without it we don't use malloc.
+#if defined(MPACK_STDLIB)
+    #if MPACK_STDLIB && !defined(MPACK_MALLOC)
+        #define MPACK_MALLOC malloc
+        #define MPACK_REALLOC realloc
+        #define MPACK_FREE free
+    #endif
+#endif
+
+/**
+ * @}
+ */
+
+
+
+// This needs to be defined after we've decided whether we have malloc().
+#ifndef MPACK_BUILDER
+    #if defined(MPACK_MALLOC) && MPACK_WRITER
+        #define MPACK_BUILDER 1
+    #else
+        #define MPACK_BUILDER 0
+    #endif
+#endif
+
+
+
+/**
+ * @name System Functions
+ * @{
+ */
+
+/**
+ * @def MPACK_MEMCMP
+ *
+ * The function MPack will use to provide @c memcmp().
+ *
+ * Set this to use a custom @c memcmp() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * int memcmp(const void* left, const void* right, size_t count);
+ * @endcode
+ */
+/**
+ * @def MPACK_MEMCPY
+ *
+ * The function MPack will use to provide @c memcpy().
+ *
+ * Set this to use a custom @c memcpy() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * void* memcpy(void* restrict dest, const void* restrict src, size_t count);
+ * @endcode
+ */
+/**
+ * @def MPACK_MEMMOVE
+ *
+ * The function MPack will use to provide @c memmove().
+ *
+ * Set this to use a custom @c memmove() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * void* memmove(void* dest, const void* src, size_t count);
+ * @endcode
+ */
+/**
+ * @def MPACK_MEMSET
+ *
+ * The function MPack will use to provide @c memset().
+ *
+ * Set this to use a custom @c memset() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * void* memset(void* p, int c, size_t count);
+ * @endcode
+ */
+/**
+ * @def MPACK_STRLEN
+ *
+ * The function MPack will use to provide @c strlen().
+ *
+ * Set this to use a custom @c strlen() function. Your function must have the
+ * signature:
+ *
+ * @code
+ * size_t strlen(const char* str);
+ * @endcode
+ */
+
+// These were briefly configurable in lowercase in an unreleased version. Just
+// to make sure no one is doing this, we make sure these aren't already defined.
+#ifdef mpack_memcmp
+    #error "Define MPACK_MEMCMP, not mpack_memcmp."
+#endif
+#ifdef mpack_memcpy
+    #error "Define MPACK_MEMCPY, not mpack_memcpy."
+#endif
+#ifdef mpack_memmove
+    #error "Define MPACK_MEMMOVE, not mpack_memmove."
+#endif
+#ifdef mpack_memset
+    #error "Define MPACK_MEMSET, not mpack_memset."
+#endif
+#ifdef mpack_strlen
+    #error "Define MPACK_STRLEN, not mpack_strlen."
+#endif
+
+// If the standard library is available, we prefer to use its functions.
+#if MPACK_STDLIB
+    #ifndef MPACK_MEMCMP
+        #define MPACK_MEMCMP memcmp
+    #endif
+    #ifndef MPACK_MEMCPY
+        #define MPACK_MEMCPY memcpy
+    #endif
+    #ifndef MPACK_MEMMOVE
+        #define MPACK_MEMMOVE memmove
+    #endif
+    #ifndef MPACK_MEMSET
+        #define MPACK_MEMSET memset
+    #endif
+    #ifndef MPACK_STRLEN
+        #define MPACK_STRLEN strlen
+    #endif
+#endif
+
+#if !MPACK_NO_BUILTINS
+    #ifdef __has_builtin
+        #if !defined(MPACK_MEMCMP) && __has_builtin(__builtin_memcmp)
+            #define MPACK_MEMCMP __builtin_memcmp
+        #endif
+        #if !defined(MPACK_MEMCPY) && __has_builtin(__builtin_memcpy)
+            #define MPACK_MEMCPY __builtin_memcpy
+        #endif
+        #if !defined(MPACK_MEMMOVE) && __has_builtin(__builtin_memmove)
+            #define MPACK_MEMMOVE __builtin_memmove
+        #endif
+        #if !defined(MPACK_MEMSET) && __has_builtin(__builtin_memset)
+            #define MPACK_MEMSET __builtin_memset
+        #endif
+        #if !defined(MPACK_STRLEN) && __has_builtin(__builtin_strlen)
+            #define MPACK_STRLEN __builtin_strlen
+        #endif
+    #elif defined(__GNUC__)
+        #ifndef MPACK_MEMCMP
+            #define MPACK_MEMCMP __builtin_memcmp
+        #endif
+        #ifndef MPACK_MEMCPY
+            #define MPACK_MEMCPY __builtin_memcpy
+        #endif
+        // There's not always a builtin memmove for GCC. If we can't test for
+        // it with __has_builtin above, we don't use it. It's been around for
+        // much longer under clang, but then so has __has_builtin, so we let
+        // the block above handle it.
+        #ifndef MPACK_MEMSET
+            #define MPACK_MEMSET __builtin_memset
+        #endif
+        #ifndef MPACK_STRLEN
+            #define MPACK_STRLEN __builtin_strlen
+        #endif
+    #endif
+#endif
+
+/**
+ * @}
+ */
+
+
+
+/**
+ * @name Debugging Options
+ * @{
+ */
+
+/**
+ * @def MPACK_DEBUG
+ *
+ * Enables debug features. You may want to wrap this around your
+ * own debug preprocs. By default, this is enabled if @c DEBUG or @c _DEBUG
+ * are defined. (@c NDEBUG is not used since it is allowed to have
+ * different values in different translation units.)
+ */
+#if !defined(MPACK_DEBUG)
+    #if defined(DEBUG) || defined(_DEBUG)
+        #define MPACK_DEBUG 1
+    #else
+        #define MPACK_DEBUG 0
+    #endif
+#endif
+
+/**
+ * @def MPACK_STRINGS
+ *
+ * Enables descriptive error and type strings.
+ *
+ * This can be turned off (by defining it to 0) to maximize space savings
+ * on embedded devices. If this is disabled, string functions such as
+ * mpack_error_to_string() and mpack_type_to_string() return an empty string.
+ */
+#ifndef MPACK_STRINGS
+    #ifdef __AVR__
+        #define MPACK_STRINGS 0
+    #else
+        #define MPACK_STRINGS 1
+    #endif
+#endif
+
+/**
+ * Set this to 1 to implement a custom @ref mpack_assert_fail() function.
+ * See the documentation on @ref mpack_assert_fail() for details.
+ *
+ * Asserts are only used when @ref MPACK_DEBUG is enabled, and can be
+ * triggered by bugs in MPack or bugs due to incorrect usage of MPack.
+ */
+#ifndef MPACK_CUSTOM_ASSERT
+#define MPACK_CUSTOM_ASSERT 0
+#endif
+
+/**
+ * @def MPACK_READ_TRACKING
+ *
+ * Enables compound type size tracking for readers. This ensures that the
+ * correct number of elements or bytes are read from a compound type.
+ *
+ * This is enabled by default in debug builds (provided a @c malloc() is
+ * available.)
+ */
+#if !defined(MPACK_READ_TRACKING)
+    #if MPACK_DEBUG && MPACK_READER && defined(MPACK_MALLOC)
+        #define MPACK_READ_TRACKING 1
+    #else
+        #define MPACK_READ_TRACKING 0
+    #endif
+#endif
+#if MPACK_READ_TRACKING && !MPACK_READER
+    #error "MPACK_READ_TRACKING requires MPACK_READER."
+#endif
+
+/**
+ * @def MPACK_WRITE_TRACKING
+ *
+ * Enables compound type size tracking for writers. This ensures that the
+ * correct number of elements or bytes are written in a compound type.
+ *
+ * Note that without write tracking enabled, it is possible for buggy code
+ * to emit invalid MessagePack without flagging an error by writing the wrong
+ * number of elements or bytes in a compound type. With tracking enabled,
+ * MPack will catch such errors and break on the offending line of code.
+ *
+ * This is enabled by default in debug builds (provided a @c malloc() is
+ * available.)
+ */
+#if !defined(MPACK_WRITE_TRACKING)
+    #if MPACK_DEBUG && MPACK_WRITER && defined(MPACK_MALLOC)
+        #define MPACK_WRITE_TRACKING 1
+    #else
+        #define MPACK_WRITE_TRACKING 0
+    #endif
+#endif
+#if MPACK_WRITE_TRACKING && !MPACK_WRITER
+    #error "MPACK_WRITE_TRACKING requires MPACK_WRITER."
+#endif
+
+/**
+ * @}
+ */
+
+
+
+
+/**
+ * @name Miscellaneous Options
+ * @{
+ */
+
+/**
+ * Whether to optimize for size or speed.
+ *
+ * Optimizing for size simplifies some parsing and encoding algorithms
+ * at the expense of speed and saves a few kilobytes of space in the
+ * resulting executable.
+ *
+ * This automatically detects -Os with GCC/Clang. Unfortunately there
+ * doesn't seem to be a macro defined for /Os under MSVC.
+ */
+#ifndef MPACK_OPTIMIZE_FOR_SIZE
+    #ifdef __OPTIMIZE_SIZE__
+        #define MPACK_OPTIMIZE_FOR_SIZE 1
+    #else
+        #define MPACK_OPTIMIZE_FOR_SIZE 0
+    #endif
+#endif
+
+/**
+ * Stack space in bytes to use when initializing a reader or writer
+ * with a stack-allocated buffer.
+ *
+ * @warning Make sure you have sufficient stack space. Some libc use relatively
+ * small stacks even on desktop platforms, e.g. musl.
+ */
+#ifndef MPACK_STACK_SIZE
+#define MPACK_STACK_SIZE 4096
+#endif
+
+/**
+ * Buffer size to use for allocated buffers (such as for a file writer.)
+ *
+ * Starting with a single page and growing as needed seems to
+ * provide the best performance with minimal memory waste.
+ * Increasing this does not improve performance even when writing
+ * huge messages.
+ */
+#ifndef MPACK_BUFFER_SIZE
+#define MPACK_BUFFER_SIZE 4096
+#endif
+
+/**
+ * Minimum size for paged allocations in bytes.
+ *
+ * This is the value used by default for MPACK_NODE_PAGE_SIZE and
+ * MPACK_BUILDER_PAGE_SIZE.
+ */
+#ifndef MPACK_PAGE_SIZE
+#define MPACK_PAGE_SIZE 4096
+#endif
+
+/**
+ * Minimum size of an allocated node page in bytes.
+ *
+ * The children for a given compound element must be contiguous, so
+ * larger pages than this may be allocated as needed. (Safety checks
+ * exist to prevent malicious data from causing too large allocations.)
+ *
+ * See @ref mpack_node_data_t for the size of nodes.
+ *
+ * Using as many nodes fit in one memory page seems to provide the
+ * best performance, and has very little waste when parsing small
+ * messages.
+ */
+#ifndef MPACK_NODE_PAGE_SIZE
+#define MPACK_NODE_PAGE_SIZE MPACK_PAGE_SIZE
+#endif
+
+/**
+ * Minimum size of an allocated builder page in bytes.
+ *
+ * Builder writes are deferred to the allocated builder buffer which is
+ * composed of a list of buffer pages. This defines the size of those pages.
+ */
+#ifndef MPACK_BUILDER_PAGE_SIZE
+#define MPACK_BUILDER_PAGE_SIZE MPACK_PAGE_SIZE
+#endif
+
+/**
+ * @def MPACK_BUILDER_INTERNAL_STORAGE
+ *
+ * Enables a small amount of internal storage within the writer to avoid some
+ * allocations when using builders.
+ *
+ * This is disabled by default. Enable it to potentially improve performance at
+ * the expense of a larger writer.
+ *
+ * @see MPACK_BUILDER_INTERNAL_STORAGE_SIZE to configure its size.
+ */
+#ifndef MPACK_BUILDER_INTERNAL_STORAGE
+#define MPACK_BUILDER_INTERNAL_STORAGE 0
+#endif
+
+/**
+ * Amount of space reserved inside @ref mpack_writer_t for the Builders. This
+ * can allow small messages to be built with the Builder API without incurring
+ * an allocation.
+ *
+ * Builder metadata is placed in this space in addition to the literal
+ * MessagePack data. It needs to be big enough to be useful, but not so big as
+ * to overflow the stack. If more space is needed, pages are allocated.
+ *
+ * This is only used if MPACK_BUILDER_INTERNAL_STORAGE is enabled.
+ *
+ * @see MPACK_BUILDER_PAGE_SIZE
+ * @see MPACK_BUILDER_INTERNAL_STORAGE
+ *
+ * @warning Writers are typically placed on the stack so make sure you have
+ * sufficient stack space. Some libc use relatively small stacks even on
+ * desktop platforms, e.g. musl.
+ */
+#ifndef MPACK_BUILDER_INTERNAL_STORAGE_SIZE
+#define MPACK_BUILDER_INTERNAL_STORAGE_SIZE 256
+#endif
+
+/**
+ * The initial depth for the node parser. When MPACK_MALLOC is available,
+ * the node parser has no practical depth limit, and it is not recursive
+ * so there is no risk of overflowing the call stack.
+ */
+#ifndef MPACK_NODE_INITIAL_DEPTH
+#define MPACK_NODE_INITIAL_DEPTH 8
+#endif
+
+/**
+ * The maximum depth for the node parser if @ref MPACK_MALLOC is not available.
+ */
+#ifndef MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC
+#define MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC 32
+#endif
+
+/**
+ * @def MPACK_NO_BUILTINS
+ *
+ * Whether to disable compiler intrinsics and other built-in functions.
+ *
+ * If this is enabled, MPack won't use `__attribute__`, `__declspec`, any
+ * function starting with `__builtin`, or pretty much anything else that isn't
+ * standard C.
+ */
+#if defined(MPACK_DOXYGEN)
+#if MPACK_DOXYGEN
+    #define MPACK_NO_BUILTINS 0
+#endif
+#endif
+
+/**
+ * @}
+ */
+
+
+
+#if MPACK_DEBUG
+/**
+ * @name Debug Functions
+ * @{
+ */
+/**
+ * Implement this and define @ref MPACK_CUSTOM_ASSERT to use a custom
+ * assertion function.
+ *
+ * This function should not return. If it does, MPack will @c abort().
+ *
+ * If you use C++, make sure you include @c mpack.h where you define
+ * this to get the correct linkage (or define it <code>extern "C"</code>.)
+ *
+ * Asserts are only used when @ref MPACK_DEBUG is enabled, and can be
+ * triggered by bugs in MPack or bugs due to incorrect usage of MPack.
+ */
+void mpack_assert_fail(const char* message);
+/**
+ * @}
+ */
+#endif
+
+
+
+// The rest of this file shouldn't show up in Doxygen docs.
+/** @cond */
+
+
+
+/*
+ * All remaining pseudo-configuration options that have not yet been set must
+ * be defined here in order to support -Wundef.
+ *
+ * These aren't real configuration options; they are implementation details of
+ * MPack.
+ */
+#ifndef MPACK_CUSTOM_BREAK
+#define MPACK_CUSTOM_BREAK 0
+#endif
+#ifndef MPACK_EMIT_INLINE_DEFS
+#define MPACK_EMIT_INLINE_DEFS 0
+#endif
+#ifndef MPACK_AMALGAMATED
+#define MPACK_AMALGAMATED 0
+#endif
+#ifndef MPACK_RELEASE_VERSION
+#define MPACK_RELEASE_VERSION 0
+#endif
+#ifndef MPACK_INTERNAL
+#define MPACK_INTERNAL 0
+#endif
+
+
+
+/* System headers (based on configuration) */
+
+#if MPACK_CONFORMING
+    #include <stddef.h>
+    #include <stdint.h>
+    #include <stdbool.h>
+    #include <inttypes.h>
+    #include <limits.h>
+#endif
+
+#if MPACK_STDLIB
+    #include <string.h>
+    #include <stdlib.h>
+#endif
+
+#if MPACK_STDIO
+    #include <stdio.h>
+    #include <errno.h>
+    #if MPACK_DEBUG
+        #include <stdarg.h>
+    #endif
+#endif
+
+
+
+/*
+ * Integer Constants and Limits
+ */
+
+#if MPACK_CONFORMING
+    #define MPACK_INT64_C INT64_C
+    #define MPACK_UINT64_C UINT64_C
+
+    #define MPACK_INT8_MIN INT8_MIN
+    #define MPACK_INT16_MIN INT16_MIN
+    #define MPACK_INT32_MIN INT32_MIN
+    #define MPACK_INT64_MIN INT64_MIN
+    #define MPACK_INT_MIN INT_MIN
+
+    #define MPACK_INT8_MAX INT8_MAX
+    #define MPACK_INT16_MAX INT16_MAX
+    #define MPACK_INT32_MAX INT32_MAX
+    #define MPACK_INT64_MAX INT64_MAX
+    #define MPACK_INT_MAX INT_MAX
+
+    #define MPACK_UINT8_MAX UINT8_MAX
+    #define MPACK_UINT16_MAX UINT16_MAX
+    #define MPACK_UINT32_MAX UINT32_MAX
+    #define MPACK_UINT64_MAX UINT64_MAX
+    #define MPACK_UINT_MAX UINT_MAX
+#else
+    // For a non-conforming implementation we assume int is 32 bits.
+
+    #define MPACK_INT64_C(x) ((int64_t)(x##LL))
+    #define MPACK_UINT64_C(x) ((uint64_t)(x##LLU))
+
+    #define MPACK_INT8_MIN ((int8_t)(0x80))
+    #define MPACK_INT16_MIN ((int16_t)(0x8000))
+    #define MPACK_INT32_MIN ((int32_t)(0x80000000))
+    #define MPACK_INT64_MIN MPACK_INT64_C(0x8000000000000000)
+    #define MPACK_INT_MIN MPACK_INT32_MIN
+
+    #define MPACK_INT8_MAX ((int8_t)(0x7f))
+    #define MPACK_INT16_MAX ((int16_t)(0x7fff))
+    #define MPACK_INT32_MAX ((int32_t)(0x7fffffff))
+    #define MPACK_INT64_MAX MPACK_INT64_C(0x7fffffffffffffff)
+    #define MPACK_INT_MAX MPACK_INT32_MAX
+
+    #define MPACK_UINT8_MAX ((uint8_t)(0xffu))
+    #define MPACK_UINT16_MAX ((uint16_t)(0xffffu))
+    #define MPACK_UINT32_MAX ((uint32_t)(0xffffffffu))
+    #define MPACK_UINT64_MAX MPACK_UINT64_C(0xffffffffffffffff)
+    #define MPACK_UINT_MAX MPACK_UINT32_MAX
+#endif
+
+
+
+/*
+ * Floating point support
+ */
+
+#if MPACK_DOUBLE && !MPACK_FLOAT
+    #error "MPACK_DOUBLE requires MPACK_FLOAT."
+#endif
+
+// If we don't have support for float or double, we poison the identifiers to
+// make sure we don't define anything related to them.
+#if MPACK_INTERNAL
+    #ifdef __GNUC__
+        #if !MPACK_FLOAT
+            #pragma GCC poison float
+        #endif
+        #if !MPACK_DOUBLE
+            #pragma GCC poison double
+        #endif
+    #endif
+#endif
+
+
+
+/*
+ * extern C
+ */
+
+#ifdef __cplusplus
+    #define MPACK_EXTERN_C_BEGIN namespace mpack {
+    #define MPACK_EXTERN_C_END   }
+#else
+    #define MPACK_EXTERN_C_BEGIN /*nothing*/
+    #define MPACK_EXTERN_C_END   /*nothing*/
+#endif
+
+
+
+/*
+ * Warnings
+ */
+
+#if defined(__GNUC__)
+    // Diagnostic push is not supported before GCC 4.6.
+    #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+        #define MPACK_SILENCE_WARNINGS_PUSH _Pragma ("GCC diagnostic push")
+        #define MPACK_SILENCE_WARNINGS_POP _Pragma ("GCC diagnostic pop")
+    #endif
+#elif defined(_MSC_VER)
+    // To support VS2017 and earlier we need to use __pragma and not _Pragma
+    #define MPACK_SILENCE_WARNINGS_PUSH __pragma(warning(push))
+    #define MPACK_SILENCE_WARNINGS_POP __pragma(warning(pop))
+#endif
+
+#if defined(_MSC_VER)
+    // These are a bunch of mostly useless warnings emitted under MSVC /W4,
+    // some as a result of the expansion of macros.
+    #define MPACK_SILENCE_WARNINGS_MSVC_W4 \
+            __pragma(warning(disable:4996)) /* _CRT_SECURE_NO_WARNINGS */ \
+            __pragma(warning(disable:4127)) /* comparison is constant */ \
+            __pragma(warning(disable:4702)) /* unreachable code */ \
+            __pragma(warning(disable:4310)) /* cast truncates constant value */
+#else
+    #define MPACK_SILENCE_WARNINGS_MSVC_W4 /*nothing*/
+#endif
+
+/* GCC versions before 5.1 warn about defining a C99 non-static inline function
+ * before declaring it (see issue #20). */
+#if defined(__GNUC__) && !defined(__clang__)
+    #if __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 1)
+        #ifdef __cplusplus
+            #define MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES \
+                _Pragma ("GCC diagnostic ignored \"-Wmissing-declarations\"")
+        #else
+            #define MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES \
+                _Pragma ("GCC diagnostic ignored \"-Wmissing-prototypes\"")
+        #endif
+    #endif
+#endif
+#ifndef MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES
+    #define MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES /*nothing*/
+#endif
+
+/* GCC versions before 4.8 warn about shadowing a function with a variable that
+ * isn't a function or function pointer (like "index"). */
+#if defined(__GNUC__) && !defined(__clang__)
+    #if __GNUC__ == 4 && __GNUC_MINOR__ < 8
+        #define MPACK_SILENCE_WARNINGS_SHADOW \
+            _Pragma ("GCC diagnostic ignored \"-Wshadow\"")
+    #endif
+#endif
+#ifndef MPACK_SILENCE_WARNINGS_SHADOW
+    #define MPACK_SILENCE_WARNINGS_SHADOW /*nothing*/
+#endif
+
+// On platforms with small size_t (e.g. AVR) we get type limits warnings where
+// we compare a size_t to e.g. MPACK_UINT32_MAX.
+#ifdef __AVR__
+    #define MPACK_SILENCE_WARNINGS_TYPE_LIMITS \
+        _Pragma ("GCC diagnostic ignored \"-Wtype-limits\"")
+#else
+    #define MPACK_SILENCE_WARNINGS_TYPE_LIMITS /*nothing*/
+#endif
+
+// MPack uses declarations after statements. This silences warnings about it
+// (e.g. when using MPack in a Linux kernel module.)
+#if defined(__GNUC__) && !defined(__cplusplus)
+    #define MPACK_SILENCE_WARNINGS_DECLARATION_AFTER_STATEMENT \
+        _Pragma ("GCC diagnostic ignored \"-Wdeclaration-after-statement\"")
+#else
+    #define MPACK_SILENCE_WARNINGS_DECLARATION_AFTER_STATEMENT /*nothing*/
+#endif
+
+#ifdef MPACK_SILENCE_WARNINGS_PUSH
+    // We only silence warnings if push/pop is supported, that way we aren't
+    // silencing warnings in code that uses MPack. If your compiler doesn't
+    // support push/pop silencing of warnings, you'll have to turn off
+    // conflicting warnings manually.
+
+    #define MPACK_SILENCE_WARNINGS_BEGIN \
+        MPACK_SILENCE_WARNINGS_PUSH \
+        MPACK_SILENCE_WARNINGS_MSVC_W4 \
+        MPACK_SILENCE_WARNINGS_MISSING_PROTOTYPES \
+        MPACK_SILENCE_WARNINGS_SHADOW \
+        MPACK_SILENCE_WARNINGS_TYPE_LIMITS \
+        MPACK_SILENCE_WARNINGS_DECLARATION_AFTER_STATEMENT
+
+    #define MPACK_SILENCE_WARNINGS_END \
+        MPACK_SILENCE_WARNINGS_POP
+#else
+    #define MPACK_SILENCE_WARNINGS_BEGIN /*nothing*/
+    #define MPACK_SILENCE_WARNINGS_END /*nothing*/
+#endif
+
+MPACK_SILENCE_WARNINGS_BEGIN
+MPACK_EXTERN_C_BEGIN
+
+
+
+/* Miscellaneous helper macros */
+
+#define MPACK_UNUSED(var) ((void)(var))
+
+#define MPACK_STRINGIFY_IMPL(arg) #arg
+#define MPACK_STRINGIFY(arg) MPACK_STRINGIFY_IMPL(arg)
+
+// This is a workaround for MSVC's incorrect expansion of __VA_ARGS__. It
+// treats __VA_ARGS__ as a single preprocessor token when passed in the
+// argument list of another macro unless we use an outer wrapper to expand it
+// lexically first. (For safety/consistency we use this in all variadic macros
+// that don't ignore the variadic arguments regardless of whether __VA_ARGS__
+// is passed to another macro.)
+//     https://stackoverflow.com/a/32400131
+#define MPACK_EXPAND(x) x
+
+// Extracts the first argument of a variadic macro list, where there might only
+// be one argument.
+#define MPACK_EXTRACT_ARG0_IMPL(first, ...) first
+#define MPACK_EXTRACT_ARG0(...) MPACK_EXPAND(MPACK_EXTRACT_ARG0_IMPL( __VA_ARGS__ , ignored))
+
+// Stringifies the first argument of a variadic macro list, where there might
+// only be one argument.
+#define MPACK_STRINGIFY_ARG0_impl(first, ...) #first
+#define MPACK_STRINGIFY_ARG0(...) MPACK_EXPAND(MPACK_STRINGIFY_ARG0_impl( __VA_ARGS__ , ignored))
+
+
+
+/*
+ * Definition of inline macros.
+ *
+ * MPack does not use static inline in header files; only one non-inline definition
+ * of each function should exist in the final build. This can reduce the binary size
+ * in cases where the compiler cannot or chooses not to inline a function.
+ * The addresses of functions should also compare equal across translation units
+ * regardless of whether they are declared inline.
+ *
+ * The above requirements mean that the declaration and definition of non-trivial
+ * inline functions must be separated so that the definitions will only
+ * appear when necessary. In addition, three different linkage models need
+ * to be supported:
+ *
+ *  - The C99 model, where a standalone function is emitted only if there is any
+ *    `extern inline` or non-`inline` declaration (including the definition itself)
+ *
+ *  - The GNU model, where an `inline` definition emits a standalone function and an
+ *    `extern inline` definition does not, regardless of other declarations
+ *
+ *  - The C++ model, where `inline` emits a standalone function with special
+ *    (COMDAT) linkage
+ *
+ * The macros below wrap up everything above. All inline functions defined in header
+ * files have a single non-inline definition emitted in the compilation of
+ * mpack-platform.c. All inline declarations and definitions use the same MPACK_INLINE
+ * specification to simplify the rules on when standalone functions are emitted.
+ * Inline functions in source files are defined MPACK_STATIC_INLINE.
+ *
+ * Additional reading:
+ *     http://www.greenend.org.uk/rjk/tech/inline.html
+ */
+
+#if defined(__cplusplus)
+    // C++ rules
+    // The linker will need COMDAT support to link C++ object files,
+    // so we don't need to worry about emitting definitions from C++
+    // translation units. If mpack-platform.c (or the amalgamation)
+    // is compiled as C, its definition will be used, otherwise a
+    // C++ definition will be used, and no other C files will emit
+    // a definition.
+    #define MPACK_INLINE inline
+
+#elif defined(_MSC_VER)
+    // MSVC 2013 always uses COMDAT linkage, but it doesn't treat 'inline' as a
+    // keyword in C99 mode. (This appears to be fixed in a later version of
+    // MSVC but we don't bother detecting it.)
+    #define MPACK_INLINE __inline
+    #define MPACK_STATIC_INLINE static __inline
+
+#elif defined(__GNUC__) && (defined(__GNUC_GNU_INLINE__) || \
+        (!defined(__GNUC_STDC_INLINE__) && !defined(__GNUC_GNU_INLINE__)))
+    // GNU rules
+    #if MPACK_EMIT_INLINE_DEFS
+        #define MPACK_INLINE inline
+    #else
+        #define MPACK_INLINE extern inline
+    #endif
+
+#elif defined(__TINYC__)
+    // tcc ignores the inline keyword, so we have to use static inline. We
+    // issue a warning to make sure you are aware. You can define the below
+    // macro to disable the warning. Hopefully this will be fixed soon:
+    //     https://lists.nongnu.org/archive/html/tinycc-devel/2019-06/msg00000.html
+    #ifndef MPACK_DISABLE_TINYC_INLINE_WARNING
+        #warning "Single-definition inline is not supported by tcc. All inlines will be static. Define MPACK_DISABLE_TINYC_INLINE_WARNING to disable this warning."
+    #endif
+    #define MPACK_INLINE static inline
+
+#else
+    // C99 rules
+    #if MPACK_EMIT_INLINE_DEFS
+        #define MPACK_INLINE extern inline
+    #else
+        #define MPACK_INLINE inline
+    #endif
+#endif
+
+#ifndef MPACK_STATIC_INLINE
+#define MPACK_STATIC_INLINE static inline
+#endif
+
+#ifdef MPACK_OPTIMIZE_FOR_SPEED
+    #error "You should define MPACK_OPTIMIZE_FOR_SIZE, not MPACK_OPTIMIZE_FOR_SPEED."
+#endif
+
+
+
+/*
+ * Prevent inlining
+ *
+ * When a function is only used once, it is almost always inlined
+ * automatically. This can cause poor instruction cache usage because a
+ * function that should rarely be called (such as buffer exhaustion handling)
+ * will get inlined into the middle of a hot code path.
+ */
+
+#if !MPACK_NO_BUILTINS
+    #if defined(_MSC_VER)
+        #define MPACK_NOINLINE __declspec(noinline)
+    #elif defined(__GNUC__) || defined(__clang__)
+        #define MPACK_NOINLINE __attribute__((__noinline__))
+    #endif
+#endif
+#ifndef MPACK_NOINLINE
+    #define MPACK_NOINLINE /* nothing */
+#endif
+
+
+
+/* restrict */
+
+// We prefer the builtins even though e.g. MSVC's __restrict may not have
+// exactly the same behaviour as the proper C99 restrict keyword because the
+// builtins work in C++, so using the same keyword in both C and C++ prevents
+// any incompatibilities when using MPack compiled as C in C++ code.
+#if !MPACK_NO_BUILTINS
+    #if defined(__GNUC__)
+        #define MPACK_RESTRICT __restrict__
+    #elif defined(_MSC_VER)
+        #define MPACK_RESTRICT __restrict
+    #endif
+#endif
+
+#ifndef MPACK_RESTRICT
+    #ifdef __cplusplus
+        #define MPACK_RESTRICT /* nothing, unavailable in C++ */
+    #endif
+#endif
+
+#ifndef MPACK_RESTRICT
+    #ifdef _MSC_VER
+        // MSVC 2015 apparently doesn't properly support the restrict keyword
+        // in C. We're using builtins above which do work on 2015, but when
+        // MPACK_NO_BUILTINS is enabled we can't use it.
+        #if _MSC_VER < 1910
+            #define MPACK_RESTRICT /*nothing*/
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_RESTRICT
+    #define MPACK_RESTRICT restrict /* required in C99 */
+#endif
+
+
+
+/* Some compiler-specific keywords and builtins */
+
+#if !MPACK_NO_BUILTINS
+    #if defined(__GNUC__) || defined(__clang__)
+        #define MPACK_UNREACHABLE __builtin_unreachable()
+        #define MPACK_NORETURN(fn) fn __attribute__((__noreturn__))
+    #elif defined(_MSC_VER)
+        #define MPACK_UNREACHABLE __assume(0)
+        #define MPACK_NORETURN(fn) __declspec(noreturn) fn
+    #endif
+#endif
+
+#ifndef MPACK_UNREACHABLE
+#define MPACK_UNREACHABLE ((void)0)
+#endif
+#ifndef MPACK_NORETURN
+#define MPACK_NORETURN(fn) fn
+#endif
+
+
+
+/*
+ * Likely/unlikely
+ *
+ * These should only really be used when a branch is taken (or not taken) less
+ * than 1/1000th of the time. Buffer flush checks when writing very small
+ * elements are a good example.
+ */
+
+#if !MPACK_NO_BUILTINS
+    #if defined(__GNUC__) || defined(__clang__)
+        #ifndef MPACK_LIKELY
+            #define MPACK_LIKELY(x) __builtin_expect((x),1)
+        #endif
+        #ifndef MPACK_UNLIKELY
+            #define MPACK_UNLIKELY(x) __builtin_expect((x),0)
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_LIKELY
+    #define MPACK_LIKELY(x) (x)
+#endif
+#ifndef MPACK_UNLIKELY
+    #define MPACK_UNLIKELY(x) (x)
+#endif
+
+
+
+/* alignof */
+
+#ifndef MPACK_ALIGNOF
+    #if defined(__STDC_VERSION__)
+        #if __STDC_VERSION__ >= 201112L
+            #define MPACK_ALIGNOF(T) (_Alignof(T))
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_ALIGNOF
+    #if defined(__cplusplus)
+        #if __cplusplus >= 201103L
+            #define MPACK_ALIGNOF(T) (alignof(T))
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_ALIGNOF
+    #if defined(__GNUC__) && !defined(MPACK_NO_BUILTINS)
+        #if defined(__clang__) || __GNUC__ >= 4
+            #define MPACK_ALIGNOF(T) (__alignof__(T))
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_ALIGNOF
+    #ifdef _MSC_VER
+        #define MPACK_ALIGNOF(T) __alignof(T)
+    #endif
+#endif
+
+// MPACK_ALIGNOF may not exist, in which case a workaround is used.
+
+
+
+/* Static assert */
+
+#ifndef MPACK_STATIC_ASSERT
+    #if defined(__cplusplus)
+        #if __cplusplus >= 201103L
+            #define MPACK_STATIC_ASSERT static_assert
+        #endif
+    #elif defined(__STDC_VERSION__)
+        #if __STDC_VERSION__ >= 201112L
+            #define MPACK_STATIC_ASSERT _Static_assert
+        #endif
+    #endif
+#endif
+
+#if !MPACK_NO_BUILTINS
+    #ifndef MPACK_STATIC_ASSERT
+        #if defined(__has_feature)
+            #if __has_feature(cxx_static_assert)
+                #define MPACK_STATIC_ASSERT static_assert
+            #elif __has_feature(c_static_assert)
+                #define MPACK_STATIC_ASSERT _Static_assert
+            #endif
+        #endif
+    #endif
+
+    #ifndef MPACK_STATIC_ASSERT
+        #if defined(__GNUC__)
+            /* Diagnostic push is not supported before GCC 4.6. */
+            #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+                #ifndef __cplusplus
+                    #if defined(__clang__) || __GNUC__ >= 5
+                    #define MPACK_IGNORE_PEDANTIC "GCC diagnostic ignored \"-Wpedantic\""
+                    #else
+                    #define MPACK_IGNORE_PEDANTIC "GCC diagnostic ignored \"-pedantic\""
+                    #endif
+                    #define MPACK_STATIC_ASSERT(expr, str) do { \
+                        _Pragma ("GCC diagnostic push") \
+                        _Pragma (MPACK_IGNORE_PEDANTIC) \
+                        _Pragma ("GCC diagnostic ignored \"-Wc++-compat\"") \
+                        _Static_assert(expr, str); \
+                        _Pragma ("GCC diagnostic pop") \
+                    } while (0)
+                #endif
+            #endif
+        #endif
+    #endif
+
+    #ifndef MPACK_STATIC_ASSERT
+        #ifdef _MSC_VER
+            #if _MSC_VER >= 1600
+                #define MPACK_STATIC_ASSERT static_assert
+            #endif
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_STATIC_ASSERT
+    #define MPACK_STATIC_ASSERT(expr, str) (MPACK_UNUSED(sizeof(char[1 - 2*!(expr)])))
+#endif
+
+
+
+/* _Generic */
+
+#ifndef MPACK_HAS_GENERIC
+    #if defined(__clang__) && defined(__has_feature)
+        // With Clang we can test for _Generic support directly
+        // and ignore C/C++ version
+        #if __has_feature(c_generic_selections)
+            #define MPACK_HAS_GENERIC 1
+        #else
+            #define MPACK_HAS_GENERIC 0
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_HAS_GENERIC
+    #if defined(__STDC_VERSION__)
+        #if __STDC_VERSION__ >= 201112L
+            #if defined(__GNUC__) && !defined(__clang__)
+                // GCC does not have full C11 support in GCC 4.7 and 4.8
+                #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)
+                    #define MPACK_HAS_GENERIC 1
+                #endif
+            #else
+                // We hope other compilers aren't lying about C11/_Generic support
+                #define MPACK_HAS_GENERIC 1
+            #endif
+        #endif
+    #endif
+#endif
+
+#ifndef MPACK_HAS_GENERIC
+    #define MPACK_HAS_GENERIC 0
+#endif
+
+
+
+/*
+ * Finite Math
+ *
+ * -ffinite-math-only, included in -ffast-math, breaks functions that
+ * that check for non-finite real values such as isnan() and isinf().
+ *
+ * We should use this to trap errors when reading data that contains
+ * non-finite reals. This isn't currently implemented.
+ */
+
+#ifndef MPACK_FINITE_MATH
+#if defined(__FINITE_MATH_ONLY__) && __FINITE_MATH_ONLY__
+#define MPACK_FINITE_MATH 1
+#endif
+#endif
+
+#ifndef MPACK_FINITE_MATH
+#define MPACK_FINITE_MATH 0
+#endif
+
+
+
+/*
+ * Endianness checks
+ *
+ * These define MPACK_NHSWAP*() which swap network<->host byte
+ * order when needed.
+ *
+ * We leave them undefined if we can't determine the endianness
+ * at compile-time, in which case we fall back to bit-shifts.
+ *
+ * See the notes in mpack-common.h.
+ */
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__)
+    #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+        #define MPACK_NHSWAP16(x) (x)
+        #define MPACK_NHSWAP32(x) (x)
+        #define MPACK_NHSWAP64(x) (x)
+    #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+
+        #if !MPACK_NO_BUILTINS
+            #if defined(__clang__)
+                #ifdef __has_builtin
+                    // Unlike the GCC builtins, the bswap builtins in Clang
+                    // significantly improve ARM performance.
+                    #if __has_builtin(__builtin_bswap16)
+                        #define MPACK_NHSWAP16(x) __builtin_bswap16(x)
+                    #endif
+                    #if __has_builtin(__builtin_bswap32)
+                        #define MPACK_NHSWAP32(x) __builtin_bswap32(x)
+                    #endif
+                    #if __has_builtin(__builtin_bswap64)
+                        #define MPACK_NHSWAP64(x) __builtin_bswap64(x)
+                    #endif
+                #endif
+
+            #elif defined(__GNUC__)
+
+                // The GCC bswap builtins are apparently poorly optimized on older
+                // versions of GCC, so we set a minimum version here just in case.
+                //     http://hardwarebug.org/2010/01/14/beware-the-builtins/
+
+                #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+                    #define MPACK_NHSWAP64(x) __builtin_bswap64(x)
+                #endif
+
+                // __builtin_bswap16() was not implemented on all platforms
+                // until GCC 4.8.0:
+                //     https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624
+                //
+                // The 16- and 32-bit versions in GCC significantly reduce performance
+                // on ARM with little effect on code size so we don't use them.
+
+            #endif
+        #endif
+    #endif
+
+#elif defined(_MSC_VER) && defined(_WIN32) && MPACK_STDLIB && !MPACK_NO_BUILTINS
+
+    // On Windows, we assume x86 and x86_64 are always little-endian.
+    // We make no assumptions about ARM even though all current
+    // Windows Phone devices are little-endian in case Microsoft's
+    // compiler is ever used with a big-endian ARM device.
+
+    // These are functions in <stdlib.h> so we depend on MPACK_STDLIB.
+    // It's not clear if these are actually faster than just doing the
+    // swap manually; maybe we shouldn't bother with this.
+
+    #if defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64)
+        #define MPACK_NHSWAP16(x) _byteswap_ushort(x)
+        #define MPACK_NHSWAP32(x) _byteswap_ulong(x)
+        #define MPACK_NHSWAP64(x) _byteswap_uint64(x)
+    #endif
+
+#endif
+
+#if defined(__FLOAT_WORD_ORDER__) && defined(__BYTE_ORDER__)
+
+    // We check where possible that the float byte order matches the
+    // integer byte order. This is extremely unlikely to fail, but
+    // we check anyway just in case.
+    //
+    // (The static assert is placed in float/double encoders instead
+    // of here because our static assert fallback doesn't work at
+    // file scope)
+
+    #define MPACK_CHECK_FLOAT_ORDER() \
+        MPACK_STATIC_ASSERT(__FLOAT_WORD_ORDER__ == __BYTE_ORDER__, \
+            "float byte order does not match int byte order! float/double " \
+            "encoding is not properly implemented on this platform.")
+
+#endif
+
+#ifndef MPACK_CHECK_FLOAT_ORDER
+    #define MPACK_CHECK_FLOAT_ORDER() /* nothing */
+#endif
+
+
+/*
+ * Here we define mpack_assert() and mpack_break(). They both work like a normal
+ * assertion function in debug mode, causing a trap or abort. However, on some platforms
+ * you can safely resume execution from mpack_break(), whereas mpack_assert() is
+ * always fatal.
+ *
+ * In release mode, mpack_assert() is converted to an assurance to the compiler
+ * that the expression cannot be false (via e.g. __assume() or __builtin_unreachable())
+ * to improve optimization where supported. There is thus no point in "safely" handling
+ * the case of this being false. Writing mpack_assert(0) rarely makes sense (except
+ * possibly as a default handler in a switch) since the compiler will throw away any
+ * code after it. If at any time an mpack_assert() is not true, the behaviour is
+ * undefined. This also means the expression is evaluated even in release.
+ *
+ * mpack_break() on the other hand is compiled to nothing in release. It is
+ * used in situations where we want to highlight a programming error as early as
+ * possible (in the debugger), but we still handle the situation safely if it
+ * happens in release to avoid producing incorrect results (such as in
+ * MPACK_WRITE_TRACKING.) It does not take an expression to test because it
+ * belongs in a safe-handling block after its failing condition has been tested.
+ *
+ * If stdio is available, we can add a format string describing the error, and
+ * on some compilers we can declare it noreturn to get correct results from static
+ * analysis tools. Note that the format string and arguments are not evaluated unless
+ * the assertion is hit.
+ *
+ * Note that any arguments to mpack_assert() beyond the first are only evaluated
+ * if the expression is false (and are never evaluated in release.)
+ *
+ * mpack_assert_fail() and mpack_break_hit() are defined separately
+ * because assert is noreturn and break isn't. This distinction is very
+ * important for static analysis tools to give correct results.
+ */
+
+#if MPACK_DEBUG
+    MPACK_NORETURN(void mpack_assert_fail_wrapper(const char* message));
+    #if MPACK_STDIO
+        MPACK_NORETURN(void mpack_assert_fail_format(const char* format, ...));
+        #define mpack_assert_fail_at(line, file, exprstr, format, ...) \
+                MPACK_EXPAND(mpack_assert_fail_format("mpack assertion failed at " file ":" #line "\n%s\n" format, exprstr, __VA_ARGS__))
+    #else
+        #define mpack_assert_fail_at(line, file, exprstr, format, ...) \
+                mpack_assert_fail_wrapper("mpack assertion failed at " file ":" #line "\n" exprstr "\n")
+    #endif
+
+    #define mpack_assert_fail_pos(line, file, exprstr, expr, ...) \
+            MPACK_EXPAND(mpack_assert_fail_at(line, file, exprstr, __VA_ARGS__))
+
+    // This contains a workaround to the pedantic C99 requirement of having at
+    // least one argument to a variadic macro. The first argument is the
+    // boolean expression, the optional second argument (if provided) must be a
+    // literal format string, and any additional arguments are the format
+    // argument list.
+    //
+    // Unfortunately this means macros are expanded in the expression before it
+    // gets stringified. I haven't found a workaround to this.
+    //
+    // This adds two unused arguments to the format argument list when a
+    // format string is provided, so this would complicate the use of
+    // -Wformat and __attribute__((__format__)) on mpack_assert_fail_format()
+    // if we ever bothered to implement it.
+    #define mpack_assert(...) \
+            MPACK_EXPAND(((!(MPACK_EXTRACT_ARG0(__VA_ARGS__))) ? \
+                mpack_assert_fail_pos(__LINE__, __FILE__, MPACK_STRINGIFY_ARG0(__VA_ARGS__) , __VA_ARGS__ , "", NULL) : \
+                (void)0))
+
+    void mpack_break_hit(const char* message);
+    #if MPACK_STDIO
+        void mpack_break_hit_format(const char* format, ...);
+        #define mpack_break_hit_at(line, file, ...) \
+                MPACK_EXPAND(mpack_break_hit_format("mpack breakpoint hit at " file ":" #line "\n" __VA_ARGS__))
+    #else
+        #define mpack_break_hit_at(line, file, ...) \
+                mpack_break_hit("mpack breakpoint hit at " file ":" #line )
+    #endif
+    #define mpack_break_hit_pos(line, file, ...) MPACK_EXPAND(mpack_break_hit_at(line, file, __VA_ARGS__))
+    #define mpack_break(...) MPACK_EXPAND(mpack_break_hit_pos(__LINE__, __FILE__, __VA_ARGS__))
+#else
+    #define mpack_assert(...) \
+            (MPACK_EXPAND((!(MPACK_EXTRACT_ARG0(__VA_ARGS__))) ? \
+                (MPACK_UNREACHABLE, (void)0) : \
+                (void)0))
+    #define mpack_break(...) ((void)0)
+#endif
+
+
+
+// make sure we don't use the stdlib directly during development
+#if MPACK_STDLIB && defined(MPACK_UNIT_TESTS) && MPACK_INTERNAL && defined(__GNUC__)
+    #undef memcmp
+    #undef memcpy
+    #undef memmove
+    #undef memset
+    #undef strlen
+    #undef malloc
+    #undef calloc
+    #undef realloc
+    #undef free
+    #pragma GCC poison memcmp
+    #pragma GCC poison memcpy
+    #pragma GCC poison memmove
+    #pragma GCC poison memset
+    #pragma GCC poison strlen
+    #pragma GCC poison malloc
+    #pragma GCC poison calloc
+    #pragma GCC poison realloc
+    #pragma GCC poison free
+#endif
+
+
+
+// If we don't have these stdlib functions, we need to define them ourselves.
+// Either way we give them a lowercase name to make the code a bit nicer.
+
+#ifdef MPACK_MEMCMP
+    #define mpack_memcmp MPACK_MEMCMP
+#else
+    int mpack_memcmp(const void* s1, const void* s2, size_t n);
+#endif
+
+#ifdef MPACK_MEMCPY
+    #define mpack_memcpy MPACK_MEMCPY
+#else
+    void* mpack_memcpy(void* MPACK_RESTRICT s1, const void* MPACK_RESTRICT s2, size_t n);
+#endif
+
+#ifdef MPACK_MEMMOVE
+    #define mpack_memmove MPACK_MEMMOVE
+#else
+    void* mpack_memmove(void* s1, const void* s2, size_t n);
+#endif
+
+#ifdef MPACK_MEMSET
+    #define mpack_memset MPACK_MEMSET
+#else
+    void* mpack_memset(void* s, int c, size_t n);
+#endif
+
+#ifdef MPACK_STRLEN
+    #define mpack_strlen MPACK_STRLEN
+#else
+    size_t mpack_strlen(const char* s);
+#endif
+
+
+
+#if MPACK_STDIO
+    #if defined(WIN32)
+        #define mpack_snprintf _snprintf
+    #else
+        #define mpack_snprintf snprintf
+    #endif
+#endif
+
+
+
+/* Debug logging */
+#if 0
+    #include <stdio.h>
+    #define mpack_log(...) (MPACK_EXPAND(printf(__VA_ARGS__)), fflush(stdout))
+#else
+    #define mpack_log(...) ((void)0)
+#endif
+
+
+
+/* Make sure our configuration makes sense */
+#ifndef MPACK_MALLOC
+    #if MPACK_STDIO
+        #error "MPACK_STDIO requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE."
+    #endif
+    #if MPACK_READ_TRACKING
+        #error "MPACK_READ_TRACKING requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE."
+    #endif
+    #if MPACK_WRITE_TRACKING
+        #error "MPACK_WRITE_TRACKING requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE."
+    #endif
+#endif
+
+
+
+/* Implement realloc if unavailable */
+#ifdef MPACK_MALLOC
+    #ifdef MPACK_REALLOC
+        MPACK_INLINE void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) {
+            MPACK_UNUSED(used_size);
+            return MPACK_REALLOC(old_ptr, new_size);
+        }
+    #else
+        void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size);
+    #endif
+#endif
+
+
+
+/** @endcond */
+/**
+ * @}
+ */
+
+MPACK_EXTERN_C_END
+MPACK_SILENCE_WARNINGS_END
+
+#endif
+
+/* mpack/mpack-common.h.h */
+
+/**
+ * @file
+ *
+ * Defines types and functions shared by the MPack reader and writer.
+ */
+
+#ifndef MPACK_COMMON_H
+#define MPACK_COMMON_H 1
+
+/* #include "mpack-platform.h" */
+
+#ifndef MPACK_PRINT_BYTE_COUNT
+#define MPACK_PRINT_BYTE_COUNT 12
+#endif
+
+MPACK_SILENCE_WARNINGS_BEGIN
+MPACK_EXTERN_C_BEGIN
+
+
+
+/**
+ * @defgroup common Tags and Common Elements
+ *
+ * Contains types, constants and functions shared by both the encoding
+ * and decoding portions of MPack.
+ *
+ * @{
+ */
+
+/* Version information */
+
+#define MPACK_VERSION_MAJOR 1  /**< The major version number of MPack. */
+#define MPACK_VERSION_MINOR 1  /**< The minor version number of MPack. */
+#define MPACK_VERSION_PATCH 0  /**< The patch version number of MPack. */
+
+/** A number containing the version number of MPack for comparison purposes. */
+#define MPACK_VERSION ((MPACK_VERSION_MAJOR * 10000) + \
+        (MPACK_VERSION_MINOR * 100) + MPACK_VERSION_PATCH)
+
+/** A macro to test for a minimum version of MPack. */
+#define MPACK_VERSION_AT_LEAST(major, minor, patch) \
+        (MPACK_VERSION >= (((major) * 10000) + ((minor) * 100) + (patch)))
+
+/** @cond */
+#if (MPACK_VERSION_PATCH > 0)
+#define MPACK_VERSION_STRING_BASE \
+        MPACK_STRINGIFY(MPACK_VERSION_MAJOR) "." \
+        MPACK_STRINGIFY(MPACK_VERSION_MINOR) "." \
+        MPACK_STRINGIFY(MPACK_VERSION_PATCH)
+#else
+#define MPACK_VERSION_STRING_BASE \
+        MPACK_STRINGIFY(MPACK_VERSION_MAJOR) "." \
+        MPACK_STRINGIFY(MPACK_VERSION_MINOR)
+#endif
+/** @endcond */
+
+/**
+ * @def MPACK_VERSION_STRING
+ * @hideinitializer
+ *
+ * A string containing the MPack version.
+ */
+#if MPACK_RELEASE_VERSION
+#define MPACK_VERSION_STRING MPACK_VERSION_STRING_BASE
+#else
+#define MPACK_VERSION_STRING MPACK_VERSION_STRING_BASE "dev"
+#endif
+
+/**
+ * @def MPACK_LIBRARY_STRING
+ * @hideinitializer
+ *
+ * A string describing MPack, containing the library name, version and debug mode.
+ */
+#if MPACK_DEBUG
+#define MPACK_LIBRARY_STRING "MPack " MPACK_VERSION_STRING "-debug"
+#else
+#define MPACK_LIBRARY_STRING "MPack " MPACK_VERSION_STRING
+#endif
+
+/** @cond */
+/**
+ * @def MPACK_MAXIMUM_TAG_SIZE
+ *
+ * The maximum encoded size of a tag in bytes.
+ */
+#define MPACK_MAXIMUM_TAG_SIZE 9
+/** @endcond */
+
+#if MPACK_EXTENSIONS
+/**
+ * @def MPACK_TIMESTAMP_NANOSECONDS_MAX
+ *
+ * The maximum value of nanoseconds for a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+#define MPACK_TIMESTAMP_NANOSECONDS_MAX 999999999
+#endif
+
+
+
+#if MPACK_COMPATIBILITY
+/**
+ * Versions of the MessagePack format.
+ *
+ * A reader, writer, or tree can be configured to serialize in an older
+ * version of the MessagePack spec. This is necessary to interface with
+ * older MessagePack libraries that do not support new MessagePack features.
+ *
+ * @note This requires @ref MPACK_COMPATIBILITY.
+ */
+typedef enum mpack_version_t {
+
+    /**
+     * Version 1.0/v4, supporting only the @c raw type without @c str8.
+     */
+    mpack_version_v4 = 4,
+
+    /**
+     * Version 2.0/v5, supporting the @c str8, @c bin and @c ext types.
+     */
+    mpack_version_v5 = 5,
+
+    /**
+     * The most recent supported version of MessagePack. This is the default.
+     */
+    mpack_version_current = mpack_version_v5,
+
+} mpack_version_t;
+#endif
+
+/**
+ * Error states for MPack objects.
+ *
+ * When a reader, writer, or tree is in an error state, all subsequent calls
+ * are ignored and their return values are nil/zero. You should check whether
+ * the source is in an error state before using such values.
+ */
+typedef enum mpack_error_t {
+    mpack_ok = 0,        /**< No error. */
+    mpack_error_io = 2,  /**< The reader or writer failed to fill or flush, or some other file or socket error occurred. */
+    mpack_error_invalid, /**< The data read is not valid MessagePack. */
+    mpack_error_unsupported, /**< The data read is not supported by this configuration of MPack. (See @ref MPACK_EXTENSIONS.) */
+    mpack_error_type,    /**< The type or value range did not match what was expected by the caller. */
+    mpack_error_too_big, /**< A read or write was bigger than the maximum size allowed for that operation. */
+    mpack_error_memory,  /**< An allocation failure occurred. */
+    mpack_error_bug,     /**< The MPack API was used incorrectly. (This will always assert in debug mode.) */
+    mpack_error_data,    /**< The contained data is not valid. */
+    mpack_error_eof,     /**< The reader failed to read because of file or socket EOF */
+} mpack_error_t;
+
+/**
+ * Converts an MPack error to a string. This function returns an empty
+ * string when MPACK_DEBUG is not set.
+ */
+const char* mpack_error_to_string(mpack_error_t error);
+
+/**
+ * Defines the type of a MessagePack tag.
+ *
+ * Note that extension types, both user defined and built-in, are represented
+ * in tags as @ref mpack_type_ext. The value for an extension type is stored
+ * separately.
+ */
+typedef enum mpack_type_t {
+    mpack_type_missing = 0, /**< Special type indicating a missing optional value. */
+    mpack_type_nil,         /**< A null value. */
+    mpack_type_bool,        /**< A boolean (true or false.) */
+    mpack_type_int,         /**< A 64-bit signed integer. */
+    mpack_type_uint,        /**< A 64-bit unsigned integer. */
+    mpack_type_float,       /**< A 32-bit IEEE 754 floating point number. */
+    mpack_type_double,      /**< A 64-bit IEEE 754 floating point number. */
+    mpack_type_str,         /**< A string. */
+    mpack_type_bin,         /**< A chunk of binary data. */
+    mpack_type_array,       /**< An array of MessagePack objects. */
+    mpack_type_map,         /**< An ordered map of key/value pairs of MessagePack objects. */
+
+    #if MPACK_EXTENSIONS
+    /**
+     * A typed MessagePack extension object containing a chunk of binary data.
+     *
+     * @note This requires @ref MPACK_EXTENSIONS.
+     */
+    mpack_type_ext,
+    #endif
+} mpack_type_t;
+
+/**
+ * Converts an MPack type to a string. This function returns an empty
+ * string when MPACK_DEBUG is not set.
+ */
+const char* mpack_type_to_string(mpack_type_t type);
+
+#if MPACK_EXTENSIONS
+/**
+ * A timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+typedef struct mpack_timestamp_t {
+    int64_t seconds; /*< The number of seconds (signed) since 1970-01-01T00:00:00Z. */
+    uint32_t nanoseconds; /*< The number of additional nanoseconds, between 0 and 999,999,999. */
+} mpack_timestamp_t;
+#endif
+
+/**
+ * An MPack tag is a MessagePack object header. It is a variant type
+ * representing any kind of object, and includes the length of compound types
+ * (e.g. map, array, string) or the value of non-compound types (e.g. boolean,
+ * integer, float.)
+ *
+ * If the type is compound (str, bin, ext, array or map), the contained
+ * elements or bytes are stored separately.
+ *
+ * This structure is opaque; its fields should not be accessed outside
+ * of MPack.
+ */
+typedef struct mpack_tag_t mpack_tag_t;
+
+/* Hide internals from documentation */
+/** @cond */
+struct mpack_tag_t {
+    mpack_type_t type; /*< The type of value. */
+
+    #if MPACK_EXTENSIONS
+    int8_t exttype; /*< The extension type if the type is @ref mpack_type_ext. */
+    #endif
+
+    /* The value for non-compound types. */
+    union {
+        uint64_t u; /*< The value if the type is unsigned int. */
+        int64_t  i; /*< The value if the type is signed int. */
+        bool     b; /*< The value if the type is bool. */
+
+        #if MPACK_FLOAT
+        float    f; /*< The value if the type is float. */
+        #else
+        uint32_t f; /*< The raw value if the type is float. */
+        #endif
+
+        #if MPACK_DOUBLE
+        double   d; /*< The value if the type is double. */
+        #else
+        uint64_t d; /*< The raw value if the type is double. */
+        #endif
+
+        /* The number of bytes if the type is str, bin or ext. */
+        uint32_t l;
+
+        /* The element count if the type is an array, or the number of
+            key/value pairs if the type is map. */
+        uint32_t n;
+    } v;
+};
+/** @endcond */
+
+/**
+ * @name Tag Generators
+ * @{
+ */
+
+/**
+ * @def MPACK_TAG_ZERO
+ *
+ * An @ref mpack_tag_t initializer that zeroes the given tag.
+ *
+ * @warning This does not make the tag nil! The tag's type is invalid when
+ * initialized this way. Use @ref mpack_tag_make_nil() to generate a nil tag.
+ */
+#if MPACK_EXTENSIONS
+#define MPACK_TAG_ZERO {(mpack_type_t)0, 0, {0}}
+#else
+#define MPACK_TAG_ZERO {(mpack_type_t)0, {0}}
+#endif
+
+/** Generates a nil tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_nil(void) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_nil;
+    return ret;
+}
+
+/** Generates a bool tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_bool(bool value) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_bool;
+    ret.v.b = value;
+    return ret;
+}
+
+/** Generates a bool tag with value true. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_true(void) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_bool;
+    ret.v.b = true;
+    return ret;
+}
+
+/** Generates a bool tag with value false. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_false(void) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_bool;
+    ret.v.b = false;
+    return ret;
+}
+
+/** Generates a signed int tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_int(int64_t value) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_int;
+    ret.v.i = value;
+    return ret;
+}
+
+/** Generates an unsigned int tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_uint(uint64_t value) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_uint;
+    ret.v.u = value;
+    return ret;
+}
+
+#if MPACK_FLOAT
+/** Generates a float tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_float(float value)
+#else
+/** Generates a float tag from a raw uint32_t. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_raw_float(uint32_t value)
+#endif
+{
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_float;
+    ret.v.f = value;
+    return ret;
+}
+
+#if MPACK_DOUBLE
+/** Generates a double tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_double(double value)
+#else
+/** Generates a double tag from a raw uint64_t. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_raw_double(uint64_t value)
+#endif
+{
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_double;
+    ret.v.d = value;
+    return ret;
+}
+
+/** Generates an array tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_array(uint32_t count) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_array;
+    ret.v.n = count;
+    return ret;
+}
+
+/** Generates a map tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_map(uint32_t count) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_map;
+    ret.v.n = count;
+    return ret;
+}
+
+/** Generates a str tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_str(uint32_t length) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_str;
+    ret.v.l = length;
+    return ret;
+}
+
+/** Generates a bin tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_bin(uint32_t length) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_bin;
+    ret.v.l = length;
+    return ret;
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Generates an ext tag.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+MPACK_INLINE mpack_tag_t mpack_tag_make_ext(int8_t exttype, uint32_t length) {
+    mpack_tag_t ret = MPACK_TAG_ZERO;
+    ret.type = mpack_type_ext;
+    ret.exttype = exttype;
+    ret.v.l = length;
+    return ret;
+}
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Tag Querying Functions
+ * @{
+ */
+
+/**
+ * Gets the type of a tag.
+ */
+MPACK_INLINE mpack_type_t mpack_tag_type(mpack_tag_t* tag) {
+    return tag->type;
+}
+
+/**
+ * Gets the boolean value of a bool-type tag. The tag must be of type @ref
+ * mpack_type_bool.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_bool. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ */
+MPACK_INLINE bool mpack_tag_bool_value(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_bool, "tag is not a bool!");
+    return tag->v.b;
+}
+
+/**
+ * Gets the signed integer value of an int-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_int. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between signed and unsigned tags! A positive
+ * integer may be stored in a tag as either @ref mpack_type_int or @ref
+ * mpack_type_uint. You must check the type first; this can only be used if the
+ * type is @ref mpack_type_int.
+ *
+ * @see mpack_type_int
+ */
+MPACK_INLINE int64_t mpack_tag_int_value(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_int, "tag is not an int!");
+    return tag->v.i;
+}
+
+/**
+ * Gets the unsigned integer value of a uint-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_uint. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between signed and unsigned tags! A positive
+ * integer may be stored in a tag as either @ref mpack_type_int or @ref
+ * mpack_type_uint. You must check the type first; this can only be used if the
+ * type is @ref mpack_type_uint.
+ *
+ * @see mpack_type_uint
+ */
+MPACK_INLINE uint64_t mpack_tag_uint_value(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_uint, "tag is not a uint!");
+    return tag->v.u;
+}
+
+/**
+ * Gets the float value of a float-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_float. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between float and double tags! This can only
+ * be used if the type is @ref mpack_type_float.
+ *
+ * @see mpack_type_float
+ */
+MPACK_INLINE
+#if MPACK_FLOAT
+float mpack_tag_float_value(mpack_tag_t* tag)
+#else
+uint32_t mpack_tag_raw_float_value(mpack_tag_t* tag)
+#endif
+{
+    mpack_assert(tag->type == mpack_type_float, "tag is not a float!");
+    return tag->v.f;
+}
+
+/**
+ * Gets the double value of a double-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_double. (No check
+ * is performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between float and double tags! This can only
+ * be used if the type is @ref mpack_type_double.
+ *
+ * @see mpack_type_double
+ */
+MPACK_INLINE
+#if MPACK_DOUBLE
+double mpack_tag_double_value(mpack_tag_t* tag)
+#else
+uint64_t mpack_tag_raw_double_value(mpack_tag_t* tag)
+#endif
+{
+    mpack_assert(tag->type == mpack_type_double, "tag is not a double!");
+    return tag->v.d;
+}
+
+/**
+ * Gets the number of elements in an array tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_array. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_array
+ */
+MPACK_INLINE uint32_t mpack_tag_array_count(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_array, "tag is not an array!");
+    return tag->v.n;
+}
+
+/**
+ * Gets the number of key-value pairs in a map tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_map. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_map
+ */
+MPACK_INLINE uint32_t mpack_tag_map_count(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_map, "tag is not a map!");
+    return tag->v.n;
+}
+
+/**
+ * Gets the length in bytes of a str-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_str. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_str
+ */
+MPACK_INLINE uint32_t mpack_tag_str_length(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_str, "tag is not a str!");
+    return tag->v.l;
+}
+
+/**
+ * Gets the length in bytes of a bin-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_bin. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_bin
+ */
+MPACK_INLINE uint32_t mpack_tag_bin_length(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_bin, "tag is not a bin!");
+    return tag->v.l;
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Gets the length in bytes of an ext-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_ext. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_type_ext
+ */
+MPACK_INLINE uint32_t mpack_tag_ext_length(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_ext, "tag is not an ext!");
+    return tag->v.l;
+}
+
+/**
+ * Gets the extension type (exttype) of an ext-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_ext. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_type_ext
+ */
+MPACK_INLINE int8_t mpack_tag_ext_exttype(mpack_tag_t* tag) {
+    mpack_assert(tag->type == mpack_type_ext, "tag is not an ext!");
+    return tag->exttype;
+}
+#endif
+
+/**
+ * Gets the length in bytes of a str-, bin- or ext-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_str, @ref
+ * mpack_type_bin or @ref mpack_type_ext. (No check is performed if MPACK_DEBUG
+ * is not set.)
+ *
+ * @see mpack_type_str
+ * @see mpack_type_bin
+ * @see mpack_type_ext
+ */
+MPACK_INLINE uint32_t mpack_tag_bytes(mpack_tag_t* tag) {
+    #if MPACK_EXTENSIONS
+    mpack_assert(tag->type == mpack_type_str || tag->type == mpack_type_bin
+            || tag->type == mpack_type_ext, "tag is not a str, bin or ext!");
+    #else
+    mpack_assert(tag->type == mpack_type_str || tag->type == mpack_type_bin,
+            "tag is not a str or bin!");
+    #endif
+    return tag->v.l;
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Other tag functions
+ * @{
+ */
+
+#if MPACK_EXTENSIONS
+/**
+ * The extension type for a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+#define MPACK_EXTTYPE_TIMESTAMP ((int8_t)(-1))
+#endif
+
+/**
+ * Compares two tags with an arbitrary fixed ordering. Returns 0 if the tags are
+ * equal, a negative integer if left comes before right, or a positive integer
+ * otherwise.
+ *
+ * \warning The ordering is not guaranteed to be preserved across MPack versions; do
+ * not rely on it in persistent data.
+ *
+ * \warning Floating point numbers are compared bit-for-bit, not using the language's
+ * operator==. This means that NaNs with matching representation will compare equal.
+ * This behaviour is up for debate; see comments in the definition of mpack_tag_cmp().
+ *
+ * See mpack_tag_equal() for more information on when tags are considered equal.
+ */
+int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right);
+
+/**
+ * Compares two tags for equality. Tags are considered equal if the types are compatible
+ * and the values (for non-compound types) are equal.
+ *
+ * The field width of variable-width fields is ignored (and in fact is not stored
+ * in a tag), and positive numbers in signed integers are considered equal to their
+ * unsigned counterparts. So for example the value 1 stored as a positive fixint
+ * is equal to the value 1 stored in a 64-bit unsigned integer field.
+ *
+ * The "extension type" of an extension object is considered part of the value
+ * and must match exactly.
+ *
+ * \warning Floating point numbers are compared bit-for-bit, not using the language's
+ * operator==. This means that NaNs with matching representation will compare equal.
+ * This behaviour is up for debate; see comments in the definition of mpack_tag_cmp().
+ */
+MPACK_INLINE bool mpack_tag_equal(mpack_tag_t left, mpack_tag_t right) {
+    return mpack_tag_cmp(left, right) == 0;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+/**
+ * Generates a json-like debug description of the given tag into the given buffer.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ *
+ * The prefix is used to print the first few hexadecimal bytes of a bin or ext
+ * type. Pass NULL if not a bin or ext.
+ */
+void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_size,
+        const char* prefix, size_t prefix_size);
+
+/**
+ * Generates a debug string description of the given tag into the given buffer.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size);
+
+/** @cond */
+
+/*
+ * A callback function for printing pseudo-JSON for debugging purposes.
+ *
+ * @see mpack_node_print_callback
+ */
+typedef void (*mpack_print_callback_t)(void* context, const char* data, size_t count);
+
+// helpers for printing debug output
+// i feel a bit like i'm re-implementing a buffered writer again...
+typedef struct mpack_print_t {
+    char* buffer;
+    size_t size;
+    size_t count;
+    mpack_print_callback_t callback;
+    void* context;
+} mpack_print_t;
+
+void mpack_print_append(mpack_print_t* print, const char* data, size_t count);
+
+MPACK_INLINE void mpack_print_append_cstr(mpack_print_t* print, const char* cstr) {
+    mpack_print_append(print, cstr, mpack_strlen(cstr));
+}
+
+void mpack_print_flush(mpack_print_t* print);
+
+void mpack_print_file_callback(void* context, const char* data, size_t count);
+
+/** @endcond */
+
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Deprecated Tag Generators
+ * @{
+ */
+
+/*
+ * "make" has been added to their names to disambiguate them from the
+ * value-fetching functions (e.g. mpack_tag_make_bool() vs
+ * mpack_tag_bool_value().)
+ *
+ * The length and count for all compound types was the wrong sign (int32_t
+ * instead of uint32_t.) These preserve the old behaviour; the new "make"
+ * functions have the correct sign.
+ */
+
+/** \deprecated Renamed to mpack_tag_make_nil(). */
+MPACK_INLINE mpack_tag_t mpack_tag_nil(void) {
+    return mpack_tag_make_nil();
+}
+
+/** \deprecated Renamed to mpack_tag_make_bool(). */
+MPACK_INLINE mpack_tag_t mpack_tag_bool(bool value) {
+    return mpack_tag_make_bool(value);
+}
+
+/** \deprecated Renamed to mpack_tag_make_true(). */
+MPACK_INLINE mpack_tag_t mpack_tag_true(void) {
+    return mpack_tag_make_true();
+}
+
+/** \deprecated Renamed to mpack_tag_make_false(). */
+MPACK_INLINE mpack_tag_t mpack_tag_false(void) {
+    return mpack_tag_make_false();
+}
+
+/** \deprecated Renamed to mpack_tag_make_int(). */
+MPACK_INLINE mpack_tag_t mpack_tag_int(int64_t value) {
+    return mpack_tag_make_int(value);
+}
+
+/** \deprecated Renamed to mpack_tag_make_uint(). */
+MPACK_INLINE mpack_tag_t mpack_tag_uint(uint64_t value) {
+    return mpack_tag_make_uint(value);
+}
+
+#if MPACK_FLOAT
+/** \deprecated Renamed to mpack_tag_make_float(). */
+MPACK_INLINE mpack_tag_t mpack_tag_float(float value) {
+    return mpack_tag_make_float(value);
+}
+#endif
+
+#if MPACK_DOUBLE
+/** \deprecated Renamed to mpack_tag_make_double(). */
+MPACK_INLINE mpack_tag_t mpack_tag_double(double value) {
+    return mpack_tag_make_double(value);
+}
+#endif
+
+/** \deprecated Renamed to mpack_tag_make_array(). */
+MPACK_INLINE mpack_tag_t mpack_tag_array(int32_t count) {
+    return mpack_tag_make_array((uint32_t)count);
+}
+
+/** \deprecated Renamed to mpack_tag_make_map(). */
+MPACK_INLINE mpack_tag_t mpack_tag_map(int32_t count) {
+    return mpack_tag_make_map((uint32_t)count);
+}
+
+/** \deprecated Renamed to mpack_tag_make_str(). */
+MPACK_INLINE mpack_tag_t mpack_tag_str(int32_t length) {
+    return mpack_tag_make_str((uint32_t)length);
+}
+
+/** \deprecated Renamed to mpack_tag_make_bin(). */
+MPACK_INLINE mpack_tag_t mpack_tag_bin(int32_t length) {
+    return mpack_tag_make_bin((uint32_t)length);
+}
+
+#if MPACK_EXTENSIONS
+/** \deprecated Renamed to mpack_tag_make_ext(). */
+MPACK_INLINE mpack_tag_t mpack_tag_ext(int8_t exttype, int32_t length) {
+    return mpack_tag_make_ext(exttype, (uint32_t)length);
+}
+#endif
+
+/**
+ * @}
+ */
+
+/** @cond */
+
+/*
+ * Helpers to perform unaligned network-endian loads and stores
+ * at arbitrary addresses. Byte-swapping builtins are used if they
+ * are available and if they improve performance.
+ *
+ * These will remain available in the public API so feel free to
+ * use them for other purposes, but they are undocumented.
+ */
+
+MPACK_INLINE uint8_t mpack_load_u8(const char* p) {
+    return (uint8_t)p[0];
+}
+
+MPACK_INLINE uint16_t mpack_load_u16(const char* p) {
+    #ifdef MPACK_NHSWAP16
+    uint16_t val;
+    mpack_memcpy(&val, p, sizeof(val));
+    return MPACK_NHSWAP16(val);
+    #else
+    return (uint16_t)((((uint16_t)(uint8_t)p[0]) << 8) |
+           ((uint16_t)(uint8_t)p[1]));
+    #endif
+}
+
+MPACK_INLINE uint32_t mpack_load_u32(const char* p) {
+    #ifdef MPACK_NHSWAP32
+    uint32_t val;
+    mpack_memcpy(&val, p, sizeof(val));
+    return MPACK_NHSWAP32(val);
+    #else
+    return (((uint32_t)(uint8_t)p[0]) << 24) |
+           (((uint32_t)(uint8_t)p[1]) << 16) |
+           (((uint32_t)(uint8_t)p[2]) <<  8) |
+            ((uint32_t)(uint8_t)p[3]);
+    #endif
+}
+
+MPACK_INLINE uint64_t mpack_load_u64(const char* p) {
+    #ifdef MPACK_NHSWAP64
+    uint64_t val;
+    mpack_memcpy(&val, p, sizeof(val));
+    return MPACK_NHSWAP64(val);
+    #else
+    return (((uint64_t)(uint8_t)p[0]) << 56) |
+           (((uint64_t)(uint8_t)p[1]) << 48) |
+           (((uint64_t)(uint8_t)p[2]) << 40) |
+           (((uint64_t)(uint8_t)p[3]) << 32) |
+           (((uint64_t)(uint8_t)p[4]) << 24) |
+           (((uint64_t)(uint8_t)p[5]) << 16) |
+           (((uint64_t)(uint8_t)p[6]) <<  8) |
+            ((uint64_t)(uint8_t)p[7]);
+    #endif
+}
+
+MPACK_INLINE void mpack_store_u8(char* p, uint8_t val) {
+    uint8_t* u = (uint8_t*)p;
+    u[0] = val;
+}
+
+MPACK_INLINE void mpack_store_u16(char* p, uint16_t val) {
+    #ifdef MPACK_NHSWAP16
+    val = MPACK_NHSWAP16(val);
+    mpack_memcpy(p, &val, sizeof(val));
+    #else
+    uint8_t* u = (uint8_t*)p;
+    u[0] = (uint8_t)((val >> 8) & 0xFF);
+    u[1] = (uint8_t)( val       & 0xFF);
+    #endif
+}
+
+MPACK_INLINE void mpack_store_u32(char* p, uint32_t val) {
+    #ifdef MPACK_NHSWAP32
+    val = MPACK_NHSWAP32(val);
+    mpack_memcpy(p, &val, sizeof(val));
+    #else
+    uint8_t* u = (uint8_t*)p;
+    u[0] = (uint8_t)((val >> 24) & 0xFF);
+    u[1] = (uint8_t)((val >> 16) & 0xFF);
+    u[2] = (uint8_t)((val >>  8) & 0xFF);
+    u[3] = (uint8_t)( val        & 0xFF);
+    #endif
+}
+
+MPACK_INLINE void mpack_store_u64(char* p, uint64_t val) {
+    #ifdef MPACK_NHSWAP64
+    val = MPACK_NHSWAP64(val);
+    mpack_memcpy(p, &val, sizeof(val));
+    #else
+    uint8_t* u = (uint8_t*)p;
+    u[0] = (uint8_t)((val >> 56) & 0xFF);
+    u[1] = (uint8_t)((val >> 48) & 0xFF);
+    u[2] = (uint8_t)((val >> 40) & 0xFF);
+    u[3] = (uint8_t)((val >> 32) & 0xFF);
+    u[4] = (uint8_t)((val >> 24) & 0xFF);
+    u[5] = (uint8_t)((val >> 16) & 0xFF);
+    u[6] = (uint8_t)((val >>  8) & 0xFF);
+    u[7] = (uint8_t)( val        & 0xFF);
+    #endif
+}
+
+MPACK_INLINE int8_t  mpack_load_i8 (const char* p) {return (int8_t) mpack_load_u8 (p);}
+MPACK_INLINE int16_t mpack_load_i16(const char* p) {return (int16_t)mpack_load_u16(p);}
+MPACK_INLINE int32_t mpack_load_i32(const char* p) {return (int32_t)mpack_load_u32(p);}
+MPACK_INLINE int64_t mpack_load_i64(const char* p) {return (int64_t)mpack_load_u64(p);}
+MPACK_INLINE void mpack_store_i8 (char* p, int8_t  val) {mpack_store_u8 (p, (uint8_t) val);}
+MPACK_INLINE void mpack_store_i16(char* p, int16_t val) {mpack_store_u16(p, (uint16_t)val);}
+MPACK_INLINE void mpack_store_i32(char* p, int32_t val) {mpack_store_u32(p, (uint32_t)val);}
+MPACK_INLINE void mpack_store_i64(char* p, int64_t val) {mpack_store_u64(p, (uint64_t)val);}
+
+#if MPACK_FLOAT
+MPACK_INLINE float mpack_load_float(const char* p) {
+    MPACK_CHECK_FLOAT_ORDER();
+    MPACK_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t), "float is wrong size??");
+    union {
+        float f;
+        uint32_t u;
+    } v;
+    v.u = mpack_load_u32(p);
+    return v.f;
+}
+#endif
+
+#if MPACK_DOUBLE
+MPACK_INLINE double mpack_load_double(const char* p) {
+    MPACK_CHECK_FLOAT_ORDER();
+    MPACK_STATIC_ASSERT(sizeof(double) == sizeof(uint64_t), "double is wrong size??");
+    union {
+        double d;
+        uint64_t u;
+    } v;
+    v.u = mpack_load_u64(p);
+    return v.d;
+}
+#endif
+
+#if MPACK_FLOAT
+MPACK_INLINE void mpack_store_float(char* p, float value) {
+    MPACK_CHECK_FLOAT_ORDER();
+    union {
+        float f;
+        uint32_t u;
+    } v;
+    v.f = value;
+    mpack_store_u32(p, v.u);
+}
+#endif
+
+#if MPACK_DOUBLE
+MPACK_INLINE void mpack_store_double(char* p, double value) {
+    MPACK_CHECK_FLOAT_ORDER();
+    union {
+        double d;
+        uint64_t u;
+    } v;
+    v.d = value;
+    mpack_store_u64(p, v.u);
+}
+#endif
+
+#if MPACK_FLOAT && !MPACK_DOUBLE
+/**
+ * Performs a manual shortening conversion on the raw 64-bit representation of
+ * a double. This is useful for parsing doubles on platforms that only support
+ * floats (such as AVR.)
+ *
+ * The significand is truncated rather than rounded and subnormal numbers are
+ * set to 0 so this may not be quite as accurate as a real double-to-float
+ * conversion.
+ */
+MPACK_INLINE float mpack_shorten_raw_double_to_float(uint64_t d) {
+    MPACK_CHECK_FLOAT_ORDER();
+    union {
+        float f;
+        uint32_t u;
+    } v;
+
+    // float has  1 bit sign,  8 bits exponent, 23 bits significand
+    // double has 1 bit sign, 11 bits exponent, 52 bits significand
+
+    uint64_t d_sign = (uint64_t)(d >> 63);
+    uint64_t d_exponent = (uint32_t)(d >> 52) & ((1 << 11) - 1);
+    uint64_t d_significand = d & (((uint64_t)1 << 52) - 1);
+
+    uint32_t f_sign = (uint32_t)d_sign;
+    uint32_t f_exponent;
+    uint32_t f_significand;
+
+    if (MPACK_UNLIKELY(d_exponent == ((1 << 11) - 1))) {
+        // infinity or NAN. shift down to preserve the top bit since it
+        // indicates signaling NAN, but also set the low bit if any bits were
+        // set (that way we can't shift NAN to infinity.)
+        f_exponent = ((1 << 8) - 1);
+        f_significand = (uint32_t)(d_significand >> 29) | (d_significand ? 1 : 0);
+
+    } else {
+        int fix_bias = (int)d_exponent - ((1 << 10) - 1) + ((1 << 7) - 1);
+        if (MPACK_UNLIKELY(fix_bias <= 0)) {
+            // we don't currently handle subnormal numbers. just set it to zero.
+            f_exponent = 0;
+            f_significand = 0;
+        } else if (MPACK_UNLIKELY(fix_bias > 0xff)) {
+            // exponent is too large; saturate to infinity
+            f_exponent = 0xff;
+            f_significand = 0;
+        } else {
+            // a normal number that fits in a float. this is the usual case.
+            f_exponent = (uint32_t)fix_bias;
+            f_significand = (uint32_t)(d_significand >> 29);
+        }
+    }
+
+    #if 0
+    printf("\n===============\n");
+    for (size_t i = 0; i < 64; ++i)
+        printf("%i%s",(int)((d>>(63-i))&1),((i%8)==7)?" ":"");
+    printf("\n%lu %lu %lu\n", d_sign, d_exponent, d_significand);
+    printf("%u %u %u\n", f_sign, f_exponent, f_significand);
+    #endif
+
+    v.u = (f_sign << 31) | (f_exponent << 23) | f_significand;
+    return v.f;
+}
+#endif
+
+/** @endcond */
+
+
+
+/** @cond */
+
+// Sizes in bytes for the various possible tags
+#define MPACK_TAG_SIZE_FIXUINT  1
+#define MPACK_TAG_SIZE_U8       2
+#define MPACK_TAG_SIZE_U16      3
+#define MPACK_TAG_SIZE_U32      5
+#define MPACK_TAG_SIZE_U64      9
+#define MPACK_TAG_SIZE_FIXINT   1
+#define MPACK_TAG_SIZE_I8       2
+#define MPACK_TAG_SIZE_I16      3
+#define MPACK_TAG_SIZE_I32      5
+#define MPACK_TAG_SIZE_I64      9
+#define MPACK_TAG_SIZE_FLOAT    5
+#define MPACK_TAG_SIZE_DOUBLE   9
+#define MPACK_TAG_SIZE_FIXARRAY 1
+#define MPACK_TAG_SIZE_ARRAY16  3
+#define MPACK_TAG_SIZE_ARRAY32  5
+#define MPACK_TAG_SIZE_FIXMAP   1
+#define MPACK_TAG_SIZE_MAP16    3
+#define MPACK_TAG_SIZE_MAP32    5
+#define MPACK_TAG_SIZE_FIXSTR   1
+#define MPACK_TAG_SIZE_STR8     2
+#define MPACK_TAG_SIZE_STR16    3
+#define MPACK_TAG_SIZE_STR32    5
+#define MPACK_TAG_SIZE_BIN8     2
+#define MPACK_TAG_SIZE_BIN16    3
+#define MPACK_TAG_SIZE_BIN32    5
+#define MPACK_TAG_SIZE_FIXEXT1  2
+#define MPACK_TAG_SIZE_FIXEXT2  2
+#define MPACK_TAG_SIZE_FIXEXT4  2
+#define MPACK_TAG_SIZE_FIXEXT8  2
+#define MPACK_TAG_SIZE_FIXEXT16 2
+#define MPACK_TAG_SIZE_EXT8     3
+#define MPACK_TAG_SIZE_EXT16    4
+#define MPACK_TAG_SIZE_EXT32    6
+
+// size in bytes for complete ext types
+#define MPACK_EXT_SIZE_TIMESTAMP4 (MPACK_TAG_SIZE_FIXEXT4 + 4)
+#define MPACK_EXT_SIZE_TIMESTAMP8 (MPACK_TAG_SIZE_FIXEXT8 + 8)
+#define MPACK_EXT_SIZE_TIMESTAMP12 (MPACK_TAG_SIZE_EXT8 + 12)
+
+/** @endcond */
+
+
+
+#if MPACK_READ_TRACKING || MPACK_WRITE_TRACKING
+/* Tracks the write state of compound elements (maps, arrays, */
+/* strings, binary blobs and extension types) */
+/** @cond */
+
+typedef struct mpack_track_element_t {
+    mpack_type_t type;
+    uint32_t left;
+
+    // indicates that a value still needs to be read/written for an already
+    // read/written key. left is not decremented until both key and value are
+    // read/written.
+    bool key_needs_value;
+
+    // tracks whether the map/array being written is using a builder. if true,
+    // the number of elements is automatic, and left is 0.
+    bool builder;
+} mpack_track_element_t;
+
+typedef struct mpack_track_t {
+    size_t count;
+    size_t capacity;
+    mpack_track_element_t* elements;
+} mpack_track_t;
+
+#if MPACK_INTERNAL
+mpack_error_t mpack_track_init(mpack_track_t* track);
+mpack_error_t mpack_track_grow(mpack_track_t* track);
+mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint32_t count);
+mpack_error_t mpack_track_push_builder(mpack_track_t* track, mpack_type_t type);
+mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type);
+mpack_error_t mpack_track_pop_builder(mpack_track_t* track, mpack_type_t type);
+mpack_error_t mpack_track_element(mpack_track_t* track, bool read);
+mpack_error_t mpack_track_peek_element(mpack_track_t* track, bool read);
+mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, size_t count);
+mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, size_t count);
+mpack_error_t mpack_track_check_empty(mpack_track_t* track);
+mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel);
+#endif
+
+/** @endcond */
+#endif
+
+
+
+#if MPACK_INTERNAL
+/** @cond */
+
+
+
+/* Miscellaneous string functions */
+
+/**
+ * Returns true if the given UTF-8 string is valid.
+ */
+bool mpack_utf8_check(const char* str, size_t bytes);
+
+/**
+ * Returns true if the given UTF-8 string is valid and contains no null characters.
+ */
+bool mpack_utf8_check_no_null(const char* str, size_t bytes);
+
+/**
+ * Returns true if the given string has no null bytes.
+ */
+bool mpack_str_check_no_null(const char* str, size_t bytes);
+
+
+
+/** @endcond */
+#endif
+
+
+
+/**
+ * @}
+ */
+
+MPACK_EXTERN_C_END
+MPACK_SILENCE_WARNINGS_END
+
+#endif
+
+
+/* mpack/mpack-writer.h.h */
+
+/**
+ * @file
+ *
+ * Declares the MPack Writer.
+ */
+
+#ifndef MPACK_WRITER_H
+#define MPACK_WRITER_H 1
+
+/* #include "mpack-common.h" */
+
+#if MPACK_WRITER
+
+MPACK_SILENCE_WARNINGS_BEGIN
+MPACK_EXTERN_C_BEGIN
+
+#if MPACK_WRITE_TRACKING
+struct mpack_track_t;
+#endif
+
+/**
+ * @defgroup writer Write API
+ *
+ * The MPack Write API encodes structured data of a fixed (hardcoded) schema to MessagePack.
+ *
+ * @{
+ */
+
+/**
+ * @def MPACK_WRITER_MINIMUM_BUFFER_SIZE
+ *
+ * The minimum buffer size for a writer with a flush function.
+ */
+#define MPACK_WRITER_MINIMUM_BUFFER_SIZE 32
+
+/**
+ * A buffered MessagePack encoder.
+ *
+ * The encoder wraps an existing buffer and, optionally, a flush function.
+ * This allows efficiently encoding to an in-memory buffer or to a stream.
+ *
+ * All write operations are synchronous; they will block until the
+ * data is fully written, or an error occurs.
+ */
+typedef struct mpack_writer_t mpack_writer_t;
+
+/**
+ * The MPack writer's flush function to flush the buffer to the output stream.
+ * It should flag an appropriate error on the writer if flushing fails (usually
+ * mpack_error_io or mpack_error_memory.)
+ *
+ * The specified context for callbacks is at writer->context.
+ */
+typedef void (*mpack_writer_flush_t)(mpack_writer_t* writer, const char* buffer, size_t count);
+
+/**
+ * An error handler function to be called when an error is flagged on
+ * the writer.
+ *
+ * The error handler will only be called once on the first error flagged;
+ * any subsequent writes and errors are ignored, and the writer is
+ * permanently in that error state.
+ *
+ * MPack is safe against non-local jumps out of error handler callbacks.
+ * This means you are allowed to longjmp or throw an exception (in C++,
+ * Objective-C, or with SEH) out of this callback.
+ *
+ * Bear in mind when using longjmp that local non-volatile variables that
+ * have changed are undefined when setjmp() returns, so you can't put the
+ * writer on the stack in the same activation frame as the setjmp without
+ * declaring it volatile.
+ *
+ * You must still eventually destroy the writer. It is not destroyed
+ * automatically when an error is flagged. It is safe to destroy the
+ * writer within this error callback, but you will either need to perform
+ * a non-local jump, or store something in your context to identify
+ * that the writer is destroyed since any future accesses to it cause
+ * undefined behavior.
+ */
+typedef void (*mpack_writer_error_t)(mpack_writer_t* writer, mpack_error_t error);
+
+/**
+ * A teardown function to be called when the writer is destroyed.
+ */
+typedef void (*mpack_writer_teardown_t)(mpack_writer_t* writer);
+
+/* Hide internals from documentation */
+/** @cond */
+
+#if MPACK_BUILDER
+/**
+ * Build buffer pages form a linked list.
+ *
+ * They don't always fill up. If there is not enough space within them to write
+ * a tag or place an mpack_build_t, a new page is allocated. For this reason
+ * they store the number of used bytes.
+ */
+typedef struct mpack_builder_page_t {
+    struct mpack_builder_page_t* next;
+    size_t bytes_used;
+} mpack_builder_page_t;
+
+/**
+ * Builds form a linked list of mpack_build_t, interleaved with their encoded
+ * contents directly in the paged builder buffer.
+ */
+typedef struct mpack_build_t {
+    //mpack_builder_page_t* page;
+    struct mpack_build_t* parent;
+    //struct mpack_build_t* next;
+
+    size_t bytes; // number of bytes between this build and the next one
+    uint32_t count; // number of elements (or key/value pairs) in this map/array
+    mpack_type_t type;
+
+    // depth of nested non-build compound elements within this
+    // build.
+    uint32_t nested_compound_elements;
+
+    // indicates that a value still needs to be written for an already
+    // written key. count is not incremented until both key and value are
+    // written.
+    bool key_needs_value;
+} mpack_build_t;
+
+/**
+ * The builder state. This is stored within mpack_writer_t.
+ */
+typedef struct mpack_builder_t {
+    mpack_build_t* current_build; // build which is accumulating elements
+    mpack_build_t* latest_build; // build which is accumulating bytes
+    mpack_builder_page_t* current_page;
+    mpack_builder_page_t* pages;
+    char* stash_buffer;
+    char* stash_position;
+    char* stash_end;
+    #if MPACK_BUILDER_INTERNAL_STORAGE
+    char internal[MPACK_BUILDER_INTERNAL_STORAGE_SIZE];
+    #endif
+} mpack_builder_t;
+#endif
+
+struct mpack_writer_t {
+    #if MPACK_COMPATIBILITY
+    mpack_version_t version;          /* Version of the MessagePack spec to write */
+    #endif
+    mpack_writer_flush_t flush;       /* Function to write bytes to the output stream */
+    mpack_writer_error_t error_fn;    /* Function to call on error */
+    mpack_writer_teardown_t teardown; /* Function to teardown the context on destroy */
+    void* context;                    /* Context for writer callbacks */
+
+    char* buffer;         /* Byte buffer */
+    char* position;       /* Current position within the buffer */
+    char* end;            /* The end of the buffer */
+    mpack_error_t error;  /* Error state */
+
+    #if MPACK_WRITE_TRACKING
+    mpack_track_t track; /* Stack of map/array/str/bin/ext writes */
+    #endif
+
+    #ifdef MPACK_MALLOC
+    /* Reserved. You can use this space to allocate a custom
+     * context in order to reduce heap allocations. */
+    void* reserved[2];
+    #endif
+
+    #if MPACK_BUILDER
+    mpack_builder_t builder;
+    #endif
+};
+
+
+#if MPACK_WRITE_TRACKING
+void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint32_t count);
+void mpack_writer_track_push_builder(mpack_writer_t* writer, mpack_type_t type);
+void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type);
+void mpack_writer_track_pop_builder(mpack_writer_t* writer, mpack_type_t type);
+void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count);
+#else
+MPACK_INLINE void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint32_t count) {
+    MPACK_UNUSED(writer);
+    MPACK_UNUSED(type);
+    MPACK_UNUSED(count);
+}
+MPACK_INLINE void mpack_writer_track_push_builder(mpack_writer_t* writer, mpack_type_t type) {
+    MPACK_UNUSED(writer);
+    MPACK_UNUSED(type);
+}
+MPACK_INLINE void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type) {
+    MPACK_UNUSED(writer);
+    MPACK_UNUSED(type);
+}
+MPACK_INLINE void mpack_writer_track_pop_builder(mpack_writer_t* writer, mpack_type_t type) {
+    MPACK_UNUSED(writer);
+    MPACK_UNUSED(type);
+}
+MPACK_INLINE void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count) {
+    MPACK_UNUSED(writer);
+    MPACK_UNUSED(count);
+}
+#endif
+
+/** @endcond */
+
+/**
+ * @name Lifecycle Functions
+ * @{
+ */
+
+/**
+ * Initializes an MPack writer with the given buffer. The writer
+ * does not assume ownership of the buffer.
+ *
+ * Trying to write past the end of the buffer will result in mpack_error_too_big
+ * unless a flush function is set with mpack_writer_set_flush(). To use the data
+ * without flushing, call mpack_writer_buffer_used() to determine the number of
+ * bytes written.
+ *
+ * @param writer The MPack writer.
+ * @param buffer The buffer into which to write MessagePack data.
+ * @param size The size of the buffer.
+ */
+void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size);
+
+#ifdef MPACK_MALLOC
+/**
+ * Initializes an MPack writer using a growable buffer.
+ *
+ * The data is placed in the given data pointer if and when the writer
+ * is destroyed without error. The data pointer is NULL during writing,
+ * and will remain NULL if an error occurs.
+ *
+ * The allocated data must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_memory if the buffer fails to grow when
+ * flushing.
+ *
+ * @param writer The MPack writer.
+ * @param data Where to place the allocated data.
+ * @param size Where to write the size of the data.
+ */
+void mpack_writer_init_growable(mpack_writer_t* writer, char** data, size_t* size);
+#endif
+
+/**
+ * Initializes an MPack writer directly into an error state. Use this if you
+ * are writing a wrapper to mpack_writer_init() which can fail its setup.
+ */
+void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error);
+
+#if MPACK_STDIO
+/**
+ * Initializes an MPack writer that writes to a file.
+ *
+ * @throws mpack_error_memory if allocation fails
+ * @throws mpack_error_io if the file cannot be opened
+ */
+void mpack_writer_init_filename(mpack_writer_t* writer, const char* filename);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_writer_init_filename().
+ */
+MPACK_INLINE void mpack_writer_init_file(mpack_writer_t* writer, const char* filename) {
+    mpack_writer_init_filename(writer, filename);
+}
+
+/**
+ * Initializes an MPack writer that writes to a libc FILE. This can be used to
+ * write to stdout or stderr, or to a file opened separately.
+ *
+ * @param writer The MPack writer.
+ * @param stdfile The FILE.
+ * @param close_when_done If true, fclose() will be called on the FILE when it
+ *         is no longer needed. If false, the file will not be flushed or
+ *         closed when writing is done.
+ *
+ * @note The writer is buffered. If you want to write other data to the FILE in
+ *         between messages, you must flush it first.
+ *
+ * @see mpack_writer_flush_message
+ */
+void mpack_writer_init_stdfile(mpack_writer_t* writer, FILE* stdfile, bool close_when_done);
+#endif
+
+/** @cond */
+
+#define mpack_writer_init_stack_line_ex(line, writer) \
+    char mpack_buf_##line[MPACK_STACK_SIZE]; \
+    mpack_writer_init(writer, mpack_buf_##line, sizeof(mpack_buf_##line))
+
+#define mpack_writer_init_stack_line(line, writer) \
+    mpack_writer_init_stack_line_ex(line, writer)
+
+/*
+ * Initializes an MPack writer using stack space as a buffer. A flush function
+ * should be added to the writer to flush the buffer.
+ *
+ * This is currently undocumented since it's not entirely useful on its own.
+ */
+
+#define mpack_writer_init_stack(writer) \
+    mpack_writer_init_stack_line(__LINE__, (writer))
+
+/** @endcond */
+
+/**
+ * Cleans up the MPack writer, flushing and closing the underlying stream,
+ * if any. Returns the final error state of the writer.
+ *
+ * No flushing is performed if the writer is in an error state. The attached
+ * teardown function is called whether or not the writer is in an error state.
+ *
+ * This will assert in tracking mode if the writer is not in an error
+ * state and has any unclosed compound types. If you want to cancel
+ * writing in the middle of a document, you need to flag an error on
+ * the writer before destroying it (such as mpack_error_data).
+ *
+ * Note that a writer may raise an error and call your error handler during
+ * the final flush. It is safe to longjmp or throw out of this error handler,
+ * but if you do, the writer will not be destroyed, and the teardown function
+ * will not be called. You can still get the writer's error state, and you
+ * must call @ref mpack_writer_destroy() again. (The second call is guaranteed
+ * not to call your error handler again since the writer is already in an error
+ * state.)
+ *
+ * @see mpack_writer_set_error_handler
+ * @see mpack_writer_set_flush
+ * @see mpack_writer_set_teardown
+ * @see mpack_writer_flag_error
+ * @see mpack_error_data
+ */
+mpack_error_t mpack_writer_destroy(mpack_writer_t* writer);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Configuration
+ * @{
+ */
+
+#if MPACK_COMPATIBILITY
+/**
+ * Sets the version of the MessagePack spec that will be generated.
+ *
+ * This can be used to interface with older libraries that do not support
+ * the newest MessagePack features (such as the @c str8 type.)
+ *
+ * @note This requires @ref MPACK_COMPATIBILITY.
+ */
+MPACK_INLINE void mpack_writer_set_version(mpack_writer_t* writer, mpack_version_t version) {
+    writer->version = version;
+}
+#endif
+
+/**
+ * Sets the custom pointer to pass to the writer callbacks, such as flush
+ * or teardown.
+ *
+ * @param writer The MPack writer.
+ * @param context User data to pass to the writer callbacks.
+ *
+ * @see mpack_writer_context()
+ */
+MPACK_INLINE void mpack_writer_set_context(mpack_writer_t* writer, void* context) {
+    writer->context = context;
+}
+
+/**
+ * Returns the custom context for writer callbacks.
+ *
+ * @see mpack_writer_set_context
+ * @see mpack_writer_set_flush
+ */
+MPACK_INLINE void* mpack_writer_context(mpack_writer_t* writer) {
+    return writer->context;
+}
+
+/**
+ * Sets the flush function to write out the data when the buffer is full.
+ *
+ * If no flush function is used, trying to write past the end of the
+ * buffer will result in mpack_error_too_big.
+ *
+ * This should normally be used with mpack_writer_set_context() to register
+ * a custom pointer to pass to the flush function.
+ *
+ * @param writer The MPack writer.
+ * @param flush The function to write out data from the buffer.
+ *
+ * @see mpack_writer_context()
+ */
+void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush);
+
+/**
+ * Sets the error function to call when an error is flagged on the writer.
+ *
+ * This should normally be used with mpack_writer_set_context() to register
+ * a custom pointer to pass to the error function.
+ *
+ * See the definition of mpack_writer_error_t for more information about
+ * what you can do from an error callback.
+ *
+ * @see mpack_writer_error_t
+ * @param writer The MPack writer.
+ * @param error_fn The function to call when an error is flagged on the writer.
+ */
+MPACK_INLINE void mpack_writer_set_error_handler(mpack_writer_t* writer, mpack_writer_error_t error_fn) {
+    writer->error_fn = error_fn;
+}
+
+/**
+ * Sets the teardown function to call when the writer is destroyed.
+ *
+ * This should normally be used with mpack_writer_set_context() to register
+ * a custom pointer to pass to the teardown function.
+ *
+ * @param writer The MPack writer.
+ * @param teardown The function to call when the writer is destroyed.
+ */
+MPACK_INLINE void mpack_writer_set_teardown(mpack_writer_t* writer, mpack_writer_teardown_t teardown) {
+    writer->teardown = teardown;
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Core Writer Functions
+ * @{
+ */
+
+/**
+ * Flushes any buffered data to the underlying stream.
+ *
+ * If the writer is connected to a socket and you are keeping it open,
+ * you will want to call this after writing a message (or set of
+ * messages) so that the data is actually sent.
+ *
+ * It is not necessary to call this if you are not keeping the writer
+ * open afterwards. You can just call `mpack_writer_destroy()` and it
+ * will flush before cleaning up.
+ *
+ * This will assert if no flush function is assigned to the writer.
+ *
+ * If write tracking is enabled, this will break and flag @ref
+ * mpack_error_bug if the writer has any open compound types, ensuring
+ * that no compound types are still open. This prevents a "missing
+ * finish" bug from causing a never-ending message.
+ */
+void mpack_writer_flush_message(mpack_writer_t* writer);
+
+/**
+ * Returns the number of bytes currently stored in the buffer. This
+ * may be less than the total number of bytes written if bytes have
+ * been flushed to an underlying stream.
+ */
+MPACK_INLINE size_t mpack_writer_buffer_used(mpack_writer_t* writer) {
+    return (size_t)(writer->position - writer->buffer);
+}
+
+/**
+ * Returns the amount of space left in the buffer. This may be reset
+ * after a write if bytes are flushed to an underlying stream.
+ */
+MPACK_INLINE size_t mpack_writer_buffer_left(mpack_writer_t* writer) {
+    return (size_t)(writer->end - writer->position);
+}
+
+/**
+ * Returns the (current) size of the buffer. This may change after a write if
+ * the flush callback changes the buffer.
+ */
+MPACK_INLINE size_t mpack_writer_buffer_size(mpack_writer_t* writer) {
+    return (size_t)(writer->end - writer->buffer);
+}
+
+/**
+ * Places the writer in the given error state, calling the error callback if one
+ * is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you write it, or if you want to cancel writing in the middle of a
+ * document. (The writer will assert if you try to destroy it without error and
+ * with unclosed compound types. In this case you should flag mpack_error_data
+ * before destroying it.)
+ *
+ * If the writer is already in an error state, this call is ignored and no
+ * error callback is called.
+ *
+ * @see mpack_writer_destroy
+ * @see mpack_error_data
+ */
+void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error);
+
+/**
+ * Queries the error state of the MPack writer.
+ *
+ * If a writer is in an error state, you should discard all data since the
+ * last time the error flag was checked. The error flag cannot be cleared.
+ */
+MPACK_INLINE mpack_error_t mpack_writer_error(mpack_writer_t* writer) {
+    return writer->error;
+}
+
+/**
+ * Writes a MessagePack object header (an MPack Tag.)
+ *
+ * If the value is a map, array, string, binary or extension type, the
+ * containing elements or bytes must be written separately and the
+ * appropriate finish function must be called (as though one of the
+ * mpack_start_*() functions was called.)
+ *
+ * @see mpack_write_bytes()
+ * @see mpack_finish_map()
+ * @see mpack_finish_array()
+ * @see mpack_finish_str()
+ * @see mpack_finish_bin()
+ * @see mpack_finish_ext()
+ * @see mpack_finish_type()
+ */
+void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t tag);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Integers
+ * @{
+ */
+
+/** Writes an 8-bit integer in the most efficient packing available. */
+void mpack_write_i8(mpack_writer_t* writer, int8_t value);
+
+/** Writes a 16-bit integer in the most efficient packing available. */
+void mpack_write_i16(mpack_writer_t* writer, int16_t value);
+
+/** Writes a 32-bit integer in the most efficient packing available. */
+void mpack_write_i32(mpack_writer_t* writer, int32_t value);
+
+/** Writes a 64-bit integer in the most efficient packing available. */
+void mpack_write_i64(mpack_writer_t* writer, int64_t value);
+
+/** Writes an integer in the most efficient packing available. */
+MPACK_INLINE void mpack_write_int(mpack_writer_t* writer, int64_t value) {
+    mpack_write_i64(writer, value);
+}
+
+/** Writes an 8-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u8(mpack_writer_t* writer, uint8_t value);
+
+/** Writes an 16-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u16(mpack_writer_t* writer, uint16_t value);
+
+/** Writes an 32-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u32(mpack_writer_t* writer, uint32_t value);
+
+/** Writes an 64-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u64(mpack_writer_t* writer, uint64_t value);
+
+/** Writes an unsigned integer in the most efficient packing available. */
+MPACK_INLINE void mpack_write_uint(mpack_writer_t* writer, uint64_t value) {
+    mpack_write_u64(writer, value);
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Other Basic Types
+ * @{
+ */
+
+#if MPACK_FLOAT
+/** Writes a float. */
+void mpack_write_float(mpack_writer_t* writer, float value);
+#else
+/** Writes a float from a raw uint32_t. */
+void mpack_write_raw_float(mpack_writer_t* writer, uint32_t raw_value);
+#endif
+
+#if MPACK_DOUBLE
+/** Writes a double. */
+void mpack_write_double(mpack_writer_t* writer, double value);
+#else
+/** Writes a double from a raw uint64_t. */
+void mpack_write_raw_double(mpack_writer_t* writer, uint64_t raw_value);
+#endif
+
+/** Writes a boolean. */
+void mpack_write_bool(mpack_writer_t* writer, bool value);
+
+/** Writes a boolean with value true. */
+void mpack_write_true(mpack_writer_t* writer);
+
+/** Writes a boolean with value false. */
+void mpack_write_false(mpack_writer_t* writer);
+
+/** Writes a nil. */
+void mpack_write_nil(mpack_writer_t* writer);
+
+/** Write a pre-encoded messagepack object */
+void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes);
+
+#if MPACK_EXTENSIONS
+/**
+ * Writes a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @param writer The writer
+ * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z.
+ * @param nanoseconds The additional number of nanoseconds from 0 to 999,999,999 inclusive.
+ */
+void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds);
+
+/**
+ * Writes a timestamp with the given number of seconds (and zero nanoseconds).
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @param writer The writer
+ * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z.
+ */
+MPACK_INLINE void mpack_write_timestamp_seconds(mpack_writer_t* writer, int64_t seconds) {
+    mpack_write_timestamp(writer, seconds, 0);
+}
+
+/**
+ * Writes a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+MPACK_INLINE void mpack_write_timestamp_struct(mpack_writer_t* writer, mpack_timestamp_t timestamp) {
+    mpack_write_timestamp(writer, timestamp.seconds, timestamp.nanoseconds);
+}
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Map and Array Functions
+ * @{
+ */
+
+/**
+ * Opens an array.
+ *
+ * `count` elements must follow, and mpack_finish_array() must be called
+ * when done.
+ *
+ * If you do not know the number of elements to be written ahead of time, call
+ * mpack_build_array() instead.
+ *
+ * @see mpack_finish_array()
+ * @see mpack_build_array() to count the number of elements automatically
+ */
+void mpack_start_array(mpack_writer_t* writer, uint32_t count);
+
+/**
+ * Opens a map.
+ *
+ * `count * 2` elements must follow, and mpack_finish_map() must be called
+ * when done.
+ *
+ * If you do not know the number of elements to be written ahead of time, call
+ * mpack_build_map() instead.
+ *
+ * Remember that while map elements in MessagePack are implicitly ordered,
+ * they are not ordered in JSON. If you need elements to be read back
+ * in the order they are written, consider use an array instead.
+ *
+ * @see mpack_finish_map()
+ * @see mpack_build_map() to count the number of key/value pairs automatically
+ */
+void mpack_start_map(mpack_writer_t* writer, uint32_t count);
+
+MPACK_INLINE void mpack_builder_compound_push(mpack_writer_t* writer) {
+    MPACK_UNUSED(writer);
+
+    #if MPACK_BUILDER
+    mpack_build_t* build = writer->builder.current_build;
+    if (build != NULL) {
+        ++build->nested_compound_elements;
+    }
+    #endif
+}
+
+MPACK_INLINE void mpack_builder_compound_pop(mpack_writer_t* writer) {
+    MPACK_UNUSED(writer);
+
+    #if MPACK_BUILDER
+    mpack_build_t* build = writer->builder.current_build;
+    if (build != NULL) {
+        mpack_assert(build->nested_compound_elements > 0);
+        --build->nested_compound_elements;
+    }
+    #endif
+}
+
+/**
+ * Finishes writing an array.
+ *
+ * This should be called only after a corresponding call to mpack_start_array()
+ * and after the array contents are written.
+ *
+ * In debug mode (or if MPACK_WRITE_TRACKING is not 0), this will track writes
+ * to ensure that the correct number of elements are written.
+ *
+ * @see mpack_start_array()
+ */
+MPACK_INLINE void mpack_finish_array(mpack_writer_t* writer) {
+    mpack_writer_track_pop(writer, mpack_type_array);
+    mpack_builder_compound_pop(writer);
+}
+
+/**
+ * Finishes writing a map.
+ *
+ * This should be called only after a corresponding call to mpack_start_map()
+ * and after the map contents are written.
+ *
+ * In debug mode (or if MPACK_WRITE_TRACKING is not 0), this will track writes
+ * to ensure that the correct number of elements are written.
+ *
+ * @see mpack_start_map()
+ */
+MPACK_INLINE void mpack_finish_map(mpack_writer_t* writer) {
+    mpack_writer_track_pop(writer, mpack_type_map);
+    mpack_builder_compound_pop(writer);
+}
+
+/**
+ * Starts building an array.
+ *
+ * Elements must follow, and mpack_complete_map() must be called when done. The
+ * number of elements is determined automatically.
+ *
+ * If you know ahead of time the number of elements in the array, it is more
+ * efficient to call mpack_start_array() instead, even if you are already
+ * within another open build.
+ *
+ * Builder containers can be nested within normal (known size) containers and
+ * vice versa. You can call mpack_build_array(), then mpack_start_array()
+ * inside it, then mpack_build_array() inside that, and so forth.
+ *
+ * @see mpack_complete_array() to complete this array
+ * @see mpack_start_array() if you already know the size of the array
+ * @see mpack_build_map() for implementation details
+ */
+void mpack_build_array(struct mpack_writer_t* writer);
+
+/**
+ * Starts building a map.
+ *
+ * An even number of elements must follow, and mpack_complete_map() must be
+ * called when done. The number of elements is determined automatically.
+ *
+ * If you know ahead of time the number of elements in the map, it is more
+ * efficient to call mpack_start_map() instead, even if you are already within
+ * another open build.
+ *
+ * Builder containers can be nested within normal (known size) containers and
+ * vice versa. You can call mpack_build_map(), then mpack_start_map() inside
+ * it, then mpack_build_map() inside that, and so forth.
+ *
+ * A writer in build mode diverts writes to a builder buffer that allocates as
+ * needed. Once the last map or array being built is completed, the deferred
+ * message is composed with computed array and map sizes into the writer.
+ * Builder maps and arrays are encoded exactly the same as ordinary maps and
+ * arrays in the final message.
+ *
+ * This indirect encoding is costly, as it incurs at least an extra copy of all
+ * data written within a builder (but not additional copies for nested
+ * builders.) Expect a speed penalty of half or more.
+ *
+ * A good strategy is to use this during early development when your messages
+ * are constantly changing, and then closer to release when your message
+ * formats have stabilized, replace all your build calls with start calls with
+ * pre-computed sizes. Or don't, if you find the builder has little impact on
+ * performance, because even with builders MPack is extremely fast.
+ *
+ * @note When an array or map starts being built, nothing will be flushed
+ *       until it is completed. If you are building a large message that
+ *       does not fit in the output stream, you won't get an error about it
+ *       until everything is written.
+ *
+ * @see mpack_complete_map() to complete this map
+ * @see mpack_start_map() if you already know the size of the map
+ */
+void mpack_build_map(struct mpack_writer_t* writer);
+
+/**
+ * Completes an array being built.
+ *
+ * @see mpack_build_array()
+ */
+void mpack_complete_array(struct mpack_writer_t* writer);
+
+/**
+ * Completes a map being built.
+ *
+ * @see mpack_build_map()
+ */
+void mpack_complete_map(struct mpack_writer_t* writer);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Data Helpers
+ * @{
+ */
+
+/**
+ * Writes a string.
+ *
+ * To stream a string in chunks, use mpack_start_str() instead.
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON. You should consider
+ * calling mpack_write_utf8() instead, especially if you will be reading
+ * it back as UTF-8.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_str(mpack_writer_t* writer, const char* str, uint32_t length);
+
+/**
+ * Writes a string, ensuring that it is valid UTF-8.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ *
+ * @throws mpack_error_invalid if the string is not valid UTF-8
+ */
+void mpack_write_utf8(mpack_writer_t* writer, const char* str, uint32_t length);
+
+/**
+ * Writes a null-terminated string. (The null-terminator is not written.)
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON. You should consider
+ * calling mpack_write_utf8_cstr() instead, especially if you will be reading
+ * it back as UTF-8.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_cstr(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a null-terminated string, or a nil node if the given cstr pointer
+ * is NULL. (The null-terminator is not written.)
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON. You should consider
+ * calling mpack_write_utf8_cstr_or_nil() instead, especially if you will
+ * be reading it back as UTF-8.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_cstr_or_nil(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a null-terminated string, ensuring that it is valid UTF-8. (The
+ * null-terminator is not written.)
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ *
+ * @throws mpack_error_invalid if the string is not valid UTF-8
+ */
+void mpack_write_utf8_cstr(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a null-terminated string ensuring that it is valid UTF-8, or
+ * writes nil if the given cstr pointer is NULL. (The null-terminator
+ * is not written.)
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ *
+ * @throws mpack_error_invalid if the string is not valid UTF-8
+ */
+void mpack_write_utf8_cstr_or_nil(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a binary blob.
+ *
+ * To stream a binary blob in chunks, use mpack_start_bin() instead.
+ *
+ * You should not call mpack_finish_bin() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count);
+
+#if MPACK_EXTENSIONS
+/**
+ * Writes an extension type.
+ *
+ * To stream an extension blob in chunks, use mpack_start_ext() instead.
+ *
+ * Extension types [0, 127] are available for application-specific types. Extension
+ * types [-128, -1] are reserved for future extensions of MessagePack.
+ *
+ * You should not call mpack_finish_ext() after calling this; this
+ * performs both start and finish.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Chunked Data Functions
+ * @{
+ */
+
+/**
+ * Opens a string. `count` bytes should be written with calls to
+ * mpack_write_bytes(), and mpack_finish_str() should be called
+ * when done.
+ *
+ * To write an entire string at once, use mpack_write_str() or
+ * mpack_write_cstr() instead.
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON.
+ */
+void mpack_start_str(mpack_writer_t* writer, uint32_t count);
+
+/**
+ * Opens a binary blob. `count` bytes should be written with calls to
+ * mpack_write_bytes(), and mpack_finish_bin() should be called
+ * when done.
+ */
+void mpack_start_bin(mpack_writer_t* writer, uint32_t count);
+
+#if MPACK_EXTENSIONS
+/**
+ * Opens an extension type. `count` bytes should be written with calls
+ * to mpack_write_bytes(), and mpack_finish_ext() should be called
+ * when done.
+ *
+ * Extension types [0, 127] are available for application-specific types. Extension
+ * types [-128, -1] are reserved for future extensions of MessagePack.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count);
+#endif
+
+/**
+ * Writes a portion of bytes for a string, binary blob or extension type which
+ * was opened by mpack_write_tag() or one of the mpack_start_*() functions.
+ *
+ * This can be called multiple times to write the data in chunks, as long as
+ * the total amount of bytes written matches the count given when the compound
+ * type was started.
+ *
+ * The corresponding mpack_finish_*() function must be called when done.
+ *
+ * To write an entire string, binary blob or extension type at
+ * once, use one of the mpack_write_*() functions instead.
+ *
+ * @see mpack_write_tag()
+ * @see mpack_start_str()
+ * @see mpack_start_bin()
+ * @see mpack_start_ext()
+ * @see mpack_finish_str()
+ * @see mpack_finish_bin()
+ * @see mpack_finish_ext()
+ * @see mpack_finish_type()
+ */
+void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count);
+
+/**
+ * Finishes writing a string.
+ *
+ * This should be called only after a corresponding call to mpack_start_str()
+ * and after the string bytes are written with mpack_write_bytes().
+ *
+ * This will track writes to ensure that the correct number of elements are written.
+ *
+ * @see mpack_start_str()
+ * @see mpack_write_bytes()
+ */
+MPACK_INLINE void mpack_finish_str(mpack_writer_t* writer) {
+    mpack_writer_track_pop(writer, mpack_type_str);
+}
+
+/**
+ * Finishes writing a binary blob.
+ *
+ * This should be called only after a corresponding call to mpack_start_bin()
+ * and after the binary bytes are written with mpack_write_bytes().
+ *
+ * This will track writes to ensure that the correct number of bytes are written.
+ *
+ * @see mpack_start_bin()
+ * @see mpack_write_bytes()
+ */
+MPACK_INLINE void mpack_finish_bin(mpack_writer_t* writer) {
+    mpack_writer_track_pop(writer, mpack_type_bin);
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Finishes writing an extended type binary data blob.
+ *
+ * This should be called only after a corresponding call to mpack_start_bin()
+ * and after the binary bytes are written with mpack_write_bytes().
+ *
+ * This will track writes to ensure that the correct number of bytes are written.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_start_ext()
+ * @see mpack_write_bytes()
+ */
+MPACK_INLINE void mpack_finish_ext(mpack_writer_t* writer) {
+    mpack_writer_track_pop(writer, mpack_type_ext);
+}
+#endif
+
+/**
+ * Finishes writing the given compound type.
+ *
+ * This will track writes to ensure that the correct number of elements
+ * or bytes are written.
+ *
+ * This can be called with the appropriate type instead the corresponding
+ * mpack_finish_*() function if you want to finish a dynamic type.
+ */
+MPACK_INLINE void mpack_finish_type(mpack_writer_t* writer, mpack_type_t type) {
+    mpack_writer_track_pop(writer, type);
+}
+
+/**
+ * @}
+ */
+
+#if MPACK_HAS_GENERIC && !defined(__cplusplus)
+
+/**
+ * @name Type-Generic Writers
+ * @{
+ */
+
+/**
+ * @def mpack_write(writer, value)
+ *
+ * Type-generic writer for primitive types.
+ *
+ * The compiler will dispatch to an appropriate write function based
+ * on the type of the @a value parameter.
+ *
+ * @note This requires C11 `_Generic` support. (A set of inline overloads
+ * are used in C++ to provide the same functionality.)
+ *
+ * @warning In C11, the indentifiers `true`, `false` and `NULL` are
+ * all of type `int`, not `bool` or `void*`! They will emit unexpected
+ * types when passed uncast, so be careful when using them.
+ */
+#if MPACK_FLOAT
+    #define MPACK_WRITE_GENERIC_FLOAT float: mpack_write_float,
+#else
+    #define MPACK_WRITE_GENERIC_FLOAT /*nothing*/
+#endif
+#if MPACK_DOUBLE
+    #define MPACK_WRITE_GENERIC_DOUBLE double: mpack_write_double,
+#else
+    #define MPACK_WRITE_GENERIC_DOUBLE /*nothing*/
+#endif
+#define mpack_write(writer, value) \
+    _Generic(((void)0, value),                      \
+              int8_t: mpack_write_i8,               \
+             int16_t: mpack_write_i16,              \
+             int32_t: mpack_write_i32,              \
+             int64_t: mpack_write_i64,              \
+             uint8_t: mpack_write_u8,               \
+            uint16_t: mpack_write_u16,              \
+            uint32_t: mpack_write_u32,              \
+            uint64_t: mpack_write_u64,              \
+                bool: mpack_write_bool,             \
+            MPACK_WRITE_GENERIC_FLOAT               \
+            MPACK_WRITE_GENERIC_DOUBLE              \
+              char *: mpack_write_cstr_or_nil,      \
+        const char *: mpack_write_cstr_or_nil       \
+    )(writer, value)
+
+/**
+ * @def mpack_write_kv(writer, key, value)
+ *
+ * Type-generic writer for key-value pairs of null-terminated string
+ * keys and primitive values.
+ *
+ * @warning @a writer may be evaluated multiple times.
+ *
+ * @warning In C11, the indentifiers `true`, `false` and `NULL` are
+ * all of type `int`, not `bool` or `void*`! They will emit unexpected
+ * types when passed uncast, so be careful when using them.
+ *
+ * @param writer The writer.
+ * @param key A null-terminated C string.
+ * @param value A primitive type supported by mpack_write().
+ */
+#define mpack_write_kv(writer, key, value) do {     \
+    mpack_write_cstr(writer, key);                  \
+    mpack_write(writer, value);                     \
+} while (0)
+
+/**
+ * @}
+ */
+
+#endif // MPACK_HAS_GENERIC && !defined(__cplusplus)
+
+// The rest of this file contains C++ overloads, so we end extern "C" here.
+MPACK_EXTERN_C_END
+
+#if defined(__cplusplus) || defined(MPACK_DOXYGEN)
+
+namespace mpack {
+/**
+ * @name C++ write overloads
+ * @{
+ */
+
+/*
+ * C++ generic writers for primitive values
+ */
+
+#ifdef MPACK_DOXYGEN
+#undef mpack_write
+#undef mpack_write_kv
+#endif
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int8_t value) {
+    mpack_write_i8(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int16_t value) {
+    mpack_write_i16(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int32_t value) {
+    mpack_write_i32(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int64_t value) {
+    mpack_write_i64(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint8_t value) {
+    mpack_write_u8(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint16_t value) {
+    mpack_write_u16(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint32_t value) {
+    mpack_write_u32(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint64_t value) {
+    mpack_write_u64(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, bool value) {
+    mpack_write_bool(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, float value) {
+    mpack_write_float(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, double value) {
+    mpack_write_double(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, char *value) {
+    mpack_write_cstr_or_nil(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, const char *value) {
+    mpack_write_cstr_or_nil(writer, value);
+}
+
+/* C++ generic write for key-value pairs */
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int8_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_i8(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int16_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_i16(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int32_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_i32(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int64_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_i64(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint8_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_u8(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint16_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_u16(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint32_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_u32(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint64_t value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_u64(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, bool value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_bool(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, float value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_float(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, double value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_double(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, char *value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_cstr_or_nil(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, const char *value) {
+    mpack_write_cstr(writer, key);
+    mpack_write_cstr_or_nil(writer, value);
+}
+
+/**
+ * @}
+ */
+}  // namespace mpack
+#endif /* __cplusplus */
+
+/**
+ * @}
+ */
+
+MPACK_SILENCE_WARNINGS_END
+
+#endif // MPACK_WRITER
+
+#endif
+
+/* mpack/mpack-reader.h.h */
+
+/**
+ * @file
+ *
+ * Declares the core MPack Tag Reader.
+ */
+
+#ifndef MPACK_READER_H
+#define MPACK_READER_H 1
+
+/* #include "mpack-common.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+MPACK_EXTERN_C_BEGIN
+
+#if MPACK_READER
+
+#if MPACK_READ_TRACKING
+struct mpack_track_t;
+#endif
+
+// The denominator to determine whether a read is a small
+// fraction of the buffer size.
+#define MPACK_READER_SMALL_FRACTION_DENOMINATOR 32
+
+/**
+ * @defgroup reader Reader API
+ *
+ * The MPack Reader API contains functions for imperatively reading dynamically
+ * typed data from a MessagePack stream.
+ *
+ * See @ref docs/reader.md for examples.
+ *
+ * @note If you are not writing code for an embedded device (or otherwise do
+ * not need maximum performance with minimal memory usage), you should not use
+ * this. You probably want to use the @link node Node API@endlink instead.
+ *
+ * This forms the basis of the @link expect Expect API@endlink, which can be
+ * used to interpret the stream of elements in expected types and value ranges.
+ *
+ * @{
+ */
+
+/**
+ * @def MPACK_READER_MINIMUM_BUFFER_SIZE
+ *
+ * The minimum buffer size for a reader with a fill function.
+ */
+#define MPACK_READER_MINIMUM_BUFFER_SIZE 32
+
+/**
+ * A buffered MessagePack decoder.
+ *
+ * The decoder wraps an existing buffer and, optionally, a fill function.
+ * This allows efficiently decoding data from existing memory buffers, files,
+ * streams, etc.
+ *
+ * All read operations are synchronous; they will block until the
+ * requested data is fully read, or an error occurs.
+ *
+ * This structure is opaque; its fields should not be accessed outside
+ * of MPack.
+ */
+typedef struct mpack_reader_t mpack_reader_t;
+
+/**
+ * The MPack reader's fill function. It should fill the buffer with at
+ * least one byte and at most the given @c count, returning the number
+ * of bytes written to the buffer.
+ *
+ * In case of error, it should flag an appropriate error on the reader
+ * (usually @ref mpack_error_io), or simply return zero. If zero is
+ * returned, mpack_error_io is raised.
+ *
+ * @note When reading from a stream, you should only copy and return
+ * the bytes that are immediately available. It is always safe to return
+ * less than the requested count as long as some non-zero number of bytes
+ * are read; if more bytes are needed, the read function will simply be
+ * called again.
+ *
+ * @see mpack_reader_context()
+ */
+typedef size_t (*mpack_reader_fill_t)(mpack_reader_t* reader, char* buffer, size_t count);
+
+/**
+ * The MPack reader's skip function. It should discard the given number
+ * of bytes from the source (for example by seeking forward.)
+ *
+ * In case of error, it should flag an appropriate error on the reader.
+ *
+ * @see mpack_reader_context()
+ */
+typedef void (*mpack_reader_skip_t)(mpack_reader_t* reader, size_t count);
+
+/**
+ * An error handler function to be called when an error is flagged on
+ * the reader.
+ *
+ * The error handler will only be called once on the first error flagged;
+ * any subsequent reads and errors are ignored, and the reader is
+ * permanently in that error state.
+ *
+ * MPack is safe against non-local jumps out of error handler callbacks.
+ * This means you are allowed to longjmp or throw an exception (in C++,
+ * Objective-C, or with SEH) out of this callback.
+ *
+ * Bear in mind when using longjmp that local non-volatile variables that
+ * have changed are undefined when setjmp() returns, so you can't put the
+ * reader on the stack in the same activation frame as the setjmp without
+ * declaring it volatile.
+ *
+ * You must still eventually destroy the reader. It is not destroyed
+ * automatically when an error is flagged. It is safe to destroy the
+ * reader within this error callback, but you will either need to perform
+ * a non-local jump, or store something in your context to identify
+ * that the reader is destroyed since any future accesses to it cause
+ * undefined behavior.
+ */
+typedef void (*mpack_reader_error_t)(mpack_reader_t* reader, mpack_error_t error);
+
+/**
+ * A teardown function to be called when the reader is destroyed.
+ */
+typedef void (*mpack_reader_teardown_t)(mpack_reader_t* reader);
+
+/* Hide internals from documentation */
+/** @cond */
+
+struct mpack_reader_t {
+    void* context;                    /* Context for reader callbacks */
+    mpack_reader_fill_t fill;         /* Function to read bytes into the buffer */
+    mpack_reader_error_t error_fn;    /* Function to call on error */
+    mpack_reader_teardown_t teardown; /* Function to teardown the context on destroy */
+    mpack_reader_skip_t skip;         /* Function to skip bytes from the source */
+
+    char* buffer;       /* Writeable byte buffer */
+    size_t size;        /* Size of the buffer */
+
+    const char* data;   /* Current data pointer (in the buffer, if it is used) */
+    const char* end;    /* The end of available data (in the buffer, if it is used) */
+
+    mpack_error_t error;  /* Error state */
+
+    #if MPACK_READ_TRACKING
+    mpack_track_t track; /* Stack of map/array/str/bin/ext reads */
+    #endif
+};
+
+/** @endcond */
+
+/**
+ * @name Lifecycle Functions
+ * @{
+ */
+
+/**
+ * Initializes an MPack reader with the given buffer. The reader does
+ * not assume ownership of the buffer, but the buffer must be writeable
+ * if a fill function will be used to refill it.
+ *
+ * @param reader The MPack reader.
+ * @param buffer The buffer with which to read MessagePack data.
+ * @param size The size of the buffer.
+ * @param count The number of bytes already in the buffer.
+ */
+void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count);
+
+/**
+ * Initializes an MPack reader directly into an error state. Use this if you
+ * are writing a wrapper to mpack_reader_init() which can fail its setup.
+ */
+void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error);
+
+/**
+ * Initializes an MPack reader to parse a pre-loaded contiguous chunk of data. The
+ * reader does not assume ownership of the data.
+ *
+ * @param reader The MPack reader.
+ * @param data The data to parse.
+ * @param count The number of bytes pointed to by data.
+ */
+void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count);
+
+#if MPACK_STDIO
+/**
+ * Initializes an MPack reader that reads from a file.
+ *
+ * The file will be automatically opened and closed by the reader.
+ */
+void mpack_reader_init_filename(mpack_reader_t* reader, const char* filename);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_reader_init_filename().
+ */
+MPACK_INLINE void mpack_reader_init_file(mpack_reader_t* reader, const char* filename) {
+    mpack_reader_init_filename(reader, filename);
+}
+
+/**
+ * Initializes an MPack reader that reads from a libc FILE. This can be used to
+ * read from stdin, or from a file opened separately.
+ *
+ * @param reader The MPack reader.
+ * @param stdfile The FILE.
+ * @param close_when_done If true, fclose() will be called on the FILE when it
+ *         is no longer needed. If false, the file will not be closed when
+ *         reading is done.
+ *
+ * @warning The reader is buffered. It will read data in advance of parsing it,
+ * and it may read more data than it parsed. See mpack_reader_remaining() to
+ * access the extra data.
+ */
+void mpack_reader_init_stdfile(mpack_reader_t* reader, FILE* stdfile, bool close_when_done);
+#endif
+
+/**
+ * @def mpack_reader_init_stack(reader)
+ * @hideinitializer
+ *
+ * Initializes an MPack reader using stack space as a buffer. A fill function
+ * should be added to the reader to fill the buffer.
+ *
+ * @see mpack_reader_set_fill
+ */
+
+/** @cond */
+#define mpack_reader_init_stack_line_ex(line, reader) \
+    char mpack_buf_##line[MPACK_STACK_SIZE]; \
+    mpack_reader_init((reader), mpack_buf_##line, sizeof(mpack_buf_##line), 0)
+
+#define mpack_reader_init_stack_line(line, reader) \
+    mpack_reader_init_stack_line_ex(line, reader)
+/** @endcond */
+
+#define mpack_reader_init_stack(reader) \
+    mpack_reader_init_stack_line(__LINE__, (reader))
+
+/**
+ * Cleans up the MPack reader, ensuring that all compound elements
+ * have been completely read. Returns the final error state of the
+ * reader.
+ *
+ * This will assert in tracking mode if the reader is not in an error
+ * state and has any incomplete reads. If you want to cancel reading
+ * in the middle of a document, you need to flag an error on the reader
+ * before destroying it (such as mpack_error_data).
+ *
+ * @see mpack_read_tag()
+ * @see mpack_reader_flag_error()
+ * @see mpack_error_data
+ */
+mpack_error_t mpack_reader_destroy(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Callbacks
+ * @{
+ */
+
+/**
+ * Sets the custom pointer to pass to the reader callbacks, such as fill
+ * or teardown.
+ *
+ * @param reader The MPack reader.
+ * @param context User data to pass to the reader callbacks.
+ *
+ * @see mpack_reader_context()
+ */
+MPACK_INLINE void mpack_reader_set_context(mpack_reader_t* reader, void* context) {
+    reader->context = context;
+}
+
+/**
+ * Returns the custom context for reader callbacks.
+ *
+ * @see mpack_reader_set_context
+ * @see mpack_reader_set_fill
+ * @see mpack_reader_set_skip
+ */
+MPACK_INLINE void* mpack_reader_context(mpack_reader_t* reader) {
+    return reader->context;
+}
+
+/**
+ * Sets the fill function to refill the data buffer when it runs out of data.
+ *
+ * If no fill function is used, truncated MessagePack data results in
+ * mpack_error_invalid (since the buffer is assumed to contain a
+ * complete MessagePack object.)
+ *
+ * If a fill function is used, truncated MessagePack data usually
+ * results in mpack_error_io (since the fill function fails to get
+ * the missing data.)
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the fill function.
+ *
+ * @param reader The MPack reader.
+ * @param fill The function to fetch additional data into the buffer.
+ */
+void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill);
+
+/**
+ * Sets the skip function to discard bytes from the source stream.
+ *
+ * It's not necessary to implement this function. If the stream is not
+ * seekable, don't set a skip callback. The reader will fall back to
+ * using the fill function instead.
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the skip function.
+ *
+ * The skip function is ignored in size-optimized builds to reduce code
+ * size. Data will be skipped with the fill function when necessary.
+ *
+ * @param reader The MPack reader.
+ * @param skip The function to discard bytes from the source stream.
+ */
+void mpack_reader_set_skip(mpack_reader_t* reader, mpack_reader_skip_t skip);
+
+/**
+ * Sets the error function to call when an error is flagged on the reader.
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the error function.
+ *
+ * See the definition of mpack_reader_error_t for more information about
+ * what you can do from an error callback.
+ *
+ * @see mpack_reader_error_t
+ * @param reader The MPack reader.
+ * @param error_fn The function to call when an error is flagged on the reader.
+ */
+MPACK_INLINE void mpack_reader_set_error_handler(mpack_reader_t* reader, mpack_reader_error_t error_fn) {
+    reader->error_fn = error_fn;
+}
+
+/**
+ * Sets the teardown function to call when the reader is destroyed.
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the teardown function.
+ *
+ * @param reader The MPack reader.
+ * @param teardown The function to call when the reader is destroyed.
+ */
+MPACK_INLINE void mpack_reader_set_teardown(mpack_reader_t* reader, mpack_reader_teardown_t teardown) {
+    reader->teardown = teardown;
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Core Reader Functions
+ * @{
+ */
+
+/**
+ * Queries the error state of the MPack reader.
+ *
+ * If a reader is in an error state, you should discard all data since the
+ * last time the error flag was checked. The error flag cannot be cleared.
+ */
+MPACK_INLINE mpack_error_t mpack_reader_error(mpack_reader_t* reader) {
+    return reader->error;
+}
+
+/**
+ * Places the reader in the given error state, calling the error callback if one
+ * is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the reader is already in an error state, this call is ignored and no
+ * error callback is called.
+ */
+void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error);
+
+/**
+ * Places the reader in the given error state if the given error is not mpack_ok,
+ * returning the resulting error state of the reader.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the given error is mpack_ok or if the reader is already in an error state,
+ * this call is ignored and the actual error state of the reader is returned.
+ */
+MPACK_INLINE mpack_error_t mpack_reader_flag_if_error(mpack_reader_t* reader, mpack_error_t error) {
+    if (error != mpack_ok)
+        mpack_reader_flag_error(reader, error);
+    return mpack_reader_error(reader);
+}
+
+/**
+ * Returns bytes left in the reader's buffer.
+ *
+ * If you are done reading MessagePack data but there is other interesting data
+ * following it, the reader may have buffered too much data. The number of bytes
+ * remaining in the buffer and a pointer to the position of those bytes can be
+ * queried here.
+ *
+ * If you know the length of the MPack chunk beforehand, it's better to instead
+ * have your fill function limit the data it reads so that the reader does not
+ * have extra data. In this case you can simply check that this returns zero.
+ *
+ * Returns 0 if the reader is in an error state.
+ *
+ * @param reader The MPack reader from which to query remaining data.
+ * @param data [out] A pointer to the remaining data, or NULL.
+ * @return The number of bytes remaining in the buffer.
+ */
+size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data);
+
+/**
+ * Reads a MessagePack object header (an MPack tag.)
+ *
+ * If an error occurs, the reader is placed in an error state and a
+ * nil tag is returned. If the reader is already in an error state,
+ * a nil tag is returned.
+ *
+ * If the type is compound (i.e. is a map, array, string, binary or
+ * extension type), additional reads are required to get the contained
+ * data, and the corresponding done function must be called when done.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @see mpack_read_bytes()
+ * @see mpack_done_array()
+ * @see mpack_done_map()
+ * @see mpack_done_str()
+ * @see mpack_done_bin()
+ * @see mpack_done_ext()
+ */
+mpack_tag_t mpack_read_tag(mpack_reader_t* reader);
+
+/**
+ * Parses the next MessagePack object header (an MPack tag) without
+ * advancing the reader.
+ *
+ * If an error occurs, the reader is placed in an error state and a
+ * nil tag is returned. If the reader is already in an error state,
+ * a nil tag is returned.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @see mpack_read_tag()
+ * @see mpack_discard()
+ */
+mpack_tag_t mpack_peek_tag(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/**
+ * @name String and Data Functions
+ * @{
+ */
+
+/**
+ * Skips bytes from the underlying stream. This is used only to
+ * skip the contents of a string, binary blob or extension object.
+ */
+void mpack_skip_bytes(mpack_reader_t* reader, size_t count);
+
+/**
+ * Reads bytes from a string, binary blob or extension object, copying
+ * them into the given buffer.
+ *
+ * A str, bin or ext must have been opened by a call to mpack_read_tag()
+ * which yielded one of these types, or by a call to an expect function
+ * such as mpack_expect_str() or mpack_expect_bin().
+ *
+ * If an error occurs, the buffer contents are undefined.
+ *
+ * This can be called multiple times for a single str, bin or ext
+ * to read the data in chunks. The total data read must add up
+ * to the size of the object.
+ *
+ * @param reader The MPack reader
+ * @param p The buffer in which to copy the bytes
+ * @param count The number of bytes to read
+ */
+void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count);
+
+/**
+ * Reads bytes from a string, ensures that the string is valid UTF-8,
+ * and copies the bytes into the given buffer.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The given byte count must match the complete size of the string as
+ * returned by the tag or expect function. You must ensure that the
+ * buffer fits the data.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * If an error occurs, the buffer contents are undefined.
+ *
+ * Unlike mpack_read_bytes(), this cannot be used to read the data in
+ * chunks (since this might split a character's UTF-8 bytes, and the
+ * reader does not keep track of the UTF-8 decoding state between reads.)
+ *
+ * @throws mpack_error_type if the string contains invalid UTF-8.
+ */
+void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count);
+
+/**
+ * Reads bytes from a string, ensures that the string contains no NUL
+ * bytes, copies the bytes into the given buffer and adds a null-terminator.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The given byte count must match the size of the string as returned
+ * by the tag or expect function. The string will only be copied if
+ * the buffer is large enough to store it.
+ *
+ * If an error occurs, the buffer will contain an empty string.
+ *
+ * @note If you know the object will be a string before reading it,
+ * it is highly recommended to use mpack_expect_cstr() instead.
+ * Alternatively you could use mpack_peek_tag() and call
+ * mpack_expect_cstr() if it's a string.
+ *
+ * @throws mpack_error_too_big if the string plus null-terminator is larger than the given buffer size
+ * @throws mpack_error_type if the string contains a null byte.
+ *
+ * @see mpack_peek_tag()
+ * @see mpack_expect_cstr()
+ * @see mpack_expect_utf8_cstr()
+ */
+void mpack_read_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count);
+
+/**
+ * Reads bytes from a string, ensures that the string is valid UTF-8
+ * with no NUL bytes, copies the bytes into the given buffer and adds a
+ * null-terminator.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The given byte count must match the size of the string as returned
+ * by the tag or expect function. The string will only be copied if
+ * the buffer is large enough to store it.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since
+ * it cannot be represented in a null-terminated string.
+ *
+ * If an error occurs, the buffer will contain an empty string.
+ *
+ * @note If you know the object will be a string before reading it,
+ * it is highly recommended to use mpack_expect_utf8_cstr() instead.
+ * Alternatively you could use mpack_peek_tag() and call
+ * mpack_expect_utf8_cstr() if it's a string.
+ *
+ * @throws mpack_error_too_big if the string plus null-terminator is larger than the given buffer size
+ * @throws mpack_error_type if the string contains invalid UTF-8 or a null byte.
+ *
+ * @see mpack_peek_tag()
+ * @see mpack_expect_utf8_cstr()
+ */
+void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count);
+
+#ifdef MPACK_MALLOC
+/** @cond */
+// This can optionally add a null-terminator, but it does not check
+// whether the data contains null bytes. This must be done separately
+// in a cstring read function (possibly as part of a UTF-8 check.)
+char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated);
+/** @endcond */
+
+/**
+ * Reads bytes from a string, binary blob or extension object, allocating
+ * storage for them and returning the allocated pointer.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * Returns NULL if any error occurs, or if count is zero.
+ */
+MPACK_INLINE char* mpack_read_bytes_alloc(mpack_reader_t* reader, size_t count) {
+    return mpack_read_bytes_alloc_impl(reader, count, false);
+}
+#endif
+
+/**
+ * Reads bytes from a string, binary blob or extension object in-place in
+ * the buffer. This can be used to avoid copying the data.
+ *
+ * A str, bin or ext must have been opened by a call to mpack_read_tag()
+ * which yielded one of these types, or by a call to an expect function
+ * such as mpack_expect_str() or mpack_expect_bin().
+ *
+ * If the bytes are from a string, the string is not null-terminated! Use
+ * mpack_read_cstr() to copy the string into a buffer and add a null-terminator.
+ *
+ * The returned pointer is invalidated on the next read, or when the buffer
+ * is destroyed.
+ *
+ * The reader will move data around in the buffer if needed to ensure that
+ * the pointer can always be returned, so this should only be used if
+ * count is very small compared to the buffer size. If you need to check
+ * whether a small size is reasonable (for example you intend to handle small and
+ * large sizes differently), you can call mpack_should_read_bytes_inplace().
+ *
+ * This can be called multiple times for a single str, bin or ext
+ * to read the data in chunks. The total data read must add up
+ * to the size of the object.
+ *
+ * NULL is returned if the reader is in an error state.
+ *
+ * @throws mpack_error_too_big if the requested size is larger than the buffer size
+ *
+ * @see mpack_should_read_bytes_inplace()
+ */
+const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count);
+
+/**
+ * Reads bytes from a string in-place in the buffer and ensures they are
+ * valid UTF-8. This can be used to avoid copying the data.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The string is not null-terminated! Use mpack_read_utf8_cstr() to
+ * copy the string into a buffer and add a null-terminator.
+ *
+ * The returned pointer is invalidated on the next read, or when the buffer
+ * is destroyed.
+ *
+ * The reader will move data around in the buffer if needed to ensure that
+ * the pointer can always be returned, so this should only be used if
+ * count is very small compared to the buffer size. If you need to check
+ * whether a small size is reasonable (for example you intend to handle small and
+ * large sizes differently), you can call mpack_should_read_bytes_inplace().
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * Unlike mpack_read_bytes_inplace(), this cannot be used to read the data in
+ * chunks (since this might split a character's UTF-8 bytes, and the
+ * reader does not keep track of the UTF-8 decoding state between reads.)
+ *
+ * NULL is returned if the reader is in an error state.
+ *
+ * @throws mpack_error_type if the string contains invalid UTF-8
+ * @throws mpack_error_too_big if the requested size is larger than the buffer size
+ *
+ * @see mpack_should_read_bytes_inplace()
+ */
+const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count);
+
+/**
+ * Returns true if it's a good idea to read the given number of bytes
+ * in-place.
+ *
+ * If the read will be larger than some small fraction of the buffer size,
+ * this will return false to avoid shuffling too much data back and forth
+ * in the buffer.
+ *
+ * Use this if you're expecting arbitrary size data, and you want to read
+ * in-place for the best performance when possible but will fall back to
+ * a normal read if the data is too large.
+ *
+ * @see mpack_read_bytes_inplace()
+ */
+MPACK_INLINE bool mpack_should_read_bytes_inplace(mpack_reader_t* reader, size_t count) {
+    return (reader->size == 0 || count <= reader->size / MPACK_READER_SMALL_FRACTION_DENOMINATOR);
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Reads a timestamp contained in an ext object of the given size, closing the
+ * ext type.
+ *
+ * An ext object of exttype @ref MPACK_EXTTYPE_TIMESTAMP must have been opened
+ * by a call to e.g. mpack_read_tag() or mpack_expect_ext().
+ *
+ * You must NOT call mpack_done_ext() after calling this. A timestamp ext
+ * object can only contain a single timestamp value, so this calls
+ * mpack_done_ext() automatically.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @throws mpack_error_invalid if the size is not one of the supported
+ * timestamp sizes, or if the nanoseconds are out of range.
+ */
+mpack_timestamp_t mpack_read_timestamp(mpack_reader_t* reader, size_t size);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Core Reader Functions
+ * @{
+ */
+
+#if MPACK_READ_TRACKING
+/**
+ * Finishes reading the given type.
+ *
+ * This will track reads to ensure that the correct number of elements
+ * or bytes are read.
+ */
+void mpack_done_type(mpack_reader_t* reader, mpack_type_t type);
+#else
+MPACK_INLINE void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) {
+    MPACK_UNUSED(reader);
+    MPACK_UNUSED(type);
+}
+#endif
+
+/**
+ * Finishes reading an array.
+ *
+ * This will track reads to ensure that the correct number of elements are read.
+ */
+MPACK_INLINE void mpack_done_array(mpack_reader_t* reader) {
+    mpack_done_type(reader, mpack_type_array);
+}
+
+/**
+ * @fn mpack_done_map(mpack_reader_t* reader)
+ *
+ * Finishes reading a map.
+ *
+ * This will track reads to ensure that the correct number of elements are read.
+ */
+MPACK_INLINE void mpack_done_map(mpack_reader_t* reader) {
+    mpack_done_type(reader, mpack_type_map);
+}
+
+/**
+ * @fn mpack_done_str(mpack_reader_t* reader)
+ *
+ * Finishes reading a string.
+ *
+ * This will track reads to ensure that the correct number of bytes are read.
+ */
+MPACK_INLINE void mpack_done_str(mpack_reader_t* reader) {
+    mpack_done_type(reader, mpack_type_str);
+}
+
+/**
+ * @fn mpack_done_bin(mpack_reader_t* reader)
+ *
+ * Finishes reading a binary data blob.
+ *
+ * This will track reads to ensure that the correct number of bytes are read.
+ */
+MPACK_INLINE void mpack_done_bin(mpack_reader_t* reader) {
+    mpack_done_type(reader, mpack_type_bin);
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * @fn mpack_done_ext(mpack_reader_t* reader)
+ *
+ * Finishes reading an extended type binary data blob.
+ *
+ * This will track reads to ensure that the correct number of bytes are read.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+MPACK_INLINE void mpack_done_ext(mpack_reader_t* reader) {
+    mpack_done_type(reader, mpack_type_ext);
+}
+#endif
+
+/**
+ * Reads and discards the next object. This will read and discard all
+ * contained data as well if it is a compound type.
+ */
+void mpack_discard(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/** @cond */
+
+#if MPACK_DEBUG && MPACK_STDIO
+/**
+ * @name Debugging Functions
+ * @{
+ */
+/*
+ * Converts a blob of MessagePack to a pseudo-JSON string for debugging
+ * purposes, placing the result in the given buffer with a null-terminator.
+ *
+ * If the buffer does not have enough space, the result will be truncated (but
+ * it is guaranteed to be null-terminated.)
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_print_data_to_buffer(const char* data, size_t data_size, char* buffer, size_t buffer_size);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes, calling the given
+ * callback as many times as is necessary to output the character data.
+ *
+ * No null-terminator or trailing newline will be written.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_print_data_to_callback(const char* data, size_t size, mpack_print_callback_t callback, void* context);
+
+/*
+ * Converts a blob of MessagePack to pseudo-JSON for debugging purposes
+ * and pretty-prints it to the given file.
+ */
+void mpack_print_data_to_file(const char* data, size_t len, FILE* file);
+
+/*
+ * Converts a blob of MessagePack to pseudo-JSON for debugging purposes
+ * and pretty-prints it to stdout.
+ */
+MPACK_INLINE void mpack_print_data_to_stdout(const char* data, size_t len) {
+    mpack_print_data_to_file(data, len, stdout);
+}
+
+/*
+ * Converts the MessagePack contained in the given `FILE*` to pseudo-JSON for
+ * debugging purposes, calling the given callback as many times as is necessary
+ * to output the character data.
+ */
+void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback, void* context);
+
+/*
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_print_data_to_stdout().
+ */
+MPACK_INLINE void mpack_print(const char* data, size_t len) {
+    mpack_print_data_to_stdout(data, len);
+}
+
+/**
+ * @}
+ */
+#endif
+
+/** @endcond */
+
+/**
+ * @}
+ */
+
+
+
+#if MPACK_INTERNAL
+
+bool mpack_reader_ensure_straddle(mpack_reader_t* reader, size_t count);
+
+/*
+ * Ensures there are at least @c count bytes left in the
+ * data, raising an error and returning false if more
+ * data cannot be made available.
+ */
+MPACK_INLINE bool mpack_reader_ensure(mpack_reader_t* reader, size_t count) {
+    mpack_assert(count != 0, "cannot ensure zero bytes!");
+    mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
+
+    if (count <= (size_t)(reader->end - reader->data))
+        return true;
+    return mpack_reader_ensure_straddle(reader, count);
+}
+
+void mpack_read_native_straddle(mpack_reader_t* reader, char* p, size_t count);
+
+// Reads count bytes into p, deferring to mpack_read_native_straddle() if more
+// bytes are needed than are available in the buffer.
+MPACK_INLINE void mpack_read_native(mpack_reader_t* reader, char* p, size_t count) {
+    mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+    if (count > (size_t)(reader->end - reader->data)) {
+        mpack_read_native_straddle(reader, p, count);
+    } else {
+        mpack_memcpy(p, reader->data, count);
+        reader->data += count;
+    }
+}
+
+#if MPACK_READ_TRACKING
+#define MPACK_READER_TRACK(reader, error_expr) \
+    (((reader)->error == mpack_ok) ? mpack_reader_flag_if_error((reader), (error_expr)) : (reader)->error)
+#else
+#define MPACK_READER_TRACK(reader, error_expr) (MPACK_UNUSED(reader), mpack_ok)
+#endif
+
+MPACK_INLINE mpack_error_t mpack_reader_track_element(mpack_reader_t* reader) {
+    return MPACK_READER_TRACK(reader, mpack_track_element(&reader->track, true));
+}
+
+MPACK_INLINE mpack_error_t mpack_reader_track_peek_element(mpack_reader_t* reader) {
+    return MPACK_READER_TRACK(reader, mpack_track_peek_element(&reader->track, true));
+}
+
+MPACK_INLINE mpack_error_t mpack_reader_track_bytes(mpack_reader_t* reader, size_t count) {
+    MPACK_UNUSED(count);
+    return MPACK_READER_TRACK(reader, mpack_track_bytes(&reader->track, true, count));
+}
+
+MPACK_INLINE mpack_error_t mpack_reader_track_str_bytes_all(mpack_reader_t* reader, size_t count) {
+    MPACK_UNUSED(count);
+    return MPACK_READER_TRACK(reader, mpack_track_str_bytes_all(&reader->track, true, count));
+}
+
+#endif
+
+
+
+#endif
+
+MPACK_EXTERN_C_END
+MPACK_SILENCE_WARNINGS_END
+
+#endif
+
+
+/* mpack/mpack-expect.h.h */
+
+/**
+ * @file
+ *
+ * Declares the MPack static Expect API.
+ */
+
+#ifndef MPACK_EXPECT_H
+#define MPACK_EXPECT_H 1
+
+/* #include "mpack-reader.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+MPACK_EXTERN_C_BEGIN
+
+#if MPACK_EXPECT
+
+#if !MPACK_READER
+#error "MPACK_EXPECT requires MPACK_READER."
+#endif
+
+/**
+ * @defgroup expect Expect API
+ *
+ * The MPack Expect API allows you to easily read MessagePack data when you
+ * expect it to follow a predefined schema.
+ *
+ * @note If you are not writing code for an embedded device (or otherwise do
+ * not need maximum performance with minimal memory usage), you should not use
+ * this. You probably want to use the @link node Node API@endlink instead.
+ *
+ * See @ref docs/expect.md for examples.
+ *
+ * The main purpose of the Expect API is convenience, so the API is lax. It
+ * automatically converts between similar types where there is no loss of
+ * precision.
+ *
+ * When using any of the expect functions, if the type or value of what was
+ * read does not match what is expected, @ref mpack_error_type is raised.
+ *
+ * @{
+ */
+
+/**
+ * @name Basic Number Functions
+ * @{
+ */
+
+/**
+ * Reads an 8-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint8_t mpack_expect_u8(mpack_reader_t* reader);
+
+/**
+ * Reads a 16-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint16_t mpack_expect_u16(mpack_reader_t* reader);
+
+/**
+ * Reads a 32-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint32_t mpack_expect_u32(mpack_reader_t* reader);
+
+/**
+ * Reads a 64-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint64_t mpack_expect_u64(mpack_reader_t* reader);
+
+/**
+ * Reads an 8-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int8_t mpack_expect_i8(mpack_reader_t* reader);
+
+/**
+ * Reads a 16-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int16_t mpack_expect_i16(mpack_reader_t* reader);
+
+/**
+ * Reads a 32-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int32_t mpack_expect_i32(mpack_reader_t* reader);
+
+/**
+ * Reads a 64-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int64_t mpack_expect_i64(mpack_reader_t* reader);
+
+#if MPACK_FLOAT
+/**
+ * Reads a number, returning the value as a float. The underlying value can be an
+ * integer, float or double; the value is converted to a float.
+ *
+ * @note Reading a double or a large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+float mpack_expect_float(mpack_reader_t* reader);
+#endif
+
+#if MPACK_DOUBLE
+/**
+ * Reads a number, returning the value as a double. The underlying value can be an
+ * integer, float or double; the value is converted to a double.
+ *
+ * @note Reading a very large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+double mpack_expect_double(mpack_reader_t* reader);
+#endif
+
+#if MPACK_FLOAT
+/**
+ * Reads a float. The underlying value must be a float, not a double or an integer.
+ * This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float.
+ */
+float mpack_expect_float_strict(mpack_reader_t* reader);
+#endif
+
+#if MPACK_DOUBLE
+/**
+ * Reads a double. The underlying value must be a float or double, not an integer.
+ * This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float or double.
+ */
+double mpack_expect_double_strict(mpack_reader_t* reader);
+#endif
+
+#if !MPACK_FLOAT
+/**
+ * Reads a float as a raw uint32_t. The underlying value must be a float, not a
+ * double or an integer.
+ *
+ * @throws mpack_error_type if the underlying value is not a float.
+ */
+uint32_t mpack_expect_raw_float(mpack_reader_t* reader);
+#endif
+
+#if !MPACK_DOUBLE
+/**
+ * Reads a double as a raw uint64_t. The underlying value must be a double, not a
+ * float or an integer.
+ *
+ * @throws mpack_error_type if the underlying value is not a double.
+ */
+uint64_t mpack_expect_raw_double(mpack_reader_t* reader);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Ranged Number Functions
+ * @{
+ */
+
+/**
+ * Reads an 8-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value);
+
+/**
+ * Reads a 16-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value);
+
+/**
+ * Reads a 32-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value);
+
+/**
+ * Reads a 64-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value);
+
+/**
+ * Reads an unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+MPACK_INLINE unsigned int mpack_expect_uint_range(mpack_reader_t* reader, unsigned int min_value, unsigned int max_value) {
+    // This should be true at compile-time, so this just wraps the 32-bit
+    // function. We fallback to 64-bit if for some reason sizeof(int) isn't 4.
+    if (sizeof(unsigned int) == 4)
+        return (unsigned int)mpack_expect_u32_range(reader, (uint32_t)min_value, (uint32_t)max_value);
+    return (unsigned int)mpack_expect_u64_range(reader, min_value, max_value);
+}
+
+/**
+ * Reads an 8-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint8_t mpack_expect_u8_max(mpack_reader_t* reader, uint8_t max_value) {
+    return mpack_expect_u8_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 16-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint16_t mpack_expect_u16_max(mpack_reader_t* reader, uint16_t max_value) {
+    return mpack_expect_u16_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 32-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint32_t mpack_expect_u32_max(mpack_reader_t* reader, uint32_t max_value) {
+    return mpack_expect_u32_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 64-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint64_t mpack_expect_u64_max(mpack_reader_t* reader, uint64_t max_value) {
+    return mpack_expect_u64_range(reader, 0, max_value);
+}
+
+/**
+ * Reads an unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE unsigned int mpack_expect_uint_max(mpack_reader_t* reader, unsigned int max_value) {
+    return mpack_expect_uint_range(reader, 0, max_value);
+}
+
+/**
+ * Reads an 8-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value);
+
+/**
+ * Reads a 16-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int16_t mpack_expect_i16_range(mpack_reader_t* reader, int16_t min_value, int16_t max_value);
+
+/**
+ * Reads a 32-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int32_t mpack_expect_i32_range(mpack_reader_t* reader, int32_t min_value, int32_t max_value);
+
+/**
+ * Reads a 64-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int64_t mpack_expect_i64_range(mpack_reader_t* reader, int64_t min_value, int64_t max_value);
+
+/**
+ * Reads a signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+MPACK_INLINE int mpack_expect_int_range(mpack_reader_t* reader, int min_value, int max_value) {
+    // This should be true at compile-time, so this just wraps the 32-bit
+    // function. We fallback to 64-bit if for some reason sizeof(int) isn't 4.
+    if (sizeof(int) == 4)
+        return (int)mpack_expect_i32_range(reader, (int32_t)min_value, (int32_t)max_value);
+    return (int)mpack_expect_i64_range(reader, min_value, max_value);
+}
+
+/**
+ * Reads an 8-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int8_t mpack_expect_i8_max(mpack_reader_t* reader, int8_t max_value) {
+    return mpack_expect_i8_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 16-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int16_t mpack_expect_i16_max(mpack_reader_t* reader, int16_t max_value) {
+    return mpack_expect_i16_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 32-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int32_t mpack_expect_i32_max(mpack_reader_t* reader, int32_t max_value) {
+    return mpack_expect_i32_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 64-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int64_t mpack_expect_i64_max(mpack_reader_t* reader, int64_t max_value) {
+    return mpack_expect_i64_range(reader, 0, max_value);
+}
+
+/**
+ * Reads an int, ensuring that it is at least zero and at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int mpack_expect_int_max(mpack_reader_t* reader, int max_value) {
+    return mpack_expect_int_range(reader, 0, max_value);
+}
+
+#if MPACK_FLOAT
+/**
+ * Reads a number, ensuring that it falls within the given range and returning
+ * the value as a float. The underlying value can be an integer, float or
+ * double; the value is converted to a float.
+ *
+ * @note Reading a double or a large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value);
+#endif
+
+#if MPACK_DOUBLE
+/**
+ * Reads a number, ensuring that it falls within the given range and returning
+ * the value as a double. The underlying value can be an integer, float or
+ * double; the value is converted to a double.
+ *
+ * @note Reading a very large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value);
+#endif
+
+/**
+ * @}
+ */
+
+
+
+// These are additional Basic Number functions that wrap inline range functions.
+
+/**
+ * @name Basic Number Functions
+ * @{
+ */
+
+/**
+ * Reads an unsigned int.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+MPACK_INLINE unsigned int mpack_expect_uint(mpack_reader_t* reader) {
+
+    // This should be true at compile-time, so this just wraps the 32-bit function.
+    if (sizeof(unsigned int) == 4)
+        return (unsigned int)mpack_expect_u32(reader);
+
+    // Otherwise we wrap the max function to ensure it fits.
+    return (unsigned int)mpack_expect_u64_max(reader, MPACK_UINT_MAX);
+
+}
+
+/**
+ * Reads a signed int.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+MPACK_INLINE int mpack_expect_int(mpack_reader_t* reader) {
+
+    // This should be true at compile-time, so this just wraps the 32-bit function.
+    if (sizeof(int) == 4)
+        return (int)mpack_expect_i32(reader);
+
+    // Otherwise we wrap the range function to ensure it fits.
+    return (int)mpack_expect_i64_range(reader, MPACK_INT_MIN, MPACK_INT_MAX);
+
+}
+
+/**
+ * @}
+ */
+
+
+
+/**
+ * @name Matching Number Functions
+ * @{
+ */
+
+/**
+ * Reads an unsigned integer, ensuring that it exactly matches the given value.
+ *
+ * mpack_error_type is raised if the value is not representable as an unsigned
+ * integer or if it does not exactly match the given value.
+ */
+void mpack_expect_uint_match(mpack_reader_t* reader, uint64_t value);
+
+/**
+ * Reads a signed integer, ensuring that it exactly matches the given value.
+ *
+ * mpack_error_type is raised if the value is not representable as a signed
+ * integer or if it does not exactly match the given value.
+ */
+void mpack_expect_int_match(mpack_reader_t* reader, int64_t value);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Other Basic Types
+ * @{
+ */
+
+/**
+ * Reads a nil, raising @ref mpack_error_type if the value is not nil.
+ */
+void mpack_expect_nil(mpack_reader_t* reader);
+
+/**
+ * Reads a boolean.
+ *
+ * @note Integers will raise mpack_error_type; the value must be strictly a boolean.
+ */
+bool mpack_expect_bool(mpack_reader_t* reader);
+
+/**
+ * Reads a boolean, raising @ref mpack_error_type if its value is not @c true.
+ */
+void mpack_expect_true(mpack_reader_t* reader);
+
+/**
+ * Reads a boolean, raising @ref mpack_error_type if its value is not @c false.
+ */
+void mpack_expect_false(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Extension Functions
+ * @{
+ */
+
+#if MPACK_EXTENSIONS
+/**
+ * Reads a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader);
+
+/**
+ * Reads a timestamp in seconds, truncating the nanoseconds (if any).
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Compound Types
+ * @{
+ */
+
+/**
+ * Reads the start of a map, returning its element count.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the map's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring a map
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_map_max()
+ * with a safe maximum size instead.
+ *
+ * @throws mpack_error_type if the value is not a map.
+ */
+uint32_t mpack_expect_map(mpack_reader_t* reader);
+
+/**
+ * Reads the start of a map with a number of elements in the given range, returning
+ * its element count.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * min_count is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not a map or if its size does
+ * not fall within the given range.
+ */
+uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_count, uint32_t max_count);
+
+/**
+ * Reads the start of a map with a number of elements at most @a max_count,
+ * returning its element count.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * Zero is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not a map or if its size is
+ * greater than max_count.
+ */
+MPACK_INLINE uint32_t mpack_expect_map_max(mpack_reader_t* reader, uint32_t max_count) {
+    return mpack_expect_map_range(reader, 0, max_count);
+}
+
+/**
+ * Reads the start of a map of the exact size given.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @throws mpack_error_type if the value is not a map or if its size
+ * does not match the given count.
+ */
+void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count);
+
+/**
+ * Reads a nil node or the start of a map, returning whether a map was
+ * read and placing its number of key/value pairs in count.
+ *
+ * If a map was read, a number of values follow equal to twice the element count
+ * of the map, alternating between keys and values. @ref mpack_done_map() should
+ * also be called once all elements have been read (only if a map was read.)
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the map's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring a map
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_map_max_or_nil()
+ * with a safe maximum size instead.
+ *
+ * @returns @c true if a map was read successfully; @c false if nil was read
+ *     or an error occurred.
+ * @throws mpack_error_type if the value is not a nil or map.
+ */
+bool mpack_expect_map_or_nil(mpack_reader_t* reader, uint32_t* count);
+
+/**
+ * Reads a nil node or the start of a map with a number of elements at most
+ * max_count, returning whether a map was read and placing its number of
+ * key/value pairs in count.
+ *
+ * If a map was read, a number of values follow equal to twice the element count
+ * of the map, alternating between keys and values. @ref mpack_done_map() should
+ * anlso be called once all elements have been read (only if a map was read.)
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON. Consider using mpack_expect_key_cstr() or mpack_expect_key_uint()
+ * to switch on the key; see @ref docs/expect.md for examples.
+ *
+ * @returns @c true if a map was read successfully; @c false if nil was read
+ *     or an error occurred.
+ * @throws mpack_error_type if the value is not a nil or map.
+ */
+bool mpack_expect_map_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count);
+
+/**
+ * Reads the start of an array, returning its element count.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the array's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring an array
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_array_max()
+ * with a safe maximum size instead.
+ */
+uint32_t mpack_expect_array(mpack_reader_t* reader);
+
+/**
+ * Reads the start of an array with a number of elements in the given range,
+ * returning its element count.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * min_count is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size does
+ * not fall within the given range.
+ */
+uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_count, uint32_t max_count);
+
+/**
+ * Reads the start of an array with a number of elements at most @a max_count,
+ * returning its element count.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * Zero is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size is
+ * greater than max_count.
+ */
+MPACK_INLINE uint32_t mpack_expect_array_max(mpack_reader_t* reader, uint32_t max_count) {
+    return mpack_expect_array_range(reader, 0, max_count);
+}
+
+/**
+ * Reads the start of an array of the exact size given.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size does
+ * not match the given count.
+ */
+void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count);
+
+/**
+ * Reads a nil node or the start of an array, returning whether an array was
+ * read and placing its number of elements in count.
+ *
+ * If an array was read, a number of values follow equal to the element count
+ * of the array. @ref mpack_done_array() should also be called once all elements
+ * have been read (only if an array was read.)
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the array's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring an array
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_array_max_or_nil()
+ * with a safe maximum size instead.
+ *
+ * @returns @c true if an array was read successfully; @c false if nil was read
+ *     or an error occurred.
+ * @throws mpack_error_type if the value is not a nil or array.
+ */
+bool mpack_expect_array_or_nil(mpack_reader_t* reader, uint32_t* count);
+
+/**
+ * Reads a nil node or the start of an array with a number of elements at most
+ * max_count, returning whether an array was read and placing its number of
+ * key/value pairs in count.
+ *
+ * If an array was read, a number of values follow equal to the element count
+ * of the array. @ref mpack_done_array() should also be called once all elements
+ * have been read (only if an array was read.)
+ *
+ * @returns @c true if an array was read successfully; @c false if nil was read
+ *     or an error occurred.
+ * @throws mpack_error_type if the value is not a nil or array.
+ */
+bool mpack_expect_array_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count);
+
+#ifdef MPACK_MALLOC
+/**
+ * @hideinitializer
+ *
+ * Reads the start of an array and allocates storage for it, placing its
+ * size in out_count. A number of objects follow equal to the element count
+ * of the array. You must call @ref mpack_done_array() when done (even
+ * if the element count is zero.)
+ *
+ * If an error occurs, NULL is returned and the reader is placed in an
+ * error state.
+ *
+ * If the count is zero, NULL is returned. This does not indicate error.
+ * You should not check the return value for NULL to check for errors; only
+ * check the reader's error state.
+ *
+ * The allocated array must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type if the value is not an array or if its size is
+ * greater than max_count.
+ */
+#define mpack_expect_array_alloc(reader, Type, max_count, out_count) \
+    ((Type*)mpack_expect_array_alloc_impl(reader, sizeof(Type), max_count, out_count, false))
+
+/**
+ * @hideinitializer
+ *
+ * Reads a nil node or the start of an array and allocates storage for it,
+ * placing its size in out_count. A number of objects follow equal to the element
+ * count of the array if a non-empty array was read.
+ *
+ * If an error occurs, NULL is returned and the reader is placed in an
+ * error state.
+ *
+ * If a nil node was read, NULL is returned. If an empty array was read,
+ * mpack_done_array() is called automatically and NULL is returned. These
+ * do not indicate error. You should not check the return value for NULL
+ * to check for errors; only check the reader's error state.
+ *
+ * The allocated array must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @warning You must call @ref mpack_done_array() if and only if a non-zero
+ * element count is read. This function does not differentiate between nil
+ * and an empty array.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size is
+ * greater than max_count.
+ */
+#define mpack_expect_array_or_nil_alloc(reader, Type, max_count, out_count) \
+    ((Type*)mpack_expect_array_alloc_impl(reader, sizeof(Type), max_count, out_count, true))
+#endif
+
+/**
+ * @}
+ */
+
+/** @cond */
+#ifdef MPACK_MALLOC
+void* mpack_expect_array_alloc_impl(mpack_reader_t* reader,
+        size_t element_size, uint32_t max_count, uint32_t* out_count, bool allow_nil);
+#endif
+/** @endcond */
+
+
+/**
+ * @name String Functions
+ * @{
+ */
+
+/**
+ * Reads the start of a string, returning its size in bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). mpack_done_str() must be called
+ * once all bytes have been read.
+ *
+ * NUL bytes are allowed in the string, and no encoding checks are done.
+ *
+ * mpack_error_type is raised if the value is not a string.
+ */
+uint32_t mpack_expect_str(mpack_reader_t* reader);
+
+/**
+ * Reads a string of at most the given size, writing it into the
+ * given buffer and returning its size in bytes.
+ *
+ * This does not add a null-terminator! Use mpack_expect_cstr() to
+ * add a null-terminator.
+ *
+ * NUL bytes are allowed in the string, and no encoding checks are done.
+ */
+size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize);
+
+/**
+ * Reads a string into the given buffer, ensuring it is a valid UTF-8 string
+ * and returning its size in bytes.
+ *
+ * This does not add a null-terminator! Use mpack_expect_utf8_cstr() to
+ * add a null-terminator.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * NUL bytes are allowed in the string (as they are in UTF-8.)
+ *
+ * Raises mpack_error_too_big if there is not enough room for the string.
+ * Raises mpack_error_type if the value is not a string or is not a valid UTF-8 string.
+ */
+size_t mpack_expect_utf8(mpack_reader_t* reader, char* buf, size_t bufsize);
+
+/**
+ * Reads the start of a string, raising an error if its length is not
+ * at most the given number of bytes (not including any null-terminator.)
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_str() must be called
+ * once all bytes have been read.
+ *
+ * @throws mpack_error_type If the value is not a string.
+ * @throws mpack_error_too_big If the string's length in bytes is larger than the given maximum size.
+ */
+MPACK_INLINE uint32_t mpack_expect_str_max(mpack_reader_t* reader, uint32_t maxsize) {
+    uint32_t length = mpack_expect_str(reader);
+    if (length > maxsize) {
+        mpack_reader_flag_error(reader, mpack_error_too_big);
+        return 0;
+    }
+    return length;
+}
+
+/**
+ * Reads the start of a string, raising an error if its length is not
+ * exactly the given number of bytes (not including any null-terminator.)
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_str() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not a string or if its
+ * length does not match.
+ */
+MPACK_INLINE void mpack_expect_str_length(mpack_reader_t* reader, uint32_t count) {
+    if (mpack_expect_str(reader) != count)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+/**
+ * Reads a string, ensuring it exactly matches the given string.
+ *
+ * Remember that maps are unordered in JSON. Don't use this for map keys
+ * unless the map has only a single key!
+ */
+void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t length);
+
+/**
+ * Reads a string into the given buffer, ensures it has no null bytes,
+ * and adds a null-terminator at the end.
+ *
+ * Raises mpack_error_too_big if there is not enough room for the string and null-terminator.
+ * Raises mpack_error_type if the value is not a string or contains a null byte.
+ */
+void mpack_expect_cstr(mpack_reader_t* reader, char* buf, size_t size);
+
+/**
+ * Reads a string into the given buffer, ensures it is a valid UTF-8 string
+ * without NUL characters, and adds a null-terminator at the end.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since
+ * it cannot be represented in a null-terminated string.
+ *
+ * Raises mpack_error_too_big if there is not enough room for the string and null-terminator.
+ * Raises mpack_error_type if the value is not a string or is not a valid UTF-8 string.
+ */
+void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t size);
+
+#ifdef MPACK_MALLOC
+/**
+ * Reads a string with the given total maximum size (including space for a
+ * null-terminator), allocates storage for it, ensures it has no null-bytes,
+ * and adds a null-terminator at the end. You assume ownership of the
+ * returned pointer if reading succeeds.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_too_big If the string plus null-terminator is larger than the given maxsize.
+ * @throws mpack_error_type If the value is not a string or contains a null byte.
+ */
+char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize);
+
+/**
+ * Reads a string with the given total maximum size (including space for a
+ * null-terminator), allocates storage for it, ensures it is valid UTF-8
+ * with no null-bytes, and adds a null-terminator at the end. You assume
+ * ownership of the returned pointer if reading succeeds.
+ *
+ * The length in bytes of the string, not including the null-terminator,
+ * will be written to size.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since
+ * it cannot be represented in a null-terminated string.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ * if you want a null-terminator.
+ *
+ * @throws mpack_error_too_big If the string plus null-terminator is larger
+ *     than the given maxsize.
+ * @throws mpack_error_type If the value is not a string or contains
+ *     invalid UTF-8 or a null byte.
+ */
+char* mpack_expect_utf8_cstr_alloc(mpack_reader_t* reader, size_t maxsize);
+#endif
+
+/**
+ * Reads a string, ensuring it exactly matches the given null-terminated
+ * string.
+ *
+ * Remember that maps are unordered in JSON. Don't use this for map keys
+ * unless the map has only a single key!
+ */
+MPACK_INLINE void mpack_expect_cstr_match(mpack_reader_t* reader, const char* cstr) {
+    mpack_assert(cstr != NULL, "cstr pointer is NULL");
+    mpack_expect_str_match(reader, cstr, mpack_strlen(cstr));
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Binary Data
+ * @{
+ */
+
+/**
+ * Reads the start of a binary blob, returning its size in bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not a binary blob.
+ */
+uint32_t mpack_expect_bin(mpack_reader_t* reader);
+
+/**
+ * Reads the start of a binary blob, raising an error if its length is not
+ * at most the given number of bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not a binary blob or if its
+ * length does not match.
+ */
+MPACK_INLINE uint32_t mpack_expect_bin_max(mpack_reader_t* reader, uint32_t maxsize) {
+    uint32_t length = mpack_expect_bin(reader);
+    if (length > maxsize) {
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return 0;
+    }
+    return length;
+}
+
+/**
+ * Reads the start of a binary blob, raising an error if its length is not
+ * exactly the given number of bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called
+ * once all bytes have been read.
+ *
+ * @throws mpack_error_type if the value is not a binary blob or if its size
+ * does not match.
+ */
+MPACK_INLINE void mpack_expect_bin_size(mpack_reader_t* reader, uint32_t count) {
+    if (mpack_expect_bin(reader) != count)
+        mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+/**
+ * Reads a binary blob into the given buffer, returning its size in bytes.
+ *
+ * For compatibility, this will accept if the underlying type is string or
+ * binary (since in MessagePack 1.0, strings and binary data were combined
+ * under the "raw" type which became string in 1.1.)
+ */
+size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t size);
+
+/**
+ * Reads a binary blob with the exact given size into the given buffer.
+ *
+ * For compatibility, this will accept if the underlying type is string or
+ * binary (since in MessagePack 1.0, strings and binary data were combined
+ * under the "raw" type which became string in 1.1.)
+ *
+ * @throws mpack_error_type if the value is not a binary blob or if its size
+ * does not match.
+ */
+void mpack_expect_bin_size_buf(mpack_reader_t* reader, char* buf, uint32_t size);
+
+/**
+ * Reads a binary blob with the given total maximum size, allocating storage for it.
+ */
+char* mpack_expect_bin_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Extension Functions
+ * @{
+ */
+
+#if MPACK_EXTENSIONS
+/**
+ * Reads the start of an extension blob, returning its size in bytes and
+ * placing the type into @p type.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called
+ * once all bytes have been read.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * mpack_error_type is raised if the value is not an extension blob. The @p
+ * type value is zero if an error occurs.
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ */
+uint32_t mpack_expect_ext(mpack_reader_t* reader, int8_t* type);
+
+/**
+ * Reads the start of an extension blob, raising an error if its length is not
+ * at most the given number of bytes and placing the type into @p type.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @see mpack_expect_ext()
+ */
+MPACK_INLINE uint32_t mpack_expect_ext_max(mpack_reader_t* reader, int8_t* type, uint32_t maxsize) {
+    uint32_t length = mpack_expect_ext(reader, type);
+    if (length > maxsize) {
+        mpack_reader_flag_error(reader, mpack_error_type);
+        return 0;
+    }
+    return length;
+}
+
+/**
+ * Reads the start of an extension blob, raising an error if its length is not
+ * exactly the given number of bytes and placing the type into @p type.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @see mpack_expect_ext()
+ */
+MPACK_INLINE void mpack_expect_ext_size(mpack_reader_t* reader, int8_t* type, uint32_t count) {
+    if (mpack_expect_ext(reader, type) != count) {
+        *type = 0;
+        mpack_reader_flag_error(reader, mpack_error_type);
+    }
+}
+
+/**
+ * Reads an extension blob into the given buffer, returning its size in bytes
+ * and placing the type into @p type.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_expect_ext()
+ */
+size_t mpack_expect_ext_buf(mpack_reader_t* reader, int8_t* type, char* buf, size_t size);
+#endif
+
+#if MPACK_EXTENSIONS && defined(MPACK_MALLOC)
+/**
+ * Reads an extension blob with the given total maximum size, allocating
+ * storage for it, and placing the type into @p type.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS and @ref MPACK_MALLOC.
+ *
+ * @see mpack_expect_ext()
+ */
+char* mpack_expect_ext_alloc(mpack_reader_t* reader, int8_t* type, size_t maxsize, size_t* size);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Special Functions
+ * @{
+ */
+
+/**
+ * Reads a MessagePack object header (an MPack tag), expecting it to exactly
+ * match the given tag.
+ *
+ * If the type is compound (i.e. is a map, array, string, binary or
+ * extension type), additional reads are required to get the contained
+ * data, and the corresponding done function must be called when done.
+ *
+ * @throws mpack_error_type if the tag does not match
+ *
+ * @see mpack_read_bytes()
+ * @see mpack_done_array()
+ * @see mpack_done_map()
+ * @see mpack_done_str()
+ * @see mpack_done_bin()
+ * @see mpack_done_ext()
+ */
+void mpack_expect_tag(mpack_reader_t* reader, mpack_tag_t tag);
+
+/**
+ * Expects a string matching one of the strings in the given array,
+ * returning its array index.
+ *
+ * If the value does not match any of the given strings,
+ * @ref mpack_error_type is flagged. Use mpack_expect_enum_optional()
+ * if you want to allow other values than the given strings.
+ *
+ * If any error occurs or the reader is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum           { APPLE ,  BANANA ,  ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_expect_enum(reader, fruits, COUNT);
+ * @endcode
+ *
+ * See @ref docs/expect.md for more examples.
+ *
+ * The maximum string length is the size of the buffer (strings are read in-place.)
+ *
+ * @param reader The reader
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ * @return The index of the matched string, or @a count in case of error
+ */
+size_t mpack_expect_enum(mpack_reader_t* reader, const char* strings[], size_t count);
+
+/**
+ * Expects a string matching one of the strings in the given array
+ * returning its array index, or @a count if no strings match.
+ *
+ * If the value is not a string, or it does not match any of the
+ * given strings, @a count is returned and no error is flagged.
+ *
+ * If any error occurs or the reader is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum           { APPLE ,  BANANA ,  ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_expect_enum_optional(reader, fruits, COUNT);
+ * @endcode
+ *
+ * See @ref docs/expect.md for more examples.
+ *
+ * The maximum string length is the size of the buffer (strings are read in-place.)
+ *
+ * @param reader The reader
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ *
+ * @return The index of the matched string, or @a count if it does not
+ * match or an error occurs
+ */
+size_t mpack_expect_enum_optional(mpack_reader_t* reader, const char* strings[], size_t count);
+
+/**
+ * Expects an unsigned integer map key between 0 and count-1, marking it
+ * as found in the given bool array and returning it.
+ *
+ * This is a helper for switching among int keys in a map. It is
+ * typically used with an enum to define the key values. It should
+ * be called in the expression of a switch() statement. See @ref
+ * docs/expect.md for an example.
+ *
+ * The found array must be cleared before expecting the first key. If the
+ * flag for a given key is already set when found (i.e. the map contains a
+ * duplicate key), mpack_error_invalid is flagged.
+ *
+ * If the key is not a non-negative integer, or if the key is @a count or
+ * larger, @a count is returned and no error is flagged. If you want an error
+ * on unrecognized keys, flag an error in the default case in your switch;
+ * otherwise you must call mpack_discard() to discard its content.
+ *
+ * @param reader The reader
+ * @param found An array of bool flags of length count
+ * @param count The number of values in the found array, and one more than the
+ *              maximum allowed key
+ *
+ * @see @ref docs/expect.md
+ */
+size_t mpack_expect_key_uint(mpack_reader_t* reader, bool found[], size_t count);
+
+/**
+ * Expects a string map key matching one of the strings in the given key list,
+ * marking it as found in the given bool array and returning its index.
+ *
+ * This is a helper for switching among string keys in a map. It is
+ * typically used with an enum with names matching the strings in the
+ * array to define the key indices. It should be called in the expression
+ * of a switch() statement. See @ref docs/expect.md for an example.
+ *
+ * The found array must be cleared before expecting the first key. If the
+ * flag for a given key is already set when found (i.e. the map contains a
+ * duplicate key), mpack_error_invalid is flagged.
+ *
+ * If the key is unrecognized, count is returned and no error is flagged. If
+ * you want an error on unrecognized keys, flag an error in the default case
+ * in your switch; otherwise you must call mpack_discard() to discard its content.
+ *
+ * The maximum key length is the size of the buffer (keys are read in-place.)
+ *
+ * @param reader The reader
+ * @param keys An array of expected string keys of length count
+ * @param found An array of bool flags of length count
+ * @param count The number of values in the keys and found arrays
+ *
+ * @see @ref docs/expect.md
+ */
+size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[],
+        bool found[], size_t count);
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+#endif
+
+MPACK_EXTERN_C_END
+MPACK_SILENCE_WARNINGS_END
+
+#endif
+
+
+
+/* mpack/mpack-node.h.h */
+
+/**
+ * @file
+ *
+ * Declares the MPack dynamic Node API.
+ */
+
+#ifndef MPACK_NODE_H
+#define MPACK_NODE_H 1
+
+/* #include "mpack-reader.h" */
+
+MPACK_SILENCE_WARNINGS_BEGIN
+MPACK_EXTERN_C_BEGIN
+
+#if MPACK_NODE
+
+/**
+ * @defgroup node Node API
+ *
+ * The MPack Node API allows you to parse a chunk of MessagePack into a
+ * dynamically typed data structure, providing random access to the parsed
+ * data.
+ *
+ * See @ref docs/node.md for examples.
+ *
+ * @{
+ */
+
+/**
+ * A handle to node data in a parsed MPack tree.
+ *
+ * Nodes represent either primitive values or compound types. If a
+ * node is a compound type, it contains a pointer to its child nodes,
+ * or a pointer to its underlying data.
+ *
+ * Nodes are immutable.
+ *
+ * @note @ref mpack_node_t is an opaque reference to the node data, not the
+ * node data itself. (It contains pointers to both the node data and the tree.)
+ * It is passed by value in the Node API.
+ */
+typedef struct mpack_node_t mpack_node_t;
+
+/**
+ * The storage for nodes in an MPack tree.
+ *
+ * You only need to use this if you intend to provide your own storage
+ * for nodes instead of letting the tree allocate it.
+ *
+ * @ref mpack_node_data_t is 16 bytes on most common architectures (32-bit
+ * and 64-bit.)
+ */
+typedef struct mpack_node_data_t mpack_node_data_t;
+
+/**
+ * An MPack tree parser to parse a blob or stream of MessagePack.
+ *
+ * When a message is parsed, the tree contains a single root node which
+ * contains all parsed data. The tree and its nodes are immutable.
+ */
+typedef struct mpack_tree_t mpack_tree_t;
+
+/**
+ * An error handler function to be called when an error is flagged on
+ * the tree.
+ *
+ * The error handler will only be called once on the first error flagged;
+ * any subsequent node reads and errors are ignored, and the tree is
+ * permanently in that error state.
+ *
+ * MPack is safe against non-local jumps out of error handler callbacks.
+ * This means you are allowed to longjmp or throw an exception (in C++,
+ * Objective-C, or with SEH) out of this callback.
+ *
+ * Bear in mind when using longjmp that local non-volatile variables that
+ * have changed are undefined when setjmp() returns, so you can't put the
+ * tree on the stack in the same activation frame as the setjmp without
+ * declaring it volatile.
+ *
+ * You must still eventually destroy the tree. It is not destroyed
+ * automatically when an error is flagged. It is safe to destroy the
+ * tree within this error callback, but you will either need to perform
+ * a non-local jump, or store something in your context to identify
+ * that the tree is destroyed since any future accesses to it cause
+ * undefined behavior.
+ */
+typedef void (*mpack_tree_error_t)(mpack_tree_t* tree, mpack_error_t error);
+
+/**
+ * The MPack tree's read function. It should fill the buffer with as many bytes
+ * as are immediately available up to the given @c count, returning the number
+ * of bytes written to the buffer.
+ *
+ * In case of error, it should flag an appropriate error on the reader
+ * (usually @ref mpack_error_io.)
+ *
+ * The blocking or non-blocking behaviour of the read should match whether you
+ * are using mpack_tree_parse() or mpack_tree_try_parse().
+ *
+ * If you are using mpack_tree_parse(), the read should block until at least
+ * one byte is read. If you return 0, mpack_tree_parse() will raise @ref
+ * mpack_error_io.
+ *
+ * If you are using mpack_tree_try_parse(), the read function can always
+ * return 0, and must never block waiting for data (otherwise
+ * mpack_tree_try_parse() would be equivalent to mpack_tree_parse().)
+ * When you return 0, mpack_tree_try_parse() will return false without flagging
+ * an error.
+ */
+typedef size_t (*mpack_tree_read_t)(mpack_tree_t* tree, char* buffer, size_t count);
+
+/**
+ * A teardown function to be called when the tree is destroyed.
+ */
+typedef void (*mpack_tree_teardown_t)(mpack_tree_t* tree);
+
+
+
+/* Hide internals from documentation */
+/** @cond */
+
+struct mpack_node_t {
+    mpack_node_data_t* data;
+    mpack_tree_t* tree;
+};
+
+struct mpack_node_data_t {
+    mpack_type_t type;
+
+    /*
+     * The element count if the type is an array;
+     * the number of key/value pairs if the type is map;
+     * or the number of bytes if the type is str, bin or ext.
+     */
+    uint32_t len;
+
+    union {
+        bool     b; /* The value if the type is bool. */
+
+        #if MPACK_FLOAT
+        float    f; /* The value if the type is float. */
+        #else
+        uint32_t f; /*< The raw value if the type is float. */
+        #endif
+
+        #if MPACK_DOUBLE
+        double   d; /* The value if the type is double. */
+        #else
+        uint64_t d; /*< The raw value if the type is double. */
+        #endif
+
+        int64_t  i; /* The value if the type is signed int. */
+        uint64_t u; /* The value if the type is unsigned int. */
+        size_t offset; /* The byte offset for str, bin and ext */
+
+        mpack_node_data_t* children; /* The children for map or array */
+    } value;
+};
+
+typedef struct mpack_tree_page_t {
+    struct mpack_tree_page_t* next;
+    mpack_node_data_t nodes[1]; // variable size
+} mpack_tree_page_t;
+
+typedef enum mpack_tree_parse_state_t {
+    mpack_tree_parse_state_not_started,
+    mpack_tree_parse_state_in_progress,
+    mpack_tree_parse_state_parsed,
+} mpack_tree_parse_state_t;
+
+typedef struct mpack_level_t {
+    mpack_node_data_t* child;
+    size_t left; // children left in level
+} mpack_level_t;
+
+typedef struct mpack_tree_parser_t {
+    mpack_tree_parse_state_t state;
+
+    // We keep track of the number of "possible nodes" left in the data rather
+    // than the number of bytes.
+    //
+    // When a map or array is parsed, we ensure at least one byte for each child
+    // exists and subtract them right away. This ensures that if ever a map or
+    // array declares more elements than could possibly be contained in the data,
+    // we will error out immediately rather than allocating storage for them.
+    //
+    // For example malicious data that repeats 0xDE 0xFF 0xFF (start of a map
+    // with 65536 key-value pairs) would otherwise cause us to run out of
+    // memory. With this, the parser can allocate at most as many nodes as
+    // there are bytes in the data (plus the paging overhead, 12%.) An error
+    // will be flagged immediately if and when there isn't enough data left to
+    // fully read all children of all open compound types on the parsing stack.
+    //
+    // Once an entire message has been parsed (and there are no nodes left to
+    // parse whose bytes have been subtracted), this matches the number of left
+    // over bytes in the data.
+    size_t possible_nodes_left;
+
+    mpack_node_data_t* nodes; // next node in current page/pool
+    size_t nodes_left; // nodes left in current page/pool
+
+    size_t current_node_reserved;
+    size_t level;
+
+    #ifdef MPACK_MALLOC
+    // It's much faster to allocate the initial parsing stack inline within the
+    // parser. We replace it with a heap allocation if we need to grow it.
+    mpack_level_t* stack;
+    size_t stack_capacity;
+    bool stack_owned;
+    mpack_level_t stack_local[MPACK_NODE_INITIAL_DEPTH];
+    #else
+    // Without malloc(), we have to reserve a parsing stack the maximum allowed
+    // parsing depth.
+    mpack_level_t stack[MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC];
+    #endif
+} mpack_tree_parser_t;
+
+struct mpack_tree_t {
+    mpack_tree_error_t error_fn;    /* Function to call on error */
+    mpack_tree_read_t read_fn;      /* Function to call to read more data */
+    mpack_tree_teardown_t teardown; /* Function to teardown the context on destroy */
+    void* context;                  /* Context for tree callbacks */
+
+    mpack_node_data_t nil_node;     /* a nil node to be returned in case of error */
+    mpack_node_data_t missing_node; /* a missing node to be returned in optional lookups */
+    mpack_error_t error;
+
+    #ifdef MPACK_MALLOC
+    char* buffer;
+    size_t buffer_capacity;
+    #endif
+
+    const char* data;
+    size_t data_length; // length of data (and content of buffer, if used)
+
+    size_t size; // size in bytes of tree (usually matches data_length, but not if tree has trailing data)
+    size_t node_count; // total number of nodes in tree (across all pages)
+
+    size_t max_size;  // maximum message size
+    size_t max_nodes; // maximum nodes in a message
+
+    mpack_tree_parser_t parser;
+    mpack_node_data_t* root;
+
+    mpack_node_data_t* pool; // pool, or NULL if no pool provided
+    size_t pool_count;
+
+    #ifdef MPACK_MALLOC
+    mpack_tree_page_t* next;
+    #endif
+};
+
+// internal functions
+
+MPACK_INLINE mpack_node_t mpack_node(mpack_tree_t* tree, mpack_node_data_t* data) {
+    mpack_node_t node;
+    node.data = data;
+    node.tree = tree;
+    return node;
+}
+
+MPACK_INLINE mpack_node_data_t* mpack_node_child(mpack_node_t node, size_t child) {
+    return node.data->value.children + child;
+}
+
+MPACK_INLINE mpack_node_t mpack_tree_nil_node(mpack_tree_t* tree) {
+    return mpack_node(tree, &tree->nil_node);
+}
+
+MPACK_INLINE mpack_node_t mpack_tree_missing_node(mpack_tree_t* tree) {
+    return mpack_node(tree, &tree->missing_node);
+}
+
+/** @endcond */
+
+
+
+/**
+ * @name Tree Initialization
+ * @{
+ */
+
+#ifdef MPACK_MALLOC
+/**
+ * Initializes a tree parser with the given data.
+ *
+ * Configure the tree if desired, then call mpack_tree_parse() to parse it. The
+ * tree will allocate pages of nodes as needed and will free them when
+ * destroyed.
+ *
+ * The tree must be destroyed with mpack_tree_destroy().
+ *
+ * Any string or blob data types reference the original data, so the given data
+ * pointer must remain valid until after the tree is destroyed.
+ */
+void mpack_tree_init_data(mpack_tree_t* tree, const char* data, size_t length);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_tree_init_data().
+ */
+MPACK_INLINE void mpack_tree_init(mpack_tree_t* tree, const char* data, size_t length) {
+    mpack_tree_init_data(tree, data, length);
+}
+
+/**
+ * Initializes a tree parser from an unbounded stream, or a stream of
+ * unknown length.
+ *
+ * The parser can be used to read a single message from a stream of unknown
+ * length, or multiple messages from an unbounded stream, allowing it to
+ * be used for RPC communication. Call @ref mpack_tree_parse() to parse
+ * a message from a blocking stream, or @ref mpack_tree_try_parse() for a
+ * non-blocking stream.
+ *
+ * The stream will use a growable internal buffer to store the most recent
+ * message, as well as allocated pages of nodes for the parse tree.
+ *
+ * Maximum allowances for message size and node count must be specified in this
+ * function (since the stream is unbounded.) They can be changed later with
+ * @ref mpack_tree_set_limits().
+ *
+ * @param tree The tree parser
+ * @param read_fn The read function
+ * @param context The context for the read function
+ * @param max_message_size The maximum size of a message in bytes
+ * @param max_message_nodes The maximum number of nodes per message. See
+ *        @ref mpack_node_data_t for the size of nodes.
+ *
+ * @see mpack_tree_read_t
+ * @see mpack_reader_context()
+ */
+void mpack_tree_init_stream(mpack_tree_t* tree, mpack_tree_read_t read_fn, void* context,
+        size_t max_message_size, size_t max_message_nodes);
+#endif
+
+/**
+ * Initializes a tree parser with the given data, using the given node data
+ * pool to store the results.
+ *
+ * Configure the tree if desired, then call mpack_tree_parse() to parse it.
+ *
+ * If the data does not fit in the pool, @ref mpack_error_too_big will be flagged
+ * on the tree.
+ *
+ * The tree must be destroyed with mpack_tree_destroy(), even if parsing fails.
+ */
+void mpack_tree_init_pool(mpack_tree_t* tree, const char* data, size_t length,
+        mpack_node_data_t* node_pool, size_t node_pool_count);
+
+/**
+ * Initializes an MPack tree directly into an error state. Use this if you
+ * are writing a wrapper to another <tt>mpack_tree_init*()</tt> function which
+ * can fail its setup.
+ */
+void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error);
+
+#if MPACK_STDIO
+/**
+ * Initializes a tree to parse the given file. The tree must be destroyed with
+ * mpack_tree_destroy(), even if parsing fails.
+ *
+ * The file is opened, loaded fully into memory, and closed before this call
+ * returns.
+ *
+ * @param tree The tree to initialize
+ * @param filename The filename passed to fopen() to read the file
+ * @param max_bytes The maximum size of file to load, or 0 for unlimited size.
+ */
+void mpack_tree_init_filename(mpack_tree_t* tree, const char* filename, size_t max_bytes);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_tree_init_filename().
+ */
+MPACK_INLINE void mpack_tree_init_file(mpack_tree_t* tree, const char* filename, size_t max_bytes) {
+    mpack_tree_init_filename(tree, filename, max_bytes);
+}
+
+/**
+ * Initializes a tree to parse the given libc FILE. This can be used to
+ * read from stdin, or from a file opened separately.
+ *
+ * The tree must be destroyed with mpack_tree_destroy(), even if parsing fails.
+ *
+ * The FILE is fully loaded fully into memory (and closed if requested) before
+ * this call returns.
+ *
+ * @param tree The tree to initialize.
+ * @param stdfile The FILE.
+ * @param max_bytes The maximum size of file to load, or 0 for unlimited size.
+ * @param close_when_done If true, fclose() will be called on the FILE when it
+ *         is no longer needed. If false, the file will not be closed when
+ *         reading is done.
+ *
+ * @warning The tree will read all data in the FILE before parsing it. If this
+ *          is used on stdin, the parser will block until it is closed, even if
+ *          a complete message has been written to it!
+ */
+void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes, bool close_when_done);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Tree Functions
+ * @{
+ */
+
+/**
+ * Sets the maximum byte size and maximum number of nodes allowed per message.
+ *
+ * The default is SIZE_MAX (no limit) unless @ref mpack_tree_init_stream() is
+ * called (where maximums are required.)
+ *
+ * If a pool of nodes is used, the node limit is the lesser of this limit and
+ * the pool size.
+ *
+ * @param tree The tree parser
+ * @param max_message_size The maximum size of a message in bytes
+ * @param max_message_nodes The maximum number of nodes per message. See
+ *        @ref mpack_node_data_t for the size of nodes.
+ */
+void mpack_tree_set_limits(mpack_tree_t* tree, size_t max_message_size,
+        size_t max_message_nodes);
+
+/**
+ * Parses a MessagePack message into a tree of immutable nodes.
+ *
+ * If successful, the root node will be available under @ref mpack_tree_root().
+ * If not, an appropriate error will be flagged.
+ *
+ * This can be called repeatedly to parse a series of messages from a data
+ * source. When this is called, all previous nodes from this tree and their
+ * contents (including the root node) are invalidated.
+ *
+ * If this is called with a stream (see @ref mpack_tree_init_stream()), the
+ * stream must block until data is available. (Otherwise, if this is called on
+ * a non-blocking stream, parsing will fail with @ref mpack_error_io when the
+ * fill function returns 0.)
+ *
+ * There is no way to recover a tree in an error state. It must be destroyed.
+ */
+void mpack_tree_parse(mpack_tree_t* tree);
+
+/**
+ * Attempts to parse a MessagePack message from a non-blocking stream into a
+ * tree of immutable nodes.
+ *
+ * A non-blocking read function must have been passed to the tree in
+ * mpack_tree_init_stream().
+ *
+ * If this returns true, a message is available under
+ * @ref mpack_tree_root(). The tree nodes and data will be valid until
+ * the next time a parse is started.
+ *
+ * If this returns false, no message is available, because either not enough
+ * data is available yet or an error has occurred. You must check the tree for
+ * errors whenever this returns false. If there is no error, you should try
+ * again later when more data is available. (You will want to select()/poll()
+ * on the underlying socket or use some other asynchronous mechanism to
+ * determine when it has data.)
+ *
+ * There is no way to recover a tree in an error state. It must be destroyed.
+ *
+ * @see mpack_tree_init_stream()
+ */
+bool mpack_tree_try_parse(mpack_tree_t* tree);
+
+/**
+ * Returns the root node of the tree, if the tree is not in an error state.
+ * Returns a nil node otherwise.
+ *
+ * @warning You must call mpack_tree_parse() before calling this. If
+ * @ref mpack_tree_parse() was never called, the tree will assert.
+ */
+mpack_node_t mpack_tree_root(mpack_tree_t* tree);
+
+/**
+ * Returns the error state of the tree.
+ */
+MPACK_INLINE mpack_error_t mpack_tree_error(mpack_tree_t* tree) {
+    return tree->error;
+}
+
+/**
+ * Returns the size in bytes of the current parsed message.
+ *
+ * If there is something in the buffer after the MessagePack object, this can
+ * be used to find it.
+ *
+ * This is zero if an error occurred during tree parsing (since the
+ * portion of the data that the first complete object occupies cannot
+ * be determined if the data is invalid or corrupted.)
+ */
+MPACK_INLINE size_t mpack_tree_size(mpack_tree_t* tree) {
+    return tree->size;
+}
+
+/**
+ * Destroys the tree.
+ */
+mpack_error_t mpack_tree_destroy(mpack_tree_t* tree);
+
+/**
+ * Sets the custom pointer to pass to the tree callbacks, such as teardown.
+ *
+ * @param tree The MPack tree.
+ * @param context User data to pass to the tree callbacks.
+ *
+ * @see mpack_reader_context()
+ */
+MPACK_INLINE void mpack_tree_set_context(mpack_tree_t* tree, void* context) {
+    tree->context = context;
+}
+
+/**
+ * Returns the custom context for tree callbacks.
+ *
+ * @see mpack_tree_set_context
+ * @see mpack_tree_init_stream
+ */
+MPACK_INLINE void* mpack_tree_context(mpack_tree_t* tree) {
+    return tree->context;
+}
+
+/**
+ * Sets the error function to call when an error is flagged on the tree.
+ *
+ * This should normally be used with mpack_tree_set_context() to register
+ * a custom pointer to pass to the error function.
+ *
+ * See the definition of mpack_tree_error_t for more information about
+ * what you can do from an error callback.
+ *
+ * @see mpack_tree_error_t
+ * @param tree The MPack tree.
+ * @param error_fn The function to call when an error is flagged on the tree.
+ */
+MPACK_INLINE void mpack_tree_set_error_handler(mpack_tree_t* tree, mpack_tree_error_t error_fn) {
+    tree->error_fn = error_fn;
+}
+
+/**
+ * Sets the teardown function to call when the tree is destroyed.
+ *
+ * This should normally be used with mpack_tree_set_context() to register
+ * a custom pointer to pass to the teardown function.
+ *
+ * @param tree The MPack tree.
+ * @param teardown The function to call when the tree is destroyed.
+ */
+MPACK_INLINE void mpack_tree_set_teardown(mpack_tree_t* tree, mpack_tree_teardown_t teardown) {
+    tree->teardown = teardown;
+}
+
+/**
+ * Places the tree in the given error state, calling the error callback if one
+ * is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the tree is already in an error state, this call is ignored and no
+ * error callback is called.
+ */
+void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Node Core Functions
+ * @{
+ */
+
+/**
+ * Places the node's tree in the given error state, calling the error callback
+ * if one is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the tree is already in an error state, this call is ignored and no
+ * error callback is called.
+ */
+void mpack_node_flag_error(mpack_node_t node, mpack_error_t error);
+
+/**
+ * Returns the error state of the node's tree.
+ */
+MPACK_INLINE mpack_error_t mpack_node_error(mpack_node_t node) {
+    return mpack_tree_error(node.tree);
+}
+
+/**
+ * Returns a tag describing the given node, or a nil tag if the
+ * tree is in an error state.
+ */
+mpack_tag_t mpack_node_tag(mpack_node_t node);
+
+/** @cond */
+
+#if MPACK_DEBUG && MPACK_STDIO
+/*
+ * Converts a node to a pseudo-JSON string for debugging purposes, placing the
+ * result in the given buffer with a null-terminator.
+ *
+ * If the buffer does not have enough space, the result will be truncated (but
+ * it is guaranteed to be null-terminated.)
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_node_print_to_buffer(mpack_node_t node, char* buffer, size_t buffer_size);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes, calling the given
+ * callback as many times as is necessary to output the character data.
+ *
+ * No null-terminator or trailing newline will be written.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_node_print_to_callback(mpack_node_t node, mpack_print_callback_t callback, void* context);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes
+ * and pretty-prints it to the given file.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_node_print_to_file(mpack_node_t node, FILE* file);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes
+ * and pretty-prints it to stdout.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+MPACK_INLINE void mpack_node_print_to_stdout(mpack_node_t node) {
+    mpack_node_print_to_file(node, stdout);
+}
+
+/*
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_node_print_to_stdout().
+ */
+MPACK_INLINE void mpack_node_print(mpack_node_t node) {
+    mpack_node_print_to_stdout(node);
+}
+#endif
+
+/** @endcond */
+
+/**
+ * @}
+ */
+
+/**
+ * @name Node Primitive Value Functions
+ * @{
+ */
+
+/**
+ * Returns the type of the node.
+ */
+mpack_type_t mpack_node_type(mpack_node_t node);
+
+/**
+ * Returns true if the given node is a nil node; false otherwise.
+ *
+ * To ensure that a node is nil and flag an error otherwise, use
+ * mpack_node_nil().
+ */
+bool mpack_node_is_nil(mpack_node_t node);
+
+/**
+ * Returns true if the given node handle indicates a missing node; false otherwise.
+ *
+ * To ensure that a node is missing and flag an error otherwise, use
+ * mpack_node_missing().
+ */
+bool mpack_node_is_missing(mpack_node_t node);
+
+/**
+ * Checks that the given node is of nil type, raising @ref mpack_error_type
+ * otherwise.
+ *
+ * Use mpack_node_is_nil() to return whether the node is nil.
+ */
+void mpack_node_nil(mpack_node_t node);
+
+/**
+ * Checks that the given node indicates a missing node, raising @ref
+ * mpack_error_type otherwise.
+ *
+ * Use mpack_node_is_missing() to return whether the node is missing.
+ */
+void mpack_node_missing(mpack_node_t node);
+
+/**
+ * Returns the bool value of the node. If this node is not of the correct
+ * type, false is returned and mpack_error_type is raised.
+ */
+bool mpack_node_bool(mpack_node_t node);
+
+/**
+ * Checks if the given node is of bool type with value true, raising
+ * mpack_error_type otherwise.
+ */
+void mpack_node_true(mpack_node_t node);
+
+/**
+ * Checks if the given node is of bool type with value false, raising
+ * mpack_error_type otherwise.
+ */
+void mpack_node_false(mpack_node_t node);
+
+/**
+ * Returns the 8-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+uint8_t mpack_node_u8(mpack_node_t node);
+
+/**
+ * Returns the 8-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int8_t mpack_node_i8(mpack_node_t node);
+
+/**
+ * Returns the 16-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+uint16_t mpack_node_u16(mpack_node_t node);
+
+/**
+ * Returns the 16-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int16_t mpack_node_i16(mpack_node_t node);
+
+/**
+ * Returns the 32-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+uint32_t mpack_node_u32(mpack_node_t node);
+
+/**
+ * Returns the 32-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int32_t mpack_node_i32(mpack_node_t node);
+
+/**
+ * Returns the 64-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised, and zero is returned.
+ */
+uint64_t mpack_node_u64(mpack_node_t node);
+
+/**
+ * Returns the 64-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int64_t mpack_node_i64(mpack_node_t node);
+
+/**
+ * Returns the unsigned int value of the node.
+ *
+ * Returns zero if an error occurs.
+ *
+ * @throws mpack_error_type If the node is not an integer type or does not fit in the range of an unsigned int
+ */
+unsigned int mpack_node_uint(mpack_node_t node);
+
+/**
+ * Returns the int value of the node.
+ *
+ * Returns zero if an error occurs.
+ *
+ * @throws mpack_error_type If the node is not an integer type or does not fit in the range of an int
+ */
+int mpack_node_int(mpack_node_t node);
+
+#if MPACK_FLOAT
+/**
+ * Returns the float value of the node. The underlying value can be an
+ * integer, float or double; the value is converted to a float.
+ *
+ * @note Reading a double or a large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+float mpack_node_float(mpack_node_t node);
+#endif
+
+#if MPACK_DOUBLE
+/**
+ * Returns the double value of the node. The underlying value can be an
+ * integer, float or double; the value is converted to a double.
+ *
+ * @note Reading a very large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+double mpack_node_double(mpack_node_t node);
+#endif
+
+#if MPACK_FLOAT
+/**
+ * Returns the float value of the node. The underlying value must be a float,
+ * not a double or an integer. This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float.
+ */
+float mpack_node_float_strict(mpack_node_t node);
+#endif
+
+#if MPACK_DOUBLE
+/**
+ * Returns the double value of the node. The underlying value must be a float
+ * or double, not an integer. This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float or double.
+ */
+double mpack_node_double_strict(mpack_node_t node);
+#endif
+
+#if !MPACK_FLOAT
+/**
+ * Returns the float value of the node as a raw uint32_t. The underlying value
+ * must be a float, not a double or an integer.
+ *
+ * @throws mpack_error_type if the underlying value is not a float.
+ */
+uint32_t mpack_node_raw_float(mpack_node_t node);
+#endif
+
+#if !MPACK_DOUBLE
+/**
+ * Returns the double value of the node as a raw uint64_t. The underlying value
+ * must be a double, not a float or an integer.
+ *
+ * @throws mpack_error_type if the underlying value is not a float or double.
+ */
+uint64_t mpack_node_raw_double(mpack_node_t node);
+#endif
+
+
+#if MPACK_EXTENSIONS
+/**
+ * Returns a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @throws mpack_error_type if the underlying value is not a timestamp.
+ */
+mpack_timestamp_t mpack_node_timestamp(mpack_node_t node);
+
+/**
+ * Returns a timestamp's (signed) seconds since 1970-01-01T00:00:00Z.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @throws mpack_error_type if the underlying value is not a timestamp.
+ */
+int64_t mpack_node_timestamp_seconds(mpack_node_t node);
+
+/**
+ * Returns a timestamp's additional nanoseconds.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @return A nanosecond count between 0 and 999,999,999 inclusive.
+ * @throws mpack_error_type if the underlying value is not a timestamp.
+ */
+uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Node String and Data Functions
+ * @{
+ */
+
+/**
+ * Checks that the given node contains a valid UTF-8 string.
+ *
+ * If the string is invalid, this flags an error, which would cause subsequent calls
+ * to mpack_node_str() to return NULL and mpack_node_strlen() to return zero. So you
+ * can check the node for error immediately after calling this, or you can call those
+ * functions to use the data anyway and check for errors later.
+ *
+ * @throws mpack_error_type If this node is not a string or does not contain valid UTF-8.
+ *
+ * @param node The string node to test
+ *
+ * @see mpack_node_str()
+ * @see mpack_node_strlen()
+ */
+void mpack_node_check_utf8(mpack_node_t node);
+
+/**
+ * Checks that the given node contains a valid UTF-8 string with no NUL bytes.
+ *
+ * This does not check that the string has a null-terminator! It only checks whether
+ * the string could safely be represented as a C-string by appending a null-terminator.
+ * (If the string does already contain a null-terminator, this will flag an error.)
+ *
+ * This is performed automatically by other UTF-8 cstr helper functions. Only
+ * call this if you will do something else with the data directly, but you still
+ * want to ensure it will be valid as a UTF-8 C-string.
+ *
+ * @throws mpack_error_type If this node is not a string, does not contain valid UTF-8,
+ *     or contains a NUL byte.
+ *
+ * @param node The string node to test
+ *
+ * @see mpack_node_str()
+ * @see mpack_node_strlen()
+ * @see mpack_node_copy_utf8_cstr()
+ * @see mpack_node_utf8_cstr_alloc()
+ */
+void mpack_node_check_utf8_cstr(mpack_node_t node);
+
+#if MPACK_EXTENSIONS
+/**
+ * Returns the extension type of the given ext node.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+int8_t mpack_node_exttype(mpack_node_t node);
+#endif
+
+/**
+ * Returns the number of bytes in the given bin node.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * If this node is not a bin, @ref mpack_error_type is raised and zero is returned.
+ */
+size_t mpack_node_bin_size(mpack_node_t node);
+
+/**
+ * Returns the length of the given str, bin or ext node.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * If this node is not a str, bin or map, @ref mpack_error_type is raised and zero
+ * is returned.
+ */
+uint32_t mpack_node_data_len(mpack_node_t node);
+
+/**
+ * Returns the length in bytes of the given string node. This does not
+ * include any null-terminator.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * If this node is not a str, @ref mpack_error_type is raised and zero is returned.
+ */
+size_t mpack_node_strlen(mpack_node_t node);
+
+/**
+ * Returns a pointer to the data contained by this node, ensuring the node is a
+ * string.
+ *
+ * @warning Strings are not null-terminated! Use one of the cstr functions
+ * to get a null-terminated string.
+ *
+ * The pointer is valid as long as the data backing the tree is valid.
+ *
+ * If this node is not a string, @ref mpack_error_type is raised and @c NULL is returned.
+ *
+ * @see mpack_node_copy_cstr()
+ * @see mpack_node_cstr_alloc()
+ * @see mpack_node_utf8_cstr_alloc()
+ */
+const char* mpack_node_str(mpack_node_t node);
+
+/**
+ * Returns a pointer to the data contained by this node.
+ *
+ * @note Strings are not null-terminated! Use one of the cstr functions
+ * to get a null-terminated string.
+ *
+ * The pointer is valid as long as the data backing the tree is valid.
+ *
+ * If this node is not of a str, bin or map, @ref mpack_error_type is raised, and
+ * @c NULL is returned.
+ *
+ * @see mpack_node_copy_cstr()
+ * @see mpack_node_cstr_alloc()
+ * @see mpack_node_utf8_cstr_alloc()
+ */
+const char* mpack_node_data(mpack_node_t node);
+
+/**
+ * Returns a pointer to the data contained by this bin node.
+ *
+ * The pointer is valid as long as the data backing the tree is valid.
+ *
+ * If this node is not a bin, @ref mpack_error_type is raised and @c NULL is
+ * returned.
+ */
+const char* mpack_node_bin_data(mpack_node_t node);
+
+/**
+ * Copies the bytes contained by this node into the given buffer, returning the
+ * number of bytes in the node.
+ *
+ * @throws mpack_error_type If this node is not a str, bin or ext type
+ * @throws mpack_error_too_big If the string does not fit in the given buffer
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's bytes
+ * @param bufsize The size of the given buffer
+ *
+ * @return The number of bytes in the node, or zero if an error occurs.
+ */
+size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t bufsize);
+
+/**
+ * Checks that the given node contains a valid UTF-8 string and copies the
+ * string into the given buffer, returning the number of bytes in the string.
+ *
+ * @throws mpack_error_type If this node is not a string
+ * @throws mpack_error_too_big If the string does not fit in the given buffer
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's bytes
+ * @param bufsize The size of the given buffer
+ *
+ * @return The number of bytes in the node, or zero if an error occurs.
+ */
+size_t mpack_node_copy_utf8(mpack_node_t node, char* buffer, size_t bufsize);
+
+/**
+ * Checks that the given node contains a string with no NUL bytes, copies the string
+ * into the given buffer, and adds a null terminator.
+ *
+ * If this node is not of a string type, @ref mpack_error_type is raised. If the string
+ * does not fit, @ref mpack_error_data is raised.
+ *
+ * If any error occurs, the buffer will contain an empty null-terminated string.
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's string
+ * @param size The size of the given buffer
+ */
+void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t size);
+
+/**
+ * Checks that the given node contains a valid UTF-8 string with no NUL bytes,
+ * copies the string into the given buffer, and adds a null terminator.
+ *
+ * If this node is not of a string type, @ref mpack_error_type is raised. If the string
+ * does not fit, @ref mpack_error_data is raised.
+ *
+ * If any error occurs, the buffer will contain an empty null-terminated string.
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's string
+ * @param size The size of the given buffer
+ */
+void mpack_node_copy_utf8_cstr(mpack_node_t node, char* buffer, size_t size);
+
+#ifdef MPACK_MALLOC
+/**
+ * Allocates a new chunk of data using MPACK_MALLOC() with the bytes
+ * contained by this node.
+ *
+ * The allocated data must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type If this node is not a str, bin or ext type
+ * @throws mpack_error_too_big If the size of the data is larger than the
+ *     given maximum size
+ * @throws mpack_error_memory If an allocation failure occurs
+ *
+ * @param node The node from which to allocate and copy data
+ * @param maxsize The maximum size to allocate
+ *
+ * @return The allocated data, or NULL if any error occurs.
+ */
+char* mpack_node_data_alloc(mpack_node_t node, size_t maxsize);
+
+/**
+ * Allocates a new null-terminated string using MPACK_MALLOC() with the string
+ * contained by this node.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type If this node is not a string or contains NUL bytes
+ * @throws mpack_error_too_big If the size of the string plus null-terminator
+ *     is larger than the given maximum size
+ * @throws mpack_error_memory If an allocation failure occurs
+ *
+ * @param node The node from which to allocate and copy string data
+ * @param maxsize The maximum size to allocate, including the null-terminator
+ *
+ * @return The allocated string, or NULL if any error occurs.
+ */
+char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxsize);
+
+/**
+ * Allocates a new null-terminated string using MPACK_MALLOC() with the UTF-8
+ * string contained by this node.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type If this node is not a string, is not valid UTF-8,
+ *     or contains NUL bytes
+ * @throws mpack_error_too_big If the size of the string plus null-terminator
+ *     is larger than the given maximum size
+ * @throws mpack_error_memory If an allocation failure occurs
+ *
+ * @param node The node from which to allocate and copy string data
+ * @param maxsize The maximum size to allocate, including the null-terminator
+ *
+ * @return The allocated string, or NULL if any error occurs.
+ */
+char* mpack_node_utf8_cstr_alloc(mpack_node_t node, size_t maxsize);
+#endif
+
+/**
+ * Searches the given string array for a string matching the given
+ * node and returns its index.
+ *
+ * If the node does not match any of the given strings,
+ * @ref mpack_error_type is flagged. Use mpack_node_enum_optional()
+ * if you want to allow values other than the given strings.
+ *
+ * If any error occurs or if the tree is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum           { APPLE ,  BANANA ,  ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_node_enum(node, fruits, COUNT);
+ * @endcode
+ *
+ * @param node The node
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ * @return The index of the matched string, or @a count in case of error
+ */
+size_t mpack_node_enum(mpack_node_t node, const char* strings[], size_t count);
+
+/**
+ * Searches the given string array for a string matching the given node,
+ * returning its index or @a count if no strings match.
+ *
+ * If the value is not a string, or it does not match any of the
+ * given strings, @a count is returned and no error is flagged.
+ *
+ * If any error occurs or if the tree is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum           { APPLE ,  BANANA ,  ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_node_enum_optional(node, fruits, COUNT);
+ * @endcode
+ *
+ * @param node The node
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ * @return The index of the matched string, or @a count in case of error
+ */
+size_t mpack_node_enum_optional(mpack_node_t node, const char* strings[], size_t count);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Compound Node Functions
+ * @{
+ */
+
+/**
+ * Returns the length of the given array node. Raises mpack_error_type
+ * and returns 0 if the given node is not an array.
+ */
+size_t mpack_node_array_length(mpack_node_t node);
+
+/**
+ * Returns the node in the given array at the given index. If the node
+ * is not an array, @ref mpack_error_type is raised and a nil node is returned.
+ * If the given index is out of bounds, @ref mpack_error_data is raised and
+ * a nil node is returned.
+ */
+mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index);
+
+/**
+ * Returns the number of key/value pairs in the given map node. Raises
+ * mpack_error_type and returns 0 if the given node is not a map.
+ */
+size_t mpack_node_map_count(mpack_node_t node);
+
+/**
+ * Returns the key node in the given map at the given index.
+ *
+ * A nil node is returned in case of error.
+ *
+ * @throws mpack_error_type if the node is not a map
+ * @throws mpack_error_data if the given index is out of bounds
+ */
+mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index);
+
+/**
+ * Returns the value node in the given map at the given index.
+ *
+ * A nil node is returned in case of error.
+ *
+ * @throws mpack_error_type if the node is not a map
+ * @throws mpack_error_data if the given index is out of bounds
+ */
+mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index);
+
+/**
+ * Returns the value node in the given map for the given integer key.
+ *
+ * The key must exist within the map. Use mpack_node_map_int_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num);
+
+/**
+ * Returns the value node in the given map for the given integer key, or a
+ * missing node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ *         not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num);
+
+/**
+ * Returns the value node in the given map for the given unsigned integer key.
+ *
+ * The key must exist within the map. Use mpack_node_map_uint_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num);
+
+/**
+ * Returns the value node in the given map for the given unsigned integer
+ * key, or a missing node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ *         not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num);
+
+/**
+ * Returns the value node in the given map for the given string key.
+ *
+ * The key must exist within the map. Use mpack_node_map_str_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length);
+
+/**
+ * Returns the value node in the given map for the given string key, or a missing
+ * node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ *         not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length);
+
+/**
+ * Returns the value node in the given map for the given null-terminated
+ * string key.
+ *
+ * The key must exist within the map. Use mpack_node_map_cstr_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr);
+
+/**
+ * Returns the value node in the given map for the given null-terminated
+ * string key, or a missing node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ *         not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given integer key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_int(mpack_node_t node, int64_t num);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given unsigned integer key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_uint(mpack_node_t node, uint64_t num);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given string key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given null-terminated string key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr);
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+#endif
+
+MPACK_EXTERN_C_END
+MPACK_SILENCE_WARNINGS_END
+
+#endif
+
+
+#endif
+
diff --git a/wpiutil/src/main/native/include/wpi/mutex.h b/wpiutil/src/main/native/include/wpi/mutex.h
index 0ce3a51..c29509a 100644
--- a/wpiutil/src/main/native/include/wpi/mutex.h
+++ b/wpiutil/src/main/native/include/wpi/mutex.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
diff --git a/wpiutil/src/main/native/include/wpi/nodiscard.h b/wpiutil/src/main/native/include/wpi/nodiscard.h
new file mode 100644
index 0000000..d924036
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/nodiscard.h
@@ -0,0 +1,21 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#ifndef WPIUTIL_WPI_NODISCARD_H_
+#define WPIUTIL_WPI_NODISCARD_H_
+
+// This should only be used on APIs that can be compiled as C or C++. If the API
+// is C++ only, use [[nodiscard]] instead.
+#ifndef WPI_NODISCARD
+#ifdef __cplusplus
+#define WPI_NODISCARD [[nodiscard]]
+#elif defined(__GNUC__) || defined(__llvm__)
+#define WPI_NODISCARD __attribute__((warn_unused_result))
+#elif _MSC_VER
+#define WPI_NODISCARD _Check_return_
+#endif
+
+#endif
+
+#endif  // WPIUTIL_WPI_NODISCARD_H_
diff --git a/wpiutil/src/main/native/include/wpi/numbers b/wpiutil/src/main/native/include/wpi/numbers
new file mode 100644
index 0000000..d45aee0
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/numbers
@@ -0,0 +1,64 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <type_traits>
+
+namespace wpi::numbers {
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T e_v = 2.718281828459045235360287471352662498L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T log2e_v = 1.442695040888963407359924681001892137L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T log10e_v = 0.434294481903251827651128918916605082L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T pi_v = 3.141592653589793238462643383279502884L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T inv_pi_v = 0.318309886183790671537767526745028724L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T inv_sqrtpi_v = 0.564189583547756286948079451560772586L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T ln2_v = 0.693147180559945309417232121458176568L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T ln10_v = 2.302585092994045684017991454684364208L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T sqrt2_v = 1.414213562373095048801688724209698078L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T sqrt3_v = 1.732050807568877293527446341505872366L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T inv_sqrt3_v = 0.577350269189625764509148780501957456L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T egamma_v = 0.577215664901532860606512090082402431L;
+
+template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
+inline constexpr T phi_v = 1.618033988749894848204586834365638117L;
+
+inline constexpr double e = e_v<double>;
+inline constexpr double log2e = log2e_v<double>;
+inline constexpr double log10e = log10e_v<double>;
+inline constexpr double pi = pi_v<double>;
+inline constexpr double inv_pi = inv_pi_v<double>;
+inline constexpr double inv_sqrtpi = inv_sqrtpi_v<double>;
+inline constexpr double ln2 = ln2_v<double>;
+inline constexpr double ln10 = ln10_v<double>;
+inline constexpr double sqrt2 = sqrt2_v<double>;
+inline constexpr double sqrt3 = sqrt3_v<double>;
+inline constexpr double inv_sqrt3 = inv_sqrt3_v<double>;
+inline constexpr double egamma = egamma_v<double>;
+inline constexpr double phi = phi_v<double>;
+
+}  // namespace wpi::numbers
diff --git a/wpiutil/src/main/native/include/wpi/optional.h b/wpiutil/src/main/native/include/wpi/optional.h
deleted file mode 100644
index a92f22c..0000000
--- a/wpiutil/src/main/native/include/wpi/optional.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-#ifndef WPIUTIL_WPI_OPTIONAL_H_
-#define WPIUTIL_WPI_OPTIONAL_H_
-
-// clang-format off
-
-#ifdef _MSC_VER
-#pragma message("warning: wpi/optional.h is deprecated; include <optional> instead")
-#else
-#warning "wpi/optional.h is deprecated; include <optional> instead"
-#endif
-
-#include <optional>
-
-namespace wpi {
-
-template <typename T>
-using optional [[deprecated("use std::optional")]] = std::optional<T>;
-
-using nullopt_t [[deprecated("use std::nullopt_t")]] = std::nullopt_t;
-
-[[deprecated("use std::nullopt")]] inline constexpr std::nullopt_t nullopt =
-    std::nullopt;
-
-}  // namespace wpi
-
-// clang-format on
-
-#endif  // WPIUTIL_WPI_OPTIONAL_H_
diff --git a/wpiutil/src/main/native/include/wpi/priority_mutex.h b/wpiutil/src/main/native/include/wpi/priority_mutex.h
index bb16289..73389ae 100644
--- a/wpiutil/src/main/native/include/wpi/priority_mutex.h
+++ b/wpiutil/src/main/native/include/wpi/priority_mutex.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
diff --git a/wpiutil/src/main/native/include/wpi/priority_queue.h b/wpiutil/src/main/native/include/wpi/priority_queue.h
index b194090..4610aba 100644
--- a/wpiutil/src/main/native/include/wpi/priority_queue.h
+++ b/wpiutil/src/main/native/include/wpi/priority_queue.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_PRIORITY_QUEUE_H_
 #define WPIUTIL_WPI_PRIORITY_QUEUE_H_
diff --git a/wpiutil/src/main/native/include/wpi/raw_istream.h b/wpiutil/src/main/native/include/wpi/raw_istream.h
index 7fbbe0b..2371fc6 100644
--- a/wpiutil/src/main/native/include/wpi/raw_istream.h
+++ b/wpiutil/src/main/native/include/wpi/raw_istream.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_RAW_ISTREAM_H_
 #define WPIUTIL_WPI_RAW_ISTREAM_H_
@@ -13,13 +10,12 @@
 #include <algorithm>
 #include <cstddef>
 #include <string>
+#include <string_view>
 #include <system_error>
 #include <vector>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
-#include "wpi/Twine.h"
+#include "wpi/span.h"
 
 namespace wpi {
 
@@ -50,7 +46,9 @@
 
   size_t readsome(void* data, size_t len) {
     size_t readlen = (std::min)(in_avail(), len);
-    if (readlen == 0) return 0;
+    if (readlen == 0) {
+      return 0;
+    }
     read_impl(data, readlen);
     return m_read_count;
   }
@@ -101,7 +99,7 @@
   // @param buf Buffer for output
   // @param maxLen Maximum length
   // @return Line
-  StringRef getline(SmallVectorImpl<char>& buf, int maxLen);
+  std::string_view getline(SmallVectorImpl<char>& buf, int maxLen);
 
   virtual void close() = 0;
 
@@ -135,9 +133,9 @@
   // not const as we don't want to allow temporaries
   explicit raw_mem_istream(std::string& str)
       : raw_mem_istream(str.data(), str.size()) {}
-  explicit raw_mem_istream(ArrayRef<char> mem)
+  explicit raw_mem_istream(span<const char> mem)
       : raw_mem_istream(mem.data(), mem.size()) {}
-  explicit raw_mem_istream(ArrayRef<uint8_t> mem)
+  explicit raw_mem_istream(span<const uint8_t> mem)
       : raw_mem_istream(reinterpret_cast<const char*>(mem.data()), mem.size()) {
   }
   explicit raw_mem_istream(const char* str)
@@ -155,11 +153,11 @@
 
 class raw_fd_istream : public raw_istream {
  public:
-  raw_fd_istream(const Twine& filename, std::error_code& ec,
+  raw_fd_istream(std::string_view filename, std::error_code& ec,
                  size_t bufSize = 4096);
   raw_fd_istream(int fd, bool shouldClose, size_t bufSize = 4096);
   ~raw_fd_istream() override;
-  void close() override;
+  void close() final;
   size_t in_avail() const override;
 
  private:
diff --git a/wpiutil/src/main/native/include/wpi/raw_ostream.h b/wpiutil/src/main/native/include/wpi/raw_ostream.h
index bb577fe..84ec925 100644
--- a/wpiutil/src/main/native/include/wpi/raw_ostream.h
+++ b/wpiutil/src/main/native/include/wpi/raw_ostream.h
@@ -14,31 +14,24 @@
 #ifndef WPIUTIL_WPI_RAW_OSTREAM_H
 #define WPIUTIL_WPI_RAW_OSTREAM_H
 
-#include "wpi/ArrayRef.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
+#include "wpi/span.h"
 #include <cassert>
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
 #include <string>
+#include <string_view>
 #include <vector>
 #include <system_error>
 
-namespace wpi {
-
-class format_object_base;
-class FormattedString;
-class FormattedNumber;
-class FormattedBytes;
-
-namespace sys {
 namespace fs {
 enum FileAccess : unsigned;
 enum OpenFlags : unsigned;
 enum CreationDisposition : unsigned;
 } // end namespace fs
-} // end namespace sys
+
+namespace wpi {
 
 /// This class implements an extremely fast bulk output stream that can *only*
 /// output to a stream.  It does not support seeking, reopening, rewinding, line
@@ -166,7 +159,7 @@
     return *this;
   }
 
-  raw_ostream &operator<<(ArrayRef<uint8_t> Arr) {
+  raw_ostream &operator<<(span<const uint8_t> Arr) {
     // Inline fast path, particularly for arrays with a known length.
     size_t Size = Arr.size();
 
@@ -181,7 +174,7 @@
     return *this;
   }
 
-  raw_ostream &operator<<(StringRef Str) {
+  raw_ostream &operator<<(std::string_view Str) {
     // Inline fast path, particularly for strings with a known length.
     size_t Size = Str.size();
 
@@ -200,7 +193,7 @@
     // Inline fast path, particularly for constant strings where a sufficiently
     // smart compiler will simplify strlen.
 
-    return this->operator<<(StringRef(Str));
+    return this->operator<<(std::string_view(Str));
   }
 
   raw_ostream &operator<<(const std::string &Str) {
@@ -221,28 +214,9 @@
     return write(Arr.data(), Arr.size());
   }
 
-  raw_ostream &operator<<(unsigned long N);
-  raw_ostream &operator<<(long N);
-  raw_ostream &operator<<(unsigned long long N);
-  raw_ostream &operator<<(long long N);
-  raw_ostream &operator<<(const void *P);
-
-  raw_ostream &operator<<(unsigned int N) {
-    return this->operator<<(static_cast<unsigned long>(N));
-  }
-
-  raw_ostream &operator<<(int N) {
-    return this->operator<<(static_cast<long>(N));
-  }
-
-  raw_ostream &operator<<(double N);
-
-  /// Output \p N in hexadecimal, without any prefix or padding.
-  raw_ostream &write_hex(unsigned long long N);
-
   /// Output \p Str, turning '\\', '\t', '\n', '"', and anything that doesn't
   /// satisfy wpi::isPrint into an escape sequence.
-  raw_ostream &write_escaped(StringRef Str, bool UseHexEscapes = false);
+  raw_ostream &write_escaped(std::string_view Str, bool UseHexEscapes = false);
 
   raw_ostream &write(unsigned char C);
   raw_ostream &write(const char *Ptr, size_t Size);
@@ -250,18 +224,6 @@
     return write(reinterpret_cast<const char *>(Ptr), Size);
   }
 
-  // Formatted output, see the format() function in Support/Format.h.
-  raw_ostream &operator<<(const format_object_base &Fmt);
-
-  // Formatted output, see the leftJustify() function in Support/Format.h.
-  raw_ostream &operator<<(const FormattedString &);
-
-  // Formatted output, see the formatHex() function in Support/Format.h.
-  raw_ostream &operator<<(const FormattedNumber &);
-
-  // Formatted output, see the format_bytes() function in Support/Format.h.
-  raw_ostream &operator<<(const FormattedBytes &);
-
   /// indent - Insert 'NumSpaces' spaces.
   raw_ostream &indent(unsigned NumSpaces);
 
@@ -429,16 +391,16 @@
   /// As a special case, if Filename is "-", then the stream will use
   /// STDOUT_FILENO instead of opening a file. This will not close the stdout
   /// descriptor.
-  raw_fd_ostream(StringRef Filename, std::error_code &EC);
-  raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                 sys::fs::CreationDisposition Disp);
-  raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                 sys::fs::FileAccess Access);
-  raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                 sys::fs::OpenFlags Flags);
-  raw_fd_ostream(StringRef Filename, std::error_code &EC,
-                 sys::fs::CreationDisposition Disp, sys::fs::FileAccess Access,
-                 sys::fs::OpenFlags Flags);
+  raw_fd_ostream(std::string_view Filename, std::error_code &EC);
+  raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                 fs::CreationDisposition Disp);
+  raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                 fs::FileAccess Access);
+  raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                 fs::OpenFlags Flags);
+  raw_fd_ostream(std::string_view Filename, std::error_code &EC,
+                 fs::CreationDisposition Disp, fs::FileAccess Access,
+                 fs::OpenFlags Flags);
 
   /// FD is the file descriptor that this writes to.  If ShouldClose is true,
   /// this closes the file when the stream is destroyed. If FD is for stdout or
@@ -545,8 +507,8 @@
 
   void flush() = delete;
 
-  /// Return a StringRef for the vector contents.
-  StringRef str() { return StringRef(OS.data(), OS.size()); }
+  /// Return a std::string_view for the vector contents.
+  std::string_view str() { return std::string_view(OS.data(), OS.size()); }
 };
 
 /// A raw_ostream that writes to a vector.  This is a
@@ -578,8 +540,8 @@
 
   void flush() = delete;
 
-  /// Return a StringRef for the vector contents.
-  StringRef str() { return StringRef(OS.data(), OS.size()); }
+  /// Return a std::string_view for the vector contents.
+  std::string_view str() { return std::string_view(OS.data(), OS.size()); }
 };
 
 /// A raw_ostream that writes to an SmallVector or SmallString.  This is a
@@ -611,8 +573,9 @@
 
   void flush() = delete;
 
-  /// Return an ArrayRef for the vector contents.
-  ArrayRef<uint8_t> array() { return ArrayRef<uint8_t>(OS.data(), OS.size()); }
+  /// Return an span for the vector contents.
+  span<uint8_t> array() { return {OS.data(), OS.size()}; }
+  span<const uint8_t> array() const { return {OS.data(), OS.size()}; }
 };
 
 /// A raw_ostream that writes to a vector.  This is a
@@ -644,8 +607,9 @@
 
   void flush() = delete;
 
-  /// Return a StringRef for the vector contents.
-  ArrayRef<uint8_t> array() { return ArrayRef<uint8_t>(OS.data(), OS.size()); }
+  /// Return a span for the vector contents.
+  span<uint8_t> array() { return {OS.data(), OS.size()}; }
+  span<const uint8_t> array() const { return {OS.data(), OS.size()}; }
 };
 
 
diff --git a/wpiutil/src/main/native/include/wpi/raw_socket_istream.h b/wpiutil/src/main/native/include/wpi/raw_socket_istream.h
index 4ff8022..f6899e2 100644
--- a/wpiutil/src/main/native/include/wpi/raw_socket_istream.h
+++ b/wpiutil/src/main/native/include/wpi/raw_socket_istream.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_RAW_SOCKET_ISTREAM_H_
 #define WPIUTIL_WPI_RAW_SOCKET_ISTREAM_H_
diff --git a/wpiutil/src/main/native/include/wpi/raw_socket_ostream.h b/wpiutil/src/main/native/include/wpi/raw_socket_ostream.h
index 151af46..5cdeb41 100644
--- a/wpiutil/src/main/native/include/wpi/raw_socket_ostream.h
+++ b/wpiutil/src/main/native/include/wpi/raw_socket_ostream.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_RAW_SOCKET_OSTREAM_H_
 #define WPIUTIL_WPI_RAW_SOCKET_OSTREAM_H_
@@ -18,7 +15,7 @@
  public:
   raw_socket_ostream(NetworkStream& stream, bool shouldClose)
       : m_stream(stream), m_shouldClose(shouldClose) {}
-  ~raw_socket_ostream();
+  ~raw_socket_ostream() override;
 
   void close();
 
diff --git a/wpiutil/src/main/native/include/wpi/raw_uv_ostream.h b/wpiutil/src/main/native/include/wpi/raw_uv_ostream.h
index e35b4a8..4773c61 100644
--- a/wpiutil/src/main/native/include/wpi/raw_uv_ostream.h
+++ b/wpiutil/src/main/native/include/wpi/raw_uv_ostream.h
@@ -1,16 +1,16 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_RAW_UV_OSTREAM_H_
 #define WPIUTIL_WPI_RAW_UV_OSTREAM_H_
 
-#include "wpi/ArrayRef.h"
+#include <functional>
+#include <utility>
+
 #include "wpi/SmallVector.h"
 #include "wpi/raw_ostream.h"
+#include "wpi/span.h"
 #include "wpi/uv/Buffer.h"
 
 namespace wpi {
@@ -29,8 +29,7 @@
    *                  performed using Buffer::Allocate().
    */
   raw_uv_ostream(SmallVectorImpl<uv::Buffer>& bufs, size_t allocSize)
-      : m_bufs(bufs),
-        m_alloc([=]() { return uv::Buffer::Allocate(allocSize); }) {
+      : m_bufs(bufs), m_alloc([=] { return uv::Buffer::Allocate(allocSize); }) {
     SetUnbuffered();
   }
 
@@ -41,19 +40,24 @@
    */
   raw_uv_ostream(SmallVectorImpl<uv::Buffer>& bufs,
                  std::function<uv::Buffer()> alloc)
-      : m_bufs(bufs), m_alloc(alloc) {
+      : m_bufs(bufs), m_alloc(std::move(alloc)) {
     SetUnbuffered();
   }
 
   ~raw_uv_ostream() override = default;
 
   /**
-   * Returns an ArrayRef to the buffers.
+   * Returns an span to the buffers.
    */
-  ArrayRef<uv::Buffer> bufs() { return m_bufs; }
+  span<uv::Buffer> bufs() { return m_bufs; }
 
   void flush() = delete;
 
+  /**
+   * Resets the amount of allocated space.
+   */
+  void reset() { m_left = 0; }
+
  private:
   void write_impl(const char* data, size_t len) override;
   uint64_t current_pos() const override;
diff --git a/wpiutil/src/main/native/include/wpi/sendable/Sendable.h b/wpiutil/src/main/native/include/wpi/sendable/Sendable.h
new file mode 100644
index 0000000..d587c3c
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/sendable/Sendable.h
@@ -0,0 +1,28 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include "wpi/SymbolExports.h"
+
+namespace wpi {
+
+class SendableBuilder;
+
+/**
+ * Interface for Sendable objects.
+ */
+class WPILIB_DLLEXPORT Sendable {
+ public:
+  virtual ~Sendable() = default;
+
+  /**
+   * Initializes this Sendable object.
+   *
+   * @param builder sendable builder
+   */
+  virtual void InitSendable(SendableBuilder& builder) = 0;
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/sendable/SendableBuilder.h b/wpiutil/src/main/native/include/wpi/sendable/SendableBuilder.h
new file mode 100644
index 0000000..e092a1e
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/sendable/SendableBuilder.h
@@ -0,0 +1,217 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "wpi/SmallVector.h"
+#include "wpi/span.h"
+
+namespace wpi {
+
+class SendableBuilder {
+ public:
+  /**
+   * The backend kinds used for the sendable builder.
+   */
+  enum BackendKind { kUnknown, kNetworkTables };
+
+  virtual ~SendableBuilder() = default;
+
+  /**
+   * Set the string representation of the named data type that will be used
+   * by the smart dashboard for this sendable.
+   *
+   * @param type    data type
+   */
+  virtual void SetSmartDashboardType(std::string_view type) = 0;
+
+  /**
+   * Set a flag indicating if this sendable should be treated as an actuator.
+   * By default this flag is false.
+   *
+   * @param value   true if actuator, false if not
+   */
+  virtual void SetActuator(bool value) = 0;
+
+  /**
+   * Set the function that should be called to set the Sendable into a safe
+   * state.  This is called when entering and exiting Live Window mode.
+   *
+   * @param func    function
+   */
+  virtual void SetSafeState(std::function<void()> func) = 0;
+
+  /**
+   * Add a boolean property.
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddBooleanProperty(std::string_view key,
+                                  std::function<bool()> getter,
+                                  std::function<void(bool)> setter) = 0;
+
+  /**
+   * Add a double property.
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddDoubleProperty(std::string_view key,
+                                 std::function<double()> getter,
+                                 std::function<void(double)> setter) = 0;
+
+  /**
+   * Add a string property.
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddStringProperty(
+      std::string_view key, std::function<std::string()> getter,
+      std::function<void(std::string_view)> setter) = 0;
+
+  /**
+   * Add a boolean array property.
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddBooleanArrayProperty(
+      std::string_view key, std::function<std::vector<int>()> getter,
+      std::function<void(wpi::span<const int>)> setter) = 0;
+
+  /**
+   * Add a double array property.
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddDoubleArrayProperty(
+      std::string_view key, std::function<std::vector<double>()> getter,
+      std::function<void(wpi::span<const double>)> setter) = 0;
+
+  /**
+   * Add a string array property.
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddStringArrayProperty(
+      std::string_view key, std::function<std::vector<std::string>()> getter,
+      std::function<void(wpi::span<const std::string>)> setter) = 0;
+
+  /**
+   * Add a raw property.
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddRawProperty(std::string_view key,
+                              std::function<std::string()> getter,
+                              std::function<void(std::string_view)> setter) = 0;
+
+  /**
+   * Add a string property (SmallString form).
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddSmallStringProperty(
+      std::string_view key,
+      std::function<std::string_view(wpi::SmallVectorImpl<char>& buf)> getter,
+      std::function<void(std::string_view)> setter) = 0;
+
+  /**
+   * Add a boolean array property (SmallVector form).
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddSmallBooleanArrayProperty(
+      std::string_view key,
+      std::function<wpi::span<const int>(wpi::SmallVectorImpl<int>& buf)>
+          getter,
+      std::function<void(wpi::span<const int>)> setter) = 0;
+
+  /**
+   * Add a double array property (SmallVector form).
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddSmallDoubleArrayProperty(
+      std::string_view key,
+      std::function<wpi::span<const double>(wpi::SmallVectorImpl<double>& buf)>
+          getter,
+      std::function<void(wpi::span<const double>)> setter) = 0;
+
+  /**
+   * Add a string array property (SmallVector form).
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddSmallStringArrayProperty(
+      std::string_view key,
+      std::function<
+          wpi::span<const std::string>(wpi::SmallVectorImpl<std::string>& buf)>
+          getter,
+      std::function<void(wpi::span<const std::string>)> setter) = 0;
+
+  /**
+   * Add a raw property (SmallVector form).
+   *
+   * @param key     property name
+   * @param getter  getter function (returns current value)
+   * @param setter  setter function (sets new value)
+   */
+  virtual void AddSmallRawProperty(
+      std::string_view key,
+      std::function<std::string_view(wpi::SmallVectorImpl<char>& buf)> getter,
+      std::function<void(std::string_view)> setter) = 0;
+
+  /**
+   * Gets the kind of backend being used.
+   *
+   * @return Backend kind
+   */
+  virtual BackendKind GetBackendKind() const = 0;
+
+  /**
+   * Return whether this sendable has been published.
+   *
+   * @return True if it has been published, false if not.
+   */
+  virtual bool IsPublished() const = 0;
+
+  /**
+   * Update the published values by calling the getters for all properties.
+   */
+  virtual void Update() = 0;
+
+  /**
+   * Clear properties.
+   */
+  virtual void ClearProperties() = 0;
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/sendable/SendableHelper.h b/wpiutil/src/main/native/include/wpi/sendable/SendableHelper.h
new file mode 100644
index 0000000..5b6bbe3
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/sendable/SendableHelper.h
@@ -0,0 +1,177 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "wpi/deprecated.h"
+#include "wpi/sendable/SendableRegistry.h"
+
+namespace wpi {
+
+/**
+ * A helper class for use with objects that add themselves to SendableRegistry.
+ * It takes care of properly calling Move() and Remove() on move and
+ * destruction.  No action is taken if the object is copied.
+ * Use public inheritance with CRTP when using this class.
+ * @tparam CRTP derived class
+ */
+template <typename Derived>
+class SendableHelper {
+ public:
+  SendableHelper(const SendableHelper& rhs) = default;
+  SendableHelper& operator=(const SendableHelper& rhs) = default;
+
+#if !defined(_MSC_VER) && (defined(__llvm__) || __GNUC__ > 7)
+  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1442819
+  __attribute__((no_sanitize("vptr")))
+#endif
+  SendableHelper(SendableHelper&& rhs) {
+    // it is safe to call Move() multiple times with the same rhs
+    SendableRegistry::Move(static_cast<Derived*>(this),
+                           static_cast<Derived*>(&rhs));
+  }
+
+#if !defined(_MSC_VER) && (defined(__llvm__) || __GNUC__ > 7)
+  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1442819
+  __attribute__((no_sanitize("vptr")))
+#endif
+  SendableHelper&
+  operator=(SendableHelper&& rhs) {
+    // it is safe to call Move() multiple times with the same rhs
+    SendableRegistry::Move(static_cast<Derived*>(this),
+                           static_cast<Derived*>(&rhs));
+    return *this;
+  }
+
+  /**
+   * Gets the name of this Sendable object.
+   *
+   * @deprecated use SendableRegistry::GetName()
+   *
+   * @return Name
+   */
+  WPI_DEPRECATED("use SendableRegistry::GetName()")
+  std::string GetName() const {
+    return SendableRegistry::GetName(static_cast<const Derived*>(this));
+  }
+
+  /**
+   * Sets the name of this Sendable object.
+   *
+   * @deprecated use SendableRegistry::SetName()
+   *
+   * @param name name
+   */
+  WPI_DEPRECATED("use SendableRegistry::SetName()")
+  void SetName(std::string_view name) {
+    SendableRegistry::SetName(static_cast<Derived*>(this), name);
+  }
+
+  /**
+   * Sets both the subsystem name and device name of this Sendable object.
+   *
+   * @deprecated use SendableRegistry::SetName()
+   *
+   * @param subsystem subsystem name
+   * @param name device name
+   */
+  WPI_DEPRECATED("use SendableRegistry::SetName()")
+  void SetName(std::string_view subsystem, std::string_view name) {
+    SendableRegistry::SetName(static_cast<Derived*>(this), subsystem, name);
+  }
+
+  /**
+   * Gets the subsystem name of this Sendable object.
+   *
+   * @deprecated use SendableRegistry::GetSubsystem().
+   *
+   * @return Subsystem name
+   */
+  WPI_DEPRECATED("use SendableRegistry::GetSubsystem()")
+  std::string GetSubsystem() const {
+    return SendableRegistry::GetSubsystem(static_cast<const Derived*>(this));
+  }
+
+  /**
+   * Sets the subsystem name of this Sendable object.
+   *
+   * @deprecated use SendableRegistry::SetSubsystem()
+   *
+   * @param subsystem subsystem name
+   */
+  WPI_DEPRECATED("use SendableRegistry::SetSubsystem()")
+  void SetSubsystem(std::string_view subsystem) {
+    SendableRegistry::SetSubsystem(static_cast<Derived*>(this), subsystem);
+  }
+
+ protected:
+  /**
+   * Add a child component.
+   *
+   * @deprecated use SendableRegistry::AddChild()
+   *
+   * @param child child component
+   */
+  WPI_DEPRECATED("use SendableRegistry::AddChild()")
+  void AddChild(std::shared_ptr<Sendable> child) {
+    SendableRegistry::AddChild(static_cast<Derived*>(this), child.get());
+  }
+
+  /**
+   * Add a child component.
+   *
+   * @deprecated use SendableRegistry::AddChild()
+   *
+   * @param child child component
+   */
+  WPI_DEPRECATED("use SendableRegistry::AddChild()")
+  void AddChild(void* child) {
+    SendableRegistry::AddChild(static_cast<Derived*>(this), child);
+  }
+
+  /**
+   * Sets the name of the sensor with a channel number.
+   *
+   * @deprecated use SendableRegistry::SetName()
+   *
+   * @param moduleType A string that defines the module name in the label for
+   *                   the value
+   * @param channel    The channel number the device is plugged into
+   */
+  WPI_DEPRECATED("use SendableRegistry::SetName()")
+  void SetName(std::string_view moduleType, int channel) {
+    SendableRegistry::SetName(static_cast<Derived*>(this), moduleType, channel);
+  }
+
+  /**
+   * Sets the name of the sensor with a module and channel number.
+   *
+   * @deprecated use SendableRegistry::SetName()
+   *
+   * @param moduleType   A string that defines the module name in the label for
+   *                     the value
+   * @param moduleNumber The number of the particular module type
+   * @param channel      The channel number the device is plugged into (usually
+   * PWM)
+   */
+  WPI_DEPRECATED("use SendableRegistry::SetName()")
+  void SetName(std::string_view moduleType, int moduleNumber, int channel) {
+    SendableRegistry::SetName(static_cast<Derived*>(this), moduleType,
+                              moduleNumber, channel);
+  }
+
+ protected:
+  SendableHelper() = default;
+
+  ~SendableHelper() {
+    // it is safe to call Remove() multiple times with the same object
+    SendableRegistry::Remove(static_cast<Derived*>(this));
+  }
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/sendable/SendableRegistry.h b/wpiutil/src/main/native/include/wpi/sendable/SendableRegistry.h
new file mode 100644
index 0000000..a08f9e9
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/sendable/SendableRegistry.h
@@ -0,0 +1,333 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "wpi/function_ref.h"
+
+namespace wpi {
+
+class Sendable;
+class SendableBuilder;
+
+/**
+ * The SendableRegistry class is the public interface for registering sensors
+ * and actuators for use on dashboards and LiveWindow.
+ */
+class SendableRegistry {
+ public:
+  SendableRegistry() = delete;
+
+  using UID = size_t;
+
+  /**
+   * Sets the factory for LiveWindow builders.
+   *
+   * @param factory factory function
+   */
+  static void SetLiveWindowBuilderFactory(
+      std::function<std::unique_ptr<SendableBuilder>()> factory);
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable object to add
+   * @param name component name
+   */
+  static void Add(Sendable* sendable, std::string_view name);
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable     object to add
+   * @param moduleType   A string that defines the module name in the label for
+   *                     the value
+   * @param channel      The channel number the device is plugged into
+   */
+  static void Add(Sendable* sendable, std::string_view moduleType, int channel);
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable     object to add
+   * @param moduleType   A string that defines the module name in the label for
+   *                     the value
+   * @param moduleNumber The number of the particular module type
+   * @param channel      The channel number the device is plugged into
+   */
+  static void Add(Sendable* sendable, std::string_view moduleType,
+                  int moduleNumber, int channel);
+
+  /**
+   * Adds an object to the registry.
+   *
+   * @param sendable object to add
+   * @param subsystem subsystem name
+   * @param name component name
+   */
+  static void Add(Sendable* sendable, std::string_view subsystem,
+                  std::string_view name);
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable object to add
+   * @param name component name
+   */
+  static void AddLW(Sendable* sendable, std::string_view name);
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable     object to add
+   * @param moduleType   A string that defines the module name in the label for
+   *                     the value
+   * @param channel      The channel number the device is plugged into
+   */
+  static void AddLW(Sendable* sendable, std::string_view moduleType,
+                    int channel);
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable     object to add
+   * @param moduleType   A string that defines the module name in the label for
+   *                     the value
+   * @param moduleNumber The number of the particular module type
+   * @param channel      The channel number the device is plugged into
+   */
+  static void AddLW(Sendable* sendable, std::string_view moduleType,
+                    int moduleNumber, int channel);
+
+  /**
+   * Adds an object to the registry and LiveWindow.
+   *
+   * @param sendable object to add
+   * @param subsystem subsystem name
+   * @param name component name
+   */
+  static void AddLW(Sendable* sendable, std::string_view subsystem,
+                    std::string_view name);
+
+  /**
+   * Adds a child object to an object.  Adds the child object to the registry
+   * if it's not already present.
+   *
+   * @param parent parent object
+   * @param child child object
+   */
+  static void AddChild(Sendable* parent, Sendable* child);
+
+  /**
+   * Adds a child object to an object.  Adds the child object to the registry
+   * if it's not already present.
+   *
+   * @param parent parent object
+   * @param child child object
+   */
+  static void AddChild(Sendable* parent, void* child);
+
+  /**
+   * Removes an object from the registry.
+   *
+   * @param sendable object to remove
+   * @return true if the object was removed; false if it was not present
+   */
+  static bool Remove(Sendable* sendable);
+
+  /**
+   * Moves an object in the registry (for use in move constructors/assignments).
+   *
+   * @param to new object
+   * @param from old object
+   */
+  static void Move(Sendable* to, Sendable* from);
+
+  /**
+   * Determines if an object is in the registry.
+   *
+   * @param sendable object to check
+   * @return True if in registry, false if not.
+   */
+  static bool Contains(const Sendable* sendable);
+
+  /**
+   * Gets the name of an object.
+   *
+   * @param sendable object
+   * @return Name (empty if object is not in registry)
+   */
+  static std::string GetName(const Sendable* sendable);
+
+  /**
+   * Sets the name of an object.
+   *
+   * @param sendable object
+   * @param name name
+   */
+  static void SetName(Sendable* sendable, std::string_view name);
+
+  /**
+   * Sets the name of an object with a channel number.
+   *
+   * @param sendable   object
+   * @param moduleType A string that defines the module name in the label for
+   *                   the value
+   * @param channel    The channel number the device is plugged into
+   */
+  static void SetName(Sendable* sendable, std::string_view moduleType,
+                      int channel);
+
+  /**
+   * Sets the name of an object with a module and channel number.
+   *
+   * @param sendable     object
+   * @param moduleType   A string that defines the module name in the label for
+   *                     the value
+   * @param moduleNumber The number of the particular module type
+   * @param channel      The channel number the device is plugged into
+   */
+  static void SetName(Sendable* sendable, std::string_view moduleType,
+                      int moduleNumber, int channel);
+
+  /**
+   * Sets both the subsystem name and device name of an object.
+   *
+   * @param sendable object
+   * @param subsystem subsystem name
+   * @param name device name
+   */
+  static void SetName(Sendable* sendable, std::string_view subsystem,
+                      std::string_view name);
+
+  /**
+   * Gets the subsystem name of an object.
+   *
+   * @param sendable object
+   * @return Subsystem name (empty if object is not in registry)
+   */
+  static std::string GetSubsystem(const Sendable* sendable);
+
+  /**
+   * Sets the subsystem name of an object.
+   *
+   * @param sendable object
+   * @param subsystem subsystem name
+   */
+  static void SetSubsystem(Sendable* sendable, std::string_view subsystem);
+
+  /**
+   * Gets a unique handle for setting/getting data with SetData() and GetData().
+   *
+   * @return Handle
+   */
+  static int GetDataHandle();
+
+  /**
+   * Associates arbitrary data with an object in the registry.
+   *
+   * @param sendable object
+   * @param handle data handle returned by GetDataHandle()
+   * @param data data to set
+   * @return Previous data (may be null)
+   */
+  static std::shared_ptr<void> SetData(Sendable* sendable, int handle,
+                                       std::shared_ptr<void> data);
+
+  /**
+   * Gets arbitrary data associated with an object in the registry.
+   *
+   * @param sendable object
+   * @param handle data handle returned by GetDataHandle()
+   * @return data (may be null if none associated)
+   */
+  static std::shared_ptr<void> GetData(Sendable* sendable, int handle);
+
+  /**
+   * Enables LiveWindow for an object.
+   *
+   * @param sendable object
+   */
+  static void EnableLiveWindow(Sendable* sendable);
+
+  /**
+   * Disables LiveWindow for an object.
+   *
+   * @param sendable object
+   */
+  static void DisableLiveWindow(Sendable* sendable);
+
+  /**
+   * Get unique id for an object.  Since objects can move, use this instead
+   * of storing Sendable* directly if ownership is in question.
+   *
+   * @param sendable object
+   * @return unique id
+   */
+  static UID GetUniqueId(Sendable* sendable);
+
+  /**
+   * Get sendable object for a given unique id.
+   *
+   * @param uid unique id
+   * @return sendable object (may be null)
+   */
+  static Sendable* GetSendable(UID uid);
+
+  /**
+   * Publishes an object in the registry.
+   *
+   * @param sendableUid sendable unique id
+   * @param builder publisher backend
+   */
+  static void Publish(UID sendableUid,
+                      std::unique_ptr<SendableBuilder> builder);
+
+  /**
+   * Updates published information from an object.
+   *
+   * @param sendableUid sendable unique id
+   */
+  static void Update(UID sendableUid);
+
+  /**
+   * Data passed to ForeachLiveWindow() callback function
+   */
+  struct CallbackData {
+    CallbackData(Sendable* sendable_, std::string_view name_,
+                 std::string_view subsystem_, wpi::Sendable* parent_,
+                 std::shared_ptr<void>& data_, SendableBuilder& builder_)
+        : sendable(sendable_),
+          name(name_),
+          subsystem(subsystem_),
+          parent(parent_),
+          data(data_),
+          builder(builder_) {}
+
+    Sendable* sendable;
+    std::string_view name;
+    std::string_view subsystem;
+    Sendable* parent;
+    std::shared_ptr<void>& data;
+    SendableBuilder& builder;
+  };
+
+  /**
+   * Iterates over LiveWindow-enabled objects in the registry.
+   * It is *not* safe to call other SendableRegistry functions from the
+   * callback (this will likely deadlock).
+   *
+   * @param dataHandle data handle to get data pointer passed to callback
+   * @param callback function to call for each object
+   */
+  static void ForeachLiveWindow(
+      int dataHandle, wpi::function_ref<void(CallbackData& cbdata)> callback);
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/include/wpi/sha1.h b/wpiutil/src/main/native/include/wpi/sha1.h
index 6a0817f..75464bf 100644
--- a/wpiutil/src/main/native/include/wpi/sha1.h
+++ b/wpiutil/src/main/native/include/wpi/sha1.h
@@ -23,8 +23,7 @@
 #include <stdint.h>
 
 #include <string>
-
-#include "wpi/StringRef.h"
+#include <string_view>
 
 namespace wpi {
 template <typename T>
@@ -34,12 +33,12 @@
 class SHA1 {
  public:
   SHA1();
-  void Update(StringRef s);
+  void Update(std::string_view s);
   void Update(raw_istream& is);
   std::string Final();
-  StringRef Final(SmallVectorImpl<char>& buf);
-  StringRef RawFinal(SmallVectorImpl<char>& buf);
-  static std::string FromFile(StringRef filename);
+  std::string_view Final(SmallVectorImpl<char>& buf);
+  std::string_view RawFinal(SmallVectorImpl<char>& buf);
+  static std::string FromFile(std::string_view filename);
 
  private:
   uint32_t digest[5];
diff --git a/wpiutil/src/main/native/include/wpi/span.h b/wpiutil/src/main/native/include/wpi/span.h
new file mode 100644
index 0000000..cf71d79
--- /dev/null
+++ b/wpiutil/src/main/native/include/wpi/span.h
@@ -0,0 +1,415 @@
+
+/*
+This is an implementation of C++20's std::span
+http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf
+*/
+
+//          Copyright Tristan Brindle 2018.
+// Distributed under the Boost Software License, Version 1.0.
+//    (See accompanying file ../../LICENSE_1_0.txt or copy at
+//          https://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef WPIUTIL_WPI_SPAN_HPP_INCLUDED
+#define WPIUTIL_WPI_SPAN_HPP_INCLUDED
+
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <type_traits>
+#if __cplusplus >= 202002L && __has_include(<span>)
+#include <span>
+#endif
+
+namespace wpi {
+
+inline constexpr std::size_t dynamic_extent = SIZE_MAX;
+
+template <typename ElementType, std::size_t Extent = dynamic_extent>
+class span;
+
+namespace detail {
+
+template <typename E, std::size_t S>
+struct span_storage {
+    constexpr span_storage() noexcept = default;
+
+    constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept
+       : ptr(p_ptr)
+    {}
+
+    E* ptr = nullptr;
+    static constexpr std::size_t size = S;
+};
+
+template <typename E>
+struct span_storage<E, dynamic_extent> {
+    constexpr span_storage() noexcept = default;
+
+    constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept
+        : ptr(p_ptr), size(p_size)
+    {}
+
+    E* ptr = nullptr;
+    std::size_t size = 0;
+};
+
+template <typename T>
+using uncvref_t =
+    typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+
+template <typename>
+struct is_span : std::false_type {};
+
+template <typename T, std::size_t S>
+struct is_span<span<T, S>> : std::true_type {};
+
+template <typename>
+struct is_std_array : std::false_type {};
+
+template <typename T, std::size_t N>
+struct is_std_array<std::array<T, N>> : std::true_type {};
+
+template <typename, typename = void>
+struct has_size_and_data : std::false_type {};
+
+template <typename T>
+struct has_size_and_data<T, std::void_t<decltype(std::size(std::declval<T>())),
+                                   decltype(std::data(std::declval<T>()))>>
+    : std::true_type {};
+
+template <typename C, typename U = uncvref_t<C>>
+struct is_container {
+    static constexpr bool value =
+        !is_span<U>::value && !is_std_array<U>::value &&
+        !std::is_array<U>::value && has_size_and_data<C>::value;
+};
+
+template <typename T>
+using remove_pointer_t = typename std::remove_pointer<T>::type;
+
+template <typename, typename, typename = void>
+struct is_container_element_type_compatible : std::false_type {};
+
+template <typename T, typename E>
+struct is_container_element_type_compatible<
+    T, E,
+    typename std::enable_if<
+        !std::is_same<typename std::remove_cv<decltype(
+                          std::data(std::declval<T>()))>::type,
+                      void>::value>::type>
+    : std::is_convertible<
+          remove_pointer_t<decltype(std::data(std::declval<T>()))> (*)[],
+          E (*)[]> {};
+
+template <typename, typename = size_t>
+struct is_complete : std::false_type {};
+
+template <typename T>
+struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
+
+} // namespace detail
+
+template <typename ElementType, std::size_t Extent>
+class span {
+    static_assert(std::is_object<ElementType>::value,
+                  "A span's ElementType must be an object type (not a "
+                  "reference type or void)");
+    static_assert(detail::is_complete<ElementType>::value,
+                  "A span's ElementType must be a complete type (not a forward "
+                  "declaration)");
+    static_assert(!std::is_abstract<ElementType>::value,
+                  "A span's ElementType cannot be an abstract class type");
+
+    using storage_type = detail::span_storage<ElementType, Extent>;
+
+public:
+    // constants and types
+    using element_type = ElementType;
+    using value_type = typename std::remove_cv<ElementType>::type;
+    using size_type = std::size_t;
+    using difference_type = std::ptrdiff_t;
+    using pointer = element_type*;
+    using const_pointer = const element_type*;
+    using reference = element_type&;
+    using const_reference = const element_type&;
+    using iterator = pointer;
+    using reverse_iterator = std::reverse_iterator<iterator>;
+
+    static constexpr size_type extent = Extent;
+
+    // [span.cons], span constructors, copy, assignment, and destructor
+    template <
+        std::size_t E = Extent,
+        typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0>
+    constexpr span() noexcept
+    {}
+
+    constexpr span(pointer ptr, size_type count)
+        : storage_(ptr, count)
+    {
+        assert(extent == dynamic_extent || count == extent);
+    }
+
+    constexpr span(pointer first_elem, pointer last_elem)
+        : storage_(first_elem, last_elem - first_elem)
+    {
+        assert(extent == dynamic_extent ||
+                        last_elem - first_elem ==
+                            static_cast<std::ptrdiff_t>(extent));
+    }
+
+    template <std::size_t N, std::size_t E = Extent,
+              typename std::enable_if<
+                  (E == dynamic_extent || N == E) &&
+                      detail::is_container_element_type_compatible<
+                          element_type (&)[N], ElementType>::value,
+                  int>::type = 0>
+    constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N)
+    {}
+
+    template <std::size_t N, std::size_t E = Extent,
+              typename std::enable_if<
+                  (E == dynamic_extent || N == E) &&
+                      detail::is_container_element_type_compatible<
+                          std::array<value_type, N>&, ElementType>::value,
+                  int>::type = 0>
+    constexpr span(std::array<value_type, N>& arr) noexcept
+        : storage_(arr.data(), N)
+    {}
+
+    template <std::size_t N, std::size_t E = Extent,
+              typename std::enable_if<
+                  (E == dynamic_extent || N == E) &&
+                      detail::is_container_element_type_compatible<
+                          const std::array<value_type, N>&, ElementType>::value,
+                  int>::type = 0>
+    constexpr span(const std::array<value_type, N>& arr) noexcept
+        : storage_(arr.data(), N)
+    {}
+
+    template <
+        typename Container, std::size_t E = Extent,
+        typename std::enable_if<
+            E == dynamic_extent && detail::is_container<Container>::value &&
+                detail::is_container_element_type_compatible<
+                    Container&, ElementType>::value,
+            int>::type = 0>
+    constexpr span(Container& cont)
+        : storage_(std::data(cont), std::size(cont))
+    {}
+
+    template <
+        typename Container, std::size_t E = Extent,
+        typename std::enable_if<
+            E == dynamic_extent && detail::is_container<Container>::value &&
+                detail::is_container_element_type_compatible<
+                    const Container&, ElementType>::value,
+            int>::type = 0>
+    constexpr span(const Container& cont)
+        : storage_(std::data(cont), std::size(cont))
+    {}
+
+    constexpr span(const span& other) noexcept = default;
+
+    template <typename OtherElementType, std::size_t OtherExtent,
+              typename std::enable_if<
+                  (Extent == OtherExtent || Extent == dynamic_extent) &&
+                      std::is_convertible<OtherElementType (*)[],
+                                          ElementType (*)[]>::value,
+                  int>::type = 0>
+    constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept
+        : storage_(other.data(), other.size())
+    {}
+
+#ifdef __cpp_lib_span
+    constexpr span(std::span<ElementType> other) noexcept
+        : storage_(other.data(), other.size())
+    {}
+#endif
+
+    ~span() noexcept = default;
+
+    constexpr span&
+    operator=(const span& other) noexcept = default;
+
+    // [span.sub], span subviews
+    template <std::size_t Count>
+    constexpr span<element_type, Count> first() const
+    {
+        assert(Count <= size());
+        return {data(), Count};
+    }
+
+    template <std::size_t Count>
+    constexpr span<element_type, Count> last() const
+    {
+        assert(Count <= size());
+        return {data() + (size() - Count), Count};
+    }
+
+    template <std::size_t Offset, std::size_t Count = dynamic_extent>
+    using subspan_return_t =
+        span<ElementType, Count != dynamic_extent
+                              ? Count
+                              : (Extent != dynamic_extent ? Extent - Offset
+                                                          : dynamic_extent)>;
+
+    template <std::size_t Offset, std::size_t Count = dynamic_extent>
+    constexpr subspan_return_t<Offset, Count> subspan() const
+    {
+        assert(Offset <= size() &&
+                        (Count == dynamic_extent || Offset + Count <= size()));
+        return {data() + Offset,
+                Count != dynamic_extent ? Count : size() - Offset};
+    }
+
+    constexpr span<element_type, dynamic_extent>
+    first(size_type count) const
+    {
+        assert(count <= size());
+        return {data(), count};
+    }
+
+    constexpr span<element_type, dynamic_extent>
+    last(size_type count) const
+    {
+        assert(count <= size());
+        return {data() + (size() - count), count};
+    }
+
+    constexpr span<element_type, dynamic_extent>
+    subspan(size_type offset, size_type count = dynamic_extent) const
+    {
+        assert(offset <= size() &&
+                        (count == dynamic_extent || offset + count <= size()));
+        return {data() + offset,
+                count == dynamic_extent ? size() - offset : count};
+    }
+
+    // [span.obs], span observers
+    constexpr size_type size() const noexcept { return storage_.size; }
+
+    constexpr size_type size_bytes() const noexcept
+    {
+        return size() * sizeof(element_type);
+    }
+
+    [[nodiscard]] constexpr bool empty() const noexcept
+    {
+        return size() == 0;
+    }
+
+    // [span.elem], span element access
+    constexpr reference operator[](size_type idx) const
+    {
+        assert(idx < size());
+        return *(data() + idx);
+    }
+
+    constexpr reference front() const
+    {
+        assert(!empty());
+        return *data();
+    }
+
+    constexpr reference back() const
+    {
+        assert(!empty());
+        return *(data() + (size() - 1));
+    }
+
+    constexpr pointer data() const noexcept { return storage_.ptr; }
+
+    // [span.iterators], span iterator support
+    constexpr iterator begin() const noexcept { return data(); }
+
+    constexpr iterator end() const noexcept { return data() + size(); }
+
+    constexpr reverse_iterator rbegin() const noexcept
+    {
+        return reverse_iterator(end());
+    }
+
+    constexpr reverse_iterator rend() const noexcept
+    {
+        return reverse_iterator(begin());
+    }
+
+#ifdef __cpp_lib_span
+    constexpr operator auto() const {
+      return std::span < ElementType,
+             (Extent == dynamic_extent)
+                 ? std::dynamic_extent
+                 : Extent > (storage_.ptr, storage_.size);
+    }
+#endif
+
+private:
+    storage_type storage_{};
+};
+
+/* Deduction Guides */
+template <class T, size_t N>
+span(T (&)[N])->span<T, N>;
+
+template <class T, size_t N>
+span(std::array<T, N>&)->span<T, N>;
+
+template <class T, size_t N>
+span(const std::array<T, N>&)->span<const T, N>;
+
+template <class Container>
+span(Container&)->span<typename Container::value_type>;
+
+template <class Container>
+span(const Container&)->span<const typename Container::value_type>;
+
+template <typename ElementType, std::size_t Extent>
+span<const std::byte, ((Extent == dynamic_extent) ? dynamic_extent
+                                             : sizeof(ElementType) * Extent)>
+as_bytes(span<ElementType, Extent> s) noexcept
+{
+    return {reinterpret_cast<const std::byte*>(s.data()), s.size_bytes()};
+}
+
+template <
+    class ElementType, size_t Extent,
+    typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0>
+span<std::byte, ((Extent == dynamic_extent) ? dynamic_extent
+                                       : sizeof(ElementType) * Extent)>
+as_writable_bytes(span<ElementType, Extent> s) noexcept
+{
+    return {reinterpret_cast<std::byte*>(s.data()), s.size_bytes()};
+}
+
+template <std::size_t N, typename E, std::size_t S>
+constexpr auto get(span<E, S> s) -> decltype(s[N])
+{
+    return s[N];
+}
+
+} // namespace wpi
+
+namespace std {
+
+template <typename ElementType, size_t Extent>
+class tuple_size<wpi::span<ElementType, Extent>>
+    : public integral_constant<size_t, Extent> {};
+
+template <typename ElementType>
+class tuple_size<wpi::span<
+    ElementType, wpi::dynamic_extent>>; // not defined
+
+template <size_t I, typename ElementType, size_t Extent>
+class tuple_element<I, wpi::span<ElementType, Extent>> {
+public:
+    static_assert(Extent != wpi::dynamic_extent &&
+                      I < Extent,
+                  "");
+    using type = ElementType;
+};
+
+} // end namespace std
+
+#endif // WPIUTIL_WPI_SPAN_HPP_INCLUDED
diff --git a/wpiutil/src/main/native/include/wpi/spinlock.h b/wpiutil/src/main/native/include/wpi/spinlock.h
index 73137f1..9e777fa 100644
--- a/wpiutil/src/main/native/include/wpi/spinlock.h
+++ b/wpiutil/src/main/native/include/wpi/spinlock.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
@@ -30,8 +27,11 @@
 
   LLVM_ATTRIBUTE_ALWAYS_INLINE
   void lock() {
-    for (unsigned int i = 1; !try_lock(); ++i)
-      if ((i & 0xff) == 0) std::this_thread::yield();
+    for (unsigned int i = 1; !try_lock(); ++i) {
+      if ((i & 0xff) == 0) {
+        std::this_thread::yield();
+      }
+    }
   }
 
   LLVM_ATTRIBUTE_ALWAYS_INLINE
@@ -58,8 +58,9 @@
                             std::memory_order_release);
     } else {
       if (owner_thread_id.load(std::memory_order_acquire) !=
-          std::this_thread::get_id())
+          std::this_thread::get_id()) {
         return false;
+      }
     }
     ++recursive_counter;
     return true;
@@ -67,8 +68,11 @@
 
   LLVM_ATTRIBUTE_ALWAYS_INLINE
   void lock() {
-    for (unsigned int i = 1; !try_lock(); ++i)
-      if ((i & 0xffff) == 0) std::this_thread::yield();
+    for (unsigned int i = 1; !try_lock(); ++i) {
+      if ((i & 0xffff) == 0) {
+        std::this_thread::yield();
+      }
+    }
   }
 
   LLVM_ATTRIBUTE_ALWAYS_INLINE
@@ -100,7 +104,9 @@
     auto us = std::this_thread::get_id();
     if (!owner_thread_id.compare_exchange_weak(owner, us,
                                                std::memory_order_acquire)) {
-      if (owner != us) return false;
+      if (owner != us) {
+        return false;
+      }
     }
     ++recursive_counter;
     return true;
@@ -108,8 +114,11 @@
 
   LLVM_ATTRIBUTE_ALWAYS_INLINE
   void lock() {
-    for (unsigned int i = 1; !try_lock(); ++i)
-      if ((i & 0xffff) == 0) std::this_thread::yield();
+    for (unsigned int i = 1; !try_lock(); ++i) {
+      if ((i & 0xffff) == 0) {
+        std::this_thread::yield();
+      }
+    }
   }
 
   LLVM_ATTRIBUTE_ALWAYS_INLINE
@@ -118,8 +127,9 @@
            std::this_thread::get_id());
     assert(recursive_counter > 0);
 
-    if (--recursive_counter == 0)
+    if (--recursive_counter == 0) {
       owner_thread_id.store(std::thread::id{}, std::memory_order_release);
+    }
   }
 };
 
diff --git a/wpiutil/src/main/native/include/wpi/static_circular_buffer.h b/wpiutil/src/main/native/include/wpi/static_circular_buffer.h
index 8329917..8cbf3fe 100644
--- a/wpiutil/src/main/native/include/wpi/static_circular_buffer.h
+++ b/wpiutil/src/main/native/include/wpi/static_circular_buffer.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
@@ -19,9 +16,7 @@
 template <class T, size_t N>
 class static_circular_buffer {
  public:
-  static_assert(N > 0, "The circular buffer size shouldn't be zero.");
-
-  constexpr static_circular_buffer() = default;
+  static_assert(N > 0, "Circular buffer size cannot be zero.");
 
   class iterator {
    public:
@@ -87,17 +82,38 @@
     size_t m_index;
   };
 
+  /**
+   * Returns begin iterator.
+   */
   iterator begin() { return iterator(this, 0); }
+
+  /**
+   * Returns end iterator.
+   */
   iterator end() {
     return iterator(this, ::wpi::static_circular_buffer<T, N>::size());
   }
 
+  /**
+   * Returns begin iterator.
+   */
   const_iterator begin() const { return const_iterator(this, 0); }
+
+  /**
+   * Returns end iterator.
+   */
   const_iterator end() const {
     return const_iterator(this, ::wpi::static_circular_buffer<T, N>::size());
   }
 
+  /**
+   * Returns begin iterator.
+   */
   const_iterator cbegin() const { return const_iterator(this, 0); }
+
+  /**
+   * Returns end iterator.
+   */
   const_iterator cend() const {
     return const_iterator(this, ::wpi::static_circular_buffer<T, N>::size());
   }
diff --git a/wpiutil/src/main/native/include/wpi/timestamp.h b/wpiutil/src/main/native/include/wpi/timestamp.h
index aefcd56..789e125 100644
--- a/wpiutil/src/main/native/include/wpi/timestamp.h
+++ b/wpiutil/src/main/native/include/wpi/timestamp.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_TIMESTAMP_H_
 #define WPIUTIL_WPI_TIMESTAMP_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Async.h b/wpiutil/src/main/native/include/wpi/uv/Async.h
index 2da870d..49f3dde 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Async.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Async.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_ASYNC_H_
 #define WPIUTIL_WPI_UV_ASYNC_H_
@@ -21,8 +18,7 @@
 #include "wpi/uv/Handle.h"
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 /**
  * Async handle.
@@ -43,10 +39,11 @@
   Async(const std::shared_ptr<Loop>& loop, const private_init&)
       : m_loop{loop} {}
   ~Async() noexcept override {
-    if (auto loop = m_loop.lock())
+    if (auto loop = m_loop.lock()) {
       this->Close();
-    else
+    } else {
       this->ForceClosed();
+    }
   }
 
   /**
@@ -69,7 +66,9 @@
         uv_async_init(loop->GetRaw(), h->GetRaw(), [](uv_async_t* handle) {
           auto& h = *static_cast<Async*>(handle->data);
           std::scoped_lock lock(h.m_mutex);
-          for (auto&& v : h.m_data) std::apply(h.wakeup, v);
+          for (auto&& v : h.m_data) {
+            std::apply(h.wakeup, v);
+          }
           h.m_data.clear();
         });
     if (err < 0) {
@@ -99,7 +98,9 @@
       std::scoped_lock lock(m_mutex);
       m_data.emplace_back(std::forward_as_tuple(std::forward<U>(u)...));
     }
-    if (loop) this->Invoke(&uv_async_send, this->GetRaw());
+    if (loop) {
+      this->Invoke(&uv_async_send, this->GetRaw());
+    }
   }
 
   /**
@@ -149,7 +150,14 @@
    * An async event will be emitted on the loop thread.
    */
   void Send() {
-    if (auto loop = m_loop.lock()) Invoke(&uv_async_send, GetRaw());
+    if (auto loop = m_loop.lock()) {
+      if (loop->GetThreadId() == std::this_thread::get_id()) {
+        // called from within the loop, just call the function directly
+        wakeup();
+      } else {
+        Invoke(&uv_async_send, GetRaw());
+      }
+    }
   }
 
   /**
@@ -161,7 +169,6 @@
   std::weak_ptr<Loop> m_loop;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_ASYNC_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/AsyncFunction.h b/wpiutil/src/main/native/include/wpi/uv/AsyncFunction.h
index 4510b84..fa4eb90 100644
--- a/wpiutil/src/main/native/include/wpi/uv/AsyncFunction.h
+++ b/wpiutil/src/main/native/include/wpi/uv/AsyncFunction.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_ASYNCFUNCTION_H_
 #define WPIUTIL_WPI_UV_ASYNCFUNCTION_H_
@@ -23,8 +20,7 @@
 #include "wpi/uv/Handle.h"
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 template <typename T>
 class AsyncFunction;
@@ -42,12 +38,13 @@
  public:
   AsyncFunction(const std::shared_ptr<Loop>& loop,
                 std::function<void(promise<R>, T...)> func, const private_init&)
-      : wakeup{func}, m_loop{loop} {}
+      : wakeup{std::move(func)}, m_loop{loop} {}
   ~AsyncFunction() noexcept override {
-    if (auto loop = m_loop.lock())
+    if (auto loop = m_loop.lock()) {
       this->Close();
-    else
+    } else {
       this->ForceClosed();
+    }
   }
 
   /**
@@ -89,10 +86,11 @@
             // waiting for it
             for (auto&& v : h.m_params) {
               auto p = h.m_promises.CreatePromise(v.first);
-              if (h.wakeup)
+              if (h.wakeup) {
                 std::apply(h.wakeup,
                            std::tuple_cat(std::make_tuple(std::move(p)),
                                           std::move(v.second)));
+              }
             }
             h.m_params.clear();
             // wake up any threads that might be waiting for the result
@@ -139,7 +137,9 @@
     }
 
     // signal the loop
-    if (loop) this->Invoke(&uv_async_send, this->GetRaw());
+    if (loop) {
+      this->Invoke(&uv_async_send, this->GetRaw());
+    }
 
     // return future
     return m_promises.CreateFuture(req);
@@ -162,7 +162,6 @@
   std::weak_ptr<Loop> m_loop;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_ASYNCFUNCTION_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Buffer.h b/wpiutil/src/main/native/include/wpi/uv/Buffer.h
index 04e96b9..5126841 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Buffer.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Buffer.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_BUFFER_H_
 #define WPIUTIL_WPI_UV_BUFFER_H_
@@ -12,14 +9,13 @@
 
 #include <cstring>
 #include <initializer_list>
+#include <string_view>
 #include <utility>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/SmallVector.h"
-#include "wpi/StringRef.h"
+#include "wpi/span.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 /**
  * Data buffer.  Convenience wrapper around uv_buf_t.
@@ -30,13 +26,13 @@
     base = nullptr;
     len = 0;
   }
-  /*implicit*/ Buffer(const uv_buf_t& oth) {  // NOLINT(runtime/explicit)
+  /*implicit*/ Buffer(const uv_buf_t& oth) {  // NOLINT
     base = oth.base;
     len = oth.len;
   }
-  /*implicit*/ Buffer(StringRef str)  // NOLINT(runtime/explicit)
+  /*implicit*/ Buffer(std::string_view str)  // NOLINT
       : Buffer{str.data(), str.size()} {}
-  /*implicit*/ Buffer(ArrayRef<uint8_t> arr)  // NOLINT(runtime/explicit)
+  /*implicit*/ Buffer(span<const uint8_t> arr)  // NOLINT
       : Buffer{reinterpret_cast<const char*>(arr.data()), arr.size()} {}
   Buffer(char* base_, size_t len_) {
     base = base_;
@@ -47,21 +43,21 @@
     len = static_cast<decltype(len)>(len_);
   }
 
-  ArrayRef<char> data() const { return ArrayRef<char>{base, len}; }
-  MutableArrayRef<char> data() { return MutableArrayRef<char>{base, len}; }
+  span<const char> data() const { return {base, len}; }
+  span<char> data() { return {base, len}; }
 
-  operator ArrayRef<char>() const { return data(); }
-  operator MutableArrayRef<char>() { return data(); }
+  operator span<const char>() const { return data(); }  // NOLINT
+  operator span<char>() { return data(); }              // NOLINT
 
   static Buffer Allocate(size_t size) { return Buffer{new char[size], size}; }
 
-  static Buffer Dup(StringRef in) {
+  static Buffer Dup(std::string_view in) {
     Buffer buf = Allocate(in.size());
     std::memcpy(buf.base, in.data(), in.size());
     return buf;
   }
 
-  static Buffer Dup(ArrayRef<uint8_t> in) {
+  static Buffer Dup(span<const uint8_t> in) {
     Buffer buf = Allocate(in.size());
     std::memcpy(buf.base, in.begin(), in.size());
     return buf;
@@ -116,7 +112,9 @@
    * Allocate a buffer.
    */
   Buffer Allocate() {
-    if (m_pool.empty()) return Buffer::Allocate(m_size);
+    if (m_pool.empty()) {
+      return Buffer::Allocate(m_size);
+    }
     auto buf = m_pool.back();
     m_pool.pop_back();
     buf.len = m_size;
@@ -133,15 +131,19 @@
    * This is NOT safe to use with arbitrary buffers unless they were
    * allocated with the same size as the buffer pool allocation size.
    */
-  void Release(MutableArrayRef<Buffer> bufs) {
-    for (auto& buf : bufs) m_pool.emplace_back(buf.Move());
+  void Release(span<Buffer> bufs) {
+    for (auto& buf : bufs) {
+      m_pool.emplace_back(buf.Move());
+    }
   }
 
   /**
    * Clear the pool, releasing all buffers.
    */
   void Clear() {
-    for (auto& buf : m_pool) buf.Deallocate();
+    for (auto& buf : m_pool) {
+      buf.Deallocate();
+    }
     m_pool.clear();
   }
 
@@ -153,10 +155,9 @@
 
  private:
   SmallVector<Buffer, DEPTH> m_pool;
-  size_t m_size;
+  size_t m_size;  // NOLINT
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_BUFFER_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Check.h b/wpiutil/src/main/native/include/wpi/uv/Check.h
index e64a9f0..7555452 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Check.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Check.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_CHECK_H_
 #define WPIUTIL_WPI_UV_CHECK_H_
@@ -15,8 +12,7 @@
 #include "wpi/Signal.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -64,7 +60,6 @@
   sig::Signal<> check;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_CHECK_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Error.h b/wpiutil/src/main/native/include/wpi/uv/Error.h
index 07a0ab8..1e717a4 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Error.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Error.h
@@ -1,24 +1,20 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_ERROR_H_
 #define WPIUTIL_WPI_UV_ERROR_H_
 
 #include <uv.h>
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 /**
  * Error code.
  */
 class Error {
  public:
-  Error() : m_err(UV_UNKNOWN) {}
+  Error() = default;
   explicit Error(int err) : m_err(err) {}
 
   /**
@@ -42,10 +38,9 @@
   const char* name() const { return uv_err_name(m_err); }
 
  private:
-  int m_err;
+  int m_err{UV_UNKNOWN};
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_ERROR_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/FsEvent.h b/wpiutil/src/main/native/include/wpi/uv/FsEvent.h
index cf91848..0ed2dcb 100644
--- a/wpiutil/src/main/native/include/wpi/uv/FsEvent.h
+++ b/wpiutil/src/main/native/include/wpi/uv/FsEvent.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_FSEVENT_H_
 #define WPIUTIL_WPI_UV_FSEVENT_H_
@@ -12,13 +9,12 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "wpi/Signal.h"
-#include "wpi/Twine.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -55,7 +51,7 @@
    * @param events Bitmask of event flags.  Only UV_FS_EVENT_RECURSIVE is
    *               supported (and only on OSX and Windows).
    */
-  void Start(const Twine& path, unsigned int flags = 0);
+  void Start(std::string_view path, unsigned int flags = 0);
 
   /**
    * Stop watching for changes.
@@ -78,7 +74,6 @@
   sig::Signal<const char*, int> fsEvent;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_FSEVENT_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/GetAddrInfo.h b/wpiutil/src/main/native/include/wpi/uv/GetAddrInfo.h
index deb4a24..18d4f32 100644
--- a/wpiutil/src/main/native/include/wpi/uv/GetAddrInfo.h
+++ b/wpiutil/src/main/native/include/wpi/uv/GetAddrInfo.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_GETADDRINFO_H_
 #define WPIUTIL_WPI_UV_GETADDRINFO_H_
@@ -12,13 +9,13 @@
 
 #include <functional>
 #include <memory>
+#include <string_view>
+#include <utility>
 
 #include "wpi/Signal.h"
-#include "wpi/Twine.h"
 #include "wpi/uv/Request.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -44,7 +41,7 @@
  * request when the resolution completes.  HandleError() is called on the
  * request if any errors occur.
  *
- * Either node or service may be null (`Twine::createNull()`) but not both.
+ * Either node or service may be empty but not both.
  *
  * @param loop Event loop
  * @param req request
@@ -54,7 +51,7 @@
  *              type constraints.
  */
 void GetAddrInfo(Loop& loop, const std::shared_ptr<GetAddrInfoReq>& req,
-                 const Twine& node, const Twine& service = Twine::createNull(),
+                 std::string_view node, std::string_view service = {},
                  const addrinfo* hints = nullptr);
 
 /**
@@ -62,7 +59,7 @@
  * request when the resolution completes.  HandleError() is called on the
  * request if any errors occur.
  *
- * Either node or service may be null (`Twine::createNull()`) but not both.
+ * Either node or service may be empty but not both.
  *
  * @param loop Event loop
  * @param req request
@@ -73,8 +70,7 @@
  */
 inline void GetAddrInfo(const std::shared_ptr<Loop>& loop,
                         const std::shared_ptr<GetAddrInfoReq>& req,
-                        const Twine& node,
-                        const Twine& service = Twine::createNull(),
+                        std::string_view node, std::string_view service = {},
                         const addrinfo* hints = nullptr) {
   GetAddrInfo(*loop, req, node, service, hints);
 }
@@ -84,7 +80,7 @@
  * completes, and errors are forwarded to the loop.  This is a convenience
  * wrapper.
  *
- * Either node or service may be null (`Twine::createNull()`) but not both.
+ * Either node or service may be empty but not both.
  *
  * @param loop Event loop
  * @param callback Callback function to call when resolution completes
@@ -94,7 +90,7 @@
  *              type constraints.
  */
 void GetAddrInfo(Loop& loop, std::function<void(const addrinfo&)> callback,
-                 const Twine& node, const Twine& service = Twine::createNull(),
+                 std::string_view node, std::string_view service = {},
                  const addrinfo* hints = nullptr);
 
 /**
@@ -102,7 +98,7 @@
  * completes, and errors are forwarded to the loop.  This is a convenience
  * wrapper.
  *
- * Either node or service may be null (`Twine::createNull()`) but not both.
+ * Either node or service may be empty but not both.
  *
  * @param loop Event loop
  * @param callback Callback function to call when resolution completes
@@ -113,13 +109,11 @@
  */
 inline void GetAddrInfo(const std::shared_ptr<Loop>& loop,
                         std::function<void(const addrinfo&)> callback,
-                        const Twine& node,
-                        const Twine& service = Twine::createNull(),
+                        std::string_view node, std::string_view service = {},
                         const addrinfo* hints = nullptr) {
-  GetAddrInfo(*loop, callback, node, service, hints);
+  GetAddrInfo(*loop, std::move(callback), node, service, hints);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_GETADDRINFO_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/GetNameInfo.h b/wpiutil/src/main/native/include/wpi/uv/GetNameInfo.h
index d6f4d62..d35dd95 100644
--- a/wpiutil/src/main/native/include/wpi/uv/GetNameInfo.h
+++ b/wpiutil/src/main/native/include/wpi/uv/GetNameInfo.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_GETNAMEINFO_H_
 #define WPIUTIL_WPI_UV_GETNAMEINFO_H_
@@ -12,13 +9,13 @@
 
 #include <functional>
 #include <memory>
+#include <string_view>
+#include <utility>
 
 #include "wpi/Signal.h"
-#include "wpi/Twine.h"
 #include "wpi/uv/Request.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -94,7 +91,7 @@
 inline void GetNameInfo(const std::shared_ptr<Loop>& loop,
                         std::function<void(const char*, const char*)> callback,
                         const sockaddr& addr, int flags = 0) {
-  GetNameInfo(*loop, callback, addr, flags);
+  GetNameInfo(*loop, std::move(callback), addr, flags);
 }
 
 /**
@@ -109,7 +106,7 @@
  * @param flags Optional flags to modify the behavior of `getnameinfo`.
  */
 void GetNameInfo4(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
-                  const Twine& ip, unsigned int port, int flags = 0);
+                  std::string_view ip, unsigned int port, int flags = 0);
 
 /**
  * Asynchronous IPv4 getnameinfo(3).  HandleResolvedName() is called on the
@@ -124,7 +121,8 @@
  */
 inline void GetNameInfo4(const std::shared_ptr<Loop>& loop,
                          const std::shared_ptr<GetNameInfoReq>& req,
-                         const Twine& ip, unsigned int port, int flags = 0) {
+                         std::string_view ip, unsigned int port,
+                         int flags = 0) {
   return GetNameInfo4(*loop, req, ip, port, flags);
 }
 
@@ -140,7 +138,7 @@
  */
 void GetNameInfo4(Loop& loop,
                   std::function<void(const char*, const char*)> callback,
-                  const Twine& ip, unsigned int port, int flags = 0);
+                  std::string_view ip, unsigned int port, int flags = 0);
 
 /**
  * Asynchronous IPv4 getnameinfo(3).  The callback is called when the resolution
@@ -155,8 +153,9 @@
  */
 inline void GetNameInfo4(const std::shared_ptr<Loop>& loop,
                          std::function<void(const char*, const char*)> callback,
-                         const Twine& ip, unsigned int port, int flags = 0) {
-  return GetNameInfo4(*loop, callback, ip, port, flags);
+                         std::string_view ip, unsigned int port,
+                         int flags = 0) {
+  return GetNameInfo4(*loop, std::move(callback), ip, port, flags);
 }
 
 /**
@@ -171,7 +170,7 @@
  * @param flags Optional flags to modify the behavior of `getnameinfo`.
  */
 void GetNameInfo6(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
-                  const Twine& ip, unsigned int port, int flags = 0);
+                  std::string_view ip, unsigned int port, int flags = 0);
 
 /**
  * Asynchronous IPv6 getnameinfo(3).  HandleResolvedName() is called on the
@@ -186,7 +185,8 @@
  */
 inline void GetNameInfo6(const std::shared_ptr<Loop>& loop,
                          const std::shared_ptr<GetNameInfoReq>& req,
-                         const Twine& ip, unsigned int port, int flags = 0) {
+                         std::string_view ip, unsigned int port,
+                         int flags = 0) {
   GetNameInfo6(*loop, req, ip, port, flags);
 }
 
@@ -203,7 +203,7 @@
  */
 void GetNameInfo6(Loop& loop,
                   std::function<void(const char*, const char*)> callback,
-                  const Twine& ip, unsigned int port, int flags = 0);
+                  std::string_view ip, unsigned int port, int flags = 0);
 
 /**
  * Asynchronous IPv6 getnameinfo(3).  The callback is called when the resolution
@@ -218,11 +218,11 @@
  */
 inline void GetNameInfo6(const std::shared_ptr<Loop>& loop,
                          std::function<void(const char*, const char*)> callback,
-                         const Twine& ip, unsigned int port, int flags = 0) {
-  return GetNameInfo6(*loop, callback, ip, port, flags);
+                         std::string_view ip, unsigned int port,
+                         int flags = 0) {
+  return GetNameInfo6(*loop, std::move(callback), ip, port, flags);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_GETNAMEINFO_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Handle.h b/wpiutil/src/main/native/include/wpi/uv/Handle.h
index 9d91d37..b61cd81 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Handle.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Handle.h
@@ -1,27 +1,24 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_HANDLE_H_
 #define WPIUTIL_WPI_UV_HANDLE_H_
 
 #include <uv.h>
 
+#include <cstdlib>
 #include <functional>
 #include <memory>
+#include <string_view>
 #include <utility>
 
 #include "wpi/Signal.h"
-#include "wpi/StringRef.h"
 #include "wpi/uv/Buffer.h"
 #include "wpi/uv/Error.h"
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 /**
  * Handle.
@@ -53,7 +50,7 @@
   /**
    * Get the name of the type of the handle.  E.g. "pipe" for pipe handles.
    */
-  StringRef GetTypeName() const noexcept {
+  std::string_view GetTypeName() const noexcept {
     return uv_handle_type_name(m_uv_handle->type);
   }
 
@@ -194,8 +191,8 @@
    */
   void SetBufferAllocator(std::function<Buffer(size_t)> alloc,
                           std::function<void(Buffer&)> dealloc) {
-    m_allocBuf = alloc;
-    m_freeBuf = dealloc;
+    m_allocBuf = std::move(alloc);
+    m_freeBuf = std::move(dealloc);
   }
 
   /**
@@ -252,7 +249,9 @@
   template <typename F, typename... Args>
   bool Invoke(F&& f, Args&&... args) const {
     auto err = std::forward<F>(f)(std::forward<Args>(args)...);
-    if (err < 0) ReportError(err);
+    if (err < 0) {
+      ReportError(err);
+    }
     return err == 0;
   }
 
@@ -290,10 +289,9 @@
   }
 
  protected:
-  HandleImpl() : Handle{reinterpret_cast<uv_handle_t*>(new U)} {}
+  HandleImpl() : Handle{static_cast<uv_handle_t*>(std::malloc(sizeof(U)))} {}
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_HANDLE_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Idle.h b/wpiutil/src/main/native/include/wpi/uv/Idle.h
index e8278b2..869d43d 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Idle.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Idle.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_IDLE_H_
 #define WPIUTIL_WPI_UV_IDLE_H_
@@ -15,8 +12,7 @@
 #include "wpi/Signal.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -73,7 +69,6 @@
   sig::Signal<> idle;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_IDLE_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Loop.h b/wpiutil/src/main/native/include/wpi/uv/Loop.h
index 2b3b27f..9693053 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Loop.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Loop.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_LOOP_H_
 #define WPIUTIL_WPI_UV_LOOP_H_
@@ -18,10 +15,10 @@
 #include <utility>
 
 #include "wpi/Signal.h"
+#include "wpi/function_ref.h"
 #include "wpi/uv/Error.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Handle;
 
@@ -175,7 +172,7 @@
    *
    * @param callback A function to be invoked once for each active handle.
    */
-  void Walk(std::function<void(Handle&)> callback);
+  void Walk(function_ref<void(Handle&)> callback);
 
   /**
    * Reinitialize any kernel state necessary in the child process after
@@ -251,7 +248,6 @@
   std::atomic<std::thread::id> m_tid;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_LOOP_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/NetworkStream.h b/wpiutil/src/main/native/include/wpi/uv/NetworkStream.h
index 05c1c92..faac9fe 100644
--- a/wpiutil/src/main/native/include/wpi/uv/NetworkStream.h
+++ b/wpiutil/src/main/native/include/wpi/uv/NetworkStream.h
@@ -1,23 +1,20 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_NETWORKSTREAM_H_
 #define WPIUTIL_WPI_UV_NETWORKSTREAM_H_
 
 #include <uv.h>
 
+#include <cstdlib>
 #include <functional>
 #include <memory>
 
 #include "wpi/Signal.h"
 #include "wpi/uv/Stream.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class NetworkStream;
 
@@ -147,10 +144,10 @@
   }
 
  protected:
-  NetworkStreamImpl() : NetworkStream{reinterpret_cast<uv_stream_t*>(new U)} {}
+  NetworkStreamImpl()
+      : NetworkStream{static_cast<uv_stream_t*>(std::malloc(sizeof(U)))} {}
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_NETWORKSTREAM_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Pipe.h b/wpiutil/src/main/native/include/wpi/uv/Pipe.h
index 72fc23b..e223268 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Pipe.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Pipe.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_PIPE_H_
 #define WPIUTIL_WPI_UV_PIPE_H_
@@ -13,12 +10,11 @@
 #include <functional>
 #include <memory>
 #include <string>
+#include <string_view>
 
-#include "wpi/Twine.h"
 #include "wpi/uv/NetworkStream.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 class PipeConnectReq;
@@ -123,7 +119,7 @@
    *
    * @param name File path (Unix) or name (Windows).
    */
-  void Bind(const Twine& name);
+  void Bind(std::string_view name);
 
   /**
    * Connect to the Unix domain socket or the named pipe.
@@ -139,7 +135,8 @@
    * @param name File path (Unix) or name (Windows).
    * @param req connection request
    */
-  void Connect(const Twine& name, const std::shared_ptr<PipeConnectReq>& req);
+  void Connect(std::string_view name,
+               const std::shared_ptr<PipeConnectReq>& req);
 
   /**
    * Connect to the Unix domain socket or the named pipe.
@@ -153,7 +150,7 @@
    * @param name File path (Unix) or name (Windows).
    * @param callback Callback function to call when connection established
    */
-  void Connect(const Twine& name, std::function<void()> callback);
+  void Connect(std::string_view name, std::function<void()> callback);
 
   /**
    * Get the name of the Unix domain socket or the named pipe.
@@ -206,7 +203,6 @@
   }
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_PIPE_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Poll.h b/wpiutil/src/main/native/include/wpi/uv/Poll.h
index fad00e4..6e1e26d 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Poll.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Poll.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_POLL_H_
 #define WPIUTIL_WPI_UV_POLL_H_
@@ -15,8 +12,7 @@
 #include "wpi/Signal.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -120,7 +116,6 @@
   std::unique_ptr<ReuseData> m_reuseData;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_POLL_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Prepare.h b/wpiutil/src/main/native/include/wpi/uv/Prepare.h
index 600922f..88ae2d9 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Prepare.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Prepare.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_PREPARE_H_
 #define WPIUTIL_WPI_UV_PREPARE_H_
@@ -15,8 +12,7 @@
 #include "wpi/Signal.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -64,7 +60,6 @@
   sig::Signal<> prepare;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_PREPARE_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Process.h b/wpiutil/src/main/native/include/wpi/uv/Process.h
index 47d09df..fc2315e 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Process.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Process.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_PROCESS_H_
 #define WPIUTIL_WPI_UV_PROCESS_H_
@@ -13,15 +10,14 @@
 #include <initializer_list>
 #include <memory>
 #include <string>
+#include <string_view>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/Signal.h"
 #include "wpi/SmallVector.h"
-#include "wpi/Twine.h"
+#include "wpi/span.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 class Pipe;
@@ -39,9 +35,9 @@
   ~Process() noexcept override = default;
 
   /**
-   * Structure for Spawn() option temporaries.  This is a reference type
-   * similar to StringRef, so if this value is stored outside of a temporary,
-   * be careful about overwriting what it points to.
+   * Structure for Spawn() option temporaries.  This is a reference type, so if
+   * this value is stored outside of a temporary, be careful about overwriting
+   * what it points to.
    */
   struct Option {
     enum Type {
@@ -61,30 +57,24 @@
 
     Option() : m_type(kNone) {}
 
-    /*implicit*/ Option(const char* arg) {  // NOLINT(runtime/explicit)
+    /*implicit*/ Option(const char* arg) {  // NOLINT
       m_data.str = arg;
     }
 
-    /*implicit*/ Option(const std::string& arg) {  // NOLINT(runtime/explicit)
+    /*implicit*/ Option(const std::string& arg) {  // NOLINT
       m_data.str = arg.data();
     }
 
-    /*implicit*/ Option(StringRef arg)  // NOLINT(runtime/explicit)
+    /*implicit*/ Option(std::string_view arg)  // NOLINT
         : m_strData(arg) {
       m_data.str = m_strData.c_str();
     }
 
-    /*implicit*/ Option(
-        const SmallVectorImpl<char>& arg)  // NOLINT(runtime/explicit)
+    /*implicit*/ Option(const SmallVectorImpl<char>& arg)  // NOLINT
         : m_strData(arg.data(), arg.size()) {
       m_data.str = m_strData.c_str();
     }
 
-    /*implicit*/ Option(const Twine& arg)  // NOLINT(runtime/explicit)
-        : m_strData(arg.str()) {
-      m_data.str = m_strData.c_str();
-    }
-
     explicit Option(Type type) : m_type(type) {}
 
     Type m_type = kArg;
@@ -110,9 +100,9 @@
    * environment is used.
    * @param env environment variable
    */
-  static Option Env(const Twine& env) {
+  static Option Env(std::string_view env) {
     Option o(Option::kEnv);
-    o.m_strData = env.str();
+    o.m_strData = env;
     o.m_data.str = o.m_strData.c_str();
     return o;
   }
@@ -121,9 +111,9 @@
    * Set the current working directory for the subprocess.
    * @param cwd current working directory
    */
-  static Option Cwd(const Twine& cwd) {
+  static Option Cwd(std::string_view cwd) {
     Option o(Option::kCwd);
-    o.m_strData = cwd.str();
+    o.m_strData = cwd;
     o.m_data.str = o.m_strData.c_str();
     return o;
   }
@@ -241,16 +231,17 @@
    * @param file Path pointing to the program to be executed
    * @param options Process options
    */
-  static std::shared_ptr<Process> SpawnArray(Loop& loop, const Twine& file,
-                                             ArrayRef<Option> options);
+  static std::shared_ptr<Process> SpawnArray(Loop& loop, std::string_view file,
+                                             span<const Option> options);
 
   static std::shared_ptr<Process> SpawnArray(
-      Loop& loop, const Twine& file, std::initializer_list<Option> options) {
-    return SpawnArray(loop, file, makeArrayRef(options.begin(), options.end()));
+      Loop& loop, std::string_view file,
+      std::initializer_list<Option> options) {
+    return SpawnArray(loop, file, {options.begin(), options.end()});
   }
 
   template <typename... Args>
-  static std::shared_ptr<Process> Spawn(Loop& loop, const Twine& file,
+  static std::shared_ptr<Process> Spawn(Loop& loop, std::string_view file,
                                         const Args&... options) {
     return SpawnArray(loop, file, {options...});
   }
@@ -269,20 +260,20 @@
    * @param options Process options
    */
   static std::shared_ptr<Process> SpawnArray(const std::shared_ptr<Loop>& loop,
-                                             const Twine& file,
-                                             ArrayRef<Option> options) {
+                                             std::string_view file,
+                                             span<const Option> options) {
     return SpawnArray(*loop, file, options);
   }
 
   static std::shared_ptr<Process> SpawnArray(
-      const std::shared_ptr<Loop>& loop, const Twine& file,
+      const std::shared_ptr<Loop>& loop, std::string_view file,
       std::initializer_list<Option> options) {
     return SpawnArray(*loop, file, options);
   }
 
   template <typename... Args>
   static std::shared_ptr<Process> Spawn(const std::shared_ptr<Loop>& loop,
-                                        const Twine& file,
+                                        std::string_view file,
                                         const Args&... options) {
     return SpawnArray(*loop, file, {options...});
   }
@@ -314,7 +305,6 @@
   sig::Signal<int64_t, int> exited;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_PROCESS_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Request.h b/wpiutil/src/main/native/include/wpi/uv/Request.h
index c33ca83..3f6a19d 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Request.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Request.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_REQUEST_H_
 #define WPIUTIL_WPI_UV_REQUEST_H_
@@ -15,8 +12,7 @@
 
 #include "wpi/uv/Error.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 /**
  * Request.  Requests are not moveable or copyable.
@@ -165,7 +161,6 @@
   U m_uv_req;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_REQUEST_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Signal.h b/wpiutil/src/main/native/include/wpi/uv/Signal.h
index 5fe34c6..9abcad3 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Signal.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Signal.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_SIGNAL_H_
 #define WPIUTIL_WPI_UV_SIGNAL_H_
@@ -15,8 +12,7 @@
 #include "wpi/Signal.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -78,7 +74,6 @@
   sig::Signal<int> signal;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_SIGNAL_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Stream.h b/wpiutil/src/main/native/include/wpi/uv/Stream.h
index be8120d..0ade972 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Stream.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Stream.h
@@ -1,27 +1,25 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_STREAM_H_
 #define WPIUTIL_WPI_UV_STREAM_H_
 
 #include <uv.h>
 
+#include <cstdlib>
 #include <functional>
 #include <initializer_list>
 #include <memory>
+#include <utility>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/Signal.h"
+#include "wpi/span.h"
 #include "wpi/uv/Buffer.h"
 #include "wpi/uv/Handle.h"
 #include "wpi/uv/Request.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Stream;
 
@@ -129,7 +127,7 @@
    * @param bufs The buffers to be written to the stream.
    * @param req write request
    */
-  void Write(ArrayRef<Buffer> bufs, const std::shared_ptr<WriteReq>& req);
+  void Write(span<const Buffer> bufs, const std::shared_ptr<WriteReq>& req);
 
   /**
    * Write data to the stream.
@@ -149,7 +147,7 @@
    */
   void Write(std::initializer_list<Buffer> bufs,
              const std::shared_ptr<WriteReq>& req) {
-    Write(makeArrayRef(bufs.begin(), bufs.end()), req);
+    Write({bufs.begin(), bufs.end()}, req);
   }
 
   /**
@@ -165,8 +163,8 @@
    * @param bufs The buffers to be written to the stream.
    * @param callback Callback function to call when the write completes
    */
-  void Write(ArrayRef<Buffer> bufs,
-             std::function<void(MutableArrayRef<Buffer>, Error)> callback);
+  void Write(span<const Buffer> bufs,
+             std::function<void(span<Buffer>, Error)> callback);
 
   /**
    * Write data to the stream.
@@ -182,8 +180,8 @@
    * @param callback Callback function to call when the write completes
    */
   void Write(std::initializer_list<Buffer> bufs,
-             std::function<void(MutableArrayRef<Buffer>, Error)> callback) {
-    Write(makeArrayRef(bufs.begin(), bufs.end()), callback);
+             std::function<void(span<Buffer>, Error)> callback) {
+    Write({bufs.begin(), bufs.end()}, std::move(callback));
   }
 
   /**
@@ -196,7 +194,7 @@
    * @param bufs The buffers to be written to the stream.
    * @return Number of bytes written.
    */
-  int TryWrite(ArrayRef<Buffer> bufs);
+  int TryWrite(span<const Buffer> bufs);
 
   /**
    * Queue a write request if it can be completed immediately.
@@ -209,7 +207,7 @@
    * @return Number of bytes written.
    */
   int TryWrite(std::initializer_list<Buffer> bufs) {
-    return TryWrite(makeArrayRef(bufs.begin(), bufs.end()));
+    return TryWrite({bufs.begin(), bufs.end()});
   }
 
   /**
@@ -296,10 +294,9 @@
   }
 
  protected:
-  StreamImpl() : Stream{reinterpret_cast<uv_stream_t*>(new U)} {}
+  StreamImpl() : Stream{static_cast<uv_stream_t*>(std::malloc(sizeof(U)))} {}
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_STREAM_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Tcp.h b/wpiutil/src/main/native/include/wpi/uv/Tcp.h
index 56c2b8f..c712f0b 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Tcp.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Tcp.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_TCP_H_
 #define WPIUTIL_WPI_UV_TCP_H_
@@ -13,12 +10,12 @@
 #include <chrono>
 #include <functional>
 #include <memory>
+#include <string_view>
+#include <utility>
 
-#include "wpi/Twine.h"
 #include "wpi/uv/NetworkStream.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 class TcpConnectReq;
@@ -116,7 +113,7 @@
   void Open(uv_os_sock_t sock) { Invoke(&uv_tcp_open, GetRaw(), sock); }
 
   /**
-   * Enable/Disable Nagle's algorithm.
+   * Enable no delay operation (turns off Nagle's algorithm).
    * @param enable True to enable it, false otherwise.
    * @return True in case of success, false otherwise.
    */
@@ -189,7 +186,7 @@
    * @param port The port to which to bind.
    * @param flags Optional additional flags.
    */
-  void Bind(const Twine& ip, unsigned int port, unsigned int flags = 0);
+  void Bind(std::string_view ip, unsigned int port, unsigned int flags = 0);
 
   /**
    * Bind the handle to an IPv6 address and port.
@@ -205,7 +202,7 @@
    * @param port The port to which to bind.
    * @param flags Optional additional flags.
    */
-  void Bind6(const Twine& ip, unsigned int port, unsigned int flags = 0);
+  void Bind6(std::string_view ip, unsigned int port, unsigned int flags = 0);
 
   /**
    * Get the current address to which the handle is bound.
@@ -262,11 +259,11 @@
   void Connect(const sockaddr& addr, std::function<void()> callback);
 
   void Connect(const sockaddr_in& addr, std::function<void()> callback) {
-    Connect(reinterpret_cast<const sockaddr&>(addr), callback);
+    Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
   }
 
   void Connect(const sockaddr_in6& addr, std::function<void()> callback) {
-    Connect(reinterpret_cast<const sockaddr&>(addr), callback);
+    Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
   }
 
   /**
@@ -285,7 +282,7 @@
    * @param port The port to which to connect to.
    * @param req connection request
    */
-  void Connect(const Twine& ip, unsigned int port,
+  void Connect(std::string_view ip, unsigned int port,
                const std::shared_ptr<TcpConnectReq>& req);
 
   /**
@@ -302,7 +299,7 @@
    * @param port The port to which to connect to.
    * @param callback Callback function to call when connection established
    */
-  void Connect(const Twine& ip, unsigned int port,
+  void Connect(std::string_view ip, unsigned int port,
                std::function<void()> callback);
 
   /**
@@ -321,7 +318,7 @@
    * @param port The port to which to connect to.
    * @param req connection request
    */
-  void Connect6(const Twine& ip, unsigned int port,
+  void Connect6(std::string_view ip, unsigned int port,
                 const std::shared_ptr<TcpConnectReq>& req);
 
   /**
@@ -338,7 +335,7 @@
    * @param port The port to which to connect to.
    * @param callback Callback function to call when connection established
    */
-  void Connect6(const Twine& ip, unsigned int port,
+  void Connect6(std::string_view ip, unsigned int port,
                 std::function<void()> callback);
 
  private:
@@ -361,7 +358,6 @@
   }
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_TCP_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Timer.h b/wpiutil/src/main/native/include/wpi/uv/Timer.h
index bdafee3..00363a5 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Timer.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Timer.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_TIMER_H_
 #define WPIUTIL_WPI_UV_TIMER_H_
@@ -13,12 +10,12 @@
 #include <chrono>
 #include <functional>
 #include <memory>
+#include <utility>
 
 #include "wpi/Signal.h"
 #include "wpi/uv/Handle.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -69,7 +66,7 @@
    */
   static void SingleShot(const std::shared_ptr<Loop>& loop, Time timeout,
                          std::function<void()> func) {
-    return SingleShot(*loop, timeout, func);
+    return SingleShot(*loop, timeout, std::move(func));
   }
 
   /**
@@ -133,7 +130,6 @@
   sig::Signal<> timeout;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_TIMER_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Tty.h b/wpiutil/src/main/native/include/wpi/uv/Tty.h
index a15d8eb..bb0a6db 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Tty.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Tty.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_TTY_H_
 #define WPIUTIL_WPI_UV_TTY_H_
@@ -15,8 +12,7 @@
 
 #include "wpi/uv/Stream.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 class Tty;
@@ -62,7 +58,9 @@
    */
   void SetMode(uv_tty_mode_t mode) {
     int err = uv_tty_set_mode(GetRaw(), mode);
-    if (err < 0) ReportError(err);
+    if (err < 0) {
+      ReportError(err);
+    }
   }
 
   /**
@@ -82,7 +80,6 @@
   }
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_TTY_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Udp.h b/wpiutil/src/main/native/include/wpi/uv/Udp.h
index 2544c50..71fab13 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Udp.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Udp.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_UDP_H_
 #define WPIUTIL_WPI_UV_UDP_H_
@@ -12,15 +9,15 @@
 
 #include <functional>
 #include <memory>
+#include <string_view>
+#include <utility>
 
-#include "wpi/ArrayRef.h"
 #include "wpi/Signal.h"
-#include "wpi/Twine.h"
+#include "wpi/span.h"
 #include "wpi/uv/Handle.h"
 #include "wpi/uv/Request.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 class Udp;
@@ -104,7 +101,7 @@
    * @param port The port to which to bind.
    * @param flags Optional additional flags.
    */
-  void Bind(const Twine& ip, unsigned int port, unsigned int flags = 0);
+  void Bind(std::string_view ip, unsigned int port, unsigned int flags = 0);
 
   /**
    * Bind the handle to an IPv6 address and port.
@@ -113,7 +110,7 @@
    * @param port The port to which to bind.
    * @param flags Optional additional flags.
    */
-  void Bind6(const Twine& ip, unsigned int port, unsigned int flags = 0);
+  void Bind6(std::string_view ip, unsigned int port, unsigned int flags = 0);
 
   /**
    * Associate the handle to a remote address and port, so every message sent
@@ -140,7 +137,7 @@
    * @param ip The address to which to bind.
    * @param port The port to which to bind.
    */
-  void Connect(const Twine& ip, unsigned int port);
+  void Connect(std::string_view ip, unsigned int port);
 
   /**
    * Associate the handle to an IPv6 address and port, so every message sent
@@ -150,7 +147,7 @@
    * @param port The port to which to bind.
    * @param flags Optional additional flags.
    */
-  void Connect6(const Twine& ip, unsigned int port);
+  void Connect6(std::string_view ip, unsigned int port);
 
   /**
    * Get the remote IP and port on connected UDP handles.
@@ -171,8 +168,8 @@
    * @param interfaceAddr Interface address
    * @param membership Should be UV_JOIN_GROUP or UV_LEAVE_GROUP
    */
-  void SetMembership(const Twine& multicastAddr, const Twine& interfaceAddr,
-                     uv_membership membership);
+  void SetMembership(std::string_view multicastAddr,
+                     std::string_view interfaceAddr, uv_membership membership);
 
   /**
    * Set IP multicast loop flag.  Makes multicast packets loop back to local
@@ -198,7 +195,7 @@
    *
    * @param interfaceAddr Interface address
    */
-  void SetMulticastInterface(const Twine& interfaceAddr);
+  void SetMulticastInterface(std::string_view interfaceAddr);
 
   /**
    * Set broadcast on or off.
@@ -234,15 +231,15 @@
    * @param bufs The buffers to be written to the stream.
    * @param req write request
    */
-  void Send(const sockaddr& addr, ArrayRef<Buffer> bufs,
+  void Send(const sockaddr& addr, span<const Buffer> bufs,
             const std::shared_ptr<UdpSendReq>& req);
 
-  void Send(const sockaddr_in& addr, ArrayRef<Buffer> bufs,
+  void Send(const sockaddr_in& addr, span<const Buffer> bufs,
             const std::shared_ptr<UdpSendReq>& req) {
     Send(reinterpret_cast<const sockaddr&>(addr), bufs, req);
   }
 
-  void Send(const sockaddr_in6& addr, ArrayRef<Buffer> bufs,
+  void Send(const sockaddr_in6& addr, span<const Buffer> bufs,
             const std::shared_ptr<UdpSendReq>& req) {
     Send(reinterpret_cast<const sockaddr&>(addr), bufs, req);
   }
@@ -254,7 +251,7 @@
    * @param bufs The buffers to be written to the stream.
    * @param req write request
    */
-  void Send(ArrayRef<Buffer> bufs, const std::shared_ptr<UdpSendReq>& req);
+  void Send(span<const Buffer> bufs, const std::shared_ptr<UdpSendReq>& req);
 
   /**
    * Send data over the UDP socket.  If the socket has not previously been bound
@@ -273,17 +270,17 @@
    * @param bufs The buffers to be sent.
    * @param callback Callback function to call when the data has been sent.
    */
-  void Send(const sockaddr& addr, ArrayRef<Buffer> bufs,
-            std::function<void(MutableArrayRef<Buffer>, Error)> callback);
+  void Send(const sockaddr& addr, span<const Buffer> bufs,
+            std::function<void(span<Buffer>, Error)> callback);
 
-  void Send(const sockaddr_in& addr, ArrayRef<Buffer> bufs,
-            std::function<void(MutableArrayRef<Buffer>, Error)> callback) {
-    Send(reinterpret_cast<const sockaddr&>(addr), bufs, callback);
+  void Send(const sockaddr_in& addr, span<const Buffer> bufs,
+            std::function<void(span<Buffer>, Error)> callback) {
+    Send(reinterpret_cast<const sockaddr&>(addr), bufs, std::move(callback));
   }
 
-  void Send(const sockaddr_in6& addr, ArrayRef<Buffer> bufs,
-            std::function<void(MutableArrayRef<Buffer>, Error)> callback) {
-    Send(reinterpret_cast<const sockaddr&>(addr), bufs, callback);
+  void Send(const sockaddr_in6& addr, span<const Buffer> bufs,
+            std::function<void(span<Buffer>, Error)> callback) {
+    Send(reinterpret_cast<const sockaddr&>(addr), bufs, std::move(callback));
   }
 
   /**
@@ -293,8 +290,8 @@
    * @param bufs The buffers to be written to the stream.
    * @param callback Callback function to call when the data has been sent.
    */
-  void Send(ArrayRef<Buffer> bufs,
-            std::function<void(MutableArrayRef<Buffer>, Error)> callback);
+  void Send(span<const Buffer> bufs,
+            std::function<void(span<Buffer>, Error)> callback);
 
   /**
    * Same as Send(), but won't queue a send request if it can't be completed
@@ -305,7 +302,7 @@
    * @param bufs The buffers to be send.
    * @return Number of bytes sent.
    */
-  int TrySend(const sockaddr& addr, ArrayRef<Buffer> bufs) {
+  int TrySend(const sockaddr& addr, span<const Buffer> bufs) {
     int val = uv_udp_try_send(GetRaw(), bufs.data(),
                               static_cast<unsigned>(bufs.size()), &addr);
     if (val < 0) {
@@ -315,11 +312,11 @@
     return val;
   }
 
-  int TrySend(const sockaddr_in& addr, ArrayRef<Buffer> bufs) {
+  int TrySend(const sockaddr_in& addr, span<const Buffer> bufs) {
     return TrySend(reinterpret_cast<const sockaddr&>(addr), bufs);
   }
 
-  int TrySend(const sockaddr_in6& addr, ArrayRef<Buffer> bufs) {
+  int TrySend(const sockaddr_in6& addr, span<const Buffer> bufs) {
     return TrySend(reinterpret_cast<const sockaddr&>(addr), bufs);
   }
 
@@ -330,7 +327,7 @@
    * @param bufs The buffers to be written to the stream.
    * @return Number of bytes sent.
    */
-  int TrySend(ArrayRef<Buffer> bufs) {
+  int TrySend(span<const Buffer> bufs) {
     int val = uv_udp_try_send(GetRaw(), bufs.data(),
                               static_cast<unsigned>(bufs.size()), nullptr);
     if (val < 0) {
@@ -376,7 +373,6 @@
   sig::Signal<Buffer&, size_t, const sockaddr&, unsigned> received;
 };
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_UDP_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/Work.h b/wpiutil/src/main/native/include/wpi/uv/Work.h
index bf061fb..5a3a5d2 100644
--- a/wpiutil/src/main/native/include/wpi/uv/Work.h
+++ b/wpiutil/src/main/native/include/wpi/uv/Work.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_WORK_H_
 #define WPIUTIL_WPI_UV_WORK_H_
@@ -12,12 +9,12 @@
 
 #include <functional>
 #include <memory>
+#include <utility>
 
 #include "wpi/Signal.h"
 #include "wpi/uv/Request.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 class Loop;
 
@@ -88,10 +85,9 @@
 inline void QueueWork(const std::shared_ptr<Loop>& loop,
                       std::function<void()> work,
                       std::function<void()> afterWork) {
-  QueueWork(*loop, work, afterWork);
+  QueueWork(*loop, std::move(work), std::move(afterWork));
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_WORK_H_
diff --git a/wpiutil/src/main/native/include/wpi/uv/util.h b/wpiutil/src/main/native/include/wpi/uv/util.h
index 14a021d..01dec0b 100644
--- a/wpiutil/src/main/native/include/wpi/uv/util.h
+++ b/wpiutil/src/main/native/include/wpi/uv/util.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifndef WPIUTIL_WPI_UV_UTIL_H_
 #define WPIUTIL_WPI_UV_UTIL_H_
@@ -11,11 +8,9 @@
 #include <uv.h>
 
 #include <cstring>
+#include <string_view>
 
-#include "wpi/Twine.h"
-
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
 /**
  * Convert a binary structure containing an IPv4 address to a string.
@@ -66,10 +61,12 @@
  */
 template <typename T>
 int AddrToName(const sockaddr_storage& addr, T* ip, unsigned int* port) {
-  if (addr.ss_family == AF_INET)
+  if (addr.ss_family == AF_INET) {
     return AddrToName(reinterpret_cast<const sockaddr_in&>(addr), ip, port);
-  if (addr.ss_family == AF_INET6)
+  }
+  if (addr.ss_family == AF_INET6) {
     return AddrToName(reinterpret_cast<const sockaddr_in6&>(addr), ip, port);
+  }
   char name[1];
   ip->assign(name, name);
   return -1;
@@ -85,10 +82,11 @@
 int AddrToName(const in_addr& addr, T* ip) {
   char name[128];
   int err = uv_inet_ntop(AF_INET, &addr, name, 128);
-  if (err == 0)
+  if (err == 0) {
     ip->assign(name, name + std::strlen(name));
-  else
+  } else {
     ip->assign(name, name);
+  }
   return err;
 }
 
@@ -102,10 +100,11 @@
 int AddrToName(const in6_addr& addr, T* ip) {
   char name[128];
   int err = uv_inet_ntop(AF_INET6, &addr, name, 128);
-  if (err == 0)
+  if (err == 0) {
     ip->assign(name, name + std::strlen(name));
-  else
+  } else {
     ip->assign(name, name);
+  }
   return err;
 }
 
@@ -116,7 +115,7 @@
  * @param addr Output binary structure
  * @return Error (same as `uv_ip4_addr()`).
  */
-int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in* addr);
+int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in* addr);
 
 /**
  * Convert a string containing an IPv6 address to a binary structure.
@@ -125,7 +124,7 @@
  * @param addr Output binary structure
  * @return Error (same as `uv_ip6_addr()`).
  */
-int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in6* addr);
+int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in6* addr);
 
 /**
  * Convert a string containing an IPv4 address to binary format.
@@ -133,7 +132,7 @@
  * @param addr Output binary
  * @return Error (same as `uv_inet_pton()`).
  */
-int NameToAddr(const Twine& ip, in_addr* addr);
+int NameToAddr(std::string_view ip, in_addr* addr);
 
 /**
  * Convert a string containing an IPv6 address to binary format.
@@ -141,9 +140,8 @@
  * @param addr Output binary
  * @return Error (same as `uv_inet_pton()`).
  */
-int NameToAddr(const Twine& ip, in6_addr* addr);
+int NameToAddr(std::string_view ip, in6_addr* addr);
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
 
 #endif  // WPIUTIL_WPI_UV_UTIL_H_
diff --git a/wpiutil/src/main/native/unix/Demangle.cpp b/wpiutil/src/main/native/unix/Demangle.cpp
index 3453084..69404ed 100644
--- a/wpiutil/src/main/native/unix/Demangle.cpp
+++ b/wpiutil/src/main/native/unix/Demangle.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2008-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/Demangle.h"
 
@@ -15,14 +12,12 @@
 
 namespace wpi {
 
-std::string Demangle(const Twine& mangledSymbol) {
-  SmallString<128> buf;
+std::string Demangle(std::string_view mangledSymbol) {
+  SmallString<128> buf{mangledSymbol};
   size_t length;
   int32_t status;
 
-  char* symbol =
-      abi::__cxa_demangle(mangledSymbol.toNullTerminatedStringRef(buf).data(),
-                          nullptr, &length, &status);
+  char* symbol = abi::__cxa_demangle(buf.c_str(), nullptr, &length, &status);
   if (status == 0) {
     std::string rv{symbol};
     std::free(symbol);
@@ -30,7 +25,7 @@
   }
 
   // If everything else failed, just return the mangled symbol
-  return mangledSymbol.str();
+  return std::string{mangledSymbol};
 }
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/unix/StackTrace.cpp b/wpiutil/src/main/native/unix/StackTrace.cpp
index 004eb39..598c9da 100644
--- a/wpiutil/src/main/native/unix/StackTrace.cpp
+++ b/wpiutil/src/main/native/unix/StackTrace.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2008-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/StackTrace.h"
 
@@ -11,7 +8,7 @@
 
 #include "wpi/Demangle.h"
 #include "wpi/SmallString.h"
-#include "wpi/StringRef.h"
+#include "wpi/StringExtras.h"
 #include "wpi/raw_ostream.h"
 
 namespace wpi {
@@ -27,16 +24,18 @@
     // Only print recursive functions once in a row.
     if (i == 0 || stackTrace[i] != stackTrace[i - 1]) {
       // extract just function name from "pathToExe(functionName+offset)"
-      StringRef sym{mangledSymbols[i]};
-      sym = sym.split('(').second;
-      sym = sym.split('+').first;
-      trace << "\tat " << Demangle(sym) << "\n";
+      std::string_view sym = split(mangledSymbols[i], '(').second;
+      std::string_view offset;
+      std::tie(sym, offset) = split(sym, '+');
+      std::string_view addr;
+      std::tie(offset, addr) = split(offset, ')');
+      trace << "\tat " << Demangle(sym) << " + " << offset << addr << "\n";
     }
   }
 
   std::free(mangledSymbols);
 
-  return trace.str();
+  return std::string{trace.str()};
 }
 
 }  // namespace wpi
diff --git a/wpiutil/src/main/native/windows/Demangle.cpp b/wpiutil/src/main/native/windows/Demangle.cpp
index a1560d6..1b0a8a7 100644
--- a/wpiutil/src/main/native/windows/Demangle.cpp
+++ b/wpiutil/src/main/native/windows/Demangle.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2008-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/Demangle.h"
 
@@ -18,15 +15,15 @@
 
 namespace wpi {
 
-std::string Demangle(const Twine& mangledSymbol) {
+std::string Demangle(std::string_view mangledSymbol) {
   static wpi::mutex m;
   std::scoped_lock lock(m);
-  SmallString<128> buf;
+  SmallString<128> buf{mangledSymbol};
   char buffer[256];
-  DWORD sz =
-      UnDecorateSymbolName(mangledSymbol.toNullTerminatedStringRef(buf).data(),
-                           buffer, sizeof(buffer), UNDNAME_COMPLETE);
-  if (sz == 0) return mangledSymbol.str();
+  DWORD sz = UnDecorateSymbolName(buf.c_str(), buffer, sizeof(buffer),
+                                  UNDNAME_COMPLETE);
+  if (sz == 0)
+    return std::string{mangledSymbol};
   return std::string(buffer, sz);
 }
 
diff --git a/wpiutil/src/main/native/windows/StackTrace.cpp b/wpiutil/src/main/native/windows/StackTrace.cpp
index 86bbdff..3e3ccfa 100644
--- a/wpiutil/src/main/native/windows/StackTrace.cpp
+++ b/wpiutil/src/main/native/windows/StackTrace.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2008-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/StackTrace.h"
 
diff --git a/wpiutil/src/netconsoleServer/native/cpp/main.cpp b/wpiutil/src/netconsoleServer/native/cpp/main.cpp
index ed45eeb..c43a670 100644
--- a/wpiutil/src/netconsoleServer/native/cpp/main.cpp
+++ b/wpiutil/src/netconsoleServer/native/cpp/main.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #ifdef __APPLE__
 #include <util.h>
@@ -11,9 +8,12 @@
 #include <pty.h>
 #endif
 
+#include <cstdio>
+
+#include "fmt/format.h"
 #include "wpi/MathExtras.h"
 #include "wpi/SmallVector.h"
-#include "wpi/raw_ostream.h"
+#include "wpi/StringExtras.h"
 #include "wpi/raw_uv_ostream.h"
 #include "wpi/timestamp.h"
 #include "wpi/uv/Loop.h"
@@ -33,9 +33,9 @@
                           wpi::SmallVectorImpl<uv::Buffer>& bufs, bool tcp,
                           uint16_t tcpSeq) {
   // scan for last newline
-  wpi::StringRef str(buf.base, len);
+  std::string_view str(buf.base, len);
   size_t idx = str.rfind('\n');
-  if (idx == wpi::StringRef::npos) {
+  if (idx == std::string_view::npos) {
     // no newline yet, just keep appending to remainder
     rem += str;
     return false;
@@ -43,7 +43,7 @@
 
   // build output
   wpi::raw_uv_ostream out(bufs, 4096);
-  wpi::StringRef toCopy = str.slice(0, idx + 1);
+  std::string_view toCopy = wpi::slice(str, 0, idx + 1);
   if (tcp) {
     // Header is 2 byte len, 1 byte type, 4 byte timestamp, 2 byte sequence num
     uint32_t ts = wpi::FloatToBits((wpi::Now() - startTime) * 1.0e-6);
@@ -57,12 +57,12 @@
                               static_cast<uint8_t>(ts & 0xff),
                               static_cast<uint8_t>((tcpSeq >> 8) & 0xff),
                               static_cast<uint8_t>(tcpSeq & 0xff)};
-    out << wpi::ArrayRef<uint8_t>(header);
+    out << wpi::span<const uint8_t>(header);
   }
   out << rem << toCopy;
 
   // reset remainder
-  rem = str.slice(idx + 1, wpi::StringRef::npos);
+  rem = wpi::slice(str, idx + 1, std::string_view::npos);
   return true;
 }
 
@@ -81,11 +81,15 @@
           uv::Buffer& buf, size_t len) {
         // build buffers
         wpi::SmallVector<uv::Buffer, 4> bufs;
-        if (!NewlineBuffer(*rem, buf, len, bufs, false, 0)) return;
+        if (!NewlineBuffer(*rem, buf, len, bufs, false, 0)) {
+          return;
+        }
 
         // send output
         outPtr->Send(addr, bufs, [](auto bufs2, uv::Error) {
-          for (auto buf : bufs2) buf.Deallocate();
+          for (auto buf : bufs2) {
+            buf.Deallocate();
+          }
         });
       },
       out);
@@ -101,12 +105,15 @@
           uv::Buffer& buf, size_t len) {
         // build buffers
         wpi::SmallVector<uv::Buffer, 4> bufs;
-        if (!NewlineBuffer(data->rem, buf, len, bufs, true, data->seq++))
+        if (!NewlineBuffer(data->rem, buf, len, bufs, true, data->seq++)) {
           return;
+        }
 
         // send output
         outPtr->Write(bufs, [](auto bufs2, uv::Error) {
-          for (auto buf : bufs2) buf.Deallocate();
+          for (auto buf : bufs2) {
+            buf.Deallocate();
+          }
         });
       },
       out);
@@ -116,8 +123,10 @@
   in.data.connect([out](uv::Buffer& buf, size_t len) {
     uv::Buffer buf2 = buf.Dup();
     buf2.len = len;
-    out->Write(buf2, [](auto bufs, uv::Error) {
-      for (auto buf : bufs) buf.Deallocate();
+    out->Write({buf2}, [](auto bufs, uv::Error) {
+      for (auto buf : bufs) {
+        buf.Deallocate();
+      }
     });
   });
 }
@@ -130,24 +139,26 @@
   bool err = false;
 
   while (programArgc < argc && argv[programArgc][0] == '-') {
-    if (wpi::StringRef(argv[programArgc]) == "-u") {
+    if (std::string_view(argv[programArgc]) == "-u") {
       useUdp = true;
-    } else if (wpi::StringRef(argv[programArgc]) == "-b") {
+    } else if (std::string_view(argv[programArgc]) == "-b") {
       useUdp = true;
       broadcastUdp = true;
     } else {
-      wpi::errs() << "unrecognized command line option " << argv[programArgc]
-                  << '\n';
+      fmt::print(stderr, "unrecognized command line option {}\n",
+                 argv[programArgc]);
       err = true;
     }
     ++programArgc;
   }
 
   if (err || (argc - programArgc) < 1) {
-    wpi::errs()
-        << argv[0] << " [-ub] program [arguments ...]\n"
-        << "  -u  send udp to localhost port 6666 instead of using tcp\n"
-        << "  -b  broadcast udp to port 6666 instead of using tcp\n";
+    std::fputs(argv[0], stderr);
+    std::fputs(
+        " [-ub] program [arguments ...]\n"
+        "  -u  send udp to localhost port 6666 instead of using tcp\n"
+        "  -b  broadcast udp to port 6666 instead of using tcp\n",
+        stderr);
     return EXIT_FAILURE;
   }
 
@@ -155,7 +166,7 @@
 
   auto loop = uv::Loop::Create();
   loop->error.connect(
-      [](uv::Error err) { wpi::errs() << "uv ERROR: " << err.str() << '\n'; });
+      [](uv::Error err) { fmt::print(stderr, "uv ERROR: {}\n", err.str()); });
 
   // create pipes to communicate with child
   auto stdinPipe = uv::Pipe::Create(loop);
@@ -168,12 +179,20 @@
   auto stderrTty = uv::Tty::Create(loop, 2, false);
 
   // pass through our console to child's (bidirectional)
-  if (stdinTty) CopyStream(*stdinTty, stdinPipe);
-  if (stdoutTty) CopyStream(*stdoutPipe, stdoutTty);
-  if (stderrTty) CopyStream(*stderrPipe, stderrTty);
+  if (stdinTty) {
+    CopyStream(*stdinTty, stdinPipe);
+  }
+  if (stdoutTty) {
+    CopyStream(*stdoutPipe, stdoutTty);
+  }
+  if (stderrTty) {
+    CopyStream(*stderrPipe, stderrTty);
+  }
 
   // when our stdin closes, also close child stdin
-  if (stdinTty) stdinTty->end.connect([stdinPipe] { stdinPipe->Close(); });
+  if (stdinTty) {
+    stdinTty->end.connect([stdinPipe] { stdinPipe->Close(); });
+  }
 
   if (useUdp) {
     auto udp = uv::Udp::Create(loop);
@@ -189,7 +208,9 @@
     // when we get a connection, accept it
     tcp->connection.connect([srv = tcp.get(), stdoutPipe, stderrPipe] {
       auto tcp = srv->Accept();
-      if (!tcp) return;
+      if (!tcp) {
+        return;
+      }
 
       // close on error
       tcp->error.connect([s = tcp.get()](wpi::uv::Error err) { s->Close(); });
@@ -227,17 +248,21 @@
       uv::Process::StdioCreatePipe(2, *stderrPipe, UV_WRITABLE_PIPE));
 
   // pass our args as the child args (argv[1] becomes child argv[0], etc)
-  for (int i = programArgc; i < argc; ++i) options.emplace_back(argv[i]);
+  for (int i = programArgc; i < argc; ++i) {
+    options.emplace_back(argv[i]);
+  }
 
   auto proc = uv::Process::SpawnArray(loop, argv[programArgc], options);
   if (!proc) {
-    wpi::errs() << "could not start subprocess\n";
+    std::fputs("could not start subprocess\n", stderr);
     return EXIT_FAILURE;
   }
   proc->exited.connect([](int64_t status, int) { std::exit(status); });
 
   // start reading
-  if (stdinTty) stdinTty->StartRead();
+  if (stdinTty) {
+    stdinTty->StartRead();
+  }
   stdoutPipe->StartRead();
   stderrPipe->StartRead();
 
diff --git a/wpiutil/src/netconsoleTee/native/cpp/main.cpp b/wpiutil/src/netconsoleTee/native/cpp/main.cpp
index 82cc82a..8abb458 100644
--- a/wpiutil/src/netconsoleTee/native/cpp/main.cpp
+++ b/wpiutil/src/netconsoleTee/native/cpp/main.cpp
@@ -1,13 +1,13 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
+#include <cstdio>
+
+#include "fmt/format.h"
 #include "wpi/MathExtras.h"
 #include "wpi/SmallVector.h"
-#include "wpi/raw_ostream.h"
+#include "wpi/StringExtras.h"
 #include "wpi/raw_uv_ostream.h"
 #include "wpi/timestamp.h"
 #include "wpi/uv/Loop.h"
@@ -24,9 +24,9 @@
                           wpi::SmallVectorImpl<uv::Buffer>& bufs, bool tcp,
                           uint16_t tcpSeq) {
   // scan for last newline
-  wpi::StringRef str(buf.base, len);
+  std::string_view str(buf.base, len);
   size_t idx = str.rfind('\n');
-  if (idx == wpi::StringRef::npos) {
+  if (idx == std::string_view::npos) {
     // no newline yet, just keep appending to remainder
     rem += str;
     return false;
@@ -34,7 +34,7 @@
 
   // build output
   wpi::raw_uv_ostream out(bufs, 4096);
-  wpi::StringRef toCopy = str.slice(0, idx + 1);
+  std::string_view toCopy = wpi::slice(str, 0, idx + 1);
   if (tcp) {
     // Header is 2 byte len, 1 byte type, 4 byte timestamp, 2 byte sequence num
     uint32_t ts = wpi::FloatToBits((wpi::Now() - startTime) * 1.0e-6);
@@ -48,12 +48,12 @@
                               static_cast<uint8_t>(ts & 0xff),
                               static_cast<uint8_t>((tcpSeq >> 8) & 0xff),
                               static_cast<uint8_t>(tcpSeq & 0xff)};
-    out << wpi::ArrayRef<uint8_t>(header);
+    out << wpi::span<const uint8_t>(header);
   }
   out << rem << toCopy;
 
   // reset remainder
-  rem = str.slice(idx + 1, wpi::StringRef::npos);
+  rem = wpi::slice(str, idx + 1, std::string_view::npos);
   return true;
 }
 
@@ -72,11 +72,15 @@
           uv::Buffer& buf, size_t len) {
         // build buffers
         wpi::SmallVector<uv::Buffer, 4> bufs;
-        if (!NewlineBuffer(*rem, buf, len, bufs, false, 0)) return;
+        if (!NewlineBuffer(*rem, buf, len, bufs, false, 0)) {
+          return;
+        }
 
         // send output
         outPtr->Send(addr, bufs, [](auto bufs2, uv::Error) {
-          for (auto buf : bufs2) buf.Deallocate();
+          for (auto buf : bufs2) {
+            buf.Deallocate();
+          }
         });
       },
       out);
@@ -92,12 +96,15 @@
           uv::Buffer& buf, size_t len) {
         // build buffers
         wpi::SmallVector<uv::Buffer, 4> bufs;
-        if (!NewlineBuffer(data->rem, buf, len, bufs, true, data->seq++))
+        if (!NewlineBuffer(data->rem, buf, len, bufs, true, data->seq++)) {
           return;
+        }
 
         // send output
         outPtr->Write(bufs, [](auto bufs2, uv::Error) {
-          for (auto buf : bufs2) buf.Deallocate();
+          for (auto buf : bufs2) {
+            buf.Deallocate();
+          }
         });
       },
       out);
@@ -107,8 +114,10 @@
   in.data.connect([out](uv::Buffer& buf, size_t len) {
     uv::Buffer buf2 = buf.Dup();
     buf2.len = len;
-    out->Write(buf2, [](auto bufs, uv::Error) {
-      for (auto buf : bufs) buf.Deallocate();
+    out->Write({buf2}, [](auto bufs, uv::Error) {
+      for (auto buf : bufs) {
+        buf.Deallocate();
+      }
     });
   });
 }
@@ -122,47 +131,56 @@
   int port = -1;
 
   while (arg < argc && argv[arg][0] == '-') {
-    if (wpi::StringRef(argv[arg]) == "-u") {
+    if (std::string_view(argv[arg]) == "-u") {
       useUdp = true;
-    } else if (wpi::StringRef(argv[arg]) == "-b") {
+    } else if (std::string_view(argv[arg]) == "-b") {
       useUdp = true;
       broadcastUdp = true;
-    } else if (wpi::StringRef(argv[arg]) == "-p") {
+    } else if (std::string_view(argv[arg]) == "-p") {
       ++arg;
+      std::optional<int> portValue;
       if (arg >= argc || argv[arg][0] == '-' ||
-          wpi::StringRef(argv[arg]).getAsInteger(10, port)) {
-        wpi::errs() << "-p must be followed by port number\n";
+          !(portValue = wpi::parse_integer<int>(argv[arg], 10))) {
+        std::fputs("-p must be followed by port number\n", stderr);
         err = true;
+      } else if (portValue) {
+        port = portValue.value();
       }
     } else {
-      wpi::errs() << "unrecognized command line option " << argv[arg] << '\n';
+      fmt::print(stderr, "unrecognized command line option {}\n", argv[arg]);
       err = true;
     }
     ++arg;
   }
 
   if (err) {
-    wpi::errs()
-        << argv[0] << " [-ub] [-p PORT]\n"
-        << "  -u       send udp to localhost port 6666 instead of using tcp\n"
-        << "  -b       broadcast udp to port 6666 instead of using tcp\n"
-        << "  -p PORT  use port PORT instead of 6666 (udp) or 1740 (tcp)\n";
+    std::fputs(argv[0], stderr);
+    std::fputs(
+        " [-ub] [-p PORT]\n"
+        "  -u       send udp to localhost port 6666 instead of using tcp\n"
+        "  -b       broadcast udp to port 6666 instead of using tcp\n"
+        "  -p PORT  use port PORT instead of 6666 (udp) or 1740 (tcp)\n",
+        stderr);
     return EXIT_FAILURE;
   }
 
   auto loop = uv::Loop::Create();
   loop->error.connect(
-      [](uv::Error err) { wpi::errs() << "uv ERROR: " << err.str() << '\n'; });
+      [](uv::Error err) { fmt::print(stderr, "uv ERROR: {}\n", err.str()); });
 
   // create ttys
   auto stdinTty = uv::Tty::Create(loop, 0, true);
   auto stdoutTty = uv::Tty::Create(loop, 1, false);
 
   // don't bother continuing if we don't have a stdin
-  if (!stdinTty) return EXIT_SUCCESS;
+  if (!stdinTty) {
+    return EXIT_SUCCESS;
+  }
 
   // pass through our input to output
-  if (stdoutTty) CopyStream(*stdinTty, stdoutTty);
+  if (stdoutTty) {
+    CopyStream(*stdinTty, stdoutTty);
+  }
 
   // when our stdin closes, exit
   stdinTty->end.connect([] { std::exit(EXIT_SUCCESS); });
@@ -180,7 +198,9 @@
     // when we get a connection, accept it
     tcp->connection.connect([srv = tcp.get(), stdinTty] {
       auto tcp = srv->Accept();
-      if (!tcp) return;
+      if (!tcp) {
+        return;
+      }
 
       // close on error
       tcp->error.connect([s = tcp.get()](wpi::uv::Error err) { s->Close(); });
@@ -194,7 +214,9 @@
   }
 
   // start reading
-  if (stdinTty) stdinTty->StartRead();
+  if (stdinTty) {
+    stdinTty->StartRead();
+  }
 
   // run the loop!
   loop->Run();
diff --git a/wpiutil/src/test/java/edu/wpi/first/wpiutil/CircularBufferTest.java b/wpiutil/src/test/java/edu/wpi/first/util/CircularBufferTest.java
similarity index 83%
rename from wpiutil/src/test/java/edu/wpi/first/wpiutil/CircularBufferTest.java
rename to wpiutil/src/test/java/edu/wpi/first/util/CircularBufferTest.java
index f995098..7972754 100644
--- a/wpiutil/src/test/java/edu/wpi/first/wpiutil/CircularBufferTest.java
+++ b/wpiutil/src/test/java/edu/wpi/first/util/CircularBufferTest.java
@@ -1,23 +1,23 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
-package edu.wpi.first.wpiutil;
-
-import org.junit.jupiter.api.Test;
+package edu.wpi.first.util;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import org.junit.jupiter.api.Test;
+
 class CircularBufferTest {
-  private final double[] m_values = {751.848, 766.366, 342.657, 234.252, 716.126,
-      132.344, 445.697, 22.727, 421.125, 799.913};
-  private final double[] m_addFirstOut = {799.913, 421.125, 22.727, 445.697, 132.344,
-      716.126, 234.252, 342.657};
-  private final double[] m_addLastOut = {342.657, 234.252, 716.126, 132.344, 445.697,
-      22.727, 421.125, 799.913};
+  private final double[] m_values = {
+    751.848, 766.366, 342.657, 234.252, 716.126, 132.344, 445.697, 22.727, 421.125, 799.913
+  };
+  private final double[] m_addFirstOut = {
+    799.913, 421.125, 22.727, 445.697, 132.344, 716.126, 234.252, 342.657
+  };
+  private final double[] m_addLastOut = {
+    342.657, 234.252, 716.126, 132.344, 445.697, 22.727, 421.125, 799.913
+  };
 
   @Test
   void addFirstTest() {
@@ -105,7 +105,6 @@
   }
 
   @Test
-  @SuppressWarnings("PMD.ExcessiveMethodLength")
   void resizeTest() {
     CircularBuffer queue = new CircularBuffer(5);
 
diff --git a/wpiutil/src/test/java/edu/wpi/first/util/WPIUtilJNITest.java b/wpiutil/src/test/java/edu/wpi/first/util/WPIUtilJNITest.java
new file mode 100644
index 0000000..7d5b229
--- /dev/null
+++ b/wpiutil/src/test/java/edu/wpi/first/util/WPIUtilJNITest.java
@@ -0,0 +1,26 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package edu.wpi.first.util;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+public class WPIUtilJNITest {
+  @Test
+  public void testEnableMockTime() {
+    assertDoesNotThrow(WPIUtilJNI::enableMockTime);
+  }
+
+  @Test
+  public void testSetMockTime() {
+    assertDoesNotThrow(() -> WPIUtilJNI.setMockTime(0L));
+  }
+
+  @Test
+  public void testNow() {
+    assertDoesNotThrow(WPIUtilJNI::now);
+  }
+}
diff --git a/wpiutil/src/test/java/edu/wpi/first/wpiutil/WPIUtilJNITest.java b/wpiutil/src/test/java/edu/wpi/first/wpiutil/WPIUtilJNITest.java
deleted file mode 100644
index e92efe4..0000000
--- a/wpiutil/src/test/java/edu/wpi/first/wpiutil/WPIUtilJNITest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
-
-package edu.wpi.first.wpiutil;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-
-public class WPIUtilJNITest {
-  @Test
-  public void testNow() {
-    assertDoesNotThrow(WPIUtilJNI::now);
-  }
-}
diff --git a/wpiutil/src/test/native/ManagedStaticTest.cpp b/wpiutil/src/test/native/ManagedStaticTest.cpp
index 81a4b9c..ab54499 100644
--- a/wpiutil/src/test/native/ManagedStaticTest.cpp
+++ b/wpiutil/src/test/native/ManagedStaticTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2019 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/ManagedStatic.h"  // NOLINT(build/include_order)
 
@@ -23,6 +20,7 @@
   {
     refCount = 0;
     wpi::ManagedStatic<StaticTestClass> managedStatic;
+    (void)managedStatic;
     ASSERT_EQ(refCount, 0);
   }
   ASSERT_EQ(refCount, 0);
@@ -46,7 +44,7 @@
   {
     refCount = 0;
     StaticTestClass* test = new StaticTestClass{};
-    ASSERT_EQ(refCount, 1);
+    ASSERT_EQ(refCount, 1);  // NOLINT
     wpi::ManagedStatic<StaticTestClass> managedStatic(
         test, [](void* val) { delete static_cast<StaticTestClass*>(val); });
     ASSERT_EQ(refCount, 1);
diff --git a/wpiutil/src/test/native/cpp/Base64Test.cpp b/wpiutil/src/test/native/cpp/Base64Test.cpp
index caa35aa..542858d 100644
--- a/wpiutil/src/test/native/cpp/Base64Test.cpp
+++ b/wpiutil/src/test/native/cpp/Base64Test.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "gtest/gtest.h"
 #include "wpi/Base64.h"
@@ -26,11 +23,12 @@
 
 class Base64Test : public ::testing::TestWithParam<Base64TestParam> {
  protected:
-  StringRef GetPlain() {
-    if (GetParam().plain_len < 0)
-      return StringRef(GetParam().plain);
-    else
-      return StringRef(GetParam().plain, GetParam().plain_len);
+  std::string_view GetPlain() {
+    if (GetParam().plain_len < 0) {
+      return GetParam().plain;
+    } else {
+      return std::string_view(GetParam().plain, GetParam().plain_len);
+    }
   }
 };
 
@@ -53,7 +51,7 @@
 
 TEST_P(Base64Test, DecodeStdString) {
   std::string s;
-  StringRef encoded = GetParam().encoded;
+  std::string_view encoded = GetParam().encoded;
   EXPECT_EQ(encoded.size(), Base64Decode(encoded, &s));
   ASSERT_EQ(GetPlain(), s);
 
@@ -64,9 +62,9 @@
 
 TEST_P(Base64Test, DecodeSmallString) {
   SmallString<128> buf;
-  StringRef encoded = GetParam().encoded;
+  std::string_view encoded = GetParam().encoded;
   size_t len;
-  StringRef plain = Base64Decode(encoded, &len, buf);
+  std::string_view plain = Base64Decode(encoded, &len, buf);
   EXPECT_EQ(encoded.size(), len);
   ASSERT_EQ(GetPlain(), plain);
 
@@ -85,7 +83,8 @@
      "mQgc28gb24uLi4K"},
 };
 
-INSTANTIATE_TEST_SUITE_P(Base64Sample, Base64Test, ::testing::ValuesIn(sample));
+INSTANTIATE_TEST_SUITE_P(Base64SampleTests, Base64Test,
+                         ::testing::ValuesIn(sample));
 
 static Base64TestParam standard[] = {
     {0, "", ""},
@@ -98,7 +97,7 @@
     {2, "\xff\xef", "/+8="},
 };
 
-INSTANTIATE_TEST_SUITE_P(Base64Standard, Base64Test,
+INSTANTIATE_TEST_SUITE_P(Base64StandardTests, Base64Test,
                          ::testing::ValuesIn(standard));
 
 }  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/CircularBufferTest.cpp b/wpiutil/src/test/native/cpp/CircularBufferTest.cpp
index 7fe9e03..ad2285e 100644
--- a/wpiutil/src/test/native/cpp/CircularBufferTest.cpp
+++ b/wpiutil/src/test/native/cpp/CircularBufferTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/circular_buffer.h"  // NOLINT(build/include_order)
 
@@ -21,31 +18,55 @@
 static const std::array<double, 8> pushBackOut = {
     {342.657, 234.252, 716.126, 132.344, 445.697, 22.727, 421.125, 799.913}};
 
-TEST(CircularBufferTest, PushFrontTest) {
+TEST(CircularBufferTest, PushFront) {
   wpi::circular_buffer<double> queue(8);
 
   for (auto& value : values) {
     queue.push_front(value);
   }
 
-  for (size_t i = 0; i < pushFrontOut.size(); i++) {
+  for (size_t i = 0; i < pushFrontOut.size(); ++i) {
     EXPECT_EQ(pushFrontOut[i], queue[i]);
   }
 }
 
-TEST(CircularBufferTest, PushBackTest) {
+TEST(CircularBufferTest, PushBack) {
   wpi::circular_buffer<double> queue(8);
 
   for (auto& value : values) {
     queue.push_back(value);
   }
 
-  for (size_t i = 0; i < pushBackOut.size(); i++) {
+  for (size_t i = 0; i < pushBackOut.size(); ++i) {
     EXPECT_EQ(pushBackOut[i], queue[i]);
   }
 }
 
-TEST(CircularBufferTest, PushPopTest) {
+TEST(CircularBufferTest, EmplaceFront) {
+  wpi::circular_buffer<double> queue(8);
+
+  for (auto& value : values) {
+    queue.emplace_front(value);
+  }
+
+  for (size_t i = 0; i < pushFrontOut.size(); ++i) {
+    EXPECT_EQ(pushFrontOut[i], queue[i]);
+  }
+}
+
+TEST(CircularBufferTest, EmplaceBack) {
+  wpi::circular_buffer<double> queue(8);
+
+  for (auto& value : values) {
+    queue.emplace_back(value);
+  }
+
+  for (size_t i = 0; i < pushBackOut.size(); ++i) {
+    EXPECT_EQ(pushBackOut[i], queue[i]);
+  }
+}
+
+TEST(CircularBufferTest, PushPop) {
   wpi::circular_buffer<double> queue(3);
 
   // Insert three elements into the buffer
@@ -88,21 +109,19 @@
   EXPECT_EQ(4.0, queue[0]);
 }
 
-TEST(CircularBufferTest, ResetTest) {
+TEST(CircularBufferTest, Reset) {
   wpi::circular_buffer<double> queue(5);
 
-  for (size_t i = 1; i < 6; i++) {
+  for (size_t i = 1; i < 6; ++i) {
     queue.push_back(i);
   }
 
   queue.reset();
 
-  for (size_t i = 0; i < 5; i++) {
-    EXPECT_EQ(0.0, queue[i]);
-  }
+  EXPECT_EQ(queue.size(), size_t{0});
 }
 
-TEST(CircularBufferTest, ResizeTest) {
+TEST(CircularBufferTest, Resize) {
   wpi::circular_buffer<double> queue(5);
 
   /* Buffer contains {1, 2, 3, _, _}
@@ -207,3 +226,29 @@
   EXPECT_EQ(2.0, queue[2]);
   EXPECT_EQ(3.0, queue[3]);
 }
+
+TEST(CircularBufferTest, Iterator) {
+  wpi::circular_buffer<double> queue(3);
+
+  queue.push_back(1.0);
+  queue.push_back(2.0);
+  queue.push_back(3.0);
+  queue.push_back(4.0);  // Overwrite 1 with 4
+
+  // The buffer now contains 2, 3 and 4
+  const std::array<double, 3> values = {2.0, 3.0, 4.0};
+
+  // iterator
+  int i = 0;
+  for (auto& elem : queue) {
+    EXPECT_EQ(values[i], elem);
+    ++i;
+  }
+
+  // const_iterator
+  i = 0;
+  for (const auto& elem : queue) {
+    EXPECT_EQ(values[i], elem);
+    ++i;
+  }
+}
diff --git a/wpiutil/src/test/native/cpp/HttpParserTest.cpp b/wpiutil/src/test/native/cpp/HttpParserTest.cpp
index 8de2bc6..281abd0 100644
--- a/wpiutil/src/test/native/cpp/HttpParserTest.cpp
+++ b/wpiutil/src/test/native/cpp/HttpParserTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/HttpParser.h"  // NOLINT(build/include_order)
 
@@ -14,7 +11,7 @@
 TEST(HttpParserTest, UrlMethodHeadersComplete) {
   HttpParser p{HttpParser::kRequest};
   int callbacks = 0;
-  p.url.connect([&](StringRef path) {
+  p.url.connect([&](std::string_view path) {
     ASSERT_EQ(path, "/foo/bar");
     ASSERT_EQ(p.GetUrl(), "/foo/bar");
     ++callbacks;
@@ -32,7 +29,7 @@
 TEST(HttpParserTest, UrlMethodHeader) {
   HttpParser p{HttpParser::kRequest};
   int callbacks = 0;
-  p.url.connect([&](StringRef path) {
+  p.url.connect([&](std::string_view path) {
     ASSERT_EQ(path, "/foo/bar");
     ASSERT_EQ(p.GetUrl(), "/foo/bar");
     ++callbacks;
@@ -52,7 +49,7 @@
 TEST(HttpParserTest, StatusHeadersComplete) {
   HttpParser p{HttpParser::kResponse};
   int callbacks = 0;
-  p.status.connect([&](StringRef status) {
+  p.status.connect([&](std::string_view status) {
     ASSERT_EQ(status, "OK");
     ASSERT_EQ(p.GetStatusCode(), 200u);
     ++callbacks;
@@ -69,7 +66,7 @@
 TEST(HttpParserTest, StatusHeader) {
   HttpParser p{HttpParser::kResponse};
   int callbacks = 0;
-  p.status.connect([&](StringRef status) {
+  p.status.connect([&](std::string_view status) {
     ASSERT_EQ(status, "OK");
     ASSERT_EQ(p.GetStatusCode(), 200u);
     ++callbacks;
@@ -86,7 +83,7 @@
 TEST(HttpParserTest, HeaderFieldComplete) {
   HttpParser p{HttpParser::kRequest};
   int callbacks = 0;
-  p.header.connect([&](StringRef name, StringRef value) {
+  p.header.connect([&](std::string_view name, std::string_view value) {
     ASSERT_EQ(name, "Foo");
     ASSERT_EQ(value, "Bar");
     ++callbacks;
@@ -109,7 +106,7 @@
 TEST(HttpParserTest, HeaderFieldNext) {
   HttpParser p{HttpParser::kRequest};
   int callbacks = 0;
-  p.header.connect([&](StringRef name, StringRef value) {
+  p.header.connect([&](std::string_view name, std::string_view value) {
     ASSERT_EQ(name, "Foo");
     ASSERT_EQ(value, "Bar");
     ++callbacks;
diff --git a/wpiutil/src/test/native/cpp/HttpUtilTest.cpp b/wpiutil/src/test/native/cpp/HttpUtilTest.cpp
index a83214d..524086a 100644
--- a/wpiutil/src/test/native/cpp/HttpUtilTest.cpp
+++ b/wpiutil/src/test/native/cpp/HttpUtilTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/HttpUtil.h"  // NOLINT(build/include_order)
 
diff --git a/wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp b/wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp
index 8022cae..18a21be 100644
--- a/wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp
+++ b/wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/HttpWebSocketServerConnection.h"  // NOLINT(build/include_order)
 
@@ -15,7 +12,7 @@
     : public HttpWebSocketServerConnection<HttpWebSocketServerConnectionTest> {
  public:
   HttpWebSocketServerConnectionTest(std::shared_ptr<uv::Stream> stream,
-                                    ArrayRef<StringRef> protocols)
+                                    span<const std::string_view> protocols)
       : HttpWebSocketServerConnection{stream, protocols} {}
 
   void ProcessRequest() override { ++gotRequest; }
diff --git a/wpiutil/src/test/native/cpp/StaticCircularBufferTest.cpp b/wpiutil/src/test/native/cpp/StaticCircularBufferTest.cpp
index 72da38b..f95f9cb 100644
--- a/wpiutil/src/test/native/cpp/StaticCircularBufferTest.cpp
+++ b/wpiutil/src/test/native/cpp/StaticCircularBufferTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2020 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/static_circular_buffer.h"  // NOLINT(build/include_order)
 
@@ -21,7 +18,7 @@
 static const std::array<double, 8> pushBackOut = {
     {342.657, 234.252, 716.126, 132.344, 445.697, 22.727, 421.125, 799.913}};
 
-TEST(StaticCircularBufferTest, PushFrontTest) {
+TEST(StaticCircularBufferTest, PushFront) {
   wpi::static_circular_buffer<double, 8> queue;
 
   for (auto& value : values) {
@@ -33,7 +30,7 @@
   }
 }
 
-TEST(StaticCircularBufferTest, PushBackTest) {
+TEST(StaticCircularBufferTest, PushBack) {
   wpi::static_circular_buffer<double, 8> queue;
 
   for (auto& value : values) {
@@ -45,7 +42,7 @@
   }
 }
 
-TEST(StaticCircularBufferTest, EmplaceFrontTest) {
+TEST(StaticCircularBufferTest, EmplaceFront) {
   wpi::static_circular_buffer<double, 8> queue;
 
   for (auto& value : values) {
@@ -57,7 +54,7 @@
   }
 }
 
-TEST(StaticCircularBufferTest, EmplaceBackTest) {
+TEST(StaticCircularBufferTest, EmplaceBack) {
   wpi::static_circular_buffer<double, 8> queue;
 
   for (auto& value : values) {
@@ -69,7 +66,7 @@
   }
 }
 
-TEST(StaticCircularBufferTest, PushPopTest) {
+TEST(StaticCircularBufferTest, PushPop) {
   wpi::static_circular_buffer<double, 3> queue;
 
   // Insert three elements into the buffer
@@ -112,7 +109,7 @@
   EXPECT_EQ(4.0, queue[0]);
 }
 
-TEST(StaticCircularBufferTest, ResetTest) {
+TEST(StaticCircularBufferTest, Reset) {
   wpi::static_circular_buffer<double, 5> queue;
 
   for (size_t i = 1; i < 6; ++i) {
@@ -124,7 +121,7 @@
   EXPECT_EQ(queue.size(), size_t{0});
 }
 
-TEST(StaticCircularBufferTest, IteratorTest) {
+TEST(StaticCircularBufferTest, Iterator) {
   wpi::static_circular_buffer<double, 3> queue;
 
   queue.push_back(1.0);
diff --git a/wpiutil/src/test/native/cpp/SynchronizationTest.cpp b/wpiutil/src/test/native/cpp/SynchronizationTest.cpp
new file mode 100644
index 0000000..a349c54
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/SynchronizationTest.cpp
@@ -0,0 +1,56 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/Synchronization.h"  // NOLINT(build/include_order)
+
+#include <thread>
+
+#include "gtest/gtest.h"
+
+TEST(EventTest, AutoReset) {
+  auto event = wpi::CreateEvent(false, false);
+  std::thread thr([&] { wpi::SetEvent(event); });
+  wpi::WaitForObject(event);
+  thr.join();
+  bool timedOut;
+  wpi::WaitForObject(event, 0, &timedOut);
+  ASSERT_EQ(timedOut, true);
+}
+
+TEST(EventTest, ManualReset) {
+  auto event = wpi::CreateEvent(true, false);
+  int done = 0;
+  std::thread thr([&] {
+    wpi::SetEvent(event);
+    ++done;
+  });
+  wpi::WaitForObject(event);
+  thr.join();
+  ASSERT_EQ(done, 1);
+  bool timedOut;
+  wpi::WaitForObject(event, 0, &timedOut);
+  ASSERT_EQ(timedOut, false);
+}
+
+TEST(EventTest, InitialSet) {
+  auto event = wpi::CreateEvent(false, true);
+  bool timedOut;
+  wpi::WaitForObject(event, 0, &timedOut);
+  ASSERT_EQ(timedOut, false);
+}
+
+TEST(EventTest, WaitMultiple) {
+  auto event1 = wpi::CreateEvent(false, false);
+  auto event2 = wpi::CreateEvent(false, false);
+  std::thread thr([&] { wpi::SetEvent(event2); });
+  WPI_Handle signaled[2];
+  auto result1 = wpi::WaitForObjects({event1, event2}, signaled);
+  thr.join();
+  ASSERT_EQ(result1.size(), 1u);
+  ASSERT_EQ(result1[0], event2);
+  bool timedOut;
+  auto result2 = wpi::WaitForObjects({event1, event2}, signaled, 0, &timedOut);
+  ASSERT_EQ(timedOut, true);
+  ASSERT_EQ(result2.size(), 0u);
+}
diff --git a/wpiutil/src/test/native/cpp/UidVectorTest.cpp b/wpiutil/src/test/native/cpp/UidVectorTest.cpp
index e11c7b2..f9e1624 100644
--- a/wpiutil/src/test/native/cpp/UidVectorTest.cpp
+++ b/wpiutil/src/test/native/cpp/UidVectorTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/UidVector.h"  // NOLINT(build/include_order)
 
@@ -39,7 +36,9 @@
   v.emplace_back(2);
   v.emplace_back(1);
   std::vector<int> out;
-  for (auto&& val : v) out.push_back(val);
+  for (auto&& val : v) {
+    out.push_back(val);
+  }
   ASSERT_EQ(out.size(), 2u);
   EXPECT_EQ(out[0], 2);
   EXPECT_EQ(out[1], 1);
diff --git a/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp b/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp
index 2db9b54..9d85e66 100644
--- a/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp
+++ b/wpiutil/src/test/native/cpp/WebSocketClientTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/WebSocket.h"  // NOLINT(build/include_order)
 
@@ -11,6 +8,7 @@
 #include "wpi/Base64.h"
 #include "wpi/HttpParser.h"
 #include "wpi/SmallString.h"
+#include "wpi/StringExtras.h"
 #include "wpi/raw_uv_ostream.h"
 #include "wpi/sha1.h"
 
@@ -20,9 +18,11 @@
  public:
   WebSocketClientTest() {
     // Bare bones server
-    req.header.connect([this](StringRef name, StringRef value) {
+    req.header.connect([this](std::string_view name, std::string_view value) {
       // save key (required for valid response)
-      if (name.equals_lower("sec-websocket-key")) clientKey = value;
+      if (equals_lower(name, "sec-websocket-key")) {
+        clientKey = value;
+      }
     });
     req.headersComplete.connect([this](bool) {
       // send response
@@ -36,37 +36,48 @@
       SHA1 hash;
       hash.Update(clientKey);
       hash.Update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
-      if (mockBadAccept) hash.Update("1");
+      if (mockBadAccept) {
+        hash.Update("1");
+      }
       SmallString<64> hashBuf;
       SmallString<64> acceptBuf;
       os << "Sec-WebSocket-Accept: "
          << Base64Encode(hash.RawFinal(hashBuf), acceptBuf) << "\r\n";
 
-      if (!mockProtocol.empty())
+      if (!mockProtocol.empty()) {
         os << "Sec-WebSocket-Protocol: " << mockProtocol << "\r\n";
+      }
 
       os << "\r\n";
 
       conn->Write(bufs, [](auto bufs, uv::Error) {
-        for (auto& buf : bufs) buf.Deallocate();
+        for (auto& buf : bufs) {
+          buf.Deallocate();
+        }
       });
 
       serverHeadersDone = true;
-      if (connected) connected();
+      if (connected) {
+        connected();
+      }
     });
 
     serverPipe->Listen([this] {
       conn = serverPipe->Accept();
       conn->StartRead();
       conn->data.connect([this](uv::Buffer& buf, size_t size) {
-        StringRef data{buf.base, size};
+        std::string_view data{buf.base, size};
         if (!serverHeadersDone) {
           data = req.Execute(data);
-          if (req.HasError()) Finish();
+          if (req.HasError()) {
+            Finish();
+          }
           ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
-          if (data.empty()) return;
+          if (data.empty()) {
+            return;
+          }
         }
-        wireData.insert(wireData.end(), data.bytes_begin(), data.bytes_end());
+        wireData.insert(wireData.end(), data.begin(), data.end());
       });
       conn->end.connect([this] { Finish(); });
     });
@@ -87,12 +98,13 @@
 
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       Finish();
-      if (code != 1005 && code != 1006)
+      if (code != 1005 && code != 1006) {
         FAIL() << "Code: " << code << " Reason: " << reason;
+      }
     });
-    ws->open.connect([&](StringRef protocol) {
+    ws->open.connect([&](std::string_view protocol) {
       ++gotOpen;
       Finish();
       ASSERT_TRUE(protocol.empty());
@@ -101,7 +113,9 @@
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotOpen, 1);
 }
 
@@ -112,12 +126,12 @@
 
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
-    ws->closed.connect([&](uint16_t code, StringRef msg) {
+    ws->closed.connect([&](uint16_t code, std::string_view msg) {
       Finish();
       ++gotClosed;
       ASSERT_EQ(code, 1002) << "Message: " << msg;
     });
-    ws->open.connect([&](StringRef protocol) {
+    ws->open.connect([&](std::string_view protocol) {
       Finish();
       FAIL() << "Got open";
     });
@@ -125,7 +139,9 @@
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotClosed, 1);
 }
 
@@ -137,12 +153,13 @@
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
                                       {"myProtocol", "myProtocol2"});
-    ws->closed.connect([&](uint16_t code, StringRef msg) {
+    ws->closed.connect([&](uint16_t code, std::string_view msg) {
       Finish();
-      if (code != 1005 && code != 1006)
+      if (code != 1005 && code != 1006) {
         FAIL() << "Code: " << code << "Message: " << msg;
+      }
     });
-    ws->open.connect([&](StringRef protocol) {
+    ws->open.connect([&](std::string_view protocol) {
       ++gotOpen;
       Finish();
       ASSERT_EQ(protocol, "myProtocol");
@@ -151,7 +168,9 @@
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotOpen, 1);
 }
 
@@ -162,12 +181,12 @@
 
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
-    ws->closed.connect([&](uint16_t code, StringRef msg) {
+    ws->closed.connect([&](uint16_t code, std::string_view msg) {
       Finish();
       ++gotClosed;
       ASSERT_EQ(code, 1003) << "Message: " << msg;
     });
-    ws->open.connect([&](StringRef protocol) {
+    ws->open.connect([&](std::string_view protocol) {
       Finish();
       FAIL() << "Got open";
     });
@@ -175,7 +194,9 @@
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotClosed, 1);
 }
 
@@ -184,13 +205,13 @@
 
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
-                                      StringRef{"myProtocol"});
-    ws->closed.connect([&](uint16_t code, StringRef msg) {
+                                      {{"myProtocol"}});
+    ws->closed.connect([&](uint16_t code, std::string_view msg) {
       Finish();
       ++gotClosed;
       ASSERT_EQ(code, 1002) << "Message: " << msg;
     });
-    ws->open.connect([&](StringRef protocol) {
+    ws->open.connect([&](std::string_view protocol) {
       Finish();
       FAIL() << "Got open";
     });
@@ -198,7 +219,9 @@
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotClosed, 1);
 }
 
@@ -213,7 +236,9 @@
   WebSocketClientDataTest() {
     clientPipe->Connect(pipeName, [&] {
       ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
-      if (setupWebSocket) setupWebSocket();
+      if (setupWebSocket) {
+        setupWebSocket();
+      }
     });
   }
 
@@ -228,8 +253,8 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) {
-      ws->SendBinary(uv::Buffer(data), [&](auto bufs, uv::Error) {
+    ws->open.connect([&](std::string_view) {
+      ws->SendBinary({{data}}, [&](auto bufs, uv::Error) {
         ++gotCallback;
         ws->Terminate();
         ASSERT_FALSE(bufs.empty());
@@ -250,7 +275,7 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+    ws->binary.connect([&](auto inData, bool fin) {
       ++gotCallback;
       ws->Terminate();
       ASSERT_TRUE(fin);
@@ -259,9 +284,7 @@
     });
   };
   auto message = BuildMessage(0x02, true, false, data);
-  connected = [&] {
-    conn->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
-  };
+  connected = [&] { conn->Write({{message}}, [&](auto bufs, uv::Error) {}); };
 
   loop->Run();
 
@@ -276,19 +299,17 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), ' ');
   setupWebSocket = [&] {
-    ws->text.connect([&](StringRef, bool) {
+    ws->text.connect([&](std::string_view, bool) {
       ws->Terminate();
       FAIL() << "Should not have gotten masked message";
     });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1002) << "reason: " << reason;
     });
   };
   auto message = BuildMessage(0x01, true, true, data);
-  connected = [&] {
-    conn->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
-  };
+  connected = [&] { conn->Write({{message}}, [&](auto bufs, uv::Error) {}); };
 
   loop->Run();
 
diff --git a/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp b/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp
index 9a66a2e..3de75d9 100644
--- a/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp
+++ b/wpiutil/src/test/native/cpp/WebSocketIntegrationTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/WebSocketServer.h"  // NOLINT(build/include_order)
 
@@ -22,7 +19,7 @@
   serverPipe->Listen([&]() {
     auto conn = serverPipe->Accept();
     auto server = WebSocketServer::Create(*conn);
-    server->connected.connect([&](StringRef url, WebSocket&) {
+    server->connected.connect([&](std::string_view url, WebSocket&) {
       ++gotServerOpen;
       ASSERT_EQ(url, "/test");
     });
@@ -30,12 +27,13 @@
 
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       Finish();
-      if (code != 1005 && code != 1006)
+      if (code != 1005 && code != 1006) {
         FAIL() << "Code: " << code << " Reason: " << reason;
+      }
     });
-    ws->open.connect([&, s = ws.get()](StringRef) {
+    ws->open.connect([&, s = ws.get()](std::string_view) {
       ++gotClientOpen;
       s->Close();
     });
@@ -54,7 +52,7 @@
   serverPipe->Listen([&]() {
     auto conn = serverPipe->Accept();
     auto server = WebSocketServer::Create(*conn, {"proto1", "proto2"});
-    server->connected.connect([&](StringRef, WebSocket& ws) {
+    server->connected.connect([&](std::string_view, WebSocket& ws) {
       ++gotServerOpen;
       ASSERT_EQ(ws.GetProtocol(), "proto1");
     });
@@ -63,12 +61,13 @@
   clientPipe->Connect(pipeName, [&] {
     auto ws =
         WebSocket::CreateClient(*clientPipe, "/test", pipeName, {"proto1"});
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       Finish();
-      if (code != 1005 && code != 1006)
+      if (code != 1005 && code != 1006) {
         FAIL() << "Code: " << code << " Reason: " << reason;
+      }
     });
-    ws->open.connect([&, s = ws.get()](StringRef protocol) {
+    ws->open.connect([&, s = ws.get()](std::string_view protocol) {
       ++gotClientOpen;
       s->Close();
       ASSERT_EQ(protocol, "proto1");
@@ -87,20 +86,21 @@
   serverPipe->Listen([&]() {
     auto conn = serverPipe->Accept();
     auto server = WebSocketServer::Create(*conn);
-    server->connected.connect([&](StringRef, WebSocket& ws) {
-      ws.SendBinary(uv::Buffer{"\x03\x04", 2}, [&](auto, uv::Error) {});
+    server->connected.connect([&](std::string_view, WebSocket& ws) {
+      ws.SendBinary({uv::Buffer{"\x03\x04", 2}}, [&](auto, uv::Error) {});
       ws.Close();
     });
   });
 
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       Finish();
-      if (code != 1005 && code != 1006)
+      if (code != 1005 && code != 1006) {
         FAIL() << "Code: " << code << " Reason: " << reason;
+      }
     });
-    ws->binary.connect([&](ArrayRef<uint8_t> data, bool) {
+    ws->binary.connect([&](auto data, bool) {
       ++gotData;
       std::vector<uint8_t> recvData{data.begin(), data.end()};
       std::vector<uint8_t> expectData{0x03, 0x04};
@@ -119,8 +119,8 @@
   serverPipe->Listen([&]() {
     auto conn = serverPipe->Accept();
     auto server = WebSocketServer::Create(*conn);
-    server->connected.connect([&](StringRef, WebSocket& ws) {
-      ws.text.connect([&](StringRef data, bool) {
+    server->connected.connect([&](std::string_view, WebSocket& ws) {
+      ws.text.connect([&](std::string_view data, bool) {
         ++gotData;
         ASSERT_EQ(data, "hello");
       });
@@ -129,13 +129,14 @@
 
   clientPipe->Connect(pipeName, [&] {
     auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       Finish();
-      if (code != 1005 && code != 1006)
+      if (code != 1005 && code != 1006) {
         FAIL() << "Code: " << code << " Reason: " << reason;
+      }
     });
-    ws->open.connect([&, s = ws.get()](StringRef) {
-      s->SendText(uv::Buffer{"hello"}, [&](auto, uv::Error) {});
+    ws->open.connect([&, s = ws.get()](std::string_view) {
+      s->SendText({{"hello"}}, [&](auto, uv::Error) {});
       s->Close();
     });
   });
diff --git a/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp b/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp
index d11fdda..83561bf 100644
--- a/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp
+++ b/wpiutil/src/test/native/cpp/WebSocketServerTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/WebSocket.h"  // NOLINT(build/include_order)
 
@@ -24,28 +21,36 @@
     serverPipe->Listen([this]() {
       auto conn = serverPipe->Accept();
       ws = WebSocket::CreateServer(*conn, "foo", "13");
-      if (setupWebSocket) setupWebSocket();
+      if (setupWebSocket) {
+        setupWebSocket();
+      }
     });
     clientPipe->Connect(pipeName, [this]() {
       clientPipe->StartRead();
       clientPipe->data.connect([this](uv::Buffer& buf, size_t size) {
-        StringRef data{buf.base, size};
+        std::string_view data{buf.base, size};
         if (!headersDone) {
           data = resp.Execute(data);
-          if (resp.HasError()) Finish();
+          if (resp.HasError()) {
+            Finish();
+          }
           ASSERT_EQ(resp.GetError(), HPE_OK)
               << http_errno_name(resp.GetError());
-          if (data.empty()) return;
+          if (data.empty()) {
+            return;
+          }
         }
-        wireData.insert(wireData.end(), data.bytes_begin(), data.bytes_end());
-        if (handleData) handleData(data);
+        wireData.insert(wireData.end(), data.begin(), data.end());
+        if (handleData) {
+          handleData(data);
+        }
       });
       clientPipe->end.connect([this]() { Finish(); });
     });
   }
 
   std::function<void()> setupWebSocket;
-  std::function<void(StringRef)> handleData;
+  std::function<void(std::string_view)> handleData;
   std::vector<uint8_t> wireData;
   std::shared_ptr<WebSocket> ws;
   HttpParser resp{HttpParser::kResponse};
@@ -59,8 +64,8 @@
 TEST_F(WebSocketServerTest, Terminate) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) { ws->Terminate(); });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->open.connect([&](std::string_view) { ws->Terminate(); });
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1006) << "reason: " << reason;
     });
@@ -75,8 +80,8 @@
 TEST_F(WebSocketServerTest, TerminateCode) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) { ws->Terminate(1000); });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->open.connect([&](std::string_view) { ws->Terminate(1000); });
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1000) << "reason: " << reason;
     });
@@ -91,8 +96,8 @@
 TEST_F(WebSocketServerTest, TerminateReason) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) { ws->Terminate(1000, "reason"); });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->open.connect([&](std::string_view) { ws->Terminate(1000, "reason"); });
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1000);
       ASSERT_EQ(reason, "reason");
@@ -112,16 +117,16 @@
 TEST_F(WebSocketServerTest, CloseBasic) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) { ws->Close(); });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->open.connect([&](std::string_view) { ws->Close(); });
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1005) << "reason: " << reason;
     });
   };
   // need to respond with close for server to finish shutdown
   auto message = BuildMessage(0x08, true, true, {});
-  handleData = [&](StringRef) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  handleData = [&](std::string_view) {
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   };
 
   loop->Run();
@@ -134,8 +139,8 @@
 TEST_F(WebSocketServerTest, CloseCode) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) { ws->Close(1000); });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->open.connect([&](std::string_view) { ws->Close(1000); });
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1000) << "reason: " << reason;
     });
@@ -143,8 +148,8 @@
   // need to respond with close for server to finish shutdown
   const uint8_t contents[] = {0x03u, 0xe8u};
   auto message = BuildMessage(0x08, true, true, contents);
-  handleData = [&](StringRef) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  handleData = [&](std::string_view) {
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   };
 
   loop->Run();
@@ -157,8 +162,8 @@
 TEST_F(WebSocketServerTest, CloseReason) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) { ws->Close(1000, "hangup"); });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->open.connect([&](std::string_view) { ws->Close(1000, "hangup"); });
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1000);
       ASSERT_EQ(reason, "hangup");
@@ -167,8 +172,8 @@
   // need to respond with close for server to finish shutdown
   const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
   auto message = BuildMessage(0x08, true, true, contents);
-  handleData = [&](StringRef) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+  handleData = [&](std::string_view) {
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   };
 
   loop->Run();
@@ -185,14 +190,14 @@
 TEST_F(WebSocketServerTest, ReceiveCloseBasic) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1005) << "reason: " << reason;
     });
   };
   auto message = BuildMessage(0x08, true, true, {});
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -206,7 +211,7 @@
 TEST_F(WebSocketServerTest, ReceiveCloseCode) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1000) << "reason: " << reason;
     });
@@ -214,7 +219,7 @@
   const uint8_t contents[] = {0x03u, 0xe8u};
   auto message = BuildMessage(0x08, true, true, contents);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -228,7 +233,7 @@
 TEST_F(WebSocketServerTest, ReceiveCloseReason) {
   int gotClosed = 0;
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotClosed;
       ASSERT_EQ(code, 1000);
       ASSERT_EQ(reason, "hangup");
@@ -237,7 +242,7 @@
   const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
   auto message = BuildMessage(0x08, true, true, contents);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -266,14 +271,14 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(4, 0x03);
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1002) << "reason: " << reason;
     });
   };
   auto message = BuildMessage(GetParam(), true, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -297,14 +302,14 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(4, 0x03);
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1002) << "reason: " << reason;
     });
   };
   auto message = BuildMessage(GetParam(), false, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -324,14 +329,14 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(4, 0x03);
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1002) << "reason: " << reason;
     });
   };
   auto message = BuildMessage(0x00, false, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -344,7 +349,7 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(4, 0x03);
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1002) << "reason: " << reason;
     });
@@ -352,8 +357,7 @@
   auto message = BuildMessage(0x01, true, true, {});  // FIN=1
   auto message2 = BuildMessage(0x00, false, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write({uv::Buffer(message), uv::Buffer(message2)},
-                      [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}, {message2}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -365,7 +369,7 @@
 TEST_F(WebSocketServerTest, ReceiveFragmentInvalidIncomplete) {
   int gotCallback = 0;
   setupWebSocket = [&] {
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1002) << "reason: " << reason;
     });
@@ -374,9 +378,8 @@
   auto message2 = BuildMessage(0x00, false, true, {});
   auto message3 = BuildMessage(0x01, true, true, {});
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(
-        {uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
-        [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}, {message2}, {message3}},
+                      [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -396,7 +399,7 @@
   combData.insert(combData.end(), data3.begin(), data3.end());
 
   setupWebSocket = [&] {
-    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+    ws->binary.connect([&](auto inData, bool fin) {
       ++gotCallback;
       ws->Terminate();
       ASSERT_TRUE(fin);
@@ -409,9 +412,8 @@
   auto message2 = BuildMessage(0x00, false, true, data2);
   auto message3 = BuildMessage(0x00, true, true, data3);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(
-        {uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
-        [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}, {message2}, {message3}},
+                      [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -432,7 +434,7 @@
 
   setupWebSocket = [&] {
     ws->SetCombineFragments(false);
-    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+    ws->binary.connect([&](auto inData, bool fin) {
       std::vector<uint8_t> recvData{inData.begin(), inData.end()};
       switch (++gotCallback) {
         case 1:
@@ -459,9 +461,8 @@
   auto message2 = BuildMessage(0x00, false, true, data2);
   auto message3 = BuildMessage(0x00, true, true, data3);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(
-        {uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
-        [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}, {message2}, {message3}},
+                      [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -483,14 +484,14 @@
       ws->Terminate();
       FAIL() << "Should not have gotten unmasked message";
     });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1009) << "reason: " << reason;
     });
   };
   auto message = BuildMessage(0x01, true, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -508,7 +509,7 @@
       ws->Terminate();
       FAIL() << "Should not have gotten unmasked message";
     });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1009) << "reason: " << reason;
     });
@@ -516,8 +517,7 @@
   auto message = BuildMessage(0x01, false, true, data);
   auto message2 = BuildMessage(0x00, true, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write({uv::Buffer(message), uv::Buffer(message2)},
-                      [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}, {message2}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -539,8 +539,8 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), ' ');
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) {
-      ws->SendText(uv::Buffer(data), [&](auto bufs, uv::Error) {
+    ws->open.connect([&](std::string_view) {
+      ws->SendText({{data}}, [&](auto bufs, uv::Error) {
         ++gotCallback;
         ws->Terminate();
         ASSERT_FALSE(bufs.empty());
@@ -560,8 +560,8 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) {
-      ws->SendBinary(uv::Buffer(data), [&](auto bufs, uv::Error) {
+    ws->open.connect([&](std::string_view) {
+      ws->SendBinary({{data}}, [&](auto bufs, uv::Error) {
         ++gotCallback;
         ws->Terminate();
         ASSERT_FALSE(bufs.empty());
@@ -581,8 +581,8 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) {
-      ws->SendPing(uv::Buffer(data), [&](auto bufs, uv::Error) {
+    ws->open.connect([&](std::string_view) {
+      ws->SendPing({{data}}, [&](auto bufs, uv::Error) {
         ++gotCallback;
         ws->Terminate();
         ASSERT_FALSE(bufs.empty());
@@ -602,8 +602,8 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->open.connect([&](StringRef) {
-      ws->SendPong(uv::Buffer(data), [&](auto bufs, uv::Error) {
+    ws->open.connect([&](std::string_view) {
+      ws->SendPong({{data}}, [&](auto bufs, uv::Error) {
         ++gotCallback;
         ws->Terminate();
         ASSERT_FALSE(bufs.empty());
@@ -623,18 +623,18 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), ' ');
   setupWebSocket = [&] {
-    ws->text.connect([&](StringRef inData, bool fin) {
+    ws->text.connect([&](std::string_view inData, bool fin) {
       ++gotCallback;
       ws->Terminate();
       ASSERT_TRUE(fin);
       std::vector<uint8_t> recvData;
-      recvData.insert(recvData.end(), inData.bytes_begin(), inData.bytes_end());
+      recvData.insert(recvData.end(), inData.begin(), inData.end());
       ASSERT_EQ(data, recvData);
     });
   };
   auto message = BuildMessage(0x01, true, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -646,7 +646,7 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
+    ws->binary.connect([&](auto inData, bool fin) {
       ++gotCallback;
       ws->Terminate();
       ASSERT_TRUE(fin);
@@ -656,7 +656,7 @@
   };
   auto message = BuildMessage(0x02, true, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -668,7 +668,7 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->ping.connect([&](ArrayRef<uint8_t> inData) {
+    ws->ping.connect([&](auto inData) {
       ++gotCallback;
       ws->Terminate();
       std::vector<uint8_t> recvData{inData.begin(), inData.end()};
@@ -677,7 +677,7 @@
   };
   auto message = BuildMessage(0x09, true, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -689,7 +689,7 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), 0x03u);
   setupWebSocket = [&] {
-    ws->pong.connect([&](ArrayRef<uint8_t> inData) {
+    ws->pong.connect([&](auto inData) {
       ++gotCallback;
       ws->Terminate();
       std::vector<uint8_t> recvData{inData.begin(), inData.end()};
@@ -698,7 +698,7 @@
   };
   auto message = BuildMessage(0x0a, true, true, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
@@ -714,18 +714,18 @@
   int gotCallback = 0;
   std::vector<uint8_t> data(GetParam(), ' ');
   setupWebSocket = [&] {
-    ws->text.connect([&](StringRef, bool) {
+    ws->text.connect([&](std::string_view, bool) {
       ws->Terminate();
       FAIL() << "Should not have gotten unmasked message";
     });
-    ws->closed.connect([&](uint16_t code, StringRef reason) {
+    ws->closed.connect([&](uint16_t code, std::string_view reason) {
       ++gotCallback;
       ASSERT_EQ(code, 1002) << "reason: " << reason;
     });
   };
   auto message = BuildMessage(0x01, true, false, data);
   resp.headersComplete.connect([&](bool) {
-    clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
+    clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
   });
 
   loop->Run();
diff --git a/wpiutil/src/test/native/cpp/WebSocketTest.cpp b/wpiutil/src/test/native/cpp/WebSocketTest.cpp
index c27bac0..b5d5e4e 100644
--- a/wpiutil/src/test/native/cpp/WebSocketTest.cpp
+++ b/wpiutil/src/test/native/cpp/WebSocketTest.cpp
@@ -1,15 +1,13 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/WebSocket.h"  // NOLINT(build/include_order)
 
 #include "WebSocketTest.h"
 
 #include "wpi/HttpParser.h"
+#include "wpi/StringExtras.h"
 
 namespace wpi {
 
@@ -38,15 +36,19 @@
     data.push_back(len & 0xff);
   } else {
     data.push_back(127u | (masking ? 0x80u : 0x00u));
-    for (int i = 56; i >= 0; i -= 8) data.push_back((len >> i) & 0xff);
+    for (int i = 56; i >= 0; i -= 8) {
+      data.push_back((len >> i) & 0xff);
+    }
   }
-  if (masking) data.insert(data.end(), &testMask[0], &testMask[4]);
+  if (masking) {
+    data.insert(data.end(), &testMask[0], &testMask[4]);
+  }
   return data;
 }
 
 std::vector<uint8_t> WebSocketTest::BuildMessage(uint8_t opcode, bool fin,
                                                  bool masking,
-                                                 ArrayRef<uint8_t> data) {
+                                                 span<const uint8_t> data) {
   auto finalData = BuildHeader(opcode, fin, masking, data.size());
   size_t headerSize = finalData.size();
   finalData.insert(finalData.end(), data.begin(), data.end());
@@ -56,7 +58,9 @@
     int n = 0;
     for (size_t i = headerSize, end = finalData.size(); i < end; ++i) {
       finalData[i] ^= mask[n++];
-      if (n >= 4) n = 0;
+      if (n >= 4) {
+        n = 0;
+      }
     }
   }
   return finalData;
@@ -64,17 +68,22 @@
 
 // If the message is masked, changes the mask to match the mask set by
 // BuildHeader() by unmasking and remasking.
-void WebSocketTest::AdjustMasking(MutableArrayRef<uint8_t> message) {
-  if (message.size() < 2) return;
-  if ((message[1] & 0x80) == 0) return;  // not masked
+void WebSocketTest::AdjustMasking(span<uint8_t> message) {
+  if (message.size() < 2) {
+    return;
+  }
+  if ((message[1] & 0x80) == 0) {
+    return;  // not masked
+  }
   size_t maskPos;
   uint8_t len = message[1] & 0x7f;
-  if (len == 126)
+  if (len == 126) {
     maskPos = 4;
-  else if (len == 127)
+  } else if (len == 127) {
     maskPos = 10;
-  else
+  } else {
     maskPos = 2;
+  }
   uint8_t mask[4] = {message[maskPos], message[maskPos + 1],
                      message[maskPos + 2], message[maskPos + 3]};
   message[maskPos] = testMask[0];
@@ -82,9 +91,11 @@
   message[maskPos + 2] = testMask[2];
   message[maskPos + 3] = testMask[3];
   int n = 0;
-  for (auto& ch : message.slice(maskPos + 4)) {
+  for (auto& ch : message.subspan(maskPos + 4)) {
     ch ^= mask[n] ^ testMask[n];
-    if (++n >= 4) n = 0;
+    if (++n >= 4) {
+      n = 0;
+    }
   }
 }
 
@@ -96,24 +107,24 @@
   int gotVersion = 0;
 
   HttpParser req{HttpParser::kRequest};
-  req.url.connect([](StringRef url) { ASSERT_EQ(url, "/test"); });
-  req.header.connect([&](StringRef name, StringRef value) {
-    if (name.equals_lower("host")) {
+  req.url.connect([](std::string_view url) { ASSERT_EQ(url, "/test"); });
+  req.header.connect([&](std::string_view name, std::string_view value) {
+    if (equals_lower(name, "host")) {
       ASSERT_EQ(value, pipeName);
       ++gotHost;
-    } else if (name.equals_lower("upgrade")) {
+    } else if (equals_lower(name, "upgrade")) {
       ASSERT_EQ(value, "websocket");
       ++gotUpgrade;
-    } else if (name.equals_lower("connection")) {
+    } else if (equals_lower(name, "connection")) {
       ASSERT_EQ(value, "Upgrade");
       ++gotConnection;
-    } else if (name.equals_lower("sec-websocket-key")) {
+    } else if (equals_lower(name, "sec-websocket-key")) {
       ++gotKey;
-    } else if (name.equals_lower("sec-websocket-version")) {
+    } else if (equals_lower(name, "sec-websocket-version")) {
       ASSERT_EQ(value, "13");
       ++gotVersion;
     } else {
-      FAIL() << "unexpected header " << name.str();
+      FAIL() << "unexpected header " << name;
     }
   });
   req.headersComplete.connect([&](bool) { Finish(); });
@@ -122,8 +133,10 @@
     auto conn = serverPipe->Accept();
     conn->StartRead();
     conn->data.connect([&](uv::Buffer& buf, size_t size) {
-      req.Execute(StringRef{buf.base, size});
-      if (req.HasError()) Finish();
+      req.Execute(std::string_view{buf.base, size});
+      if (req.HasError()) {
+        Finish();
+      }
       ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
     });
   });
@@ -133,7 +146,9 @@
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotHost, 1);
   ASSERT_EQ(gotUpgrade, 1);
   ASSERT_EQ(gotConnection, 1);
@@ -145,11 +160,11 @@
   int gotExtra1 = 0;
   int gotExtra2 = 0;
   HttpParser req{HttpParser::kRequest};
-  req.header.connect([&](StringRef name, StringRef value) {
-    if (name.equals("Extra1")) {
+  req.header.connect([&](std::string_view name, std::string_view value) {
+    if (equals(name, "Extra1")) {
       ASSERT_EQ(value, "Data1");
       ++gotExtra1;
-    } else if (name.equals("Extra2")) {
+    } else if (equals(name, "Extra2")) {
       ASSERT_EQ(value, "Data2");
       ++gotExtra2;
     }
@@ -160,24 +175,28 @@
     auto conn = serverPipe->Accept();
     conn->StartRead();
     conn->data.connect([&](uv::Buffer& buf, size_t size) {
-      req.Execute(StringRef{buf.base, size});
-      if (req.HasError()) Finish();
+      req.Execute(std::string_view{buf.base, size});
+      if (req.HasError()) {
+        Finish();
+      }
       ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
     });
   });
   clientPipe->Connect(pipeName, [&]() {
     WebSocket::ClientOptions options;
-    SmallVector<std::pair<StringRef, StringRef>, 4> extraHeaders;
+    SmallVector<std::pair<std::string_view, std::string_view>, 4> extraHeaders;
     extraHeaders.emplace_back("Extra1", "Data1");
     extraHeaders.emplace_back("Extra2", "Data2");
     options.extraHeaders = extraHeaders;
-    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
-                                      ArrayRef<StringRef>{}, options);
+    auto ws =
+        WebSocket::CreateClient(*clientPipe, "/test", pipeName, {}, options);
   });
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotExtra1, 1);
   ASSERT_EQ(gotExtra2, 1);
 }
@@ -188,9 +207,9 @@
   clientPipe->Connect(pipeName, [&]() {
     WebSocket::ClientOptions options;
     options.handshakeTimeout = uv::Timer::Time{100};
-    auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
-                                      ArrayRef<StringRef>{}, options);
-    ws->closed.connect([&](uint16_t code, StringRef) {
+    auto ws =
+        WebSocket::CreateClient(*clientPipe, "/test", pipeName, {}, options);
+    ws->closed.connect([&](uint16_t code, std::string_view) {
       Finish();
       ++gotClosed;
       ASSERT_EQ(code, 1006);
@@ -199,7 +218,9 @@
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotClosed, 1);
 }
 
@@ -211,22 +232,22 @@
   int gotOpen = 0;
 
   HttpParser resp{HttpParser::kResponse};
-  resp.status.connect([&](StringRef status) {
+  resp.status.connect([&](std::string_view status) {
     ++gotStatus;
     ASSERT_EQ(resp.GetStatusCode(), 101u) << "status: " << status;
   });
-  resp.header.connect([&](StringRef name, StringRef value) {
-    if (name.equals_lower("upgrade")) {
+  resp.header.connect([&](std::string_view name, std::string_view value) {
+    if (equals_lower(name, "upgrade")) {
       ASSERT_EQ(value, "websocket");
       ++gotUpgrade;
-    } else if (name.equals_lower("connection")) {
+    } else if (equals_lower(name, "connection")) {
       ASSERT_EQ(value, "Upgrade");
       ++gotConnection;
-    } else if (name.equals_lower("sec-websocket-accept")) {
+    } else if (equals_lower(name, "sec-websocket-accept")) {
       ASSERT_EQ(value, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
       ++gotAccept;
     } else {
-      FAIL() << "unexpected header " << name.str();
+      FAIL() << "unexpected header " << name;
     }
   });
   resp.headersComplete.connect([&](bool) { Finish(); });
@@ -234,7 +255,7 @@
   serverPipe->Listen([&]() {
     auto conn = serverPipe->Accept();
     auto ws = WebSocket::CreateServer(*conn, "dGhlIHNhbXBsZSBub25jZQ==", "13");
-    ws->open.connect([&](StringRef protocol) {
+    ws->open.connect([&](std::string_view protocol) {
       ++gotOpen;
       ASSERT_TRUE(protocol.empty());
     });
@@ -242,15 +263,19 @@
   clientPipe->Connect(pipeName, [&] {
     clientPipe->StartRead();
     clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
-      resp.Execute(StringRef{buf.base, size});
-      if (resp.HasError()) Finish();
+      resp.Execute(std::string_view{buf.base, size});
+      if (resp.HasError()) {
+        Finish();
+      }
       ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
     });
   });
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotStatus, 1);
   ASSERT_EQ(gotUpgrade, 1);
   ASSERT_EQ(gotConnection, 1);
@@ -263,8 +288,8 @@
   int gotOpen = 0;
 
   HttpParser resp{HttpParser::kResponse};
-  resp.header.connect([&](StringRef name, StringRef value) {
-    if (name.equals_lower("sec-websocket-protocol")) {
+  resp.header.connect([&](std::string_view name, std::string_view value) {
+    if (equals_lower(name, "sec-websocket-protocol")) {
       ++gotProtocol;
       ASSERT_EQ(value, "myProtocol");
     }
@@ -274,7 +299,7 @@
   serverPipe->Listen([&]() {
     auto conn = serverPipe->Accept();
     auto ws = WebSocket::CreateServer(*conn, "foo", "13", "myProtocol");
-    ws->open.connect([&](StringRef protocol) {
+    ws->open.connect([&](std::string_view protocol) {
       ++gotOpen;
       ASSERT_EQ(protocol, "myProtocol");
     });
@@ -282,15 +307,19 @@
   clientPipe->Connect(pipeName, [&] {
     clientPipe->StartRead();
     clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
-      resp.Execute(StringRef{buf.base, size});
-      if (resp.HasError()) Finish();
+      resp.Execute(std::string_view{buf.base, size});
+      if (resp.HasError()) {
+        Finish();
+      }
       ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
     });
   });
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotProtocol, 1);
   ASSERT_EQ(gotOpen, 1);
 }
@@ -301,19 +330,19 @@
   int gotUpgrade = 0;
 
   HttpParser resp{HttpParser::kResponse};
-  resp.status.connect([&](StringRef status) {
+  resp.status.connect([&](std::string_view status) {
     ++gotStatus;
     ASSERT_EQ(resp.GetStatusCode(), 426u) << "status: " << status;
   });
-  resp.header.connect([&](StringRef name, StringRef value) {
-    if (name.equals_lower("sec-websocket-version")) {
+  resp.header.connect([&](std::string_view name, std::string_view value) {
+    if (equals_lower(name, "sec-websocket-version")) {
       ++gotVersion;
       ASSERT_EQ(value, "13");
-    } else if (name.equals_lower("upgrade")) {
+    } else if (equals_lower(name, "upgrade")) {
       ++gotUpgrade;
       ASSERT_EQ(value, "WebSocket");
     } else {
-      FAIL() << "unexpected header " << name.str();
+      FAIL() << "unexpected header " << name;
     }
   });
   resp.headersComplete.connect([&](bool) { Finish(); });
@@ -321,7 +350,7 @@
   serverPipe->Listen([&] {
     auto conn = serverPipe->Accept();
     auto ws = WebSocket::CreateServer(*conn, "foo", "14");
-    ws->open.connect([&](StringRef) {
+    ws->open.connect([&](std::string_view) {
       Finish();
       FAIL();
     });
@@ -329,15 +358,19 @@
   clientPipe->Connect(pipeName, [&] {
     clientPipe->StartRead();
     clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
-      resp.Execute(StringRef{buf.base, size});
-      if (resp.HasError()) Finish();
+      resp.Execute(std::string_view{buf.base, size});
+      if (resp.HasError()) {
+        Finish();
+      }
       ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
     });
   });
 
   loop->Run();
 
-  if (HasFatalFailure()) return;
+  if (HasFatalFailure()) {
+    return;
+  }
   ASSERT_EQ(gotStatus, 1);
   ASSERT_EQ(gotVersion, 1);
   ASSERT_EQ(gotUpgrade, 1);
diff --git a/wpiutil/src/test/native/cpp/WebSocketTest.h b/wpiutil/src/test/native/cpp/WebSocketTest.h
index 8b40440..fda4473 100644
--- a/wpiutil/src/test/native/cpp/WebSocketTest.h
+++ b/wpiutil/src/test/native/cpp/WebSocketTest.h
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #pragma once
 
@@ -12,7 +9,7 @@
 #include <vector>
 
 #include "gtest/gtest.h"
-#include "wpi/ArrayRef.h"
+#include "wpi/span.h"
 #include "wpi/uv/Loop.h"
 #include "wpi/uv/Pipe.h"
 #include "wpi/uv/Timer.h"
@@ -51,7 +48,7 @@
     failTimer->Unreference();
   }
 
-  ~WebSocketTest() { Finish(); }
+  ~WebSocketTest() override { Finish(); }
 
   void Finish() {
     loop->Walk([](uv::Handle& it) { it.Close(); });
@@ -61,8 +58,8 @@
                                           bool masking, uint64_t len);
   static std::vector<uint8_t> BuildMessage(uint8_t opcode, bool fin,
                                            bool masking,
-                                           ArrayRef<uint8_t> data);
-  static void AdjustMasking(MutableArrayRef<uint8_t> message);
+                                           span<const uint8_t> data);
+  static void AdjustMasking(span<uint8_t> message);
   static const uint8_t testMask[4];
 
   std::shared_ptr<uv::Loop> loop;
diff --git a/wpiutil/src/test/native/cpp/WorkerThreadTest.cpp b/wpiutil/src/test/native/cpp/WorkerThreadTest.cpp
index 610029c..9bc9a17 100644
--- a/wpiutil/src/test/native/cpp/WorkerThreadTest.cpp
+++ b/wpiutil/src/test/native/cpp/WorkerThreadTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/WorkerThread.h"  // NOLINT(build/include_order)
 
@@ -15,14 +12,14 @@
 
 namespace wpi {
 
-TEST(WorkerThread, Future) {
+TEST(WorkerThreadTest, Future) {
   WorkerThread<int(bool)> worker;
   future<int> f =
       worker.QueueWork([](bool v) -> int { return v ? 1 : 2; }, true);
   ASSERT_EQ(f.get(), 1);
 }
 
-TEST(WorkerThread, FutureVoid) {
+TEST(WorkerThreadTest, FutureVoid) {
   int callbacks = 0;
   WorkerThread<void(int)> worker;
   future<void> f = worker.QueueWork(
@@ -35,7 +32,7 @@
   ASSERT_EQ(callbacks, 1);
 }
 
-TEST(WorkerThread, Loop) {
+TEST(WorkerThreadTest, Loop) {
   int callbacks = 0;
   WorkerThread<int(bool)> worker;
   auto loop = uv::Loop::Create();
@@ -53,7 +50,7 @@
   ASSERT_EQ(callbacks, 1);
 }
 
-TEST(WorkerThread, LoopVoid) {
+TEST(WorkerThreadTest, LoopVoid) {
   int callbacks = 0;
   WorkerThread<void(bool)> worker;
   auto loop = uv::Loop::Create();
diff --git a/wpiutil/src/test/native/cpp/future_test.cpp b/wpiutil/src/test/native/cpp/future_test.cpp
index b9b79a8..8817f2d 100644
--- a/wpiutil/src/test/native/cpp/future_test.cpp
+++ b/wpiutil/src/test/native/cpp/future_test.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/future.h"  // NOLINT(build/include_order)
 
@@ -13,7 +10,7 @@
 
 namespace wpi {
 
-TEST(Future, Then) {
+TEST(FutureTest, Then) {
   promise<bool> inPromise;
   future<int> outFuture =
       inPromise.get_future().then([](bool v) { return v ? 5 : 6; });
@@ -22,7 +19,7 @@
   ASSERT_EQ(outFuture.get(), 5);
 }
 
-TEST(Future, ThenSame) {
+TEST(FutureTest, ThenSame) {
   promise<bool> inPromise;
   future<bool> outFuture =
       inPromise.get_future().then([](bool v) { return !v; });
@@ -31,7 +28,7 @@
   ASSERT_EQ(outFuture.get(), false);
 }
 
-TEST(Future, ThenFromVoid) {
+TEST(FutureTest, ThenFromVoid) {
   promise<void> inPromise;
   future<int> outFuture = inPromise.get_future().then([] { return 5; });
 
@@ -39,7 +36,7 @@
   ASSERT_EQ(outFuture.get(), 5);
 }
 
-TEST(Future, ThenToVoid) {
+TEST(FutureTest, ThenToVoid) {
   promise<bool> inPromise;
   future<void> outFuture = inPromise.get_future().then([](bool v) {});
 
@@ -47,7 +44,7 @@
   ASSERT_TRUE(outFuture.is_ready());
 }
 
-TEST(Future, ThenVoidVoid) {
+TEST(FutureTest, ThenVoidVoid) {
   promise<void> inPromise;
   future<void> outFuture = inPromise.get_future().then([] {});
 
@@ -55,7 +52,7 @@
   ASSERT_TRUE(outFuture.is_ready());
 }
 
-TEST(Future, Implicit) {
+TEST(FutureTest, Implicit) {
   promise<bool> inPromise;
   future<int> outFuture = inPromise.get_future();
 
@@ -63,7 +60,7 @@
   ASSERT_EQ(outFuture.get(), 1);
 }
 
-TEST(Future, MoveSame) {
+TEST(FutureTest, MoveSame) {
   promise<bool> inPromise;
   future<bool> outFuture1 = inPromise.get_future();
   future<bool> outFuture(std::move(outFuture1));
@@ -72,7 +69,7 @@
   ASSERT_EQ(outFuture.get(), true);
 }
 
-TEST(Future, MoveVoid) {
+TEST(FutureTest, MoveVoid) {
   promise<void> inPromise;
   future<void> outFuture1 = inPromise.get_future();
   future<void> outFuture(std::move(outFuture1));
diff --git a/wpiutil/src/test/native/cpp/hostname.cpp b/wpiutil/src/test/native/cpp/hostname.cpp
index d5ec535..29cceb1 100644
--- a/wpiutil/src/test/native/cpp/hostname.cpp
+++ b/wpiutil/src/test/native/cpp/hostname.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/hostname.h"
 
@@ -12,7 +9,9 @@
 #include "wpi/SmallVector.h"
 
 namespace wpi {
-TEST(HostNameTest, HostNameNotEmpty) { ASSERT_NE(GetHostname(), ""); }
+TEST(HostNameTest, HostNameNotEmpty) {
+  ASSERT_NE(GetHostname(), "");
+}
 TEST(HostNameTest, HostNameNotEmptySmallVector) {
   SmallVector<char, 256> name;
   ASSERT_NE(GetHostname(name), "");
diff --git a/wpiutil/src/test/native/cpp/json/unit-readme.cpp b/wpiutil/src/test/native/cpp/json/unit-readme.cpp
index ecb7f79..14d2b1b 100644
--- a/wpiutil/src/test/native/cpp/json/unit-readme.cpp
+++ b/wpiutil/src/test/native/cpp/json/unit-readme.cpp
@@ -200,7 +200,7 @@
 {
     std::vector<int> c_vector {1, 2, 3, 4};
     json j_vec(c_vector);
-    json j_vec2(wpi::makeArrayRef(c_vector));
+    json j_vec2(wpi::span<const int>(c_vector.data(), c_vector.size()));
     // [1, 2, 3, 4]
 
     std::deque<float> c_deque {1.2f, 2.3f, 3.4f, 5.6f};
diff --git a/wpiutil/src/test/native/cpp/json/unit-unicode.cpp b/wpiutil/src/test/native/cpp/json/unit-unicode.cpp
index e0179b6..6755fe2 100644
--- a/wpiutil/src/test/native/cpp/json/unit-unicode.cpp
+++ b/wpiutil/src/test/native/cpp/json/unit-unicode.cpp
@@ -37,7 +37,7 @@
 #include "unit-json.h"
 using wpi::json;
 
-#include "wpi/Format.h"
+#include "fmt/format.h"
 #include "wpi/StringExtras.h"
 #include "wpi/raw_ostream.h"
 
@@ -919,11 +919,7 @@
     // reverse solidus, followed by the lowercase letter u, followed
     // by four hexadecimal digits that encode the character's code
     // point
-    std::string s;
-    wpi::raw_string_ostream ss(s);
-    ss << "\\u" << wpi::format_hex_no_prefix(cp, 4);
-    ss.flush();
-    return s;
+    return fmt::format("\\u{:04x}", cp);
 }
 
 // correct sequences
diff --git a/wpiutil/src/test/native/cpp/leb128Test.cpp b/wpiutil/src/test/native/cpp/leb128Test.cpp
index a3a7ed0..cc537ae 100644
--- a/wpiutil/src/test/native/cpp/leb128Test.cpp
+++ b/wpiutil/src/test/native/cpp/leb128Test.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 //===- llvm/unittest/Support/LEB128Test.cpp - LEB128 function tests -------===//
 //
@@ -17,23 +14,23 @@
 #include <stdint.h>
 
 #include <string>
+#include <string_view>
 
 #include "gtest/gtest.h"
 #include "wpi/SmallString.h"
-#include "wpi/StringRef.h"
 #include "wpi/leb128.h"
 #include "wpi/raw_istream.h"
 
 namespace wpi {
 
 TEST(LEB128Test, WriteUleb128) {
-#define EXPECT_ULEB128_EQ(EXPECTED, VALUE, PAD)         \
-  do {                                                  \
-    StringRef expected(EXPECTED, sizeof(EXPECTED) - 1); \
-    SmallString<32> buf;                                \
-    size_t size = WriteUleb128(buf, VALUE);             \
-    EXPECT_EQ(size, buf.size());                        \
-    EXPECT_EQ(expected, buf.str());                     \
+#define EXPECT_ULEB128_EQ(EXPECTED, VALUE, PAD)                \
+  do {                                                         \
+    std::string_view expected(EXPECTED, sizeof(EXPECTED) - 1); \
+    SmallString<32> buf;                                       \
+    size_t size = WriteUleb128(buf, VALUE);                    \
+    EXPECT_EQ(size, buf.size());                               \
+    EXPECT_EQ(expected, buf.str());                            \
   } while (0)
 
   // Write ULEB128
@@ -48,6 +45,8 @@
   EXPECT_ULEB128_EQ("\xff\x01", 0xff, 0);
   EXPECT_ULEB128_EQ("\x80\x02", 0x100, 0);
   EXPECT_ULEB128_EQ("\x81\x02", 0x101, 0);
+  EXPECT_ULEB128_EQ("\x80\x41", 0x2080, 0);
+  EXPECT_ULEB128_EQ("\x80\xc1\x80\x80\x10", 0x100002080, 0);
 
 #undef EXPECT_ULEB128_EQ
 }
@@ -73,7 +72,8 @@
   EXPECT_READ_ULEB128_EQ(0xffu, "\xff\x01");
   EXPECT_READ_ULEB128_EQ(0x100u, "\x80\x02");
   EXPECT_READ_ULEB128_EQ(0x101u, "\x81\x02");
-  EXPECT_READ_ULEB128_EQ(8320u, "\x80\xc1\x80\x80\x10");
+  EXPECT_READ_ULEB128_EQ(0x2080u, "\x80\x41");
+  EXPECT_READ_ULEB128_EQ(0x100002080u, "\x80\xc1\x80\x80\x10");
 
 #undef EXPECT_READ_ULEB128_EQ
 }
diff --git a/wpiutil/src/test/native/cpp/main.cpp b/wpiutil/src/test/native/cpp/main.cpp
index 1e5ecf0..09072ee 100644
--- a/wpiutil/src/test/native/cpp/main.cpp
+++ b/wpiutil/src/test/native/cpp/main.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2015-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "gtest/gtest.h"
 
diff --git a/wpiutil/src/test/native/cpp/priority_mutex_test.cpp b/wpiutil/src/test/native/cpp/priority_mutex_test.cpp
index 448a757..832e8f3 100644
--- a/wpiutil/src/test/native/cpp/priority_mutex_test.cpp
+++ b/wpiutil/src/test/native/cpp/priority_mutex_test.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2016-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include <wpi/priority_mutex.h>  // NOLINT(build/include_order)
 
@@ -58,7 +55,7 @@
 
 void SetThreadRealtimePriorityOrDie(int32_t priority) {
   struct sched_param param;
-  // Set realtime priority for this thread
+  // Set real-time priority for this thread
   param.sched_priority = priority + sched_get_priority_min(SCHED_RR);
   ASSERT_EQ(pthread_setschedparam(pthread_self(), SCHED_RR, &param), 0)
       << ": Failed to set scheduler priority.";
@@ -225,7 +222,7 @@
 // TODO: Fix roborio permissions to run as root.
 
 // Priority inversion test.
-TEST(MutexTest, DISABLED_PriorityInversionTest) {
+TEST(MutexTest, DISABLED_PriorityInversion) {
   InversionTestRunner<priority_mutex> runner;
   std::thread runner_thread(std::ref(runner));
   runner_thread.join();
@@ -233,7 +230,7 @@
 }
 
 // Verify that the non-priority inversion mutex doesn't pass the test.
-TEST(MutexTest, DISABLED_StdMutexPriorityInversionTest) {
+TEST(MutexTest, DISABLED_StdMutexPriorityInversion) {
   InversionTestRunner<std::mutex> runner;
   std::thread runner_thread(std::ref(runner));
   runner_thread.join();
@@ -250,7 +247,7 @@
 }
 
 // Priority inversion test.
-TEST(MutexTest, DISABLED_ReentrantPriorityInversionTest) {
+TEST(MutexTest, DISABLED_ReentrantPriorityInversion) {
   InversionTestRunner<priority_recursive_mutex> runner;
   std::thread runner_thread(std::ref(runner));
   runner_thread.join();
diff --git a/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp b/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp
index d1a0f6c..985fb8b 100644
--- a/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp
+++ b/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/raw_uv_ostream.h"  // NOLINT(build/include_order)
 
@@ -22,6 +19,10 @@
   ASSERT_EQ(bufs[0].base[1], '2');
   ASSERT_EQ(bufs[0].base[2], '3');
   ASSERT_EQ(bufs[0].base[3], '4');
+
+  for (auto& buf : bufs) {
+    buf.Deallocate();
+  }
 }
 
 TEST(RawUvStreamTest, BoundaryWrite) {
@@ -34,6 +35,10 @@
   ASSERT_EQ(bufs.size(), 1u);
   os << "56";
   ASSERT_EQ(bufs.size(), 2u);
+
+  for (auto& buf : bufs) {
+    buf.Deallocate();
+  }
 }
 
 TEST(RawUvStreamTest, LargeWrite) {
@@ -43,6 +48,10 @@
   ASSERT_EQ(bufs.size(), 2u);
   ASSERT_EQ(bufs[1].len, 2u);
   ASSERT_EQ(bufs[1].base[0], '5');
+
+  for (auto& buf : bufs) {
+    buf.Deallocate();
+  }
 }
 
 TEST(RawUvStreamTest, PrevDataWrite) {
@@ -53,6 +62,10 @@
   ASSERT_EQ(bufs.size(), 2u);
   ASSERT_EQ(bufs[0].len, 1024u);
   ASSERT_EQ(bufs[1].len, 4u);
+
+  for (auto& buf : bufs) {
+    buf.Deallocate();
+  }
 }
 
 }  // namespace wpi
diff --git a/wpiutil/src/test/native/cpp/sha1Test.cpp b/wpiutil/src/test/native/cpp/sha1Test.cpp
index 08d85fe..0257431 100644
--- a/wpiutil/src/test/native/cpp/sha1Test.cpp
+++ b/wpiutil/src/test/native/cpp/sha1Test.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /*
     test_sha1.cpp - test program of
diff --git a/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp b/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp
index 240e688..f31e296 100644
--- a/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp
+++ b/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /*
 
@@ -119,7 +116,7 @@
 
 namespace wpi {
 
-TEST(Signal, FunctionTraits) {
+TEST(SignalTest, FunctionTraits) {
   auto l1 = [](int, char, float) {};
   auto l2 = [&](int, char, float) mutable {};
   auto l3 = [&](auto...) mutable {};
diff --git a/wpiutil/src/test/native/cpp/sigslot/recursive.cpp b/wpiutil/src/test/native/cpp/sigslot/recursive.cpp
index 85ae9a9..82613f2 100644
--- a/wpiutil/src/test/native/cpp/sigslot/recursive.cpp
+++ b/wpiutil/src/test/native/cpp/sigslot/recursive.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /*
 
@@ -42,7 +39,7 @@
 
 template <typename T>
 struct object {
-  object(T i) : v{i} {}  // NOLINT(runtime/explicit)
+  object(T i) : v{i} {}  // NOLINT
 
   void inc_val(const T& i) {
     if (i != v) {
@@ -66,7 +63,7 @@
 
 namespace wpi {
 
-TEST(Signal, Recursive) {
+TEST(SignalTest, Recursive) {
   object<int> i1(-1);
   object<int> i2(10);
 
@@ -78,7 +75,7 @@
   ASSERT_EQ(i1.v, i2.v);
 }
 
-TEST(Signal, SelfRecursive) {
+TEST(SignalTest, SelfRecursive) {
   int i = 0;
 
   wpi::sig::Signal_r<int> s;
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp
index 1ebbc8e..a0dbdfc 100644
--- a/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp
+++ b/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /*
 
@@ -71,7 +68,7 @@
 
 namespace wpi {
 
-TEST(SignalExtended, FreeConnection) {
+TEST(SignalExtendedTest, FreeConnection) {
   sum = 0;
   Signal<int> sig;
   sig.connect_extended(f);
@@ -82,7 +79,7 @@
   ASSERT_EQ(sum, 1);
 }
 
-TEST(SignalExtended, StaticConnection) {
+TEST(SignalExtendedTest, StaticConnection) {
   sum = 0;
   Signal<int> sig;
   sig.connect_extended(&s::sf);
@@ -93,7 +90,7 @@
   ASSERT_EQ(sum, 1);
 }
 
-TEST(SignalExtended, PmfConnection) {
+TEST(SignalExtendedTest, PmfConnection) {
   sum = 0;
   Signal<int> sig;
   s p;
@@ -105,7 +102,7 @@
   ASSERT_EQ(sum, 1);
 }
 
-TEST(SignalExtended, FunctionObjectConnection) {
+TEST(SignalExtendedTest, FunctionObjectConnection) {
   sum = 0;
   Signal<int> sig;
   sig.connect_extended(o{});
@@ -116,7 +113,7 @@
   ASSERT_EQ(sum, 1);
 }
 
-TEST(SignalExtended, LambdaConnection) {
+TEST(SignalExtendedTest, LambdaConnection) {
   sum = 0;
   Signal<int> sig;
 
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp
index c4f7cdb..00001a8 100644
--- a/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp
+++ b/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /*
 
@@ -48,16 +45,22 @@
 
 std::atomic<int> sum{0};
 
-void f(int i) { sum += i; }
+void f(int i) {
+  sum += i;
+}
 
 void emit_many(Signal_mt<int>& sig) {
-  for (int i = 0; i < 10000; ++i) sig(1);
+  for (int i = 0; i < 10000; ++i) {
+    sig(1);
+  }
 }
 
 void connect_emit(Signal_mt<int>& sig) {
   for (int i = 0; i < 100; ++i) {
     auto s = sig.connect_scoped(f);
-    for (int j = 0; j < 100; ++j) sig(1);
+    for (int j = 0; j < 100; ++j) {
+      sig(1);
+    }
   }
 }
 
@@ -65,27 +68,35 @@
 
 namespace wpi {
 
-TEST(Signal, ThreadedMix) {
+TEST(SignalTest, ThreadedMix) {
   sum = 0;
 
   Signal_mt<int> sig;
 
   std::array<std::thread, 10> threads;
-  for (auto& t : threads) t = std::thread(connect_emit, std::ref(sig));
+  for (auto& t : threads) {
+    t = std::thread(connect_emit, std::ref(sig));
+  }
 
-  for (auto& t : threads) t.join();
+  for (auto& t : threads) {
+    t.join();
+  }
 }
 
-TEST(Signal, ThreadedEmission) {
+TEST(SignalTest, ThreadedEmission) {
   sum = 0;
 
   Signal_mt<int> sig;
   sig.connect(f);
 
   std::array<std::thread, 10> threads;
-  for (auto& t : threads) t = std::thread(emit_many, std::ref(sig));
+  for (auto& t : threads) {
+    t = std::thread(emit_many, std::ref(sig));
+  }
 
-  for (auto& t : threads) t.join();
+  for (auto& t : threads) {
+    t.join();
+  }
 
   ASSERT_EQ(sum, 100000);
 }
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp
index 89ffd36..d05ee1a 100644
--- a/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp
+++ b/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /*
 
@@ -48,7 +45,9 @@
 
 int sum = 0;
 
-void f1(int i) { sum += i; }
+void f1(int i) {
+  sum += i;
+}
 struct o1 {
   void operator()(int i) { sum += 2 * i; }
 };
@@ -73,7 +72,7 @@
 
 namespace wpi {
 
-TEST(Signal, TrackShared) {
+TEST(SignalTest, TrackShared) {
   sum = 0;
   Signal<int> sig;
 
@@ -96,7 +95,7 @@
   ASSERT_EQ(sum, 5);
 }
 
-TEST(Signal, TrackOther) {
+TEST(SignalTest, TrackOther) {
   sum = 0;
   Signal<int> sig;
 
@@ -119,7 +118,7 @@
   ASSERT_EQ(sum, 5);
 }
 
-TEST(Signal, TrackOverloadedFunctionObject) {
+TEST(SignalTest, TrackOverloadedFunctionObject) {
   sum = 0;
   Signal<int> sig;
   Signal<double> sig1;
@@ -144,7 +143,7 @@
   ASSERT_EQ(sum, 5);
 }
 
-TEST(Signal, TrackGenericLambda) {
+TEST(SignalTest, TrackGenericLambda) {
   std::stringstream s;
 
   auto f = [&](auto a, auto... args) {
diff --git a/wpiutil/src/test/native/cpp/sigslot/signal.cpp b/wpiutil/src/test/native/cpp/sigslot/signal.cpp
index a4f9208..cc2ff80 100644
--- a/wpiutil/src/test/native/cpp/sigslot/signal.cpp
+++ b/wpiutil/src/test/native/cpp/sigslot/signal.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /*
 
@@ -48,8 +45,12 @@
 
 int sum = 0;
 
-void f1(int i) { sum += i; }
-void f2(int i) /*noexcept*/ { sum += 2 * i; }
+void f1(int i) {
+  sum += i;
+}
+void f2(int i) /*noexcept*/ {
+  sum += 2 * i;
+}
 
 struct s {
   static void s1(int i) { sum += i; }
@@ -99,7 +100,7 @@
 
 namespace wpi {
 
-TEST(Signal, FreeConnection) {
+TEST(SignalTest, FreeConnection) {
   sum = 0;
   Signal<int> sig;
 
@@ -112,7 +113,7 @@
   ASSERT_EQ(sum, 4);
 }
 
-TEST(Signal, StaticConnection) {
+TEST(SignalTest, StaticConnection) {
   sum = 0;
   Signal<int> sig;
 
@@ -125,7 +126,7 @@
   ASSERT_EQ(sum, 4);
 }
 
-TEST(Signal, PmfConnection) {
+TEST(SignalTest, PmfConnection) {
   sum = 0;
   Signal<int> sig;
   s p;
@@ -143,7 +144,7 @@
   ASSERT_EQ(sum, 8);
 }
 
-TEST(Signal, ConstPmfConnection) {
+TEST(SignalTest, ConstPmfConnection) {
   sum = 0;
   Signal<int> sig;
   const s p;
@@ -157,7 +158,7 @@
   ASSERT_EQ(sum, 4);
 }
 
-TEST(Signal, FunctionObjectConnection) {
+TEST(SignalTest, FunctionObjectConnection) {
   sum = 0;
   Signal<int> sig;
 
@@ -174,7 +175,7 @@
   ASSERT_EQ(sum, 8);
 }
 
-TEST(Signal, OverloadedFunctionObjectConnection) {
+TEST(SignalTest, OverloadedFunctionObjectConnection) {
   sum = 0;
   Signal<int> sig;
   Signal<double> sig1;
@@ -188,7 +189,7 @@
   ASSERT_EQ(sum, 5);
 }
 
-TEST(Signal, LambdaConnection) {
+TEST(SignalTest, LambdaConnection) {
   sum = 0;
   Signal<int> sig;
 
@@ -201,7 +202,7 @@
   ASSERT_EQ(sum, 4);
 }
 
-TEST(Signal, GenericLambdaConnection) {
+TEST(SignalTest, GenericLambdaConnection) {
   std::stringstream s;
 
   auto f = [&](auto a, auto... args) {
@@ -228,7 +229,7 @@
   ASSERT_EQ(s.str(), "1foo4.1");
 }
 
-TEST(Signal, LvalueEmission) {
+TEST(SignalTest, LvalueEmission) {
   sum = 0;
   Signal<int> sig;
 
@@ -242,7 +243,7 @@
   ASSERT_EQ(sum, 4);
 }
 
-TEST(Signal, Mutation) {
+TEST(SignalTest, Mutation) {
   int res = 0;
   Signal<int&> sig;
 
@@ -255,7 +256,7 @@
   ASSERT_EQ(res, 4);
 }
 
-TEST(Signal, CompatibleArgs) {
+TEST(SignalTest, CompatibleArgs) {
   long ll = 0;  // NOLINT(runtime/int)
   std::string ss;
   short ii = 0;  // NOLINT(runtime/int)
@@ -275,7 +276,7 @@
   ASSERT_EQ(ii, 1);
 }
 
-TEST(Signal, Disconnection) {
+TEST(SignalTest, Disconnection) {
   // test removing only connected
   {
     sum = 0;
@@ -327,7 +328,7 @@
   }
 }
 
-TEST(Signal, ScopedConnection) {
+TEST(SignalTest, ScopedConnection) {
   sum = 0;
   Signal<int> sig;
 
@@ -360,7 +361,7 @@
   ASSERT_EQ(sum, 4);
 }
 
-TEST(Signal, ConnectionBlocking) {
+TEST(SignalTest, ConnectionBlocking) {
   sum = 0;
   Signal<int> sig;
 
@@ -378,7 +379,7 @@
   ASSERT_EQ(sum, 8);
 }
 
-TEST(Signal, ConnectionBlocker) {
+TEST(SignalTest, ConnectionBlocker) {
   sum = 0;
   Signal<int> sig;
 
@@ -397,7 +398,7 @@
   ASSERT_EQ(sum, 8);
 }
 
-TEST(Signal, SignalBlocking) {
+TEST(SignalTest, SignalBlocking) {
   sum = 0;
   Signal<int> sig;
 
@@ -415,7 +416,7 @@
   ASSERT_EQ(sum, 6);
 }
 
-TEST(Signal, AllDisconnection) {
+TEST(SignalTest, AllDisconnection) {
   sum = 0;
   Signal<int> sig;
 
@@ -429,7 +430,7 @@
   ASSERT_EQ(sum, 3);
 }
 
-TEST(Signal, ConnectionCopyingMoving) {
+TEST(SignalTest, ConnectionCopyingMoving) {
   sum = 0;
   Signal<int> sig;
 
@@ -458,7 +459,7 @@
   ASSERT_EQ(sum, 9);
 }
 
-TEST(Signal, ScopedConnectionMoving) {
+TEST(SignalTest, ScopedConnectionMoving) {
   sum = 0;
   Signal<int> sig;
 
@@ -484,7 +485,7 @@
   ASSERT_EQ(sum, 10);
 }
 
-TEST(Signal, SignalMoving) {
+TEST(SignalTest, SignalMoving) {
   sum = 0;
   Signal<int> sig;
 
@@ -506,7 +507,7 @@
 template <typename T>
 struct object {
   object();
-  object(T i) : v{i} {}  // NOLINT(runtime/explicit)
+  object(T i) : v{i} {}  // NOLINT
 
   const T& val() const { return v; }
   T& val() { return v; }
@@ -524,7 +525,7 @@
   Signal<T> s;
 };
 
-TEST(Signal, Loop) {
+TEST(SignalTest, Loop) {
   object<int> i1(0);
   object<int> i2(3);
 
diff --git a/wpiutil/src/test/native/cpp/span/test_conversions.cpp b/wpiutil/src/test/native/cpp/span/test_conversions.cpp
new file mode 100644
index 0000000..fa111a7
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/span/test_conversions.cpp
@@ -0,0 +1,46 @@
+#if __has_include(<span>)
+#include <span>
+#endif
+#include "wpi/span.h"
+
+#include "gtest/gtest.h"
+
+void func1(wpi::span<const int> x) {}
+#ifdef __cpp_lib_span
+void func2(std::span<const int> x) {}
+#endif
+
+void func4(wpi::span<int> x) {}
+#ifdef __cpp_lib_span
+void func5(std::span<int> x) {}
+#endif
+
+TEST(Span, ConvertStdSpan) {
+  func1(wpi::span<const int>{});
+  func1(wpi::span<int>{});
+#ifdef __cpp_lib_span
+  func1(std::span<const int>{});
+  func1(std::span<int>{});
+#endif
+
+#ifdef __cpp_lib_span
+  func2(wpi::span<const int>{});
+  func2(wpi::span<int>{});
+  func2(std::span<const int>{});
+  func2(std::span<int>{});
+#endif
+
+  //func4(wpi::span<const int>{});
+  func4(wpi::span<int>{});
+#ifdef __cpp_lib_span
+  //func4(std::span<const int>{});
+  func4(std::span<int>{});
+#endif
+
+#ifdef __cpp_lib_span
+  //func5(wpi::span<const int>{});
+  func5(wpi::span<int>{});
+  //func5(std::span<const int>{});
+  func5(std::span<int>{});
+#endif
+}
diff --git a/wpiutil/src/test/native/cpp/span/test_deduction_guides.cpp b/wpiutil/src/test/native/cpp/span/test_deduction_guides.cpp
new file mode 100644
index 0000000..9851b5e
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/span/test_deduction_guides.cpp
@@ -0,0 +1,98 @@
+
+#include "wpi/span.h"
+
+#include "gtest/gtest.h"
+
+#include <vector>
+
+using wpi::span;
+
+namespace {
+
+template <typename R1, typename R2>
+constexpr bool equal(R1&& r1, R2&& r2)
+{
+    auto first1 = std::begin(r1);
+    const auto last1 = std::end(r1);
+    auto first2 = std::begin(r2);
+    const auto last2 = std::end(r2);
+
+    while (first1 != last1 && first2 != last2) {
+        if (*first1 != *first2) {
+            return false;
+        }
+        ++first1;
+        ++first2;
+    }
+
+    return true;
+}
+
+constexpr bool test_raw_array()
+{
+    int arr[] = {1, 2, 3};
+    auto s = span{arr};
+    static_assert(std::is_same_v<decltype(s), span<int, 3>>);
+
+    return equal(arr, s);
+}
+
+constexpr bool test_const_raw_array()
+{
+    constexpr int arr[] = {1, 2, 3};
+    auto s = span{arr};
+    static_assert(std::is_same_v<decltype(s), span<const int, 3>>);
+
+    return equal(arr, s);
+}
+
+constexpr bool test_std_array()
+{
+    auto arr = std::array<int, 3>{1, 2, 3};
+    auto s = span{arr};
+    static_assert(std::is_same_v<decltype(s), span<int, 3>>);
+
+    return equal(arr, s);
+}
+
+constexpr bool test_const_std_array()
+{
+    const auto arr = std::array<int, 3>{1, 2, 3};
+    auto s = span{arr};
+    static_assert(std::is_same_v<decltype(s), span<const int, 3>>);
+
+    return equal(arr, s);
+}
+
+bool test_vec()
+{
+    auto arr = std::vector{1, 2, 3};
+    auto s = span{arr};
+    static_assert(std::is_same_v<decltype(s), span<int>>);
+
+    return equal(arr, s);
+}
+
+bool test_const_vec()
+{
+    const auto arr = std::vector{1, 2, 3};
+    auto s = span{arr};
+    static_assert(std::is_same_v<decltype(s), span<const int>>);
+
+    return equal(arr, s);
+}
+
+}
+
+TEST(Deduction, FromRawArrays)
+{
+    static_assert(test_raw_array());
+    static_assert(test_const_raw_array());
+    static_assert(test_std_array());
+    static_assert(test_const_std_array());
+
+    ASSERT_TRUE(test_std_array());
+    ASSERT_TRUE(test_const_std_array());
+    ASSERT_TRUE(test_vec());
+    ASSERT_TRUE(test_const_vec());
+}
diff --git a/wpiutil/src/test/native/cpp/span/test_span.cpp b/wpiutil/src/test/native/cpp/span/test_span.cpp
new file mode 100644
index 0000000..7cecfd0
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/span/test_span.cpp
@@ -0,0 +1,663 @@
+
+#include "wpi/span.h"
+
+#include <algorithm>
+#include <cassert>
+#include <deque>
+#include <initializer_list>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+using wpi::span;
+
+struct base {};
+struct derived : base {};
+
+TEST(Span, DefaultConstruction)
+{
+    static_assert(std::is_nothrow_default_constructible<span<int>>::value, "");
+    static_assert(std::is_nothrow_default_constructible<span<int, 0>>::value,
+                  "");
+    static_assert(!std::is_default_constructible<span<int, 42>>::value, "");
+
+    //SECTION("dynamic size")
+    {
+        constexpr span<int> s{};
+        static_assert(s.size() == 0, "");
+        static_assert(s.data() == nullptr, "");
+        // This causes an ICE on MSVC
+#ifndef _MSC_VER
+        static_assert(s.begin() == s.end(), "");
+#else
+        ASSERT_EQ(s.begin(), s.end());
+#endif
+    }
+
+    //SECTION("fixed size")
+    {
+        constexpr span<int, 0> s{};
+        static_assert(s.size() == 0, "");
+        static_assert(s.data() == nullptr, "");
+#ifndef _MSC_VER
+        static_assert(s.begin() == s.end(), "");
+#else
+        ASSERT_EQ(s.begin(), s.end());
+#endif
+    }
+}
+
+TEST(Span, PointerLengthConstruction)
+{
+    static_assert(std::is_constructible<span<int>, int*, int>::value, "");
+    static_assert(std::is_constructible<span<const int>, int*, int>::value, "");
+    static_assert(
+        std::is_constructible<span<const int>, const int*, int>::value, "");
+
+    static_assert(std::is_constructible<span<int, 42>, int*, int>::value, "");
+    static_assert(std::is_constructible<span<const int, 42>, int*, int>::value,
+                  "");
+    static_assert(
+        std::is_constructible<span<const int, 42>, const int*, int>::value, "");
+
+    //SECTION("dynamic size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int> s(arr, 3);
+
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+
+    //SECTION("fixed size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int, 3> s(arr, 3);
+
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+}
+
+TEST(Span, PointerPointerConstruction)
+{
+    static_assert(std::is_constructible<span<int>, int*, int*>::value, "");
+    static_assert(!std::is_constructible<span<int>, float*, float*>::value, "");
+    static_assert(std::is_constructible<span<int, 42>, int*, int*>::value, "");
+    static_assert(!std::is_constructible<span<int, 42>, float*, float*>::value,
+                  "");
+
+    //SECTION("dynamic size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int> s{arr, arr + 3};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+
+    //SECTION("fixed size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int, 3> s{arr, arr + 3};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+}
+
+TEST(Span, CArrayConstruction)
+{
+    using int_array_t = int[3];
+    using float_array_t = float[3];
+
+    static_assert(std::is_nothrow_constructible<span<int>, int_array_t&>::value,
+                  "");
+    static_assert(!std::is_constructible<span<int>, int_array_t const&>::value,
+                  "");
+    static_assert(!std::is_constructible<span<int>, float_array_t>::value, "");
+
+    static_assert(
+        std::is_nothrow_constructible<span<const int>, int_array_t&>::value,
+        "");
+    static_assert(std::is_nothrow_constructible<span<const int>,
+                                                int_array_t const&>::value,
+                  "");
+    static_assert(!std::is_constructible<span<const int>, float_array_t>::value,
+                  "");
+
+    static_assert(
+        std::is_nothrow_constructible<span<int, 3>, int_array_t&>::value, "");
+    static_assert(
+        !std::is_constructible<span<int, 3>, int_array_t const&>::value, "");
+    static_assert(!std::is_constructible<span<int, 3>, float_array_t&>::value,
+                  "");
+
+    static_assert(
+        std::is_nothrow_constructible<span<const int, 3>, int_array_t&>::value,
+        "");
+    static_assert(std::is_nothrow_constructible<span<const int, 3>,
+                                                int_array_t const&>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<span<const int, 3>, float_array_t>::value, "");
+
+    static_assert(!std::is_constructible<span<int, 42>, int_array_t&>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<span<int, 42>, int_array_t const&>::value, "");
+    static_assert(!std::is_constructible<span<int, 42>, float_array_t&>::value,
+                  "");
+
+    static_assert(
+        !std::is_constructible<span<const int, 42>, int_array_t&>::value, "");
+    static_assert(
+        !std::is_constructible<span<const int, 42>, int_array_t const&>::value,
+        "");
+    static_assert(
+        !std::is_constructible<span<const int, 42>, float_array_t&>::value, "");
+
+    //SECTION("non-const, dynamic size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+
+    //SECTION("const, dynamic size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int const> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+
+    //SECTION("non-const, static size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int, 3> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+
+    //SECTION("const, dynamic size")
+    {
+        int arr[] = {1, 2, 3};
+        span<int const, 3> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.begin(), std::begin(arr));
+        ASSERT_EQ(s.end(), std::end(arr));
+    }
+}
+
+TEST(Span, StdArrayConstruction)
+{
+    using int_array_t = std::array<int, 3>;
+    using float_array_t = std::array<float, 3>;
+    using zero_array_t = std::array<int, 0>;
+
+    static_assert(std::is_nothrow_constructible<span<int>, int_array_t&>::value,
+                  "");
+    static_assert(!std::is_constructible<span<int>, int_array_t const&>::value,
+                  "");
+    static_assert(!std::is_constructible<span<int>, float_array_t>::value, "");
+
+    static_assert(
+        std::is_nothrow_constructible<span<const int>, int_array_t&>::value,
+        "");
+    static_assert(std::is_nothrow_constructible<span<const int>,
+                                                int_array_t const&>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<span<const int>, float_array_t const&>::value,
+        "");
+
+    static_assert(
+        std::is_nothrow_constructible<span<int, 3>, int_array_t&>::value, "");
+    static_assert(
+        !std::is_constructible<span<int, 3>, int_array_t const&>::value, "");
+    static_assert(!std::is_constructible<span<int, 3>, float_array_t>::value,
+                  "");
+
+    static_assert(
+        std::is_nothrow_constructible<span<const int, 3>, int_array_t&>::value,
+        "");
+    static_assert(std::is_nothrow_constructible<span<const int, 3>,
+                                                int_array_t const&>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<span<const int, 3>, float_array_t const&>::value,
+        "");
+
+    static_assert(!std::is_constructible<span<int, 42>, int_array_t&>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<span<int, 42>, int_array_t const&>::value, "");
+    static_assert(
+        !std::is_constructible<span<int, 42>, float_array_t const&>::value, "");
+
+    static_assert(
+        !std::is_constructible<span<const int, 42>, int_array_t&>::value, "");
+    static_assert(
+        !std::is_constructible<span<const int, 42>, int_array_t const&>::value,
+        "");
+    static_assert(
+        !std::is_constructible<span<const int, 42>, float_array_t&>::value, "");
+
+    static_assert(std::is_constructible<span<int>, zero_array_t&>::value, "");
+    static_assert(!std::is_constructible<span<int>, const zero_array_t&>::value,
+                  "");
+    static_assert(std::is_constructible<span<const int>, zero_array_t&>::value,
+                  "");
+    static_assert(
+        std::is_constructible<span<const int>, const zero_array_t&>::value, "");
+
+    static_assert(std::is_constructible<span<int, 0>, zero_array_t&>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<span<int, 0>, const zero_array_t&>::value, "");
+    static_assert(
+        std::is_constructible<span<const int, 0>, zero_array_t&>::value, "");
+    static_assert(
+        std::is_constructible<span<const int, 0>, const zero_array_t&>::value,
+        "");
+
+    //SECTION("non-const, dynamic size")
+    {
+        int_array_t arr = {1, 2, 3};
+        span<int> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+
+    //SECTION("const, dynamic size")
+    {
+        int_array_t arr = {1, 2, 3};
+        span<int const> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+
+    //SECTION("non-const, static size")
+    {
+        int_array_t arr = {1, 2, 3};
+        span<int, 3> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+
+    //SECTION("const, dynamic size")
+    {
+        int_array_t arr = {1, 2, 3};
+        span<int const, 3> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+}
+
+TEST(Span, ConstructionFromOtherContainers)
+{
+    using vec_t = std::vector<int>;
+    using deque_t = std::deque<int>;
+
+    static_assert(std::is_constructible<span<int>, vec_t&>::value, "");
+    static_assert(!std::is_constructible<span<int>, const vec_t&>::value, "");
+    static_assert(!std::is_constructible<span<int>, const deque_t&>::value, "");
+
+    static_assert(std::is_constructible<span<const int>, vec_t&>::value, "");
+    static_assert(std::is_constructible<span<const int>, const vec_t&>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<span<const int>, const deque_t&>::value, "");
+
+    static_assert(!std::is_constructible<span<int, 3>, vec_t&>::value, "");
+    static_assert(!std::is_constructible<span<int, 3>, const vec_t&>::value,
+                  "");
+    static_assert(!std::is_constructible<span<int, 3>, const deque_t&>::value,
+                  "");
+
+    static_assert(!std::is_constructible<span<const int, 3>, vec_t&>::value, "");
+    static_assert(
+        !std::is_constructible<span<const int, 3>, const vec_t&>::value, "");
+    static_assert(
+        !std::is_constructible<span<const int, 3>, const deque_t&>::value, "");
+
+    // vector<bool> is not contiguous and cannot be converted to span<bool>
+    // Regression test for https://github.com/tcbrindle/span/issues/24
+    static_assert(
+        !std::is_constructible<span<bool>, std::vector<bool>&>::value, "");
+    static_assert(!std::is_constructible<span<const bool>,
+                                         const std::vector<bool>&>::value, "");
+
+    //SECTION("non-const, dynamic size")
+    {
+        vec_t arr = {1, 2, 3};
+        span<int> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+
+    //SECTION("const, dynamic size")
+    {
+        vec_t arr = {1, 2, 3};
+        span<int const> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+
+    //SECTION("non-const, static size")
+    {
+        std::array<int, 3> arr = {1, 2, 3};
+        span<int, 3> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+
+    //SECTION("const, dynamic size")
+    {
+        std::array<int, 3> arr = {1, 2, 3};
+        span<int const, 3> s{arr};
+        ASSERT_EQ(s.size(), 3u);
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.begin(), arr.data());
+        ASSERT_EQ(s.end(), arr.data() + 3);
+    }
+}
+
+TEST(Span, ConstructionFromSpansOfDifferentSize)
+{
+    using zero_span = span<int, 0>;
+    using zero_const_span = span<const int, 0>;
+    using big_span = span<int, 1000000>;
+    using big_const_span = span<const int, 1000000>;
+    using dynamic_span = span<int>;
+    using dynamic_const_span = span<const int>;
+
+    static_assert(std::is_trivially_copyable<zero_span>::value, "");
+    static_assert(std::is_trivially_move_constructible<zero_span>::value, "");
+    static_assert(!std::is_constructible<zero_span, zero_const_span>::value,
+                  "");
+    static_assert(!std::is_constructible<zero_span, big_span>::value, "");
+    static_assert(!std::is_constructible<zero_span, big_const_span>::value, "");
+    static_assert(!std::is_constructible<zero_span, dynamic_span>::value, "");
+    static_assert(!std::is_constructible<zero_span, dynamic_const_span>::value,
+                  "");
+
+    static_assert(
+        std::is_nothrow_constructible<zero_const_span, zero_span>::value, "");
+    static_assert(std::is_trivially_copyable<zero_const_span>::value, "");
+    static_assert(std::is_trivially_move_constructible<zero_const_span>::value,
+                  "");
+    static_assert(!std::is_constructible<zero_const_span, big_span>::value, "");
+    static_assert(
+        !std::is_constructible<zero_const_span, big_const_span>::value, "");
+    static_assert(!std::is_constructible<zero_const_span, dynamic_span>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<zero_const_span, dynamic_const_span>::value, "");
+
+    static_assert(!std::is_constructible<big_span, zero_span>::value, "");
+    static_assert(!std::is_constructible<big_span, zero_const_span>::value, "");
+    static_assert(std::is_trivially_copyable<big_span>::value, "");
+    static_assert(std::is_trivially_move_constructible<big_span>::value, "");
+    static_assert(!std::is_constructible<big_span, big_const_span>::value, "");
+    static_assert(!std::is_constructible<big_span, dynamic_span>::value, "");
+    static_assert(!std::is_constructible<big_span, dynamic_const_span>::value,
+                  "");
+
+    static_assert(!std::is_constructible<big_const_span, zero_span>::value, "");
+    static_assert(
+        !std::is_constructible<big_const_span, zero_const_span>::value, "");
+    static_assert(std::is_trivially_copyable<big_const_span>::value, "");
+    static_assert(std::is_trivially_move_constructible<big_const_span>::value,
+                  "");
+    static_assert(
+        std::is_nothrow_constructible<big_const_span, big_span>::value, "");
+    static_assert(!std::is_constructible<big_const_span, dynamic_span>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<big_const_span, dynamic_const_span>::value, "");
+
+    static_assert(std::is_nothrow_constructible<dynamic_span, zero_span>::value,
+                  "");
+    static_assert(!std::is_constructible<dynamic_span, zero_const_span>::value,
+                  "");
+    static_assert(std::is_nothrow_constructible<dynamic_span, big_span>::value,
+                  "");
+    static_assert(!std::is_constructible<dynamic_span, big_const_span>::value,
+                  "");
+    static_assert(std::is_trivially_copyable<dynamic_span>::value, "");
+    static_assert(std::is_trivially_move_constructible<dynamic_span>::value,
+                  "");
+    static_assert(
+        !std::is_constructible<dynamic_span, dynamic_const_span>::value, "");
+
+    static_assert(
+        std::is_nothrow_constructible<dynamic_const_span, zero_span>::value,
+        "");
+    static_assert(std::is_nothrow_constructible<dynamic_const_span,
+                                                zero_const_span>::value,
+                  "");
+    static_assert(
+        std::is_nothrow_constructible<dynamic_const_span, big_span>::value, "");
+    static_assert(std::is_nothrow_constructible<dynamic_const_span,
+                                                big_const_span>::value,
+                  "");
+    static_assert(
+        std::is_nothrow_constructible<dynamic_const_span, dynamic_span>::value,
+        "");
+    static_assert(std::is_trivially_copyable<dynamic_const_span>::value, "");
+    static_assert(
+        std::is_trivially_move_constructible<dynamic_const_span>::value, "");
+
+    constexpr zero_const_span s0{};
+    constexpr dynamic_const_span d{s0};
+
+    static_assert(d.size() == 0, "");
+    static_assert(d.data() == nullptr, "");
+#ifndef _MSC_VER
+    static_assert(d.begin() == d.end(), "");
+#else
+    ASSERT_EQ(d.begin(), d.end());
+#endif
+}
+
+TEST(Span, MemberSubviewOperations)
+{
+    //SECTION("first<N>")
+    {
+        int arr[] = {1, 2, 3, 4, 5};
+        span<int, 5> s{arr};
+        auto f = s.first<3>();
+
+        static_assert(std::is_same<decltype(f), span<int, 3>>::value, "");
+        ASSERT_EQ(f.size(), 3u);
+        ASSERT_EQ(f.data(), arr);
+        ASSERT_EQ(f.begin(), arr);
+        ASSERT_EQ(f.end(), arr + 3);
+    }
+
+    //SECTION("last<N>")
+    {
+        int arr[] = {1, 2, 3, 4, 5};
+        span<int, 5> s{arr};
+        auto l = s.last<3>();
+
+        static_assert(std::is_same<decltype(l), span<int, 3>>::value, "");
+        ASSERT_EQ(l.size(), 3u);
+        ASSERT_EQ(l.data(), arr + 2);
+        ASSERT_EQ(l.begin(), arr + 2);
+        ASSERT_EQ(l.end(), std::end(arr));
+    }
+
+    //SECTION("subspan<N>")
+    {
+        int arr[] = {1, 2, 3, 4, 5};
+        span<int, 5> s{arr};
+        auto ss = s.subspan<1, 2>();
+
+        static_assert(std::is_same<decltype(ss), span<int, 2>>::value, "");
+        ASSERT_EQ(ss.size(), 2u);
+        ASSERT_EQ(ss.data(), arr + 1);
+        ASSERT_EQ(ss.begin(), arr + 1);
+        ASSERT_EQ(ss.end(), arr + 1 + 2);
+    }
+
+    //SECTION("first(n)")
+    {
+        int arr[] = {1, 2, 3, 4, 5};
+        span<int, 5> s{arr};
+        auto f = s.first(3);
+
+        static_assert(std::is_same<decltype(f), span<int>>::value, "");
+        ASSERT_EQ(f.size(), 3u);
+        ASSERT_EQ(f.data(), arr);
+        ASSERT_EQ(f.begin(), arr);
+        ASSERT_EQ(f.end(), arr + 3);
+    }
+
+    //SECTION("last(n)")
+    {
+        int arr[] = {1, 2, 3, 4, 5};
+        span<int, 5> s{arr};
+        auto l = s.last(3);
+
+        static_assert(std::is_same<decltype(l), span<int>>::value, "");
+        ASSERT_EQ(l.size(), 3u);
+        ASSERT_EQ(l.data(), arr + 2);
+        ASSERT_EQ(l.begin(), arr + 2);
+        ASSERT_EQ(l.end(), std::end(arr));
+    }
+
+    //SECTION("subspan(n)")
+    {
+        int arr[] = {1, 2, 3, 4, 5};
+        span<int, 5> s{arr};
+        auto ss = s.subspan(1, 2);
+
+        static_assert(std::is_same<decltype(ss), span<int>>::value, "");
+        ASSERT_EQ(ss.size(), 2u);
+        ASSERT_EQ(ss.data(), arr + 1);
+        ASSERT_EQ(ss.begin(), arr + 1);
+        ASSERT_EQ(ss.end(), arr + 1 + 2);
+    }
+
+    // TODO: Test all the dynamic subspan possibilities
+}
+
+TEST(Span, Observers)
+{
+    // We already use this everywhere, but whatever
+    constexpr span<int, 0> empty{};
+    static_assert(empty.size() == 0, "");
+    static_assert(empty.empty(), "");
+
+    constexpr int arr[] = {1, 2, 3};
+    static_assert(span<const int>{arr}.size() == 3, "");
+    static_assert(!span<const int>{arr}.empty(), "");
+}
+
+TEST(Span, ElementAccess)
+{
+    constexpr int arr[] = {1, 2, 3};
+    span<const int> s{arr};
+
+    ASSERT_EQ(s[0], arr[0]);
+    ASSERT_EQ(s[1], arr[1]);
+    ASSERT_EQ(s[2], arr[2]);
+}
+
+TEST(Span, IteratorSupport)
+{
+    {
+        std::vector<int> vec;
+        span<int> s{vec};
+        std::sort(s.begin(), s.end());
+        ASSERT_TRUE(std::is_sorted(vec.cbegin(), vec.cend()));
+    }
+
+    {
+        const std::vector<int> vec{1, 2, 3};
+        span<const int> s{vec};
+        ASSERT_TRUE(std::equal(s.rbegin(), s.rend(), vec.crbegin()));
+    }
+}
+
+TEST(Span, MakeSpan)
+{
+    {
+        int arr[3] = {1, 2, 3};
+        auto s = wpi::span(arr);
+        static_assert(std::is_same<decltype(s), span<int, 3>>::value, "");
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.size(), 3u);
+    }
+
+    {
+        const int arr[3] = {1, 2, 3};
+        auto s = wpi::span(arr);
+        static_assert(std::is_same<decltype(s), span<const int, 3>>::value, "");
+        ASSERT_EQ(s.data(), arr);
+        ASSERT_EQ(s.size(), 3u);
+    }
+
+    {
+        std::array<int, 3> arr = {1, 2, 3};
+        auto s = wpi::span(arr);
+        static_assert(std::is_same<decltype(s), span<int, 3>>::value, "");
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.size(), arr.size());
+    }
+
+    {
+        const std::array<int, 3> arr = {1, 2, 3};
+        auto s = wpi::span(arr);
+        static_assert(std::is_same<decltype(s), span<const int, 3>>::value, "");
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.size(), 3u);
+    }
+
+    {
+        std::vector<int> arr = {1, 2, 3};
+        auto s = wpi::span(arr);
+        static_assert(std::is_same<decltype(s), span<int>>::value, "");
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.size(), arr.size());
+    }
+
+    {
+        const std::vector<int> arr = {1, 2, 3};
+        auto s = wpi::span(arr);
+        static_assert(std::is_same<decltype(s), span<const int>>::value, "");
+        ASSERT_EQ(s.data(), arr.data());
+        ASSERT_EQ(s.size(), arr.size());
+    }
+}
diff --git a/wpiutil/src/test/native/cpp/span/test_structured_bindings.cpp b/wpiutil/src/test/native/cpp/span/test_structured_bindings.cpp
new file mode 100644
index 0000000..5ce2969
--- /dev/null
+++ b/wpiutil/src/test/native/cpp/span/test_structured_bindings.cpp
@@ -0,0 +1,30 @@
+
+#include "wpi/span.h"
+
+#include "gtest/gtest.h"
+
+using static_span_t = wpi::span<int, 3>;
+using dynamic_span_t = wpi::span<int>;
+
+static_assert(std::tuple_size_v<static_span_t> == static_span_t::extent);
+static_assert(!wpi::detail::is_complete<std::tuple_size<dynamic_span_t>>::value);
+
+TEST(StructuredBindings, Test)
+{
+    // C++, why you no let me do constexpr structured bindings?
+
+    int arr[] = {1, 2, 3};
+
+    auto& [a1, a2, a3] = arr;
+    auto&& [s1, s2, s3] = wpi::span(arr);
+
+    ASSERT_EQ(a1, s1);
+    ASSERT_EQ(a2, s2);
+    ASSERT_EQ(a3, s3);
+
+    a1 = 99;
+    ASSERT_EQ(s1, 99);
+
+    s2 = 100;
+    ASSERT_EQ(a2, 100);
+}
diff --git a/wpiutil/src/test/native/cpp/spinlock_bench.cpp b/wpiutil/src/test/native/cpp/spinlock_bench.cpp
index b36e558..9534837 100644
--- a/wpiutil/src/test/native/cpp/spinlock_bench.cpp
+++ b/wpiutil/src/test/native/cpp/spinlock_bench.cpp
@@ -1,17 +1,14 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/spinlock.h"  // NOLINT(build/include_order)
 
 #include <chrono>
-#include <iostream>
 #include <mutex>
 #include <thread>
 
+#include "fmt/core.h"
 #include "gtest/gtest.h"
 #include "wpi/mutex.h"
 
@@ -53,9 +50,8 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "std::mutex sizeof: " << sizeof(std_mutex)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("std::mutex sizeof: {} time: {} value: {}\n", sizeof(std_mutex),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thrb.join();
 
@@ -68,9 +64,9 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "std::recursive_mutex sizeof: " << sizeof(std_recursive_mutex)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("std::recursive_mutex sizeof: {} time: {} value: {}\n",
+               sizeof(std_recursive_mutex),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thrb2.join();
 
@@ -83,9 +79,8 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "wpi::mutex sizeof: " << sizeof(wpi_mutex)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("wpi::mutex sizeof: {} time: {} value: {}\n", sizeof(wpi_mutex),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thr2.join();
 
@@ -98,9 +93,9 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "wpi::recursive_mutex sizeof: " << sizeof(wpi_recursive_mutex)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("wpi::recursive_mutex sizeof: {} time: {} value: {}\n",
+               sizeof(wpi_recursive_mutex),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thr2b.join();
 
@@ -113,9 +108,8 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "spinlock sizeof: " << sizeof(spinlock)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("spinlock sizeof: {} time: {} value: {}\n", sizeof(spinlock),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thr3.join();
 
@@ -128,9 +122,9 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "recursive_spinlock1 sizeof: " << sizeof(recursive_spinlock1)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("recursive_spinlock1 sizeof: {} time: {} value: {}\n",
+               sizeof(recursive_spinlock1),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thr4.join();
 
@@ -143,9 +137,9 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "recursive_spinlock2 sizeof: " << sizeof(recursive_spinlock2)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("recursive_spinlock2 sizeof: {} time: {} value: {}\n",
+               sizeof(recursive_spinlock2),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thr4b.join();
 
@@ -158,9 +152,9 @@
       ++value;
     }
     auto stop = high_resolution_clock::now();
-    std::cout << "recursive_spinlock sizeof: " << sizeof(recursive_spinlock)
-              << " time: " << duration_cast<microseconds>(stop - start).count()
-              << " value: " << value << "\n";
+    fmt::print("recursive_spinlock sizeof: {} time: {} value: {}\n",
+               sizeof(recursive_spinlock),
+               duration_cast<microseconds>(stop - start).count(), value);
   });
   thr4c.join();
 }
diff --git a/wpiutil/src/test/native/cpp/uv/UvAsyncFunctionTest.cpp b/wpiutil/src/test/native/cpp/uv/UvAsyncFunctionTest.cpp
index 132adea..8407935 100644
--- a/wpiutil/src/test/native/cpp/uv/UvAsyncFunctionTest.cpp
+++ b/wpiutil/src/test/native/cpp/uv/UvAsyncFunctionTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/AsyncFunction.h"  // NOLINT(build/include_order)
 
@@ -14,10 +11,9 @@
 #include "wpi/uv/Loop.h"
 #include "wpi/uv/Prepare.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-TEST(UvAsyncFunction, Test) {
+TEST(UvAsyncFunctionTest, Basic) {
   int prepare_cb_called = 0;
   int async_cb_called[2] = {0, 0};
   int close_cb_called = 0;
@@ -32,7 +28,9 @@
 
   prepare->error.connect([](Error) { FAIL(); });
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] {
       auto call0 = async->Call(0);
       auto call1 = async->Call(1);
@@ -59,10 +57,12 @@
   ASSERT_EQ(async_cb_called[1], 1);
   ASSERT_EQ(close_cb_called, 1);
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsyncFunction, Ref) {
+TEST(UvAsyncFunctionTest, Ref) {
   int prepare_cb_called = 0;
   int val = 0;
 
@@ -73,7 +73,9 @@
   auto prepare = Prepare::Create(loop);
 
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] { ASSERT_EQ(async->Call(1, val).get(), 2); });
   });
   prepare->Start();
@@ -89,10 +91,12 @@
 
   ASSERT_EQ(val, 1);
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsyncFunction, Movable) {
+TEST(UvAsyncFunctionTest, Movable) {
   int prepare_cb_called = 0;
 
   std::thread theThread;
@@ -103,7 +107,9 @@
   auto prepare = Prepare::Create(loop);
 
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] {
       auto val = std::make_unique<int>(1);
       auto val2 = async->Call(std::move(val)).get();
@@ -122,10 +128,12 @@
 
   loop->Run();
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsyncFunction, CallIgnoreResult) {
+TEST(UvAsyncFunctionTest, CallIgnoreResult) {
   int prepare_cb_called = 0;
 
   std::thread theThread;
@@ -136,7 +144,9 @@
   auto prepare = Prepare::Create(loop);
 
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] { async->Call(std::make_unique<int>(1)); });
   });
   prepare->Start();
@@ -150,10 +160,12 @@
 
   loop->Run();
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsyncFunction, VoidCall) {
+TEST(UvAsyncFunctionTest, VoidCall) {
   int prepare_cb_called = 0;
 
   std::thread theThread;
@@ -163,7 +175,9 @@
   auto prepare = Prepare::Create(loop);
 
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] { async->Call(); });
   });
   prepare->Start();
@@ -176,10 +190,12 @@
 
   loop->Run();
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsyncFunction, WaitFor) {
+TEST(UvAsyncFunctionTest, WaitFor) {
   int prepare_cb_called = 0;
 
   std::thread theThread;
@@ -189,7 +205,9 @@
   auto prepare = Prepare::Create(loop);
 
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] {
       ASSERT_FALSE(async->Call().wait_for(std::chrono::milliseconds(10)));
     });
@@ -205,10 +223,12 @@
 
   loop->Run();
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsyncFunction, VoidWaitFor) {
+TEST(UvAsyncFunctionTest, VoidWaitFor) {
   int prepare_cb_called = 0;
 
   std::thread theThread;
@@ -218,7 +238,9 @@
   auto prepare = Prepare::Create(loop);
 
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] {
       ASSERT_FALSE(async->Call().wait_for(std::chrono::milliseconds(10)));
     });
@@ -234,8 +256,9 @@
 
   loop->Run();
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp b/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp
index e18f972..5561f1e 100644
--- a/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp
+++ b/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
  *
@@ -37,10 +34,9 @@
 #include "wpi/uv/Loop.h"
 #include "wpi/uv/Prepare.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-TEST(UvAsync, Test) {
+TEST(UvAsyncTest, CallbackOnly) {
   std::atomic_int async_cb_called{0};
   int prepare_cb_called = 0;
   int close_cb_called = 0;
@@ -59,7 +55,9 @@
   prepare->error.connect([](Error) { FAIL(); });
   prepare->closed.connect([&] { close_cb_called++; });
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] {
       for (;;) {
         mutex.lock();
@@ -98,10 +96,12 @@
   ASSERT_EQ(async_cb_called, 3);
   ASSERT_EQ(close_cb_called, 2);
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsync, Data) {
+TEST(UvAsyncTest, Data) {
   int prepare_cb_called = 0;
   int async_cb_called[2] = {0, 0};
   int close_cb_called = 0;
@@ -116,7 +116,9 @@
 
   prepare->error.connect([](Error) { FAIL(); });
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] {
       async->Send(0, [&](int v) {
         ASSERT_EQ(v, 0);
@@ -142,10 +144,12 @@
   ASSERT_EQ(async_cb_called[1], 1);
   ASSERT_EQ(close_cb_called, 1);
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-TEST(UvAsync, DataRef) {
+TEST(UvAsyncTest, DataRef) {
   int prepare_cb_called = 0;
   int val = 0;
 
@@ -156,7 +160,9 @@
   auto prepare = Prepare::Create(loop);
 
   prepare->prepare.connect([&] {
-    if (prepare_cb_called++) return;
+    if (prepare_cb_called++) {
+      return;
+    }
     theThread = std::thread([&] { async->Send(1, val); });
   });
   prepare->Start();
@@ -171,8 +177,9 @@
 
   ASSERT_EQ(val, 1);
 
-  if (theThread.joinable()) theThread.join();
+  if (theThread.joinable()) {
+    theThread.join();
+  }
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/test/native/cpp/uv/UvBufferTest.cpp b/wpiutil/src/test/native/cpp/uv/UvBufferTest.cpp
index e837ca9..e262181 100644
--- a/wpiutil/src/test/native/cpp/uv/UvBufferTest.cpp
+++ b/wpiutil/src/test/native/cpp/uv/UvBufferTest.cpp
@@ -1,50 +1,48 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Buffer.h"  // NOLINT(build/include_order)
 
 #include "gtest/gtest.h"  // NOLINT(build/include_order)
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-TEST(UvSimpleBufferPool, ConstructDefault) {
+TEST(UvSimpleBufferPoolTest, ConstructDefault) {
   SimpleBufferPool<> pool;
   auto buf1 = pool.Allocate();
-  ASSERT_EQ(buf1.len, 4096u);
+  ASSERT_EQ(buf1.len, 4096u);  // NOLINT
+  pool.Release({&buf1, 1});
 }
 
-TEST(UvSimpleBufferPool, ConstructSize) {
+TEST(UvSimpleBufferPoolTest, ConstructSize) {
   SimpleBufferPool<4> pool{8192};
   auto buf1 = pool.Allocate();
-  ASSERT_EQ(buf1.len, 8192u);
+  ASSERT_EQ(buf1.len, 8192u);  // NOLINT
+  pool.Release({&buf1, 1});
 }
 
-TEST(UvSimpleBufferPool, ReleaseReuse) {
+TEST(UvSimpleBufferPoolTest, ReleaseReuse) {
   SimpleBufferPool<4> pool;
   auto buf1 = pool.Allocate();
   auto buf1copy = buf1;
   auto origSize = buf1.len;
   buf1.len = 8;
-  pool.Release(buf1);
+  pool.Release({&buf1, 1});
   ASSERT_EQ(buf1.base, nullptr);
   auto buf2 = pool.Allocate();
   ASSERT_EQ(buf1copy.base, buf2.base);
   ASSERT_EQ(buf2.len, origSize);
+  pool.Release({&buf2, 1});
 }
 
-TEST(UvSimpleBufferPool, ClearRemaining) {
+TEST(UvSimpleBufferPoolTest, ClearRemaining) {
   SimpleBufferPool<4> pool;
   auto buf1 = pool.Allocate();
-  pool.Release(buf1);
+  pool.Release({&buf1, 1});
   ASSERT_EQ(pool.Remaining(), 1u);
   pool.Clear();
   ASSERT_EQ(pool.Remaining(), 0u);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp b/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp
index 20d27f1..b397e24 100644
--- a/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp
+++ b/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
  *
@@ -34,10 +31,9 @@
 
 #define CONCURRENT_COUNT 10
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-TEST(UvGetAddrInfo, BothNull) {
+TEST(UvGetAddrInfoTest, BothNull) {
   int fail_cb_called = 0;
 
   auto loop = Loop::Create();
@@ -47,12 +43,12 @@
   });
 
   GetAddrInfo(
-      loop, [](const addrinfo&) { FAIL(); }, Twine::createNull());
+      loop, [](const addrinfo&) { FAIL(); }, "");
   loop->Run();
   ASSERT_EQ(fail_cb_called, 1);
 }
 
-TEST(UvGetAddrInfo, FailedLookup) {
+TEST(UvGetAddrInfoTest, FailedLookup) {
   int fail_cb_called = 0;
 
   auto loop = Loop::Create();
@@ -69,7 +65,7 @@
   ASSERT_EQ(fail_cb_called, 1);
 }
 
-TEST(UvGetAddrInfo, Basic) {
+TEST(UvGetAddrInfoTest, Basic) {
   int getaddrinfo_cbs = 0;
 
   auto loop = Loop::Create();
@@ -84,7 +80,7 @@
 }
 
 #ifndef _WIN32
-TEST(UvGetAddrInfo, Concurrent) {
+TEST(UvGetAddrInfoTest, Concurrent) {
   int getaddrinfo_cbs = 0;
   int callback_counts[CONCURRENT_COUNT];
 
@@ -110,5 +106,4 @@
 }
 #endif
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp b/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp
index 76d22d9..db1eefe 100644
--- a/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp
+++ b/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2020 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
  *
@@ -32,10 +29,9 @@
 
 #include "wpi/uv/Loop.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-TEST(UvGetNameInfo, BasicIp4) {
+TEST(UvGetNameInfoTest, BasicIp4) {
   int getnameinfo_cbs = 0;
 
   auto loop = Loop::Create();
@@ -55,7 +51,7 @@
   ASSERT_EQ(getnameinfo_cbs, 1);
 }
 
-TEST(UvGetNameInfo, BasicIp6) {
+TEST(UvGetNameInfoTest, BasicIp6) {
   int getnameinfo_cbs = 0;
 
   auto loop = Loop::Create();
@@ -75,5 +71,4 @@
   ASSERT_EQ(getnameinfo_cbs, 1);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp b/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp
index 5d2e120..38a3342 100644
--- a/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp
+++ b/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp
@@ -1,9 +1,6 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
  *
@@ -32,10 +29,9 @@
 
 #include "wpi/uv/Timer.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-TEST(UvLoop, Walk) {
+TEST(UvLoopTest, Walk) {
   int seen_timer_handle = 0;
 
   auto loop = Loop::Create();
@@ -47,7 +43,9 @@
 
   timer->timeout.connect([&, theTimer = timer.get()] {
     theTimer->GetLoopRef().Walk([&](Handle& it) {
-      if (&it == timer.get()) seen_timer_handle++;
+      if (&it == timer.get()) {
+        seen_timer_handle++;
+      }
     });
     theTimer->Close();
   });
@@ -61,10 +59,11 @@
   // Loop is finished, should not see our timer handle
   seen_timer_handle = 0;
   loop->Walk([&](Handle& it) {
-    if (&it == timer.get()) seen_timer_handle++;
+    if (&it == timer.get()) {
+      seen_timer_handle++;
+    }
   });
   ASSERT_EQ(seen_timer_handle, 0);
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv
diff --git a/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp b/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp
index 6e1bd5c..1a6258f 100644
--- a/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp
+++ b/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp
@@ -1,18 +1,14 @@
-/*----------------------------------------------------------------------------*/
-/* Copyright (c) 2018-2019 FIRST. All Rights Reserved.                        */
-/* Open Source Software - may be modified and shared by FRC teams. The code   */
-/* must be accompanied by the FIRST BSD license file in the root directory of */
-/* the project.                                                               */
-/*----------------------------------------------------------------------------*/
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
 
 #include "wpi/uv/Timer.h"  // NOLINT(build/include_order)
 
 #include "gtest/gtest.h"
 
-namespace wpi {
-namespace uv {
+namespace wpi::uv {
 
-TEST(UvTimer, StartAndStop) {
+TEST(UvTimerTest, StartAndStop) {
   auto loop = Loop::Create();
   auto handleNoRepeat = Timer::Create(loop);
   auto handleRepeat = Timer::Create(loop);
@@ -59,7 +55,7 @@
   ASSERT_TRUE(checkTimerRepeatEvent);
 }
 
-TEST(UvTimer, Repeat) {
+TEST(UvTimerTest, Repeat) {
   auto loop = Loop::Create();
   auto handle = Timer::Create(loop);
 
@@ -70,5 +66,4 @@
   loop->Run();  // forces close callback to run
 }
 
-}  // namespace uv
-}  // namespace wpi
+}  // namespace wpi::uv