#!/usr/bin/env python

import os
from builtins import range
from dataclasses import dataclass
from functools import reduce
from typing import (
    Any,
    Dict,
    List,  # Needed for python 3.8 compatibility.
    NewType,
    Optional,
    Set,
)
import functools
import json
from libcxx.header_information import module_c_headers, module_headers, header_restrictions, headers_not_available, libcxx_root


def get_libcxx_paths():
    utils_path = os.path.dirname(os.path.abspath(__file__))
    script_name = os.path.basename(__file__)
    assert os.path.exists(utils_path)
    src_root = os.path.dirname(utils_path)
    include_path = os.path.join(src_root, "include")
    assert os.path.exists(include_path)
    docs_path = os.path.join(src_root, "docs")
    assert os.path.exists(docs_path)
    macro_test_path = os.path.join(
        src_root,
        "test",
        "std",
        "language.support",
        "support.limits",
        "support.limits.general",
    )
    assert os.path.exists(macro_test_path)
    assert os.path.exists(
        os.path.join(macro_test_path, "version.version.compile.pass.cpp")
    )
    return script_name, src_root, include_path, docs_path, macro_test_path


script_name, source_root, include_path, docs_path, macro_test_path = get_libcxx_paths()


def has_header(h):
    h_path = os.path.join(include_path, h)
    return os.path.exists(h_path)


def add_version_header(tc):
    tc["headers"].append("version")
    return tc


