Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option parsing #142

Merged
merged 9 commits into from
Sep 8, 2017
Merged

Option parsing #142

merged 9 commits into from
Sep 8, 2017

Conversation

wthrowe
Copy link
Member

@wthrowe wthrowe commented Aug 3, 2017

I have made many changes from the original code. Some of the highlights:

  • Most of the documentation and error output has been rewritten, including more checks for incorrect input.
  • The parser-enforced value checks have been simplified: bounds checks on individual container elements are gone, fixed size vectors are gone (but could still be obtained using min size=max size), and all remaining constraints are shown in the help output.
  • Formatting checks are done on option help. (These existed before but were not run due to a bug.)
  • MAKE_CREATABLE_FROM_YAML macro for non-factory stuff as discussed elsewhere.
  • A "forwarding" form for MAKE_CREATABLE_FROM_YAML to allow parsing simple types without using full option parsing (e.g., parsing an old-style Point class as Center: [1, 2, 3] or an enum as Norm: L2).
  • Constructors called from Factory and MCFY now take an OptionContext parameter that can be passed to the PARSE_ERROR macro to include information about the input file location in an error message (generally for doing more complex checks on input values).
  • OptionsList in factory/MCFY classes has been renamed to options because it should be camel case and it isn't a list in MCFY forwarding.
  • Added `Options<...>::apply

I've put some examples of parsing error output at http://astro.cornell.edu/~wthrowe/parsing_errors (executable at http://astro.cornell.edu/~wthrowe/parsing_errors.cpp).

@wthrowe
Copy link
Member Author

wthrowe commented Aug 4, 2017

Builds are failing because yaml-cpp is too old. The latest release can't handle non-copyable types. We need something more recent than November 2016.

@nilsdeppe
Copy link
Member

Okay, I can update the container we run on Travis

@wthrowe
Copy link
Member Author

wthrowe commented Aug 4, 2017

I've prepared a commit documenting the yaml-cpp version requirement, but I'll hold off pushing until it can trigger a Travis run with the update.

/// parser, for use in printing errors. A default-constructed
/// OptionContext is printed as an empty string. This struct is
/// primarily used as an argument to PARSE_ERROR for reporting input
/// file parsing errors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a brief explanation of what YAML::Mark is used for or link to the yaml-cpp documentation? The yaml-cpp documentation is not exactly super complete or easy to navigate. If I understand correctly, YAML::Mark used to store the line and column in the file where a parsing error occurred?

@@ -168,13 +168,13 @@ inline constexpr auto make_array_from_list() {

/// \ingroup ConstantExpressions
/// \brief Compute the length of a const char* at compile time
SPECTRE_ALWAYS_INLINE constexpr size_t cstring_length(const char* str) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the place that caused the error use a std::string instead which actually supports these features?

// See LICENSE.txt for details.

/// \file
/// Defined classes and functions that handle parsing of input parameters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Defines"


void append(const std::string& c) { context += c + ":\n"; }
};
inline std::ostream& operator<<(std::ostream& s, const OptionContext& c) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line

/// data and an OptionContext.
class Option_t {
public:
Option_t() = delete;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comment saying we don't want a default constructor


/// NOTE: This constructor overwrites the mark data in the supplied
/// context with the one from the node.
Option_t(YAML::Node node, OptionContext context = OptionContext())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit


/// Holds details of the implementation of Options
namespace Options_details {
template <typename S, typename U = void>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

U = void -> = cpp17::void_t<>

template <typename T>
void operator()(tmpl::type_<T> /*meta*/) {
std::ostringstream ss;
ss << " " << std::setw(max_label_size_) << std::left << T::label
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look at setw things

<< std::setw(0) << T::help << "\n\n";
value += ss.str();
}
value_type value{};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line above

}
}

template <typename T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to TypeTraits.hpp

} // namespace

TEST_CASE("Unit.Options.Defaulted.specified", "[Unit][Options]") {
const std::string yaml = "Defaulted: 20";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inline short strings

///
/// To use a factory, the base class (here `BaseClass`) should inherit
/// from Factory<BaseClass> and define a static method \code
/// static std::string class_id() { return "BaseClass"; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could experiment with using pretty_type to replace class_id()

/// from Factory<BaseClass> and define a static method \code
/// static std::string class_id() { return "BaseClass"; }
/// \endcode
/// and a type \code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\code to next line

/// input file.
///
/// To use a factory, the base class (here `BaseClass`) should inherit
/// from Factory<BaseClass> and define a static method \code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\code on next line

/// class method shown above.
/// 2) define static OptionString_t help containing class-specfic help
/// text
/// 3) define a type options as a typelist of option structs required
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

define a type alias options

static std::string help_derived();

template <
typename CreateLs,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateLs to CreateList

};

template <typename BaseClass>
template <typename CreateLs,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateLs

derived_opts.Context().append("While creating type " + id);
auto derived =
create_derived<typename BaseClass::creatable_classes>(id, derived_opts);
if (derived) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

derived != nullptr for clarity

<< std::setw(name_col) << ""
<< std::setw(help_col - name_col - 1) << T::class_id();
if (ss.str().size() >= help_col) {
ss << std::setw(0) << "\n" << std::setw(help_col - 1) << "";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to setw(0)

return true;
}
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// namespace YAML

std::string arg_;
};

struct OptionsVecArg {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up

static_assert(cpp17::is_same_v<decltype(T::lower_bound()), typename T::type>,
"Lower bound is not of the same type as the option.");
static_assert(not cpp17::is_same_v<typename T::type, bool>,
"Cannot set an lower bound for a bool.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an -> a

@wthrowe
Copy link
Member Author

wthrowe commented Aug 10, 2017

The new commits fall into a few groups:

  1. Minor fixes, largely based on above comments
  2. Removing class_id and option labels in favor of using the name of the class/struct. Assuming we don't run into any issues with compiler/library support, I think the class_id changes are a clear win. The option label changes sometimes force you to name option structs in ways that may conflict with other names. This can be avoided by wrapping options in an additional struct, so I think we're still OK, but the change is not completely downside free.
  3. Redesigning MAKE_CREATABLE_FROM_YAML, since in trying to write an example I found some serious problems with the previous version. (This does mean that T::options is always a typelist again, so it can be changed back to T::options_list if anyone cares.)

There was discussion of adding support for giving the option parser a small set of values allowed for an option. There is currently support for adding parsability to an enum (see example on MCFY) and for providing parsing errors from factory class constructors. The only additional feature that I think the parser could add would be displaying options in the help text, but we're trying to keep that short so I'm kind of leery of printing arbitrary lists of values there.

@wthrowe
Copy link
Member Author

wthrowe commented Aug 10, 2017

And looks like yaml-cpp is broken on the clang Travis setups.

@wthrowe
Copy link
Member Author

wthrowe commented Aug 10, 2017

Trying to clean up the doxygen warnings, I've hit two issues:

  1. It looks like doxygen doesn't understand decltype(auto) return type.
  2. One of the corrected \snippet lines is over 80 characters, and doxygen doesn't allow the line to be broken. I guess we just have to use short tag names in files with long names?

context_.mark = node_.Mark();
}

/// Convert to an object ot type `T`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ot -> of

for (auto it = options.begin(); it != options.end(); ++it) {
const auto key = it->first.as<std::string>();
const auto value = it->second;
for (const auto& kv : node) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while I've used kv for the range-based key-value pair, others ( @gsb76 , @osheamonn ) have voiced other opinions

* \brief Extract the "base name" from a name, that is, the name
* without template parameters or scopes.
*/
std::string extract_base_name(std::string name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot think of an alternative at the moment, but when I first saw base_name I thought about the name of a base class...

/// returns a unique id for the derived class, similar to the base
/// class method shown above.
/// 2) define static OptionString_t help containing class-specfic help
/// 1) define static OptionString_t help containing class-specfic help
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doxygen does have a syntax for enumerated list (having to relabel the comments made me think of this...)

/// the `options` and an OptionContext.
/// A class to be created must be default constructible and move
/// assignable. If create_from_yaml is not specialized for the class
/// it must additionally declare a tnpl::list of Options-style option
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tnpl -> tmpl

@wthrowe
Copy link
Member Author

wthrowe commented Aug 11, 2017

Speculation on the Travis yaml-cpp problem: using a libstdc++-linked yaml-cpp for a libc++ build.

@nilsdeppe
Copy link
Member

Yes, that is what it is.

/// Parse an Option_t to obtain options and their values.
void parse(const Option_t& options);

/// Parse an file containing options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parse a

//@}

//@{
/// Check for an iterable type that the size is above the upper bound
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

above -> below

/// If the options has a lower bound, check it is satisfied.
///
/// Note: Lower bounds are >=, not just >.
/// \tparam T the option "type"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"type" -> something clearer

/// If the options has a upper bound, check it is satisfied.
///
/// Note: Upper bounds are <=, not just <.
/// \tparam T the option "type"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


// \cond
// Doxygen is confused by decltype(auto)
template <typename OptionsList>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OptionsList -> OptionList

};
/// [options_example_scalar_struct]
} // namespace

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test that a out-of-bounds default fails

@kidder kidder mentioned this pull request Sep 1, 2017
Copy link
Member

@nilsdeppe nilsdeppe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

squash

@@ -232,7 +232,10 @@ class Options {
void parse(const YAML::Node& node);

//@{
/// Check for an iterable type that the size is above the lower bound
/// Check for an that the size is not smaller than the lower bound
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that the....

@@ -247,7 +250,10 @@ class Options {
//@}

//@{
/// Check for an iterable type that the size is below the upper bound
/// Check for an that the size is not larger than the upper bound
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again

@@ -263,6 +269,8 @@ class Options {

//@{
/// Returns the default value or errors if there is no default.
///
/// \tparam T the option struct
template <typename T,
std::enable_if_t<Options_details::has_default<T>::value>* = nullptr>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change all to Requires<...> = nullptr

@kidder
Copy link
Member

kidder commented Sep 4, 2017

Will, can you rebase on the latest commit that attempted to fix Travis stuff

@nilsdeppe
Copy link
Member

I'll look at what the heck Travis is doing

@nilsdeppe
Copy link
Member

Could you rebase to get the new Travis things that uses libstdc++ with Clang?

@nilsdeppe
Copy link
Member

@wthrowe could you try applying the changes in this commit nilsdeppe@0dfcc15 ?

@kidder kidder merged commit e69a135 into sxs-collaboration:develop Sep 8, 2017
@codecov-io
Copy link

codecov-io commented Sep 8, 2017

Codecov Report

Merging #142 into develop will decrease coverage by 0.34%.
The diff coverage is 93.95%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #142      +/-   ##
===========================================
- Coverage    98.93%   98.59%   -0.35%     
===========================================
  Files          110      115       +5     
  Lines         3766     4047     +281     
===========================================
+ Hits          3726     3990     +264     
- Misses          40       57      +17
Impacted Files Coverage Δ
src/Utilities/PrettyType.cpp 100% <100%> (ø)
src/Utilities/PrettyType.hpp 100% <100%> (ø) ⬆️
src/Options/MakeCreatableFromYaml.hpp 100% <100%> (ø)
src/Options/Options.hpp 91.89% <91.89%> (ø)
src/Options/Factory.hpp 94.54% <94.54%> (ø)
src/Options/OptionsDetails.hpp 96.77% <96.77%> (ø)
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 89f4671...b660505. Read the comment docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants