Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/axom/inlet/docs/sphinx/advanced_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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<Car>();

********************************
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
**********************************
Expand Down
4 changes: 4 additions & 0 deletions src/axom/inlet/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 )

Expand Down
155 changes: 155 additions & 0 deletions src/axom/inlet/examples/user_defined_variant.cpp
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <type_traits>
#include <variant>

// _inlet_user_defined_variant_start
struct Circle
{
double radius;
};

struct Box
{
double width;
double height;
};

using Shape = std::variant<Circle, Box>;

template <>
struct FromInlet<Circle>
{
Circle operator()(const axom::inlet::Container& input_data) { return {input_data["radius"]}; }
};

template <>
struct FromInlet<Box>
{
Box operator()(const axom::inlet::Container& input_data)
{
return {input_data["width"], input_data["height"]};
}
};

template <>
struct FromInlet<Shape>
{
// Shape is a std::variant, so it cannot be read through Container::get<T>().
// 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<decltype(concrete_shape)>;
if constexpr(std::is_same_v<ShapeType, Circle>)
{
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<axom::inlet::LuaReader>();
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<Shape> {}(inlet["shape"]);
// _inlet_user_defined_variant_access_end

printShape(shape);

return 0;
}