dstroy0 / InputHandler

Arduino input handler
https://dstroy0.github.io/InputHandler/
GNU General Public License v3.0
1 stars 0 forks source link

UserInput constructor #35

Closed dstroy0 closed 2 years ago

dstroy0 commented 2 years ago

lets pass the UserInput constructor parameters in an object, and put the object in PROGMEM. Then instead of an insane long init list we can just memcpy_P the whole constructor parameters into a private object.

also lets allow more than one delimiter sequence and change the "c-string" delimiter type to a start/stop delimiter sequence.

so, like strtok, but n-length instead of single-char, and regex-like n-length start/stop match sequences but using memchr/memcmp without a lot of wrapping (performant).

thoughts?

I think after this I will have implemented nearly everything I can think of, so I really appreciate any suggestions for additional features before release.

2bndy5 commented 2 years ago

Sounds good to me. If this is the last thing on your todo list for this repo, are you planning on expanding the examples a bit?

In my re-vision attempt, I just used a length of the chars used in the delimiter instead of a c-string. That way I didn't have to keep a char* in memory for the delimiter of each command. Although I don't know how well that would fit into the idea here.

dstroy0 commented 2 years ago

Whatever you want to do sounds good, I'm having quite a bit of fun, and learning a lot. Edit: so I don't really mind how much time I have to spend on it.

2bndy5 commented 2 years ago

First library I published (on pypi) took almost half a year to get it into a stable state, learning a bunch along the way also...

dstroy0 commented 2 years ago

I really appreciate your help. There's a lot to learn all at once, even if I'm ok at picking up language that only helps so much.

dstroy0 commented 2 years ago

So any examples that you'd like to see I'll make an issue thread

dstroy0 commented 2 years ago

I'm going to fork and merge for this since it's pretty extensive.

dstroy0 commented 2 years ago
/**
 * @brief proposed UserInput ctor parameters struct
 *
 */
struct UserInput_ctor_prm
{
    char* output_buffer;                            ///< pointer to output char buffer
    size_t output_buffer_len;                       ///< len of output char buffer
    const char* process_name;                       ///< this process' name, can be NULL
    const char* input_single_control_char_sequence; ///< two char len sequence to input a control char
    size_t num_token_delimiters;                    ///< the number of token delimiters in delimiter_sequences
    const char** delimiter_sequences;               ///< string-literal "" delimiter sequence array
    size_t num_start_stop_sequences;                ///< num start/stop sequences
    const char** start_stop_sequence_pairs;         ///< start/stop sequences.  Match start, match end, copy what is between
};
dstroy0 commented 2 years ago

it's coming along nicely, this is what the struct looks like now:

/**
 * @brief UserInput input process parameters, constructor parameters
 *
 */
struct InputProcessParameters
{
    char process_name[UI_PROCESS_NAME_PGM_LEN];                                       ///< this process' name, can be NULL
    char end_of_line_term[UI_EOL_SEQ_PGM_LEN];                                        ///< end of line term
    char input_control_char_sequence[UI_INPUT_CONTROL_CHAR_SEQ_PGM_LEN];              ///< two char len sequence to input a control char
    size_t num_token_delimiters;                                                      ///< the number of token delimiters in delimiter_sequences
    char delimiter_sequences[UI_MAX_DELIM_SEQ][UI_DELIM_SEQ_PGM_LEN];                 ///< string-literal "" delimiter sequence array
    uint8_t delimiter_lens[UI_MAX_DELIM_SEQ];                                         ///< delimiter sequence lens
    size_t num_start_stop_sequences;                                                  ///< num start/stop sequences
    char start_stop_sequence_pairs[UI_MAX_START_STOP_SEQ][UI_START_STOP_SEQ_PGM_LEN]; ///< start/stop sequences.  Match start, match end, copy what is between
    uint8_t start_stop_sequence_lens[UI_MAX_START_STOP_SEQ];                          ///< start stop sequence lens
};

/**
 * @brief UserInput default InputProcessParameters
 *
 */
const PROGMEM InputProcessParameters _DEFAULT_UI_INPUT_PRM_[1] = {
    "",           ///< process name
    "\r\n",       ///< eol term
    "##",         ///< input control char sequence
    2,            ///< number of delimiter sequences
    {" ", ","},   ///< delimiter sequences
    {1, 1},       ///< delimiter sequence lens
    1,            ///< num start stop sequence pairs
    {"\"", "\""}, ///< start stop sequence pairs
    {1, 1}        ///< start stop sequence lens
};

GetCommandFromStream basic/advanced are working on the mega2560, I haven't tested other platforms.

dstroy0 commented 2 years ago

Invoking the default constructor causes all input parameters to be default NULL, which is what is tested for to load _DEFAULT_UI_INPUT_PRM_

UserInput inputHandler;
UserInput(const InputProcessParameters* input_prm = NULL, char* output_buffer = NULL, size_t output_buffer_len = 0)
        : _input_prm_((input_prm == NULL) ? *_DEFAULT_UI_INPUT_PRM_ : *input_prm),
          _output_buffer_(output_buffer),
          _output_enabled_((output_buffer == NULL) ? false : true),
          _output_buffer_len_(output_buffer_len),
          _output_buffer_bytes_left_(output_buffer_len),
          _term_len_(strlen_P(((input_prm == NULL) ? _DEFAULT_UI_INPUT_PRM_->end_of_line_term : input_prm->end_of_line_term))),
          _term_index_(0),
          _default_function_(NULL),
          _commands_head_(NULL),
          _commands_tail_(NULL),
          _commands_count_(0),
          _output_flag_(false),
          _token_buffer_(NULL),
          _data_pointers_(NULL),
          _data_pointers_index_(0),
          _data_pointers_index_max_(0),
          _rec_num_arg_strings_(0),
          _failed_on_subcommand_(0),
          _current_search_depth_(1),
          _null_('\0'),
          _neg_('-'),
          _dot_('.'),
          _stream_buffer_allocated_(false),
          _new_stream_data_(false),
          _stream_data_(NULL),
          _stream_data_index_(0),
          _begin_(false)
    {
    }
dstroy0 commented 2 years ago

What I would like to do now is fragment the struct further so that the longer sequences are only loaded into ram when they're needed for comp in their subfuncs.

So a DelimiterSequences and StartStopSequences pair of structs... Also, some other variable names to reinforce their place and purpose in InputProcessParameters like typedefs or something? Just to make it so that the interface accepts as few square things into round holes as possible if that makes sense...

dstroy0 commented 2 years ago

I've fragmented it like so:

/**
 * @brief InputProcessDelimiterSequences struct holds user defined input data delimiters
 *
 */
struct InputProcessDelimiterSequences
{
    size_t num_seq;                                      ///< the number of token delimiters in delimiter_sequences
    uint8_t delimiter_lens[UI_MAX_DELIM_SEQ];                         ///< delimiter sequence lens
    char delimiter_sequences[UI_MAX_DELIM_SEQ][UI_DELIM_SEQ_PGM_LEN]; ///< string-literal "" delimiter sequence array
};

/**
 * @brief InputProcessStartStopSequences struct holds regex-like start-stop match sequence pairs
 *
 */
struct InputProcessStartStopSequences
{
    size_t num_seq;                                             ///< num start/stop sequences
    uint8_t start_stop_sequence_lens[UI_MAX_START_STOP_SEQ];                          ///< start stop sequence lens
    char start_stop_sequence_pairs[UI_MAX_START_STOP_SEQ][UI_START_STOP_SEQ_PGM_LEN]; ///< start/stop sequences.  Match start, match end, copy what is between
};

/**
 * @brief IH_pname is a char array typedef the size of UI_PROCESS_NAME_PGM_LEN
 *
 */
