Closed dstroy0 closed 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.
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.
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...
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.
So any examples that you'd like to see I'll make an issue thread
I'm going to fork and merge for this since it's pretty extensive.
/**
* @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
};
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.
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)
{
}
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...
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.
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.
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));
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);
merged, n-length delims, and n-length start/stop pairs
You could link pull requests to an open issue by adding "closes #\
See more details at github docs because there are numerous ways to do this (some without using specific comments).
Thank you so much! I was trying to figure out how to do that and didn't know where to look.
Does that work if I do it in a commit description or comments as well?
I found the answer, thanks for pointing me in the right direction!
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.