# ================  ============================================================
# Field             Description
# ================  ============================================================
# name              The name of the feature-test macro.
# values            A dict whose keys are C++ versions and whose values are the
#                   value of the feature-test macro for that C++ version.
#                   (TODO: This isn't a very clean model for feature-test
#                   macros affected by multiple papers.)
# headers           An array with the headers that should provide the
#                   feature-test macro.
# test_suite_guard  An optional string field. When this field is provided,
#                   `libcxx_guard` must also be provided. This field is used
#                   only to generate the unit tests for the feature-test macros.
#                   It can't depend on macros defined in <__config> because the
#                   `test/std/` parts of the test suite are intended to be
#                   portable to any C++ standard library implementation, not
#                   just libc++. It may depend on
#                    * macros defined by the compiler itself, or
#                    * macros generated by CMake.
#                   In some cases we add also depend on macros defined in
#                   <__configuration/availability.h>.
# libcxx_guard      An optional string field. When this field is provided,
#                   `test_suite_guard` must also be provided. This field is used
#                   only to guard the feature-test macro in <version>. It may
#                   be the same as `test_suite_guard`, or it may depend on
#                   macros defined in <__config>.
# unimplemented     An optional Boolean field with the value `True`. This field
#                   is only used when a feature isn't fully implemented. Once
#                   you've fully implemented the feature, you should remove
#                   this field.
# ================  ============================================================
feature_test_macros = [
    add_version_header(x)
    for x in [
        {
            "name": "__cpp_lib_adaptor_iterator_pair_constructor",
            "values": {"c++23": 202106},
            "headers": ["queue", "stack"],
        },
        {
            "name": "__cpp_lib_addressof_constexpr",
            "values": {"c++17": 201603},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_aligned_accessor",
            "values": {"c++26": 202411},
            "headers": ["mdspan"],
        },
        {
            "name": "__cpp_lib_allocate_at_least",
            "values": {
                # Note LWG3887 Version macro for allocate_at_least
                "c++23": 202302,  # P2652R2 Disallow User Specialization of allocator_traits
            },
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_allocator_traits_is_always_equal",
            "values": {"c++17": 201411},
            "headers": [
                "deque",
                "forward_list",
                "list",
                "map",
                "memory",
                "scoped_allocator",
                "set",
                "string",
                "unordered_map",
                "unordered_set",
                "vector",
            ],
        },
        {
            "name": "__cpp_lib_any",
            "values": {"c++17": 201606},
            "headers": ["any"],
        },
        {
            "name": "__cpp_lib_apply",
            "values": {"c++17": 201603},
            "headers": ["tuple"],
        },
        {
            "name": "__cpp_lib_array_constexpr",
            "values": {"c++17": 201603, "c++20": 201811},
            "headers": ["array", "iterator"],
        },
        {
            "name": "__cpp_lib_as_const",
            "values": {"c++17": 201510},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_associative_heterogeneous_erasure",
            "values": {"c++23": 202110},
            "headers": ["map", "set", "unordered_map", "unordered_set"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_associative_heterogeneous_insertion",
            "values": {
                "c++26": 202306  # P2363R5 Extending associative containers with the remaining heterogeneous overloads
            },
            "headers": ["map", "set", "unordered_map", "unordered_set"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_assume_aligned",
            "values": {"c++20": 201811},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_atomic_flag_test",
            "values": {"c++20": 201907},
            "headers": ["atomic"],
        },
        {
            "name": "__cpp_lib_atomic_float",
            "values": {"c++20": 201711},
            "headers": ["atomic"],
        },
        {
            "name": "__cpp_lib_atomic_is_always_lock_free",
            "values": {"c++17": 201603},
            "headers": ["atomic"],
        },
        {
            "name": "__cpp_lib_atomic_lock_free_type_aliases",
            "values": {"c++20": 201907},
            "headers": ["atomic"],
        },
        {
            "name": "__cpp_lib_atomic_min_max",
            "values": {"c++26": 202403}, # P0493R5: Atomic minimum/maximum
            "headers": ["atomic"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_atomic_ref",
            "values": {"c++20": 201806},
            "headers": ["atomic"],
        },
        {
            "name": "__cpp_lib_atomic_shared_ptr",
            "values": {"c++20": 201711},
            "headers": ["atomic"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_atomic_value_initialization",
            "values": {"c++20": 201911},
            "headers": ["atomic", "memory"],
        },
        {
            "name": "__cpp_lib_atomic_wait",
            "values": {"c++20": 201907},
            "headers": ["atomic"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_SYNC",
            "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_SYNC",
        },
        {
            "name": "__cpp_lib_barrier",
            "values": {"c++20": 201907},
            "headers": ["barrier"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)",
            "libcxx_guard": "_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC",
        },
        {
            "name": "__cpp_lib_bind_back",
            "values": {
                "c++23": 202202,
                # "c++26": 202306,  # P2714R1 Bind front and back to NTTP callables
            },
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_bind_front",
            "values": {
                "c++20": 201907,
                "c++26": 202306,  # P2714R1 Bind front and back to NTTP callables
            },
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_bit_cast",
            "values": {"c++20": 201806},
            "headers": ["bit"],
        },
        {
            "name": "__cpp_lib_bitops",
            "values": {"c++20": 201907},
            "headers": ["bit"],
        },
        {
            "name": "__cpp_lib_bitset",
            "values": {"c++26": 202306},  # P2697R1 Interfacing bitset with string_view
            "headers": ["bitset"],
        },
        {
            "name": "__cpp_lib_bool_constant",
            "values": {"c++17": 201505},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_bounded_array_traits",
            "values": {"c++20": 201902},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_boyer_moore_searcher",
            "values": {"c++17": 201603},
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_byte",
            "values": {"c++17": 201603},
            "headers": ["cstddef"],
        },
        {
            "name": "__cpp_lib_byteswap",
            "values": {"c++23": 202110},
            "headers": ["bit"],
        },
        {
            "name": "__cpp_lib_char8_t",
            "values": {"c++20": 201907},
            "headers": [
                "atomic",
                "filesystem",
                "istream",
                "limits",
                "locale",
                "ostream",
                "string",
                "string_view",
            ],
            "test_suite_guard": "defined(__cpp_char8_t)",
            "libcxx_guard": "_LIBCPP_HAS_CHAR8_T",
        },
        {
            "name": "__cpp_lib_chrono",
            "values": {
                "c++17": 201611,
                # "c++26": 202306, # P2592R3 Hashing support for std::chrono value classes
            },
            "headers": ["chrono"],
        },
        {
            "name": "__cpp_lib_chrono_udls",
            "values": {"c++14": 201304},
            "headers": ["chrono"],
        },
        {
            "name": "__cpp_lib_clamp",
            "values": {"c++17": 201603},
            "headers": ["algorithm"],
        },
        {
            "name": "__cpp_lib_common_reference",
            "values": {"c++20": 202302},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_common_reference_wrapper",
            "values": {"c++20": 202302},
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_complex_udls",
            "values": {"c++14": 201309},
            "headers": ["complex"],
        },
        {
            "name": "__cpp_lib_concepts",
            "values": {"c++20": 202002},
            "headers": ["concepts"],
        },
        {
            "name": "__cpp_lib_constexpr_algorithms",
            "values": {
                "c++20": 201806,
                "c++26": 202306,
            },
            "headers": ["algorithm", "utility"],
        },
        {
            "name": "__cpp_lib_constexpr_bitset",
            "values": {"c++23": 202207},
            "headers": ["bitset"],
        },
        {
            "name": "__cpp_lib_constexpr_charconv",
            "values": {"c++23": 202207},
            "headers": ["charconv"],
        },
        {
            "name": "__cpp_lib_constexpr_cmath",
            "values": {"c++23": 202202},
            "headers": ["cmath", "cstdlib"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_constexpr_complex",
            "values": {"c++20": 201711},
            "headers": ["complex"],
        },
        {
            "name": "__cpp_lib_constexpr_dynamic_alloc",
            "values": {"c++20": 201907},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_constexpr_forward_list",
            "values": {"c++26": 202502},
            "headers": ["forward_list"],
        },
        {
            "name": "__cpp_lib_constexpr_functional",
            "values": {"c++20": 201907},
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_constexpr_iterator",
            "values": {"c++20": 201811},
            "headers": ["iterator"],
        },
        {
            "name": "__cpp_lib_constexpr_list",
            "values": {"c++26": 202502},
            "headers": ["list"],
        },
        {
            "name": "__cpp_lib_constexpr_memory",
            "values": {"c++20": 201811, "c++23": 202202},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_constexpr_new",
            "values": {"c++26": 202406},  # P2747R2 constexpr placement new
            "headers": ["new"],
            "test_suite_guard": "!defined(_LIBCPP_ABI_VCRUNTIME)",
            "libcxx_guard": "!defined(_LIBCPP_ABI_VCRUNTIME)",
        },
        {
            "name": "__cpp_lib_constexpr_numeric",
            "values": {"c++20": 201911},
            "headers": ["numeric"],
        },
        {
            "name": "__cpp_lib_constexpr_queue",
            "values": {"c++26": 202502},
            "headers": ["queue"],
        },
        {
            "name": "__cpp_lib_constexpr_string",
            "values": {"c++20": 201907},
            "headers": ["string"],
        },
        {
            "name": "__cpp_lib_constexpr_string_view",
            "values": {"c++20": 201811},
            "headers": ["string_view"],
        },
        {
            "name": "__cpp_lib_constexpr_tuple",
            "values": {"c++20": 201811},
            "headers": ["tuple"],
        },
        {
            "name": "__cpp_lib_constexpr_typeinfo",
            "values": {"c++23": 202106},
            "headers": ["typeinfo"],
        },
        {
            "name": "__cpp_lib_constexpr_utility",
            "values": {"c++20": 201811},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_constexpr_vector",
            "values": {"c++20": 201907},
            "headers": ["vector"],
        },
        {
            "name": "__cpp_lib_constrained_equality",
            "values": {
                # "c++26": 202403,  # P2944R3: Comparisons for reference_wrapper
                "c++26": 202411,  # P3379R0: Constrain std::expected equality operators
            },
            "headers": ["expected", "optional", "tuple", "utility", "variant"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_containers_ranges",
            "values": {"c++23": 202202},
            "headers": [
                "deque",
                "forward_list",
                "list",
                "map",
                "queue",
                "set",
                "stack",
                "string",
                "unordered_map",
                "unordered_set",
                "vector",
            ],
        },
        {
            "name": "__cpp_lib_copyable_function",
            "values": {"c++26": 202306},  # P2548R6 copyable_function
            "headers": ["functional"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_coroutine",
            "values": {"c++20": 201902},
            "headers": ["coroutine"],
        },
        {
            "name": "__cpp_lib_debugging",
            "values": {
                "c++26": 202311, # P2546R5 Debugging Support
                # "c++26": 202403, # P2810R4: is_debugger_present is_replaceable
            },
            "headers": ["debugging"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_default_template_type_for_algorithm_values",
            "values": {"c++26": 202403}, # P2248R8: Enabling list-initialization for algorithms
            "headers": ["algorithm", "deque", "forward_list", "list", "ranges", "string", "vector"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_destroying_delete",
            "values": {"c++20": 201806},
            "headers": ["new"],
            "test_suite_guard": "TEST_STD_VER > 17 && defined(__cpp_impl_destroying_delete) && __cpp_impl_destroying_delete >= 201806L",
            "libcxx_guard": "_LIBCPP_STD_VER >= 20 && defined(__cpp_impl_destroying_delete) && __cpp_impl_destroying_delete >= 201806L",
        },
        {
            "name": "__cpp_lib_enable_shared_from_this",
            "values": {"c++17": 201603},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_endian",
            "values": {"c++20": 201907},
            "headers": ["bit"],
        },
        {
            "name": "__cpp_lib_erase_if",
            "values": {"c++20": 202002},
            "headers": [
                "deque",
                "forward_list",
                "list",
                "map",
                "set",
                "string",
                "unordered_map",
                "unordered_set",
                "vector",
            ],
        },
        {
            "name": "__cpp_lib_exchange_function",
            "values": {"c++14": 201304},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_execution",
            "values": {"c++17": 201603, "c++20": 201902},
            "headers": ["execution"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_expected",
            "values": {"c++23": 202211},
            "headers": ["expected"],
        },
        {
            "name": "__cpp_lib_filesystem",
            "values": {"c++17": 201703},
            "headers": ["filesystem"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_FILESYSTEM && _LIBCPP_AVAILABILITY_HAS_FILESYSTEM_LIBRARY)",
            "libcxx_guard": "_LIBCPP_HAS_FILESYSTEM && _LIBCPP_AVAILABILITY_HAS_FILESYSTEM_LIBRARY",
        },
        {
            "name": "__cpp_lib_flat_map",
            "values": {"c++23": 202207},
            "headers": ["flat_map"],
        },
        {
            "name": "__cpp_lib_flat_set",
            "values": {"c++23": 202207},
            "headers": ["flat_set"],
        },
        {
            "name": "__cpp_lib_format",
            "values": {
                "c++20": 202110,
                # "c++23": 202207, Not implemented P2419R2 Clarify handling of encodings in localized formatting of chrono types
                # "c++26": 202306, P2637R3 Member Visit (implemented)
                # "c++26": 202311, P2918R2 Runtime format strings II (implemented)
            },
            # Note these three papers are adopted at the June 2023 meeting and have sequential numbering
            # 202304 P2510R3 Formatting pointers (Implemented)
            # 202305 P2757R3 Type-checking format args
            # 202306 P2637R3 Member Visit
            "headers": ["format"],
            # Trying to use `std::format` where to_chars floating-point is not
            # available causes compilation errors, even with non floating-point types.
            # https://github.com/llvm/llvm-project/issues/125353
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_TO_CHARS_FLOATING_POINT",
            "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_TO_CHARS_FLOATING_POINT",
        },
        {
            "name": "__cpp_lib_format_path",
            "values": {"c++26": 202403},  # P2845R8: Formatting of std::filesystem::path
            "headers": ["filesystem"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_format_ranges",
            "values": {"c++23": 202207},
            "headers": ["format"],
        },
        {
            "name": "__cpp_lib_format_uchar",
            "values": {
                "c++20": 202311  # DR P2909R4 Fix formatting of code units as integers
            },
            "headers": [
                "format"  # TODO verify this entry since the paper was underspecified.
            ],
        },
        {
            "name": "__cpp_lib_formatters",
            "values": {"c++23": 202302},
            "headers": ["stacktrace", "thread"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_forward_like",
            "values": {"c++23": 202207},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_freestanding_algorithm",
            "values": {
                "c++26": 202311  # P2407R5 Freestanding Library: Partial Classes
            },
            "headers": ["algorithm"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_freestanding_array",
            "values": {
                "c++26": 202311  # P2407R5 Freestanding Library: Partial Classes
            },
            "headers": ["array"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_freestanding_cstring",
            "values": {
                "c++26": 202306  # P2338R4 Freestanding Library: Character primitives and the C library
                #        202311  # P2407R5 Freestanding Library: Partial Classes
            },
            "headers": ["cstring"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_freestanding_expected",
            "values": {
                "c++26": 202311  # P2833R2 Freestanding Library: inout expected span
            },
            "headers": ["expected"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_freestanding_mdspan",
            "values": {
                "c++26": 202311  # P2833R2 Freestanding Library: inout expected span
            },
            "headers": ["mdspan"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_freestanding_optional",
            "values": {
                "c++26": 202311  # P2407R5 Freestanding Library: Partial Classes
            },
            "headers": ["optional"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_freestanding_string_view",
            "values": {
                "c++26": 202311  # P2407R5 Freestanding Library: Partial Classes
            },
            "headers": ["string_view"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_freestanding_variant",
            "values": {
                "c++26": 202311  # P2407R5 Freestanding Library: Partial Classes
            },
            "headers": ["variant"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_fstream_native_handle",
            "values": {"c++26": 202306},  # P1759R6 Native handles and file streams
            "headers": ["fstream"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION)",
            "libcxx_guard": "_LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION",
        },
        {
            "name": "__cpp_lib_function_ref",
            "values": {
                "c++26": 202306  # P0792R14 function_ref: a type-erased callable reference
            },
            "headers": ["functional"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_gcd_lcm",
            "values": {"c++17": 201606},
            "headers": ["numeric"],
        },
        {
            "name": "__cpp_lib_generate_random",
            "values": {"c++26": 202403}, # P1068R11: Vector API for random number generation
            "headers": ["random"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_generic_associative_lookup",
            "values": {"c++14": 201304},
            "headers": ["map", "set"],
        },
        {
            "name": "__cpp_lib_generic_unordered_lookup",
            "values": {"c++20": 201811},
            "headers": ["unordered_map", "unordered_set"],
        },
        {
            "name": "__cpp_lib_hardware_interference_size",
            "values": {"c++17": 201703},
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || (defined(__GCC_DESTRUCTIVE_SIZE) && defined(__GCC_CONSTRUCTIVE_SIZE))",
            "libcxx_guard": "defined(__GCC_DESTRUCTIVE_SIZE) && defined(__GCC_CONSTRUCTIVE_SIZE)",
            "headers": ["new"],
        },
        {
            "name": "__cpp_lib_has_unique_object_representations",
            "values": {"c++17": 201606},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_hazard_pointer",
            "values": {"c++26": 202306},  # P2530R3 Hazard Pointers for C++26
            "headers": [
                "hazard_pointer"  # TODO verify this entry since the paper was underspecified.
            ],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_hypot",
            "values": {"c++17": 201603},
            "headers": ["cmath"],
        },
        {
            "name": "__cpp_lib_incomplete_container_elements",
            "values": {"c++17": 201505},
            "headers": ["forward_list", "list", "vector"],
        },
        {
            "name": "__cpp_lib_inplace_vector",
            "values": {"c++26": 202406},  # P0843R14 inplace_vector
            "headers": ["inplace_vector"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_int_pow2",
            "values": {"c++20": 202002},
            "headers": ["bit"],
        },
        {
            "name": "__cpp_lib_integer_comparison_functions",
            "values": {"c++20": 202002},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_integer_sequence",
            "values": {"c++14": 201304},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_integral_constant_callable",
            "values": {"c++14": 201304},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_interpolate",
            "values": {"c++20": 201902},
            "headers": ["cmath", "numeric"],
        },
        {
            "name": "__cpp_lib_invoke",
            "values": {"c++17": 201411},
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_invoke_r",
            "values": {"c++23": 202106},
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_ios_noreplace",
            "values": {"c++23": 202207},
            "headers": ["ios"],
        },
        {
            "name": "__cpp_lib_is_aggregate",
            "values": {"c++17": 201703},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_constant_evaluated",
            "values": {"c++20": 201811},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_final",
            "values": {"c++14": 201402},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_implicit_lifetime",
            "values": {"c++23": 202302},
            "headers": ["type_traits"],
            "test_suite_guard": "__has_builtin(__builtin_is_implicit_lifetime)",
            "libcxx_guard": "__has_builtin(__builtin_is_implicit_lifetime)",
        },
        {
            "name": "__cpp_lib_is_invocable",
            "values": {"c++17": 201703},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_layout_compatible",
            "values": {"c++20": 201907},
            "headers": ["type_traits"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_is_nothrow_convertible",
            "values": {"c++20": 201806},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_null_pointer",
            "values": {"c++14": 201309},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_pointer_interconvertible",
            "values": {"c++20": 201907},
            "headers": ["type_traits"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_is_scoped_enum",
            "values": {"c++23": 202011},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_sufficiently_aligned",
            "values": {"c++26": 202411},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_is_swappable",
            "values": {"c++17": 201603},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_is_virtual_base_of",
            "values": {
                "c++26": 202406  # P2985R0 A type trait for detecting virtual base classes
            },
            "headers": ["type_traits"],
            "test_suite_guard": "__has_builtin(__builtin_is_virtual_base_of)",
            "libcxx_guard": "__has_builtin(__builtin_is_virtual_base_of)",
        },
        {
            "name": "__cpp_lib_is_within_lifetime",
            # Note this name was changed from "__cpp_lib_within_lifetime" when the paper was adopted
            # https://github.com/cplusplus/draft/commit/0facada4cadd97e1ba15bfaea76a804f1dc5c309
            "values": {
                "c++26": 202306  # P2641R4 Checking if a union alternative is active
            },
            "headers": ["type_traits"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_jthread",
            "values": {"c++20": 201911},
            "headers": ["stop_token", "thread"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)",
            "libcxx_guard": "_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC",
        },
        {
            "name": "__cpp_lib_latch",
            "values": {"c++20": 201907},
            "headers": ["latch"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)",
            "libcxx_guard": "_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC",
        },
        {
            "name": "__cpp_lib_launder",
            "values": {"c++17": 201606},
            "headers": ["new"],
        },
        {
            "name": "__cpp_lib_linalg",
            "values": {
                "c++26": 202311  # P1673 A free function linear algebra interface based on the BLAS
            },
            "headers": ["linalg"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_list_remove_return_type",
            "values": {"c++20": 201806},
            "headers": ["forward_list", "list"],
        },
        {
            "name": "__cpp_lib_logical_traits",
            "values": {"c++17": 201510},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_make_from_tuple",
            "values": {"c++17": 201606},
            "headers": ["tuple"],
        },
        {
            "name": "__cpp_lib_make_reverse_iterator",
            "values": {"c++14": 201402},
            "headers": ["iterator"],
        },
        {
            "name": "__cpp_lib_make_unique",
            "values": {"c++14": 201304},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_map_try_emplace",
            "values": {"c++17": 201411},
            "headers": ["map"],
        },
        {
            "name": "__cpp_lib_math_constants",
            "values": {"c++20": 201907},
            "headers": ["numbers"],
        },
        {
            "name": "__cpp_lib_math_special_functions",
            "values": {"c++17": 201603},
            "headers": ["cmath"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_mdspan",
            "values": {
                "c++23": 202207,
                "c++26": 202406,  # P2389R2 dextents Index Type Parameter
            },
            "headers": ["mdspan"],
        },
        {
            "name": "__cpp_lib_memory_resource",
            "values": {"c++17": 201603},
            "headers": ["memory_resource"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_PMR",
            "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_PMR",
        },
        {
            "name": "__cpp_lib_modules",
            "values": {"c++23": 202207},
            "headers": [],
        },
        {
            "name": "__cpp_lib_move_iterator_concept",
            "values": {"c++20": 202207},
            "headers": ["iterator"],
        },
        {
            "name": "__cpp_lib_move_only_function",
            "values": {"c++23": 202110},
            "headers": ["functional"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_node_extract",
            "values": {"c++17": 201606},
            "headers": ["map", "set", "unordered_map", "unordered_set"],
        },
        {
            "name": "__cpp_lib_nonmember_container_access",
            "values": {"c++17": 201411},
            "headers": [
                "array",
                "deque",
                "forward_list",
                "iterator",
                "list",
                "map",
                "regex",
                "set",
                "string",
                "unordered_map",
                "unordered_set",
                "vector",
            ],
        },
        {
            "name": "__cpp_lib_not_fn",
            "values": {
                "c++17": 201603,
                "c++26": 202306,  # P2714R1 Bind front and back to NTTP callables
            },
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_null_iterators",
            "values": {"c++14": 201304},
            "headers": ["iterator"],
        },
        {
            "name": "__cpp_lib_optional",
            "values": {
                "c++17": 201606,
                "c++20": 202106,  # P2231R1 Missing constexpr in std::optional and std::variant
                "c++23": 202110,  # P0798R8 Monadic operations for std::optional + LWG3621 Remove feature-test macro __cpp_lib_monadic_optional
            },
            "headers": ["optional"],
        },
        {
            "name": "__cpp_lib_optional_range_support",
            "values": {"c++26": 202406},  # P3168R2 Give std::optional Range Support
            "headers": ["optional"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_out_ptr",
            "values": {
                "c++23": 202106,
                "c++26": 202311,  # P2833R2 Freestanding Library: inout expected span
            },
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_parallel_algorithm",
            "values": {"c++17": 201603},
            "headers": ["algorithm", "numeric"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_philox_engine",
            "values": {
                "c++26": 202406
            },  # P2075R6 Philox as an extension of the C++ RNG engines
            # Note the paper mentions 202310L as value, which differs from the typical procedure.
            "headers": ["random"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_polymorphic_allocator",
            "values": {"c++20": 201902},
            "headers": ["memory_resource"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_PMR",
            "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_PMR",
        },
        {
            "name": "__cpp_lib_print",
            "values": {
                "c++23": 202207,
                # "c++26": 202403, # P3107R5: Permit an efficient implementation of std::print
                # "c++26": 202406, # P3235R3 std::print more types faster with less memory
            },
            "headers": ["ostream", "print"],
            # Trying to use `std::print` where to_chars floating-point is not
            # available causes compilation errors, even with non floating-point types.
            # https://github.com/llvm/llvm-project/issues/125353
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_TO_CHARS_FLOATING_POINT",
            "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_TO_CHARS_FLOATING_POINT",
        },
        {
            "name": "__cpp_lib_quoted_string_io",
            "values": {"c++14": 201304},
            "headers": ["iomanip"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_HAS_LOCALIZATION",
            "libcxx_guard": "_LIBCPP_HAS_LOCALIZATION",
        },
        {
            "name": "__cpp_lib_ranges",
            "values": {
                "c++20": 202110,  # P2415R2 What is a view?
                "c++23": 202406,  # P2997R1 Removing the common reference requirement from the indirectly invocable concepts (implemented as a DR against C++20)
            },
            "headers": ["algorithm", "functional", "iterator", "memory", "ranges"],
        },
        {
            "name": "__cpp_lib_ranges_as_const",
            "values": {
                "c++23": 202207  # P2278R4 cbegin should always return a constant iterator
                #        202311  # DR P2836R1 std::basic_const_iterator should follow its underlying type’s convertibility
            },
            "headers": ["ranges"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_ranges_as_rvalue",
            "values": {"c++23": 202207},
            "headers": ["ranges"],
        },
        {
            "name": "__cpp_lib_ranges_chunk",
            "values": {"c++23": 202202},
            "headers": ["ranges"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_ranges_chunk_by",
            "values": {"c++23": 202202},
            "headers": ["ranges"],
        },
        {
            "name": "__cpp_lib_ranges_concat",
            "values": {"c++26": 202403}, # P2542R8: views::concat
            "headers": ["ranges"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_ranges_contains",
            "values": {"c++23": 202207},
            "headers": ["algorithm"],
        },
        {
            "name": "__cpp_lib_ranges_find_last",
            "values": {"c++23": 202207},
            "headers": ["algorithm"],
        },
        {
            "name": "__cpp_lib_ranges_iota",
            "values": {"c++23": 202202},
            "headers": ["numeric"],
        },
        {
            "name": "__cpp_lib_ranges_join_with",
            "values": {"c++23": 202202},
            "headers": ["ranges"],
        },
        {
            "name": "__cpp_lib_ranges_repeat",
            "values": {"c++23": 202207},
            "headers": ["ranges"],
        },
        {
            "name": "__cpp_lib_ranges_slide",
            "values": {"c++23": 202202},
            "headers": ["ranges"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_ranges_starts_ends_with",
            "values": {"c++23": 202106},
            "headers": ["algorithm"],
        },
        {
            "name": "__cpp_lib_ranges_to_container",
            "values": {"c++23": 202202},
            "headers": ["ranges"],
        },
        {
            "name": "__cpp_lib_ranges_zip",
            "values": {"c++23": 202110},
            "headers": ["ranges", "tuple", "utility"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_ratio",
            "values": {"c++26": 202306},  # P2734R0 Adding the new SI prefixes
            "headers": ["ratio"],
        },
        {
            "name": "__cpp_lib_raw_memory_algorithms",
            "values": {"c++17": 201606},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_rcu",
            "values": {"c++26": 202306},  # P2545R4 Read-Copy Update (RCU)
            "headers": [
                "rcu"  # TODO verify this entry since the paper was underspecified.
            ],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_reference_from_temporary",
            "values": {"c++23": 202202},
            "headers": ["type_traits"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_reference_wrapper",
            "values": {"c++26": 202403}, # P2944R3: Comparisons for reference_wrapper
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_remove_cvref",
            "values": {"c++20": 201711},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_result_of_sfinae",
            "values": {"c++14": 201210},
            "headers": ["functional", "type_traits"],
        },
        {
            "name": "__cpp_lib_robust_nonmodifying_seq_ops",
            "values": {"c++14": 201304},
            "headers": ["algorithm"],
        },
        {
            "name": "__cpp_lib_sample",
            "values": {"c++17": 201603},
            "headers": ["algorithm"],
        },
        {
            "name": "__cpp_lib_saturation_arithmetic",
            "values": {"c++26": 202311},  # P0543R3 Saturation arithmetic
            "headers": ["numeric"],
        },
        {
            "name": "__cpp_lib_scoped_lock",
            "values": {"c++17": 201703},
            "headers": ["mutex"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_HAS_THREADS",
            "libcxx_guard": "_LIBCPP_HAS_THREADS",
        },
        {
            "name": "__cpp_lib_semaphore",
            "values": {"c++20": 201907},
            "headers": ["semaphore"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)",
            "libcxx_guard": "_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC",
        },
        {
            "name": "__cpp_lib_senders",
            "values": {"c++26": 202406},  # P2300R10 std::execution
            "headers": ["execution"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_shared_mutex",
            "values": {"c++17": 201505},
            "headers": ["shared_mutex"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_HAS_THREADS",
            "libcxx_guard": "_LIBCPP_HAS_THREADS",
        },
        {
            "name": "__cpp_lib_shared_ptr_arrays",
            "values": {"c++17": 201611, "c++20": 201707},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_shared_ptr_weak_type",
            "values": {"c++17": 201606},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_shared_timed_mutex",
            "values": {"c++14": 201402},
            "headers": ["shared_mutex"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_HAS_THREADS",
            "libcxx_guard": "_LIBCPP_HAS_THREADS",
        },
        {
            "name": "__cpp_lib_shift",
            "values": {"c++20": 201806},
            "headers": ["algorithm"],
        },
        {
            "name": "__cpp_lib_smart_ptr_for_overwrite",
            "values": {"c++20": 202002},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_smart_ptr_owner_equality",
            "values": {
                "c++26": 202306  # P1901R2 Enabling the Use of weak_ptr as Keys in Unordered Associative Containers
            },
            "headers": ["memory"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_source_location",
            "values": {"c++20": 201907},
            "headers": ["source_location"],
        },
        {
            "name": "__cpp_lib_span",
            "values": {
                "c++20": 202002,
                # "c++26": 202311,  # P2821R5 span.at()
                #          202311   # P2833R2 Freestanding Library: inout expected span
            },
            "headers": ["span"],
        },
        {
            "name": "__cpp_lib_span_at",
            "values": {"c++26": 202311},  # P2821R3 span.at()
            "headers": ["span"],
        },
        {
            "name": "__cpp_lib_span_initializer_list",
            "values": {"c++26": 202311},  # P2447R6 std::span over an initializer list
            "headers": ["span"],
        },
        {
            "name": "__cpp_lib_spanstream",
            "values": {"c++23": 202106},
            "headers": ["spanstream"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_ssize",
            "values": {"c++20": 201902},
            "headers": ["iterator"],
        },
        {
            "name": "__cpp_lib_sstream_from_string_view",
            "values": {
                "c++26": 202306  # P2495R3 Interfacing stringstreams with string_view
            },
            "headers": ["sstream"],
        },
        {
            "name": "__cpp_lib_stacktrace",
            "values": {"c++23": 202011},
            "headers": ["stacktrace"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_starts_ends_with",
            "values": {"c++20": 201711},
            "headers": ["string", "string_view"],
        },
        {
            "name": "__cpp_lib_stdatomic_h",
            "values": {"c++23": 202011},
            "headers": ["stdatomic.h"],
        },
        {
            "name": "__cpp_lib_string_contains",
            "values": {"c++23": 202011},
            "headers": ["string", "string_view"],
        },
        {
            "name": "__cpp_lib_string_resize_and_overwrite",
            "values": {"c++23": 202110},
            "headers": ["string"],
        },
        {
            "name": "__cpp_lib_string_udls",
            "values": {"c++14": 201304},
            "headers": ["string"],
        },
        {
            "name": "__cpp_lib_string_view",
            "values": {
                "c++17": 201606,
                "c++20": 201803,
                "c++26": 202403,  # P2591R5: Concatenation of strings and string views
            },
            "headers": ["string", "string_view"],
        },
        {
            "name": "__cpp_lib_submdspan",
            "values": {
                "c++26": 202306, # P2630R4: submdspan
                # "c++26": 202403, # P2642R6: Padded mdspan layouts
            },
            "headers": ["mdspan"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_syncbuf",
            "values": {"c++20": 201803},
            "headers": ["syncstream"],
            "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM",
            "libcxx_guard": "_LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM",
        },
        {
            "name": "__cpp_lib_text_encoding",
            "values": {
                "c++26": 202306  # P1885R12 Naming Text Encodings to Demystify Them
            },
            "headers": ["text_encoding"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_three_way_comparison",
            "values": {"c++20": 201907},
            "headers": ["compare"],
        },
        {
            "name": "__cpp_lib_to_address",
            "values": {"c++20": 201711},
            "headers": ["memory"],
        },
        {
            "name": "__cpp_lib_to_array",
            "values": {"c++20": 201907},
            "headers": ["array"],
        },
        {
            "name": "__cpp_lib_to_chars",
            "values": {
                "c++17": 201611,
                "c++26": 202306,  # P2497R0 Testing for success or failure of <charconv> functions
            },
            "headers": ["charconv"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_to_string",
            "values": {"c++26": 202306},  # P2587R3 to_string or not to_string
            "headers": ["string"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_to_underlying",
            "values": {"c++23": 202102},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_transformation_trait_aliases",
            "values": {"c++14": 201304},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_transparent_operators",
            "values": {"c++14": 201210, "c++17": 201510},
            "headers": ["functional", "memory"],
        },
        {
            "name": "__cpp_lib_tuple_element_t",
            "values": {"c++14": 201402},
            "headers": ["tuple"],
        },
        {
            "name": "__cpp_lib_tuple_like",
            "values": {
                "c++23": 202207,  # P2165R4 Compatibility between tuple, pair and tuple-like objects
                "c++26": 202311,  # P2819R2 Add tuple protocol to complex (implemented)
            },
            "headers": ["map", "tuple", "unordered_map", "utility"],
            "unimplemented": True,
        },
        {
            "name": "__cpp_lib_tuples_by_type",
            "values": {"c++14": 201304},
            "headers": ["tuple", "utility"],
        },
        {
            "name": "__cpp_lib_type_identity",
            "values": {"c++20": 201806},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_type_trait_variable_templates",
            "values": {"c++17": 201510},
            "headers": ["type_traits"],
        },
        {
            "name": "__cpp_lib_uncaught_exceptions",
            "values": {"c++17": 201411},
            "headers": ["exception"],
        },
        {
            "name": "__cpp_lib_unordered_map_try_emplace",
            "values": {"c++17": 201411},
            "headers": ["unordered_map"],
        },
        {
            "name": "__cpp_lib_unreachable",
            "values": {"c++23": 202202},
            "headers": ["utility"],
        },
        {
            "name": "__cpp_lib_unwrap_ref",
            "values": {"c++20": 201811},
            "headers": ["functional"],
        },
        {
            "name": "__cpp_lib_variant",
            "values": {
                "c++17": 202102,  # std::visit for classes derived from std::variant
                "c++20": 202106,  # P2231R1 Missing constexpr in std::optional and std::variant
                "c++26": 202306,  # P2637R3 Member visit
            },
            "headers": ["variant"],
        },
        {
            "name": "__cpp_lib_void_t",
            "values": {"c++17": 201411},
            "headers": ["type_traits"],
        },
    ]
]

assert feature_test_macros == sorted(feature_test_macros, key=lambda tc: tc["name"])
for tc in feature_test_macros:
    assert tc["headers"] == sorted(tc["headers"]), tc
    assert ("libcxx_guard" in tc) == ("test_suite_guard" in tc), tc
    valid_keys = ["name", "values", "headers", "libcxx_guard", "test_suite_guard", "unimplemented"]
    assert all(key in valid_keys for key in tc.keys()), tc

# Map from each header to the Lit annotations that should be used for
# tests that include that header.
#
# For example, when threads are not supported, any test that includes
# <thread> should be marked as UNSUPPORTED, because including <thread>
# is a hard error in that case.
lit_markup = {
    "barrier": ["UNSUPPORTED: no-threads"],
    "filesystem": ["UNSUPPORTED: no-filesystem"],
    "fstream": ["UNSUPPORTED: no-localization"],
    "iomanip": ["UNSUPPORTED: no-localization"],
    "ios": ["UNSUPPORTED: no-localization"],
    "iostream": ["UNSUPPORTED: no-localization"],
    "istream": ["UNSUPPORTED: no-localization"],
    "latch": ["UNSUPPORTED: no-threads"],
    "locale": ["UNSUPPORTED: no-localization"],
    "mutex": ["UNSUPPORTED: no-threads"],
    "ostream": ["UNSUPPORTED: no-localization"],
    "print": ["UNSUPPORTED: no-filesystem"],
    "regex": ["UNSUPPORTED: no-localization"],
    "semaphore": ["UNSUPPORTED: no-threads"],
    "shared_mutex": ["UNSUPPORTED: no-threads"],
    "sstream": ["UNSUPPORTED: no-localization"],
    "syncstream": ["UNSUPPORTED: no-localization"],
    "stdatomic.h": ["UNSUPPORTED: no-threads"],
    "stop_token": ["UNSUPPORTED: no-threads"],
    "thread": ["UNSUPPORTED: no-threads"],
}


def get_std_dialects():
    std_dialects = ["c++14", "c++17", "c++20", "c++23", "c++26"]
    return list(std_dialects)


def get_first_std(d):
    for s in get_std_dialects():
        if s in d.keys():
            return s
    return None


def get_last_std(d):
    rev_dialects = get_std_dialects()
    rev_dialects.reverse()
    for s in rev_dialects:
        if s in d.keys():
            return s
    return None


def get_std_before(d, std):
    std_dialects = get_std_dialects()
    candidates = std_dialects[0 : std_dialects.index(std)]
    candidates.reverse()
    for cand in candidates:
        if cand in d.keys():
            return cand
    return None


def get_value_before(d, std):
    new_std = get_std_before(d, std)
    if new_std is None:
        return None
    return d[new_std]


def get_for_std(d, std):
    # This catches the C++11 case for which there should be no defined feature
    # test macros.
    std_dialects = get_std_dialects()
    if std not in std_dialects:
        return None
    # Find the value for the newest C++ dialect between C++14 and std
    std_list = list(std_dialects[0 : std_dialects.index(std) + 1])
    std_list.reverse()
    for s in std_list:
        if s in d.keys():
            return d[s]
    return None


def get_std_number(std):
    return std.replace("c++", "")


"""
  Functions to produce the <version> header
"""


def produce_macros_definition_for_std(std):
    result = ""
    indent = 55
    for tc in feature_test_macros:
        if std not in tc["values"]:
            continue
        inner_indent = 1
        if "test_suite_guard" in tc.keys():
            result += "# if %s\n" % tc["libcxx_guard"]
            inner_indent += 2
        if get_value_before(tc["values"], std) is not None:
            assert "test_suite_guard" not in tc.keys()
            result += "# undef  %s\n" % tc["name"]
        line = "#%sdefine %s" % ((" " * inner_indent), tc["name"])
        line += " " * (indent - len(line))
        line += " %sL" % tc["values"][std]
        if "unimplemented" in tc.keys():
            line = "// " + line
        result += line
        result += "\n"
        if "test_suite_guard" in tc.keys():
            result += "# endif\n"
    return result.strip()


def produce_macros_definitions():
    macro_definition_template = """#if _LIBCPP_STD_VER >= {std_number}
{macro_definition}
#endif"""

    macros_definitions = []
    for std in get_std_dialects():
        macros_definitions.append(
            macro_definition_template.format(
                std_number=get_std_number(std),
                macro_definition=produce_macros_definition_for_std(std),
            )
        )

    return "\n\n".join(macros_definitions)


def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i : i + n]


def produce_version_synopsis():
    indent = 56
    header_indent = 56 + len("20XXYYL ")
    result = ""

    def indent_to(s, val):
        if len(s) >= val:
            return s
        s += " " * (val - len(s))
        return s

    line = indent_to("Macro name", indent) + "Value"
    line = indent_to(line, header_indent) + "Headers"
    result += line + "\n"
    for tc in feature_test_macros:
        prev_defined_std = get_last_std(tc["values"])
        line = "{name: <{indent}}{value}L ".format(
            name=tc["name"], indent=indent, value=tc["values"][prev_defined_std]
        )
        headers = list(tc["headers"])
        headers.remove("version")
        for chunk in chunks(headers, 3):
            line = indent_to(line, header_indent)
            chunk = ["<%s>" % header for header in chunk]
            line += " ".join(chunk)
            result += line
            result += "\n"
            line = ""
        while True:
            prev_defined_std = get_std_before(tc["values"], prev_defined_std)
            if prev_defined_std is None:
                break
            result += "%s%sL // %s\n" % (
                indent_to("", indent),
                tc["values"][prev_defined_std],
                prev_defined_std.replace("c++", "C++"),
            )
    return result


def produce_version_header():
    template = """// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_VERSIONH
#define _LIBCPP_VERSIONH

/*
  version synopsis

{synopsis}

*/

#if __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
#  include <__cxx03/version>
#else
#  include <__config>

#  if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#    pragma GCC system_header
#  endif

// clang-format off

{cxx_macros}

#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)

// clang-format on

#endif // _LIBCPP_VERSIONH
"""

    version_str = template.format(
        synopsis=produce_version_synopsis().strip(),
        cxx_macros=produce_macros_definitions(),
    )
    version_header_path = os.path.join(include_path, "version")
    with open(version_header_path, "w", newline="\n") as f:
        f.write(version_str)


"""
    Functions to produce test files
"""

test_types = {
    "undefined": """
#  ifdef {name}
#    error "{name} should not be defined before {std_first}"
#  endif
""",
    "test_suite_guard": """
#  if {test_suite_guard}
#    ifndef {name}
#      error "{name} should be defined in {std}"
#    endif
#    if {name} != {value}
#      error "{name} should have the value {value} in {std}"
#    endif
#  else
#    ifdef {name}
#      error "{name} should not be defined when the requirement '{test_suite_guard}' is not met!"
#    endif
#  endif
""",
    "unimplemented": """
#  if !defined(_LIBCPP_VERSION)
#    ifndef {name}
#      error "{name} should be defined in {std}"
#    endif
#    if {name} != {value}
#      error "{name} should have the value {value} in {std}"
#    endif
#  else
#    ifdef {name}
#      error "{name} should not be defined because it is unimplemented in libc++!"
#    endif
#  endif
""",
    "defined": """
#  ifndef {name}
#    error "{name} should be defined in {std}"
#  endif
#  if {name} != {value}
#    error "{name} should have the value {value} in {std}"
#  endif
""",
}


def generate_std_test(test_list, std):
    result = ""
    for tc in test_list:
        val = get_for_std(tc["values"], std)
        if val is not None:
            val = "%sL" % val
        if val is None:
            result += test_types["undefined"].format(
                name=tc["name"], std_first=get_first_std(tc["values"])
            )
        elif "unimplemented" in tc.keys():
            result += test_types["unimplemented"].format(
                name=tc["name"], value=val, std=std
            )
        elif "test_suite_guard" in tc.keys():
            result += test_types["test_suite_guard"].format(
                name=tc["name"],
                value=val,
                std=std,
                test_suite_guard=tc["test_suite_guard"],
            )
        else:
            result += test_types["defined"].format(name=tc["name"], value=val, std=std)
    return result.strip()


def generate_std_tests(test_list):
    std_tests_template = """#if TEST_STD_VER < {first_std_number}

{pre_std_test}

{other_std_tests}

#elif TEST_STD_VER > {penultimate_std_number}

{last_std_test}

#endif // TEST_STD_VER > {penultimate_std_number}"""

    std_dialects = get_std_dialects()

    other_std_tests = []
    for std in std_dialects[:-1]:
        other_std_tests.append("#elif TEST_STD_VER == " + get_std_number(std))
        other_std_tests.append(generate_std_test(test_list, std))

    std_tests = std_tests_template.format(
        first_std_number=get_std_number(std_dialects[0]),
        pre_std_test=generate_std_test(test_list, "c++11"),
        other_std_tests="\n\n".join(other_std_tests),
        penultimate_std_number=get_std_number(std_dialects[-2]),
        last_std_test=generate_std_test(test_list, std_dialects[-1]),
    )

    return std_tests


def generate_synopsis(test_list):
    max_name_len = max([len(tc["name"]) for tc in test_list])
    indent = max_name_len + 8

    def mk_line(prefix, suffix):
        return "{prefix: <{max_len}}{suffix}\n".format(
            prefix=prefix, suffix=suffix, max_len=indent
        )

    result = ""
    result += mk_line("/*  Constant", "Value")
    for tc in test_list:
        prefix = "    %s" % tc["name"]
        for std in [s for s in get_std_dialects() if s in tc["values"].keys()]:
            result += mk_line(
                prefix, "%sL [%s]" % (tc["values"][std], std.replace("c++", "C++"))
            )
            prefix = ""
    result += "*/"
    return result


def produce_tests():
    headers = set([h for tc in feature_test_macros for h in tc["headers"]])
    for h in headers:
        test_list = [tc for tc in feature_test_macros if h in tc["headers"]]
        if not has_header(h):
            for tc in test_list:
                assert "unimplemented" in tc.keys()
            continue
        markup = "\n".join("// " + tag for tag in lit_markup.get(h, []))
        test_body = """//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// WARNING: This test was generated by {script_name}
// and should not be edited manually.
{markup}
// <{header}>

// Test the feature test macros defined by <{header}>

// clang-format off

#include <{header}>
#include "test_macros.h"

{cxx_tests}

// clang-format on

""".format(
            script_name=script_name,
            header=h,
            markup=("\n{}\n".format(markup) if markup else ""),
            cxx_tests=generate_std_tests(test_list),
        )
        test_name = "{header}.version.compile.pass.cpp".format(header=h)
        out_path = os.path.join(macro_test_path, test_name)
        with open(out_path, "w", newline="\n") as f:
            f.write(test_body)


"""
    Produce documentation for the feature test macros
"""


def make_widths(grid):
    widths = []
    for i in range(0, len(grid[0])):
        cell_width = 2 + max(
            reduce(lambda x, y: x + y, [[len(row[i])] for row in grid], [])
        )
        widths += [cell_width]
    return widths


def create_table(grid, indent):
    indent_str = " " * indent
    col_widths = make_widths(grid)
    result = [indent_str + add_divider(col_widths, 2)]
    header_flag = 2
    for row_i in range(0, len(grid)):
        row = grid[row_i]
        line = indent_str + " ".join(
            [pad_cell(row[i], col_widths[i]) for i in range(0, len(row))]
        )
        result.append(line.rstrip())
        if row_i == len(grid) - 1:
            header_flag = 2
        if row[0].startswith("**"):
            header_flag += 1
        separator = indent_str + add_divider(col_widths, header_flag)
        result.append(separator.rstrip())
        header_flag = 0
    return "\n".join(result)


def add_divider(widths, header_flag):
    if header_flag == 3:
        return "=".join(["=" * w for w in widths])
    if header_flag == 2:
        return " ".join(["=" * w for w in widths])
    if header_flag == 1:
        return "-".join(["-" * w for w in widths])
    else:
        return " ".join(["-" * w for w in widths])


def pad_cell(s, length, left_align=True):
    padding = (length - len(s)) * " "
    return s + padding


def get_status_table():
    table = [["Macro Name", "Value"]]
    for std in get_std_dialects():
        table += [["**" + std.replace("c++", "C++") + "**", ""]]
        for tc in feature_test_macros:
            if std not in tc["values"].keys():
                continue
            value = "``%sL``" % tc["values"][std]
            if "unimplemented" in tc.keys():
                value = "*unimplemented*"
            table += [["``%s``" % tc["name"], value]]
    return table


def produce_docs():
    doc_str = """.. _FeatureTestMacroTable:

==========================
Feature Test Macro Support
==========================

.. contents::
   :local:

Overview
========

This file documents the feature test macros currently supported by libc++.

.. _feature-status:

Status
======

.. table:: Current Status
    :name: feature-status-table
    :widths: auto

{status_tables}

""".format(
        status_tables=create_table(get_status_table(), 4)
    )

    table_doc_path = os.path.join(docs_path, "FeatureTestMacroTable.rst")
    with open(table_doc_path, "w", newline="\n") as f:
        f.write(doc_str)


Std = NewType("Std", str)  # Standard version number
Ftm = NewType("Ftm", str)  # The name of a feature test macro
Value = NewType("Value", str)  # The value of a feature test macro including the L suffix

@dataclass
class Metadata:
    headers: List[str] = None
    available_since: Std = None
    test_suite_guard: str = None
    libcxx_guard: str = None


@dataclass
class VersionHeader:
    value: Value = None
    implemented: bool = None
    need_undef: bool = None
    condition: str = None


@dataclass
class FtmHeaderTest:
    value: Value = None
    implemented: bool = None
    condition: str = None

def get_ftms(
    data, std_dialects: List[Std], use_implemented_status: bool
) -> Dict[Ftm, Dict[Std, Optional[Value]]]:
    """Impementation for FeatureTestMacros.(standard|implemented)_ftms()."""
    result = dict()
    for feature in data:
        last = None
        entry = dict()
        implemented = True
        for std in std_dialects:
            if std not in feature["values"].keys():
                if last == None:
                    continue
                else:
                    entry[std] = last
            else:
                if implemented:
                    values = feature["values"][std]
                    assert len(values) > 0, f"{feature['name']}[{std}] has no entries"
                    for value in values:
                        papers = list(values[value])
                        assert (
                            len(papers) > 0
                        ), f"{feature['name']}[{std}][{value}] has no entries"
                        for paper in papers:
                            if use_implemented_status and not paper["implemented"]:
                                implemented = False
                                break
                        if implemented:
                            last = f"{value}L"
                        else:
                            break

                if last:
                    entry[std] = last
        result[feature["name"]] = entry

    return result


def generate_version_header_dialect_block(data: Dict[Ftm, VersionHeader]) -> str:
    """Generates the contents of the version header for a dialect.

    This generates the contents of a
      #if  _LIBCPP_STD_VER >= XY
      #endif // _LIBCPP_STD_VER >= XY
    block.
    """
    result = ""
    for element in data:
        for ftm, entry in element.items():
            if not entry.implemented:
                # When a FTM is not implemented don't add the guards
                # or undefine the (possibly) defined macro.
                result += f"// define {ftm} {entry.value}\n"
            else:
                need_undef = entry.need_undef
                if entry.condition:
                    result += f"#  if {entry.condition}\n"
                    if entry.need_undef:
                        result += f"#    undef {ftm}\n"
                    result += f"#    define {ftm} {entry.value}\n"
                    result += f"#  endif\n"
                else:
                    if entry.need_undef:
                        result += f"#  undef {ftm}\n"
                    result += f"#  define {ftm} {entry.value}\n"

    return result


def generate_version_header_implementation(
    data: Dict[Std, Dict[Ftm, VersionHeader]]
) -> str:
    """Generates the body of the version header."""

    template = """#if _LIBCPP_STD_VER >= {dialect}
{feature_test_macros}#endif // _LIBCPP_STD_VER >= {dialect}"""

    result = []
    for std, ftms in data.items():
        result.append(
            template.format(
                dialect=std,
                feature_test_macros=generate_version_header_dialect_block(ftms),
            )
        )

    return "\n\n".join(result)

#
# The templates used to create a FTM test file
#


ftm_header_test_file_contents = """//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// WARNING: This test was generated by {script_name}
// and should not be edited manually.

{lit_markup}// <{header}>

// Test the feature test macros defined by <{header}>

// clang-format off

{include}
#include "test_macros.h"
{data}

// clang-format on

"""


ftm_header_test_file_include_unconditional = """\
#include <{header}>\
"""

# On Windows the Windows SDK is on the include path, that means the MSVC STL
# headers can be found as well, tricking __has_include into thinking that
# libc++ provides the header. This means the test is also not executed when
# using this test with MSVC and MSVC STL.
ftm_header_test_file_include_conditional = """\
#if !defined(_WIN32) && __has_include(<{header}>)
#  include <{header}>
#endif\
"""

ftm_header_test_file_dialect_block = """
#{pp_if} TEST_STD_VER {operator} {dialect}
{tests}\
"""

class FeatureTestMacros:
    """Provides all feature-test macro (FTM) output components.

    The class has several generators to use the feature-test macros in libc++:
    - FTM status page
    - The version header and its tests

    This class is not intended to duplicate
    https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations#library-feature-test-macros
    SD-FeatureTest: Feature-Test Macros and Policies

    Historically libc++ did not list all papers affecting a FTM, the new data
    structure is able to do that. However there is no intention to add the
    historical data. After papers have been implemented this information can be
    removed. For example, __cpp_lib_format's value 201907 requires 3 papers,
    once implemented it can be reduced to 1 paper and remove the paper number
    and title. This would reduce the size of the data.

    The input data is stored in the following JSON format:
    [ # A list with multiple feature-test macro entries.
      {
        # required
        # The name of the feature test macro. These names should be unique and
        # sorted in the list.
        "name": "__cpp_lib_any",

        # required
        # A map with the value of the FTM based on the language standard. Only
        # the versions in which the value of the FTM changes are listed. For
        # example, this macro's value does not change in C++20 so it does not
        # list C++20. If it changes in C++26, it will have entries for C++17 and
        # C++26.
        "values": {

          # required
          # The language standard, also named dialect in this class.
          "c++17": {

            # required
            # The value of the feature test macro. This contains an array with
            # one or more papers that need to be implemented before this value
            # is considered implemented.
            "201606": [
              {
                # optional
                # Contains the paper number that is part of the FTM version.
                "number": "P0220R1",

                # optional
                # Contains the title of the paper that is part of the FTM
                # version.
                "title": "Adopt Library Fundamentals V1 TS Components for C++17"

                # required
                # The implementation status of the paper.
                "implemented": true
              }
            ]
          }
        },

        # required
        # A sorted list of headers that should provide the FTM. The header
        # <version> is automatically added to this list. This list could be
        # empty. For example, __cpp_lib_modules is only present in version.
        # Requiring the field makes it easier to detect accidental omission.
        "headers": [
          "any"
        ],

        # optional, required when libcxx_guard is present
        # This field is used only to generate the unit tests for the
        # feature-test macros. It can't depend on macros defined in <__config>
        # because the `test/std/` parts of the test suite are intended to be
        # portable to any C++ standard library implementation, not just libc++.
        # It may depend on
        # * macros defined by the compiler itself, or
        # * macros generated by CMake.
        # In some cases we add also depend on macros defined in
        # <__availability>.
        "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_PMR"

        # optional, required when test_suite_guard is present
        # This field is used only to guard the feature-test macro in
        # <version>. It may be the same as `test_suite_guard`, or it may
        # depend on macros defined in <__config>.
        "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_PMR"
      },
    ]
    """

    # The JSON data structure.
    __data = None
    # The headers not available in libc++.
    #
    # This could be detected based on FTM status, however that gives some odd
    # results. For example, at the moment __cpp_lib_constexpr_cmath is not
    # implemented, which flags `<cstdlib>` as not implemented. The availability
    # of headers is maintained for the C++ Standard Library modules.
    __unavailable_headers = None

    def __init__(self, filename: str, unavailable_headers: List[str]):
        """Initializes the class with the JSON data in the file 'filename'."""
        with open(filename) as f:
            self.__data = json.load(f)

        self.__unavailable_headers = set(unavailable_headers)

    @functools.cached_property
    def std_dialects(self) -> List[Std]:
        """Returns the C++ dialects avaiable.

        The available dialects are based on the 'c++xy' keys found the 'values'
        entries in '__data'. So when WG21 starts to feature-test macros for a
        future C++ Standard this dialect will automatically be available.

        The return value is a sorted list with the C++ dialects used. Since FTM
        were added in C++14 the list will not contain C++03 or C++11.
        """
        dialects = set()
        for feature in self.__data:
            keys = feature["values"].keys()
            assert len(keys) > 0, "'values' is empty"
            dialects |= keys

        return sorted(list(dialects))

    @functools.cached_property
    def standard_ftms(self) -> Dict[Ftm, Dict[Std, Optional[Value]]]:
        """Returns the FTM versions per dialect in the Standard.

        This function does not use the 'implemented' flag. The output contains
        the versions used in the Standard. When a FTM in libc++ is not
        implemented according to the Standard to output may opt to show the
        expected value.
        """
        return get_ftms(self.__data, self.std_dialects, False)

    @functools.cached_property
    def standard_library_headers(self) -> Set[str]:
        """Returns a list of headers that contain at least one FTM."""

        result = set()
        for value in self.ftm_metadata.values():
            for header in value.headers:
                result.add(header)

        return list(result)

    @functools.cached_property
    def implemented_ftms(self) -> Dict[Ftm, Dict[Std, Optional[Value]]]:
        """Returns the FTM versions per dialect implemented in libc++.

        Unlike `get_std_dialect_versions` this function uses the 'implemented'
        flag. This returns the actual implementation status in libc++.
        """

        return get_ftms(self.__data, self.std_dialects, True)


    def is_implemented(self, ftm: Ftm, std: Std) -> bool:
        """Has the FTM `ftm` been implemented in the dialect `std`?"""

        # When a paper for C++20 has not been implemented in libc++, then there will be no
        # FTM entry in implemented_ftms for C++23 and later. Similarly, a paper like <format>
        # has no entry in standard_ftms for e.g. C++11.
        if not std in self.implemented_ftms[ftm].keys() or not std in self.standard_ftms[ftm].keys():
            return False

        return self.implemented_ftms[ftm][std] == self.standard_ftms[ftm][std]

    @functools.cached_property
    def ftm_metadata(self) -> Dict[Ftm, Metadata]:
        """Returns the metadata of the FTMs defined in the Standard.

        The metadata does not depend on the C++ dialect used.
        """
        result = dict()
        for feature in self.__data:
            result[feature["name"]] = Metadata(
                feature["headers"],
                list(feature["values"])[0],
                feature.get("test_suite_guard", None),
                feature.get("libcxx_guard", None),
            )

        return result

    @property
    def version_header_implementation(self) -> Dict[Std, Dict[Ftm, VersionHeader]]:
        """Generates the body of the version header."""
        result = dict()
        for std in self.std_dialects:
            result[get_std_number(std)] = list()

        for ftm, values in self.standard_ftms.items():
            last_value = None
            last_entry = None
            for std, value in values.items():
                # When a newer Standard does not change the value of the macro
                # there is no need to redefine it with the same value.
                if last_value and value == last_value:
                    continue
                last_value = value

                implemented = self.is_implemented(ftm, std)
                entry = VersionHeader(
                    value,
                    implemented,
                    last_entry is not None and last_entry.implemented and implemented,
                    self.ftm_metadata[ftm].libcxx_guard,
                )

                last_entry = entry
                result[get_std_number(std)].append(dict({ftm: entry}))

        return result

    @property
    def version_header(self) -> str:
        """Generates the version header."""
        template = """// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_VERSIONH
#define _LIBCPP_VERSIONH

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#  pragma GCC system_header
#endif

{feature_test_macros}

#endif // _LIBCPP_VERSIONH
"""
        return template.format(
            feature_test_macros=generate_version_header_implementation(
                self.version_header_implementation
            )
        )

    def header_ftm_data(self, header: str) -> Dict[Std, List[Dict[Ftm, FtmHeaderTest]]]:
        """Generates the FTM information for a `header`."""

        result = dict()
        for std in self.std_dialects:
            result[get_std_number(std)] = list()

        for ftm, values in self.standard_ftms.items():
            if not header in self.ftm_metadata[ftm].headers:
                continue

            last_value = None
            last_entry = None

            for std in self.std_dialects:
                if not std in values.keys():
                    result[get_std_number(std)].append({ftm: None})
                    continue

                result[get_std_number(std)].append(
                        {
                            ftm: FtmHeaderTest(
                                values[std],
                                self.is_implemented(ftm, std),
                                self.ftm_metadata[ftm].test_suite_guard,
                            )
                        }
                )

        return result


    def generate_ftm_test(self, std: Std, ftm: Ftm, value: FtmHeaderTest) -> str:
        """Adds a single `ftm` test for C++ `std` based on the status information in `value`.

        When std == None this test is generating the TEST_STD_VER < MIN. Where
        MIN is the minimum version that has a FTM defined. (In the real data
        this is 14, since FTM have been introduced in C++14.)
        """

        ftm_unavailable_in_dialect = """
#  ifdef {ftm}
#    error "{ftm} should not be defined before {dialect}"
#  endif
"""

        ftm_not_implemented = """
#  if !defined(_LIBCPP_VERSION)
#    ifndef {ftm}
#      error "{ftm} should be defined in {dialect}"
#    endif
#    if {ftm} != {value}
#      error "{ftm} should have the value {value} in {dialect}"
#    endif
#  else
#    ifdef {ftm}
#      error "{ftm} should not be defined because it is unimplemented in libc++!"
#    endif
#  endif
"""

        ftm_conditionally_implemented = """
#  if {condition}
#    ifndef {ftm}
#      error "{ftm} should be defined in {dialect}"
#    endif
#    if {ftm} != {value}
#      error "{ftm} should have the value {value} in {dialect}"
#    endif
#  else
#    ifdef {ftm}
#      error "{ftm} should not be defined when the requirement '{condition}' is not met!"
#    endif
#  endif
"""

        ftm_implemented = """
#  ifndef {ftm}
#    error "{ftm} should be defined in {dialect}"
#  endif
#  if {ftm} != {value}
#    error "{ftm} should have the value {value} in {dialect}"
#  endif
"""

        if std == None or value == None:
            return ftm_unavailable_in_dialect.format(
                ftm=ftm, dialect=self.ftm_metadata[ftm].available_since
            )

        if not value.implemented:
            return ftm_not_implemented.format(
                ftm=ftm, value=value.value, dialect=std
            )

        if self.ftm_metadata[ftm].test_suite_guard:
            return ftm_conditionally_implemented.format(
                ftm=ftm,
                value=value.value,
                dialect=std,
                condition=self.ftm_metadata[ftm].test_suite_guard,
            )

        return ftm_implemented.format(ftm=ftm, value=value.value, dialect=std)

    def generate_header_test_dialect(
        self, std: Std, data: List[Dict[Ftm, FtmHeaderTest]]
    ) -> str:
        """Returns the body a single `std` for the FTM test of a `header`."""
        return "".join(
            self.generate_ftm_test(std, ftm, value)
            for element in data
            for ftm, value in element.items()
        )

    def generate_lit_markup(self, header:str) -> str:
        if not header in lit_markup.keys():
            return ""

        return "\n".join(f"// {markup}" for markup in lit_markup[header]) + "\n\n"

    def generate_header_test_file(self, header: str) -> str:
        """Returns the body for the FTM test of a `header`."""

        # FTM block before the first Standard that introduced them.
        # This test the macros are not available before this version.
        data = ftm_header_test_file_dialect_block.format(
                pp_if="if",
                operator="<",
                dialect=get_std_number(self.std_dialects[0]),
                tests=self.generate_header_test_dialect(
                    None, next(iter(self.header_ftm_data(header).values()))
                ),
            )

        # FTM for all Standards that have FTM defined.
        # Note in libc++ the TEST_STD_VER contains 99 for the Standard
        # in development, therefore the last entry uses a different #elif.
        data += "".join(
                ftm_header_test_file_dialect_block.format(
                    pp_if="elif",
                    operator="==" if std != get_std_number(self.std_dialects[-1]) else ">",
                    dialect=std
                    if std != get_std_number(self.std_dialects[-1])
                    else get_std_number(self.std_dialects[-2]),
                    tests=self.generate_header_test_dialect(f"c++{std}", values),
                )
                for std, values in self.header_ftm_data(header).items()
            )

        # The final #endif for the last #elif block.
        data += f"\n#endif // TEST_STD_VER > {get_std_number(self.std_dialects[-2])}"

        # Generate the test for the requested header.
        return ftm_header_test_file_contents.format(
            script_name=script_name,
            lit_markup=self.generate_lit_markup(header),
            header=header,
            include=(
                ftm_header_test_file_include_conditional.format(header=header)
                if header in self.__unavailable_headers
                else ftm_header_test_file_include_unconditional.format(header=header)
            ),
            data=data
        )

    def generate_header_test_directory(self, path: os.path) -> None:
        """Generates all FTM tests in the directory `path`."""

        if not os.path.exists(path):
            os.makedirs(path)

        for header in self.standard_library_headers:
            with open(
                os.path.join(path, f"{header}.version.compile.pass.cpp"),
                "w",
                newline="\n",
            ) as f:
                f.write(self.generate_header_test_file(header))


def main():
    produce_version_header()
    produce_tests()
    produce_docs()

    # Example how to use the new generator to generate the output.
    if False:
        ftm = FeatureTestMacros(
            os.path.join(
                source_root, "test", "libcxx", "feature_test_macro", "test_data.json"
            ), headers_not_available
        )
        version_header_path = os.path.join(include_path, "version")
        with open(version_header_path, "w", newline="\n") as f:
            f.write(ftm.version_header)

        ftm.generate_header_test_directory(macro_test_path)


if __name__ == "__main__":
    main()
