diff --git a/src/axom/inlet/docs/sphinx/advanced_types.rst b/src/axom/inlet/docs/sphinx/advanced_types.rst index 14927bf9b6..5059917511 100644 --- a/src/axom/inlet/docs/sphinx/advanced_types.rst +++ b/src/axom/inlet/docs/sphinx/advanced_types.rst @@ -88,6 +88,44 @@ If a ``Car`` object as defined above is located at the path "car" within the inp Car car = inlet["car"].get(); +******************************** +Individual Variant Structs +******************************** + +When a single input entry may describe one of several user-defined struct types, +use a normal ``addStruct`` schema with a discriminator field and provide a +``FromInlet`` specialization that constructs the selected alternative. This is +useful for inputs that are variant-valued but are not arrays or dictionaries. + +For example, a single ``shape`` table can use a ``kind`` field to select the +concrete shape: + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_input_start + :end-before: _inlet_user_defined_variant_input_end + :language: lua + +The C++ type can store the concrete result in a ``std::variant`` while keeping +the Inlet-facing type as a user-defined struct: + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_start + :end-before: _inlet_user_defined_variant_end + :language: C++ + +Define the schema with ``addStruct`` and retrieve the result as the wrapper +user-defined type: + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_schema_usage_start + :end-before: _inlet_user_defined_variant_schema_usage_end + :language: C++ + +.. literalinclude:: ../../examples/user_defined_variant.cpp + :start-after: _inlet_user_defined_variant_access_start + :end-before: _inlet_user_defined_variant_access_end + :language: C++ + ********************************** Arrays and Dictionaries of Structs ********************************** diff --git a/src/axom/inlet/examples/CMakeLists.txt b/src/axom/inlet/examples/CMakeLists.txt index 38c7d9026e..593ce27bf1 100644 --- a/src/axom/inlet/examples/CMakeLists.txt +++ b/src/axom/inlet/examples/CMakeLists.txt @@ -23,6 +23,7 @@ blt_list_append( fields.cpp homogeneous_collections.cpp lua_library.cpp + user_defined_variant.cpp variant_struct_collections.cpp variant_collections.cpp containers.cpp @@ -89,6 +90,9 @@ if (SOL_FOUND) axom_add_test( NAME inlet_user_defined_type_ex COMMAND inlet_user_defined_type_ex --file ${CMAKE_CURRENT_LIST_DIR}/example1.lua) + axom_add_test( NAME inlet_user_defined_variant_ex + COMMAND inlet_user_defined_variant_ex ) + axom_add_test( NAME inlet_verification_ex COMMAND inlet_verification_ex ) diff --git a/src/axom/inlet/examples/user_defined_variant.cpp b/src/axom/inlet/examples/user_defined_variant.cpp new file mode 100644 index 0000000000..5fcc46fa6d --- /dev/null +++ b/src/axom/inlet/examples/user_defined_variant.cpp @@ -0,0 +1,155 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/inlet.hpp" +#include "axom/slic/core/SimpleLogger.hpp" +#include "axom/fmt.hpp" + +#include +#include +#include + +// _inlet_user_defined_variant_start +struct Circle +{ + double radius; +}; + +struct Box +{ + double width; + double height; +}; + +using Shape = std::variant; + +template <> +struct FromInlet +{ + Circle operator()(const axom::inlet::Container& input_data) { return {input_data["radius"]}; } +}; + +template <> +struct FromInlet +{ + Box operator()(const axom::inlet::Container& input_data) + { + return {input_data["width"], input_data["height"]}; + } +}; + +template <> +struct FromInlet +{ + // Shape is a std::variant, so it cannot be read through Container::get(). + // Use the public Proxy returned by inlet["shape"] to access its fields instead. + Shape operator()(const axom::inlet::Proxy& input_data) + { + const std::string kind = input_data["kind"]; + if(kind == "circle") + { + return Circle {input_data["radius"]}; + } + else if(kind == "box") + { + return Box {input_data["width"], input_data["height"]}; + } + + SLIC_ERROR(axom::fmt::format("Unknown shape discriminator '{}'", kind)); + return Box {0.0, 0.0}; + } +}; + +void defineShapeSchema(axom::inlet::Container& shape) +{ + shape.addString("kind", "Shape variant discriminator").required().validValues({"circle", "box"}); + shape.addDouble("radius", "Circle radius").required(false); + shape.addDouble("width", "Box width").required(false); + shape.addDouble("height", "Box height").required(false); + + shape.registerVerifier([](const axom::inlet::Container& input_data) { + if(!input_data.isUserProvided("kind")) + { + return false; + } + + const std::string kind = input_data["kind"]; + if(kind == "circle") + { + return input_data.isUserProvided("radius") && !input_data.isUserProvided("width") && + !input_data.isUserProvided("height"); + } + else if(kind == "box") + { + return input_data.isUserProvided("width") && input_data.isUserProvided("height") && + !input_data.isUserProvided("radius"); + } + + return false; + }); +} +// _inlet_user_defined_variant_end + +const std::string input = R"( + -- _inlet_user_defined_variant_input_start + shape = { + kind = "box", + width = 3.0, + height = 4.0 + } + -- _inlet_user_defined_variant_input_end +)"; + +void printShape(const Shape& shape) +{ + std::visit( + [](const auto& concrete_shape) { + using ShapeType = std::decay_t; + if constexpr(std::is_same_v) + { + SLIC_INFO(axom::fmt::format("circle radius = {}", concrete_shape.radius)); + } + else + { + SLIC_INFO(axom::fmt::format("box width = {}, height = {}", + concrete_shape.width, + concrete_shape.height)); + } + }, + shape); +} + +int main() +{ + // Initialize Axom's logger + axom::slic::SimpleLogger logger; + + // Create Inlet object with the Lua Reader and parse the input file snippet + auto lr = std::make_unique(); + lr->parseString(input); + axom::inlet::Inlet inlet(std::move(lr)); + + // Define the input file schema + // _inlet_user_defined_variant_schema_usage_start + auto& shape_schema = inlet.addStruct("shape", "A single user-defined variant"); + defineShapeSchema(shape_schema); + // _inlet_user_defined_variant_schema_usage_end + + // Verify input file validates against the schema + if(!inlet.verify()) + { + SLIC_ERROR("Inlet failed to verify against provided schema"); + } + + // Create a shape object from inlet container + // _inlet_user_defined_variant_access_start + const Shape shape = FromInlet {}(inlet["shape"]); + // _inlet_user_defined_variant_access_end + + printShape(shape); + + return 0; +}