On Thu, 2018-01-25 at 09:08 +0100, Georg Chini wrote: > On 24.01.2018 01:40, Tanu Kaskinen wrote: > > On Mon, 2018-01-22 at 16:19 +0100, Georg Chini wrote: > > > On 21.01.2018 01:03, Tanu Kaskinen wrote: > > > > It looks like we're anyway going to > > > > need a bunch of "helper" functions, so there's not that much difference > > > > between our approaches, mainly list parsing details are affected > > > > somewhat. How to organize the helper functions is an open question, > > > > since you have so far opposed my pa_message idea, and haven't provided > > > > a proper alternative for keeping parameter writing state. > > > > > > Can you perhaps explain your idea again? Maybe I just opposed > > > because I did not understand what you proposed. I agree that we > > > need helper functions to read and write values and if you like to > > > include the braces around the parameter in the helper functions, > > > I can live with it as well. What do you mean by "keeping parameter > > > writing state"? > > > > I mean the state that pa_strbuf keeps. Apparently you want to make > > pa_strbuf available in libpulse and add helper functions in a different > > namespace that nevertheless operates on pa_strbuf. You never explained > > that explicitly, so it was not clear to me that you wanted that. > > > > I'm ok with that plan. I would prefer pa_message_params as the helper > > function namespace rather than just pa_message. > > What do you mean by "make strbuf available in libpulse"? Do I > need to move the strbuf code to libpulse? Yes if applications have to use it directly. Not if you add a wrapper object. > Currently the functions > are already used in libpulse by format.c, json.c and proplist.c. > I was thinking of hiding the use of strbuf behind some wrappers, > so that that you call for example pa_message_params_new() > instead of pa_strbuf_new() to avoid having a mixed name space. > Not sure if this is a good idea, because some of the functions > (like _new() or _write_string()) would really not be more than > simple wrappers. Did you plan to return pa_strbuf from pa_message_params_new(), or did you plan adding a new pa_message_params wrapper object? I would prefer a pa_message_params object (it would be quite close to my original pa_message proposal). If clients are going to interact with pa_strbuf directly, then I don't think duplicating already-existing functions from the pa_strbuf API makes sense. > Do you think we should have a new file (pulse/message-helper.c/.h) > for the helper functions or should they go into pulse/util.c? They should go into pulse/message-params.[ch]. > Which functions do we need for a start? > > Reading: > > pa_message_params_split_list() - splits the list, see below > pa_message_params_read_string() - reads a string > pa_message_params_read_double() - reads a float value > pa_message_params_read_int() - reads a long integer > pa_message_params_read_unsigned() - reads an unsigned long > pa_message_param_read_bool() - reads a boolean > > Anything else? That's already more than what we strictly need in the beginning, since we only use string values, but I'm not opposed at all to having these. We shouldn't specify that the integer functions deal with "long integers", because the server and the client may have different ideas about what that means. I'd have at least int64 and uint64 types, possibly also int32 and uint32. > Writing: > > pa_message_params_new() - returns a new strbuf > pa_message_params_to_string() - converts the strbuf to a string > pa_message_params_write_string() - writes a string > pa_message_params_write_escaped_string() - writes an escaped string > pa_message_params_write_double() - writes a float value > pa_message_params_write_int() - writes a long integer > pa_message_params_write_unsigned() - writes an unsigned long > pa_message_param_write_bool() - writes a boolean > > Anything else? Should _write_escaped_string() and _write_string() > be two different functions or should we pass a boolean to > _write_string() to indicate if we want escaping or not? If write_string() adds the curly braces around the value, then it doesn't make sense to have an unescaped version. If you don't want to have write_list_start() and write_list_end(), then we need a function for appending arbitrary stuff to the parameters (if clients have direct access to the pa_strbuf object, then that function already exists). > Concerning the problem of writing/reading floats: Do you think > it is OK if we search for "." or "," in the string and replace the > character with the decimal point character of the current locale > before conversion? This would work as long as the float is written > without thousand separator and we can ensure that in the > _write_float() function. What do you mean by "the string"? write_double() should not take a string as an argument. > Any further ideas/suggestions/restrictions that need to be taken > into account? > > > > > > > How should reading and writing nulls work? Writing nulls is not > > > > complicated, we could just have pa_message_write_null() (or since we > > > > probably will have a "raw string append" function, it's trivial to just > > > > append "{}" manually). Reading nulls is less obvious. > > > > > > > > All reading functions could have an "is_null" parameter: > > > > > > > > double double_var; > > > > int is_null; > > > > ret = pa_message_read_double(msg, &double_var, &is_null); > > > > > > > > If the is_null parameter isn't provided (i.e. is itself NULL), the > > > > function could fail if it encounters null. > > > > > > If we define that NULL is represented by the "special element" > > > {} it is very easy to determine if an element is empty, because > > > in this case, the length returned by the parsing function will be 0. > > > So I don't think we need to include an allow_null parameter in > > > the helper function (unless we treat a sequence of white spaces > > > like an empty element), because in cases where it is important, > > > we can check the length of the element before calling the helper. > > > Currently, parsing a value (including full error check) would look > > > something like > > > > > > ret = pa_split_message_parameter_string(list, max_length, &start_pos, > > > &length, NULL, &state); > > > if (ret == 0) > > > handle empty element; > > > if (ret == -1) > > > handle parse error; > > > > > > /* start_pos contains now the first valid character of the element > > > * after { and length the length of the element without closing > > > * bracket */ > > > > > > ret = pa_message_read_double(start_pos, length, &double_var) > > > > > > > > > Strings would not need a (read) helper function because they can > > > be directly returned by the parsing function. > > > > So you're proposing that reading a double should take two function > > calls. I think the two steps should be merged into one > > pa_message_params_read_double() function. Then error handling needs to > > be done only once, rather than twice per value. With this approach, > > however, the question arises how to deal with nulls. > > > > You are right, it should be folded into one function. I guess the > easiest way to handle NULL's is to have a special return value, > let's say 1 if parsing was OK, 0 if NULL was found and -1 on > parse error, similar to the split function. > > Actually I noticed, that the code above is not correct. After > pa_split_message_parameter_string() three checks need to > be performed: > > 1) ret = 0: This does NOT mean that the element is empty, > it means that the element is completely missing (end of list). > It can still be treated like an empty element. Why should it ever be treated like an empty element? Doesn't that make it impossible to distinguish between an empty element and end-of-list? > 2) ret= -1: Parse error > 3) length = 0: This means the element is empty. > > I'll rename the split function to pa_message_params_split_list() > to match the name space. Or do you have a better idea? pa_message_params_split_list() seems good name to me. The argument list of pa_message_params_split_list() is not what I had in mind when the idea of making it process the string in-place was first brought up. I don't even know what all those parameters in your example are used for. This is what I had in mind: int pa_message_params_split_list(char *params, char **value, void *state); params is the original string, value is the returned string and state is for remembering the current position in the original string (so internally it has type char*). state is initialized to NULL. Alternatively the params and state arguments can be merged: int pa_message_params_split_list(char **state, char **value); In this case the calling code has to initialize and use state like this: int ret; char *state = params; char *value; while ((ret = pa_message_params_split_list(&state, &value)) > 0) { ... } params in the first version and state in the second version is not declared const, because the function replaces the closing bracket of the read value with a null byte. That will make the returned value string null-terminated. The returned string could be declared const to make it clear that it doesn't need to be freed, although it would appear slighly odd, since it will point inside a non-const string that the caller passed. -- Tanu https://liberapay.com/tanuk https://www.patreon.com/tanuk