[m-rev.] for review: fix a bug in duration parsing
Zoltan Somogyi
zoltan.somogyi at runbox.com
Mon Jul 6 17:42:13 AEST 2026
On Mon, 6 Jul 2026 16:53:50 +1000, Julien Fischer <jfischer at opturion.com> wrote:
> +:- pred read_char(char::out, list(char)::in, list(char)::out) is semidet.
> +
> +read_char(Char, [Char | Rest], Rest).
I would call this pred read_NEXT_char, because you later have
predicates that look for just a SPECIFIC char.
> +:- pred read_int_and_num_chars(int::out, int::out,
> + list(char)::in, list(char)::out) is det.
This does not read num chars beyond reading ints.
I would rename it to read_int_and_return_{num_char,len}.
> +read_int_and_num_chars(Val, N, !Chars) :-
> + read_int_and_num_chars_2(0, Val, 0, N, !Chars).
N should be something like Len, because Val and N are thoroughly
confusable. Or NumDigits, which you use later.
> +:- pred read_int_and_num_chars_2(int::in, int::out, int::in, int::out,
> + list(char)::in, list(char)::out) is det.
This pred name should be something like read_int_and_return_len_loop.
> +read_int_and_num_chars_2(!Val, !N, !Chars) :-
> + ( if
> + !.Chars = [Char | Rest],
> + decimal_digit_to_int(Char, Digit)
> + then
> + !:Val = !.Val * 10 + Digit,
> + read_int_and_num_chars_2(!Val, !.N + 1, !:N, Rest, !:Chars)
> + else
> + true
> + ).
I think that having both state variables and non-state variables
representing the different parts of a list (or any data structure)
is a recipe for confusion. Here, I think Rest could be just the next
value of !.Chars. In later predicates, I think it should be all separate
named variables.
> + % Read an optional leading minus sign. Sign is -1 if one is present (and is
> + % consumed), and 1 otherwise (consuming nothing).
> + %
> :- pred read_sign(int::out, list(char)::in, list(char)::out) is det.
s/Sign is/Set Sign to/
> read_days(Days, !Chars) :-
> read_int_and_char_or_zero(Days, 'D', !Chars).
I would flip the order of the first two args. 'D' is input; Days is output.
I think that is more important than following the order in the date string,
which is in the name anyway.
Also, I think a name such as read_int_and_GIVEN_char_or_RETURN_zero
would be clearer.
> -:- pred read_hours(int::out, list(char)::in, list(char)::out) is det.
> +:- pred read_hours(int::out, list(char)::in, list(char)::out) is semidet.
>
> read_hours(Hours, !Chars) :-
> read_int_and_char_or_zero(Hours, 'H', !Chars).
>
> -:- pred read_minutes(int::out, list(char)::in, list(char)::out) is det.
> +:- pred read_minutes(int::out, list(char)::in, list(char)::out) is semidet.
>
> read_minutes(Minutes, !Chars) :-
> read_int_and_char_or_zero(Minutes, 'M', !Chars).
>
> + % read_int_and_char_or_zero(Int, Char, !Chars):
> + %
> + % Read one optional "<integer><Char>" duration component from the front of
> + % !.Chars, where <integer> is a run of decimal digits.
> + % If !.Chars begins with Char preceded by at least one digit, then set Int
> + % to the value of those digits and !:Chars to the characters after Char.
> + % If !.Chars begins with Char with no digits before it, then fail.
> + % Otherwise, set Int to zero and leave !:Chars unchanged.
> + %
> +:- pred read_int_and_char_or_zero(int::out, char::in,
> + list(char)::in, list(char)::out) is semidet.
> +
> +read_int_and_char_or_zero(Int, Char, !Chars) :-
> + ( if
> + read_int_and_num_chars(Int0, NumDigits, !.Chars, Chars1),
> + Chars1 = [Char | Rest]
> + then
> + NumDigits > 0,
> + !:Chars = Rest,
> + Int = Int0
> + else
> + Int = 0
> + ).
This is an example where I think the state var !Chars hurts more than helps.
Apart from the above, the diff is fine.
Zoltan.
More information about the reviews
mailing list