typedef char IH_pname[UI_PROCESS_NAME_PGM_LEN];

/**
 * @brief IH_eol is a char array typedef the size of UI_EOL_SEQ_PGM_LEN
 *
 */
typedef char IH_eol[UI_EOL_SEQ_PGM_LEN];

/**
 * @brief IH_input_cc is a char array typedef the size of UI_INPUT_CONTROL_CHAR_SEQ_PGM_LEN
 *
 */
typedef char IH_input_cc[UI_INPUT_CONTROL_CHAR_SEQ_PGM_LEN];

/**
 * @brief UserInput input process parameters, constructor parameters
 *
 */
struct InputProcessParameters
{
    const IH_pname* pname;                           ///< this process' name, can be NULL
    const IH_eol* peol;                              ///< end of line term
    const IH_input_cc* pinputcc;                     ///< two char len sequence to input a control char
    const InputProcessDelimiterSequences* pdelimseq; ///< reference to InputProcessDelimiterSequences struct
    const InputProcessStartStopSequences* pststpseq; ///< reference to InputProcessStartStopSequences struct
};

I plan on loading InputProcessParameters into a private object to avoid having to memcpy_P it repeatedly in subfuncs.

Fragmenting it worked well, right now I'm loading the entire matrix for delimiters and start stop sequences in getTokens subfuncs, I would like to just memcmp_P a single element, which should be trivial.

And because it's a struct, and we're using aggregate init it will only accept references to those specific objects, the compiler says what type of object it expects, so it makes it pretty clear to the user that only one predefined type is accepted there.

dstroy0 commented 2 years ago

I also am going change the order of UserInput constructor variables so that InputProcessParameters comes last, so you can have a default init UserInput with or without output, leaving process configuration for more advanced projects.

dstroy0 commented 2 years ago

Now you can have a default init process with or without output:

// default init w/ no output
UserInput inputHandler;

// default init w/ output
char output_buffer[512]{};
UserInput inputHandler(output_buffer, buffsz(output_buffer));
dstroy0 commented 2 years ago

Or you can configure the input process:

/*
  this output buffer is formatted by UserInput's methods
  you have to empty it out yourself with
  OutputToStream()
*/
char output_buffer[650] = {'\0'}; //  output buffer

const PROGMEM IH_pname pname = "_test_";         ///< default process name
const PROGMEM IH_eol peol = "\r\n";        ///< default process eol characters
const PROGMEM IH_input_cc pinputcc = "##"; ///< default input control character sequence

const PROGMEM InputProcessDelimiterSequences pdelimseq = {
    2,         ///< number of delimiter sequences
    {1, 1},    ///< delimiter sequence lens
    {" ", ","} ///< delimiter sequences
};

const PROGMEM InputProcessStartStopSequences pststpseq = {
    1,           ///< num start stop sequence pairs
    {1, 1},      ///< start stop sequence lens
    {"\"", "\""} ///< start stop sequence pairs
};

const PROGMEM InputProcessParameters input_prm[1] = {
  &pname,
  &peol,
  &pinputcc,
  &pdelimseq,
  &pststpseq
};
UserInput inputHandler(output_buffer, buffsz(output_buffer), input_prm);
dstroy0 commented 2 years ago

merged, n-length delims, and n-length start/stop pairs

2bndy5 commented 2 years ago

You could link pull requests to an open issue by adding "closes #\" anywhere in the PR description or comments. When the linked PR is merged into the master branch (or main - whatever your default branch is named), it will auto-close the corresponding issue.

See more details at github docs because there are numerous ways to do this (some without using specific comments).

dstroy0 commented 2 years ago

Thank you so much! I was trying to figure out how to do that and didn't know where to look.

dstroy0 commented 2 years ago

Does that work if I do it in a commit description or comments as well?

dstroy0 commented 2 years ago

I found the answer, thanks for pointing me in the right direction!