--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, TBOOX Open Source Group.
--
-- @author      ruki
-- @file        cxxfeatures.lua
--

-- set features
function _set(feature, condition)
    _g.features = _g.features or {}
    _g.features[feature] = condition
end

-- get features
--
-- http://gcc.gnu.org/projects/cxx0x.html
-- http://gcc.gnu.org/projects/cxx1y.html
-- https://gcc.gnu.org/projects/cxx-status.html
--
-- porting from Modules/Compiler/GNU-CXX-FeatureTests.cmake
--
function main()

    -- init conditions
    -- gcc -x c++ -std=c++20 -dM -E - < /dev/null | grep __cplusplus
    local gcc_minver        = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 404"
    local gcc90_cxx20       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 900 && __cplusplus >= 202002L"
    local gcc70_cxx17       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 700 && __cplusplus >= 201703L"
    local gcc50_cxx14       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 500 && __cplusplus >= 201402L"
    local gcc49_cxx14       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L"
    local gcc481_cxx11      = "((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40801) && __cplusplus >= 201103L"
    local gcc48_cxx11       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L"
    local gcc47_cxx11       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L"
    local gcc_cxx0x_defined = "(__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__))"
    local gcc46_cxx11       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && " .. gcc_cxx0x_defined
    local gcc45_cxx11       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 405 && " .. gcc_cxx0x_defined
    local gcc44_cxx11       = "(__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && " .. gcc_cxx0x_defined
    local gcc43_cxx11       = gcc_minver .. " && " .. gcc_cxx0x_defined

    -- set language standard supports
    _set("cxx_std_98", gcc_minver)
    _set("cxx_std_11", gcc43_cxx11)
    _set("cxx_std_14", gcc49_cxx14)
    _set("cxx_std_17", gcc70_cxx17)
    _set("cxx_std_20", gcc90_cxx20)

    -- set features
    _set("cxx_variable_templates",             gcc50_cxx14)
    _set("cxx_relaxed_constexpr",              gcc50_cxx14)
    _set("cxx_aggregate_default_initializers", gcc50_cxx14)

    -- GNU 4.9 in c++14 mode sets __cplusplus to 201300L, so don't test for the
    -- correct value of it below.
    -- https://patchwork.ozlabs.org/patch/382470/
    _set("cxx_contextual_conversions", gcc49_cxx14)
    _set("cxx_attribute_deprecated",   gcc49_cxx14)
    _set("cxx_decltype_auto",          gcc49_cxx14)
    _set("cxx_digit_separators",       gcc49_cxx14)
    _set("cxx_generic_lambdas",        gcc49_cxx14)
    _set("cxx_lambda_init_captures",   gcc49_cxx14)

    -- GNU 4.3 supports binary literals as an extension, but may warn about
    -- use of extensions prior to GNU 4.9
    -- http://stackoverflow.com/questions/16334024/difference-between-gcc-binary-literals-and-c14-ones
    _set("cxx_binary_literals", gcc49_cxx14)

    -- The feature below is documented as available in GNU 4.8 (by implementing an
    -- earlier draft of the standard paper), but that version of the compiler
    -- does not set __cplusplus to a value greater than 201103L until GNU 4.9:
    -- http://gcc.gnu.org/onlinedocs/gcc-4.8.2/cpp/Standard-Predefined-Macros.html#Standard-Predefined-Macros
    -- http://gcc.gnu.org/onlinedocs/gcc-4.9.0/cpp/Standard-Predefined-Macros.html#Standard-Predefined-Macros
    -- So, CMake only reports availability for it with GNU 4.9 or later.
    _set("cxx_return_type_deduction", gcc49_cxx14)

    -- Introduced in GCC 4.8.1
    _set("cxx_decltype_incomplete_return_types", gcc481_cxx11)
    _set("cxx_reference_qualified_functions",    gcc481_cxx11)

    -- The alignof feature works with GNU 4.7 and -std=c++11, but it is documented
    -- as available with GNU 4.8, so treat that as true.
    _set("cxx_alignas",                 gcc48_cxx11)
    _set("cxx_alignof",                 gcc48_cxx11)
    _set("cxx_attributes",              gcc48_cxx11)
    _set("cxx_inheriting_constructors", gcc48_cxx11)
    _set("cxx_thread_local",            gcc48_cxx11)

    _set("cxx_alias_templates",              gcc47_cxx11)
    _set("cxx_delegating_constructors",      gcc47_cxx11)
    _set("cxx_extended_friend_declarations", gcc47_cxx11)
    _set("cxx_final",                        gcc47_cxx11)
    _set("cxx_nonstatic_member_init",        gcc47_cxx11)
    _set("cxx_override",                     gcc47_cxx11)
    _set("cxx_user_literals",                gcc47_cxx11)

    -- NOTE: C++11 was ratified in September 2011. GNU 4.7 is the first minor
    -- release following that (March 2012), and the first minor release to
    -- support -std=c++11. Prior to that, support for C++11 features is technically
    -- experiemental and possibly incomplete (see for example the note below about
    -- cxx_variadic_template_template_parameters)
    -- GNU does not define __cplusplus correctly before version 4.7.
    -- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=1773
    -- __GXX_EXPERIMENTAL_CXX0X__ is defined in prior versions, but may not be
    -- defined in the future.
    _set("cxx_constexpr",                   gcc46_cxx11)
    _set("cxx_defaulted_move_initializers", gcc46_cxx11)
    _set("cxx_enum_forward_declarations",   gcc46_cxx11)
    _set("cxx_noexcept",                    gcc46_cxx11)
    _set("cxx_nullptr",                     gcc46_cxx11)
    _set("cxx_range_for",                   gcc46_cxx11)
    _set("cxx_unrestricted_unions",         gcc46_cxx11)

    _set("cxx_explicit_conversions",     gcc45_cxx11)
    _set("cxx_lambdas",                  gcc45_cxx11)
    _set("cxx_local_type_template_args", gcc45_cxx11)
    _set("cxx_raw_string_literals",      gcc45_cxx11)

    _set("cxx_auto_type",                gcc44_cxx11)
    _set("cxx_defaulted_functions",      gcc44_cxx11)
    _set("cxx_deleted_functions",        gcc44_cxx11)
    _set("cxx_generalized_initializers", gcc44_cxx11)
    _set("cxx_inline_namespaces",        gcc44_cxx11)
    _set("cxx_sizeof_member",            gcc44_cxx11)
    _set("cxx_strong_enums",             gcc44_cxx11)
    _set("cxx_trailing_return_types",    gcc44_cxx11)
    _set("cxx_unicode_literals",         gcc44_cxx11)
    _set("cxx_uniform_initialization",   gcc44_cxx11)
    _set("cxx_variadic_templates",       gcc44_cxx11)

    -- TODO: If features are ever recorded for GNU 4.3, there should possibly
    -- be a new feature added like cxx_variadic_template_template_parameters,
    -- which is implemented by GNU 4.4, but not 4.3. cxx_variadic_templates is
    -- actually implemented by GNU 4.3, but variadic template template parameters
    -- 'completes' it, so that is the version we record as having the variadic
    -- templates capability in CMake. See
    -- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2555.pdf
    -- TODO: Should be supported by GNU 4.3
    _set("cxx_decltype",                       gcc43_cxx11)
    _set("cxx_default_function_template_args", gcc43_cxx11)
    _set("cxx_long_long_type",                 gcc43_cxx11)
    _set("cxx_right_angle_brackets",           gcc43_cxx11)
    _set("cxx_rvalue_references",              gcc43_cxx11)
    _set("cxx_static_assert",                  gcc43_cxx11)

    -- TODO: Should be supported since GNU 3.4?
    _set("cxx_extern_templates", gcc_minver .. " && " .. gcc_cxx0x_defined)

    -- TODO: Should be supported forever?
    _set("cxx_func_identifier", gcc_minver .. " && " .. gcc_cxx0x_defined)
    _set("cxx_variadic_macros", gcc_minver .. " && " .. gcc_cxx0x_defined)
    _set("cxx_template_template_parameters", gcc_minver .. " && __cplusplus")

    -- c++17 language features with predefined macros
    -- https://en.cppreference.com/w/cpp/feature_test
    local features_cxx17 = {
        "__cpp_aggregate_bases",
        "__cpp_aligned_new",
        "__cpp_capture_star_this",
        "__cpp_constexpr",
        "__cpp_deduction_guides",
        "__cpp_enumerator_attributes",
        "__cpp_fold_expressions",
        "__cpp_guaranteed_copy_elision",
        "__cpp_hex_float",
        "__cpp_if_constexpr",
        "__cpp_inheriting_constructors",
        "__cpp_inline_variables",
        "__cpp_namespace_attributes",
        "__cpp_noexcept_function_type",
        "__cpp_nontype_template_args",
        "__cpp_nontype_template_parameter_auto",
        "__cpp_range_based_for",
        "__cpp_static_assert",
        "__cpp_structured_bindings",
        "__cpp_template_template_args",
        "__cpp_variadic_using"}
    for _, feature in ipairs(features_cxx17) do
        _set((feature:gsub("__cpp", "cxx")), "__cplusplus && defined(" .. feature .. ")")
    end

    -- c++20 language features with predefined macros
    -- https://en.cppreference.com/w/cpp/feature_test
    local features_cxx20 = {
        "__cpp_aggregate_paren_init",
        "__cpp_char8_t",
        "__cpp_concepts",
        "__cpp_conditional_explicit",
        "__cpp_consteval",
        "__cpp_constexpr",
        "__cpp_constexpr_dynamic_alloc",
        "__cpp_constexpr_in_decltype",
        "__cpp_constinit",
        "__cpp_deduction_guides",
        "__cpp_designated_initializers",
        "__cpp_generic_lambdas",
        "__cpp_impl_coroutine",
        "__cpp_impl_destroying_delete",
        "__cpp_impl_three_way_comparison",
        "__cpp_init_captures",
        "__cpp_modules",
        "__cpp_nontype_template_args",
        "__cpp_using_enum"}
    for _, feature in ipairs(features_cxx20) do
        _set((feature:gsub("__cpp", "cxx")), "__cplusplus && defined(" .. feature .. ")")
    end

    -- get features
    return _g.features
end

