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

feat (facade): simple argument parser #1747

Merged
merged 10 commits into from
Aug 31, 2023
Merged

Conversation

dranikpg
Copy link
Contributor

@dranikpg dranikpg commented Aug 26, 2023

Adds a simple parser for handling command arguments

See comments below

@dranikpg
Copy link
Contributor Author

dranikpg commented Aug 26, 2023

@kostasrim

With the search work I'm doing I need to parse a lot of parameters and the parsing code for it would grow over multiple hundreds of lines (given that I already have a few). Most of it is annoying boilerplate so I want to finally simplify it a little.

I want to add a proper argument parser instead of always looping manually, converting values, checking errors, etc. I want to use it for search and replace current mapping code in some command families.

Given that you'll be working on new commands as well with ACL, what do you think of it? What should be included?

I placed it in the redis_parser_test just because I was to lazy to add a separate target to compile some code, its just a playground for now.

My thoughts. Check this function out for example https://github.com/dranikpg/dragonfly/blob/search-named-return/src/server/search/search_family.cc#L97

What is an issue?

  1. Iteration -> instead of manually doing i++ and keeping track of it, the parser should
  2. Conversion -> instead of calling absl::SimpleAtoi the parser should
  3. Its annoying to place if (i + 1 > args.size()) { cntx->Reply.... break/continue/etc} blocks, instead we can finish the block with default values and then check if any errors were reported before
  4. The previous point is even more annoying with optional clauses where you can't calculate the size ahead.
  5. Optional clauses (like field_name [AS ALIAS]) should be easy to handle with the parser
  6. Cases where you would need to do a switch can be handled in one line as well

UPD: See the example in search family

@dranikpg dranikpg force-pushed the arg-parser branch 2 times, most recently from 33fdeaa to 099969e Compare August 26, 2023 19:19
Copy link
Contributor

@royjacobson royjacobson left a comment

Choose a reason for hiding this comment

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

I know this is still WIP, but still had a few comments to add :)

src/facade/arg_parser.h Outdated Show resolved Hide resolved
src/facade/arg_parser.h Outdated Show resolved Hide resolved
src/facade/arg_parser.h Outdated Show resolved Hide resolved
src/facade/arg_parser.h Outdated Show resolved Hide resolved
src/facade/arg_parser.h Outdated Show resolved Hide resolved
@dranikpg dranikpg force-pushed the arg-parser branch 2 times, most recently from c432b1b to 4e52094 Compare August 27, 2023 20:42
@dranikpg dranikpg requested a review from royjacobson August 27, 2023 20:48
@kostasrim
Copy link
Contributor

I like the approach and it does simplify a lot of the bounds checking etc.

Given that you'll be working on new commands as well with ACL, what do you think of it? What should be included?

The ACL commands are very easy to parse, only acl setuser is a "beast". I can take care of any extensions when required so you don't have to worry about this.

P.s. I will take more detailed look later today.


// Get optional error if occured
std::optional<ErrorInfo> Error() {
return std::move(error_);
Copy link
Contributor

Choose a reason for hiding this comment

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

safety suggestion:

Suggested change
return std::move(error_);
auto rv = std::move(error_);
error_.reset();
return rv;

then put CHECK(!error_.has_value()); in CmdArgParser's destructor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Today I learned that moving from an optional still keeps the value 😳

Copy link
Contributor

Choose a reason for hiding this comment

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

you made me double check 😆

a moved-from std::optional still contains a value, but the value itself is moved from.

Copy link
Contributor

@kostasrim kostasrim Aug 31, 2023

Choose a reason for hiding this comment

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

I actually double checked as well 🤣

Today I learned that moving from an optional still keeps the value

It's up to the value_type of what the state of that value will be.

For the optional, has_value will return true. This is on par with the std::move semantics:

Unless otherwise specified, all standard library objects that have been moved from are placed in a "valid but unspecified state", meaning the object's class invariants hold (so functions without preconditions, such as the assignment operator, can be safely used on the object after it was moved from):

@dranikpg
try return std::exchange(error_, {}) for a slick one liner 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's up to the value_type of what the state of that value will be.

I meant the state of the optional won't be nullopt

Signed-off-by: Vladislav Oleshko <[email protected]>
Signed-off-by: Vladislav Oleshko <[email protected]>
Signed-off-by: Vladislav Oleshko <[email protected]>
Signed-off-by: Vladislav Oleshko <[email protected]>
Signed-off-by: Vladislav Oleshko <[email protected]>
Comment on lines 111 to 119
// RETURN {num} [{ident} AS {name}...]
if (ArgS(args, i) == "RETURN") {
if (i + 1 >= args.size()) {
(*cntx)->SendError(kSyntaxErr);
return nullopt;
}

uint64_t num_fields = args.size();
if (!absl::SimpleAtoi(ArgS(args, i + 1), &num_fields)) {
(*cntx)->SendError(kSyntaxErr);
return nullopt;
}

i += 1;

if (parser.Check("RETURN").ExpectTail(1)) {
size_t num_fields = parser.Next().Int<size_t>();
alias_list = SearchParams::FieldAliasList{};
while (alias_list->size() < num_fields) {
if (++i >= args.size()) {
(*cntx)->SendError(kSyntaxErr);
return nullopt;
}

string_view ident = ArgS(args, i);
string_view alias = ident;

if (i + 2 < args.size() && absl::EqualsIgnoreCase(ArgS(args, i + 1), "AS")) {
alias = ArgS(args, i + 2);
i += 2;
}

string_view ident = parser.Next();
string_view alias = parser.Check("AS").ExpectTail(1) ? parser.Next() : string_view{ident};
alias_list->emplace_back(ident, alias);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

namespace facade {

// Utility class for easily parsing command options from argument lists.
struct CmdArgParser {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it actually ok to keep this header only?

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, probably not (noticing 2s after giving the approval)

Signed-off-by: Vladislav Oleshko <[email protected]>
Signed-off-by: Vladislav Oleshko <[email protected]>
@dranikpg dranikpg marked this pull request as ready for review August 30, 2023 06:20
Copy link
Contributor

@royjacobson royjacobson left a comment

Choose a reason for hiding this comment

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

This is great :)

Copy link
Contributor

@royjacobson royjacobson left a comment

Choose a reason for hiding this comment

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

just for moving things into a .cc file

Signed-off-by: Vladislav Oleshko <[email protected]>
Comment on lines +6 to +12

#include <absl/strings/ascii.h>
#include <absl/strings/match.h>
#include <absl/strings/numbers.h>

#include "base/logging.h"
#include "facade/error.h"
Copy link
Contributor Author

@dranikpg dranikpg Aug 30, 2023

Choose a reason for hiding this comment

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

I moved all function longer than 3 lines and all functions that have some sependencies (absl and our stuff)

@dranikpg dranikpg requested a review from royjacobson August 30, 2023 20:30
royjacobson
royjacobson previously approved these changes Aug 31, 2023
src/facade/cmd_arg_parser.h Outdated Show resolved Hide resolved
src/facade/cmd_arg_parser.h Outdated Show resolved Hide resolved
src/facade/cmd_arg_parser.h Show resolved Hide resolved
@kostasrim
Copy link
Contributor

sorry for my late input 👨‍🍳

Great work, I left some comments 🚀

Signed-off-by: Vladislav Oleshko <[email protected]>
@dranikpg dranikpg merged commit 47ef6d1 into dragonflydb:main Aug 31, 2023
@dranikpg dranikpg deleted the arg-parser branch September 15, 2023 06:37
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