/*
 * Parsing physical units.
 *
 * Author: Malte J. Ziebarth (malte.ziebarth@tum.de)
 *
 * Copyright (C) 2026 Technische Universität München
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *
 *    1. Redistributions of source code must retain the above copyright notice, this
 *       list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * SPDX-FileCopyrightText: 2026 Technical University of Munich
 * SPDX-License-Identifier: BSD-3-Clause
 * SPDX-FileContributor: Malte J. Ziebarth <malte.ziebarth@tum.de>
 */
#ifndef FUNKTIONSKLEMPNER_NO_BOOST

#ifndef FUNKTIONSKLEMPNER_UNITS_HPP
#define FUNKTIONSKLEMPNER_UNITS_HPP

#include <array>
#include <cmath>
#include <ranges>
#include <limits>
#include <variant>
#include <numbers>
#include <funktionsklempner/fixedmap.hpp>
#include <funktionsklempner/math.hpp>
#include <funktionsklempner/tartanllama/expected.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/get_dimension.hpp>
#include <boost/units/systems/si.hpp>
#include <boost/units/is_unit_of_system.hpp>

namespace funktionsklempner {

/*
 * Concept to match boost::units
 */
template<typename T>
constexpr bool is_unit = false;

template<typename Dim, typename System>
constexpr bool is_unit<boost::units::unit<Dim, System>> = true;

template<typename T>
concept Unit = is_unit<T>;



/*
 * A lot of backend in a ::unit namespace:
 */
namespace unit {

constexpr size_t N_UNIT = 9;


/*
 * Transform the boost unit into an array representation:
 */
namespace _array_representation {

template<typename Dim>
constexpr inline unsigned int dimension_index = -1;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::time_base_dimension
> = 0;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::length_base_dimension
> = 1;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::mass_base_dimension
> = 2;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::current_base_dimension
> = 3;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::temperature_base_dimension
> = 4;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::amount_base_dimension
> = 5;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::luminous_intensity_base_dimension
> = 6;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::plane_angle_base_dimension
> = 7;

template<>
constexpr inline unsigned int dimension_index<
    boost::units::solid_angle_base_dimension
> = 8;



/*
 * This structure iterates the dimensions of a boost::unit::list (the
 * backbone of a unit) and, for each dimension, adds the corresponding integer
 * dimension exponent to the corresponding elements of an array representation
 * of the unit.
 * Its core is the `represent` static method which recursively calls itself
 * while iterating over the list elements of the unit.
 */
template<typename T>
struct iterate_and_represent;

template<
    typename Derived,
    boost::units::integer_type nom,
    boost::units::integer_type denom,
    typename Next
>
struct iterate_and_represent<
    boost::units::list<
        boost::units::dim<
            Derived,
            boost::units::static_rational<nom, denom>
        >,
        Next
    >
>
{
    static_assert(denom == 1, "Only integer dimensions supported.");

    constexpr static
    std::array<int, N_UNIT>
    represent()
    {
        /* Get the contributions of the previous base units of the list: */
        std::array<int, N_UNIT> prev
            = iterate_and_represent<Next>::represent();

        /* Assign the current base unit: */
        constexpr unsigned int i = dimension_index<Derived>;
        prev[i] += nom;

        return prev;
    }
};


/*
 * The recursive-iteration begin:
 */
template<>
struct iterate_and_represent<boost::units::dimensionless_type>
{
    constexpr static std::array<int, N_UNIT>
    represent()
    {
        std::array<int, N_UNIT> res;
        for (int& i : res)
            i = 0;
        return res;
    }
};




} // namespace _array_representation



template<Unit U>
constexpr std::array<int, N_UNIT>
array_representation()
{
    return _array_representation::iterate_and_represent<
        typename boost::units::get_dimension<U>::type
    >::represent();
}




constexpr
tl::expected<int, const char*>
parse_int(std::u8string_view sv)
{
    if (sv.empty())
        return tl::unexpected(
            "Trying to parse an integer from an empty string."
        );

    /* Parse first the sign.
     * We allow both the classical hyphen-minus '-' (U+002D)
     * and the mathematical minus '−' (U+2212) to specify
     * the negative sign.
     */
    int sign = 1;
    if (sv[0] == u8'-'){
        sign = -1;
        sv = sv.substr(1);
    } else if (sv.starts_with(u8"−")){
        sign = -1;
        sv = sv.substr(sizeof(u8"−")-1);
    }

    constexpr int MAX = std::numeric_limits<int>::max();

    /* 'res' holds the integer we are building,
     * 'base' is the base of the current digit.
     */
    int res = 0;
    int base = 1;
    for (auto it = sv.rbegin(); it != sv.rend(); ++it){
        char8_t c = *it;
        /* Ensure that we have a digit, and obtain it: */
        if (c < u8'0' || c > u8'9'){
            return tl::unexpected(
                "Found non-digit character trying to parse string "
                "into an integer."
            );
        }
        int digit = c - '0';

        /* Check whether we would overflow.
         * Here we know that 'res' has not yet overflown.
         * 1. `base * digit` may overflow.
         *    Check that digit <= MAX / base
         *    (-> MAX >= digit * base)
         * 2. otherwise, `res + base * digit` may overflow
         *    Check that res <= MAX - base * digit
         *    (-> MAX >= res + base * digit)
         */
        int summand = digit * base;
        if (MAX / base < digit || MAX - summand < res)
            return tl::unexpected(
                "Trying to parse an integer out of 'int' bounds."
            );

        res += summand;

        base *= 10;
    }


    return sign * res;
}


constexpr auto unit_strings()
{
    std::array<const char*, 10> res;
    /* Base units: */
    res[0] = "s";
    res[1] = "m";
    res[2] = "g";

    return res;
}

constexpr std::optional<int>
parse_single_prefix(char8_t c)
{
    /* Search array: */
    constexpr FixedMap<char8_t,int,22> fm(
        std::array<std::pair<char8_t, int>, 22>(
            {{
                {'Q', 30}, {'R', 27}, {'Y', 24}, {'Z', 21} , {'E', 18},
                {'P', 15}, {'T', 12}, {'G', 9}, {'M', 6}, {'k', 3},
                {'h', 2}, {'d', -1}, {'c', -2}, {'m', -3},
                {'n', -9}, {'p', -12}, {'f', -15}, {'a', -18}, {'z', -21},
                {'y', -24}, {'r', -27}, {'q', -30}
            }}
        )
    );

    return fm.find(c);
}

constexpr std::optional<std::pair<int, size_t>>
parse_long_prefix(std::u8string_view sv)
{
    /*
     * Returns, if found,
     *   1. the prefix exponent of a parsed prefix
     *   2. the size of the parsed prefix in bytes (excl. the 0 terminator)
     */
    if (sv.starts_with(u8"µ"))
        return std::pair(-6, sizeof(u8"µ")-1);
    else if (sv.starts_with(u8"da"))
        return std::pair(1, sizeof(u8"da")-1);

    return std::optional<std::pair<int,size_t>>();
}



enum prefix_class {
    PREFIX,
    NO_PREFIX
};

using ParsedUnit = std::variant<
    std::array<char, N_UNIT>,
    std::tuple<std::array<char, N_UNIT>, int>,
    std::tuple<std::array<char, N_UNIT>, int, double, prefix_class>
>;





constexpr std::optional<ParsedUnit>
parse_si_unit_single(
    std::u8string_view s
)
{
    using Key = std::u8string_view;
    using Val = ParsedUnit;
    using Val0 = std::array<char, N_UNIT>;
    using Val1 = std::tuple<std::array<char, N_UNIT>, int>;
    using Val2 = std::tuple<std::array<char, N_UNIT>, int, double, prefix_class>;

    constexpr double pi = std::numbers::pi_v<double>;

    /* The SI units, plus some more.
     * Tables noted here refer to the SI brochure 9
     *
     * > The International System of Units (SI). Bureau International
     * > des Poids et Mesures, 9th edition, 2019.
     */
    constexpr FixedMap<Key, Val, 55> known_units(
        std::array<std::pair<Key,Val>, 55>(
            {{  //           s  m kg  A  K mol cd rad sr
                /* Base quantities (table 3) */
                {u8"s",   {Val0{ 1, 0, 0, 0, 0,  0, 0,  0, 0}}},
                {u8"m",   {Val0{ 0, 1, 0, 0, 0,  0, 0,  0, 0}}},
                {u8"kg",  {Val0{ 0, 0, 1, 0, 0,  0, 0,  0, 0}}},
                {u8"A",   {Val0{ 0, 0, 0, 1, 0,  0, 0,  0, 0}}},
                {u8"K",   {Val0{ 0, 0, 0, 0, 1,  0, 0,  0, 0}}},
                {u8"mol", {Val0{ 0, 0, 0, 0, 0,  1, 0,  0, 0}}},
                {u8"cd",  {Val0{ 0, 0, 0, 0, 0,  0, 1,  0, 0}}},
                {u8"rad", {Val0{ 0, 0, 0, 0, 0,  0, 0,  1, 0}}},
                {u8"sr",  {Val0{ 0, 0, 0, 0, 0,  0, 0,  0, 1}}},
                {u8"rad", {Val0{ 0, 0, 0, 0, 0,  0, 0,  1, 0}}},
                /* Units with special names (table 4) */
                {u8"Hz",  {Val0{-1, 0, 0, 0, 0,  0, 0,  0, 0}}},
                {u8"N",   {Val0{-2, 1, 1, 0, 0,  0, 0,  0, 0}}},
                {u8"Pa",  {Val0{-2,-1, 1, 0, 0,  0, 0,  0, 0}}},
                {u8"J",   {Val0{-2, 2, 1, 0, 0,  0, 0,  0, 0}}},
                {u8"W",   {Val0{-3, 2, 1, 0, 0,  0, 0,  0, 0}}},
                {u8"C",   {Val0{ 1, 0, 0, 1, 0,  0, 0,  0, 0}}},
                {u8"V",   {Val0{-3, 2, 1,-1, 0,  0, 0,  0, 0}}},
                {u8"Ω",   {Val0{-3, 2, 1,-2, 0,  0, 0,  0, 0}}},
                {u8"S",   {Val0{ 3,-2,-1, 2, 0,  0, 0,  0, 0}}},
                {u8"F",   {Val0{ 4,-2,-1, 2, 0,  0, 0,  0, 0}}},
                {u8"H",   {Val0{-2, 2, 1,-2, 0,  0, 0,  0, 0}}},
                {u8"T",   {Val0{-2, 0, 1,-1, 0,  0, 0,  0, 0}}},
                {u8"Wb",  {Val0{-2, 2, 1,-1, 0,  0, 0,  0, 0}}},
                {u8"°C",  {Val0{ 0, 0, 0, 0, 1,  0, 0,  0, 0}}},
                {u8"lm",  {Val0{ 0, 0, 0, 0, 0,  0, 1,  0, 1}}},
                {u8"lx",  {Val0{ 0,-2, 0, 0, 0,  0, 1,  0, 1}}},
                {u8"Bq",  {Val0{-1, 0, 0, 0, 0,  0, 0,  0, 0}}},
                {u8"Gy",  {Val0{-2, 2, 0, 0, 0,  0, 0,  0, 0}}},
                {u8"Sv",  {Val0{-2, 2, 0, 0, 0,  0, 0,  0, 0}}},
                {u8"kat", {Val0{-1, 0, 0, 0, 0,  1, 0,  0, 0}}},
                /* CGS: */
                {u8"Gal", {Val1{{-2, 1, 0, 0, 0,  0, 0,  0, 0}, -2}}},
                {u8"dyn", {Val1{{-2, 1, 1, 0, 0,  0, 0,  0, 0}, -5}}},
                {u8"erg", {Val1{{-2, 2, 1, 0, 0,  0, 0,  0, 0}, -7}}},
                {u8"Ba",  {Val1{{-2,-1, 1, 0, 0,  0, 0,  0, 0}, -1}}}, // Barye
                {u8"P",   {Val1{{-1,-1, 1, 0, 0,  0, 0,  0, 0}, -1}}}, // Poise
                {u8"St",  {Val1{{-1, 2, 0, 0, 0,  0, 0,  0, 0}, -4}}}, // Stokes
                /* Non-SI accepted units (Table 8): */
                {u8"min", {Val2{{ 1, 0, 0, 0, 0,  0, 0,  0, 0}, 0, 60.0, PREFIX}}},
                {u8"h",   {Val2{{ 1, 0, 0, 0, 0,  0, 0,  0, 0}, 0, 3600.0, PREFIX}}},
                {u8"d",   {Val2{{ 1, 0, 0, 0, 0,  0, 0,  0, 0}, 0, 86400.0, PREFIX}}},
                {u8"au",  {Val2{{ 0, 1, 0, 0, 0,  0, 0,  0, 0}, 0, 149597870700.0, PREFIX}}},
                {u8"°",   {Val2{{ 0, 0, 0, 0, 0,  0, 0,  1, 0}, 0, pi / 180, NO_PREFIX}}},
                {u8"'",   {Val2{{ 0, 0, 0, 0, 0,  0, 0,  1, 0}, 0, pi / 10800, NO_PREFIX}}},
                {u8"\"",  {Val2{{ 0, 0, 0, 0, 0,  0, 0,  1, 0}, 0, pi / 648000, NO_PREFIX}}},
                {u8"ha",  {Val1{{ 0, 2, 0, 0, 0,  0, 0,  0, 0}, 4}}},
                {u8"l",   {Val1{{ 0, 3, 0, 0, 0,  0, 0,  0, 0}, -3}}},
                {u8"L",   {Val1{{ 0, 3, 0, 0, 0,  0, 0,  0, 0}, -3}}},
                {u8"t",   {Val1{{ 0, 0, 1, 0, 0,  0, 0,  0, 0}, 3}}},
                {u8"g",   {Val1{{ 0, 0, 1, 0, 0,  0, 0,  0, 0}, -3}}},
                {u8"Da",  {Val2{{ 0, 0, 1, 0, 0,  0, 0,  0, 0}, -27, 1.66053906892, PREFIX}}},
                {u8"eV",  {Val2{{-2, 2, 1, 0, 0,  0, 0,  0, 0}, -19, 1.602176634, PREFIX}}},
                /* Imperial & US.
                 * Here, we use only length and mass, which are in parts equal
                 * between imperial and US systems [1, p. 13].
                 * Everything else would depend on context---I'm afraid
                 * the users will have to handle that on their own.
                 * Note also that we cannot have
                 *    - prefixes
                 *    - ' and " abbreviations
                 * with imperial/US units.
                 *
                 * [1] United States Department of Commerce. Research Highlights of the
                 *     National Bureau of Standards. Annual Report, Fiscal Year 1959.
                 */
                {u8"in", {Val2{{ 0, 1, 0, 0, 0,  0, 0,  0, 0}, -4, 254., NO_PREFIX}}},
                {u8"ft", {Val2{{ 0, 1, 0, 0, 0,  0, 0,  0, 0}, -4, 3048., NO_PREFIX}}},
                {u8"yd", {Val2{{ 0, 1, 0, 0, 0,  0, 0,  0, 0}, -4, 9144., NO_PREFIX}}},
                {u8"lb", {Val2{{ 0, 0, 1, 0, 0,  0, 0,  0, 0}, -8, 45359237., NO_PREFIX}}},
                {u8"oz", {Val2{{ 0, 0, 1, 0, 0,  0, 0,  0, 0}, -8, 2834952.3125, NO_PREFIX}}},

            }}
        )
    );

    return known_units.find(s);
}


constexpr bool prefix_allowed(const ParsedUnit& u)
{
    return std::visit(
        [](auto&& pu) -> bool
        {
            using T = std::decay_t<decltype(pu)>;
            using T1 = std::variant_alternative_t<2, ParsedUnit>;
            if constexpr (std::is_same_v<T, T1>)
                return std::get<3>(pu) == PREFIX;
            return true;
        },
        u
    );
}




constexpr
tl::expected<ParsedUnit, const char*>
parse_unit_component(std::u8string_view component) noexcept
{
    /* Check for emptiness: */
    if (component.empty())
        return tl::unexpected(
            "Trying to parse the unit of an empty component."
        );


    /* Check if the string is a unit: */
    std::optional<ParsedUnit> pu(parse_si_unit_single(component));
    if (pu)
        return *pu;

    /*
     * This function takes a parsed prefix and adds it to a parsed unit.
     * depending on what type the `ParsedUnit` variant actually refers to,
     * this means that the return type needs to be adjusted.
     */
    auto augment_prefix = [](ParsedUnit& pu, int prefix_) -> ParsedUnit
    {
        return std::visit(
            [prefix_](auto&& pu) -> ParsedUnit
            {
                using T = std::decay_t<decltype(pu)>;
                if constexpr (std::is_same_v<T, std::array<char, N_UNIT>>)
                    /* No prefix in this type. */
                    return std::tuple<std::array<char, N_UNIT>, int>{
                        pu, prefix_
                    };
                else
                    /* Prefix is always second: */
                    std::get<1>(pu) += prefix_;
                return pu;
            },
            pu
        );
    };

    /* If not, see if the first character can be parsed into a prefix: */
    if (std::optional<int> prefix = parse_single_prefix(component[0])){
        /* Is a valid prefix. Check if the remainder can be parsed
         * into a unit: */
        pu = parse_si_unit_single(component.substr(1));
        if (pu && prefix_allowed(*pu))
            return augment_prefix(*pu, *prefix);

        /* No unit could be parsed. */
    }

    /* Otherwise see if we can parse another prefix: */
    if (auto prefix = parse_long_prefix(component)){
        /* Is a valid prefix. Check if the remainder can be parsed
         * into a unit: */
        pu = parse_si_unit_single(component.substr(prefix->second));
        if (pu && prefix_allowed(*pu))
            return augment_prefix(*pu, prefix->first);

            /* No unit could be parsed. */
    }
    return tl::unexpected(
        "Could not parse the unit."
    );
}

} // namespace ::unit



template<Unit U>
constexpr
tl::expected<boost::units::quantity<U>, const char*>
parse_si_unit(std::u8string_view unit_str) noexcept
{
    /* Assert that the system is SI: */
    static_assert(boost::units::is_unit_of_system<U, boost::units::si::system>::value);
    constexpr size_t npos = std::u8string_view::npos;

    /* This array holds the dimension-array representation
     * of the destination unit:*/
    constexpr std::array<int, unit::N_UNIT>
        dest_unit = unit::array_representation<U>();


    /* Initialize the result:
     * parsed_unit<0>: unit vector
     * parsed_unit<1>: prefix scale (log10)
     * parsed_unit<2>: scale (linear)
     */
    std::tuple<std::array<int, unit::N_UNIT>, int, double>
        parsed_unit{{}, 0, 1.0};


    auto parse_component =
    [&parsed_unit](
        std::u8string_view component
    ) -> std::optional<const char*>
    {
        /* Check if there is an exponent: */
        int exponent = 1;
        size_t i_exp = component.find_first_of(u8'^');
        if (i_exp != npos){
            /* Parsing the exponent may fail, in which case we return
             * the error of that failure here: */
            auto expected_exponent = unit::parse_int(component.substr(i_exp+1));
            if (!expected_exponent)
                return expected_exponent.error();
            exponent = expected_exponent.value();
            component = component.substr(0, i_exp);
        }

        /* Check for empty component: */
        if (component.empty())
            return "Trying to parse the unit out of an empty component.";


        /* Parse the unit: */
        auto expected_unit(unit::parse_unit_component(component));
        if (!expected_unit)
            return expected_unit.error();
        unit::ParsedUnit pu(expected_unit.value());

        std::visit(
            [&parsed_unit, exponent](auto&& pu)
            {
                /*
                 * This function computes `U += exponent * Up`,
                 * where `U` is the outer `parsed_unit` that we assemble,
                 * `Up` is the result of parsing the current component, and
                 * `exponent` is the exponent parsed from the current component.
                 */
                auto add_exponentiated_unit =
                [&parsed_unit, exponent]
                (const std::array<char, unit::N_UNIT>& pu)
                {
                    for (size_t i=0; i<unit::N_UNIT; ++i){
                        std::get<0>(parsed_unit)[i] += exponent * pu[i];
                    }
                };

                /* Depending on the type, we have to proceed differently: */
                using UType0 = std::array<char, unit::N_UNIT>;
                using UType1 = std::tuple<std::array<char, unit::N_UNIT>, int>;
                using UType2 = std::tuple<std::array<char, unit::N_UNIT>, int, double, unit::prefix_class>;
                using T = std::decay_t<decltype(pu)>;
                if constexpr (std::is_same_v<T, UType0>)
                    add_exponentiated_unit(pu);

                else if constexpr (std::is_same_v<T, UType1>){
                    /* Add the exponentiated unit: */
                    add_exponentiated_unit(std::get<0>(pu));

                    /* Add the exponentiated prefix: */
                    std::get<1>(parsed_unit) += exponent * std::get<1>(pu);

                } else {
                    /* Possibly keeping sane in future changes: */
                    static_assert(std::is_same_v<T, UType2>);

                    /* Add the exponentiated unit: */
                    add_exponentiated_unit(std::get<0>(pu));

                    /* Add the exponentiated prefix: */
                    std::get<1>(parsed_unit) += exponent * std::get<1>(pu);

                    /* Add the exponentiated scale: */
                    std::get<2>(parsed_unit) *= math::pow(std::get<2>(pu), exponent);
                }
            },
            pu
        );

        return std::optional<const char*>();
    };

    /* Parse the string: */
    size_t start = 0;
    size_t next;
    std::optional<const char*> err;
    while ((next = unit_str.find_first_of(u8' ', start)) != npos)
    {
        err = parse_component(unit_str.substr(start, next - start));
        if (err)
            return tl::unexpected(*err);

        /* Advance in the unit string: */
        start = next + 1;
    }
    err = parse_component(unit_str.substr(start, npos));
    if (err)
        return tl::unexpected(*err);

    /* Assert that quantity is correct: */
    for (size_t i=0; i<unit::N_UNIT; ++i){
        if (dest_unit[i] != std::get<0>(parsed_unit)[i])
            return tl::unexpected(
                "Destination unit not equal to parsed unit."
            );
    }

    /* Collect prefix and scale: */
    double val = std::get<2>(parsed_unit) * math::pow10(std::get<1>(parsed_unit));
    return boost::units::quantity<U>::from_value(val);
}

template<Unit U>
constexpr
tl::expected<boost::units::quantity<U>, const char*>
parse_si_unit(const char8_t* unit_str)
{
    std::u8string_view unit_view(unit_str);
    return parse_si_unit<U>(unit_view);
}

template<Unit U>
tl::expected<boost::units::quantity<U>, const char*>
parse_si_unit(const char* unit_str)
{
    std::u8string_view unit_view(reinterpret_cast<const char8_t*>(unit_str));
    return parse_si_unit<U>(unit_view);
}



} // namespace funktionsklempner



#endif
#endif